From 536e53f1846394089b5f06e11572e6ff3cc2dfb0 Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Tue, 4 Apr 2017 01:17:01 -0300 Subject: [PATCH] =?UTF-8?q?[Validator]=C2=A0add=20new=20`Timezone`=20valid?= =?UTF-8?q?ation=20constraint.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Validator/Constraints/Timezone.php | 51 ++++ .../Constraints/TimezoneValidator.php | 92 ++++++ .../Resources/translations/validators.de.xlf | 4 + .../Resources/translations/validators.en.xlf | 4 + .../Resources/translations/validators.es.xlf | 4 + .../Resources/translations/validators.fr.xlf | 4 + .../Tests/Constraints/TimezoneTest.php | 63 ++++ .../Constraints/TimezoneValidatorTest.php | 274 ++++++++++++++++++ 9 files changed, 497 insertions(+) create mode 100644 src/Symfony/Component/Validator/Constraints/Timezone.php create mode 100644 src/Symfony/Component/Validator/Constraints/TimezoneValidator.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index c44c49bee8c33..237dc68147b84 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.3.0 ----- + * added `Timezone` constraint * added `NotCompromisedPassword` constraint * added options `iban` and `ibanPropertyPath` to Bic constraint * added UATP cards support to `CardSchemeValidator` diff --git a/src/Symfony/Component/Validator/Constraints/Timezone.php b/src/Symfony/Component/Validator/Constraints/Timezone.php new file mode 100644 index 0000000000000..b7323fb874118 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/Timezone.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Javier Spagnoletti + * @author Hugo Hamon + */ +class Timezone extends Constraint +{ + public const TIMEZONE_IDENTIFIER_ERROR = '5ce113e6-5e64-4ea2-90fe-d2233956db13'; + public const TIMEZONE_IDENTIFIER_IN_ZONE_ERROR = 'b57767b1-36c0-40ac-a3d7-629420c775b8'; + public const TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR = 'c4a22222-dc92-4fc0-abb0-d95b268c7d0b'; + + public $zone = \DateTimeZone::ALL; + public $countryCode; + public $message = 'This value is not a valid timezone.'; + + protected static $errorNames = [ + self::TIMEZONE_IDENTIFIER_ERROR => 'TIMEZONE_IDENTIFIER_ERROR', + self::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR => 'TIMEZONE_IDENTIFIER_IN_ZONE_ERROR', + self::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR => 'TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR', + ]; + + /** + * {@inheritdoc} + */ + public function __construct(array $options = null) + { + parent::__construct($options); + + if ($this->countryCode && \DateTimeZone::PER_COUNTRY !== $this->zone) { + throw new ConstraintDefinitionException('The option "countryCode" can only be used when "zone" option is configured with `\DateTimeZone::PER_COUNTRY`.'); + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php new file mode 100644 index 0000000000000..14b1668bb595b --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid timezone identifier. + * + * @author Javier Spagnoletti + * @author Hugo Hamon + */ +class TimezoneValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Timezone) { + throw new UnexpectedTypeException($constraint, Timezone::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + // @see: https://bugs.php.net/bug.php?id=75928 + if ($constraint->countryCode) { + $timezoneIds = \DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode); + } else { + $timezoneIds = \DateTimeZone::listIdentifiers($constraint->zone); + } + + if ($timezoneIds && \in_array($value, $timezoneIds, true)) { + return; + } + + if ($constraint->countryCode) { + $code = Timezone::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR; + } elseif (\DateTimeZone::ALL !== $constraint->zone) { + $code = Timezone::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR; + } else { + $code = Timezone::TIMEZONE_IDENTIFIER_ERROR; + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'zone'; + } + + /** + * {@inheritdoc} + */ + protected function formatValue($value, $format = 0) + { + $value = parent::formatValue($value, $format); + + if (!$value || \DateTimeZone::PER_COUNTRY === $value) { + return $value; + } + + return array_search($value, (new \ReflectionClass(\DateTimeZone::class))->getConstants(), true) ?: $value; + } +} diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf index 2fc62d942e8e8..64543d5e8de2f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf @@ -354,6 +354,10 @@ This collection should contain only unique elements. Diese Sammlung darf keine doppelten Elemente enthalten. + + This value is not a valid timezone. + Dieser Wert ist keine gültige Zeitzone. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 39c47e1275c5c..d74b0fefcafac 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -354,6 +354,10 @@ This collection should contain only unique elements. This collection should contain only unique elements. + + This value is not a valid timezone. + This value is not a valid timezone. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index 69ab34e8b29ce..2fcdc44f3e4ec 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -330,6 +330,10 @@ This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. Este Código de Identificación Bancaria (BIC) no está asociado con el IBAN {{ iban }}. + + This value is not a valid timezone. + Este valor no es una zona horaria válida. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 7b1799d53315c..b802c0950eb21 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -334,6 +334,10 @@ This value should be valid JSON. Cette valeur doit être un JSON valide. + + This value is not a valid timezone. + Cette valeur n'est pas un fuseau horaire valide. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php new file mode 100644 index 0000000000000..b7ca37486ed91 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraints\Timezone; + +/** + * @author Javier Spagnoletti + */ +class TimezoneTest extends TestCase +{ + public function testValidTimezoneConstraints() + { + $constraint = new Timezone(); + + $constraint = new Timezone([ + 'message' => 'myMessage', + 'zone' => \DateTimeZone::PER_COUNTRY, + 'countryCode' => 'AR', + ]); + + $constraint = new Timezone([ + 'message' => 'myMessage', + 'zone' => \DateTimeZone::ALL, + ]); + + // Make an assertion in order to avoid this test to be marked as risky + $this->assertInstanceOf(Timezone::class, $constraint); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testExceptionForGroupedTimezonesByCountryWithWrongTimezone() + { + $constraint = new Timezone([ + 'message' => 'myMessage', + 'zone' => \DateTimeZone::ALL, + 'countryCode' => 'AR', + ]); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testExceptionForGroupedTimezonesByCountryWithoutTimezone() + { + $constraint = new Timezone([ + 'message' => 'myMessage', + 'countryCode' => 'AR', + ]); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php new file mode 100644 index 0000000000000..e18bab86fe670 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use Symfony\Component\Validator\Constraints\Timezone; +use Symfony\Component\Validator\Constraints\TimezoneValidator; +use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; + +/** + * @author Javier Spagnoletti + * @author Hugo Hamon + */ +class TimezoneValidatorTest extends ConstraintValidatorTestCase +{ + protected function createValidator(): TimezoneValidator + { + return new TimezoneValidator(); + } + + public function testNullIsValid() + { + $this->validator->validate(null, new Timezone()); + + $this->assertNoViolation(); + } + + public function testEmptyStringIsValid() + { + $this->validator->validate('', new Timezone()); + + $this->assertNoViolation(); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException + */ + public function testExpectsStringCompatibleType() + { + $this->validator->validate(new \stdClass(), new Timezone()); + } + + /** + * @dataProvider getValidTimezones + */ + public function testValidTimezones(string $timezone) + { + $this->validator->validate($timezone, new Timezone()); + + $this->assertNoViolation(); + } + + public function getValidTimezones(): iterable + { + yield ['America/Argentina/Buenos_Aires']; + yield ['America/Barbados']; + yield ['America/Toronto']; + yield ['Antarctica/Syowa']; + yield ['Africa/Douala']; + yield ['Atlantic/Canary']; + yield ['Asia/Gaza']; + yield ['Australia/Sydney']; + yield ['Europe/Copenhagen']; + yield ['Europe/Paris']; + yield ['Pacific/Noumea']; + yield ['UTC']; + } + + /** + * @dataProvider getValidGroupedTimezones + */ + public function testValidGroupedTimezones(string $timezone, int $zone) + { + $constraint = new Timezone([ + 'zone' => $zone, + ]); + + $this->validator->validate($timezone, $constraint); + + $this->assertNoViolation(); + } + + public function getValidGroupedTimezones(): iterable + { + yield ['America/Argentina/Cordoba', \DateTimeZone::AMERICA]; + yield ['America/Barbados', \DateTimeZone::AMERICA]; + yield ['Africa/Cairo', \DateTimeZone::AFRICA]; + yield ['Atlantic/Cape_Verde', \DateTimeZone::ATLANTIC]; + yield ['Europe/Bratislava', \DateTimeZone::EUROPE]; + yield ['Indian/Christmas', \DateTimeZone::INDIAN]; + yield ['Pacific/Kiritimati', \DateTimeZone::ALL]; + yield ['Pacific/Kiritimati', \DateTimeZone::ALL_WITH_BC]; + yield ['Pacific/Kiritimati', \DateTimeZone::PACIFIC]; + yield ['Arctic/Longyearbyen', \DateTimeZone::ARCTIC]; + yield ['Asia/Beirut', \DateTimeZone::ASIA]; + yield ['Atlantic/Bermuda', \DateTimeZone::ASIA | \DateTimeZone::ATLANTIC]; + yield ['Atlantic/Azores', \DateTimeZone::ATLANTIC | \DateTimeZone::ASIA]; + } + + /** + * @dataProvider getInvalidTimezones + */ + public function testInvalidTimezoneWithoutZone(string $timezone) + { + $constraint = new Timezone([ + 'message' => 'myMessage', + ]); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', sprintf('"%s"', $timezone)) + ->setCode(Timezone::TIMEZONE_IDENTIFIER_ERROR) + ->assertRaised(); + } + + public function getInvalidTimezones(): iterable + { + yield ['Buenos_Aires/Argentina/America']; + yield ['Mayotte/Indian']; + yield ['foobar']; + } + + /** + * @dataProvider getInvalidGroupedTimezones + */ + public function testInvalidGroupedTimezones(string $timezone, int $zone) + { + $constraint = new Timezone([ + 'zone' => $zone, + 'message' => 'myMessage', + ]); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', sprintf('"%s"', $timezone)) + ->setCode(Timezone::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR) + ->assertRaised(); + } + + public function getInvalidGroupedTimezones(): iterable + { + yield ['Antarctica/McMurdo', \DateTimeZone::AMERICA]; + yield ['America/Barbados', \DateTimeZone::ANTARCTICA]; + yield ['Europe/Kiev', \DateTimeZone::ARCTIC]; + yield ['Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN]; + yield ['Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN | \DateTimeZone::ANTARCTICA]; + } + + /** + * @dataProvider getValidGroupedTimezonesByCountry + */ + public function testValidGroupedTimezonesByCountry(string $timezone, string $country) + { + $constraint = new Timezone([ + 'zone' => \DateTimeZone::PER_COUNTRY, + 'countryCode' => $country, + ]); + + $this->validator->validate($timezone, $constraint); + + $this->assertNoViolation(); + } + + public function getValidGroupedTimezonesByCountry(): iterable + { + yield ['America/Argentina/Cordoba', 'AR']; + yield ['America/Barbados', 'BB']; + yield ['Africa/Cairo', 'EG']; + yield ['Arctic/Longyearbyen', 'SJ']; + yield ['Asia/Beirut', 'LB']; + yield ['Atlantic/Azores', 'PT']; + yield ['Atlantic/Bermuda', 'BM']; + yield ['Atlantic/Cape_Verde', 'CV']; + yield ['Australia/Sydney', 'AU']; + yield ['Australia/Melbourne', 'AU']; + yield ['Europe/Bratislava', 'SK']; + yield ['Europe/Paris', 'FR']; + yield ['Europe/Madrid', 'ES']; + yield ['Europe/Monaco', 'MC']; + yield ['Indian/Christmas', 'CX']; + yield ['Pacific/Kiritimati', 'KI']; + yield ['Pacific/Kiritimati', 'KI']; + yield ['Pacific/Kiritimati', 'KI']; + } + + /** + * @dataProvider getInvalidGroupedTimezonesByCountry + */ + public function testInvalidGroupedTimezonesByCountry(string $timezone, string $invalidCountryCode) + { + $constraint = new Timezone([ + 'message' => 'myMessage', + 'zone' => \DateTimeZone::PER_COUNTRY, + 'countryCode' => $invalidCountryCode, + ]); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', sprintf('"%s"', $timezone)) + ->setCode(Timezone::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR) + ->assertRaised(); + } + + public function getInvalidGroupedTimezonesByCountry(): iterable + { + yield ['America/Argentina/Cordoba', 'FR']; + yield ['America/Barbados', 'PT']; + yield ['Europe/Bern', 'FR']; + } + + /** + * @dataProvider getDeprecatedTimezones + */ + public function testDeprecatedTimezonesAreValidWithBC(string $timezone) + { + $constraint = new Timezone([ + 'zone' => \DateTimeZone::ALL_WITH_BC, + ]); + + $this->validator->validate($timezone, $constraint); + + $this->assertNoViolation(); + } + + /** + * @dataProvider getDeprecatedTimezones + */ + public function testDeprecatedTimezonesAreInvalidWithoutBC(string $timezone) + { + $constraint = new Timezone([ + 'message' => 'myMessage', + ]); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', sprintf('"%s"', $timezone)) + ->setCode(Timezone::TIMEZONE_IDENTIFIER_ERROR) + ->assertRaised(); + } + + public function getDeprecatedTimezones(): iterable + { + yield ['America/Buenos_Aires']; + yield ['America/Montreal']; + yield ['Australia/ACT']; + yield ['Australia/LHI']; + yield ['Australia/Queensland']; + yield ['Canada/Eastern']; + yield ['Canada/Central']; + yield ['Canada/Mountain']; + yield ['Canada/Pacific']; + yield ['CET']; + yield ['CST6CDT']; + yield ['Etc/GMT']; + yield ['Etc/Greenwich']; + yield ['Etc/UCT']; + yield ['Etc/Universal']; + yield ['Etc/UTC']; + yield ['Etc/Zulu']; + yield ['US/Pacific']; + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy