From 9512580de9e923442e6f764b5f2c1a4669953902 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Mon, 27 Jan 2020 17:00:33 +0200 Subject: [PATCH 01/21] Fix camelcase method name in tests [skip ci] --- tests/Rule/CompareToTest.php | 2 +- tests/Rule/EmailTest.php | 6 +++--- tests/Rule/InRangeTest.php | 8 ++++---- tests/Rule/IpTest.php | 6 +++--- tests/Rule/MatchRegularExpressionTest.php | 2 +- tests/Rule/NumberTest.php | 22 +++++++++++----------- tests/Rule/RequiredTest.php | 2 +- tests/Rule/UrlTest.php | 4 ++-- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/Rule/CompareToTest.php b/tests/Rule/CompareToTest.php index 4b91d8a58..190a3323a 100644 --- a/tests/Rule/CompareToTest.php +++ b/tests/Rule/CompareToTest.php @@ -10,7 +10,7 @@ */ class CompareToTest extends TestCase { - public function testvalidate(): void + public function testValidate(): void { $value = 18449; // default config diff --git a/tests/Rule/EmailTest.php b/tests/Rule/EmailTest.php index f46dbce0c..9bdf26db4 100644 --- a/tests/Rule/EmailTest.php +++ b/tests/Rule/EmailTest.php @@ -10,7 +10,7 @@ */ class EmailTest extends TestCase { - public function testvalidate(): void + public function testValidate(): void { $validator = new Email(); @@ -49,7 +49,7 @@ public function testvalidate(): void $this->assertFalse($validator->validate(['developer@yiiframework.com'])->isValid()); } - public function testvalidateIdn(): void + public function testValidateIdn(): void { if (!function_exists('idn_to_ascii')) { $this->markTestSkipped('Intl extension required'); @@ -93,7 +93,7 @@ public function testvalidateIdn(): void $this->assertFalse($validator->validate('Короткое имя <тест@это-доменное-имя.после-преобразования-в-idn.будет-содержать-больше-254-символов.бла-бла-бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.com>')->isValid()); } - public function testvalidateMx(): void + public function testValidateMx(): void { $this->markTestSkipped('Too slow :('); diff --git a/tests/Rule/InRangeTest.php b/tests/Rule/InRangeTest.php index d780ad804..db9233407 100644 --- a/tests/Rule/InRangeTest.php +++ b/tests/Rule/InRangeTest.php @@ -17,7 +17,7 @@ public function testInitException(): void new InRange('not an array'); } - public function testvalidate(): void + public function testValidate(): void { $val = new InRange(range(1, 10, 1)); $this->assertTrue($val->validate(1)->isValid()); @@ -29,7 +29,7 @@ public function testvalidate(): void $this->assertTrue($val->validate('5')->isValid()); } - public function testvalidateEmpty(): void + public function testValidateEmpty(): void { $rule = (new InRange(range(10, 20, 1)))->skipOnEmpty(false); $this->assertFalse($rule->validate(null)->isValid()); //row RangeValidatorTest.php:101 @@ -56,7 +56,7 @@ public function testValidateArrayValue(): void $this->assertTrue($rule->validate(['1', '2', '3', 4, 5, 6])->isValid()); } - public function testvalidateStrict(): void + public function testValidateStrict(): void { $rule = (new InRange(range(1, 10, 1))) ->strict(); @@ -79,7 +79,7 @@ public function testValidateArrayValueStrict(): void $this->assertFalse($rule->validate(['1', '2', '3', 4, 5, 6])->isValid()); } - public function testvalidateNot() + public function testValidateNot() { $rule = (new InRange(range(1, 10, 1))) ->not(); diff --git a/tests/Rule/IpTest.php b/tests/Rule/IpTest.php index 3ac058bc7..b76f16d64 100644 --- a/tests/Rule/IpTest.php +++ b/tests/Rule/IpTest.php @@ -90,7 +90,7 @@ public function testValidateNotAnIP($badIp): void $this->assertFalse($validator->validate($badIp)->isValid()); } - public function testvalidateIPv4() + public function testValidateIPv4() { $validator = new Ip(); @@ -125,7 +125,7 @@ public function testvalidateIPv4() } - public function testvalidateIPv6() + public function testValidateIPv6() { $validator = new Ip(); @@ -157,7 +157,7 @@ public function testvalidateIPv6() $this->assertFalse($validator->validate('!!2008:fa::0:1/64')->isValid()); } - public function testvalidateIPvBoth() + public function testValidateIPvBoth() { $validator = new Ip(); diff --git a/tests/Rule/MatchRegularExpressionTest.php b/tests/Rule/MatchRegularExpressionTest.php index 5db689061..855282fc8 100644 --- a/tests/Rule/MatchRegularExpressionTest.php +++ b/tests/Rule/MatchRegularExpressionTest.php @@ -10,7 +10,7 @@ */ class MatchRegularExpressionTest extends TestCase { - public function testvalidate(): void + public function testValidate(): void { $rule = new MatchRegularExpression('/^[a-zA-Z0-9](\.)?([^\/]*)$/m'); $this->assertTrue($rule->validate('b.4')->isValid()); diff --git a/tests/Rule/NumberTest.php b/tests/Rule/NumberTest.php index 534c9927c..0bda1ede6 100644 --- a/tests/Rule/NumberTest.php +++ b/tests/Rule/NumberTest.php @@ -50,7 +50,7 @@ protected function setUp(): void $this->oldLocale = setlocale(LC_NUMERIC, 0); } - public function testvalidateSimple(): void + public function testValidateSimple(): void { $rule = new Number(); $this->assertTrue($rule->validate(20)->isValid()); @@ -68,7 +68,7 @@ public function testvalidateSimple(): void $this->assertFalse($rule->validate('12:45')->isValid()); } - public function testvalidateSimpleInteger(): void + public function testValidateSimpleInteger(): void { $rule = (new Number()) ->integer(); @@ -83,7 +83,7 @@ public function testvalidateSimpleInteger(): void $this->assertFalse($rule->validate('0x14')->isValid()); // todo check this } - public function testvalidateAdvanced(): void + public function testValidateAdvanced(): void { $rule = new Number(); $this->assertTrue($rule->validate('-1.23')->isValid()); // signed float @@ -95,7 +95,7 @@ public function testvalidateAdvanced(): void $this->assertFalse($rule->validate('12.23^4')->isValid()); // expression instead of value } - public function testvalidateAdvancedInteger(): void + public function testValidateAdvancedInteger(): void { $rule = (new Number())->integer(); $this->assertFalse($rule->validate('-1.23')->isValid()); @@ -107,7 +107,7 @@ public function testvalidateAdvancedInteger(): void $this->assertFalse($rule->validate('12.23^4')->isValid()); } - public function testvalidateWithLocaleWhereDecimalPointIsComma(): void + public function testValidateWithLocaleWhereDecimalPointIsComma(): void { $rule = new Number(); @@ -120,7 +120,7 @@ public function testvalidateWithLocaleWhereDecimalPointIsComma(): void $this->restoreLocale(); } - public function testvalidateMin(): void + public function testValidateMin(): void { $rule = (new Number()) ->min(1); @@ -135,7 +135,7 @@ public function testvalidateMin(): void $this->assertTrue($rule->validate(PHP_INT_MAX + 1)->isValid()); } - public function testvalidateMinInteger(): void + public function testValidateMinInteger(): void { $rule = (new Number()) ->min(1) @@ -146,7 +146,7 @@ public function testvalidateMinInteger(): void $this->assertFalse($rule->validate('22e-12')->isValid(), '22e-12 is not a valid integer'); } - public function testvalidateMax(): void + public function testValidateMax(): void { $rule = (new Number()) ->max(1.25); @@ -157,7 +157,7 @@ public function testvalidateMax(): void $this->assertTrue($rule->validate('125e-2')->isValid()); } - public function testvalidateMaxInteger(): void + public function testValidateMaxInteger(): void { $rule = (new Number()) ->max(1.25) @@ -169,7 +169,7 @@ public function testvalidateMaxInteger(): void $this->assertFalse($rule->validate('125e-2')->isValid()); } - public function testvalidateRange(): void + public function testValidateRange(): void { $rule = (new Number()) ->min(-10) @@ -181,7 +181,7 @@ public function testvalidateRange(): void $this->assertFalse($rule->validate(21)->isValid()); } - public function testvalidateRangeInteger(): void + public function testValidateRangeInteger(): void { $rule = (new Number()) ->min(-10) diff --git a/tests/Rule/RequiredTest.php b/tests/Rule/RequiredTest.php index 19bc070a8..a2282c4b7 100644 --- a/tests/Rule/RequiredTest.php +++ b/tests/Rule/RequiredTest.php @@ -10,7 +10,7 @@ */ class RequiredTest extends TestCase { - public function testvalidateWithDefaults() + public function testValidateWithDefaults() { $val = new Required(); $this->assertFalse($val->validate(null)->isValid()); diff --git a/tests/Rule/UrlTest.php b/tests/Rule/UrlTest.php index 61287e2ea..a4b04fe68 100644 --- a/tests/Rule/UrlTest.php +++ b/tests/Rule/UrlTest.php @@ -10,7 +10,7 @@ */ class UrlTest extends TestCase { - public function testvalidate() + public function testValidate() { $val = new Url(); $this->assertFalse($val->validate('google.de')->isValid()); @@ -41,7 +41,7 @@ public function testvalidate() $this->assertFalse($val->validate('http://äüö?=!"§$%&/()=}][{³²€.edu')->isValid()); } - public function testvalidateWithoutScheme() + public function testValidateWithoutScheme() { $val = (new Url()) ->pattern('/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i'); From fe9bbd746df68b5c27a4c93ccf98092abdd996ee Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Tue, 25 Feb 2020 11:01:10 +0200 Subject: [PATCH 02/21] translator implementation and refactoring ... --- src/DataSetInterface.php | 1 + src/MissingAttributeException.php | 1 + src/Result.php | 9 +- src/ResultSet.php | 2 + src/Rule.php | 30 ++---- src/Rule/Boolean.php | 28 +++--- src/Rule/Callback.php | 7 +- src/Rule/CompareTo.php | 34 ++++--- src/Rule/Each.php | 34 +++++-- src/Rule/Email.php | 25 +++-- src/Rule/HasLength.php | 59 ++++++----- src/Rule/InRange.php | 82 ++++++--------- src/Rule/Ip.php | 151 ++++++++++++++++------------ src/Rule/MatchRegularExpression.php | 38 +++---- src/Rule/Number.php | 65 ++++++------ src/Rule/Required.php | 27 +++-- src/Rule/Url.php | 15 +-- src/RuleResult.php | 29 ++++++ src/Rules.php | 8 +- src/Validator.php | 38 +++++-- tests/ResultSetTest.php | 1 + tests/ResultTest.php | 1 + tests/Rule/BooleanTest.php | 2 + tests/Rule/CallbackTest.php | 17 ++-- tests/Rule/CompareToTest.php | 2 + tests/Rule/EachTest.php | 2 + tests/Rule/EmailTest.php | 44 ++++++-- tests/Rule/HasLengthTest.php | 18 +++- tests/RuleResultTest.php | 57 +++++++++++ tests/RulesTest.php | 33 +++--- tests/ValidatorTest.php | 52 ++++++---- 31 files changed, 543 insertions(+), 369 deletions(-) create mode 100644 src/RuleResult.php create mode 100644 tests/RuleResultTest.php diff --git a/src/DataSetInterface.php b/src/DataSetInterface.php index 7d0c8c9e8..864289852 100644 --- a/src/DataSetInterface.php +++ b/src/DataSetInterface.php @@ -1,5 +1,6 @@ valid; + return $this->errors === []; } public function addError(string $message): void { - $this->valid = false; $this->errors[] = $message; } diff --git a/src/ResultSet.php b/src/ResultSet.php index ea9144c1c..e038b2f58 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -1,5 +1,7 @@ skipOnEmpty && $this->isEmpty($value)) { - return new Result(); + return new RuleResult(); } return $this->validateValue($value, $dataSet); @@ -31,28 +32,9 @@ final public function validate($value, DataSetInterface $dataSet = null): Result * * @param mixed $value value to be validated * @param DataSetInterface|null $dataSet optional data set that could be used for contextual validation - * @return Result + * @return RuleResult */ - abstract protected function validateValue($value, DataSetInterface $dataSet = null): Result; - - protected function formatMessage(string $message, array $arguments = []): string - { - $replacements = []; - foreach ($arguments as $key => $value) { - if (is_array($value)) { - $value = 'array'; - } elseif (is_object($value)) { - $value = 'object'; - } elseif (is_resource($value)) { - $value = 'resource'; - } - - $replacements['{' . $key . '}'] = $value; - } - - // TODO: move it to upper level and make it configurable? - return strtr($message, $replacements); - } + abstract protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult; /** * @param bool $value if validation should be skipped if value validated is empty diff --git a/src/Rule/Boolean.php b/src/Rule/Boolean.php index 695563d48..35026e86f 100644 --- a/src/Rule/Boolean.php +++ b/src/Rule/Boolean.php @@ -1,10 +1,12 @@ strict) { $valid = $value === $this->trueValue || $value === $this->falseValue; @@ -63,22 +65,18 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $valid = $value == $this->trueValue || $value == $this->falseValue; } - $result = new Result(); + $result = new RuleResult(); if (!$valid) { - $result->addError($this->getMessage()); + $result->addError( + $this->message, + [ + 'true' => $this->trueValue === true ? 'true' : $this->trueValue, + 'false' => $this->falseValue === false ? 'false' : $this->falseValue, + ] + ); } return $result; } - - private function getMessage(): string - { - $arguments = [ - 'true' => $this->trueValue === true ? 'true' : $this->trueValue, - 'false' => $this->falseValue === false ? 'false' : $this->falseValue, - ]; - - return $this->formatMessage($this->message, $arguments); - } } diff --git a/src/Rule/Callback.php b/src/Rule/Callback.php index 1d71240c9..dfd3e4c2f 100644 --- a/src/Rule/Callback.php +++ b/src/Rule/Callback.php @@ -1,11 +1,12 @@ callback = $callback; } - protected function validateValue($value, DataSetInterface $dataSet = null): Result + protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult { $callback = $this->callback; return $callback($value, $dataSet); diff --git a/src/Rule/CompareTo.php b/src/Rule/CompareTo.php index ab0030e6c..03cbdf981 100644 --- a/src/Rule/CompareTo.php +++ b/src/Rule/CompareTo.php @@ -1,10 +1,12 @@ 1, ]; - private function getMessage(array $arguments): string + private function getMessage(): string { switch ($this->operator) { case '==': case '===': - return $this->formatMessage('Value must be equal to "{value}".', $arguments); + return 'Value must be equal to "{value}".'; case '!=': case '!==': - return $this->formatMessage('Value must not be equal to "{value}".', $arguments); + return 'Value must not be equal to "{value}".'; case '>': - return $this->formatMessage('Value must be greater than "{value}".', $arguments); + return 'Value must be greater than "{value}".'; case '>=': - return $this->formatMessage('Value must be greater than or equal to "{value}".', $arguments); + return 'Value must be greater than or equal to "{value}".'; case '<': - return $this->formatMessage('Value must be less than "{value}".', $arguments); + return 'Value must be less than "{value}".'; case '<=': - return $this->formatMessage('Value must be less than or equal to "{value}".', $arguments); + return 'Value must be less than or equal to "{value}".'; default: throw new \RuntimeException("Unknown operator: {$this->operator}"); } @@ -125,17 +127,21 @@ public function asNumber(): self return $this; } - protected function validateValue($value, DataSetInterface $dataSet = null): Result + protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult { - $result = new Result(); + $result = new RuleResult(); if ($this->compareValue === null) { throw new \RuntimeException('CompareValidator::compareValue must be set.'); } + if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) { - $result->addError($this->getMessage([ - 'value' => $this->compareValue, - ])); + $result->addError( + $this->getMessage(), + [ + 'value' => $this->compareValue, + ] + ); } return $result; diff --git a/src/Rule/Each.php b/src/Rule/Each.php index 958baafd7..af25a8226 100644 --- a/src/Rule/Each.php +++ b/src/Rule/Each.php @@ -1,11 +1,13 @@ rules = $rules; } - protected function validateValue($value, DataSetInterface $dataSet = null): Result + protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult { - $result = new Result(); + $result = new RuleResult(); if (!is_iterable($value)) { $result->addError($this->incorrectInputMessage); return $result; @@ -34,15 +36,29 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $itemResult = $this->rules->validate($item, $dataSet); if ($itemResult->isValid() === false) { foreach ($itemResult->getErrors() as $error) { - $message = $this->formatMessage($this->message, [ - 'error' => $error, - 'value' => $item, - ]); - $result->addError($message); + $result->addError( + $this->message, + [ + 'error' => $error, + 'value' => $item, + ] + ); } } } return $result; } + + public function incorrectInputMessage(string $message): self + { + $this->incorrectInputMessage = $message; + return $this; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } } diff --git a/src/Rule/Email.php b/src/Rule/Email.php index 16233080f..4687b3a63 100644 --- a/src/Rule/Email.php +++ b/src/Rule/Email.php @@ -1,10 +1,12 @@ (?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P.+)@(?P[^>]+))(?P>?))$/i', $value, $matches)) { + } elseif (!preg_match( + '/^(?P(?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P.+)@(?P[^>]+))(?P>?))$/i', + $value, + $matches + )) { $valid = false; } else { if ($this->enableIDN) { @@ -72,16 +77,18 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu // http://www.rfc-editor.org/errata_search.php?eid=1690 $valid = false; } else { - $valid = preg_match($this->pattern, $value) || ($this->allowName && preg_match($this->fullPattern, $value)); + $valid = preg_match($this->pattern, $value) || ($this->allowName && preg_match( + $this->fullPattern, + $value + )); if ($valid && $this->checkDNS) { $valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A'); } } } - if ($valid === false) { - $result->addError($this->formatMessage($this->message)); + $result->addError($this->message); } return $result; diff --git a/src/Rule/HasLength.php b/src/Rule/HasLength.php index 34e9fc80b..46f0cc178 100644 --- a/src/Rule/HasLength.php +++ b/src/Rule/HasLength.php @@ -1,15 +1,12 @@ addError($this->message); + return $result; + } + + $length = mb_strlen($value, $this->encoding); + + if ($this->min !== null && $length < $this->min) { + $result->addError($this->tooShortMessage, ['min' => $this->min]); + } + if ($this->max !== null && $length > $this->max) { + $result->addError($this->tooLongMessage, ['min' => $this->max]); + } + + return $result; + } + public function min(int $value): self { $this->min = $value; @@ -65,27 +83,6 @@ public function encoding(string $encoding): self return $this; } - protected function validateValue($value, DataSetInterface $dataSet = null): Result - { - $result = new Result(); - - if (!is_string($value)) { - $result->addError($this->formatMessage($this->message)); - return $result; - } - - $length = mb_strlen($value, $this->encoding); - - if ($this->min !== null && $length < $this->min) { - $result->addError($this->formatMessage($this->tooShortMessage, ['min' => $this->min])); - } - if ($this->max !== null && $length > $this->max) { - $result->addError($this->formatMessage($this->tooLongMessage, ['min' => $this->max])); - } - - return $result; - } - public function message(string $message): self { $this->message = $message; diff --git a/src/Rule/InRange.php b/src/Rule/InRange.php index 6c5743aab..626f44b9c 100644 --- a/src/Rule/InRange.php +++ b/src/Rule/InRange.php @@ -1,11 +1,13 @@ range = $range; - $this->message = $this->formatMessage('{attribute} is invalid.'); - } - - public function strict(): self - { - $this->strict = true; - return $this; - } - - public function not(): self - { - $this->not = true; - return $this; - } - - public function message(string $message): self - { - $this->message = $message; - return $this; } - public function allowArray(bool $value): self - { - // TODO: do we really need this option? - $this->allowArray = $value; - return $this; - } - - protected function validateValue($value, DataSetInterface $dataSet = null): Result + protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult { $in = false; - if ($this->allowArray - && ($value instanceof \Traversable || is_array($value)) - && ArrayHelper::isSubset($value, $this->range, $this->strict) + if ( + ($value instanceof \Traversable || is_array($value)) && + ArrayHelper::isSubset($value, $this->range, $this->strict) ) { $in = true; } @@ -98,7 +59,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $in = true; } - $result = new Result(); + $result = new RuleResult(); if ($this->not === $in) { $result->addError($this->message); @@ -106,4 +67,23 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu return $result; } + + + public function strict(): self + { + $this->strict = true; + return $this; + } + + public function not(): self + { + $this->not = true; + return $this; + } + + public function message(string $message): self + { + $this->message = $message; + return $this; + } } diff --git a/src/Rule/Ip.php b/src/Rule/Ip.php index 7d92b4423..290e0cb77 100644 --- a/src/Rule/Ip.php +++ b/src/Rule/Ip.php @@ -1,7 +1,10 @@ ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', '2001:db8::/32'], 'system' => ['multicast', 'linklocal', 'localhost', 'documentation'], ]; + /** * @var bool whether the validating value can be an IPv6 address. Defaults to `true`. */ private bool $allowIpv6 = true; + /** * @var bool whether the validating value can be an IPv4 address. Defaults to `true`. */ private bool $allowIpv4 = true; + /** * @var bool whether the address can be an IP with CIDR subnet, like `192.168.10.0/24`. * The following values are possible: @@ -83,10 +89,12 @@ class Ip extends Rule * - `true` - specifying a subnet is optional. */ private bool $allowSubnet = false; + /** * @var bool */ private bool $requireSubnet = false; + /** * @var bool whether address may have a [[NEGATION_CHAR]] character at the beginning. * Defaults to `false`. @@ -102,6 +110,7 @@ class Ip extends Rule * - `{value}`: the value of the attribute being validated */ private string $message = 'Must be a valid IP address.'; + /** * @var string user-defined error message is used when validation fails due to the disabled IPv6 validation. * @@ -113,6 +122,7 @@ class Ip extends Rule * @see allowIpv6 */ private string $ipv6NotAllowed = 'Must not be an IPv6 address.'; + /** * @var string user-defined error message is used when validation fails due to the disabled IPv4 validation. * @@ -124,6 +134,7 @@ class Ip extends Rule * @see allowIpv4 */ private string $ipv4NotAllowed = 'Must not be an IPv4 address.'; + /** * @var string user-defined error message is used when validation fails due to the wrong CIDR. * @@ -134,6 +145,7 @@ class Ip extends Rule * @see allowSubnet */ private string $wrongCidr = 'Contains wrong subnet mask.'; + /** * @var string user-defined error message is used when validation fails due to subnet [[subnet]] set to 'only', * but the CIDR prefix is not set. @@ -146,6 +158,7 @@ class Ip extends Rule * @see allowSubnet */ private string $noSubnet = 'Must be an IP address with specified subnet.'; + /** * @var string user-defined error message is used when validation fails * due to [[subnet]] is false, but CIDR prefix is present. @@ -158,6 +171,7 @@ class Ip extends Rule * @see allowSubnet */ private string $hasSubnet = 'Must not be a subnet.'; + /** * @var string user-defined error message is used when validation fails due to IP address * is not not allowed by [[ranges]] check. @@ -176,6 +190,72 @@ class Ip extends Rule */ private array $ranges = []; + protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + { + if (!$this->allowIpv4 && !$this->allowIpv6) { + throw new \RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time'); + } + $result = new RuleResult(); + if (!is_string($value)) { + $result->addError($this->message); + return $result; + } + + if (preg_match($this->getIpParsePattern(), $value, $matches) === 0) { + $result->addError($this->message); + return $result; + } + $negation = !empty($matches['not'] ?? null); + $ip = $matches['ip']; + $cidr = $matches['cidr'] ?? null; + $ipCidr = $matches['ipCidr']; + + try { + $ipVersion = IpHelper::getIpVersion($ip, false); + } catch (\InvalidArgumentException $e) { + $result->addError($this->message); + return $result; + } + + if ($this->requireSubnet === true && $cidr === null) { + $result->addError($this->noSubnet); + return $result; + } + if ($this->allowSubnet === false && $cidr !== null) { + $result->addError($this->hasSubnet); + return $result; + } + if ($this->allowNegation === false && $negation) { + $result->addError($this->message); + return $result; + } + if ($ipVersion === IpHelper::IPV6 && !$this->allowIpv6) { + $result->addError($this->ipv6NotAllowed); + return $result; + } + if ($ipVersion === IpHelper::IPV4 && !$this->allowIpv4) { + $result->addError($this->ipv4NotAllowed); + return $result; + } + if (!$result->isValid()) { + return $result; + } + if ($cidr !== null) { + try { + $cidr = IpHelper::getCidrBits($ipCidr); + } catch (\InvalidArgumentException $e) { + $result->addError($this->wrongCidr); + return $result; + } + } + if (!$this->isAllowed($ipCidr)) { + $result->addError($this->notInRange); + return $result; + } + + return $result; + } + /** * Set the IPv4 or IPv6 ranges that are allowed or forbidden. * @@ -288,72 +368,6 @@ public function disallowSubnet(): self return $this; } - protected function validateValue($value, DataSetInterface $dataSet = null): Result - { - if (!$this->allowIpv4 && !$this->allowIpv6) { - throw new \RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time'); - } - $result = new Result(); - if (!is_string($value)) { - $result->addError($this->formatMessage($this->message)); - return $result; - } - - if (preg_match($this->getIpParsePattern(), $value, $matches) === 0) { - $result->addError($this->formatMessage($this->message)); - return $result; - } - $negation = !empty($matches['not'] ?? null); - $ip = $matches['ip']; - $cidr = $matches['cidr'] ?? null; - $ipCidr = $matches['ipCidr']; - - try { - $ipVersion = IpHelper::getIpVersion($ip, false); - } catch (\InvalidArgumentException $e) { - $result->addError($this->formatMessage($this->message)); - return $result; - } - - if ($this->requireSubnet === true && $cidr === null) { - $result->addError($this->formatMessage($this->noSubnet)); - return $result; - } - if ($this->allowSubnet === false && $cidr !== null) { - $result->addError($this->formatMessage($this->hasSubnet)); - return $result; - } - if ($this->allowNegation === false && $negation) { - $result->addError($this->formatMessage($this->message)); - return $result; - } - if ($ipVersion === IpHelper::IPV6 && !$this->allowIpv6) { - $result->addError($this->formatMessage($this->ipv6NotAllowed)); - return $result; - } - if ($ipVersion === IpHelper::IPV4 && !$this->allowIpv4) { - $result->addError($this->formatMessage($this->ipv4NotAllowed)); - return $result; - } - if (!$result->isValid()) { - return $result; - } - if ($cidr !== null) { - try { - $cidr = IpHelper::getCidrBits($ipCidr); - } catch (\InvalidArgumentException $e) { - $result->addError($this->formatMessage($this->wrongCidr)); - return $result; - } - } - if (!$this->isAllowed($ipCidr)) { - $result->addError($this->formatMessage($this->notInRange)); - return $result; - } - - return $result; - } - /** * The method checks whether the IP address with specified CIDR is allowed according to the [[ranges]] list. * @@ -424,6 +438,9 @@ private function prepareRanges(array $ranges): array */ public function getIpParsePattern(): string { - return '/^(?' . preg_quote(static::NEGATION_CHAR, '/') . ')?(?(?(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?-?\d+))?)$/'; + return '/^(?' . preg_quote( + static::NEGATION_CHAR, + '/' + ) . ')?(?(?(?:' . IpHelper::IPV4_PATTERN . ')|(?:' . IpHelper::IPV6_PATTERN . '))(?:\/(?-?\d+))?)$/'; } } diff --git a/src/Rule/MatchRegularExpression.php b/src/Rule/MatchRegularExpression.php index d7db29033..141b8e3cb 100644 --- a/src/Rule/MatchRegularExpression.php +++ b/src/Rule/MatchRegularExpression.php @@ -1,12 +1,12 @@ pattern = $pattern; } + protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + { + $result = new RuleResult(); + + $valid = !is_array($value) && + ((!$this->not && preg_match($this->pattern, $value)) + || ($this->not && !preg_match($this->pattern, $value))); + + if (!$valid) { + $result->addError($this->message); + } + + return $result; + } + public function not(): self { $this->not = true; @@ -43,19 +58,4 @@ public function message(string $message): self $this->message = $message; return $this; } - - protected function validateValue($value, DataSetInterface $dataSet = null): Result - { - $result = new Result(); - - $valid = !is_array($value) && - ((!$this->not && preg_match($this->pattern, $value)) - || ($this->not && !preg_match($this->pattern, $value))); - - if (!$valid) { - $result->addError($this->formatMessage($this->message)); - } - - return $result; - } } diff --git a/src/Rule/Number.php b/src/Rule/Number.php index 03c8989e5..ce56dad52 100644 --- a/src/Rule/Number.php +++ b/src/Rule/Number.php @@ -1,11 +1,13 @@ asInteger === true) { - return $this->formatMessage('Value must be an integer.', $arguments); + $result = new RuleResult(); + + if ($this->isNotNumber($value)) { + $result->addError($this->getNotANumberMessage(), ['value' => $value]); + return $result; } - return $this->formatMessage('Value must be a number.', $arguments); - } + $pattern = $this->asInteger ? $this->integerPattern : $this->numberPattern; - private function getTooBigMessage(array $arguments): string - { - return $this->formatMessage($this->tooBigMessage, $arguments); - } + if (!preg_match($pattern, StringHelper::normalizeNumber($value))) { + $result->addError($this->getNotANumberMessage(), ['value' => $value]); + } elseif ($this->min !== null && $value < $this->min) { + $result->addError($this->tooSmallMessage, ['min' => $this->min]); + } elseif ($this->max !== null && $value > $this->max) { + $result->addError($this->tooBigMessage, ['max' => $this->max]); + } - private function getTooSmallMessage(array $arguments): string - { - return $this->formatMessage($this->tooSmallMessage, $arguments); + return $result; } public function integer(): self @@ -91,35 +96,27 @@ public function tooSmallMessage(string $message): self return $this; } - protected function validateValue($value, DataSetInterface $dataSet = null): Result + public function tooBigMessage(string $message): self { - $result = new Result(); - - if ($this->isNotNumber($value)) { - $result->addError($this->getNotANumberMessage(['value' => $value])); - return $result; - } - - $pattern = $this->asInteger ? $this->integerPattern : $this->numberPattern; + $this->tooBigMessage = $message; + return $this; + } - if (!preg_match($pattern, StringHelper::normalizeNumber($value))) { - $result->addError($this->getNotANumberMessage(['value' => $value])); - } elseif ($this->min !== null && $value < $this->min) { - $result->addError($this->getTooSmallMessage(['min' => $this->min])); - } elseif ($this->max !== null && $value > $this->max) { - $result->addError($this->getTooBigMessage(['max' => $this->max])); + private function getNotANumberMessage(): string + { + if ($this->asInteger === true) { + return 'Value must be an integer.'; } - - return $result; + return 'Value must be a number.'; } - /* + /** * @param mixed $value the data value to be checked. */ private function isNotNumber($value): bool { return is_array($value) - || (is_object($value) && !method_exists($value, '__toString')) - || (!is_object($value) && !is_scalar($value) && $value !== null); + || (is_object($value) && !method_exists($value, '__toString')) + || (!is_object($value) && !is_scalar($value) && $value !== null); } } diff --git a/src/Rule/Required.php b/src/Rule/Required.php index fac0e3b73..372d62ef9 100644 --- a/src/Rule/Required.php +++ b/src/Rule/Required.php @@ -1,10 +1,12 @@ strict && $value !== null) || (!$this->strict && !$this->isEmpty(is_string($value) ? trim($value) : $value))) { + if ( + ($this->strict && $value !== null) || + (!$this->strict && !$this->isEmpty(is_string($value) ? trim($value) : $value)) + ) { return $result; } - $result->addError($this->formatMessage($this->message)); + $result->addError($this->message); + return $result; } diff --git a/src/Rule/Url.php b/src/Rule/Url.php index 477b56085..f63029c22 100644 --- a/src/Rule/Url.php +++ b/src/Rule/Url.php @@ -1,10 +1,12 @@ addError($this->formatMessage($this->message)); + $result->addError($this->message); + return $result; } diff --git a/src/RuleResult.php b/src/RuleResult.php new file mode 100644 index 000000000..700cf03cb --- /dev/null +++ b/src/RuleResult.php @@ -0,0 +1,29 @@ +errors === []; + } + + public function addError(string $message, array $arguments = []): void + { + $this->errors[] = [$message, $arguments]; + } + + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Rules.php b/src/Rules.php index a3ad5e56b..7f9338b2d 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -1,5 +1,6 @@ rules[] = $this->normalizeRule($rule); } - public function validate($value, DataSetInterface $dataSet = null): Result + public function validate($value, DataSetInterface $dataSet = null): RuleResult { - $compoundResult = new Result(); + $compoundResult = new RuleResult(); foreach ($this->rules as $rule) { $ruleResult = $rule->validate($value, $dataSet); if ($ruleResult->isValid() === false) { foreach ($ruleResult->getErrors() as $error) { - $compoundResult->addError($error); + [$message, $arguments] = $error; + $compoundResult->addError($message, $arguments); } } } diff --git a/src/Validator.php b/src/Validator.php index 66936353b..81ac10f8d 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -1,26 +1,33 @@ translator = $translator; + $this->domain = $domain ?? 'validators'; + foreach ($rules as $attribute => $ruleSets) { foreach ($ruleSets as $rule) { if (is_callable($rule)) { @@ -35,7 +42,10 @@ public function validate(DataSetInterface $dataSet): ResultSet { $results = new ResultSet(); foreach ($this->attributeRules as $attribute => $rules) { - $results->addResult($attribute, $rules->validate($dataSet->getAttributeValue($attribute))); + $results->addResult( + $attribute, + $this->translateMessages($rules->validate($dataSet->getAttributeValue($attribute))) + ); } return $results; } @@ -48,4 +58,18 @@ public function addRule(string $attribute, Rule $rule): void $this->attributeRules[$attribute]->add($rule); } + + private function translateMessages(RuleResult $ruleResult): Result + { + $result = new Result(); + + if ($ruleResult->isValid() === false) { + foreach ($ruleResult->getErrors() as $error) { + [$message, $arguments] = $error; + $result->addError($this->translator->translate($message, $arguments, $this->domain)); + } + } + + return $result; + } } diff --git a/tests/ResultSetTest.php b/tests/ResultSetTest.php index 548c0a0da..8adb0a970 100644 --- a/tests/ResultSetTest.php +++ b/tests/ResultSetTest.php @@ -1,5 +1,6 @@ addError('Value should be 42!'); + $rule = new Callback( + static function ($value): RuleResult { + $result = new RuleResult(); + if ($value !== 42) { + $result->addError('Value should be 42!'); + } + return $result; } - return $result; - }); + ); $result = $rule->validate(41); diff --git a/tests/Rule/CompareToTest.php b/tests/Rule/CompareToTest.php index 190a3323a..5ab35bae6 100644 --- a/tests/Rule/CompareToTest.php +++ b/tests/Rule/CompareToTest.php @@ -1,5 +1,7 @@ assertFalse($validator->validate('Informtation info@oertliches.de')->isValid()); $this->assertTrue($validator->validate('test@example.com')->isValid()); $this->assertTrue($validator->validate('John Smith ')->isValid()); - $this->assertTrue($validator->validate('"This name is longer than 64 characters. Blah blah blah blah blah" ')->isValid()); + $this->assertTrue( + $validator->validate( + '"This name is longer than 64 characters. Blah blah blah blah blah" ' + )->isValid() + ); $this->assertFalse($validator->validate('John Smith ')->isValid()); - $this->assertFalse($validator->validate('Short Name ')->isValid()); - $this->assertFalse($validator->validate('Short Name ')->isValid()); + $this->assertFalse( + $validator->validate( + 'Short Name ' + )->isValid() + ); + $this->assertFalse( + $validator->validate( + 'Short Name ' + )->isValid() + ); $this->assertFalse($validator->validate(['developer@yiiframework.com'])->isValid()); } @@ -87,10 +101,22 @@ public function testValidateIdn(): void $this->assertTrue($validator->validate('')->isValid()); $this->assertTrue($validator->validate('test@example.com')->isValid()); $this->assertTrue($validator->validate('John Smith ')->isValid()); - $this->assertTrue($validator->validate('"Такое имя достаточно длинное, но оно все равно может пройти валидацию" ')->isValid()); + $this->assertTrue( + $validator->validate( + '"Такое имя достаточно длинное, но оно все равно может пройти валидацию" ' + )->isValid() + ); $this->assertFalse($validator->validate('John Smith ')->isValid()); - $this->assertFalse($validator->validate('Короткое имя <после-преобразования-в-idn-тут-будет-больше-чем-64-символа@пример.com>')->isValid()); - $this->assertFalse($validator->validate('Короткое имя <тест@это-доменное-имя.после-преобразования-в-idn.будет-содержать-больше-254-символов.бла-бла-бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.com>')->isValid()); + $this->assertFalse( + $validator->validate( + 'Короткое имя <после-преобразования-в-idn-тут-будет-больше-чем-64-символа@пример.com>' + )->isValid() + ); + $this->assertFalse( + $validator->validate( + 'Короткое имя <тест@это-доменное-имя.после-преобразования-в-idn.будет-содержать-больше-254-символов.бла-бла-бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.бла-бла-бла-бла-бла-бла.com>' + )->isValid() + ); } public function testValidateMx(): void @@ -114,8 +140,10 @@ public function testValidateMx(): void 'Ivan Petrov ', ]; foreach ($emails as $email) { - $this->assertTrue($validator->validate($email)->isValid(), - "Email: '$email' failed to validate(checkDNS=true, allowName=true)"); + $this->assertTrue( + $validator->validate($email)->isValid(), + "Email: '$email' failed to validate(checkDNS=true, allowName=true)" + ); } } diff --git a/tests/Rule/HasLengthTest.php b/tests/Rule/HasLengthTest.php index 0d1df1585..dc870eebd 100644 --- a/tests/Rule/HasLengthTest.php +++ b/tests/Rule/HasLengthTest.php @@ -1,5 +1,7 @@ validate(''); $this->assertFalse($result->isValid()); - $this->assertStringContainsString('{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.', $result->getErrors()[0]); + $this->assertStringContainsString( + 'This value should contain at least {min, number} {min, plural, one{character} other{characters}}.', + $result->getErrors()[0][0] + ); $this->assertTrue($rule->validate(str_repeat('x', 5))->isValid()); } @@ -76,7 +81,10 @@ public function testValidateMax(): void $result = $rule->validate(str_repeat('x', 1230)); $this->assertFalse($result->isValid()); - $this->assertStringContainsString('{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.', $result->getErrors()[0]); + $this->assertStringContainsString( + 'This value should contain at most {max, number} {max, plural, one{character} other{characters}}.', + $result->getErrors()[0][0] + ); } public function testValidateMessages() @@ -88,8 +96,8 @@ public function testValidateMessages() ->min(3) ->max(5); - $this->assertEquals('is not string error', $rule->validate(null)->getErrors()[0]); - $this->assertEquals('is to short test', $rule->validate(str_repeat('x', 1))->getErrors()[0]); - $this->assertEquals('is to long test', $rule->validate(str_repeat('x', 6))->getErrors()[0]); + $this->assertEquals('is not string error', $rule->validate(null)->getErrors()[0][0]); + $this->assertEquals('is to short test', $rule->validate(str_repeat('x', 1))->getErrors()[0][0]); + $this->assertEquals('is to long test', $rule->validate(str_repeat('x', 6))->getErrors()[0][0]); } } diff --git a/tests/RuleResultTest.php b/tests/RuleResultTest.php new file mode 100644 index 000000000..717f65238 --- /dev/null +++ b/tests/RuleResultTest.php @@ -0,0 +1,57 @@ +assertTrue($result->isValid()); + } + + /** + * @test + */ + public function errorsAreEmptyByDefault(): void + { + $result = new RuleResult(); + $this->assertEmpty($result->getErrors()); + } + + /** + * @test + */ + public function errorIsProperlyAdded(): void + { + $result = new RuleResult(); + $result->addError('Error'); + $this->assertEquals([['Error', []]], $result->getErrors()); + } + + public function errorIsProperlyAddedWithArguments(): void + { + $result = new RuleResult(); + $result->addError('Error', ['value' => 'test']); + $this->assertEquals([['Error', ['value' => 'test']]], $result->getErrors()); + } + + /** + * @test + */ + public function addingErrorChangesIsValid(): void + { + $result = new Result(); + $result->addError('Error'); + $this->assertFalse($result->isValid()); + } +} diff --git a/tests/RulesTest.php b/tests/RulesTest.php index f90b9dba9..c8a85f529 100644 --- a/tests/RulesTest.php +++ b/tests/RulesTest.php @@ -1,13 +1,14 @@ max(10) - ]); + $rules = new Rules( + [ + new Required(), + (new Number())->max(10) + ] + ); $result = $rules->validate(42); $this->assertFalse($result->isValid()); @@ -36,15 +39,17 @@ public function testArraySyntax(): void public function testCallback(): void { - $rules = new Rules([ - static function ($value): Result { - $result = new Result(); - if ($value !== 42) { - $result->addError('Value should be 42!'); + $rules = new Rules( + [ + static function ($value): RuleResult { + $result = new RuleResult(); + if ($value !== 42) { + $result->addError('Value should be 42!'); + } + return $result; } - return $result; - } - ]); + ] + ); $result = $rules->validate(41); $this->assertFalse($result->isValid()); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 02bdf3f89..516dc185e 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -1,5 +1,7 @@ getDataObject([ - 'bool' => true, - 'int' => 41, - ]); - - $validator = new Validator([ - 'bool' => [new Boolean()], - 'int' => [ - (new Number())->integer(), - (new Number())->integer()->min(44), - static function ($value): Result { - $result = new Result(); - if ($value !== 42) { - $result->addError('Value should be 42!'); + $dataObject = $this->getDataObject( + [ + 'bool' => true, + 'int' => 41, + ] + ); + + $validator = new Validator( + [ + 'bool' => [new Boolean()], + 'int' => [ + (new Number())->integer(), + (new Number())->integer()->min(44), + static function ($value): Result { + $result = new Result(); + if ($value !== 42) { + $result->addError('Value should be 42!'); + } + return $result; } - return $result; - } - ], - ]); + ], + ] + ); $results = $validator->validate($dataObject); @@ -71,10 +77,12 @@ static function ($value): Result { public function testAddingRulesOneByOne(): void { - $dataObject = $this->getDataObject([ - 'bool' => true, - 'int' => 42, - ]); + $dataObject = $this->getDataObject( + [ + 'bool' => true, + 'int' => 42, + ] + ); $validator = new Validator(); $validator->addRule('bool', new Boolean()); From 4e3284466825f232246efeadd233694a69a67a46 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Tue, 25 Feb 2020 15:29:15 +0200 Subject: [PATCH 03/21] translator implementation & refactoring in progress... --- src/Result.php | 29 ++++++++++++--- src/ResultSet.php | 13 +++++-- src/Rule.php | 10 ++--- src/Rule/Boolean.php | 6 +-- src/Rule/Callback.php | 4 +- src/Rule/CompareTo.php | 6 +-- src/Rule/Each.php | 6 +-- src/Rule/Email.php | 6 +-- src/Rule/HasLength.php | 6 +-- src/Rule/InRange.php | 6 +-- src/Rule/Ip.php | 7 ++-- src/Rule/MatchRegularExpression.php | 6 +-- src/Rule/Number.php | 6 +-- src/Rule/Required.php | 6 +-- src/Rule/Url.php | 6 +-- src/RuleResult.php | 29 --------------- src/Rules.php | 4 +- src/Validator.php | 36 +++++++----------- src/ValidatorInterface.php | 10 +++++ tests/Rule/CallbackTest.php | 6 +-- tests/RuleResultTest.php | 57 ----------------------------- tests/RulesTest.php | 6 +-- 22 files changed, 106 insertions(+), 165 deletions(-) delete mode 100644 src/RuleResult.php create mode 100644 src/ValidatorInterface.php delete mode 100644 tests/RuleResultTest.php diff --git a/src/Result.php b/src/Result.php index 53c7038f5..a6536d748 100644 --- a/src/Result.php +++ b/src/Result.php @@ -4,6 +4,8 @@ namespace Yiisoft\Validator; +use Yiisoft\I18n\TranslatorInterface; + final class Result { private array $errors = []; @@ -13,13 +15,30 @@ public function isValid(): bool return $this->errors === []; } - public function addError(string $message): void + public function addError(string $message, array $arguments = []): void { - $this->errors[] = $message; + $this->errors[] = [$message, $arguments]; } - public function getErrors(): array - { - return $this->errors; + public function getErrors( + TranslatorInterface $translator = null, + string $translationDomain = null, + string $translationLocale = null + ): array { + if ($translator === null) { + return $this->errors; + } + + $errors = []; + foreach ($this->errors as [$message, $parameters]) { + $errors[] = $translator->translate( + $message, + $parameters, + $translationDomain ?? 'validators', + $translationLocale + ); + } + + return $errors; } } diff --git a/src/ResultSet.php b/src/ResultSet.php index e038b2f58..0f1ea8c95 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -4,6 +4,8 @@ namespace Yiisoft\Validator; +use Yiisoft\I18n\TranslatorInterface; + /** * ResultSet stores validation result of each attribute from {@link DataSetInterface}. * It is typically obtained by validating data set with {@link Validator}. @@ -15,8 +17,13 @@ final class ResultSet implements \IteratorAggregate */ private array $results = []; - public function addResult(string $attribute, Result $result): void - { + public function addResult( + string $attribute, + Result $result, + ?TranslatorInterface $translator = null, + ?string $translationDomain = null, + ?string $translationLocale = null + ): void { if (!isset($this->results[$attribute])) { $this->results[$attribute] = $result; return; @@ -24,7 +31,7 @@ public function addResult(string $attribute, Result $result): void if ($result->isValid()) { return; } - foreach ($result->getErrors() as $error) { + foreach ($result->getErrors($translator, $translationDomain, $translationLocale) as $error) { $this->results[$attribute]->addError($error); } } diff --git a/src/Rule.php b/src/Rule.php index f01a059c0..ea59c1043 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -16,12 +16,12 @@ abstract class Rule * * @param mixed $value value to be validated * @param DataSetInterface|null $dataSet optional data set that could be used for contextual validation - * @return RuleResult + * @return Result */ - final public function validate($value, DataSetInterface $dataSet = null): RuleResult + final public function validate($value, DataSetInterface $dataSet = null): Result { if ($this->skipOnEmpty && $this->isEmpty($value)) { - return new RuleResult(); + return new Result(); } return $this->validateValue($value, $dataSet); @@ -32,9 +32,9 @@ final public function validate($value, DataSetInterface $dataSet = null): RuleRe * * @param mixed $value value to be validated * @param DataSetInterface|null $dataSet optional data set that could be used for contextual validation - * @return RuleResult + * @return Result */ - abstract protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult; + abstract protected function validateValue($value, DataSetInterface $dataSet = null): Result; /** * @param bool $value if validation should be skipped if value validated is empty diff --git a/src/Rule/Boolean.php b/src/Rule/Boolean.php index 35026e86f..224ffdf6d 100644 --- a/src/Rule/Boolean.php +++ b/src/Rule/Boolean.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -57,7 +57,7 @@ public function strict(bool $value): self return $this; } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { if ($this->strict) { $valid = $value === $this->trueValue || $value === $this->falseValue; @@ -65,7 +65,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Rule $valid = $value == $this->trueValue || $value == $this->falseValue; } - $result = new RuleResult(); + $result = new Result(); if (!$valid) { $result->addError( diff --git a/src/Rule/Callback.php b/src/Rule/Callback.php index dfd3e4c2f..b700fc40f 100644 --- a/src/Rule/Callback.php +++ b/src/Rule/Callback.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; class Callback extends Rule @@ -17,7 +17,7 @@ public function __construct(callable $callback) $this->callback = $callback; } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { $callback = $this->callback; return $callback($value, $dataSet); diff --git a/src/Rule/CompareTo.php b/src/Rule/CompareTo.php index 03cbdf981..d78f72cca 100644 --- a/src/Rule/CompareTo.php +++ b/src/Rule/CompareTo.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -127,9 +127,9 @@ public function asNumber(): self return $this; } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); if ($this->compareValue === null) { throw new \RuntimeException('CompareValidator::compareValue must be set.'); diff --git a/src/Rule/Each.php b/src/Rule/Each.php index af25a8226..52b52fb80 100644 --- a/src/Rule/Each.php +++ b/src/Rule/Each.php @@ -6,7 +6,7 @@ use Yiisoft\Validator\Rule; use Yiisoft\Validator\Rules; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -24,9 +24,9 @@ public function __construct(Rules $rules) $this->rules = $rules; } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); if (!is_iterable($value)) { $result->addError($this->incorrectInputMessage); return $result; diff --git a/src/Rule/Email.php b/src/Rule/Email.php index 4687b3a63..133a7f259 100644 --- a/src/Rule/Email.php +++ b/src/Rule/Email.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -45,9 +45,9 @@ class Email extends Rule private string $message = '{attribute} is not a valid email address.'; - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); if (!is_string($value)) { $valid = false; diff --git a/src/Rule/HasLength.php b/src/Rule/HasLength.php index 46f0cc178..c3d47eec4 100644 --- a/src/Rule/HasLength.php +++ b/src/Rule/HasLength.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -44,9 +44,9 @@ class HasLength extends Rule */ protected string $encoding = 'UTF-8'; - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); if (!is_string($value)) { $result->addError($this->message); diff --git a/src/Rule/InRange.php b/src/Rule/InRange.php index 626f44b9c..694e7f47e 100644 --- a/src/Rule/InRange.php +++ b/src/Rule/InRange.php @@ -6,7 +6,7 @@ use Yiisoft\Validator\Rule; use Yiisoft\Arrays\ArrayHelper; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -44,7 +44,7 @@ public function __construct($range) $this->range = $range; } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { $in = false; @@ -59,7 +59,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Rule $in = true; } - $result = new RuleResult(); + $result = new Result(); if ($this->not === $in) { $result->addError($this->message); diff --git a/src/Rule/Ip.php b/src/Rule/Ip.php index 290e0cb77..fcc3cc211 100644 --- a/src/Rule/Ip.php +++ b/src/Rule/Ip.php @@ -4,10 +4,9 @@ namespace Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\NetworkUtilities\IpHelper; use Yiisoft\Validator\DataSetInterface; -use Yiisoft\Validator\Result; use Yiisoft\Validator\Rule; /** @@ -190,12 +189,12 @@ class Ip extends Rule */ private array $ranges = []; - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { if (!$this->allowIpv4 && !$this->allowIpv6) { throw new \RuntimeException('Both IPv4 and IPv6 checks can not be disabled at the same time'); } - $result = new RuleResult(); + $result = new Result(); if (!is_string($value)) { $result->addError($this->message); return $result; diff --git a/src/Rule/MatchRegularExpression.php b/src/Rule/MatchRegularExpression.php index 141b8e3cb..e668af36e 100644 --- a/src/Rule/MatchRegularExpression.php +++ b/src/Rule/MatchRegularExpression.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -32,9 +32,9 @@ public function __construct(string $pattern) $this->pattern = $pattern; } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); $valid = !is_array($value) && ((!$this->not && preg_match($this->pattern, $value)) diff --git a/src/Rule/Number.php b/src/Rule/Number.php index ce56dad52..a0068c838 100644 --- a/src/Rule/Number.php +++ b/src/Rule/Number.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Strings\StringHelper; use Yiisoft\Validator\DataSetInterface; @@ -50,9 +50,9 @@ class Number extends Rule */ private string $numberPattern = '/^\s*[-+]?\d*\.?\d+([eE][-+]?\d+)?\s*$/'; - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); if ($this->isNotNumber($value)) { $result->addError($this->getNotANumberMessage(), ['value' => $value]); diff --git a/src/Rule/Required.php b/src/Rule/Required.php index 372d62ef9..ea020091d 100644 --- a/src/Rule/Required.php +++ b/src/Rule/Required.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -25,9 +25,9 @@ class Required extends Rule private string $message = 'Value cannot be blank.'; - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); if ( ($this->strict && $value !== null) || diff --git a/src/Rule/Url.php b/src/Rule/Url.php index f63029c22..05750c227 100644 --- a/src/Rule/Url.php +++ b/src/Rule/Url.php @@ -5,7 +5,7 @@ namespace Yiisoft\Validator\Rule; use Yiisoft\Validator\Rule; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\DataSetInterface; /** @@ -44,9 +44,9 @@ public function __construct() } } - protected function validateValue($value, DataSetInterface $dataSet = null): RuleResult + protected function validateValue($value, DataSetInterface $dataSet = null): Result { - $result = new RuleResult(); + $result = new Result(); // make sure the length is limited to avoid DOS attacks if (is_string($value) && strlen($value) < 2000) { diff --git a/src/RuleResult.php b/src/RuleResult.php deleted file mode 100644 index 700cf03cb..000000000 --- a/src/RuleResult.php +++ /dev/null @@ -1,29 +0,0 @@ -errors === []; - } - - public function addError(string $message, array $arguments = []): void - { - $this->errors[] = [$message, $arguments]; - } - - public function getErrors(): array - { - return $this->errors; - } -} diff --git a/src/Rules.php b/src/Rules.php index 7f9338b2d..3547562c0 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -41,9 +41,9 @@ public function add(Rule $rule): void $this->rules[] = $this->normalizeRule($rule); } - public function validate($value, DataSetInterface $dataSet = null): RuleResult + public function validate($value, DataSetInterface $dataSet = null): Result { - $compoundResult = new RuleResult(); + $compoundResult = new Result(); foreach ($this->rules as $rule) { $ruleResult = $rule->validate($value, $dataSet); if ($ruleResult->isValid() === false) { diff --git a/src/Validator.php b/src/Validator.php index 81ac10f8d..358cca1c2 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -10,10 +10,11 @@ /** * Validator validates {@link DataSetInterface} against rules set for data set attributes. */ -class Validator +class Validator implements ValidatorInterface { - private ?TranslatorInterface $translator = null; - private string $domain; + private ?TranslatorInterface $translator; + private ?string $translationDomain; + private ?string $translationLocale; /** * @var Rules[] @@ -23,11 +24,9 @@ class Validator public function __construct( iterable $rules = [], TranslatorInterface $translator = null, - string $domain = null + string $translationDomain = null, + string $translationLocale = null ) { - $this->translator = $translator; - $this->domain = $domain ?? 'validators'; - foreach ($rules as $attribute => $ruleSets) { foreach ($ruleSets as $rule) { if (is_callable($rule)) { @@ -36,6 +35,10 @@ public function __construct( $this->addRule($attribute, $rule); } } + + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->translationLocale = $translationLocale; } public function validate(DataSetInterface $dataSet): ResultSet @@ -44,7 +47,10 @@ public function validate(DataSetInterface $dataSet): ResultSet foreach ($this->attributeRules as $attribute => $rules) { $results->addResult( $attribute, - $this->translateMessages($rules->validate($dataSet->getAttributeValue($attribute))) + $rules->validate($dataSet->getAttributeValue($attribute)), + $this->translator, + $this->translationDomain, + $this->translationLocale ); } return $results; @@ -58,18 +64,4 @@ public function addRule(string $attribute, Rule $rule): void $this->attributeRules[$attribute]->add($rule); } - - private function translateMessages(RuleResult $ruleResult): Result - { - $result = new Result(); - - if ($ruleResult->isValid() === false) { - foreach ($ruleResult->getErrors() as $error) { - [$message, $arguments] = $error; - $result->addError($this->translator->translate($message, $arguments, $this->domain)); - } - } - - return $result; - } } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php new file mode 100644 index 000000000..91e1e119a --- /dev/null +++ b/src/ValidatorInterface.php @@ -0,0 +1,10 @@ +addError('Value should be 42!'); } diff --git a/tests/RuleResultTest.php b/tests/RuleResultTest.php deleted file mode 100644 index 717f65238..000000000 --- a/tests/RuleResultTest.php +++ /dev/null @@ -1,57 +0,0 @@ -assertTrue($result->isValid()); - } - - /** - * @test - */ - public function errorsAreEmptyByDefault(): void - { - $result = new RuleResult(); - $this->assertEmpty($result->getErrors()); - } - - /** - * @test - */ - public function errorIsProperlyAdded(): void - { - $result = new RuleResult(); - $result->addError('Error'); - $this->assertEquals([['Error', []]], $result->getErrors()); - } - - public function errorIsProperlyAddedWithArguments(): void - { - $result = new RuleResult(); - $result->addError('Error', ['value' => 'test']); - $this->assertEquals([['Error', ['value' => 'test']]], $result->getErrors()); - } - - /** - * @test - */ - public function addingErrorChangesIsValid(): void - { - $result = new Result(); - $result->addError('Error'); - $this->assertFalse($result->isValid()); - } -} diff --git a/tests/RulesTest.php b/tests/RulesTest.php index c8a85f529..6936cf54e 100644 --- a/tests/RulesTest.php +++ b/tests/RulesTest.php @@ -6,7 +6,7 @@ use Yiisoft\Validator\Rules; use PHPUnit\Framework\TestCase; -use Yiisoft\Validator\RuleResult; +use Yiisoft\Validator\Result; use Yiisoft\Validator\Rule\Number; use Yiisoft\Validator\Rule\Required; @@ -41,8 +41,8 @@ public function testCallback(): void { $rules = new Rules( [ - static function ($value): RuleResult { - $result = new RuleResult(); + static function ($value): Result { + $result = new Result(); if ($value !== 42) { $result->addError('Value should be 42!'); } From 9cc2a534328e05746b967a94fe6179b725424ebf Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Tue, 25 Feb 2020 16:05:11 +0200 Subject: [PATCH 04/21] minor refactoring --- src/ResultSet.php | 21 ++++++++++++++++----- src/Validator.php | 12 +++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/ResultSet.php b/src/ResultSet.php index 0f1ea8c95..c1a523d24 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -12,6 +12,20 @@ */ final class ResultSet implements \IteratorAggregate { + private ?TranslatorInterface $translator; + private ?string $translationDomain; + private ?string $translationLocale; + + public function __construct( + ?TranslatorInterface $translator = null, + ?string $translationDomain = null, + ?string $translationLocale = null + ) { + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->translationLocale = $translationLocale; + } + /** * @var Result[] */ @@ -19,10 +33,7 @@ final class ResultSet implements \IteratorAggregate public function addResult( string $attribute, - Result $result, - ?TranslatorInterface $translator = null, - ?string $translationDomain = null, - ?string $translationLocale = null + Result $result ): void { if (!isset($this->results[$attribute])) { $this->results[$attribute] = $result; @@ -31,7 +42,7 @@ public function addResult( if ($result->isValid()) { return; } - foreach ($result->getErrors($translator, $translationDomain, $translationLocale) as $error) { + foreach ($result->getErrors($this->translator, $this->translationDomain, $this->translationLocale) as $error) { $this->results[$attribute]->addError($error); } } diff --git a/src/Validator.php b/src/Validator.php index 358cca1c2..7f87fea92 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -43,14 +43,16 @@ public function __construct( public function validate(DataSetInterface $dataSet): ResultSet { - $results = new ResultSet(); + $results = new ResultSet( + $this->translator, + $this->translationDomain, + $this->translationLocale + ); + foreach ($this->attributeRules as $attribute => $rules) { $results->addResult( $attribute, - $rules->validate($dataSet->getAttributeValue($attribute)), - $this->translator, - $this->translationDomain, - $this->translationLocale + $rules->validate($dataSet->getAttributeValue($attribute)) ); } return $results; From 8e2d2667420fb9749b343590c34186ac18192498 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Tue, 25 Feb 2020 16:11:19 +0200 Subject: [PATCH 05/21] minor refactoring --- src/Rules.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Rules.php b/src/Rules.php index 3547562c0..f6504b693 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -47,8 +47,7 @@ public function validate($value, DataSetInterface $dataSet = null): Result foreach ($this->rules as $rule) { $ruleResult = $rule->validate($value, $dataSet); if ($ruleResult->isValid() === false) { - foreach ($ruleResult->getErrors() as $error) { - [$message, $arguments] = $error; + foreach ($ruleResult->getErrors() as [$message, $arguments]) { $compoundResult->addError($message, $arguments); } } From dde6cd7e923adb1936952cb6439fdf09f88e5309 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 11:16:19 +0200 Subject: [PATCH 06/21] moved message translation to rule --- src/Result.php | 29 +++++--------------------- src/ResultSet.php | 18 +--------------- src/Rule.php | 31 ++++++++++++++++++++++++++++ src/Rule/Boolean.php | 15 +++++++------- src/Rule/CompareTo.php | 10 +++++---- src/Rule/Each.php | 12 ++++++----- src/Rule/Email.php | 24 ++-------------------- src/Rule/HasLength.php | 7 ++----- src/Rule/InRange.php | 2 +- src/Rule/Ip.php | 22 ++++++++++---------- src/Rule/MatchRegularExpression.php | 2 +- src/Rule/Number.php | 8 ++++---- src/Rule/Required.php | 23 ++------------------- src/Rule/Url.php | 12 +++++++---- src/Rules.php | 32 +++++++++++++++++++++++++---- src/Validator.php | 13 ++++++------ 16 files changed, 123 insertions(+), 137 deletions(-) diff --git a/src/Result.php b/src/Result.php index a6536d748..53c7038f5 100644 --- a/src/Result.php +++ b/src/Result.php @@ -4,8 +4,6 @@ namespace Yiisoft\Validator; -use Yiisoft\I18n\TranslatorInterface; - final class Result { private array $errors = []; @@ -15,30 +13,13 @@ public function isValid(): bool return $this->errors === []; } - public function addError(string $message, array $arguments = []): void + public function addError(string $message): void { - $this->errors[] = [$message, $arguments]; + $this->errors[] = $message; } - public function getErrors( - TranslatorInterface $translator = null, - string $translationDomain = null, - string $translationLocale = null - ): array { - if ($translator === null) { - return $this->errors; - } - - $errors = []; - foreach ($this->errors as [$message, $parameters]) { - $errors[] = $translator->translate( - $message, - $parameters, - $translationDomain ?? 'validators', - $translationLocale - ); - } - - return $errors; + public function getErrors(): array + { + return $this->errors; } } diff --git a/src/ResultSet.php b/src/ResultSet.php index c1a523d24..758cc5084 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -4,28 +4,12 @@ namespace Yiisoft\Validator; -use Yiisoft\I18n\TranslatorInterface; - /** * ResultSet stores validation result of each attribute from {@link DataSetInterface}. * It is typically obtained by validating data set with {@link Validator}. */ final class ResultSet implements \IteratorAggregate { - private ?TranslatorInterface $translator; - private ?string $translationDomain; - private ?string $translationLocale; - - public function __construct( - ?TranslatorInterface $translator = null, - ?string $translationDomain = null, - ?string $translationLocale = null - ) { - $this->translator = $translator; - $this->translationDomain = $translationDomain; - $this->translationLocale = $translationLocale; - } - /** * @var Result[] */ @@ -42,7 +26,7 @@ public function addResult( if ($result->isValid()) { return; } - foreach ($result->getErrors($this->translator, $this->translationDomain, $this->translationLocale) as $error) { + foreach ($result->getErrors() as $error) { $this->results[$attribute]->addError($error); } } diff --git a/src/Rule.php b/src/Rule.php index ea59c1043..23051b9e7 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -4,11 +4,16 @@ namespace Yiisoft\Validator; +use Yiisoft\I18n\TranslatorInterface; + /** * Rule represents a single value validation rule. */ abstract class Rule { + private ?TranslatorInterface $translator = null; + private ?string $translationDomain = null; + private ?string $translationLocale = null; private bool $skipOnEmpty = false; /** @@ -36,6 +41,32 @@ final public function validate($value, DataSetInterface $dataSet = null): Result */ abstract protected function validateValue($value, DataSetInterface $dataSet = null): Result; + public function setTranslator(TranslatorInterface $translator): self + { + $this->translator = $translator; + return $this; + } + + public function setTranslationDomain(string $translation): self + { + $this->translationDomain = $translation; + return $this; + } + + public function setTranslationLocale(string $locale): self + { + $this->translationLocale = $locale; + return $this; + } + + public function translateMessage(string $message, array $arguments = []): string + { + if ($this->translator === null) { + return $message; + } + return $this->translator->translate($message, $arguments, $this->translationDomain, $this->translationLocale); + } + /** * @param bool $value if validation should be skipped if value validated is empty * @return self diff --git a/src/Rule/Boolean.php b/src/Rule/Boolean.php index 224ffdf6d..ab69bde42 100644 --- a/src/Rule/Boolean.php +++ b/src/Rule/Boolean.php @@ -28,9 +28,6 @@ class Boolean extends Rule */ private bool $strict = false; - /** - * @var string - */ private string $message = 'The value must be either "{true}" or "{false}".'; public function message(string $message): self @@ -69,11 +66,13 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu if (!$valid) { $result->addError( - $this->message, - [ - 'true' => $this->trueValue === true ? 'true' : $this->trueValue, - 'false' => $this->falseValue === false ? 'false' : $this->falseValue, - ] + $this->translateMessage( + $this->message, + [ + 'true' => $this->trueValue === true ? 'true' : $this->trueValue, + 'false' => $this->falseValue === false ? 'false' : $this->falseValue, + ] + ) ); } diff --git a/src/Rule/CompareTo.php b/src/Rule/CompareTo.php index d78f72cca..999a87072 100644 --- a/src/Rule/CompareTo.php +++ b/src/Rule/CompareTo.php @@ -137,10 +137,12 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) { $result->addError( - $this->getMessage(), - [ - 'value' => $this->compareValue, - ] + $this->translateMessage( + $this->getMessage(), + [ + 'value' => $this->compareValue, + ] + ) ); } diff --git a/src/Rule/Each.php b/src/Rule/Each.php index 52b52fb80..77e5e95b7 100644 --- a/src/Rule/Each.php +++ b/src/Rule/Each.php @@ -37,11 +37,13 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu if ($itemResult->isValid() === false) { foreach ($itemResult->getErrors() as $error) { $result->addError( - $this->message, - [ - 'error' => $error, - 'value' => $item, - ] + $this->translateMessage( + $this->message, + [ + 'error' => $error, + 'value' => $item, + ] + ) ); } } diff --git a/src/Rule/Email.php b/src/Rule/Email.php index 133a7f259..25f1e859f 100644 --- a/src/Rule/Email.php +++ b/src/Rule/Email.php @@ -43,7 +43,7 @@ class Email extends Rule */ private bool $enableIDN = false; - private string $message = '{attribute} is not a valid email address.'; + private string $message = 'This value is not a valid email address.'; protected function validateValue($value, DataSetInterface $dataSet = null): Result { @@ -88,7 +88,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu } if ($valid === false) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); } return $result; @@ -99,50 +99,30 @@ private function idnToAscii($idn) return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46); } - /** - * @param bool $allowName - * - * @return Email - */ public function allowName(bool $allowName): self { $this->allowName = $allowName; - return $this; } - /** - * @param bool $checkDNS - * - * @return Email - */ public function checkDNS(bool $checkDNS): self { $this->checkDNS = $checkDNS; - return $this; } - /** - * @param bool $enableIDN - * - * @return Email - */ public function enableIDN(bool $enableIDN): self { if ($enableIDN && !function_exists('idn_to_ascii')) { throw new \RuntimeException('In order to use IDN validation intl extension must be installed and enabled.'); } - $this->enableIDN = $enableIDN; - return $this; } public function message(string $message): self { $this->message = $message; - return $this; } } diff --git a/src/Rule/HasLength.php b/src/Rule/HasLength.php index c3d47eec4..7797b6839 100644 --- a/src/Rule/HasLength.php +++ b/src/Rule/HasLength.php @@ -56,10 +56,10 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $length = mb_strlen($value, $this->encoding); if ($this->min !== null && $length < $this->min) { - $result->addError($this->tooShortMessage, ['min' => $this->min]); + $result->addError($this->translateMessage($this->tooShortMessage, ['min' => $this->min])); } if ($this->max !== null && $length > $this->max) { - $result->addError($this->tooLongMessage, ['min' => $this->max]); + $result->addError($this->translateMessage($this->tooLongMessage, ['min' => $this->max])); } return $result; @@ -86,21 +86,18 @@ public function encoding(string $encoding): self public function message(string $message): self { $this->message = $message; - return $this; } public function tooShortMessage(string $message): self { $this->tooShortMessage = $message; - return $this; } public function tooLongMessage(string $message): self { $this->tooLongMessage = $message; - return $this; } } diff --git a/src/Rule/InRange.php b/src/Rule/InRange.php index 694e7f47e..d04735430 100644 --- a/src/Rule/InRange.php +++ b/src/Rule/InRange.php @@ -62,7 +62,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $result = new Result(); if ($this->not === $in) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); } return $result; diff --git a/src/Rule/Ip.php b/src/Rule/Ip.php index fcc3cc211..0b4a25414 100644 --- a/src/Rule/Ip.php +++ b/src/Rule/Ip.php @@ -196,12 +196,12 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu } $result = new Result(); if (!is_string($value)) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); return $result; } if (preg_match($this->getIpParsePattern(), $value, $matches) === 0) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); return $result; } $negation = !empty($matches['not'] ?? null); @@ -212,28 +212,28 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu try { $ipVersion = IpHelper::getIpVersion($ip, false); } catch (\InvalidArgumentException $e) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); return $result; } if ($this->requireSubnet === true && $cidr === null) { - $result->addError($this->noSubnet); + $result->addError($this->translateMessage($this->noSubnet)); return $result; } if ($this->allowSubnet === false && $cidr !== null) { - $result->addError($this->hasSubnet); + $result->addError($this->translateMessage($this->hasSubnet)); return $result; } if ($this->allowNegation === false && $negation) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); return $result; } if ($ipVersion === IpHelper::IPV6 && !$this->allowIpv6) { - $result->addError($this->ipv6NotAllowed); + $result->addError($this->translateMessage($this->ipv6NotAllowed)); return $result; } if ($ipVersion === IpHelper::IPV4 && !$this->allowIpv4) { - $result->addError($this->ipv4NotAllowed); + $result->addError($this->translateMessage($this->ipv4NotAllowed)); return $result; } if (!$result->isValid()) { @@ -241,14 +241,14 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu } if ($cidr !== null) { try { - $cidr = IpHelper::getCidrBits($ipCidr); + IpHelper::getCidrBits($ipCidr); } catch (\InvalidArgumentException $e) { - $result->addError($this->wrongCidr); + $result->addError($this->translateMessage($this->wrongCidr)); return $result; } } if (!$this->isAllowed($ipCidr)) { - $result->addError($this->notInRange); + $result->addError($this->translateMessage($this->notInRange)); return $result; } diff --git a/src/Rule/MatchRegularExpression.php b/src/Rule/MatchRegularExpression.php index e668af36e..6e5d57c5d 100644 --- a/src/Rule/MatchRegularExpression.php +++ b/src/Rule/MatchRegularExpression.php @@ -41,7 +41,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu || ($this->not && !preg_match($this->pattern, $value))); if (!$valid) { - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); } return $result; diff --git a/src/Rule/Number.php b/src/Rule/Number.php index a0068c838..6a36a2a2b 100644 --- a/src/Rule/Number.php +++ b/src/Rule/Number.php @@ -55,18 +55,18 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $result = new Result(); if ($this->isNotNumber($value)) { - $result->addError($this->getNotANumberMessage(), ['value' => $value]); + $result->addError($this->translateMessage($this->getNotANumberMessage(), ['value' => $value])); return $result; } $pattern = $this->asInteger ? $this->integerPattern : $this->numberPattern; if (!preg_match($pattern, StringHelper::normalizeNumber($value))) { - $result->addError($this->getNotANumberMessage(), ['value' => $value]); + $result->addError($this->translateMessage($this->getNotANumberMessage(), ['value' => $value])); } elseif ($this->min !== null && $value < $this->min) { - $result->addError($this->tooSmallMessage, ['min' => $this->min]); + $result->addError($this->translateMessage($this->tooSmallMessage, ['min' => $this->min])); } elseif ($this->max !== null && $value > $this->max) { - $result->addError($this->tooBigMessage, ['max' => $this->max]); + $result->addError($this->translateMessage($this->tooBigMessage, ['max' => $this->max])); } return $result; diff --git a/src/Rule/Required.php b/src/Rule/Required.php index ea020091d..9db65e466 100644 --- a/src/Rule/Required.php +++ b/src/Rule/Required.php @@ -13,30 +13,17 @@ */ class Required extends Rule { - /** - * @var bool whether the comparison between the attribute value and [[requiredValue]] is strict. - * When this is true, both the values and types must match. - * Defaults to false, meaning only the values need to match. - * Note that when [[requiredValue]] is null, if this property is true, the validator will check - * if the attribute value is null; If this property is false, the validator will call [[isEmpty]] - * to check if the attribute value is empty. - */ - private bool $strict = false; - private string $message = 'Value cannot be blank.'; protected function validateValue($value, DataSetInterface $dataSet = null): Result { $result = new Result(); - if ( - ($this->strict && $value !== null) || - (!$this->strict && !$this->isEmpty(is_string($value) ? trim($value) : $value)) - ) { + if (!$this->isEmpty(is_string($value) ? trim($value) : $value)) { return $result; } - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); return $result; } @@ -46,10 +33,4 @@ public function message(string $message): self $this->message = $message; return $this; } - - public function strict(): self - { - $this->strict = true; - return $this; - } } diff --git a/src/Rule/Url.php b/src/Rule/Url.php index 05750c227..bf995b2f2 100644 --- a/src/Rule/Url.php +++ b/src/Rule/Url.php @@ -57,9 +57,13 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu } if ($this->enableIDN) { - $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) { - return '://' . $this->idnToAscii($matches[1]); - }, $value); + $value = preg_replace_callback( + '/:\/\/([^\/]+)/', + function ($matches) { + return '://' . $this->idnToAscii($matches[1]); + }, + $value + ); } if (preg_match($pattern, $value)) { @@ -67,7 +71,7 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu } } - $result->addError($this->message); + $result->addError($this->translateMessage($this->message)); return $result; } diff --git a/src/Rules.php b/src/Rules.php index f6504b693..b808563b4 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -5,6 +5,7 @@ namespace Yiisoft\Validator; use Yiisoft\Validator\Rule\Callback; +use Yiisoft\I18n\TranslatorInterface; /** * Rules represents multiple rules for a single value @@ -15,12 +16,23 @@ class Rules * @var Rule[] */ private array $rules = []; + private ?TranslatorInterface $translator; + private ?string $translationDomain; + private ?string $translationLocale; - public function __construct(iterable $rules = []) - { + public function __construct( + iterable $rules = [], + ?TranslatorInterface $translator = null, + ?string $translationDomain = null, + ?string $translationLocale = null + ) { foreach ($rules as $rule) { $this->rules[] = $this->normalizeRule($rule); } + + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->translationLocale = $translationLocale; } private function normalizeRule($rule): Rule @@ -33,6 +45,18 @@ private function normalizeRule($rule): Rule throw new \InvalidArgumentException('Rule should be either instance of Rule class or a callable'); } + if ($this->translator !== null) { + $rule->setTranslator($this->translator); + } + + if ($this->translationDomain !== null) { + $rule->setTranslationDomain($this->translationDomain); + } + + if ($this->translationLocale !== null) { + $rule->setTranslationLocale($this->translationLocale); + } + return $rule; } @@ -47,8 +71,8 @@ public function validate($value, DataSetInterface $dataSet = null): Result foreach ($this->rules as $rule) { $ruleResult = $rule->validate($value, $dataSet); if ($ruleResult->isValid() === false) { - foreach ($ruleResult->getErrors() as [$message, $arguments]) { - $compoundResult->addError($message, $arguments); + foreach ($ruleResult->getErrors() as $message) { + $compoundResult->addError($message); } } } diff --git a/src/Validator.php b/src/Validator.php index 7f87fea92..10005eb33 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -43,11 +43,7 @@ public function __construct( public function validate(DataSetInterface $dataSet): ResultSet { - $results = new ResultSet( - $this->translator, - $this->translationDomain, - $this->translationLocale - ); + $results = new ResultSet(); foreach ($this->attributeRules as $attribute => $rules) { $results->addResult( @@ -61,7 +57,12 @@ public function validate(DataSetInterface $dataSet): ResultSet public function addRule(string $attribute, Rule $rule): void { if (!isset($this->attributeRules[$attribute])) { - $this->attributeRules[$attribute] = new Rules(); + $this->attributeRules[$attribute] = new Rules( + [], + $this->translator, + $this->translationDomain, + $this->translationLocale + ); } $this->attributeRules[$attribute]->add($rule); From 7f1f51dac07977f054b65574d323aeb272c0ab51 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 11:20:19 +0200 Subject: [PATCH 07/21] add default translation domain --- src/Rule.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Rule.php b/src/Rule.php index 23051b9e7..1acd8a087 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -64,7 +64,12 @@ public function translateMessage(string $message, array $arguments = []): string if ($this->translator === null) { return $message; } - return $this->translator->translate($message, $arguments, $this->translationDomain, $this->translationLocale); + return $this->translator->translate( + $message, + $arguments, + $this->translationDomain ?? 'validators', + $this->translationLocale + ); } /** From 5e2df48066929a1acb2589856d74c222562d89cb Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 11:27:04 +0200 Subject: [PATCH 08/21] minor refactoring --- src/Rule/Required.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Rule/Required.php b/src/Rule/Required.php index 9db65e466..8489cadfa 100644 --- a/src/Rule/Required.php +++ b/src/Rule/Required.php @@ -19,12 +19,10 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu { $result = new Result(); - if (!$this->isEmpty(is_string($value) ? trim($value) : $value)) { - return $result; + if ($this->isEmpty(is_string($value) ? trim($value) : $value)) { + $result->addError($this->translateMessage($this->message)); } - $result->addError($this->translateMessage($this->message)); - return $result; } From 9225b2c617bf9285ccca62251259b9210a491e5d Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 12:00:55 +0200 Subject: [PATCH 09/21] added ability to translate callback message --- src/Rule/Callback.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Rule/Callback.php b/src/Rule/Callback.php index b700fc40f..40f03afdc 100644 --- a/src/Rule/Callback.php +++ b/src/Rule/Callback.php @@ -19,7 +19,17 @@ public function __construct(callable $callback) protected function validateValue($value, DataSetInterface $dataSet = null): Result { + $result = new Result(); $callback = $this->callback; - return $callback($value, $dataSet); + /** + * @var $callbackResult Result + */ + $callbackResult = $callback($value, $dataSet); + if ($callbackResult->isValid() === false) { + foreach ($callbackResult->getErrors() as $message) { + $result->addError($this->translateMessage($message)); + } + } + return $result; } } From 953393d15c90624f161b472af5c7f7528067df29 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 12:01:26 +0200 Subject: [PATCH 10/21] add fallback formatMessage --- src/Rule.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Rule.php b/src/Rule.php index 1acd8a087..e367dbf06 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -62,8 +62,9 @@ public function setTranslationLocale(string $locale): self public function translateMessage(string $message, array $arguments = []): string { if ($this->translator === null) { - return $message; + return $this->formatMessage($message); } + return $this->translator->translate( $message, $arguments, @@ -83,6 +84,24 @@ public function skipOnEmpty(bool $value): self return $new; } + protected function formatMessage(string $message, array $arguments = []): string + { + $replacements = []; + foreach ($arguments as $key => $value) { + if (is_array($value)) { + $value = 'array'; + } elseif (is_object($value)) { + $value = 'object'; + } elseif (is_resource($value)) { + $value = 'resource'; + } + + $replacements['{' . $key . '}'] = $value; + } + + return strtr($message, $replacements); + } + /** * Checks if the given value is empty. * A value is considered empty if it is null, an empty array, or an empty string. From 41213a65deb6e33e2f7ff66fa6d103e387f15f94 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 12:34:27 +0200 Subject: [PATCH 11/21] change to private "formatMessage" --- src/Rule.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Rule.php b/src/Rule.php index e367dbf06..6cf3f68cb 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -84,7 +84,7 @@ public function skipOnEmpty(bool $value): self return $new; } - protected function formatMessage(string $message, array $arguments = []): string + private function formatMessage(string $message, array $arguments = []): string { $replacements = []; foreach ($arguments as $key => $value) { @@ -95,10 +95,8 @@ protected function formatMessage(string $message, array $arguments = []): string } elseif (is_resource($value)) { $value = 'resource'; } - $replacements['{' . $key . '}'] = $value; } - return strtr($message, $replacements); } From a67aacbe880705cd36c38e78c8a8a673338bbe3f Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 12:46:29 +0200 Subject: [PATCH 12/21] minor fix --- src/Rules.php | 8 ++++---- src/Validator.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Rules.php b/src/Rules.php index b808563b4..1fb778060 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -26,13 +26,13 @@ public function __construct( ?string $translationDomain = null, ?string $translationLocale = null ) { - foreach ($rules as $rule) { - $this->rules[] = $this->normalizeRule($rule); - } - $this->translator = $translator; $this->translationDomain = $translationDomain; $this->translationLocale = $translationLocale; + + foreach ($rules as $rule) { + $this->rules[] = $this->normalizeRule($rule); + } } private function normalizeRule($rule): Rule diff --git a/src/Validator.php b/src/Validator.php index 10005eb33..0da1f9ca4 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -27,6 +27,10 @@ public function __construct( string $translationDomain = null, string $translationLocale = null ) { + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->translationLocale = $translationLocale; + foreach ($rules as $attribute => $ruleSets) { foreach ($ruleSets as $rule) { if (is_callable($rule)) { @@ -35,10 +39,6 @@ public function __construct( $this->addRule($attribute, $rule); } } - - $this->translator = $translator; - $this->translationDomain = $translationDomain; - $this->translationLocale = $translationLocale; } public function validate(DataSetInterface $dataSet): ResultSet From 0498ad689dafc8756e778cd63d54c54fa3fc575e Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Wed, 26 Feb 2020 17:40:00 +0200 Subject: [PATCH 13/21] fix --- src/Rule.php | 2 +- src/Rule/Email.php | 4 ++-- src/Rule/InRange.php | 1 - tests/Rule/CallbackTest.php | 1 + tests/Rule/HasLengthTest.php | 11 ++++++----- tests/Rule/InRangeTest.php | 27 ++++++++++----------------- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/Rule.php b/src/Rule.php index 6cf3f68cb..08387ab7f 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -62,7 +62,7 @@ public function setTranslationLocale(string $locale): self public function translateMessage(string $message, array $arguments = []): string { if ($this->translator === null) { - return $this->formatMessage($message); + return $this->formatMessage($message, $arguments); } return $this->translator->translate( diff --git a/src/Rule/Email.php b/src/Rule/Email.php index 25f1e859f..417bf524f 100644 --- a/src/Rule/Email.php +++ b/src/Rule/Email.php @@ -64,11 +64,11 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu $value = $matches['name'] . $matches['open'] . $matches['local'] . '@' . $matches['domain'] . $matches['close']; } - if (strlen($matches['local']) > 64) { + if (is_string($matches['local']) && strlen($matches['local']) > 64) { // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1 // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 $valid = false; - } elseif (strlen($matches['local'] . '@' . $matches['domain']) > 254) { + } elseif (is_string($matches['local']) && strlen($matches['local'] . '@' . $matches['domain']) > 254) { // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the // upper limit on address lengths should normally be considered to be 254. diff --git a/src/Rule/InRange.php b/src/Rule/InRange.php index d04735430..57c153a94 100644 --- a/src/Rule/InRange.php +++ b/src/Rule/InRange.php @@ -68,7 +68,6 @@ protected function validateValue($value, DataSetInterface $dataSet = null): Resu return $result; } - public function strict(): self { $this->strict = true; diff --git a/tests/Rule/CallbackTest.php b/tests/Rule/CallbackTest.php index 85b678ef3..c5d0ddaca 100644 --- a/tests/Rule/CallbackTest.php +++ b/tests/Rule/CallbackTest.php @@ -26,5 +26,6 @@ static function ($value): Result { $this->assertFalse($result->isValid()); $this->assertCount(1, $result->getErrors()); + $this->assertEquals('Value should be 42!', $result->getErrors()[0]); } } diff --git a/tests/Rule/HasLengthTest.php b/tests/Rule/HasLengthTest.php index dc870eebd..0d6ce4300 100644 --- a/tests/Rule/HasLengthTest.php +++ b/tests/Rule/HasLengthTest.php @@ -64,9 +64,10 @@ public function testValidateMin(): void $result = $rule->validate(''); $this->assertFalse($result->isValid()); + $this->assertStringContainsString( 'This value should contain at least {min, number} {min, plural, one{character} other{characters}}.', - $result->getErrors()[0][0] + $result->getErrors()[0] ); $this->assertTrue($rule->validate(str_repeat('x', 5))->isValid()); } @@ -83,7 +84,7 @@ public function testValidateMax(): void $this->assertFalse($result->isValid()); $this->assertStringContainsString( 'This value should contain at most {max, number} {max, plural, one{character} other{characters}}.', - $result->getErrors()[0][0] + $result->getErrors()[0] ); } @@ -96,8 +97,8 @@ public function testValidateMessages() ->min(3) ->max(5); - $this->assertEquals('is not string error', $rule->validate(null)->getErrors()[0][0]); - $this->assertEquals('is to short test', $rule->validate(str_repeat('x', 1))->getErrors()[0][0]); - $this->assertEquals('is to long test', $rule->validate(str_repeat('x', 6))->getErrors()[0][0]); + $this->assertEquals('is not string error', $rule->validate(null)->getErrors()[0]); + $this->assertEquals('is to short test', $rule->validate(str_repeat('x', 1))->getErrors()[0]); + $this->assertEquals('is to long test', $rule->validate(str_repeat('x', 6))->getErrors()[0]); } } diff --git a/tests/Rule/InRangeTest.php b/tests/Rule/InRangeTest.php index db9233407..691249a20 100644 --- a/tests/Rule/InRangeTest.php +++ b/tests/Rule/InRangeTest.php @@ -19,7 +19,7 @@ public function testInitException(): void public function testValidate(): void { - $val = new InRange(range(1, 10, 1)); + $val = new InRange(range(1, 10)); $this->assertTrue($val->validate(1)->isValid()); $this->assertFalse($val->validate(0)->isValid()); $this->assertFalse($val->validate(11)->isValid()); @@ -31,23 +31,21 @@ public function testValidate(): void public function testValidateEmpty(): void { - $rule = (new InRange(range(10, 20, 1)))->skipOnEmpty(false); + $rule = (new InRange(range(10, 20)))->skipOnEmpty(false); $this->assertFalse($rule->validate(null)->isValid()); //row RangeValidatorTest.php:101 $this->assertFalse($rule->validate('0')->isValid()); $this->assertFalse($rule->validate(0)->isValid()); $this->assertFalse($rule->validate('')->isValid()); $rule = (new InRange(range(10, 20, 1))) - ->skipOnEmpty(false) - ->allowArray(true); + ->skipOnEmpty(false); $this->assertTrue($rule->validate([])->isValid()); } public function testValidateArrayValue(): void { - $rule = (new InRange(range(1, 10, 1))) - ->allowArray(true); + $rule = (new InRange(range(1, 10))); $this->assertTrue($rule->validate([1, 2, 3, 4, 5])->isValid()); $this->assertTrue($rule->validate([6, 7, 8, 9, 10])->isValid()); @@ -58,7 +56,7 @@ public function testValidateArrayValue(): void public function testValidateStrict(): void { - $rule = (new InRange(range(1, 10, 1))) + $rule = (new InRange(range(1, 10))) ->strict(); $this->assertTrue($rule->validate(1)->isValid()); @@ -72,8 +70,7 @@ public function testValidateStrict(): void public function testValidateArrayValueStrict(): void { $rule = (new InRange(range(1, 10, 1))) - ->strict() - ->allowArray(true); + ->strict(); $this->assertFalse($rule->validate(['1', '2', '3', '4', '5', '6'])->isValid()); $this->assertFalse($rule->validate(['1', '2', '3', 4, 5, 6])->isValid()); @@ -96,27 +93,23 @@ public function testValidateNot() public function testValidateSubsetArrayable(): void { // Test in array, values are arrays. IE: ['a'] in [['a'], ['b']] - $rule = (new InRange([['a'], ['b']])) - ->allowArray(false); + $rule = (new InRange([['a'], ['b']])); $this->assertTrue($rule->validate(['a'])->isValid()); // Test in array, values are arrays. IE: ['a', 'b'] subset [['a', 'b', 'c'] - $rule = (new InRange(['a', 'b', 'c'])) - ->allowArray(true); + $rule = (new InRange(['a', 'b', 'c'])); $this->assertTrue($rule->validate(['a', 'b'])->isValid()); // Test in array, values are arrays. IE: ['a', 'b'] subset [['a', 'b', 'c'] - $rule = (new InRange(['a', 'b', 'c'])) - ->allowArray(true); + $rule = (new InRange(['a', 'b', 'c'])); $this->assertTrue($rule->validate(new \ArrayObject(['a', 'b']))->isValid()); // Test range as ArrayObject. - $rule = (new InRange(new \ArrayObject(['a', 'b']))) - ->allowArray(false); + $rule = (new InRange(new \ArrayObject(['a', 'b']))); $this->assertTrue($rule->validate('a')->isValid()); } From d6e7987fe9d9fb65a5fffcd29eb72b0b43ed7451 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Thu, 27 Feb 2020 11:33:40 +0200 Subject: [PATCH 14/21] implement skipOnError ability & minor refactoring --- src/ResultSet.php | 9 +++++++ src/Rule.php | 56 ++++++++++++++++++++++++++++++++++------- src/Rules.php | 27 +++++++++++++++++--- src/Validator.php | 3 +-- tests/RulesTest.php | 16 ++++++++++++ tests/ValidatorTest.php | 37 ++++++++++++++++++++++++++- 6 files changed, 132 insertions(+), 16 deletions(-) diff --git a/src/ResultSet.php b/src/ResultSet.php index 758cc5084..f6b953ea8 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -14,11 +14,15 @@ final class ResultSet implements \IteratorAggregate * @var Result[] */ private array $results = []; + private bool $hasErrors = false; public function addResult( string $attribute, Result $result ): void { + if ($this->hasErrors === false && $result->isValid() === false) { + $this->hasErrors = true; + } if (!isset($this->results[$attribute])) { $this->results[$attribute] = $result; return; @@ -31,6 +35,11 @@ public function addResult( } } + public function hasErrors(): bool + { + return $this->hasErrors; + } + public function getResult(string $attribute): Result { if (!isset($this->results[$attribute])) { diff --git a/src/Rule.php b/src/Rule.php index 08387ab7f..ff9e63697 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -11,10 +11,15 @@ */ abstract class Rule { + const SKIP_ON_ANY_ERROR = 1; + const SKIP_ON_ATTRIBUTE_ERROR = 2; + private ?TranslatorInterface $translator = null; private ?string $translationDomain = null; private ?string $translationLocale = null; private bool $skipOnEmpty = false; + private bool $skipOnError = true; + private int $skipErrorMode = self::SKIP_ON_ATTRIBUTE_ERROR; /** * Validates the value @@ -41,22 +46,25 @@ final public function validate($value, DataSetInterface $dataSet = null): Result */ abstract protected function validateValue($value, DataSetInterface $dataSet = null): Result; - public function setTranslator(TranslatorInterface $translator): self + public function withTranslator(TranslatorInterface $translator): self { - $this->translator = $translator; - return $this; + $new = clone $this; + $new->translator = $translator; + return $new; } - public function setTranslationDomain(string $translation): self + public function withTranslationDomain(string $translation): self { - $this->translationDomain = $translation; - return $this; + $new = clone $this; + $new->translationDomain = $translation; + return $new; } - public function setTranslationLocale(string $locale): self + public function withTranslationLocale(string $locale): self { - $this->translationLocale = $locale; - return $this; + $new = clone $this; + $new->translationLocale = $locale; + return $new; } public function translateMessage(string $message, array $arguments = []): string @@ -73,6 +81,36 @@ public function translateMessage(string $message, array $arguments = []): string ); } + public function getSkipOnError(): bool + { + return $this->skipOnError; + } + + public function getSkipErrorMode(): int + { + return $this->skipErrorMode; + } + + public function skipOnError(bool $value): self + { + $new = clone $this; + $new->skipOnError = $value; + return $new; + } + + public function skipErrorMode(int $mode): self + { + $modes = [self::SKIP_ON_ANY_ERROR, self::SKIP_ON_ATTRIBUTE_ERROR]; + if (!in_array($mode, $modes, true)) { + throw new \InvalidArgumentException( + sprintf('Unknown mode given %s, supported modes %s.', $mode, implode(', ', $modes)) + ); + } + $new = clone $this; + $new->skipErrorMode = $mode; + return $new; + } + /** * @param bool $value if validation should be skipped if value validated is empty * @return self diff --git a/src/Rules.php b/src/Rules.php index 1fb778060..bbb18e0b5 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -46,15 +46,15 @@ private function normalizeRule($rule): Rule } if ($this->translator !== null) { - $rule->setTranslator($this->translator); + $rule->withTranslator($this->translator); } if ($this->translationDomain !== null) { - $rule->setTranslationDomain($this->translationDomain); + $rule->withTranslationDomain($this->translationDomain); } if ($this->translationLocale !== null) { - $rule->setTranslationLocale($this->translationLocale); + $rule->withTranslationLocale($this->translationLocale); } return $rule; @@ -65,10 +65,16 @@ public function add(Rule $rule): void $this->rules[] = $this->normalizeRule($rule); } - public function validate($value, DataSetInterface $dataSet = null): Result + public function validate($value, DataSetInterface $dataSet = null, ResultSet $resultSet = null): Result { $compoundResult = new Result(); + /** + * @var $rule Rule + */ foreach ($this->rules as $rule) { + if ($this->skipValidate($rule, $compoundResult, $resultSet)) { + continue; + } $ruleResult = $rule->validate($value, $dataSet); if ($ruleResult->isValid() === false) { foreach ($ruleResult->getErrors() as $message) { @@ -78,4 +84,17 @@ public function validate($value, DataSetInterface $dataSet = null): Result } return $compoundResult; } + + private function skipValidate(Rule $rule, Result $result, ResultSet $resultSet = null): bool + { + if ($rule->getSkipOnError() === true) { + if ( + ($rule->getSkipErrorMode() === Rule::SKIP_ON_ATTRIBUTE_ERROR && $result->isValid() === false) || + ($rule->getSkipErrorMode() === Rule::SKIP_ON_ANY_ERROR && $resultSet->hasErrors() === true) + ) { + return true; + } + } + return false; + } } diff --git a/src/Validator.php b/src/Validator.php index 0da1f9ca4..d411da897 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -44,11 +44,10 @@ public function __construct( public function validate(DataSetInterface $dataSet): ResultSet { $results = new ResultSet(); - foreach ($this->attributeRules as $attribute => $rules) { $results->addResult( $attribute, - $rules->validate($dataSet->getAttributeValue($attribute)) + $rules->validate($dataSet->getAttributeValue($attribute), null, $results) ); } return $results; diff --git a/tests/RulesTest.php b/tests/RulesTest.php index 6936cf54e..df2b437c3 100644 --- a/tests/RulesTest.php +++ b/tests/RulesTest.php @@ -55,4 +55,20 @@ static function ($value): Result { $this->assertFalse($result->isValid()); $this->assertCount(1, $result->getErrors()); } + + public function testSkipOnError() + { + $rules = new Rules( + [ + (new Number())->min(10), + (new Number())->min(10)->skipOnError(false), + (new Number())->min(10) + ] + ); + + $result = $rules->validate(1); + + $this->assertFalse($result->isValid()); + $this->assertCount(2, $result->getErrors()); + } } diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 516dc185e..06cda4e71 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -4,6 +4,7 @@ namespace Yiisoft\Validator\Tests; +use Yiisoft\Validator\Rule; use PHPUnit\Framework\TestCase; use Yiisoft\Validator\DataSetInterface; use Yiisoft\Validator\MissingAttributeException; @@ -72,7 +73,7 @@ static function ($value): Result { $intResult = $results->getResult('int'); $this->assertFalse($intResult->isValid()); - $this->assertCount(2, $intResult->getErrors()); + $this->assertCount(1, $intResult->getErrors()); } public function testAddingRulesOneByOne(): void @@ -97,4 +98,38 @@ public function testAddingRulesOneByOne(): void $this->assertFalse($intResult->isValid()); $this->assertCount(1, $intResult->getErrors()); } + + public function testSkipOnErrorWithAnyError() + { + $dataObject = $this->getDataObject( + [ + 'first_attribute' => 30, + 'second_attribute' => 30, + 'third_attribute' => 30, + ] + ); + + $validator = new Validator( + [ + 'first_attribute' => [ + (new Number())->min(35), + (new Number())->min(35) + ], + 'second_attribute' => [ + (new Number())->min(35)->skipErrorMode(Rule::SKIP_ON_ANY_ERROR), + (new Number())->min(35) + ], + 'third_attribute' => [ + (new Number())->min(35), + (new Number())->min(35)->skipOnError(false), + ] + ] + ); + + $results = $validator->validate($dataObject); + + $this->assertCount(1, $results->getResult('first_attribute')->getErrors()); + $this->assertCount(1, $results->getResult('second_attribute')->getErrors()); + $this->assertCount(2, $results->getResult('third_attribute')->getErrors()); + } } From 4c3b41dfda3f6187a39a6c3152ad02afae472fcc Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Thu, 27 Feb 2020 11:48:26 +0200 Subject: [PATCH 15/21] minor changes --- src/Rules.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Rules.php b/src/Rules.php index bbb18e0b5..dafdfa512 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -72,7 +72,7 @@ public function validate($value, DataSetInterface $dataSet = null, ResultSet $re * @var $rule Rule */ foreach ($this->rules as $rule) { - if ($this->skipValidate($rule, $compoundResult, $resultSet)) { + if ($rule->getSkipOnError() === true && $this->skipValidate($rule, $compoundResult, $resultSet)) { continue; } $ruleResult = $rule->validate($value, $dataSet); @@ -87,14 +87,13 @@ public function validate($value, DataSetInterface $dataSet = null, ResultSet $re private function skipValidate(Rule $rule, Result $result, ResultSet $resultSet = null): bool { - if ($rule->getSkipOnError() === true) { - if ( - ($rule->getSkipErrorMode() === Rule::SKIP_ON_ATTRIBUTE_ERROR && $result->isValid() === false) || - ($rule->getSkipErrorMode() === Rule::SKIP_ON_ANY_ERROR && $resultSet->hasErrors() === true) - ) { - return true; - } + if ( + ($rule->getSkipErrorMode() === Rule::SKIP_ON_ATTRIBUTE_ERROR && $result->isValid() === false) || + ($rule->getSkipErrorMode() === Rule::SKIP_ON_ANY_ERROR && $resultSet->hasErrors() === true) + ) { + return true; } + return false; } } From deb3464e823e0dc178765b06f07154274c85bdc6 Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Thu, 27 Feb 2020 12:22:44 +0200 Subject: [PATCH 16/21] added "when" method for validation at condition --- src/Rule.php | 12 ++++++++++++ tests/RulesTest.php | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Rule.php b/src/Rule.php index ff9e63697..78919d8c5 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -20,6 +20,7 @@ abstract class Rule private bool $skipOnEmpty = false; private bool $skipOnError = true; private int $skipErrorMode = self::SKIP_ON_ATTRIBUTE_ERROR; + private $when = null; /** * Validates the value @@ -34,6 +35,10 @@ final public function validate($value, DataSetInterface $dataSet = null): Result return new Result(); } + if ($this->when !== null && !call_user_func($this->when)) { + return new Result(); + } + return $this->validateValue($value, $dataSet); } @@ -81,6 +86,13 @@ public function translateMessage(string $message, array $arguments = []): string ); } + public function when(callable $callback): self + { + $new = clone $this; + $new->when = $callback; + return $new; + } + public function getSkipOnError(): bool { return $this->skipOnError; diff --git a/tests/RulesTest.php b/tests/RulesTest.php index df2b437c3..4afc38ee1 100644 --- a/tests/RulesTest.php +++ b/tests/RulesTest.php @@ -56,6 +56,22 @@ static function ($value): Result { $this->assertCount(1, $result->getErrors()); } + public function testWhenValidate() + { + $rules = new Rules( + [ + (new Number())->min(10), + (new Number())->min(10)->when(fn() => false)->skipOnError(false), + (new Number())->min(10)->skipOnError(false) + ] + ); + + $result = $rules->validate(1); + + $this->assertFalse($result->isValid()); + $this->assertCount(2, $result->getErrors()); + } + public function testSkipOnError() { $rules = new Rules( From b3b06a55a9e754e2b8a8a6b3116be1f31d97e3ae Mon Sep 17 00:00:00 2001 From: thenotsoft Date: Mon, 2 Mar 2020 17:42:40 +0200 Subject: [PATCH 17/21] removed unnecessary test --- tests/ValidatorTest.php | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 06cda4e71..2812252a3 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -98,38 +98,4 @@ public function testAddingRulesOneByOne(): void $this->assertFalse($intResult->isValid()); $this->assertCount(1, $intResult->getErrors()); } - - public function testSkipOnErrorWithAnyError() - { - $dataObject = $this->getDataObject( - [ - 'first_attribute' => 30, - 'second_attribute' => 30, - 'third_attribute' => 30, - ] - ); - - $validator = new Validator( - [ - 'first_attribute' => [ - (new Number())->min(35), - (new Number())->min(35) - ], - 'second_attribute' => [ - (new Number())->min(35)->skipErrorMode(Rule::SKIP_ON_ANY_ERROR), - (new Number())->min(35) - ], - 'third_attribute' => [ - (new Number())->min(35), - (new Number())->min(35)->skipOnError(false), - ] - ] - ); - - $results = $validator->validate($dataObject); - - $this->assertCount(1, $results->getResult('first_attribute')->getErrors()); - $this->assertCount(1, $results->getResult('second_attribute')->getErrors()); - $this->assertCount(2, $results->getResult('third_attribute')->getErrors()); - } } From a04db481e65db4e462a23c986e9818d0ff299579 Mon Sep 17 00:00:00 2001 From: thenotsoft <44147615+thenotsoft@users.noreply.github.com> Date: Mon, 2 Mar 2020 17:43:43 +0200 Subject: [PATCH 18/21] Update src/Rule.php Co-Authored-By: Dmitriy Derepko --- src/Rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rule.php b/src/Rule.php index 1df1438a1..16e77fc49 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -34,7 +34,7 @@ final public function validate($value, DataSetInterface $dataSet = null, bool $p if ( ($this->skipOnError && $previousRulesErrored) || - ($this->when !== null && !call_user_func($this->when)) + (is_callable($this->when) && !call_user_func($this->when)) ) { return new Result(); } From c7e89d8b4056f47f8340937e8fb8d3a4f389fd64 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 3 Mar 2020 22:07:48 +0300 Subject: [PATCH 19/21] hasErrors() wasn't used anywhere --- src/ResultSet.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/ResultSet.php b/src/ResultSet.php index f6b953ea8..758cc5084 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -14,15 +14,11 @@ final class ResultSet implements \IteratorAggregate * @var Result[] */ private array $results = []; - private bool $hasErrors = false; public function addResult( string $attribute, Result $result ): void { - if ($this->hasErrors === false && $result->isValid() === false) { - $this->hasErrors = true; - } if (!isset($this->results[$attribute])) { $this->results[$attribute] = $result; return; @@ -35,11 +31,6 @@ public function addResult( } } - public function hasErrors(): bool - { - return $this->hasErrors; - } - public function getResult(string $attribute): Result { if (!isset($this->results[$attribute])) { From f73e4dbc2d0a138034982e146bce12bdd5e729c3 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 3 Mar 2020 22:15:17 +0300 Subject: [PATCH 20/21] Remove unused import --- tests/ValidatorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 2812252a3..75e8921d0 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -4,7 +4,6 @@ namespace Yiisoft\Validator\Tests; -use Yiisoft\Validator\Rule; use PHPUnit\Framework\TestCase; use Yiisoft\Validator\DataSetInterface; use Yiisoft\Validator\MissingAttributeException; From 2b6ea60a4e9700be8657c459b6e1d5d7f89b2503 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 3 Mar 2020 22:31:07 +0300 Subject: [PATCH 21/21] Add phpdoc --- src/Rule.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Rule.php b/src/Rule.php index 16e77fc49..81d3652e9 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -16,6 +16,10 @@ abstract class Rule private ?string $translationLocale = null; private bool $skipOnEmpty = false; private bool $skipOnError = true; + + /** + * @var callable|null + */ private $when = null; /** @@ -34,7 +38,7 @@ final public function validate($value, DataSetInterface $dataSet = null, bool $p if ( ($this->skipOnError && $previousRulesErrored) || - (is_callable($this->when) && !call_user_func($this->when)) + (is_callable($this->when) && !call_user_func($this->when, $value, $dataSet)) ) { return new Result(); } @@ -86,6 +90,25 @@ public function translateMessage(string $message, array $arguments = []): string ); } + /** + * Add a PHP callable whose return value determines whether this rule should be applied. + * By default rule will be always applied. + * + * The signature of the callable should be `function ($value, DataSetInterface $dataSet): bool`, where $value and $dataSet + * refer to the value validated and the data set in which context it is validated. The callable should return + * a boolean value. + * + * The following example will enable the validator only when the country currently selected is USA: + * + * ```php + * function ($value, DataSetInterface $dataSet) { + return $dataSet->getAttributeValue('country') === Country::USA; + } + * ``` + * + * @param callable $callback + * @return $this + */ public function when(callable $callback): self { $new = clone $this;