feature #38499 [Validator] Upgraded constraints to enable named arguments and attributes (derrabus)

This PR was squashed before being merged into the 5.x branch.

Discussion
----------

[Validator] Upgraded constraints to enable named arguments and attributes

| Q             | A
| ------------- | ---
| Branch?       | 5.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | #38096
| License       | MIT
| Doc PR        | TODO with symfony/symfony-docs#14305

This PR enables all remaining atomic (!= composite) constraints to be used as attributes.

The only exception is `UniqueEntity` from Doctrine bridge because we don't have a Doctrine ORM release yet that supports PHP 8. So I could migrate that one as well, but I cannot really test it.

Commits
-------

fb99eb2052 [Validator] Upgraded constraints to enable named arguments and attributes
This commit is contained in:
Fabien Potencier 2020-10-11 08:04:09 +02:00
commit 87920d266e
94 changed files with 2849 additions and 137 deletions

View File

@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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
*/

View File

@ -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}
*/

View File

@ -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",

View File

@ -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 <michael.vhirsch@gmail.com>
*/
#[\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);
}
}

View File

@ -22,8 +22,22 @@ use Symfony\Component\Validator\Constraint;
* @author Tim Nagel <t.nagel@infinite.net.au>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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';

View File

@ -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})$/',
],
];

View File

@ -20,9 +20,10 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
*
* @author Jules Pietri <jules@heahprod.com>
*/
#[\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__));

View File

@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\MissingOptionsException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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']);

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\LogicException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -22,6 +22,7 @@ use Symfony\Component\Validator\Exception\LogicException;
* @author Miha Vrhovnik <miha.vrhovnik@pagein.si>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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';

View File

@ -24,9 +24,10 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\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__));

View File

@ -22,6 +22,7 @@ use Symfony\Component\Validator\Exception\LogicException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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__));

View File

@ -24,9 +24,10 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\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__));

View File

@ -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 <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
/**

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Andrey Sevastianov <mrpkmail@gmail.com>
*/
#[\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}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
#[\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;
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Constraint;
* @author Michael Schummel
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -24,6 +24,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException;
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Joseph Bielawski <stloyd@gmail.com>
*/
#[\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)));

View File

@ -21,8 +21,12 @@ use Symfony\Component\Validator\Constraint;
* @author Manuel Reinhard <manu@sprain.ch>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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}
*/

View File

@ -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;
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Laurent Masforné <l.masforne@gmail.com>
*/
#[\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;
}
}

View File

@ -20,6 +20,7 @@ use Symfony\Component\Validator\Constraint;
* @author Antonio J. García Lagar <aj@garcialagar.es>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Imad ZAIRIG <imadzairig@gmail.com>
*/
#[\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;
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\LogicException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\MissingOptionsException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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']);

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\LogicException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -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 <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\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;
}
}

View File

@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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)));

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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;
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
* @author Javier Spagnoletti <phansys@gmail.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*/
#[\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) {

View File

@ -19,17 +19,21 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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);
}
/**

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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}
*/

View File

@ -18,6 +18,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Laurent Clouet <laurent35240@gmail.com>
*/
#[\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;
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraint;
*
* @author Yevgeniy Zholkevskiy <zhenya.zholkevskiy@gmail.com>
*/
#[\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;
}
}

View File

@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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)));

View File

@ -20,6 +20,7 @@ use Symfony\Component\Validator\Exception\InvalidArgumentException;
* @author Colin O'Dell <colinodell@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\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)));

View File

@ -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';
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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 [

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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
{
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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
*/

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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"

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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 [

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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([

View File

@ -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 [

View File

@ -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 <dunglas@gmail.com>
@ -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
{
}

View File

@ -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;
}

View File

@ -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 <dunglas@gmail.com>
@ -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
{
}

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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 <renantaranto@gmail.com>
@ -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;
}

View File

@ -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 [

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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
*/

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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();
}
}

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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);

View File

@ -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 <renantaranto@gmail.com>
@ -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;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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 [

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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 <dunglas@gmail.com>
@ -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;
}

View File

@ -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);

View File

@ -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 <bschussek@gmail.com>
@ -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;
}

View File

@ -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 [

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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 <phansys@gmail.com>
@ -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;
}

View File

@ -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
*/

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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
{
}

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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()

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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();
}
}

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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();
}
}

View File

@ -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 <renantaranto@gmail.com>
@ -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;
}

View File

@ -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 <renantaranto@gmail.com>
@ -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;
}

View File

@ -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();
}
}