From fb99eb2052523cfa2a9ba36006d020796548aaa6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 3 Oct 2020 19:01:44 +0200 Subject: [PATCH] [Validator] Upgraded constraints to enable named arguments and attributes --- .../Constraints/UserPasswordTest.php | 81 ++++++++++++++++ .../Constraints/UserPasswordValidatorTest.php | 27 ++++-- .../Validator/Constraints/UserPassword.php | 9 ++ .../Component/Security/Core/composer.json | 5 +- .../Component/Validator/Constraints/Bic.php | 25 ++++- .../Validator/Constraints/CardScheme.php | 32 +++++++ .../Constraints/CardSchemeValidator.php | 24 ++--- .../Validator/Constraints/Cascade.php | 3 +- .../Component/Validator/Constraints/Count.php | 51 +++++++--- .../Validator/Constraints/Country.php | 15 ++- .../Validator/Constraints/Currency.php | 7 +- .../Component/Validator/Constraints/Date.php | 8 ++ .../Validator/Constraints/DateTime.php | 19 ++++ .../Constraints/DisableAutoMapping.php | 3 +- .../Component/Validator/Constraints/Email.php | 17 +++- .../Constraints/EnableAutoMapping.php | 3 +- .../Validator/Constraints/Expression.php | 30 +++++- .../Constraints/ExpressionLanguageSyntax.php | 12 ++- .../Validator/Constraints/Hostname.php | 14 +++ .../Component/Validator/Constraints/Iban.php | 8 ++ .../Component/Validator/Constraints/Ip.php | 17 +++- .../Component/Validator/Constraints/Isbn.php | 33 +++++++ .../Validator/Constraints/IsbnValidator.php | 8 +- .../Component/Validator/Constraints/Isin.php | 8 ++ .../Component/Validator/Constraints/Issn.php | 16 ++++ .../Component/Validator/Constraints/Json.php | 8 ++ .../Validator/Constraints/Language.php | 15 ++- .../Validator/Constraints/Length.php | 53 ++++++++--- .../Validator/Constraints/Locale.php | 15 ++- .../Component/Validator/Constraints/Luhn.php | 12 +++ .../Constraints/NotCompromisedPassword.php | 16 ++++ .../Component/Validator/Constraints/Regex.php | 31 ++++++- .../Component/Validator/Constraints/Time.php | 12 +++ .../Validator/Constraints/Timezone.php | 26 +++++- .../Validator/Constraints/Traverse.php | 10 +- .../Component/Validator/Constraints/Type.php | 19 ++++ .../Component/Validator/Constraints/Ulid.php | 12 +++ .../Validator/Constraints/Unique.php | 12 +++ .../Component/Validator/Constraints/Url.php | 19 +++- .../Component/Validator/Constraints/Uuid.php | 42 ++++++--- .../Tests/Constraints/BicValidatorTest.php | 79 ++++++++++++++++ .../Tests/Constraints/CardSchemeTest.php | 55 +++++++++++ .../Constraints/CardSchemeValidatorTest.php | 26 ++++++ .../Tests/Constraints/CascadeTest.php | 38 ++++++++ .../Validator/Tests/Constraints/CountTest.php | 62 +++++++++++++ .../Tests/Constraints/CountValidatorTest.php | 93 +++++++++++++++++++ .../Tests/Constraints/CountryTest.php | 54 +++++++++++ .../Constraints/CountryValidatorTest.php | 16 ++++ .../Tests/Constraints/CurrencyTest.php | 50 ++++++++++ .../Constraints/CurrencyValidatorTest.php | 16 ++++ .../Validator/Tests/Constraints/DateTest.php | 50 ++++++++++ .../Tests/Constraints/DateTimeTest.php | 55 +++++++++++ .../Constraints/DateTimeValidatorTest.php | 15 +++ .../Tests/Constraints/DateValidatorTest.php | 15 +++ .../Constraints/DisableAutoMappingTest.php | 20 ++++ .../Validator/Tests/Constraints/EmailTest.php | 36 +++++++ .../Constraints/EnableAutoMappingTest.php | 20 ++++ .../ExpressionLanguageSyntaxTest.php | 86 +++++++++++++++++ .../Tests/Constraints/ExpressionTest.php | 56 +++++++++++ .../Tests/Constraints/HostnameTest.php | 54 +++++++++++ .../Constraints/HostnameValidatorTest.php | 16 ++++ .../Tests/Constraints/IbanValidatorTest.php | 26 ++++++ .../Validator/Tests/Constraints/IpTest.php | 37 ++++++++ .../Tests/Constraints/IpValidatorTest.php | 13 +++ .../Validator/Tests/Constraints/IsbnTest.php | 54 +++++++++++ .../Tests/Constraints/IsbnValidatorTest.php | 32 +++++++ .../Validator/Tests/Constraints/IsinTest.php | 50 ++++++++++ .../Validator/Tests/Constraints/IssnTest.php | 56 +++++++++++ .../Tests/Constraints/IssnValidatorTest.php | 16 ++++ .../Validator/Tests/Constraints/JsonTest.php | 50 ++++++++++ .../Tests/Constraints/LanguageTest.php | 54 +++++++++++ .../Constraints/LanguageValidatorTest.php | 17 ++++ .../Tests/Constraints/LengthTest.php | 58 ++++++++++++ .../Tests/Constraints/LengthValidatorTest.php | 74 +++++++++++---- .../Tests/Constraints/LocaleTest.php | 54 +++++++++++ .../Tests/Constraints/LocaleValidatorTest.php | 49 ++++++++++ .../Validator/Tests/Constraints/LuhnTest.php | 50 ++++++++++ .../NotCompromisedPasswordTest.php | 38 ++++++++ .../NotCompromisedPasswordValidatorTest.php | 32 ++++++- .../Validator/Tests/Constraints/RegexTest.php | 40 ++++++++ .../Tests/Constraints/RegexValidatorTest.php | 28 ++++++ .../Validator/Tests/Constraints/TimeTest.php | 50 ++++++++++ .../Tests/Constraints/TimezoneTest.php | 36 +++++++ .../Constraints/TimezoneValidatorTest.php | 15 +++ .../Tests/Constraints/TraverseTest.php | 50 ++++++++++ .../Validator/Tests/Constraints/TypeTest.php | 54 +++++++++++ .../Tests/Constraints/TypeValidatorTest.php | 28 +++--- .../Validator/Tests/Constraints/UlidTest.php | 50 ++++++++++ .../Tests/Constraints/UlidValidatorTest.php | 15 +++ .../Tests/Constraints/UniqueTest.php | 50 ++++++++++ .../Tests/Constraints/UniqueValidatorTest.php | 14 +++ .../Validator/Tests/Constraints/UrlTest.php | 39 ++++++++ .../Validator/Tests/Constraints/UuidTest.php | 39 ++++++++ .../Tests/Constraints/UuidValidatorTest.php | 29 ++++++ 94 files changed, 2849 insertions(+), 137 deletions(-) create mode 100644 src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/CardSchemeTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/CascadeTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/CountTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/CountryTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/CurrencyTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/DateTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/DateTimeTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/ExpressionTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/HostnameTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/IsbnTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/IsinTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/IssnTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/JsonTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/LanguageTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/LocaleTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/LuhnTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/TimeTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/TraverseTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/TypeTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/UlidTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/UniqueTest.php diff --git a/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordTest.php b/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordTest.php new file mode 100644 index 0000000000..9fd1438446 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests\Validator\Constraints; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +class UserPasswordTest extends TestCase +{ + public function testValidatedByStandardValidator() + { + $constraint = new UserPassword(); + + self::assertSame('security.validator.user_password', $constraint->validatedBy()); + } + + /** + * @dataProvider provideServiceValidatedConstraints + */ + public function testValidatedByService(UserPassword $constraint) + { + self::assertSame('my_service', $constraint->validatedBy()); + } + + public function provideServiceValidatedConstraints(): iterable + { + yield 'Doctrine style' => [new UserPassword(['service' => 'my_service'])]; + + if (\PHP_VERSION_ID < 80000) { + return; + } + + yield 'named arguments' => [eval('return new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(service: "my_service");')]; + + $metadata = new ClassMetadata(UserPasswordDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + yield 'attribute' => [$metadata->properties['b']->constraints[0]]; + } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(UserPasswordDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'UserPasswordDummy'], $bConstraint->groups); + self::assertNull($bConstraint->payload); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class UserPasswordDummy +{ + #[UserPassword] + private $a; + + #[UserPassword(service: 'my_service', message: 'myMessage')] + private $b; + + #[UserPassword(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordValidatorTest.php b/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordValidatorTest.php index efdc8585f0..f5d29e96df 100644 --- a/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordValidatorTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Validator/Constraints/UserPasswordValidatorTest.php @@ -56,12 +56,11 @@ abstract class UserPasswordValidatorTest extends ConstraintValidatorTestCase parent::setUp(); } - public function testPasswordIsValid() + /** + * @dataProvider provideConstraints + */ + public function testPasswordIsValid(UserPassword $constraint) { - $constraint = new UserPassword([ - 'message' => 'myMessage', - ]); - $this->encoder->expects($this->once()) ->method('isPasswordValid') ->with(static::PASSWORD, 'secret', static::SALT) @@ -72,12 +71,11 @@ abstract class UserPasswordValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } - public function testPasswordIsNotValid() + /** + * @dataProvider provideConstraints + */ + public function testPasswordIsNotValid(UserPassword $constraint) { - $constraint = new UserPassword([ - 'message' => 'myMessage', - ]); - $this->encoder->expects($this->once()) ->method('isPasswordValid') ->with(static::PASSWORD, 'secret', static::SALT) @@ -89,6 +87,15 @@ abstract class UserPasswordValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + public function provideConstraints(): iterable + { + yield 'Doctrine style' => [new UserPassword(['message' => 'myMessage'])]; + + if (\PHP_VERSION_ID >= 80000) { + yield 'named arguments' => [eval('return new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(message: "myMessage");')]; + } + } + /** * @dataProvider emptyPasswordData */ diff --git a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php index 35537b338a..f9de213906 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPassword.php @@ -17,11 +17,20 @@ use Symfony\Component\Validator\Constraint; * @Annotation * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class UserPassword extends Constraint { public $message = 'This value should be the user\'s current password.'; public $service = 'security.validator.user_password'; + public function __construct(array $options = null, string $message = null, string $service = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->service = $service ?? $this->service; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 7c2a7be513..70f7a9387d 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -28,13 +28,14 @@ "symfony/expression-language": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", "symfony/ldap": "^4.4|^5.0", - "symfony/validator": "^4.4|^5.0", + "symfony/validator": "^5.2", "psr/log": "~1.0" }, "conflict": { "symfony/event-dispatcher": "<4.4", "symfony/security-guard": "<4.4", - "symfony/ldap": "<4.4" + "symfony/ldap": "<4.4", + "symfony/validator": "<5.2" }, "suggest": { "psr/container-implementation": "To instantiate the Security class", diff --git a/src/Symfony/Component/Validator/Constraints/Bic.php b/src/Symfony/Component/Validator/Constraints/Bic.php index 0813728b4a..81cc1bd195 100644 --- a/src/Symfony/Component/Validator/Constraints/Bic.php +++ b/src/Symfony/Component/Validator/Constraints/Bic.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\Intl\Countries; use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyPathInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\LogicException; @@ -23,6 +24,7 @@ use Symfony\Component\Validator\Exception\LogicException; * * @author Michael Hirschler */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Bic extends Constraint { const INVALID_LENGTH_ERROR = '66dad313-af0b-4214-8566-6c799be9789c'; @@ -45,20 +47,33 @@ class Bic extends Constraint public $iban; public $ibanPropertyPath; - public function __construct($options = null) + /** + * {@inheritdoc} + * + * @param string|PropertyPathInterface|null $ibanPropertyPath + */ + public function __construct(array $options = null, string $message = null, string $iban = null, $ibanPropertyPath = null, string $ibanMessage = null, array $groups = null, $payload = null) { if (!class_exists(Countries::class)) { throw new LogicException('The Intl component is required to use the Bic constraint. Try running "composer require symfony/intl".'); } + if (null !== $ibanPropertyPath && !\is_string($ibanPropertyPath) && !$ibanPropertyPath instanceof PropertyPathInterface) { + throw new \TypeError(sprintf('"%s": Expected argument $ibanPropertyPath to be either null, a string or an instance of "%s", got "%s".', __METHOD__, PropertyPathInterface::class, get_debug_type($ibanPropertyPath))); + } - if (isset($options['iban']) && isset($options['ibanPropertyPath'])) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->ibanMessage = $ibanMessage ?? $this->ibanMessage; + $this->iban = $iban ?? $this->iban; + $this->ibanPropertyPath = $ibanPropertyPath ?? $this->ibanPropertyPath; + + if (null !== $this->iban && null !== $this->ibanPropertyPath) { throw new ConstraintDefinitionException('The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time.'); } - if (isset($options['ibanPropertyPath']) && !class_exists(PropertyAccess::class)) { + if (null !== $this->ibanPropertyPath && !class_exists(PropertyAccess::class)) { throw new LogicException(sprintf('The "symfony/property-access" component is required to use the "%s" constraint with the "ibanPropertyPath" option.', self::class)); } - - parent::__construct($options); } } diff --git a/src/Symfony/Component/Validator/Constraints/CardScheme.php b/src/Symfony/Component/Validator/Constraints/CardScheme.php index 4284826008..fb70473250 100644 --- a/src/Symfony/Component/Validator/Constraints/CardScheme.php +++ b/src/Symfony/Component/Validator/Constraints/CardScheme.php @@ -22,8 +22,22 @@ use Symfony\Component\Validator\Constraint; * @author Tim Nagel * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class CardScheme extends Constraint { + const AMEX = 'AMEX'; + const CHINA_UNIONPAY = 'CHINA_UNIONPAY'; + const DINERS = 'DINERS'; + const DISCOVER = 'DISCOVER'; + const INSTAPAYMENT = 'INSTAPAYMENT'; + const JCB = 'JCB'; + const LASER = 'LASER'; + const MAESTRO = 'MAESTRO'; + const MASTERCARD = 'MASTERCARD'; + const MIR = 'MIR'; + const UATP = 'UATP'; + const VISA = 'VISA'; + const NOT_NUMERIC_ERROR = 'a2ad9231-e827-485f-8a1e-ef4d9a6d5c2e'; const INVALID_FORMAT_ERROR = 'a8faedbf-1c2f-4695-8d22-55783be8efed'; @@ -35,6 +49,24 @@ class CardScheme extends Constraint public $message = 'Unsupported card type or invalid card number.'; public $schemes; + /** + * {@inheritdoc} + * + * @param array|string $schemes The schemes to validate against or a set of options + */ + public function __construct($schemes, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($schemes) && \is_string(key($schemes))) { + $options = array_merge($schemes, $options); + } else { + $options['value'] = $schemes; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + public function getDefaultOption() { return 'schemes'; diff --git a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php index 478047fb8f..f6ab7acde8 100644 --- a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php @@ -28,44 +28,44 @@ class CardSchemeValidator extends ConstraintValidator { protected $schemes = [ // American Express card numbers start with 34 or 37 and have 15 digits. - 'AMEX' => [ + CardScheme::AMEX => [ '/^3[47][0-9]{13}$/', ], // China UnionPay cards start with 62 and have between 16 and 19 digits. // Please note that these cards do not follow Luhn Algorithm as a checksum. - 'CHINA_UNIONPAY' => [ + CardScheme::CHINA_UNIONPAY => [ '/^62[0-9]{14,17}$/', ], // Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits. // There are Diners Club cards that begin with 5 and have 16 digits. // These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard. - 'DINERS' => [ + CardScheme::DINERS => [ '/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/', ], // Discover card numbers begin with 6011, 622126 through 622925, 644 through 649 or 65. // All have 16 digits. - 'DISCOVER' => [ + CardScheme::DISCOVER => [ '/^6011[0-9]{12}$/', '/^64[4-9][0-9]{13}$/', '/^65[0-9]{14}$/', '/^622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|91[0-9]|92[0-5])[0-9]{10}$/', ], // InstaPayment cards begin with 637 through 639 and have 16 digits. - 'INSTAPAYMENT' => [ + CardScheme::INSTAPAYMENT => [ '/^63[7-9][0-9]{13}$/', ], // JCB cards beginning with 2131 or 1800 have 15 digits. // JCB cards beginning with 35 have 16 digits. - 'JCB' => [ + CardScheme::JCB => [ '/^(?:2131|1800|35[0-9]{3})[0-9]{11}$/', ], // Laser cards begin with either 6304, 6706, 6709 or 6771 and have between 16 and 19 digits. - 'LASER' => [ + CardScheme::LASER => [ '/^(6304|670[69]|6771)[0-9]{12,15}$/', ], // Maestro international cards begin with 675900..675999 and have between 12 and 19 digits. // Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits. - 'MAESTRO' => [ + CardScheme::MAESTRO => [ '/^(6759[0-9]{2})[0-9]{6,13}$/', '/^(50[0-9]{4})[0-9]{6,13}$/', '/^5[6-9][0-9]{10,17}$/', @@ -73,20 +73,20 @@ class CardSchemeValidator extends ConstraintValidator ], // All MasterCard numbers start with the numbers 51 through 55. All have 16 digits. // October 2016 MasterCard numbers can also start with 222100 through 272099. - 'MASTERCARD' => [ + CardScheme::MASTERCARD => [ '/^5[1-5][0-9]{14}$/', '/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/', ], // Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then 12 digits - 'MIR' => [ + CardScheme::MIR => [ '/^220[0-4][0-9]{12}$/', ], // All UATP card numbers start with a 1 and have a length of 15 digits. - 'UATP' => [ + CardScheme::UATP => [ '/^1[0-9]{14}$/', ], // All Visa card numbers start with a 4 and have a length of 13, 16, or 19 digits. - 'VISA' => [ + CardScheme::VISA => [ '/^4([0-9]{12}|[0-9]{15}|[0-9]{18})$/', ], ]; diff --git a/src/Symfony/Component/Validator/Constraints/Cascade.php b/src/Symfony/Component/Validator/Constraints/Cascade.php index a5566eaa4e..2458d5c316 100644 --- a/src/Symfony/Component/Validator/Constraints/Cascade.php +++ b/src/Symfony/Component/Validator/Constraints/Cascade.php @@ -20,9 +20,10 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; * * @author Jules Pietri */ +#[\Attribute(\Attribute::TARGET_CLASS)] class Cascade extends Constraint { - public function __construct($options = null) + public function __construct(array $options = null) { if (\is_array($options) && \array_key_exists('groups', $options)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/Count.php b/src/Symfony/Component/Validator/Constraints/Count.php index 106ea7ed8a..c87b86d0eb 100644 --- a/src/Symfony/Component/Validator/Constraints/Count.php +++ b/src/Symfony/Component/Validator/Constraints/Count.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\MissingOptionsException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Count extends Constraint { const TOO_FEW_ERROR = 'bef8e338-6ae5-4caf-b8e2-50e7b0579e69'; @@ -40,19 +41,47 @@ class Count extends Constraint public $max; public $divisibleBy; - public function __construct($options = null) - { - if (null !== $options && !\is_array($options)) { - $options = [ - 'min' => $options, - 'max' => $options, - ]; - } elseif (\is_array($options) && isset($options['value']) && !isset($options['min']) && !isset($options['max'])) { - $options['min'] = $options['max'] = $options['value']; - unset($options['value']); + /** + * {@inheritdoc} + * + * @param int|array|null $exactly The expected exact count or a set of options + */ + public function __construct( + $exactly = null, + int $min = null, + int $max = null, + int $divisibleBy = null, + string $exactMessage = null, + string $minMessage = null, + string $maxMessage = null, + string $divisibleByMessage = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($exactly)) { + $options = array_merge($exactly, $options); + $exactly = $options['value'] ?? null; } - parent::__construct($options); + $min = $min ?? $options['min'] ?? null; + $max = $max ?? $options['max'] ?? null; + + unset($options['value'], $options['min'], $options['max']); + + if (null !== $exactly && null === $min && null === $max) { + $min = $max = $exactly; + } + + parent::__construct($options, $groups, $payload); + + $this->min = $min; + $this->max = $max; + $this->divisibleBy = $divisibleBy ?? $this->divisibleBy; + $this->exactMessage = $exactMessage ?? $this->exactMessage; + $this->minMessage = $minMessage ?? $this->minMessage; + $this->maxMessage = $maxMessage ?? $this->maxMessage; + $this->divisibleByMessage = $divisibleByMessage ?? $this->divisibleByMessage; if (null === $this->min && null === $this->max && null === $this->divisibleBy) { throw new MissingOptionsException(sprintf('Either option "min", "max" or "divisibleBy" must be given for constraint "%s".', __CLASS__), ['min', 'max', 'divisibleBy']); diff --git a/src/Symfony/Component/Validator/Constraints/Country.php b/src/Symfony/Component/Validator/Constraints/Country.php index 1cf099c64a..4c445d31cd 100644 --- a/src/Symfony/Component/Validator/Constraints/Country.php +++ b/src/Symfony/Component/Validator/Constraints/Country.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\LogicException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Country extends Constraint { const NO_SUCH_COUNTRY_ERROR = '8f900c12-61bd-455d-9398-996cd040f7f0'; @@ -32,12 +33,20 @@ class Country extends Constraint public $message = 'This value is not a valid country.'; public $alpha3 = false; - public function __construct($options = null) - { + public function __construct( + array $options = null, + string $message = null, + bool $alpha3 = null, + array $groups = null, + $payload = null + ) { if (!class_exists(Countries::class)) { throw new LogicException('The Intl component is required to use the Country constraint. Try running "composer require symfony/intl".'); } - parent::__construct($options); + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->alpha3 = $alpha3 ?? $this->alpha3; } } diff --git a/src/Symfony/Component/Validator/Constraints/Currency.php b/src/Symfony/Component/Validator/Constraints/Currency.php index 2ee5ae3789..16bda3a163 100644 --- a/src/Symfony/Component/Validator/Constraints/Currency.php +++ b/src/Symfony/Component/Validator/Constraints/Currency.php @@ -22,6 +22,7 @@ use Symfony\Component\Validator\Exception\LogicException; * @author Miha Vrhovnik * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Currency extends Constraint { const NO_SUCH_CURRENCY_ERROR = '69945ac1-2db4-405f-bec7-d2772f73df52'; @@ -32,12 +33,14 @@ class Currency extends Constraint public $message = 'This value is not a valid currency.'; - public function __construct($options = null) + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) { if (!class_exists(Currencies::class)) { throw new LogicException('The Intl component is required to use the Currency constraint. Try running "composer require symfony/intl".'); } - parent::__construct($options); + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; } } diff --git a/src/Symfony/Component/Validator/Constraints/Date.php b/src/Symfony/Component/Validator/Constraints/Date.php index 530475ff7d..e2135aec45 100644 --- a/src/Symfony/Component/Validator/Constraints/Date.php +++ b/src/Symfony/Component/Validator/Constraints/Date.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Date extends Constraint { const INVALID_FORMAT_ERROR = '69819696-02ac-4a99-9ff0-14e127c4d1bc'; @@ -30,4 +31,11 @@ class Date extends Constraint ]; public $message = 'This value is not a valid date.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/DateTime.php b/src/Symfony/Component/Validator/Constraints/DateTime.php index b700bacd3f..281e625797 100644 --- a/src/Symfony/Component/Validator/Constraints/DateTime.php +++ b/src/Symfony/Component/Validator/Constraints/DateTime.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class DateTime extends Constraint { const INVALID_FORMAT_ERROR = '1a9da513-2640-4f84-9b6a-4d99dcddc628'; @@ -34,6 +35,24 @@ class DateTime extends Constraint public $format = 'Y-m-d H:i:s'; public $message = 'This value is not a valid datetime.'; + /** + * {@inheritdoc} + * + * @param string|array|null $format + */ + public function __construct($format = null, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($format)) { + $options = array_merge($format, $options); + } elseif (null !== $format) { + $options['value'] = $format; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + public function getDefaultOption() { return 'format'; diff --git a/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php b/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php index 66f7b1e491..9a91f009c8 100644 --- a/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php +++ b/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php @@ -24,9 +24,10 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; * * @author Kévin Dunglas */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] class DisableAutoMapping extends Constraint { - public function __construct($options = null) + public function __construct(array $options = null) { if (\is_array($options) && \array_key_exists('groups', $options)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/Email.php b/src/Symfony/Component/Validator/Constraints/Email.php index 22895ad356..92ee58c17d 100644 --- a/src/Symfony/Component/Validator/Constraints/Email.php +++ b/src/Symfony/Component/Validator/Constraints/Email.php @@ -22,6 +22,7 @@ use Symfony\Component\Validator\Exception\LogicException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Email extends Constraint { public const VALIDATION_MODE_HTML5 = 'html5'; @@ -49,13 +50,23 @@ class Email extends Constraint public $mode; public $normalizer; - public function __construct($options = null) - { + public function __construct( + array $options = null, + string $message = null, + string $mode = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { if (\is_array($options) && \array_key_exists('mode', $options) && !\in_array($options['mode'], self::$validationModes, true)) { throw new InvalidArgumentException('The "mode" parameter value is not valid.'); } - parent::__construct($options); + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->mode = $mode ?? $this->mode; + $this->normalizer = $normalizer ?? $this->normalizer; if (self::VALIDATION_MODE_STRICT === $this->mode && !class_exists(StrictEmailValidator::class)) { throw new LogicException(sprintf('The "egulias/email-validator" component is required to use the "%s" constraint in strict mode.', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php b/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php index 8c485e186e..3136fd3ed7 100644 --- a/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php +++ b/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php @@ -24,9 +24,10 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; * * @author Kévin Dunglas */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] class EnableAutoMapping extends Constraint { - public function __construct($options = null) + public function __construct(array $options = null) { if (\is_array($options) && \array_key_exists('groups', $options)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); diff --git a/src/Symfony/Component/Validator/Constraints/Expression.php b/src/Symfony/Component/Validator/Constraints/Expression.php index 4c79ea0f22..94ffbb8aca 100644 --- a/src/Symfony/Component/Validator/Constraints/Expression.php +++ b/src/Symfony/Component/Validator/Constraints/Expression.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Constraints; +use Symfony\Component\ExpressionLanguage\Expression as ExpressionObject; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\LogicException; @@ -22,6 +23,7 @@ use Symfony\Component\Validator\Exception\LogicException; * @author Fabien Potencier * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class Expression extends Constraint { const EXPRESSION_FAILED_ERROR = '6b3befbc-2f01-4ddf-be21-b57898905284'; @@ -34,13 +36,35 @@ class Expression extends Constraint public $expression; public $values = []; - public function __construct($options = null) - { + /** + * {@inheritdoc} + * + * @param string|ExpressionObject|array $expression The expression to evaluate or an array of options + */ + public function __construct( + $expression, + string $message = null, + array $values = null, + array $groups = null, + $payload = null, + array $options = [] + ) { if (!class_exists(ExpressionLanguage::class)) { throw new LogicException(sprintf('The "symfony/expression-language" component is required to use the "%s" constraint.', __CLASS__)); } - parent::__construct($options); + if (\is_array($expression)) { + $options = array_merge($expression, $options); + } elseif (!\is_string($expression) && !$expression instanceof ExpressionObject) { + throw new \TypeError(sprintf('"%s": Expected argument $expression to be either a string, an instance of "%s" or an array, got "%s".', __METHOD__, ExpressionObject::class, get_debug_type($expression))); + } else { + $options['value'] = $expression; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->values = $values ?? $this->values; } /** diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php b/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php index 50790fba05..a68d2e69a1 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionLanguageSyntax.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Andrey Sevastianov */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class ExpressionLanguageSyntax extends Constraint { const EXPRESSION_LANGUAGE_SYNTAX_ERROR = '1766a3f3-ff03-40eb-b053-ab7aa23d988a'; @@ -29,7 +30,16 @@ class ExpressionLanguageSyntax extends Constraint public $message = 'This value should be a valid expression.'; public $service; - public $allowedVariables = null; + public $allowedVariables; + + public function __construct(array $options = null, string $message = null, string $service = null, array $allowedVariables = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->service = $service ?? $this->service; + $this->allowedVariables = $allowedVariables ?? $this->allowedVariables; + } /** * {@inheritdoc} diff --git a/src/Symfony/Component/Validator/Constraints/Hostname.php b/src/Symfony/Component/Validator/Constraints/Hostname.php index aaa994b2d3..4f923c6fc8 100644 --- a/src/Symfony/Component/Validator/Constraints/Hostname.php +++ b/src/Symfony/Component/Validator/Constraints/Hostname.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Dmitrii Poddubnyi */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Hostname extends Constraint { const INVALID_HOSTNAME_ERROR = '7057ffdb-0af4-4f7e-bd5e-e9acfa6d7a2d'; @@ -29,4 +30,17 @@ class Hostname extends Constraint public $message = 'This value is not a valid hostname.'; public $requireTld = true; + + public function __construct( + array $options = null, + string $message = null, + bool $requireTld = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->requireTld = $requireTld ?? $this->requireTld; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Iban.php b/src/Symfony/Component/Validator/Constraints/Iban.php index 231f8c838d..1d2989f2e5 100644 --- a/src/Symfony/Component/Validator/Constraints/Iban.php +++ b/src/Symfony/Component/Validator/Constraints/Iban.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Constraint; * @author Michael Schummel * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Iban extends Constraint { const INVALID_COUNTRY_CODE_ERROR = 'de78ee2c-bd50-44e2-aec8-3d8228aeadb9'; @@ -38,4 +39,11 @@ class Iban extends Constraint ]; public $message = 'This is not a valid International Bank Account Number (IBAN).'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Ip.php b/src/Symfony/Component/Validator/Constraints/Ip.php index b4bb592855..e3b5a80a93 100644 --- a/src/Symfony/Component/Validator/Constraints/Ip.php +++ b/src/Symfony/Component/Validator/Constraints/Ip.php @@ -24,6 +24,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException; * @author Bernhard Schussek * @author Joseph Bielawski */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Ip extends Constraint { const V4 = '4'; @@ -78,9 +79,19 @@ class Ip extends Constraint /** * {@inheritdoc} */ - public function __construct($options = null) - { - parent::__construct($options); + public function __construct( + array $options = null, + string $version = null, + string $message = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->version = $version ?? $this->version; + $this->message = $message ?? $this->message; + $this->normalizer = $normalizer ?? $this->normalizer; if (!\in_array($this->version, self::$versions)) { throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', self::$versions))); diff --git a/src/Symfony/Component/Validator/Constraints/Isbn.php b/src/Symfony/Component/Validator/Constraints/Isbn.php index 6629c823e1..4e910876c4 100644 --- a/src/Symfony/Component/Validator/Constraints/Isbn.php +++ b/src/Symfony/Component/Validator/Constraints/Isbn.php @@ -21,8 +21,12 @@ use Symfony\Component\Validator\Constraint; * @author Manuel Reinhard * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Isbn extends Constraint { + const ISBN_10 = 'isbn10'; + const ISBN_13 = 'isbn13'; + const TOO_SHORT_ERROR = '949acbb0-8ef5-43ed-a0e9-032dfd08ae45'; const TOO_LONG_ERROR = '3171387d-f80a-47b3-bd6e-60598545316a'; const INVALID_CHARACTERS_ERROR = '23d21cea-da99-453d-98b1-a7d916fbb339'; @@ -43,6 +47,35 @@ class Isbn extends Constraint public $type; public $message; + /** + * {@inheritdoc} + * + * @param string|array|null $type The ISBN standard to validate or a set of options + */ + public function __construct( + $type = null, + string $message = null, + string $isbn10Message = null, + string $isbn13Message = null, + string $bothIsbnMessage = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($type)) { + $options = array_merge($type, $options); + } elseif (null !== $type) { + $options['value'] = $type; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->isbn10Message = $isbn10Message ?? $this->isbn10Message; + $this->isbn13Message = $isbn13Message ?? $this->isbn13Message; + $this->bothIsbnMessage = $bothIsbnMessage ?? $this->bothIsbnMessage; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Validator/Constraints/IsbnValidator.php b/src/Symfony/Component/Validator/Constraints/IsbnValidator.php index 5b59f3c7a8..e8e54eb636 100644 --- a/src/Symfony/Component/Validator/Constraints/IsbnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsbnValidator.php @@ -48,7 +48,7 @@ class IsbnValidator extends ConstraintValidator $canonical = str_replace('-', '', $value); // Explicitly validate against ISBN-10 - if ('isbn10' === $constraint->type) { + if (Isbn::ISBN_10 === $constraint->type) { if (true !== ($code = $this->validateIsbn10($canonical))) { $this->context->buildViolation($this->getMessage($constraint, $constraint->type)) ->setParameter('{{ value }}', $this->formatValue($value)) @@ -60,7 +60,7 @@ class IsbnValidator extends ConstraintValidator } // Explicitly validate against ISBN-13 - if ('isbn13' === $constraint->type) { + if (Isbn::ISBN_13 === $constraint->type) { if (true !== ($code = $this->validateIsbn13($canonical))) { $this->context->buildViolation($this->getMessage($constraint, $constraint->type)) ->setParameter('{{ value }}', $this->formatValue($value)) @@ -174,9 +174,9 @@ class IsbnValidator extends ConstraintValidator { if (null !== $constraint->message) { return $constraint->message; - } elseif ('isbn10' === $type) { + } elseif (Isbn::ISBN_10 === $type) { return $constraint->isbn10Message; - } elseif ('isbn13' === $type) { + } elseif (Isbn::ISBN_13 === $type) { return $constraint->isbn13Message; } diff --git a/src/Symfony/Component/Validator/Constraints/Isin.php b/src/Symfony/Component/Validator/Constraints/Isin.php index 586ea829d2..940577774d 100644 --- a/src/Symfony/Component/Validator/Constraints/Isin.php +++ b/src/Symfony/Component/Validator/Constraints/Isin.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Laurent Masforné */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Isin extends Constraint { const VALIDATION_LENGTH = 12; @@ -35,4 +36,11 @@ class Isin extends Constraint ]; public $message = 'This is not a valid International Securities Identification Number (ISIN).'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Issn.php b/src/Symfony/Component/Validator/Constraints/Issn.php index 88d181fa63..4608b9c3ed 100644 --- a/src/Symfony/Component/Validator/Constraints/Issn.php +++ b/src/Symfony/Component/Validator/Constraints/Issn.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Constraint; * @author Antonio J. García Lagar * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Issn extends Constraint { const TOO_SHORT_ERROR = '6a20dd3d-f463-4460-8e7b-18a1b98abbfb'; @@ -41,4 +42,19 @@ class Issn extends Constraint public $message = 'This value is not a valid ISSN.'; public $caseSensitive = false; public $requireHyphen = false; + + public function __construct( + array $options = null, + string $message = null, + bool $caseSensitive = null, + bool $requireHyphen = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->caseSensitive = $caseSensitive ?? $this->caseSensitive; + $this->requireHyphen = $requireHyphen ?? $this->requireHyphen; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Json.php b/src/Symfony/Component/Validator/Constraints/Json.php index 74d55f775d..861502ca61 100644 --- a/src/Symfony/Component/Validator/Constraints/Json.php +++ b/src/Symfony/Component/Validator/Constraints/Json.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Imad ZAIRIG */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Json extends Constraint { const INVALID_JSON_ERROR = '0789c8ad-2d2b-49a4-8356-e2ce63998504'; @@ -28,4 +29,11 @@ class Json extends Constraint ]; public $message = 'This value should be valid JSON.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Language.php b/src/Symfony/Component/Validator/Constraints/Language.php index 1eac3126f2..e4c437fab0 100644 --- a/src/Symfony/Component/Validator/Constraints/Language.php +++ b/src/Symfony/Component/Validator/Constraints/Language.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\LogicException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Language extends Constraint { const NO_SUCH_LANGUAGE_ERROR = 'ee65fec4-9a20-4202-9f39-ca558cd7bdf7'; @@ -32,12 +33,20 @@ class Language extends Constraint public $message = 'This value is not a valid language.'; public $alpha3 = false; - public function __construct($options = null) - { + public function __construct( + array $options = null, + string $message = null, + bool $alpha3 = null, + array $groups = null, + $payload = null + ) { if (!class_exists(Languages::class)) { throw new LogicException('The Intl component is required to use the Language constraint. Try running "composer require symfony/intl".'); } - parent::__construct($options); + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->alpha3 = $alpha3 ?? $this->alpha3; } } diff --git a/src/Symfony/Component/Validator/Constraints/Length.php b/src/Symfony/Component/Validator/Constraints/Length.php index 3daebf8ff1..aaf5d66df3 100644 --- a/src/Symfony/Component/Validator/Constraints/Length.php +++ b/src/Symfony/Component/Validator/Constraints/Length.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\MissingOptionsException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Length extends Constraint { const TOO_SHORT_ERROR = '9ff3fdc4-b214-49db-8718-39c315e33d45'; @@ -43,19 +44,49 @@ class Length extends Constraint public $normalizer; public $allowEmptyString = false; - public function __construct($options = null) - { - if (null !== $options && !\is_array($options)) { - $options = [ - 'min' => $options, - 'max' => $options, - ]; - } elseif (\is_array($options) && isset($options['value']) && !isset($options['min']) && !isset($options['max'])) { - $options['min'] = $options['max'] = $options['value']; - unset($options['value']); + /** + * {@inheritdoc} + * + * @param int|array|null $exactly The expected exact length or a set of options + */ + public function __construct( + $exactly = null, + int $min = null, + int $max = null, + string $charset = null, + callable $normalizer = null, + string $exactMessage = null, + string $minMessage = null, + string $maxMessage = null, + string $charsetMessage = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($exactly)) { + $options = array_merge($exactly, $options); + $exactly = $options['value'] ?? null; } - parent::__construct($options); + $min = $min ?? $options['min'] ?? null; + $max = $max ?? $options['max'] ?? null; + + unset($options['value'], $options['min'], $options['max']); + + if (null !== $exactly && null === $min && null === $max) { + $min = $max = $exactly; + } + + parent::__construct($options, $groups, $payload); + + $this->min = $min; + $this->max = $max; + $this->charset = $charset ?? $this->charset; + $this->normalizer = $normalizer ?? $this->normalizer; + $this->exactMessage = $exactMessage ?? $this->exactMessage; + $this->minMessage = $minMessage ?? $this->minMessage; + $this->maxMessage = $maxMessage ?? $this->maxMessage; + $this->charsetMessage = $charsetMessage ?? $this->charsetMessage; if (null === $this->min && null === $this->max) { throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint "%s".', __CLASS__), ['min', 'max']); diff --git a/src/Symfony/Component/Validator/Constraints/Locale.php b/src/Symfony/Component/Validator/Constraints/Locale.php index b2e73a4694..e07b13fd00 100644 --- a/src/Symfony/Component/Validator/Constraints/Locale.php +++ b/src/Symfony/Component/Validator/Constraints/Locale.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\LogicException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Locale extends Constraint { const NO_SUCH_LOCALE_ERROR = 'a0af4293-1f1a-4a1c-a328-979cba6182a2'; @@ -32,12 +33,20 @@ class Locale extends Constraint public $message = 'This value is not a valid locale.'; public $canonicalize = true; - public function __construct($options = null) - { + public function __construct( + array $options = null, + string $message = null, + bool $canonicalize = null, + array $groups = null, + $payload = null + ) { if (!class_exists(Locales::class)) { throw new LogicException('The Intl component is required to use the Locale constraint. Try running "composer require symfony/intl".'); } - parent::__construct($options); + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->canonicalize = $canonicalize ?? $this->canonicalize; } } diff --git a/src/Symfony/Component/Validator/Constraints/Luhn.php b/src/Symfony/Component/Validator/Constraints/Luhn.php index 85eebc793b..6a18508322 100644 --- a/src/Symfony/Component/Validator/Constraints/Luhn.php +++ b/src/Symfony/Component/Validator/Constraints/Luhn.php @@ -23,6 +23,7 @@ use Symfony\Component\Validator\Constraint; * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/ * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Luhn extends Constraint { const INVALID_CHARACTERS_ERROR = 'dfad6d23-1b74-4374-929b-5cbb56fc0d9e'; @@ -34,4 +35,15 @@ class Luhn extends Constraint ]; public $message = 'Invalid card number.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php b/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php index d38cab1a92..f1b459e2c9 100644 --- a/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php +++ b/src/Symfony/Component/Validator/Constraints/NotCompromisedPassword.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Constraint; * * @author Kévin Dunglas */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class NotCompromisedPassword extends Constraint { const COMPROMISED_PASSWORD_ERROR = 'd9bcdbfe-a9d6-4bfa-a8ff-da5fd93e0f6d'; @@ -30,4 +31,19 @@ class NotCompromisedPassword extends Constraint public $message = 'This password has been leaked in a data breach, it must not be used. Please use another password.'; public $threshold = 1; public $skipOnError = false; + + public function __construct( + array $options = null, + string $message = null, + int $threshold = null, + bool $skipOnError = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->threshold = $threshold ?? $this->threshold; + $this->skipOnError = $skipOnError ?? $this->skipOnError; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Regex.php b/src/Symfony/Component/Validator/Constraints/Regex.php index ccb815ca9c..6bf88a0232 100644 --- a/src/Symfony/Component/Validator/Constraints/Regex.php +++ b/src/Symfony/Component/Validator/Constraints/Regex.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Regex extends Constraint { const REGEX_FAILED_ERROR = 'de1e3db3-5ed4-4941-aae4-59f3667cc3a3'; @@ -34,9 +35,33 @@ class Regex extends Constraint public $match = true; public $normalizer; - public function __construct($options = null) - { - parent::__construct($options); + /** + * {@inheritdoc} + * + * @param string|array $pattern The pattern to evaluate or an array of options. + */ + public function __construct( + $pattern, + string $message = null, + string $htmlPattern = null, + bool $match = null, + callable $normalizer = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($pattern)) { + $options = array_merge($pattern, $options); + } elseif (null !== $pattern) { + $options['value'] = $pattern; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->htmlPattern = $htmlPattern ?? $this->htmlPattern; + $this->match = $match ?? $this->match; + $this->normalizer = $normalizer ?? $this->normalizer; if (null !== $this->normalizer && !\is_callable($this->normalizer)) { throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); diff --git a/src/Symfony/Component/Validator/Constraints/Time.php b/src/Symfony/Component/Validator/Constraints/Time.php index d1a3397b24..5517fb9c4a 100644 --- a/src/Symfony/Component/Validator/Constraints/Time.php +++ b/src/Symfony/Component/Validator/Constraints/Time.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Time extends Constraint { const INVALID_FORMAT_ERROR = '9d27b2bb-f755-4fbf-b725-39b1edbdebdf'; @@ -30,4 +31,15 @@ class Time extends Constraint ]; public $message = 'This value is not a valid time.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Timezone.php b/src/Symfony/Component/Validator/Constraints/Timezone.php index 9a46c15ddf..bdc3ee582e 100644 --- a/src/Symfony/Component/Validator/Constraints/Timezone.php +++ b/src/Symfony/Component/Validator/Constraints/Timezone.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; * @author Javier Spagnoletti * @author Hugo Hamon */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Timezone extends Constraint { public const TIMEZONE_IDENTIFIER_ERROR = '5ce113e6-5e64-4ea2-90fe-d2233956db13'; @@ -42,10 +43,29 @@ class Timezone extends Constraint /** * {@inheritdoc} + * + * @param int|array|null $zone A combination of {@see \DateTimeZone} class constants or a set of options. */ - public function __construct($options = null) - { - parent::__construct($options); + public function __construct( + $zone = null, + string $message = null, + string $countryCode = null, + bool $intlCompatible = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($zone)) { + $options = array_merge($zone, $options); + } elseif (null !== $zone) { + $options['value'] = $zone; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->countryCode = $countryCode ?? $this->countryCode; + $this->intlCompatible = $intlCompatible ?? $this->intlCompatible; if (null === $this->countryCode) { if (0 >= $this->zone || \DateTimeZone::ALL_WITH_BC < $this->zone) { diff --git a/src/Symfony/Component/Validator/Constraints/Traverse.php b/src/Symfony/Component/Validator/Constraints/Traverse.php index f5f66f53ff..fe6527dae3 100644 --- a/src/Symfony/Component/Validator/Constraints/Traverse.php +++ b/src/Symfony/Component/Validator/Constraints/Traverse.php @@ -19,17 +19,21 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_CLASS)] class Traverse extends Constraint { public $traverse = true; - public function __construct($options = null) + /** + * @param bool|array|null $traverse + */ + public function __construct($traverse = null) { - if (\is_array($options) && \array_key_exists('groups', $options)) { + if (\is_array($traverse) && \array_key_exists('groups', $traverse)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); } - parent::__construct($options); + parent::__construct($traverse); } /** diff --git a/src/Symfony/Component/Validator/Constraints/Type.php b/src/Symfony/Component/Validator/Constraints/Type.php index ac798bcd44..e0318e728a 100644 --- a/src/Symfony/Component/Validator/Constraints/Type.php +++ b/src/Symfony/Component/Validator/Constraints/Type.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Type extends Constraint { const INVALID_TYPE_ERROR = 'ba785a8c-82cb-4283-967c-3cf342181b40'; @@ -30,6 +31,24 @@ class Type extends Constraint public $message = 'This value should be of type {{ type }}.'; public $type; + /** + * {@inheritdoc} + * + * @param string|array $type One ore multiple types to validate against or a set of options. + */ + public function __construct($type, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($type) && \is_string(key($type))) { + $options = array_merge($type, $options); + } elseif (null !== $type) { + $options['value'] = $type; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Validator/Constraints/Ulid.php b/src/Symfony/Component/Validator/Constraints/Ulid.php index cf41107030..0ba2a7ccfd 100644 --- a/src/Symfony/Component/Validator/Constraints/Ulid.php +++ b/src/Symfony/Component/Validator/Constraints/Ulid.php @@ -18,6 +18,7 @@ use Symfony\Component\Validator\Constraint; * * @author Laurent Clouet */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Ulid extends Constraint { const TOO_SHORT_ERROR = '7b44804e-37d5-4df4-9bdd-b738d4a45bb4'; @@ -33,4 +34,15 @@ class Ulid extends Constraint ]; public $message = 'This is not a valid ULID.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Unique.php b/src/Symfony/Component/Validator/Constraints/Unique.php index 743ec02e91..ee50eed95f 100644 --- a/src/Symfony/Component/Validator/Constraints/Unique.php +++ b/src/Symfony/Component/Validator/Constraints/Unique.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint; * * @author Yevgeniy Zholkevskiy */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Unique extends Constraint { public const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a'; @@ -28,4 +29,15 @@ class Unique extends Constraint ]; public $message = 'This collection should contain only unique elements.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } } diff --git a/src/Symfony/Component/Validator/Constraints/Url.php b/src/Symfony/Component/Validator/Constraints/Url.php index 71b1121d69..d40541fcf9 100644 --- a/src/Symfony/Component/Validator/Constraints/Url.php +++ b/src/Symfony/Component/Validator/Constraints/Url.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException; * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Url extends Constraint { const INVALID_URL_ERROR = '57c2f299-1154-4870-89bb-ef3b1f5ad229'; @@ -33,9 +34,21 @@ class Url extends Constraint public $relativeProtocol = false; public $normalizer; - public function __construct($options = null) - { - parent::__construct($options); + public function __construct( + array $options = null, + string $message = null, + array $protocols = null, + bool $relativeProtocol = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->protocols = $protocols ?? $this->protocols; + $this->relativeProtocol = $relativeProtocol ?? $this->relativeProtocol; + $this->normalizer = $normalizer ?? $this->normalizer; if (null !== $this->normalizer && !\is_callable($this->normalizer)) { throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); diff --git a/src/Symfony/Component/Validator/Constraints/Uuid.php b/src/Symfony/Component/Validator/Constraints/Uuid.php index e43b04e4a5..55c7f9bb26 100644 --- a/src/Symfony/Component/Validator/Constraints/Uuid.php +++ b/src/Symfony/Component/Validator/Constraints/Uuid.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException; * @author Colin O'Dell * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Uuid extends Constraint { const TOO_SHORT_ERROR = 'aa314679-dac9-4f54-bf97-b2049df8f2a3'; @@ -46,6 +47,15 @@ class Uuid extends Constraint const V5_SHA1 = 5; const V6_SORTABLE = 6; + const ALL_VERSIONS = [ + self::V1_MAC, + self::V2_DCE, + self::V3_MD5, + self::V4_RANDOM, + self::V5_SHA1, + self::V6_SORTABLE, + ]; + /** * Message to display when validation fails. * @@ -69,20 +79,30 @@ class Uuid extends Constraint * * @var int[] */ - public $versions = [ - self::V1_MAC, - self::V2_DCE, - self::V3_MD5, - self::V4_RANDOM, - self::V5_SHA1, - self::V6_SORTABLE, - ]; + public $versions = self::ALL_VERSIONS; public $normalizer; - public function __construct($options = null) - { - parent::__construct($options); + /** + * {@inheritdoc} + * + * @param int[]|null $versions + */ + public function __construct( + array $options = null, + string $message = null, + array $versions = null, + bool $strict = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->versions = $versions ?? $this->versions; + $this->strict = $strict ?? $this->strict; + $this->normalizer = $normalizer ?? $this->normalizer; if (null !== $this->normalizer && !\is_callable($this->normalizer)) { throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php index b07f6f9f83..816fca1de4 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use Symfony\Component\Validator\Constraints\Bic; use Symfony\Component\Validator\Constraints\BicValidator; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; class BicValidatorTest extends ConstraintValidatorTestCase @@ -68,6 +70,27 @@ class BicValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + */ + public function testInvalidComparisonToPropertyPathFromAttribute() + { + $classMetadata = new ClassMetadata(BicDummy::class); + (new AnnotationLoader())->loadClassMetadata($classMetadata); + + [$constraint] = $classMetadata->properties['bic1']->constraints; + + $this->setObject(new BicDummy()); + + $this->validator->validate('UNCRIT2B912', $constraint); + + $this->buildViolation('Constraint Message') + ->setParameter('{{ value }}', '"UNCRIT2B912"') + ->setParameter('{{ iban }}', 'FR14 2004 1010 0505 0001 3M02 606') + ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR) + ->assertRaised(); + } + public function testValidComparisonToValue() { $constraint = new Bic(['iban' => 'FR14 2004 1010 0505 0001 3M02 606']); @@ -92,6 +115,25 @@ class BicValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + */ + public function testInvalidComparisonToValueFromAttribute() + { + $classMetadata = new ClassMetadata(BicDummy::class); + (new AnnotationLoader())->loadClassMetadata($classMetadata); + + [$constraint] = $classMetadata->properties['bic1']->constraints; + + $this->validator->validate('UNCRIT2B912', $constraint); + + $this->buildViolation('Constraint Message') + ->setParameter('{{ value }}', '"UNCRIT2B912"') + ->setParameter('{{ iban }}', 'FR14 2004 1010 0505 0001 3M02 606') + ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR) + ->assertRaised(); + } + public function testNoViolationOnNullObjectWithPropertyPath() { $constraint = new Bic(['ibanPropertyPath' => 'propertyPath']); @@ -113,6 +155,17 @@ class BicValidatorTest extends ConstraintValidatorTestCase ]); } + /** + * @requires PHP 8 + */ + public function testThrowsConstraintExceptionIfBothValueAndPropertyPathNamed() + { + $this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); + $this->expectExceptionMessage('The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time'); + + eval('new \Symfony\Component\Validator\Constraints\Bic(iban: "value", ibanPropertyPath: "propertyPath");'); + } + public function testInvalidValuePath() { $constraint = new Bic(['ibanPropertyPath' => 'foo']); @@ -173,6 +226,22 @@ class BicValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getInvalidBics + */ + public function testInvalidBicsNamed($bic, $code) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Bic(message: "myMessage");'); + + $this->validator->validate($bic, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$bic.'"') + ->setCode($code) + ->assertRaised(); + } + public function getInvalidBics() { return [ @@ -258,3 +327,13 @@ class BicComparisonTestClass return $this->value; } } + +class BicDummy +{ + #[Bic(iban: 'FR14 2004 1010 0505 0001 3M02 606', ibanMessage: 'Constraint Message')] + private $bic1; + #[Bic(ibanPropertyPath: 'iban', ibanMessage: 'Constraint Message')] + private $bic2; + + private $iban = 'FR14 2004 1010 0505 0001 3M02 606'; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeTest.php new file mode 100644 index 0000000000..5c1cacbc4c --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeTest.php @@ -0,0 +1,55 @@ + + * + * 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\CardScheme; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class CardSchemeTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(CardSchemeDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame([CardScheme::MASTERCARD, CardScheme::VISA], $aConstraint->schemes); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame([CardScheme::AMEX], $bConstraint->schemes); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'CardSchemeDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame([CardScheme::DINERS], $cConstraint->schemes); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class CardSchemeDummy +{ + #[CardScheme([CardScheme::MASTERCARD, CardScheme::VISA])] + private $a; + + #[CardScheme(schemes: [CardScheme::AMEX], message: 'myMessage')] + private $b; + + #[CardScheme(schemes: [CardScheme::DINERS], groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php index ddc9edb6c0..57204fe357 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php @@ -46,6 +46,16 @@ class CardSchemeValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + public function testValidNumberWithOrderedArguments() + { + $this->validator->validate( + '5555555555554444', + new CardScheme([CardScheme::MASTERCARD, CardScheme::VISA]) + ); + + $this->assertNoViolation(); + } + /** * @dataProvider getInvalidNumbers */ @@ -64,6 +74,22 @@ class CardSchemeValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + */ + public function testInvalidNumberNamedArguments() + { + $this->validator->validate( + '2721001234567890', + eval('use Symfony\Component\Validator\Constraints\CardScheme; return new CardScheme(schemes: [CardScheme::MASTERCARD, CardScheme::VISA], message: "myMessage");') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"2721001234567890"') + ->setCode(CardScheme::INVALID_FORMAT_ERROR) + ->assertRaised(); + } + public function getValidNumbers() { return [ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CascadeTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CascadeTest.php new file mode 100644 index 0000000000..a63059313c --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/CascadeTest.php @@ -0,0 +1,38 @@ + + * + * 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\Cascade; +use Symfony\Component\Validator\Mapping\CascadingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class CascadeTest extends TestCase +{ + public function testCascadeAttribute() + { + $metadata = new ClassMetadata(CascadeDummy::class); + $loader = new AnnotationLoader(); + self::assertSame(CascadingStrategy::NONE, $metadata->getCascadingStrategy()); + self::assertTrue($loader->loadClassMetadata($metadata)); + self::assertSame(CascadingStrategy::CASCADE, $metadata->getCascadingStrategy()); + } +} + +#[Cascade] +class CascadeDummy +{ +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CountTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CountTest.php new file mode 100644 index 0000000000..fe84d8eb0f --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/CountTest.php @@ -0,0 +1,62 @@ + + * + * 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\Count; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class CountTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(CountDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(42, $aConstraint->min); + self::assertSame(42, $aConstraint->max); + self::assertNull($aConstraint->divisibleBy); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(1, $bConstraint->min); + self::assertSame(4711, $bConstraint->max); + self::assertNull($bConstraint->divisibleBy); + self::assertSame('myMinMessage', $bConstraint->minMessage); + self::assertSame('myMaxMessage', $bConstraint->maxMessage); + self::assertSame(['Default', 'CountDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertNull($cConstraint->min); + self::assertNull($cConstraint->max); + self::assertSame(10, $cConstraint->divisibleBy); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class CountDummy +{ + #[Count(exactly: 42)] + private $a; + + #[Count(min: 1, max: 4711, minMessage: 'myMinMessage', maxMessage: 'myMaxMessage')] + private $b; + + #[Count(divisibleBy: 10, groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CountValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CountValidatorTest.php index a6337a534e..6616457313 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CountValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CountValidatorTest.php @@ -79,6 +79,18 @@ abstract class CountValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + /** + * @requires PHP 8 + * @dataProvider getThreeOrLessElements + */ + public function testValidValuesMaxNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Count(max: 3);'); + $this->validator->validate($value, $constraint); + + $this->assertNoViolation(); + } + /** * @dataProvider getFiveOrMoreElements */ @@ -90,6 +102,18 @@ abstract class CountValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + /** + * @requires PHP 8 + * @dataProvider getFiveOrMoreElements + */ + public function testValidValuesMinNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Count(min: 5);'); + $this->validator->validate($value, $constraint); + + $this->assertNoViolation(); + } + /** * @dataProvider getFourElements */ @@ -101,6 +125,18 @@ abstract class CountValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + /** + * @requires PHP 8 + * @dataProvider getFourElements + */ + public function testValidValuesExactNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Count(exactly: 4);'); + $this->validator->validate($value, $constraint); + + $this->assertNoViolation(); + } + /** * @dataProvider getFiveOrMoreElements */ @@ -122,6 +158,25 @@ abstract class CountValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getFiveOrMoreElements + */ + public function testTooManyValuesNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Count(max: 4, maxMessage: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ count }}', \count($value)) + ->setParameter('{{ limit }}', 4) + ->setInvalidValue($value) + ->setPlural(4) + ->setCode(Count::TOO_MANY_ERROR) + ->assertRaised(); + } + /** * @dataProvider getThreeOrLessElements */ @@ -143,6 +198,25 @@ abstract class CountValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getThreeOrLessElements + */ + public function testTooFewValuesNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Count(min: 4, minMessage: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ count }}', \count($value)) + ->setParameter('{{ limit }}', 4) + ->setInvalidValue($value) + ->setPlural(4) + ->setCode(Count::TOO_FEW_ERROR) + ->assertRaised(); + } + /** * @dataProvider getFiveOrMoreElements */ @@ -165,6 +239,25 @@ abstract class CountValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getFiveOrMoreElements + */ + public function testTooManyValuesExactNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Count(exactly: 4, exactMessage: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ count }}', \count($value)) + ->setParameter('{{ limit }}', 4) + ->setInvalidValue($value) + ->setPlural(4) + ->setCode(Count::TOO_MANY_ERROR) + ->assertRaised(); + } + /** * @dataProvider getThreeOrLessElements */ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CountryTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CountryTest.php new file mode 100644 index 0000000000..6db035710e --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/CountryTest.php @@ -0,0 +1,54 @@ + + * + * 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\Country; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class CountryTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(CountryDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertFalse($aConstraint->alpha3); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertTrue($bConstraint->alpha3); + self::assertSame(['Default', 'CountryDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class CountryDummy +{ + #[Country] + private $a; + + #[Country(message: 'myMessage', alpha3: true)] + private $b; + + #[Country(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php index e46079838d..27d83fea27 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php @@ -152,6 +152,22 @@ class CountryValidatorTest extends ConstraintValidatorTestCase ]; } + /** + * @requires PHP 8 + */ + public function testInvalidAlpha3CountryNamed() + { + $this->validator->validate( + 'DE', + eval('return new \Symfony\Component\Validator\Constraints\Country(alpha3: true, message: "myMessage");') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"DE"') + ->setCode(Country::NO_SUCH_COUNTRY_ERROR) + ->assertRaised(); + } + public function testValidateUsingCountrySpecificLocale() { // in order to test with "en_GB" diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CurrencyTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CurrencyTest.php new file mode 100644 index 0000000000..1276a1b7ec --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/CurrencyTest.php @@ -0,0 +1,50 @@ + + * + * 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\Currency; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class CurrencyTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(CurrencyDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'CurrencyDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class CurrencyDummy +{ + #[Currency] + private $a; + + #[Currency(message: 'myMessage')] + private $b; + + #[Currency(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php index 08aef0010d..f2ca4b5a10 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php @@ -111,6 +111,22 @@ class CurrencyValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getInvalidCurrencies + */ + public function testInvalidCurrenciesNamed($currency) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Currency(message: "myMessage");'); + + $this->validator->validate($currency, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$currency.'"') + ->setCode(Currency::NO_SUCH_CURRENCY_ERROR) + ->assertRaised(); + } + public function getInvalidCurrencies() { return [ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateTest.php new file mode 100644 index 0000000000..911be3f0c9 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateTest.php @@ -0,0 +1,50 @@ + + * + * 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\Date; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class DateTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(DateDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'DateDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class DateDummy +{ + #[Date] + private $a; + + #[Date(message: 'myMessage')] + private $b; + + #[Date(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateTimeTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateTimeTest.php new file mode 100644 index 0000000000..6c76a3a890 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateTimeTest.php @@ -0,0 +1,55 @@ + + * + * 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\DateTime; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class DateTimeTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(DateTimeDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame('Y-m-d H:i:s', $aConstraint->format); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('d.m.Y', $bConstraint->format); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'DateTimeDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame('m/d/Y', $cConstraint->format); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class DateTimeDummy +{ + #[DateTime] + private $a; + + #[DateTime(format: 'd.m.Y', message: 'myMessage')] + private $b; + + #[DateTime('m/d/Y', groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateTimeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateTimeValidatorTest.php index ceb3738d28..438957af74 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DateTimeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateTimeValidatorTest.php @@ -115,6 +115,21 @@ class DateTimeValidatorTest extends ConstraintValidatorTestCase ]; } + /** + * @requires PHP 8 + */ + public function testInvalidDateTimeNamed() + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\DateTime(message: "myMessage", format: "Y-m-d");'); + + $this->validator->validate('2010-01-01 00:00:00', $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"2010-01-01 00:00:00"') + ->setCode(DateTime::INVALID_FORMAT_ERROR) + ->assertRaised(); + } + public function testDateTimeWithTrailingData() { $this->validator->validate('1995-05-10 00:00:00', new DateTime([ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php index b07dfc37c7..ed97ece8e5 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php @@ -78,6 +78,21 @@ class DateValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + */ + public function testInvalidDateNamed() + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Date(message: "myMessage");'); + + $this->validator->validate('foobar', $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"foobar"') + ->setCode(Date::INVALID_FORMAT_ERROR) + ->assertRaised(); + } + public function getInvalidDates() { return [ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php index 49dd532c7b..a81f0249d9 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php @@ -14,6 +14,9 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\DisableAutoMapping; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Mapping\AutoMappingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Kévin Dunglas @@ -27,4 +30,21 @@ class DisableAutoMappingTest extends TestCase new DisableAutoMapping(['groups' => 'foo']); } + + /** + * @requires PHP 8 + */ + public function testDisableAutoMappingAttribute() + { + $metadata = new ClassMetadata(DisableAutoMappingDummy::class); + $loader = new AnnotationLoader(); + self::assertSame(AutoMappingStrategy::NONE, $metadata->getAutoMappingStrategy()); + self::assertTrue($loader->loadClassMetadata($metadata)); + self::assertSame(AutoMappingStrategy::DISABLED, $metadata->getAutoMappingStrategy()); + } +} + +#[DisableAutoMapping] +class DisableAutoMappingDummy +{ } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php index 57c71780df..8697a85528 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; class EmailTest extends TestCase { @@ -50,4 +52,38 @@ class EmailTest extends TestCase $this->expectExceptionMessage('The "normalizer" option must be a valid callable ("stdClass" given).'); new Email(['normalizer' => new \stdClass()]); } + + /** + * @requires PHP 8 + */ + public function testAttribute() + { + $metadata = new ClassMetadata(EmailDummy::class); + (new AnnotationLoader())->loadClassMetadata($metadata); + + [$aConstraint] = $metadata->properties['a']->constraints; + self::assertNull($aConstraint->mode); + self::assertNull($aConstraint->normalizer); + + [$bConstraint] = $metadata->properties['b']->constraints; + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(Email::VALIDATION_MODE_HTML5, $bConstraint->mode); + self::assertSame('trim', $bConstraint->normalizer); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class EmailDummy +{ + #[Email] + private $a; + + #[Email(message: 'myMessage', mode: Email::VALIDATION_MODE_HTML5, normalizer: 'trim')] + private $b; + + #[Email(groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php index 7dc8b17068..d61d25c43e 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php @@ -14,6 +14,9 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\EnableAutoMapping; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Mapping\AutoMappingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Kévin Dunglas @@ -27,4 +30,21 @@ class EnableAutoMappingTest extends TestCase new EnableAutoMapping(['groups' => 'foo']); } + + /** + * @requires PHP 8 + */ + public function testDisableAutoMappingAttribute() + { + $metadata = new ClassMetadata(EnableAutoMappingDummy::class); + $loader = new AnnotationLoader(); + self::assertSame(AutoMappingStrategy::NONE, $metadata->getAutoMappingStrategy()); + self::assertTrue($loader->loadClassMetadata($metadata)); + self::assertSame(AutoMappingStrategy::ENABLED, $metadata->getAutoMappingStrategy()); + } +} + +#[EnableAutoMapping] +class EnableAutoMappingDummy +{ } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php new file mode 100644 index 0000000000..f6c1457a0b --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxTest.php @@ -0,0 +1,86 @@ + + * + * 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\ExpressionLanguageSyntax; +use Symfony\Component\Validator\Constraints\ExpressionLanguageSyntaxValidator; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +class ExpressionLanguageSyntaxTest extends TestCase +{ + public function testValidatedByStandardValidator() + { + $constraint = new ExpressionLanguageSyntax(); + + self::assertSame(ExpressionLanguageSyntaxValidator::class, $constraint->validatedBy()); + } + + /** + * @dataProvider provideServiceValidatedConstraints + */ + public function testValidatedByService(ExpressionLanguageSyntax $constraint) + { + self::assertSame('my_service', $constraint->validatedBy()); + } + + public function provideServiceValidatedConstraints(): iterable + { + yield 'Doctrine style' => [new ExpressionLanguageSyntax(['service' => 'my_service'])]; + + if (\PHP_VERSION_ID < 80000) { + return; + } + + yield 'named arguments' => [eval('return new \Symfony\Component\Validator\Constraints\ExpressionLanguageSyntax(service: "my_service");')]; + + $metadata = new ClassMetadata(ExpressionLanguageSyntaxDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + yield 'attribute' => [$metadata->properties['b']->constraints[0]]; + } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(ExpressionLanguageSyntaxDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertNull($aConstraint->service); + self::assertNull($aConstraint->allowedVariables); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('my_service', $bConstraint->service); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'ExpressionLanguageSyntaxDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['foo', 'bar'], $cConstraint->allowedVariables); + self::assertSame(['my_group'], $cConstraint->groups); + } +} + +class ExpressionLanguageSyntaxDummy +{ + #[ExpressionLanguageSyntax] + private $a; + + #[ExpressionLanguageSyntax(service: 'my_service', message: 'myMessage')] + private $b; + + #[ExpressionLanguageSyntax(allowedVariables: ['foo', 'bar'], groups: ['my_group'])] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionTest.php new file mode 100644 index 0000000000..a22b9df0de --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionTest.php @@ -0,0 +1,56 @@ + + * + * 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\Expression; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class ExpressionTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(ExpressionDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame('value == "1"', $aConstraint->expression); + self::assertSame([], $aConstraint->values); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('value == "1"', $bConstraint->expression); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'ExpressionDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame('value == someVariable', $cConstraint->expression); + self::assertSame(['someVariable' => 42], $cConstraint->values); + self::assertSame(['foo'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class ExpressionDummy +{ + #[Expression('value == "1"')] + private $a; + + #[Expression(expression: 'value == "1"', message: 'myMessage')] + private $b; + + #[Expression(expression: 'value == someVariable', values: ['someVariable' => 42], groups: ['foo'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/HostnameTest.php b/src/Symfony/Component/Validator/Tests/Constraints/HostnameTest.php new file mode 100644 index 0000000000..cafa7d442a --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/HostnameTest.php @@ -0,0 +1,54 @@ + + * + * 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\Hostname; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class HostnameTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(HostnameDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertTrue($aConstraint->requireTld); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertFalse($bConstraint->requireTld); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'HostnameDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class HostnameDummy +{ + #[Hostname] + private $a; + + #[Hostname(message: "myMessage", requireTld: false)] + private $b; + + #[Hostname(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/HostnameValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/HostnameValidatorTest.php index 20bdf87a32..4764176795 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/HostnameValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/HostnameValidatorTest.php @@ -157,6 +157,22 @@ class HostnameValidatorTest extends ConstraintValidatorTestCase ]; } + /** + * @requires PHP 8 + */ + public function testReservedDomainsRaiseViolationIfTldRequiredNamed() + { + $this->validator->validate( + 'example', + eval('return new \Symfony\Component\Validator\Constraints\Hostname(message: "myMessage", requireTld: true);') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"example"') + ->setCode(Hostname::INVALID_HOSTNAME_ERROR) + ->assertRaised(); + } + /** * @dataProvider getTopLevelDomains */ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IbanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IbanValidatorTest.php index ba426799ca..566a692129 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IbanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IbanValidatorTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use Symfony\Component\Validator\Constraints\Iban; use Symfony\Component\Validator\Constraints\IbanValidator; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; class IbanValidatorTest extends ConstraintValidatorTestCase @@ -423,6 +425,24 @@ class IbanValidatorTest extends ConstraintValidatorTestCase $this->assertViolationRaised($iban, Iban::INVALID_COUNTRY_CODE_ERROR); } + /** + * @requires PHP 8 + */ + public function testLoadFromAttribute() + { + $classMetadata = new ClassMetadata(IbanDummy::class); + (new AnnotationLoader())->loadClassMetadata($classMetadata); + + [$constraint] = $classMetadata->properties['iban']->constraints; + + $this->validator->validate('DE89 3704 0044 0532 0130 01', $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"DE89 3704 0044 0532 0130 01"') + ->setCode(Iban::CHECKSUM_FAILED_ERROR) + ->assertRaised(); + } + public function getIbansWithInvalidCountryCode() { return [ @@ -446,3 +466,9 @@ class IbanValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } } + +class IbanDummy +{ + #[Iban(message: 'myMessage')] + private $iban; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IpTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IpTest.php index f8147ac27f..65d7573a33 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IpTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IpTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Ip; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Renan Taranto @@ -39,4 +41,39 @@ class IpTest extends TestCase $this->expectExceptionMessage('The "normalizer" option must be a valid callable ("stdClass" given).'); new Ip(['normalizer' => new \stdClass()]); } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(IpDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(Ip::V4, $aConstraint->version); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(Ip::V6, $bConstraint->version); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame('trim', $bConstraint->normalizer); + self::assertSame(['Default', 'IpDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class IpDummy +{ + #[Ip] + private $a; + + #[Ip(version: Ip::V6, message: "myMessage", normalizer: 'trim')] + private $b; + + #[Ip(groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php index 554f80d1fb..29ae46dacc 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IpValidatorTest.php @@ -89,6 +89,19 @@ class IpValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + /** + * @requires PHP 8 + */ + public function testValidIpV6WithWhitespacesNamed() + { + $this->validator->validate( + "\n\t2001:0db8:85a3:0000:0000:8a2e:0370:7334\r\n", + eval('return new \Symfony\Component\Validator\Constraints\Ip(version: \Symfony\Component\Validator\Constraints\Ip::V6, normalizer: "trim");') + ); + + $this->assertNoViolation(); + } + public function getValidIpsV4WithWhitespaces() { return [ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IsbnTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IsbnTest.php new file mode 100644 index 0000000000..e9dc7c38b0 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/IsbnTest.php @@ -0,0 +1,54 @@ + + * + * 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\Isbn; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class IsbnTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(IsbnDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertNull($aConstraint->type); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(Isbn::ISBN_13, $bConstraint->type); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'IsbnDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class IsbnDummy +{ + #[Isbn] + private $a; + + #[Isbn(message: "myMessage", type: Isbn::ISBN_13)] + private $b; + + #[Isbn(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IsbnValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IsbnValidatorTest.php index 895f9fbf18..57d087937d 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IsbnValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IsbnValidatorTest.php @@ -176,6 +176,22 @@ class IsbnValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + */ + public function testInvalidIsbn10Named() + { + $this->validator->validate( + '978-2723442282', + eval('return new \Symfony\Component\Validator\Constraints\Isbn(type: \Symfony\Component\Validator\Constraints\Isbn::ISBN_10, isbn10Message: "myMessage");') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"978-2723442282"') + ->setCode(Isbn::TOO_LONG_ERROR) + ->assertRaised(); + } + /** * @dataProvider getValidIsbn13 */ @@ -206,6 +222,22 @@ class IsbnValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + */ + public function testInvalidIsbn13Named() + { + $this->validator->validate( + '2723442284', + eval('return new \Symfony\Component\Validator\Constraints\Isbn(type: \Symfony\Component\Validator\Constraints\Isbn::ISBN_13, isbn13Message: "myMessage");') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"2723442284"') + ->setCode(Isbn::TOO_SHORT_ERROR) + ->assertRaised(); + } + /** * @dataProvider getValidIsbn */ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IsinTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IsinTest.php new file mode 100644 index 0000000000..4979a1ae74 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/IsinTest.php @@ -0,0 +1,50 @@ + + * + * 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\Isin; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class IsinTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(IsinDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'IsinDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class IsinDummy +{ + #[Isin] + private $a; + + #[Isin(message: 'myMessage')] + private $b; + + #[Isin(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IssnTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IssnTest.php new file mode 100644 index 0000000000..1ee6ec9ee7 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/IssnTest.php @@ -0,0 +1,56 @@ + + * + * 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\Issn; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class IssnTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(IssnDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertFalse($aConstraint->caseSensitive); + self::assertFalse($aConstraint->requireHyphen); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertTrue($bConstraint->caseSensitive); + self::assertTrue($bConstraint->requireHyphen); + self::assertSame(['Default', 'IssnDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class IssnDummy +{ + #[Issn] + private $a; + + #[Issn(message: 'myMessage', caseSensitive: true, requireHyphen: true)] + private $b; + + #[Issn(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IssnValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IssnValidatorTest.php index 9099dae62f..83b112dfcc 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IssnValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IssnValidatorTest.php @@ -177,4 +177,20 @@ class IssnValidatorTest extends ConstraintValidatorTestCase ->setCode($code) ->assertRaised(); } + + /** + * @requires PHP 8 + */ + public function testNamedArguments() + { + $this->validator->validate( + '2162321x', + eval('return new \Symfony\Component\Validator\Constraints\Issn(message: "myMessage", caseSensitive: true, requireHyphen: true);') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"2162321x"') + ->setCode(Issn::MISSING_HYPHEN_ERROR) + ->assertRaised(); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/JsonTest.php b/src/Symfony/Component/Validator/Tests/Constraints/JsonTest.php new file mode 100644 index 0000000000..9a23170312 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/JsonTest.php @@ -0,0 +1,50 @@ + + * + * 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\Json; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class JsonTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(JsonDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'JsonDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class JsonDummy +{ + #[Json] + private $a; + + #[Json(message: 'myMessage')] + private $b; + + #[Json(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LanguageTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LanguageTest.php new file mode 100644 index 0000000000..3fec1f952b --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/LanguageTest.php @@ -0,0 +1,54 @@ + + * + * 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\Language; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class LanguageTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(LanguageDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertFalse($aConstraint->alpha3); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertTrue($bConstraint->alpha3); + self::assertSame(['Default', 'LanguageDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class LanguageDummy +{ + #[Language] + private $a; + + #[Language(message: 'myMessage', alpha3: true)] + private $b; + + #[Language(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php index 73584a7e9c..c0e62f7b23 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php @@ -151,6 +151,23 @@ class LanguageValidatorTest extends ConstraintValidatorTestCase ]; } + /** + * @requires PHP 8 + */ + public function testInvalidAlpha3LanguageNamed() + { + $this->validator->validate( + 'DE', + eval('return new \Symfony\Component\Validator\Constraints\Language(alpha3: true, message: "myMessage");') + ); + + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"DE"') + ->setCode(Language::NO_SUCH_LANGUAGE_ERROR) + ->assertRaised(); + } + public function testValidateUsingCountrySpecificLocale() { IntlTestHelper::requireFullIntl($this, false); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php index c1c9d60d8b..a8c9922662 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Renan Taranto @@ -61,4 +63,60 @@ class LengthTest extends TestCase [false], ]; } + + public function testConstraintDefaultOption() + { + $constraint = new Length(5); + + self::assertEquals(5, $constraint->min); + self::assertEquals(5, $constraint->max); + } + + public function testConstraintAnnotationDefaultOption() + { + $constraint = new Length(['value' => 5, 'exactMessage' => 'message']); + + self::assertEquals(5, $constraint->min); + self::assertEquals(5, $constraint->max); + self::assertEquals('message', $constraint->exactMessage); + } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(LengthDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(42, $aConstraint->min); + self::assertSame(42, $aConstraint->max); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(1, $bConstraint->min); + self::assertSame(4711, $bConstraint->max); + self::assertSame('myMinMessage', $bConstraint->minMessage); + self::assertSame('myMaxMessage', $bConstraint->maxMessage); + self::assertSame('trim', $bConstraint->normalizer); + self::assertSame('ISO-8859-15', $bConstraint->charset); + self::assertSame(['Default', 'LengthDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class LengthDummy +{ + #[Length(exactly: 42)] + private $a; + + #[Length(min: 1, max: 4711, minMessage: 'myMinMessage', maxMessage: 'myMaxMessage', normalizer: 'trim', charset: 'ISO-8859-15')] + private $b; + + #[Length(exactly: 10, groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php index 584f1e4ae3..603f9562ae 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php @@ -186,6 +186,25 @@ class LengthValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getThreeOrLessCharacters + */ + public function testInvalidValuesMinNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Length(min: 4, minMessage: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$value.'"') + ->setParameter('{{ limit }}', 4) + ->setInvalidValue($value) + ->setPlural(4) + ->setCode(Length::TOO_SHORT_ERROR) + ->assertRaised(); + } + /** * @dataProvider getFiveOrMoreCharacters */ @@ -207,6 +226,25 @@ class LengthValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getFiveOrMoreCharacters + */ + public function testInvalidValuesMaxNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Length(max: 4, maxMessage: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$value.'"') + ->setParameter('{{ limit }}', 4) + ->setInvalidValue($value) + ->setPlural(4) + ->setCode(Length::TOO_LONG_ERROR) + ->assertRaised(); + } + /** * @dataProvider getThreeOrLessCharacters */ @@ -229,6 +267,25 @@ class LengthValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getThreeOrLessCharacters + */ + public function testInvalidValuesExactLessThanFourNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Length(exactly: 4, exactMessage: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$value.'"') + ->setParameter('{{ limit }}', 4) + ->setInvalidValue($value) + ->setPlural(4) + ->setCode(Length::TOO_SHORT_ERROR) + ->assertRaised(); + } + /** * @dataProvider getFiveOrMoreCharacters */ @@ -276,21 +333,4 @@ class LengthValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } } - - public function testConstraintDefaultOption() - { - $constraint = new Length(5); - - $this->assertEquals(5, $constraint->min); - $this->assertEquals(5, $constraint->max); - } - - public function testConstraintAnnotationDefaultOption() - { - $constraint = new Length(['value' => 5, 'exactMessage' => 'message']); - - $this->assertEquals(5, $constraint->min); - $this->assertEquals(5, $constraint->max); - $this->assertEquals('message', $constraint->exactMessage); - } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LocaleTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LocaleTest.php new file mode 100644 index 0000000000..edd56f5cca --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/LocaleTest.php @@ -0,0 +1,54 @@ + + * + * 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\Locale; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class LocaleTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(LocaleDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertTrue($aConstraint->canonicalize); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertFalse($bConstraint->canonicalize); + self::assertSame(['Default', 'LocaleDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class LocaleDummy +{ + #[Locale] + private $a; + + #[Locale(message: 'myMessage', canonicalize: false)] + private $b; + + #[Locale(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LocaleValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LocaleValidatorTest.php index 1ae86299b7..71ac2e0eea 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LocaleValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LocaleValidatorTest.php @@ -104,6 +104,55 @@ class LocaleValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + /** + * @dataProvider getValidLocales + */ + public function testValidLocalesWithoutCanonicalization(string $locale) + { + $constraint = new Locale([ + 'message' => 'myMessage', + 'canonicalize' => false, + ]); + + $this->validator->validate($locale, $constraint); + + $this->assertNoViolation(); + } + + /** + * @dataProvider getUncanonicalizedLocales + */ + public function testInvalidLocalesWithoutCanonicalization(string $locale) + { + $constraint = new Locale([ + 'message' => 'myMessage', + 'canonicalize' => false, + ]); + + $this->validator->validate($locale, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$locale.'"') + ->setCode(Locale::NO_SUCH_LOCALE_ERROR) + ->assertRaised(); + } + + /** + * @requires PHP 8 + */ + public function testInvalidLocaleWithoutCanonicalizationNamed() + { + $this->validator->validate( + 'en-US', + eval('return new \Symfony\Component\Validator\Constraints\Locale(message: "myMessage", canonicalize: false);') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"en-US"') + ->setCode(Locale::NO_SUCH_LOCALE_ERROR) + ->assertRaised(); + } + public function getUncanonicalizedLocales(): iterable { return [ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LuhnTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LuhnTest.php new file mode 100644 index 0000000000..2aea6e98ec --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/LuhnTest.php @@ -0,0 +1,50 @@ + + * + * 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\Luhn; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class LuhnTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(LuhnDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'LuhnDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class LuhnDummy +{ + #[Luhn] + private $a; + + #[Luhn(message: 'myMessage')] + private $b; + + #[Luhn(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordTest.php index 4ae9b8e852..84c9ef6b4b 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\NotCompromisedPassword; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Kévin Dunglas @@ -25,4 +27,40 @@ class NotCompromisedPasswordTest extends TestCase $this->assertSame(1, $constraint->threshold); $this->assertFalse($constraint->skipOnError); } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(NotCompromisedPasswordDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(1, $aConstraint->threshold); + self::assertFalse($aConstraint->skipOnError); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(42, $bConstraint->threshold); + self::assertTrue($bConstraint->skipOnError); + self::assertSame(['Default', 'NotCompromisedPasswordDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class NotCompromisedPasswordDummy +{ + #[NotCompromisedPassword] + private $a; + + #[NotCompromisedPassword(message: 'myMessage', threshold: 42, skipOnError: true)] + private $b; + + #[NotCompromisedPassword(groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php index a0277d45a6..868040fafb 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php @@ -91,13 +91,25 @@ class NotCompromisedPasswordValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } - public function testThresholdNotReached() + /** + * @dataProvider provideConstraintsWithThreshold + */ + public function testThresholdNotReached(NotCompromisedPassword $constraint) { - $this->validator->validate(self::PASSWORD_LEAKED, new NotCompromisedPassword(['threshold' => 10])); + $this->validator->validate(self::PASSWORD_LEAKED, $constraint); $this->assertNoViolation(); } + public function provideConstraintsWithThreshold(): iterable + { + yield 'Doctrine style' => [new NotCompromisedPassword(['threshold' => 10])]; + + if (\PHP_VERSION_ID >= 80000) { + yield 'named arguments' => [eval('return new \Symfony\Component\Validator\Constraints\NotCompromisedPassword(threshold: 10);')]; + } + } + public function testValidPassword() { $this->validator->validate(self::PASSWORD_NOT_LEAKED, new NotCompromisedPassword()); @@ -170,12 +182,24 @@ class NotCompromisedPasswordValidatorTest extends ConstraintValidatorTestCase $this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, new NotCompromisedPassword()); } - public function testApiErrorSkipped() + /** + * @dataProvider provideErrorSkippingConstraints + */ + public function testApiErrorSkipped(NotCompromisedPassword $constraint) { - $this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, new NotCompromisedPassword(['skipOnError' => true])); + $this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, $constraint); $this->assertTrue(true); // No exception have been thrown } + public function provideErrorSkippingConstraints(): iterable + { + yield 'Doctrine style' => [new NotCompromisedPassword(['skipOnError' => true])]; + + if (\PHP_VERSION_ID >= 80000) { + yield 'named arguments' => [eval('return new \Symfony\Component\Validator\Constraints\NotCompromisedPassword(skipOnError: true);')]; + } + } + private function createHttpClientStub(): HttpClientInterface { $httpClientStub = $this->createMock(HttpClientInterface::class); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/RegexTest.php b/src/Symfony/Component/Validator/Tests/Constraints/RegexTest.php index f49f2c0bb4..f71ae135fc 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/RegexTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/RegexTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Regex; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Bernhard Schussek @@ -106,4 +108,42 @@ class RegexTest extends TestCase $this->expectExceptionMessage('The "normalizer" option must be a valid callable ("stdClass" given).'); new Regex(['pattern' => '/^[0-9]+$/', 'normalizer' => new \stdClass()]); } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(RegexDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame('/^[0-9]+$/', $aConstraint->pattern); + self::assertTrue($aConstraint->match); + self::assertNull($aConstraint->normalizer); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame('/^[0-9]+$/', $bConstraint->pattern); + self::assertSame('[0-9]+', $bConstraint->htmlPattern); + self::assertFalse($bConstraint->match); + self::assertSame(['Default', 'RegexDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class RegexDummy +{ + #[Regex('/^[0-9]+$/')] + private $a; + + #[Regex(message: 'myMessage', pattern: '/^[0-9]+$/', htmlPattern: '[0-9]+', match: false, normalizer: 'trim')] + private $b; + + #[Regex('/^[0-9]+$/', groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php index 8ad07d940c..384c0ec36c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php @@ -64,6 +64,18 @@ class RegexValidatorTest extends ConstraintValidatorTestCase $this->assertNoViolation(); } + /** + * @requires PHP 8 + * @dataProvider getValidValuesWithWhitespaces + */ + public function testValidValuesWithWhitespacesNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Regex(pattern: "/^[0-9]+\$/", normalizer: "trim");'); + $this->validator->validate($value, $constraint); + + $this->assertNoViolation(); + } + public function getValidValues() { return [ @@ -110,6 +122,22 @@ class RegexValidatorTest extends ConstraintValidatorTestCase ->assertRaised(); } + /** + * @requires PHP 8 + * @dataProvider getInvalidValues + */ + public function testInvalidValuesNamed($value) + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Regex(pattern: "/^[0-9]+\$/", message: "myMessage");'); + + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$value.'"') + ->setCode(Regex::REGEX_FAILED_ERROR) + ->assertRaised(); + } + public function getInvalidValues() { return [ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimeTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimeTest.php new file mode 100644 index 0000000000..30cf3a13d7 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimeTest.php @@ -0,0 +1,50 @@ + + * + * 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\Time; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class TimeTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(TimeDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'TimeDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class TimeDummy +{ + #[Time] + private $a; + + #[Time(message: 'myMessage')] + private $b; + + #[Time(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php index 47566ea6de..b6c98c6970 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Timezone; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Javier Spagnoletti @@ -62,4 +64,38 @@ class TimezoneTest extends TestCase yield [0]; yield [\DateTimeZone::ALL_WITH_BC + 1]; } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(TimezoneDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(\DateTimeZone::ALL, $aConstraint->zone); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(\DateTimeZone::PER_COUNTRY, $bConstraint->zone); + self::assertSame('DE', $bConstraint->countryCode); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'TimezoneDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class TimezoneDummy +{ + #[Timezone] + private $a; + + #[Timezone(zone: \DateTimeZone::PER_COUNTRY, countryCode: 'DE', message: 'myMessage')] + private $b; + + #[Timezone(groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php index 1f43224641..9e3e4e2f2f 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php @@ -179,6 +179,21 @@ class TimezoneValidatorTest extends ConstraintValidatorTestCase yield ['Etc/UTC', \DateTimeZone::EUROPE]; } + /** + * @requires PHP 8 + */ + public function testInvalidGroupedTimezoneNamed() + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Timezone(zone: \DateTimeZone::AMERICA, message: "myMessage");'); + + $this->validator->validate('Europe/Berlin', $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"Europe/Berlin"') + ->setCode(Timezone::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR) + ->assertRaised(); + } + /** * @dataProvider getValidGroupedTimezonesByCountry */ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TraverseTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TraverseTest.php new file mode 100644 index 0000000000..95ce221f9f --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TraverseTest.php @@ -0,0 +1,50 @@ + + * + * 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\Traverse; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Validator\Mapping\TraversalStrategy; + +/** + * @requires PHP 8 + */ +class TraverseTest extends TestCase +{ + public function testPositiveAttributes() + { + $metadata = new ClassMetadata(TraverseDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + self::assertSame(TraversalStrategy::TRAVERSE, $metadata->getTraversalStrategy()); + } + + public function testNegativeAttribute() + { + $metadata = new ClassMetadata(DoNotTraverseMe::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + self::assertSame(TraversalStrategy::NONE, $metadata->getTraversalStrategy()); + } +} + +#[Traverse] +class TraverseDummy +{ +} + +#[Traverse(false)] +class DoNotTraverseMe +{ +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TypeTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TypeTest.php new file mode 100644 index 0000000000..0e5163705d --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TypeTest.php @@ -0,0 +1,54 @@ + + * + * 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\Type; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class TypeTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(TypeDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame('integer', $aConstraint->type); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(\DateTime::class, $bConstraint->type); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'TypeDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['string', 'array'], $cConstraint->type); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class TypeDummy +{ + #[Type('integer')] + private $a; + + #[Type(type: \DateTime::class, message: 'myMessage')] + private $b; + + #[Type(type: ['string', 'array'], groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TypeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TypeValidatorTest.php index 40c24029a9..024de45536 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/TypeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/TypeValidatorTest.php @@ -184,29 +184,29 @@ class TypeValidatorTest extends ConstraintValidatorTestCase } /** - * @dataProvider getInvalidValuesMultipleTypes + * @dataProvider provideConstraintsWithMultipleTypes */ - public function testInvalidValuesMultipleTypes($value, $types, $valueAsString) + public function testInvalidValuesMultipleTypes(Type $constraint) { - $constraint = new Type([ - 'type' => $types, - 'message' => 'myMessage', - ]); - - $this->validator->validate($value, $constraint); + $this->validator->validate('12345', $constraint); $this->buildViolation('myMessage') - ->setParameter('{{ value }}', $valueAsString) - ->setParameter('{{ type }}', implode('|', $types)) + ->setParameter('{{ value }}', '"12345"') + ->setParameter('{{ type }}', implode('|', ['boolean', 'array'])) ->setCode(Type::INVALID_TYPE_ERROR) ->assertRaised(); } - public function getInvalidValuesMultipleTypes() + public function provideConstraintsWithMultipleTypes() { - return [ - ['12345', ['boolean', 'array'], '"12345"'], - ]; + yield 'Doctrine style' => [new Type([ + 'type' => ['boolean', 'array'], + 'message' => 'myMessage', + ])]; + + if (\PHP_VERSION_ID >= 80000) { + yield 'named arguments' => [eval('return new \Symfony\Component\Validator\Constraints\Type(type: ["boolean", "array"], message: "myMessage");')]; + } } protected function createFile() diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UlidTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UlidTest.php new file mode 100644 index 0000000000..b6cc9c807a --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/UlidTest.php @@ -0,0 +1,50 @@ + + * + * 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\Ulid; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class UlidTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(UlidDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'UlidDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class UlidDummy +{ + #[Ulid] + private $a; + + #[Ulid(message: 'myMessage')] + private $b; + + #[Ulid(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php index 0f184c85c8..2c97c97604 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php @@ -80,4 +80,19 @@ class UlidValidatorTest extends ConstraintValidatorTestCase ['Z1ARZ3NDEKTSV4RRFFQ69G5FAV', Ulid::TOO_LARGE_ERROR], ]; } + + /** + * @requires PHP 8 + */ + public function testInvalidUlidNamed() + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Ulid(message: "testMessage");'); + + $this->validator->validate('01ARZ3NDEKTSV4RRFFQ69G5FA', $constraint); + + $this->buildViolation('testMessage') + ->setParameter('{{ value }}', '"01ARZ3NDEKTSV4RRFFQ69G5FA"') + ->setCode(Ulid::TOO_SHORT_ERROR) + ->assertRaised(); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UniqueTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UniqueTest.php new file mode 100644 index 0000000000..f34721f64e --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/UniqueTest.php @@ -0,0 +1,50 @@ + + * + * 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\Unique; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; + +/** + * @requires PHP 8 + */ +class UniqueTest extends TestCase +{ + public function testAttributes() + { + $metadata = new ClassMetadata(UniqueDummy::class); + $loader = new AnnotationLoader(); + self::assertTrue($loader->loadClassMetadata($metadata)); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'UniqueDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class UniqueDummy +{ + #[Unique] + private $a; + + #[Unique(message: 'myMessage')] + private $b; + + #[Unique(groups: ['my_group'], payload: 'some attached data')] + private $c; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php index da46323db3..ee05aa0b88 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php @@ -84,4 +84,18 @@ class UniqueValidatorTest extends ConstraintValidatorTestCase yield 'not unique objects' => [[$object, $object]], ]; } + + /** + * @requires PHP 8 + */ + public function testInvalidValueNamed() + { + $constraint = eval('return new \Symfony\Component\Validator\Constraints\Unique(message: "myMessage");'); + $this->validator->validate([1, 2, 3, 3], $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', 'array') + ->setCode(Unique::IS_NOT_UNIQUE) + ->assertRaised(); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UrlTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UrlTest.php index c1799ed551..2fce08bd53 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Url; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Renan Taranto @@ -39,4 +41,41 @@ class UrlTest extends TestCase $this->expectExceptionMessage('The "normalizer" option must be a valid callable ("stdClass" given).'); new Url(['normalizer' => new \stdClass()]); } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(UrlDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(['http', 'https'], $aConstraint->protocols); + self::assertFalse($aConstraint->relativeProtocol); + self::assertNull($aConstraint->normalizer); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame(['ftp', 'gopher'], $bConstraint->protocols); + self::assertSame('trim', $bConstraint->normalizer); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'UrlDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertTrue($cConstraint->relativeProtocol); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class UrlDummy +{ + #[Url] + private $a; + + #[Url(message: 'myMessage', protocols: ['ftp', 'gopher'], normalizer: 'trim')] + private $b; + + #[Url(relativeProtocol: true, groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UuidTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UuidTest.php index e048ecb211..4c172ec617 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UuidTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UuidTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Uuid; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; /** * @author Renan Taranto @@ -39,4 +41,41 @@ class UuidTest extends TestCase $this->expectExceptionMessage('The "normalizer" option must be a valid callable ("stdClass" given).'); new Uuid(['normalizer' => new \stdClass()]); } + + /** + * @requires PHP 8 + */ + public function testAttributes() + { + $metadata = new ClassMetadata(UuidDummy::class); + self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata)); + + list($aConstraint) = $metadata->properties['a']->getConstraints(); + self::assertSame(Uuid::ALL_VERSIONS, $aConstraint->versions); + self::assertTrue($aConstraint->strict); + self::assertNull($aConstraint->normalizer); + + list($bConstraint) = $metadata->properties['b']->getConstraints(); + self::assertSame([Uuid::V4_RANDOM, Uuid::V6_SORTABLE], $bConstraint->versions); + self::assertFalse($bConstraint->strict); + self::assertSame('trim', $bConstraint->normalizer); + self::assertSame('myMessage', $bConstraint->message); + self::assertSame(['Default', 'UuidDummy'], $bConstraint->groups); + + list($cConstraint) = $metadata->properties['c']->getConstraints(); + self::assertSame(['my_group'], $cConstraint->groups); + self::assertSame('some attached data', $cConstraint->payload); + } +} + +class UuidDummy +{ + #[Uuid] + private $a; + + #[Uuid(message: 'myMessage', versions: [Uuid::V4_RANDOM, Uuid::V6_SORTABLE], normalizer: 'trim', strict: false)] + private $b; + + #[Uuid(groups: ['my_group'], payload: 'some attached data')] + private $c; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php index e9d1575b8b..7bb500dcaa 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php @@ -110,6 +110,19 @@ class UuidValidatorTest extends ConstraintValidatorTestCase ]; } + /** + * @requires PHP 8 + */ + public function testValidStrictUuidWithWhitespacesNamed() + { + $this->validator->validate( + "\x09\x09216fff40-98d9-11e3-a5e2-0800200c9a66", + eval('return new \Symfony\Component\Validator\Constraints\Uuid(normalizer: "trim", versions: [\Symfony\Component\Validator\Constraints\Uuid::V1_MAC]);') + ); + + $this->assertNoViolation(); + } + /** * @dataProvider getInvalidStrictUuids */ @@ -237,4 +250,20 @@ class UuidValidatorTest extends ConstraintValidatorTestCase ['216fff40-98d9-11e3-a5e2-0800200c9a666', Uuid::TOO_LONG_ERROR], ]; } + + /** + * @requires PHP 8 + */ + public function testInvalidNonStrictUuidNamed() + { + $this->validator->validate( + '216fff40-98d9-11e3-a5e2_0800200c9a66', + eval('return new \Symfony\Component\Validator\Constraints\Uuid(strict: false, message: "myMessage");') + ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"216fff40-98d9-11e3-a5e2_0800200c9a66"') + ->setCode(Uuid::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } }