From 5b7ea866e436353b356572bcaf2f006df5a7aec1 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Fri, 2 Feb 2024 16:48:42 +0100 Subject: [PATCH 1/3] Do not allow lowercase country codes Country codes in ISO 3166-1 should be in uppercase, and the `AbstractSearcher` should not change the input to search for a value. While working on this fix, I also discovered that the "PublicDomainSuffix" rule would throw an exception if it got a non-scalar value as an input. Signed-off-by: Henrique Moody --- library/Rules/AbstractSearcher.php | 3 +- library/Rules/PublicDomainSuffix.php | 31 ++++++++++++--------- tests/unit/Rules/CountryCodeTest.php | 1 + tests/unit/Rules/PublicDomainSuffixTest.php | 4 +++ 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/library/Rules/AbstractSearcher.php b/library/Rules/AbstractSearcher.php index f73d96305..b749bb381 100644 --- a/library/Rules/AbstractSearcher.php +++ b/library/Rules/AbstractSearcher.php @@ -13,7 +13,6 @@ use function in_array; use function is_scalar; -use function mb_strtoupper; /** * Abstract class for searches into arrays. @@ -45,6 +44,6 @@ public function validate($input): bool return false; } - return in_array(mb_strtoupper((string) $input), $dataSource, true); + return in_array((string) $input, $dataSource, true); } } diff --git a/library/Rules/PublicDomainSuffix.php b/library/Rules/PublicDomainSuffix.php index 67f07ac61..d25980b48 100644 --- a/library/Rules/PublicDomainSuffix.php +++ b/library/Rules/PublicDomainSuffix.php @@ -9,29 +9,34 @@ namespace Respect\Validation\Rules; +use Respect\Validation\Helpers\CanValidateUndefined; use Respect\Validation\Helpers\DomainInfo; use function array_pop; use function explode; +use function in_array; +use function is_scalar; +use function strtoupper; -final class PublicDomainSuffix extends AbstractSearcher +final class PublicDomainSuffix extends AbstractRule { - /** - * @var string[] - */ - private $domainInfo; - - /** - * {@inheritDoc} - */ - protected function getDataSource($input = null): array + use CanValidateUndefined; + + public function validate($input): bool { - $parts = explode('.', $input); + if (!is_scalar($input)) { + return false; + } + + $parts = explode('.', (string) $input); $tld = array_pop($parts); $domainInfo = new DomainInfo($tld); - $this->domainInfo = $domainInfo->getPublicSuffixes(); + $dataSource = $domainInfo->getPublicSuffixes(); + if ($this->isUndefined($input) && empty($dataSource)) { + return true; + } - return $this->domainInfo; + return in_array(strtoupper((string) $input), $dataSource, true); } } diff --git a/tests/unit/Rules/CountryCodeTest.php b/tests/unit/Rules/CountryCodeTest.php index 408ef3244..17c7a4974 100644 --- a/tests/unit/Rules/CountryCodeTest.php +++ b/tests/unit/Rules/CountryCodeTest.php @@ -61,6 +61,7 @@ public static function providerForValidInput(): array public static function providerForInvalidInput(): array { return [ + [new CountryCode(), 'ca'], [new CountryCode(CountryCode::ALPHA2), 'USA'], [new CountryCode(CountryCode::ALPHA3), 'US'], [new CountryCode(CountryCode::NUMERIC), '000'], diff --git a/tests/unit/Rules/PublicDomainSuffixTest.php b/tests/unit/Rules/PublicDomainSuffixTest.php index 487e59672..53faa53b4 100644 --- a/tests/unit/Rules/PublicDomainSuffixTest.php +++ b/tests/unit/Rules/PublicDomainSuffixTest.php @@ -10,6 +10,7 @@ namespace Respect\Validation\Rules; use Respect\Validation\Test\RuleTestCase; +use stdClass; /** * @covers \Respect\Validation\Exceptions\PublicDomainSuffixException @@ -41,6 +42,9 @@ public static function providerForInvalidInput(): array $rule = new PublicDomainSuffix(); return [ + [$rule, []], + [$rule, null], + [$rule, new stdClass()], [$rule, 'NONONONONONONONONON'], [$rule, 'NONONONONONONONONON.uk'], [$rule, 'invalid.com'], From 5ba9b31cc8c735efb5bbd685739feaba355776e5 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Sun, 4 Feb 2024 15:52:36 +0100 Subject: [PATCH 2/3] Make the behavior of DateTime more explicit We decided to make the date format validation stricter[1]. Although that's good, it could present some challenges for some people, considering that the DateTime rule would be more flexible. There are many cases in which PHP can parse a date but can't output it the same way. [1]: 5fe4b96ebf651407d6b8ba2272e4bdab980b4438 Signed-off-by: Henrique Moody --- docs/rules/DateTime.md | 43 +++++++++++++++++++++++++------ tests/library/RuleTestCase.php | 14 ++++++++-- tests/unit/Rules/DateTimeTest.php | 5 ++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/docs/rules/DateTime.md b/docs/rules/DateTime.md index 83813e9c8..84c6e7ff0 100644 --- a/docs/rules/DateTime.md +++ b/docs/rules/DateTime.md @@ -1,16 +1,20 @@ + # DateTime - `DateTime()` - `DateTime(string $format)` -Validates whether an input is a date/time or not. The `$format` argument should -be in accordance to PHP's [date()](http://php.net/date) function. +Validates whether an input is a date/time or not. + +The `$format` argument should be in accordance to [DateTime::format()][]. See more in the [Formats](#formats) section. + +When a `$format` is not given its default value is `Y-m-d H:i:s`. ```php v::dateTime()->validate('2009-01-01'); // true ``` -Also accepts strtotime values: +Also accepts [strtotime()](http://php.net/strtotime) values: ```php v::dateTime()->validate('now'); // true @@ -31,7 +35,26 @@ v::dateTime('Y-m-d')->validate('01-01-2009'); // false Format has no effect when validating DateTime instances. -Message template for this validator includes `{{format}}`. +Message template for this validator includes `{{sample}}`. + +## Formats + +Note that this rule validates whether the input **matches a given [DateTime::format()][] format** and **NOT if the input +can be parsed with a given [DateTimeImmutable::createFromFormat()][] format**. That makes the validation stricter but +offers some limitations. + +The way [DateTimeImmutable::createFromFormat()][] parses an input allows for many different conversions. Overall +[DateTimeImmutable::createFromFormat()][] tend to be more lenient than [DateTime::format()][]. This might be what +you desire, and you may want to use [Callback](Callback.md) to create a custom validation. + +```php +$input = '2014-04-12T23:20:50.052Z'; + +v::callback(fn($input) => is_string($input) && DateTime::createFromFormat(DateTime::RFC3339_EXTENDED, $input)) + ->validate($input); // true + +v::dateTime(DateTime::RFC3339_EXTENDED)->validate($input); // false +``` ## Categorization @@ -39,10 +62,11 @@ Message template for this validator includes `{{format}}`. ## Changelog -Version | Description ---------|------------- - 2.2.4 | `v::dateTime('z')` is no longer supported. - 2.0.0 | Created +| Version | Description | +|---------|--------------------------------------------| +| 2.3.0 | Validation became a lot stricter | +| 2.2.4 | `v::dateTime('z')` is no longer supported. | +| 2.0.0 | Created | *** See also: @@ -53,3 +77,6 @@ See also: - [LeapYear](LeapYear.md) - [MinAge](MinAge.md) - [Time](Time.md) + +[DateTimeImmutable::createFromFormat()]: https://www.php.net/datetimeimmutable.createfromformat +[DateTime::format()]: https://www.php.net/datetime.format diff --git a/tests/library/RuleTestCase.php b/tests/library/RuleTestCase.php index bdd951394..c76ff8de6 100644 --- a/tests/library/RuleTestCase.php +++ b/tests/library/RuleTestCase.php @@ -16,6 +16,8 @@ use function realpath; use function Respect\Stringifier\stringify; use function sprintf; +use function strrchr; +use function substr; /** * Abstract class to create TestCases for Rules. @@ -98,7 +100,11 @@ public static function assertValidInput(Validatable $rule, $input): void { self::assertTrue( $rule->validate($input), - sprintf('Validation with input %s is expected to pass', stringify($input)) + sprintf( + '%s should pass with %s', + substr((string) strrchr($rule::class, '\\'), 1), + stringify($rule->reportError($input)->getParams()) + ) ); } @@ -109,7 +115,11 @@ public static function assertInvalidInput(Validatable $rule, $input): void { self::assertFalse( $rule->validate($input), - sprintf('Validation with input %s it not expected to pass', stringify($input)) + sprintf( + '%s should not pass with %s', + substr((string) strrchr($rule::class, '\\'), 1), + stringify($rule->reportError($input)->getParams()) + ) ); } } diff --git a/tests/unit/Rules/DateTimeTest.php b/tests/unit/Rules/DateTimeTest.php index 67eefa406..09790cee0 100644 --- a/tests/unit/Rules/DateTimeTest.php +++ b/tests/unit/Rules/DateTimeTest.php @@ -11,6 +11,7 @@ use DateTime as DateTimeMutable; use DateTimeImmutable; +use DateTimeInterface; use Respect\Validation\Test\RuleTestCase; use function date_default_timezone_get; @@ -102,6 +103,8 @@ public static function providerForValidInput(): array [new DateTime('U'), 1464658596], [new DateTime('h'), 6], [new DateTime('Ym'), 202305], + [new DateTime(DateTimeInterface::RFC3339), '2018-02-23T12:00:00+00:00'], + [new DateTime(DateTimeInterface::RFC3339_EXTENDED), '2024-02-04T14:14:47.000+00:00'], ]; } @@ -123,6 +126,8 @@ public static function providerForInvalidInput(): array [new DateTime('c'), new DateTimeMutable()], [new DateTime('c'), new DateTimeImmutable()], [new DateTime('Y-m-d H:i:s'), '21-3-123:12:01'], + [new DateTime(DateTimeInterface::RFC3339_EXTENDED), '2005-12-30T01:02:03Z'], + [new DateTime(DateTimeInterface::RFC3339_EXTENDED), '1937-01-01T12:00:27.87+00:20'], ]; } } From d8cd6a30e0c954f25b401db16ce9a66b486d4963 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Sun, 4 Feb 2024 16:11:57 +0100 Subject: [PATCH 3/3] Change documentation order This allows someone who wants to learn more about Validation to have a more linear inflow of information. Signed-off-by: Henrique Moody --- docs/{installation.md => 01-installation.md} | 0 docs/{feature-guide.md => 02-feature-guide.md} | 0 docs/{message-translation.md => 03-message-translation.md} | 0 ...older-conversion.md => 04-message-placeholder-conversion.md} | 2 +- docs/{concrete-api.md => 05-concrete-api.md} | 0 docs/{custom-rules.md => 06-custom-rules.md} | 0 docs/{comparable-values.md => 07-comparable-values.md} | 0 docs/{list-of-rules.md => 08-list-of-rules-by-category.md} | 2 +- docs/{license.md => 09-license.md} | 0 9 files changed, 2 insertions(+), 2 deletions(-) rename docs/{installation.md => 01-installation.md} (100%) rename docs/{feature-guide.md => 02-feature-guide.md} (100%) rename docs/{message-translation.md => 03-message-translation.md} (100%) rename docs/{message-placeholder-conversion.md => 04-message-placeholder-conversion.md} (95%) rename docs/{concrete-api.md => 05-concrete-api.md} (100%) rename docs/{custom-rules.md => 06-custom-rules.md} (100%) rename docs/{comparable-values.md => 07-comparable-values.md} (100%) rename docs/{list-of-rules.md => 08-list-of-rules-by-category.md} (99%) rename docs/{license.md => 09-license.md} (100%) diff --git a/docs/installation.md b/docs/01-installation.md similarity index 100% rename from docs/installation.md rename to docs/01-installation.md diff --git a/docs/feature-guide.md b/docs/02-feature-guide.md similarity index 100% rename from docs/feature-guide.md rename to docs/02-feature-guide.md diff --git a/docs/message-translation.md b/docs/03-message-translation.md similarity index 100% rename from docs/message-translation.md rename to docs/03-message-translation.md diff --git a/docs/message-placeholder-conversion.md b/docs/04-message-placeholder-conversion.md similarity index 95% rename from docs/message-placeholder-conversion.md rename to docs/04-message-placeholder-conversion.md index 90140ce21..23584668f 100644 --- a/docs/message-placeholder-conversion.md +++ b/docs/04-message-placeholder-conversion.md @@ -1,4 +1,4 @@ -## Message placeholder conversion +# Message placeholder conversion Messages in Validation usually have placeholders that are in between "{{" and "}}" characters. To replace those placeholders with the real parameters, we need diff --git a/docs/concrete-api.md b/docs/05-concrete-api.md similarity index 100% rename from docs/concrete-api.md rename to docs/05-concrete-api.md diff --git a/docs/custom-rules.md b/docs/06-custom-rules.md similarity index 100% rename from docs/custom-rules.md rename to docs/06-custom-rules.md diff --git a/docs/comparable-values.md b/docs/07-comparable-values.md similarity index 100% rename from docs/comparable-values.md rename to docs/07-comparable-values.md diff --git a/docs/list-of-rules.md b/docs/08-list-of-rules-by-category.md similarity index 99% rename from docs/list-of-rules.md rename to docs/08-list-of-rules-by-category.md index c6fee8bc7..720b95726 100644 --- a/docs/list-of-rules.md +++ b/docs/08-list-of-rules-by-category.md @@ -1,4 +1,4 @@ -# List of rules +# List of rules by category ## Arrays diff --git a/docs/license.md b/docs/09-license.md similarity index 100% rename from docs/license.md rename to docs/09-license.md