From 389e8437e7dd548db5ce8bb9bcc712e1b9c1d063 Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:39:51 +0100 Subject: [PATCH 1/8] Require `laminas-translator` and drop dev requirement on `laminas-i18n` Signed-off-by: George Steel --- composer.json | 2 +- composer.lock | 188 +++++++++++++++++++++----------------------------- 2 files changed, 79 insertions(+), 111 deletions(-) diff --git a/composer.json b/composer.json index 70e68cf4..dff5a079 100644 --- a/composer.json +++ b/composer.json @@ -34,11 +34,11 @@ "php": "~8.1.0 || ~8.2.0 || ~8.3.0", "laminas/laminas-servicemanager": "^3.21.0", "laminas/laminas-stdlib": "^3.13", + "laminas/laminas-translator": "^1.0", "psr/http-message": "^1.0.1 || ^2.0.0" }, "require-dev": { "laminas/laminas-coding-standard": "^2.5", - "laminas/laminas-i18n": "^2.26.0", "phpunit/phpunit": "^10.5.20", "psalm/plugin-phpunit": "^0.19.0", "psr/http-client": "^1.0.3", diff --git a/composer.lock b/composer.lock index 9e488df1..230c5cba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "75ff361798e13f2b1b1afb69d10b4fba", + "content-hash": "f60ed8d25850730dee1fed5ebbac79cf", "packages": [ { "name": "laminas/laminas-servicemanager", @@ -155,6 +155,59 @@ ], "time": "2024-01-19T12:39:49+00:00" }, + { + "name": "laminas/laminas-translator", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-translator.git", + "reference": "86d176c01a96b0ef205192b776cb69e8d4ca06b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-translator/zipball/86d176c01a96b0ef205192b776cb69e8d4ca06b1", + "reference": "86d176c01a96b0ef205192b776cb69e8d4ca06b1", + "shasum": "" + }, + "require": { + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.5.0", + "vimeo/psalm": "^5.24.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Translator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Interfaces for the Translator component of laminas-i18n", + "homepage": "https://laminas.dev", + "keywords": [ + "i18n", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-i18n/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-translator/issues", + "rss": "https://github.com/laminas/laminas-translator/releases.atom", + "source": "https://github.com/laminas/laminas-translator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2024-06-18T15:09:24+00:00" + }, { "name": "psr/container", "version": "1.1.2", @@ -1039,91 +1092,6 @@ ], "time": "2023-01-05T15:53:40+00:00" }, - { - "name": "laminas/laminas-i18n", - "version": "2.26.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-i18n.git", - "reference": "01738410cb263994d1d192861f642387e7e12ace" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/01738410cb263994d1d192861f642387e7e12ace", - "reference": "01738410cb263994d1d192861f642387e7e12ace", - "shasum": "" - }, - "require": { - "ext-intl": "*", - "laminas/laminas-servicemanager": "^3.21.0", - "laminas/laminas-stdlib": "^3.0", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" - }, - "conflict": { - "laminas/laminas-view": "<2.20.0", - "zendframework/zend-i18n": "*" - }, - "require-dev": { - "laminas/laminas-cache": "^3.12.0", - "laminas/laminas-cache-storage-adapter-memory": "^2.3.0", - "laminas/laminas-cache-storage-deprecated-factory": "^1.2", - "laminas/laminas-coding-standard": "~2.5.0", - "laminas/laminas-config": "^3.9.0", - "laminas/laminas-eventmanager": "^3.13", - "laminas/laminas-filter": "^2.34", - "laminas/laminas-validator": "^2.46", - "laminas/laminas-view": "^2.33", - "phpunit/phpunit": "^10.5.5", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.18.0" - }, - "suggest": { - "laminas/laminas-cache": "You should install this package to cache the translations", - "laminas/laminas-config": "You should install this package to use the INI translation format", - "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", - "laminas/laminas-filter": "You should install this package to use the provided filters", - "laminas/laminas-i18n-resources": "This package provides validator and captcha translations", - "laminas/laminas-validator": "You should install this package to use the provided validators", - "laminas/laminas-view": "You should install this package to use the provided view helpers" - }, - "type": "library", - "extra": { - "laminas": { - "component": "Laminas\\I18n", - "config-provider": "Laminas\\I18n\\ConfigProvider" - } - }, - "autoload": { - "psr-4": { - "Laminas\\I18n\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Provide translations for your application, and filter and validate internationalized values", - "homepage": "https://laminas.dev", - "keywords": [ - "i18n", - "laminas" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-i18n/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-i18n/issues", - "rss": "https://github.com/laminas/laminas-i18n/releases.atom", - "source": "https://github.com/laminas/laminas-i18n" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2024-01-04T13:49:00+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.12.0", @@ -1620,16 +1588,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.14", + "version": "10.1.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b" + "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", - "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", "shasum": "" }, "require": { @@ -1686,7 +1654,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" }, "funding": [ { @@ -1694,7 +1662,7 @@ "type": "github" } ], - "time": "2024-03-12T15:33:41+00:00" + "time": "2024-06-29T08:25:15+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3384,16 +3352,16 @@ }, { "name": "symfony/console", - "version": "v6.4.8", + "version": "v6.4.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91" + "reference": "6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/be5854cee0e8c7b110f00d695d11debdfa1a2a91", - "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91", + "url": "https://api.github.com/repos/symfony/console/zipball/6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9", + "reference": "6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9", "shasum": "" }, "require": { @@ -3458,7 +3426,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.8" + "source": "https://github.com/symfony/console/tree/v6.4.9" }, "funding": [ { @@ -3474,7 +3442,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-06-28T09:49:33+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3545,16 +3513,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.8", + "version": "v6.4.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3" + "reference": "b51ef8059159330b74a4d52f68e671033c0fe463" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d37529150e7081c51b3c5d5718c55a04a9503f3", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463", + "reference": "b51ef8059159330b74a4d52f68e671033c0fe463", "shasum": "" }, "require": { @@ -3591,7 +3559,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.8" + "source": "https://github.com/symfony/filesystem/tree/v6.4.9" }, "funding": [ { @@ -3607,7 +3575,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-06-28T09:49:33+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4012,16 +3980,16 @@ }, { "name": "symfony/string", - "version": "v6.4.8", + "version": "v6.4.9", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d" + "reference": "76792dbd99690a5ebef8050d9206c60c59e681d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a147c0f826c4a1f3afb763ab8e009e37c877a44d", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d", + "url": "https://api.github.com/repos/symfony/string/zipball/76792dbd99690a5ebef8050d9206c60c59e681d7", + "reference": "76792dbd99690a5ebef8050d9206c60c59e681d7", "shasum": "" }, "require": { @@ -4078,7 +4046,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.8" + "source": "https://github.com/symfony/string/tree/v6.4.9" }, "funding": [ { @@ -4094,7 +4062,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-06-28T09:25:38+00:00" }, { "name": "theseer/tokenizer", From b26c05c9add23cd0df8d8a6832929e8ec6ee6696 Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:41:38 +0100 Subject: [PATCH 2/8] Refactor the `TranslatorAwareInterface` to use `Laminas\Translator` Adds parameter and return types Signed-off-by: George Steel --- src/Translator/TranslatorAwareInterface.php | 36 +++++++-------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/Translator/TranslatorAwareInterface.php b/src/Translator/TranslatorAwareInterface.php index b3789600..97bd6c67 100644 --- a/src/Translator/TranslatorAwareInterface.php +++ b/src/Translator/TranslatorAwareInterface.php @@ -4,61 +4,47 @@ namespace Laminas\Validator\Translator; +use Laminas\Translator\TranslatorInterface; + interface TranslatorAwareInterface { /** * Sets translator to use in helper * - * @param TranslatorInterface $translator [optional] translator. + * @param TranslatorInterface|null $translator [optional] translator. * Default is null, which sets no translator. - * @param string $textDomain [optional] text domain + * @param string|null $textDomain [optional] text domain * Default is null, which skips setTranslatorTextDomain - * @return self */ - public function setTranslator(?TranslatorInterface $translator = null, $textDomain = null); + public function setTranslator(?TranslatorInterface $translator = null, ?string $textDomain = null): void; /** * Returns translator used in object - * - * @return TranslatorInterface|null */ - public function getTranslator(); + public function getTranslator(): ?TranslatorInterface; /** * Checks if the object has a translator - * - * @return bool */ - public function hasTranslator(); + public function hasTranslator(): bool; /** * Sets whether translator is enabled and should be used - * - * @param bool $enabled [optional] whether translator should be used. - * Default is true. - * @return self */ - public function setTranslatorEnabled($enabled = true); + public function setTranslatorEnabled(bool $enabled = true): void; /** * Returns whether translator is enabled and should be used - * - * @return bool */ - public function isTranslatorEnabled(); + public function isTranslatorEnabled(): bool; /** * Set translation text domain - * - * @param string $textDomain - * @return TranslatorAwareInterface */ - public function setTranslatorTextDomain($textDomain = 'default'); + public function setTranslatorTextDomain(string $textDomain = 'default'): void; /** * Return the translation text domain - * - * @return string */ - public function getTranslatorTextDomain(); + public function getTranslatorTextDomain(): string; } From a8f31ab477b784fc3d89864829bc6f48d3d2c3b4 Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:43:13 +0100 Subject: [PATCH 3/8] Add types to existing setters and getters and migrate to `Laminas\Translator` Signed-off-by: George Steel --- src/AbstractValidator.php | 122 ++++++++++++-------------------------- src/EmailAddress.php | 13 ++-- src/Hostname.php | 7 ++- 3 files changed, 50 insertions(+), 92 deletions(-) diff --git a/src/AbstractValidator.php b/src/AbstractValidator.php index b36b0c98..b52f3503 100644 --- a/src/AbstractValidator.php +++ b/src/AbstractValidator.php @@ -5,6 +5,7 @@ namespace Laminas\Validator; use Laminas\Stdlib\ArrayUtils; +use Laminas\Translator\TranslatorInterface; use Traversable; use function array_key_exists; @@ -31,7 +32,7 @@ * messages: array, * messageTemplates: array, * messageVariables: array, - * translator: Translator\TranslatorInterface|null, + * translator: TranslatorInterface|null, * translatorTextDomain: string|null, * translatorEnabled: bool, * valueObscured: bool, @@ -53,17 +54,13 @@ abstract class AbstractValidator implements /** * Default translation object for all validate objects - * - * @var Translator\TranslatorInterface */ - protected static $defaultTranslator; + protected static ?TranslatorInterface $defaultTranslator = null; /** * Default text domain to be used with translator - * - * @var string */ - protected static $defaultTranslatorTextDomain = 'default'; + protected static string $defaultTranslatorTextDomain = 'default'; /** * Limits the maximum returned length of an error message @@ -73,11 +70,11 @@ abstract class AbstractValidator implements protected static $messageLength = -1; /** @var AbstractOptions&array */ - protected $abstractOptions = [ + protected array $abstractOptions = [ 'messages' => [], // Array of validation failure messages 'messageTemplates' => [], // Array of validation failure message templates 'messageVariables' => [], // Array of additional variables available for validation failure messages - 'translator' => null, // Translation object to used -> Translator\TranslatorInterface + 'translator' => null, // Translator instance to use -> TranslatorInterface 'translatorTextDomain' => null, // Translation text domain 'translatorEnabled' => true, // Is translation enabled? 'valueObscured' => false, // Flag indicating whether value should be obfuscated in error messages @@ -363,12 +360,7 @@ protected function createMessage($messageKey, $value) return $message; } - /** - * @param string|null $messageKey - * @param null|string|array|object $value OPTIONAL - * @return void - */ - protected function error($messageKey, $value = null) + protected function error(string $messageKey, mixed $value = null): void { if ($messageKey === null) { $keys = array_keys($this->abstractOptions['messageTemplates']); @@ -434,84 +426,69 @@ public function isValueObscured() } /** - * Set translation object - * - * @param string $textDomain (optional) - * @return $this - * @throws Exception\InvalidArgumentException + * Set the translator for this instance */ - public function setTranslator(?Translator\TranslatorInterface $translator = null, $textDomain = null) + public function setTranslator(?TranslatorInterface $translator = null, ?string $textDomain = null): void { $this->abstractOptions['translator'] = $translator; if (null !== $textDomain) { $this->setTranslatorTextDomain($textDomain); } - return $this; } /** - * Return translation object - * - * @return Translator\TranslatorInterface|null + * Return the translator for this instance */ - public function getTranslator() + public function getTranslator(): ?TranslatorInterface { if (! $this->isTranslatorEnabled()) { return null; } - if (null === $this->abstractOptions['translator']) { - $this->abstractOptions['translator'] = self::getDefaultTranslator(); + $translator = $this->abstractOptions['translator'] ?? null; + if ($translator instanceof TranslatorInterface) { + return $translator; } - return $this->abstractOptions['translator']; + return self::$defaultTranslator; } /** * Does this validator have its own specific translator? - * - * @return bool */ - public function hasTranslator() + public function hasTranslator(): bool { - return (bool) $this->abstractOptions['translator']; + return $this->abstractOptions['translator'] instanceof TranslatorInterface; } /** * Set translation text domain - * - * @param string $textDomain - * @return $this */ - public function setTranslatorTextDomain($textDomain = 'default') + public function setTranslatorTextDomain(string $textDomain = 'default'): void { $this->abstractOptions['translatorTextDomain'] = $textDomain; - return $this; } /** * Return the translation text domain - * - * @return string */ - public function getTranslatorTextDomain() + public function getTranslatorTextDomain(): string { if (null === $this->abstractOptions['translatorTextDomain']) { $this->abstractOptions['translatorTextDomain'] = self::getDefaultTranslatorTextDomain(); } + return $this->abstractOptions['translatorTextDomain']; } /** - * Set default translation object for all validate objects - * - * @param string $textDomain (optional) - * @return void - * @throws Exception\InvalidArgumentException + * Set the default, static translator for all validators */ - public static function setDefaultTranslator(?Translator\TranslatorInterface $translator = null, $textDomain = null) - { + public static function setDefaultTranslator( + ?TranslatorInterface $translator = null, + ?string $textDomain = null, + ): void { static::$defaultTranslator = $translator; if (null !== $textDomain) { self::setDefaultTranslatorTextDomain($textDomain); @@ -519,86 +496,65 @@ public static function setDefaultTranslator(?Translator\TranslatorInterface $tra } /** - * Get default translation object for all validate objects - * - * @return Translator\TranslatorInterface|null + * Return the default translator */ - public static function getDefaultTranslator() + public static function getDefaultTranslator(): ?TranslatorInterface { return static::$defaultTranslator; } /** - * Is there a default translation object set? - * - * @return bool + * Is there a default translator available */ - public static function hasDefaultTranslator() + public static function hasDefaultTranslator(): bool { - return (bool) static::$defaultTranslator; + return self::$defaultTranslator !== null; } /** * Set default translation text domain for all validate objects - * - * @param string $textDomain - * @return void */ - public static function setDefaultTranslatorTextDomain($textDomain = 'default') + public static function setDefaultTranslatorTextDomain(string $textDomain = 'default'): void { static::$defaultTranslatorTextDomain = $textDomain; } /** * Get default translation text domain for all validate objects - * - * @return string */ - public static function getDefaultTranslatorTextDomain() + public static function getDefaultTranslatorTextDomain(): string { return static::$defaultTranslatorTextDomain; } /** * Indicate whether or not translation should be enabled - * - * @param bool $enabled - * @return $this */ - public function setTranslatorEnabled($enabled = true) + public function setTranslatorEnabled(bool $enabled = true): void { - /** @psalm-suppress RedundantCastGivenDocblockType */ - $this->abstractOptions['translatorEnabled'] = (bool) $enabled; - return $this; + $this->abstractOptions['translatorEnabled'] = $enabled; } /** * Is translation enabled? - * - * @return bool */ - public function isTranslatorEnabled() + public function isTranslatorEnabled(): bool { return $this->abstractOptions['translatorEnabled']; } /** * Returns the maximum allowed message length - * - * @return int */ - public static function getMessageLength() + public static function getMessageLength(): int { return static::$messageLength; } /** * Sets the maximum allowed message length - * - * @param int $length - * @return void */ - public static function setMessageLength($length = -1) + public static function setMessageLength(int $length = -1): void { static::$messageLength = $length; } @@ -607,10 +563,8 @@ public static function setMessageLength($length = -1) * Translate a validation message * * @param string $messageKey - * @param string $message - * @return string */ - protected function translateMessage($messageKey, $message) + protected function translateMessage($messageKey, string $message): string { $translator = $this->getTranslator(); if (! $translator) { diff --git a/src/EmailAddress.php b/src/EmailAddress.php index 8c04b515..6ff8b4ec 100644 --- a/src/EmailAddress.php +++ b/src/EmailAddress.php @@ -436,20 +436,21 @@ protected function validateMXRecords() */ protected function validateHostnamePart() { - $hostname = $this->getHostnameValidator()->setTranslator($this->getTranslator()) - ->isValid($this->hostname); - if (! $hostname) { + $hostname = $this->getHostnameValidator(); + $hostname->setTranslator($this->getTranslator()); + $isValid = $hostname->isValid($this->hostname); + if (! $isValid) { $this->error(self::INVALID_HOSTNAME); // Get messages and errors from hostnameValidator - foreach ($this->getHostnameValidator()->getMessages() as $code => $message) { + foreach ($hostname->getMessages() as $code => $message) { $this->abstractOptions['messages'][$code] = $message; } } elseif ($this->options['useMxCheck']) { // MX check on hostname - $hostname = $this->validateMXRecords(); + $isValid = $this->validateMXRecords(); } - return $hostname; + return $isValid; } /** diff --git a/src/Hostname.php b/src/Hostname.php index 7ed4c686..e7770a51 100644 --- a/src/Hostname.php +++ b/src/Hostname.php @@ -1947,10 +1947,13 @@ public function isValid($value) $this->setValue($value); // Check input against IP address schema + $ipValidator = $this->getIpValidator(); + $ipValidator->setTranslator($this->getTranslator()); + if ( ((preg_match('/^[0-9.]*$/', $value) && str_contains($value, '.')) || (preg_match('/^[0-9a-f:.]*$/i', $value) && str_contains($value, ':'))) - && $this->getIpValidator()->setTranslator($this->getTranslator())->isValid($value) + && $ipValidator->isValid($value) ) { if (! ($this->getAllow() & self::ALLOW_IP)) { $this->error(self::IP_ADDRESS_NOT_ALLOWED); @@ -1985,7 +1988,7 @@ public function isValid($value) // Prevent partial IP V4 addresses (ending '.') if ( count($domainParts) === 4 && preg_match('/^[0-9.a-e:.]*$/i', $value) - && $this->getIpValidator()->setTranslator($this->getTranslator())->isValid($value) + && $ipValidator->isValid($value) ) { $this->error(self::INVALID_LOCAL_NAME); } From 5268f938a1cac8ef340b650655a4983e31d3698d Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:45:28 +0100 Subject: [PATCH 4/8] Drop the validator specific translator interfaces and implementations Signed-off-by: George Steel --- src/ConfigProvider.php | 4 +- src/Translator/DummyTranslator.php | 25 -- src/Translator/Translator.php | 45 --- src/Translator/TranslatorFactory.php | 122 -------- src/Translator/TranslatorInterface.php | 16 - src/ValidatorPluginManager.php | 4 +- test/TestAsset/ArrayTranslator.php | 21 -- test/Translator/TranslatorFactoryTest.php | 359 ---------------------- test/Translator/TranslatorTest.php | 78 ----- 9 files changed, 3 insertions(+), 671 deletions(-) delete mode 100644 src/Translator/DummyTranslator.php delete mode 100644 src/Translator/Translator.php delete mode 100644 src/Translator/TranslatorFactory.php delete mode 100644 src/Translator/TranslatorInterface.php delete mode 100644 test/TestAsset/ArrayTranslator.php delete mode 100644 test/Translator/TranslatorFactoryTest.php delete mode 100644 test/Translator/TranslatorTest.php diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 998eda4a..eb15293d 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -27,11 +27,9 @@ public function getDependencyConfig() { return [ 'aliases' => [ - Translator\TranslatorInterface::class => Translator\Translator::class, - 'ValidatorManager' => ValidatorPluginManager::class, + 'ValidatorManager' => ValidatorPluginManager::class, ], 'factories' => [ - Translator\Translator::class => Translator\TranslatorFactory::class, ValidatorPluginManager::class => ValidatorPluginManagerFactory::class, ], ]; diff --git a/src/Translator/DummyTranslator.php b/src/Translator/DummyTranslator.php deleted file mode 100644 index fb153abd..00000000 --- a/src/Translator/DummyTranslator.php +++ /dev/null @@ -1,25 +0,0 @@ -translator->translate($message, $textDomain, $locale); - } - - /** - * Provide a pluralized translation of the given string using the given text domain and locale - * - * @param string $singular - * @param string $plural - * @param int $number - * @param string $textDomain - * @param string $locale - * @return string - */ - public function translatePlural($singular, $plural, $number, $textDomain = 'default', $locale = null) - { - return $this->translator->translatePlural($singular, $plural, $number, $textDomain, $locale); - } -} diff --git a/src/Translator/TranslatorFactory.php b/src/Translator/TranslatorFactory.php deleted file mode 100644 index 35086ab7..00000000 --- a/src/Translator/TranslatorFactory.php +++ /dev/null @@ -1,122 +0,0 @@ -has(TranslatorInterface::class)) { - return new Translator($container->get(TranslatorInterface::class)); - } - - return $this->marshalTranslator($container); - } - - /** - * Marshal an Translator. - * - * If configuration exists, will pass it to the I18nTranslator::factory, - * decorating the returned instance in an MvcTranslator. - * - * Otherwise: - * - * - returns an Translator decorating a DummyTranslator instance if - * ext/intl is not loaded. - * - returns an Translator decorating an empty I18nTranslator instance. - */ - private function marshalTranslator(ContainerInterface $container): Translator - { - // Load a translator from configuration, if possible - $translator = $this->marshalTranslatorFromConfig($container); - - if ($translator instanceof Translator) { - return $translator; - } - - // If ext/intl is not loaded, return a dummy translator - if (! extension_loaded('intl')) { - return new Translator(new DummyTranslator()); - } - - return new Translator(new I18nTranslator()); - } - - /** - * Attempt to marshal a translator from configuration. - * - * Returns: - * - an Translator seeded with a DummyTranslator if "translator" - * configuration is available, and evaluates to boolean false. - * - an Translator seed with an I18nTranslator if "translator" - * configuration is available, and is a non-empty array or a Traversable - * instance. - * - null in all other cases, including absence of a configuration service. - */ - private function marshalTranslatorFromConfig(ContainerInterface $container): ?Translator - { - if (! $container->has('config')) { - return null; - } - - $config = $container->get('config'); - - if (! is_array($config) || ! array_key_exists('translator', $config)) { - return null; - } - - // 'translator' => false - if ($config['translator'] === false) { - return new Translator(new DummyTranslator()); - } - - // Empty translator configuration - if (is_array($config['translator']) && empty($config['translator'])) { - return null; - } - - // Unusable translator configuration - if (! is_array($config['translator']) && ! $config['translator'] instanceof Traversable) { - return null; - } - - // Create translator from configuration - $i18nTranslator = I18nTranslator::factory($config['translator']); - - // Inject plugins, if present - if ($container->has('TranslatorPluginManager')) { - $loaderManager = $container->get('TranslatorPluginManager'); - - assert($loaderManager instanceof LoaderPluginManager); - - $i18nTranslator->setPluginManager($loaderManager); - } - - // Inject into service manager instances - if ($container instanceof ServiceManager) { - $container->setService(TranslatorInterface::class, $i18nTranslator); - } - - return new Translator($i18nTranslator); - } -} diff --git a/src/Translator/TranslatorInterface.php b/src/Translator/TranslatorInterface.php deleted file mode 100644 index 7276a753..00000000 --- a/src/Translator/TranslatorInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -has(TranslatorInterface::class)) { - $validator->setTranslator($container->get(Translator\TranslatorInterface::class)); + $validator->setTranslator($container->get(TranslatorInterface::class)); } } diff --git a/test/TestAsset/ArrayTranslator.php b/test/TestAsset/ArrayTranslator.php deleted file mode 100644 index 825eedd2..00000000 --- a/test/TestAsset/ArrayTranslator.php +++ /dev/null @@ -1,21 +0,0 @@ -translations); - } -} diff --git a/test/Translator/TranslatorFactoryTest.php b/test/Translator/TranslatorFactoryTest.php deleted file mode 100644 index 19c361af..00000000 --- a/test/Translator/TranslatorFactoryTest.php +++ /dev/null @@ -1,359 +0,0 @@ -createMock(TranslatorInterface::class); - - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::once()) - ->method('has') - ->with(TranslatorInterface::class) - ->willReturn(true); - $container - ->expects(self::once()) - ->method('get') - ->with(TranslatorInterface::class) - ->willReturn($translator); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $this->assertSame($translator, $prop->getValue($test)); - } - - /** @psalm-return array */ - public static function expectedTranslatorProvider(): array - { - return extension_loaded('intl') - ? ['intl-loaded' => [I18nTranslator::class]] - : ['no-intl-loaded' => [DummyTranslator::class]]; - } - - /** - * @dataProvider expectedTranslatorProvider - * @psalm-param class-string $expected - */ - public function testFactoryReturnsTranslatorDecoratingDefaultTranslatorWhenNoConfigPresent( - string $expected - ): void { - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(2)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', false], - ] - ); - $container - ->expects(self::never()) - ->method('get'); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $this->assertInstanceOf($expected, $prop->getValue($test)); - } - - /** - * @dataProvider expectedTranslatorProvider - * @psalm-param class-string $expected - */ - public function testFactoryReturnsMvcDecoratorDecoratingDefaultTranslatorWhenNoTranslatorConfigPresent( - string $expected - ): void { - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(2)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', true], - ] - ); - $container - ->expects(self::once()) - ->method('get') - ->with('config') - ->willReturn([]); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $this->assertInstanceOf($expected, $prop->getValue($test)); - } - - public function testFactoryReturnsMvcDecoratorDecoratingDummyTranslatorWhenTranslatorConfigIsFalse(): void - { - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(2)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', true], - ] - ); - $container - ->expects(self::once()) - ->method('get') - ->with('config') - ->willReturn(['translator' => false]); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $this->assertInstanceOf(DummyTranslator::class, $prop->getValue($test)); - } - - /** - * @dataProvider expectedTranslatorProvider - * @psalm-param class-string $expected - */ - public function testFactoryReturnsMvcDecoratorDecoratingDefaultTranslatorWhenEmptyTranslatorConfigPresent( - string $expected - ): void { - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(2)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', true], - ] - ); - $container - ->expects(self::once()) - ->method('get') - ->with('config') - ->willReturn(['translator' => []]); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $this->assertInstanceOf($expected, $prop->getValue($test)); - } - - /** @psalm-return array, 1: class-string}> */ - public static function invalidTranslatorConfig(): array - { - $expectedTranslator = extension_loaded('intl') - ? I18nTranslator::class - : DummyTranslator::class; - - return [ - 'null' => [['translator' => null], $expectedTranslator], - 'true' => [['translator' => true], $expectedTranslator], - 'zero' => [['translator' => 0], $expectedTranslator], - 'int' => [['translator' => 1], $expectedTranslator], - 'float-0' => [['translator' => 0.0], $expectedTranslator], - 'float' => [['translator' => 1.1], $expectedTranslator], - 'string' => [['translator' => 'invalid'], $expectedTranslator], - 'object' => [['translator' => (object) ['translator' => 'invalid']], $expectedTranslator], - ]; - } - - /** - * @param array $config - * @psalm-param class-string $expected - * @dataProvider invalidTranslatorConfig - */ - public function testFactoryReturnsDecoratorDecoratingDefaultTranslatorWithInvalidTranslatorConfig( - $config, - $expected - ): void { - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(2)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', true], - ] - ); - $container - ->expects(self::once()) - ->method('get') - ->with('config') - ->willReturn($config); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $this->assertInstanceOf($expected, $prop->getValue($test)); - } - - /** - * @psalm-return array|ArrayAccess}> - */ - public static function validTranslatorConfig(): array - { - $locale = Locale::getDefault() === 'en-US' ? 'de-DE' : Locale::getDefault(); - $config = [ - 'locale' => $locale, - 'event_manager_enabled' => true, - ]; - - return [ - 'array' => [$config], - 'traversable' => [new ArrayObject($config)], - ]; - } - - /** - * @requires extension intl - * @dataProvider validTranslatorConfig - * @param array|ArrayAccess $config - */ - public function testFactoryReturnsConfiguredTranslatorWhenValidConfigIsPresent($config): void - { - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(3)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', true], - ['TranslatorPluginManager', false], - ] - ); - $container - ->expects(self::once()) - ->method('get') - ->with('config') - ->willReturn(['translator' => $config]); - $container - ->expects(self::once()) - ->method('setService') - ->with(TranslatorInterface::class, new IsInstanceOf(I18nTranslator::class)); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $decorated = $prop->getValue($test); - - $this->assertInstanceOf(I18nTranslator::class, $decorated); - $locale = $config['locale'] ?? null; - self::assertIsString($locale); - $this->assertEquals($locale, $decorated->getLocale()); - $this->assertTrue($decorated->isEventManagerEnabled()); - } - - /** - * @param array|ArrayAccess $config - * @requires extension intl - * @dataProvider validTranslatorConfig - */ - public function testFactoryReturnsConfiguredTranslatorInjectedWithTranslatorPluginManagerWhenValidConfigIsPresent( - $config - ): void { - $loaders = $this->createMock(LoaderPluginManager::class); - - $container = $this->createMock(ServiceManager::class); - $container - ->expects(self::exactly(3)) - ->method('has') - ->willReturnMap( - [ - [TranslatorInterface::class, false], - ['config', true], - ['TranslatorPluginManager', true], - ] - ); - $container - ->expects(self::exactly(2)) - ->method('get') - ->willReturnMap( - [ - ['config', ['translator' => $config]], - ['TranslatorPluginManager', $loaders], - ] - ); - $container - ->expects(self::once()) - ->method('setService') - ->with(TranslatorInterface::class, new IsInstanceOf(I18nTranslator::class)); - - self::assertInstanceOf(ContainerInterface::class, $container); - - $factory = new TranslatorFactory(); - $test = $factory($container); - - $this->assertInstanceOf(Translator::class, $test); - - $prop = new ReflectionProperty($test, 'translator'); - $decorated = $prop->getValue($test); - - $this->assertInstanceOf(I18nTranslator::class, $decorated); - $this->assertEquals($config['locale'], $decorated->getLocale()); - $this->assertTrue($decorated->isEventManagerEnabled()); - $this->assertSame($loaders, $decorated->getPluginManager()); - } -} diff --git a/test/Translator/TranslatorTest.php b/test/Translator/TranslatorTest.php deleted file mode 100644 index af613f1e..00000000 --- a/test/Translator/TranslatorTest.php +++ /dev/null @@ -1,78 +0,0 @@ -i18nTranslator = $this->createMock(I18nTranslator::class); - $this->translator = new Translator($this->i18nTranslator); - } - - public function testTranslate(): void - { - $message = 'This is the message'; - $textDomain = 'default'; - $locale = 'en_US'; - - $this->i18nTranslator->expects($this->once()) - ->method('translate') - ->with($message, $textDomain, $locale) - ->willReturn($message); - - $this->assertEquals( - $message, - $this->translator->translate( - $message, - $textDomain, - $locale - ) - ); - } - - public function testTranslatePlural(): void - { - $singular = 'singular'; - $plural = 'plural'; - $number = 2; - $textDomain = 'default'; - $locale = 'en_US'; - - $this->i18nTranslator->expects($this->once()) - ->method('translatePlural') - ->with( - $singular, - $plural, - $number, - $textDomain, - $locale - ) - ->willReturn($singular); - - $this->assertEquals( - $singular, - $this->translator->translatePlural( - $singular, - $plural, - $number, - $textDomain, - $locale - ) - ); - } -} From 2eafcc58189840a5925d9a4d914da320b2854659 Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:47:15 +0100 Subject: [PATCH 5/8] Fix invalid test Internally, `AbstractValidator::setError(0)` is called which is a type error. This test is about testing the effect of an option Signed-off-by: George Steel --- test/File/MimeTypeTest.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/File/MimeTypeTest.php b/test/File/MimeTypeTest.php index d7ca8aa7..a448ec5e 100644 --- a/test/File/MimeTypeTest.php +++ b/test/File/MimeTypeTest.php @@ -254,15 +254,9 @@ public function testDisableMagicFile(): void #[Group('Laminas-10461')] public function testDisablingMagicFileByConstructor(): void { - $files = [ - 'name' => 'picture.jpg', - 'size' => 200, - 'tmp_name' => __DIR__ . '/_files/picture.jpg', - 'error' => 0, + $validator = new MimeType([ 'magicFile' => false, - ]; - - $validator = new MimeType($files); + ]); self::assertFalse($validator->getMagicFile()); } From 6411f7a6cdc6295cc33f4fe6ed57b1bf393791c1 Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:48:38 +0100 Subject: [PATCH 6/8] Refactor translator stub to accept translations to the constructor Signed-off-by: George Steel --- test/TestAsset/Translator.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/TestAsset/Translator.php b/test/TestAsset/Translator.php index f34bcd7e..2b16221a 100644 --- a/test/TestAsset/Translator.php +++ b/test/TestAsset/Translator.php @@ -4,9 +4,24 @@ namespace LaminasTest\Validator\TestAsset; -use Laminas\I18n\Translator\Translator as I18nTranslator; -use Laminas\Validator\Translator\TranslatorInterface as ValidatorTranslatorInterface; +use Laminas\Translator\TranslatorInterface; -class Translator extends I18nTranslator implements ValidatorTranslatorInterface +class Translator implements TranslatorInterface { + /** @param array $translations */ + public function __construct(public array $translations) + { + } + + /** @inheritDoc */ + public function translate($message, $textDomain = 'default', $locale = null) + { + return $this->translations[$message] ?? $message; + } + + /** @inheritDoc */ + public function translatePlural($singular, $plural, $number, $textDomain = 'default', $locale = null) + { + return $number === 1 ? $singular : $plural; + } } From eda78e2600ed1903112c90e393893a45e5f909da Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:50:47 +0100 Subject: [PATCH 7/8] Clean up tests and alter expectations around the translator type Signed-off-by: George Steel --- psalm-baseline.xml | 24 --------- test/AbstractValidatorTest.php | 35 +++---------- test/EmailAddressTest.php | 80 ++++++++++++----------------- test/HostnameTest.php | 16 ++---- test/StaticValidatorTest.php | 20 ++------ test/ValidatorPluginManagerTest.php | 6 +-- 6 files changed, 52 insertions(+), 129 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 203df737..74b1171b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1251,17 +1251,6 @@ - - - - - - - - - - - @@ -1334,10 +1323,6 @@ - - - - @@ -1421,9 +1406,6 @@ 1]]]> - - - @@ -1746,9 +1728,6 @@ - - - @@ -1930,9 +1909,6 @@ - - - diff --git a/test/AbstractValidatorTest.php b/test/AbstractValidatorTest.php index 30c1bdec..3a992aa6 100644 --- a/test/AbstractValidatorTest.php +++ b/test/AbstractValidatorTest.php @@ -8,7 +8,6 @@ use Laminas\Validator\EmailAddress; use Laminas\Validator\Exception\InvalidArgumentException; use Laminas\Validator\Hostname; -use LaminasTest\Validator\TestAsset\ArrayTranslator; use LaminasTest\Validator\TestAsset\ConcreteValidator; use LaminasTest\Validator\TestAsset\Translator; use PHPUnit\Framework\Attributes\DataProvider; @@ -17,7 +16,6 @@ use ReflectionMethod; use stdClass; -use function extension_loaded; use function reset; use function sprintf; use function var_export; @@ -47,7 +45,7 @@ public function testTranslatorNullByDefault(): void public function testCanSetTranslator(): void { - $translator = new Translator(); + $translator = new Translator([]); $this->validator->setTranslator($translator); self::assertSame($translator, $this->validator->getTranslator()); @@ -63,18 +61,9 @@ public function testCanSetTranslatorToNull(): void public function testErrorMessagesAreTranslatedWhenTranslatorPresent(): void { - if (! extension_loaded('intl')) { - self::markTestSkipped('ext/intl not enabled'); - } - - $loader = new ArrayTranslator(); - $loader->translations = [ + $translator = new Translator([ '%value% was passed' => 'This is the translated message for %value%', - ]; - $translator = new Translator(); - $translator->getPluginManager()->setService('default', $loader); - $translator->addTranslationFile('default', null); - + ]); $this->validator->setTranslator($translator); self::assertFalse($this->validator->isValid('bar')); @@ -83,7 +72,7 @@ public function testErrorMessagesAreTranslatedWhenTranslatorPresent(): void self::assertArrayHasKey('fooMessage', $messages); self::assertStringContainsString('bar', $messages['fooMessage'], var_export($messages, true)); - self::assertStringContainsString('This is the translated message for ', $messages['fooMessage']); + self::assertStringContainsString('This is the translated message for bar', $messages['fooMessage']); } public function testObscureValueFlagFalseByDefault(): void @@ -132,7 +121,7 @@ public function testDoesNotFailOnObjectInput(): void public function testTranslatorEnabledPerDefault(): void { - $translator = new Translator(); + $translator = new Translator([]); $this->validator->setTranslator($translator); self::assertTrue($this->validator->isTranslatorEnabled()); @@ -140,17 +129,9 @@ public function testTranslatorEnabledPerDefault(): void public function testCanDisableTranslator(): void { - if (! extension_loaded('intl')) { - self::markTestSkipped('ext/intl not enabled'); - } - - $loader = new ArrayTranslator(); - $loader->translations = [ + $translator = new Translator([ '%value% was passed' => 'This is the translated message for %value%', - ]; - $translator = new Translator(); - $translator->getPluginManager()->setService('default', $loader); - $translator->addTranslationFile('default', null); + ]); $this->validator->setTranslator($translator); self::assertFalse($this->validator->isValid('bar')); @@ -159,7 +140,7 @@ public function testCanDisableTranslator(): void self::assertArrayHasKey('fooMessage', $messages); self::assertStringContainsString('bar', $messages['fooMessage']); - self::assertStringContainsString('This is the translated message for ', $messages['fooMessage']); + self::assertStringContainsString('This is the translated message for bar', $messages['fooMessage']); $this->validator->setTranslatorEnabled(false); diff --git a/test/EmailAddressTest.php b/test/EmailAddressTest.php index 41d1b721..b22c63bc 100644 --- a/test/EmailAddressTest.php +++ b/test/EmailAddressTest.php @@ -4,9 +4,11 @@ namespace LaminasTest\Validator; +use Generator; use Laminas\Validator\EmailAddress; use Laminas\Validator\Exception\InvalidArgumentException; use Laminas\Validator\Hostname; +use LaminasTest\Validator\TestAsset\Translator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\Attributes\Group; @@ -17,7 +19,6 @@ use function checkdnsrr; use function count; use function current; -use function extension_loaded; use function implode; use function next; use function preg_replace; @@ -218,34 +219,31 @@ public function testEmailDisplay(): void } /** - * @psalm-return array + * @psalm-return Generator */ - public static function validEmailAddresses(): array - { - // @codingStandardsIgnoreStart - $return = [ - 'bob@domain.com' => ['bob@domain.com'], - 'bob.jones@domain.co.uk' => ['bob.jones@domain.co.uk'], - 'bob.jones.smythe@domain.co.uk' => ['bob.jones.smythe@domain.co.uk'], - 'BoB@domain.museum' => ['BoB@domain.museum'], - 'bobjones@domain.info' => ['bobjones@domain.info'], - 'bob+jones@domain.us' => ['bob+jones@domain.us'], - 'bob+jones@domain.co.uk' => ['bob+jones@domain.co.uk'], - 'bob@some.domain.uk.com' => ['bob@some.domain.uk.com'], - 'bob@verylongdomainsupercalifragilisticexpialidociousspoonfulofsugar.com' => ['bob@verylongdomainsupercalifragilisticexpialidociousspoonfulofsugar.com'], - "B.O'Callaghan@domain.com" => ["B.O'Callaghan@domain.com"], + public static function validEmailAddresses(): Generator + { + $list = [ + 'bob@domain.com', + 'bob.jones@domain.co.uk', + 'bob.jones.smythe@domain.co.uk', + 'BoB@domain.museum', + 'bobjones@domain.info', + 'bob+jones@domain.us', + 'bob+jones@domain.co.uk', + 'bob@some.domain.uk.com', + 'bob@verylongdomainsupercalifragilisticexpialidociousspoonfulofsugar.com', + "B.O'Callaghan@domain.com", + 'иван@письмо.рф', + 'öäü@ä-umlaut.de', + 'frédéric@domain.com', + 'bob@тест.рф', + 'bob@xn--e1aybc.xn--p1ai', ]; - if (extension_loaded('intl')) { - $return['иван@письмо.рф'] = ['иван@письмо.рф']; - $return['öäü@ä-umlaut.de'] = ['öäü@ä-umlaut.de']; - $return['frédéric@domain.com'] = ['frédéric@domain.com']; - $return['bob@тест.рф'] = ['bob@тест.рф']; - $return['bob@xn--e1aybc.xn--p1ai'] = ['bob@xn--e1aybc.xn--p1ai']; + foreach ($list as $email) { + yield $email => [$email]; } - - return $return; - // @codingStandardsIgnoreEnd } /** @@ -453,12 +451,8 @@ public function testGetMessages(): void #[Group('Laminas-2861')] public function testHostnameValidatorMessagesShouldBeTranslated(): void { - if (! extension_loaded('intl')) { - self::markTestSkipped('ext/intl not enabled'); - } - - $hostnameValidator = new Hostname(); - $translations = [ + $hostnameValidator = new Hostname(); + $translations = [ 'hostnameIpAddressNotAllowed' => 'hostnameIpAddressNotAllowed translation', 'hostnameUnknownTld' => 'The input appears to be a DNS hostname ' . 'but cannot match TLD against known list', @@ -469,13 +463,11 @@ public function testHostnameValidatorMessagesShouldBeTranslated(): void 'hostnameInvalidLocalName' => 'hostnameInvalidLocalName translation', 'hostnameLocalNameNotAllowed' => 'hostnameLocalNameNotAllowed translation', ]; - $loader = new TestAsset\ArrayTranslator(); - $loader->translations = $translations; - $translator = new TestAsset\Translator(); - $translator->getPluginManager()->setService('test', $loader); - $translator->addTranslationFile('test', null); - $this->validator->setTranslator($translator)->setHostnameValidator($hostnameValidator); + $translator = new Translator($translations); + + $this->validator->setTranslator($translator); + $this->validator->setHostnameValidator($hostnameValidator); $this->validator->isValid('_XX.!!3xx@0.239,512.777'); $messages = $hostnameValidator->getMessages(); @@ -760,13 +752,10 @@ public function testUseMxCheckBasicValid(): void 'bob+jones@dailymail.co.uk', 'bob@teaparty.uk.com', 'bob@thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com', + 'test@кц.рф', // Registry for .рф-TLD + 'test@xn--j1ay.xn--p1ai', ]; - if (extension_loaded('intl')) { - $emailAddresses[] = 'test@кц.рф'; // Registry for .рф-TLD - $emailAddresses[] = 'test@xn--j1ay.xn--p1ai'; - } - foreach ($emailAddresses as $input) { self::assertTrue( $validator->isValid($input), @@ -800,13 +789,10 @@ public function testUseMxRecordsBasicInvalid(): void 'bob@ domain.com', 'bob @ domain.com', 'Abc..123@example.com', + 'иван@письмо.рф', + 'xn--@-7sbfxdyelgv5j.xn--p1ai', ]; - if (! extension_loaded('intl')) { - $emailAddresses[] = 'иван@письмо.рф'; - $emailAddresses[] = 'xn--@-7sbfxdyelgv5j.xn--p1ai'; - } - foreach ($emailAddresses as $input) { self::assertFalse($validator->isValid($input), implode("\n", $this->validator->getMessages()) . $input); } diff --git a/test/HostnameTest.php b/test/HostnameTest.php index 6814e8d4..5f4b9f60 100644 --- a/test/HostnameTest.php +++ b/test/HostnameTest.php @@ -5,7 +5,6 @@ namespace LaminasTest\Validator; use Laminas\Validator\Hostname; -use LaminasTest\Validator\TestAsset\ArrayTranslator; use LaminasTest\Validator\TestAsset\Translator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; @@ -13,7 +12,6 @@ use function array_key_exists; use function array_keys; -use function extension_loaded; use function implode; use function ini_get; use function ini_set; @@ -310,18 +308,10 @@ public function testGetAllow(): void #[Group('Laminas-6676')] public function testValidatorMessagesShouldBeTranslated(): void { - if (! extension_loaded('intl')) { - self::markTestSkipped('ext/intl not enabled'); - } - - $translations = [ - 'hostnameInvalidLocalName' => 'The input does not appear to be a valid local network name', + $translations = [ + Hostname::INVALID_LOCAL_NAME => 'The input does not appear to be a valid local network name', ]; - $loader = new ArrayTranslator(); - $loader->translations = $translations; - $translator = new Translator(); - $translator->getPluginManager()->setService('default', $loader); - $translator->addTranslationFile('default', null); + $translator = new Translator($translations); $this->validator->setTranslator($translator); $this->validator->isValid('0.239,512.777'); diff --git a/test/StaticValidatorTest.php b/test/StaticValidatorTest.php index ed67979b..0a65cddf 100644 --- a/test/StaticValidatorTest.php +++ b/test/StaticValidatorTest.php @@ -13,14 +13,12 @@ use Laminas\Validator\StringLength; use Laminas\Validator\ValidatorInterface; use Laminas\Validator\ValidatorPluginManager; -use LaminasTest\Validator\TestAsset\ArrayTranslator; use LaminasTest\Validator\TestAsset\Translator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use function current; -use function extension_loaded; use function strlen; final class StaticValidatorTest extends TestCase @@ -49,7 +47,7 @@ protected function tearDown(): void public function testCanSetGlobalDefaultTranslator(): void { - $translator = new Translator(); + $translator = new Translator([]); AbstractValidator::setDefaultTranslator($translator); self::assertSame($translator, AbstractValidator::getDefaultTranslator()); @@ -65,7 +63,7 @@ public function testGlobalDefaultTranslatorUsedWhenNoLocalTranslatorSet(): void public function testLocalTranslatorPreferredOverGlobalTranslator(): void { $this->testCanSetGlobalDefaultTranslator(); - $translator = new Translator(); + $translator = new Translator([]); $this->validator->setTranslator($translator); self::assertNotSame(AbstractValidator::getDefaultTranslator(), $this->validator->getTranslator()); @@ -73,23 +71,15 @@ public function testLocalTranslatorPreferredOverGlobalTranslator(): void public function testMaximumErrorMessageLength(): void { - if (! extension_loaded('intl')) { - self::markTestSkipped('ext/intl not enabled'); - } - self::assertSame(-1, AbstractValidator::getMessageLength()); AbstractValidator::setMessageLength(10); self::assertSame(10, AbstractValidator::getMessageLength()); - $loader = new ArrayTranslator(); - $loader->translations = [ + $translator = new Translator([ 'Invalid type given. String expected' => 'This is the translated message for %value%', - ]; - $translator = new Translator(); - $translator->getPluginManager()->setService('default', $loader); - $translator->addTranslationFile('default', null); + ]); $this->validator->setTranslator($translator); @@ -118,7 +108,7 @@ public function testSetGetMessageLengthLimitation(): void public function testSetGetDefaultTranslator(): void { - $translator = new Translator(); + $translator = new Translator([]); AbstractValidator::setDefaultTranslator($translator); self::assertSame($translator, AbstractValidator::getDefaultTranslator()); diff --git a/test/ValidatorPluginManagerTest.php b/test/ValidatorPluginManagerTest.php index ec5e92a7..9f5b5b5a 100644 --- a/test/ValidatorPluginManagerTest.php +++ b/test/ValidatorPluginManagerTest.php @@ -7,10 +7,10 @@ use Exception; use Laminas\ServiceManager\Exception\InvalidServiceException; use Laminas\ServiceManager\ServiceManager; +use Laminas\Translator\TranslatorInterface; use Laminas\Validator\AbstractValidator; use Laminas\Validator\Exception\RuntimeException; use Laminas\Validator\NotEmpty; -use Laminas\Validator\Translator\TranslatorInterface; use Laminas\Validator\ValidatorInterface; use Laminas\Validator\ValidatorPluginManager; use Laminas\Validator\ValidatorPluginManagerAwareInterface; @@ -62,7 +62,7 @@ public function testAllowsInjectingTranslator(): void public function testAllowsInjectingTranslatorInterface(): void { - $translator = $this->createMock(Translator::class); + $translator = $this->createMock(TranslatorInterface::class); $container = $this->createMock(ContainerInterface::class); @@ -72,7 +72,7 @@ public function testAllowsInjectingTranslatorInterface(): void ->willReturnMap( [ ['MvcTranslator', false], - [\Laminas\I18n\Translator\TranslatorInterface::class, true], + [TranslatorInterface::class, true], ], ); From 3f795ad0a96c43c2a3e440c576320d8af260766f Mon Sep 17 00:00:00 2001 From: George Steel Date: Tue, 2 Jul 2024 11:57:46 +0100 Subject: [PATCH 8/8] Additional types and static analysis fixes Signed-off-by: George Steel --- psalm-baseline.xml | 18 ------------------ src/AbstractValidator.php | 18 ++++-------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 74b1171b..c457437c 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -49,23 +49,6 @@ options]]> options]]> options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> @@ -119,7 +102,6 @@ cardLength[$type]]]> options['type']]]> options['type']]]> - options['type'][]]]> diff --git a/src/AbstractValidator.php b/src/AbstractValidator.php index b52f3503..371393bc 100644 --- a/src/AbstractValidator.php +++ b/src/AbstractValidator.php @@ -48,9 +48,9 @@ abstract class AbstractValidator implements /** * The value to be validated * - * @var mixed + * phpcs:disable WebimpressCodingStandard.Classes.NoNullValues */ - protected $value; + protected mixed $value = null; /** * Default translation object for all validate objects @@ -309,12 +309,8 @@ public function __get($property) * * If a translator is available and a translation exists for $messageKey, * the translation will be used. - * - * @param string $messageKey - * @param string|array|object $value - * @return null|string */ - protected function createMessage($messageKey, $value) + protected function createMessage(string $messageKey, mixed $value): ?string { if (! isset($this->abstractOptions['messageTemplates'][$messageKey])) { return null; @@ -331,7 +327,6 @@ protected function createMessage($messageKey, $value) } elseif (is_array($value)) { $value = var_export($value, true); } else { - /** @psalm-suppress RedundantCastGivenDocblockType $value */ $value = (string) $value; } @@ -362,13 +357,8 @@ protected function createMessage($messageKey, $value) protected function error(string $messageKey, mixed $value = null): void { - if ($messageKey === null) { - $keys = array_keys($this->abstractOptions['messageTemplates']); - $messageKey = current($keys); - } - if ($value === null) { - /** @psalm-var string|array|object $value */ + /** @psalm-var mixed $value */ $value = $this->value; }