feature #38309 [Validator] Constraints as php 8 Attributes (derrabus)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Validator] Constraints as php 8 Attributes

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | #38096
| License       | MIT
| Doc PR        | TODO

This is my attempt to teach the validator to load constraints from PHP attributes. Like we've done it for the `Route` attribute, I've hooked into the existing `AnnotationLoader`, so we can again mix and match annotations and attributes.

### Named Arguments

An attribute declaration is basically a constructor call. This is why, in order to effectively use a constraint as attribute, we need to equip it with a constructor that works nicely with named arguments. This way, IDEs like PhpStorm can provide auto-completion and guide a developer when declaring a constraint without the need for additional plugins. Right now, PhpStorm supports neither attributes nor named arguments, but I expect those features to be implemented relatively soon.

To showcase this, I have migrated the `Range` and `Choice` constraints. The example presented in #38096 works with this PR.

```php
#[Assert\Choice(
    choices: ['fiction', 'non-fiction'],
    message: 'Choose a valid genre.',
)]
private $genre;
```

A nice side effect is that we get a more decent constructor call in php 8 in situations where we directly instantiate constraints, for instance when attaching constraints to a form field via the form builder.

```php
$builder->add('genre, TextType::class, [
    'constraints' => [new Assert\Choice(
        choices: ['fiction', 'non-fiction'],
        message: 'Choose a valid genre.',
    )],
]);
```

The downside is that all those constructors generate the need for some boilerplate code that was previously abstracted away by the `Constraint` class.

The alternative to named arguments would be leaving the constructors as they are. That would basically mean that when declaring a constraint we would have to emulate the array that Doctrine annotations would crate. We would lose IDE support and the declarations would be uglier, but it would work.

```php
#[Assert\Choice([
    'choices' => ['fiction', 'non-fiction'],
    'message' => 'Choose a valid genre.',
])]
private $genre;
```

### Nested Attributes

PHP does not support nesting attributes (yet?). This is why I could not recreate composite annotations like `All` and `Collection`. I think it's okay if they're not included in the first iteration of attribute constraints and we can work on a solution in a later PR. Possible options:
* A later PHP 8.x release might give us nested attributes.
* We could find a way to flatten those constraints so we can recreate them without nesting.
* We could come up with a convention for a structure that lets us emulate nested attributes in userland.

### Repeatable attributes

In order to attach two instances of the same attribute class to an element, we explicitly have to allow repetition via the flag `Attribute::IS_REPEATABLE`. While we could argue if it really makes sense to do this for certain constraints (like `NotNull` for instance), there are others (like `Callback`) where having two instances with different configurations could make sense. On the other hand, the validator certainly won't break if we repeat any of the constraints and all other ways to configure constraints allow repetition. This is why I decided to allow repetition for all constraints I've marked as attributes in this PR and propose to continue with that practice for all other constraints.

### Migration Path

This PR only migrates a handful of constraints. My plan is to discuss the general idea with this PR first and use it as a blueprint to migrate the individual constraints afterwards. Right now, the migration path would look like this:

* Attach the `#[Attribute]` attribute.
* Recreate all options of the constraint as constructor arguments.
* Add test cases for constructor calls with named arguments to the test class of the constraint's validator.

Commits
-------

d1cb2d6354 [Validator] Constraints as php 8 Attributes.
This commit is contained in:
Fabien Potencier 2020-09-30 07:35:13 +02:00
commit 5eb442ec18
59 changed files with 957 additions and 199 deletions

View File

@ -36,6 +36,7 @@ foreach ($loader->getClassMap() as $class => $file) {
case false !== strpos($file, '/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures'):
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php'):
case false !== strpos($file, '/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php') && \PHP_VERSION_ID < 70400:

View File

@ -11,10 +11,9 @@
namespace Symfony\Component\HttpKernel\Tests\Fixtures\Attribute;
use Attribute;
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;
#[Attribute(Attribute::TARGET_PARAMETER)]
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class Foo implements ArgumentInterface
{
private $foo;

View File

@ -11,8 +11,6 @@
namespace Symfony\Component\Routing\Annotation;
use Attribute;
/**
* Annotation class for @Route().
*
@ -22,7 +20,7 @@ use Attribute;
* @author Fabien Potencier <fabien@symfony.com>
* @author Alexander M. Turek <me@derrabus.de>
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Route
{
private $path;

View File

@ -11,13 +11,12 @@
namespace Symfony\Component\Security\Http\Attribute;
use Attribute;
use Symfony\Component\HttpKernel\Attribute\ArgumentInterface;
/**
* Indicates that a controller argument should receive the current logged user.
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class CurrentUser implements ArgumentInterface
{
}

View File

@ -32,6 +32,7 @@ CHANGELOG
* added the `Isin` constraint and validator
* added the `ULID` constraint and validator
* added support for UUIDv6 in `Uuid` constraint
* enabled the validator to load constraints from PHP attributes
5.1.0
-----

View File

@ -91,9 +91,11 @@ abstract class Constraint
* getRequiredOptions() to return the names of these options. If any
* option is not set here, an exception is thrown.
*
* @param mixed $options The options (as associative array)
* or the value for the default
* option (any other type)
* @param mixed $options The options (as associative array)
* or the value for the default
* option (any other type)
* @param string[] $groups An array of validation groups
* @param mixed $payload Domain-specific data attached to a constraint
*
* @throws InvalidOptionsException When you pass the names of non-existing
* options
@ -103,9 +105,15 @@ abstract class Constraint
* array, but getDefaultOption() returns
* null
*/
public function __construct($options = null)
public function __construct($options = null, array $groups = null, $payload = null)
{
foreach ($this->normalizeOptions($options) as $name => $value) {
$options = $this->normalizeOptions($options);
if (null !== $groups) {
$options['groups'] = $groups;
}
$options['payload'] = $payload ?? $options['payload'] ?? null;
foreach ($options as $name => $value) {
$this->$name = $value;
}
}

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 Blank extends Constraint
{
const NOT_BLANK_ERROR = '183ad2de-533d-4796-a439-6d3c3852b549';
@ -28,4 +29,11 @@ class Blank extends Constraint
];
public $message = 'This value should be blank.';
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_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Callback extends Constraint
{
/**
@ -28,19 +29,23 @@ class Callback extends Constraint
/**
* {@inheritdoc}
*
* @param array|string|callable $callback The callback or a set of options
*/
public function __construct($options = null)
public function __construct($callback = null, array $groups = null, $payload = null, array $options = [])
{
// Invocation through annotations with an array parameter only
if (\is_array($options) && 1 === \count($options) && isset($options['value'])) {
$options = $options['value'];
if (\is_array($callback) && 1 === \count($callback) && isset($callback['value'])) {
$callback = $callback['value'];
}
if (\is_array($options) && !isset($options['callback']) && !isset($options['groups']) && !isset($options['payload'])) {
$options = ['callback' => $options];
if (!\is_array($callback) || (!isset($callback['callback']) && !isset($callback['groups']) && !isset($callback['payload']))) {
$options['callback'] = $callback;
} else {
$options = array_merge($callback, $options);
}
parent::__construct($options);
parent::__construct($options, $groups, $payload);
}
/**

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 Choice extends Constraint
{
const NO_SUCH_CHOICE_ERROR = '8e179f1b-97aa-4560-a02f-2a8b42e49df7';
@ -49,4 +50,38 @@ class Choice extends Constraint
{
return 'choices';
}
public function __construct(
$choices = null,
$callback = null,
bool $multiple = null,
bool $strict = null,
int $min = null,
int $max = null,
string $message = null,
string $multipleMessage = null,
string $minMessage = null,
string $maxMessage = null,
$groups = null,
$payload = null,
array $options = []
) {
if (\is_array($choices) && \is_string(key($choices))) {
$options = array_merge($choices, $options);
} elseif (null !== $choices) {
$options['choices'] = $choices;
}
parent::__construct($options, $groups, $payload);
$this->callback = $callback ?? $this->callback;
$this->multiple = $multiple ?? $this->multiple;
$this->strict = $strict ?? $this->strict;
$this->min = $min ?? $this->min;
$this->max = $max ?? $this->max;
$this->message = $message ?? $this->message;
$this->multipleMessage = $multipleMessage ?? $this->multipleMessage;
$this->minMessage = $minMessage ?? $this->minMessage;
$this->maxMessage = $maxMessage ?? $this->maxMessage;
}
}

View File

@ -51,6 +51,7 @@ namespace Symfony\Component\Validator\Constraints;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class GroupSequence
{
/**

View File

@ -19,6 +19,7 @@ namespace Symfony\Component\Validator\Constraints;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class GroupSequenceProvider
{
}

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 IsFalse extends Constraint
{
const NOT_FALSE_ERROR = 'd53a91b0-def3-426a-83d7-269da7ab4200';
@ -28,4 +29,11 @@ class IsFalse extends Constraint
];
public $message = 'This value should be false.';
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 IsNull extends Constraint
{
const NOT_NULL_ERROR = '60d2f30b-8cfa-4372-b155-9656634de120';
@ -28,4 +29,11 @@ class IsNull extends Constraint
];
public $message = 'This value should be null.';
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 IsTrue extends Constraint
{
const NOT_TRUE_ERROR = '2beabf1c-54c0-4882-a928-05249b26e23b';
@ -28,4 +29,11 @@ class IsTrue extends Constraint
];
public $message = 'This value should be true.';
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\InvalidArgumentException;
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotBlank extends Constraint
{
const IS_BLANK_ERROR = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
@ -33,9 +34,13 @@ class NotBlank extends Constraint
public $allowNull = false;
public $normalizer;
public function __construct($options = null)
public function __construct(array $options = null, string $message = null, bool $allowNull = null, callable $normalizer = null, array $groups = null, $payload = null)
{
parent::__construct($options);
parent::__construct($options ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
$this->allowNull = $allowNull ?? $this->allowNull;
$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 NotNull extends Constraint
{
const IS_NULL_ERROR = 'ad32d13f-c3d4-423b-909a-857b961eb720';
@ -28,4 +29,11 @@ class NotNull extends Constraint
];
public $message = 'This value should not be null.';
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

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Constraints;
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\MissingOptionsException;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Range extends Constraint
{
const INVALID_CHARACTERS_ERROR = 'ad9a9798-7a99-4df7-8ce9-46e416a1e60b';
@ -57,36 +59,62 @@ class Range extends Constraint
*/
public $deprecatedMaxMessageSet = false;
public function __construct($options = null)
{
if (\is_array($options)) {
if (isset($options['min']) && isset($options['minPropertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', static::class));
}
/**
* {@inheritdoc}
*
* @param string|PropertyPathInterface|null $minPropertyPath
* @param string|PropertyPathInterface|null $maxPropertyPath
*/
public function __construct(
array $options = null,
string $notInRangeMessage = null,
string $minMessage = null,
string $maxMessage = null,
string $invalidMessage = null,
string $invalidDateTimeMessage = null,
$min = null,
$minPropertyPath = null,
$max = null,
$maxPropertyPath = null,
array $groups = null,
$payload = null
) {
parent::__construct($options, $groups, $payload);
if (isset($options['max']) && isset($options['maxPropertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', static::class));
}
if ((isset($options['minPropertyPath']) || isset($options['maxPropertyPath'])) && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', static::class));
}
if (isset($options['min']) && isset($options['max'])) {
$this->deprecatedMinMessageSet = isset($options['minMessage']);
$this->deprecatedMaxMessageSet = isset($options['maxMessage']);
// BC layer, should throw a ConstraintDefinitionException in 6.0
if ($this->deprecatedMinMessageSet || $this->deprecatedMaxMessageSet) {
trigger_deprecation('symfony/validator', '4.4', '"minMessage" and "maxMessage" are deprecated when the "min" and "max" options are both set. Use "notInRangeMessage" instead.');
}
}
}
parent::__construct($options);
$this->notInRangeMessage = $notInRangeMessage ?? $this->notInRangeMessage;
$this->minMessage = $minMessage ?? $this->minMessage;
$this->maxMessage = $maxMessage ?? $this->maxMessage;
$this->invalidMessage = $invalidMessage ?? $this->invalidMessage;
$this->invalidDateTimeMessage = $invalidDateTimeMessage ?? $this->invalidDateTimeMessage;
$this->min = $min ?? $this->min;
$this->minPropertyPath = $minPropertyPath ?? $this->minPropertyPath;
$this->max = $max ?? $this->max;
$this->maxPropertyPath = $maxPropertyPath ?? $this->maxPropertyPath;
if (null === $this->min && null === $this->minPropertyPath && null === $this->max && null === $this->maxPropertyPath) {
throw new MissingOptionsException(sprintf('Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given for constraint "%s".', __CLASS__), ['min', 'minPropertyPath', 'max', 'maxPropertyPath']);
}
if (null !== $this->min && null !== $this->minPropertyPath) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', static::class));
}
if (null !== $this->max && null !== $this->maxPropertyPath) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', static::class));
}
if ((null !== $this->minPropertyPath || null !== $this->maxPropertyPath) && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', static::class));
}
if (null !== $this->min && null !== $this->max) {
$this->deprecatedMinMessageSet = isset($options['minMessage']) || null !== $minMessage;
$this->deprecatedMaxMessageSet = isset($options['maxMessage']) || null !== $maxMessage;
// BC layer, should throw a ConstraintDefinitionException in 6.0
if ($this->deprecatedMinMessageSet || $this->deprecatedMaxMessageSet) {
trigger_deprecation('symfony/validator', '4.4', '"minMessage" and "maxMessage" are deprecated when the "min" and "max" options are both set. Use "notInRangeMessage" instead.');
}
}
}
}

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 Valid extends Constraint
{
public $traverse = true;

View File

@ -23,12 +23,13 @@ use Symfony\Component\Validator\Mapping\ClassMetadata;
* Loads validation metadata using a Doctrine annotation {@link Reader}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Alexander M. Turek <me@derrabus.de>
*/
class AnnotationLoader implements LoaderInterface
{
protected $reader;
public function __construct(Reader $reader)
public function __construct(Reader $reader = null)
{
$this->reader = $reader;
}
@ -42,7 +43,7 @@ class AnnotationLoader implements LoaderInterface
$className = $reflClass->name;
$success = false;
foreach ($this->reader->getClassAnnotations($reflClass) as $constraint) {
foreach ($this->getAnnotations($reflClass) as $constraint) {
if ($constraint instanceof GroupSequence) {
$metadata->setGroupSequence($constraint->groups);
} elseif ($constraint instanceof GroupSequenceProvider) {
@ -56,7 +57,7 @@ class AnnotationLoader implements LoaderInterface
foreach ($reflClass->getProperties() as $property) {
if ($property->getDeclaringClass()->name === $className) {
foreach ($this->reader->getPropertyAnnotations($property) as $constraint) {
foreach ($this->getAnnotations($property) as $constraint) {
if ($constraint instanceof Constraint) {
$metadata->addPropertyConstraint($property->name, $constraint);
}
@ -68,7 +69,7 @@ class AnnotationLoader implements LoaderInterface
foreach ($reflClass->getMethods() as $method) {
if ($method->getDeclaringClass()->name === $className) {
foreach ($this->reader->getMethodAnnotations($method) as $constraint) {
foreach ($this->getAnnotations($method) as $constraint) {
if ($constraint instanceof Callback) {
$constraint->callback = $method->getName();
@ -88,4 +89,35 @@ class AnnotationLoader implements LoaderInterface
return $success;
}
/**
* @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection
*/
private function getAnnotations(object $reflection): iterable
{
if (\PHP_VERSION_ID >= 80000) {
foreach ($reflection->getAttributes(GroupSequence::class) as $attribute) {
yield $attribute->newInstance();
}
foreach ($reflection->getAttributes(GroupSequenceProvider::class) as $attribute) {
yield $attribute->newInstance();
}
foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
yield $attribute->newInstance();
}
}
if (!$this->reader) {
return;
}
if ($reflection instanceof \ReflectionClass) {
yield from $this->reader->getClassAnnotations($reflection);
}
if ($reflection instanceof \ReflectionMethod) {
yield from $this->reader->getMethodAnnotations($reflection);
}
if ($reflection instanceof \ReflectionProperty) {
yield from $this->reader->getPropertyAnnotations($reflection);
}
}
}

View File

@ -229,22 +229,29 @@ class CallbackValidatorTest extends ConstraintValidatorTestCase
public function testPayloadIsPassedToCallback()
{
$object = new \stdClass();
$payloadCopy = null;
$payloadCopy = 'Replace me!';
$callback = function ($object, ExecutionContextInterface $constraint, $payload) use (&$payloadCopy) {
$payloadCopy = $payload;
};
$constraint = new Callback([
'callback' => function ($object, ExecutionContextInterface $constraint, $payload) use (&$payloadCopy) {
$payloadCopy = $payload;
},
'callback' => $callback,
'payload' => 'Hello world!',
]);
$this->validator->validate($object, $constraint);
$this->assertEquals('Hello world!', $payloadCopy);
$payloadCopy = null;
if (\PHP_VERSION_ID >= 80000) {
$payloadCopy = 'Replace me!';
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Callback(callback: $callback, payload: "Hello world!");');
$this->validator->validate($object, $constraint);
$this->assertEquals('Hello world!', $payloadCopy);
$payloadCopy = 'Replace me!';
}
$payloadCopy = 'Replace me!';
$constraint = new Callback([
'callback' => function ($object, ExecutionContextInterface $constraint, $payload) use (&$payloadCopy) {
$payloadCopy = $payload;
},
'callback' => $callback,
]);
$this->validator->validate($object, $constraint);
$this->assertNull($payloadCopy);

View File

@ -72,44 +72,52 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
$this->validator->validate('foobar', new Choice(['callback' => 'abcd']));
}
public function testValidChoiceArray()
/**
* @dataProvider provideConstraintsWithChoicesArray
*/
public function testValidChoiceArray(Choice $constraint)
{
$constraint = new Choice(['choices' => ['foo', 'bar']]);
$this->validator->validate('bar', $constraint);
$this->assertNoViolation();
}
public function testValidChoiceCallbackFunction()
public function provideConstraintsWithChoicesArray(): iterable
{
$constraint = new Choice(['callback' => __NAMESPACE__.'\choice_callback']);
yield 'Doctrine style' => [new Choice(['choices' => ['foo', 'bar']])];
yield 'Doctrine default option' => [new Choice(['value' => ['foo', 'bar']])];
yield 'first argument' => [new Choice(['foo', 'bar'])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments' => [eval('return new \Symfony\Component\Validator\Constraints\Choice(choices: ["foo", "bar"]);')];
}
}
/**
* @dataProvider provideConstraintsWithCallbackFunction
*/
public function testValidChoiceCallbackFunction(Choice $constraint)
{
$this->validator->validate('bar', $constraint);
$this->assertNoViolation();
}
public function testValidChoiceCallbackClosure()
public function provideConstraintsWithCallbackFunction(): iterable
{
$constraint = new Choice([
yield 'doctrine style, namespaced function' => [new Choice(['callback' => __NAMESPACE__.'\choice_callback'])];
yield 'doctrine style, closure' => [new Choice([
'callback' => function () {
return ['foo', 'bar'];
},
]);
])];
yield 'doctrine style, static method' => [new Choice(['callback' => [__CLASS__, 'staticCallback']])];
$this->validator->validate('bar', $constraint);
$this->assertNoViolation();
}
public function testValidChoiceCallbackStaticMethod()
{
$constraint = new Choice(['callback' => [__CLASS__, 'staticCallback']]);
$this->validator->validate('bar', $constraint);
$this->assertNoViolation();
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments, namespaced function' => [eval("return new \Symfony\Component\Validator\Constraints\Choice(callback: 'Symfony\Component\Validator\Tests\Constraints\choice_callback');")];
yield 'named arguments, closure' => [eval('return new \Symfony\Component\Validator\Constraints\Choice(callback: fn () => ["foo", "bar"]);')];
yield 'named arguments, static method' => [eval('return new \Symfony\Component\Validator\Constraints\Choice(callback: ["Symfony\Component\Validator\Tests\Constraints\ChoiceValidatorTest", "staticCallback"]);')];
}
}
public function testValidChoiceCallbackContextMethod()
@ -136,25 +144,36 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
public function testMultipleChoices()
/**
* @dataProvider provideConstraintsWithMultipleTrue
*/
public function testMultipleChoices(Choice $constraint)
{
$constraint = new Choice([
'choices' => ['foo', 'bar', 'baz'],
'multiple' => true,
]);
$this->validator->validate(['baz', 'bar'], $constraint);
$this->assertNoViolation();
}
public function testInvalidChoice()
public function provideConstraintsWithMultipleTrue(): iterable
{
$constraint = new Choice([
'choices' => ['foo', 'bar'],
'message' => 'myMessage',
]);
yield 'Doctrine style' => [new Choice([
'choices' => ['foo', 'bar', 'baz'],
'multiple' => true,
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments' => [eval("return new \Symfony\Component\Validator\Constraints\Choice(
choices: ['foo', 'bar', 'baz'],
multiple: true,
);")];
}
}
/**
* @dataProvider provideConstraintsWithMessage
*/
public function testInvalidChoice(Choice $constraint)
{
$this->validator->validate('baz', $constraint);
$this->buildViolation('myMessage')
@ -164,6 +183,15 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
public function provideConstraintsWithMessage(): iterable
{
yield 'Doctrine style' => [new Choice(['choices' => ['foo', 'bar'], 'message' => 'myMessage'])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments' => [eval('return new \Symfony\Component\Validator\Constraints\Choice(choices: ["foo", "bar"], message: "myMessage");')];
}
}
public function testInvalidChoiceEmptyChoices()
{
$constraint = new Choice([
@ -182,14 +210,11 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
public function testInvalidChoiceMultiple()
/**
* @dataProvider provideConstraintsWithMultipleMessage
*/
public function testInvalidChoiceMultiple(Choice $constraint)
{
$constraint = new Choice([
'choices' => ['foo', 'bar'],
'multipleMessage' => 'myMessage',
'multiple' => true,
]);
$this->validator->validate(['foo', 'baz'], $constraint);
$this->buildViolation('myMessage')
@ -200,15 +225,28 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
public function testTooFewChoices()
public function provideConstraintsWithMultipleMessage(): iterable
{
$constraint = new Choice([
'choices' => ['foo', 'bar', 'moo', 'maa'],
yield 'Doctrine style' => [new Choice([
'choices' => ['foo', 'bar'],
'multipleMessage' => 'myMessage',
'multiple' => true,
'min' => 2,
'minMessage' => 'myMessage',
]);
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments' => [eval("return new \Symfony\Component\Validator\Constraints\Choice(
choices: ['foo', 'bar'],
multipleMessage: 'myMessage',
multiple: true,
);")];
}
}
/**
* @dataProvider provideConstraintsWithMin
*/
public function testTooFewChoices(Choice $constraint)
{
$value = ['foo'];
$this->setValue($value);
@ -223,15 +261,30 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
public function testTooManyChoices()
public function provideConstraintsWithMin(): iterable
{
$constraint = new Choice([
yield 'Doctrine style' => [new Choice([
'choices' => ['foo', 'bar', 'moo', 'maa'],
'multiple' => true,
'max' => 2,
'maxMessage' => 'myMessage',
]);
'min' => 2,
'minMessage' => 'myMessage',
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments' => [eval("return new \Symfony\Component\Validator\Constraints\Choice(
choices: ['foo', 'bar', 'moo', 'maa'],
multiple: true,
min: 2,
minMessage: 'myMessage',
);")];
}
}
/**
* @dataProvider provideConstraintsWithMax
*/
public function testTooManyChoices(Choice $constraint)
{
$value = ['foo', 'bar', 'moo'];
$this->setValue($value);
@ -246,6 +299,25 @@ class ChoiceValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
public function provideConstraintsWithMax(): iterable
{
yield 'Doctrine style' => [new Choice([
'choices' => ['foo', 'bar', 'moo', 'maa'],
'multiple' => true,
'max' => 2,
'maxMessage' => 'myMessage',
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named arguments' => [eval("return new \Symfony\Component\Validator\Constraints\Choice(
choices: ['foo', 'bar', 'moo', 'maa'],
multiple: true,
max: 2,
maxMessage: 'myMessage',
);")];
}
}
public function testStrictAllowsExactValue()
{
$constraint = new Choice([

View File

@ -15,7 +15,7 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Validator\Constraints\Expression;
use Symfony\Component\Validator\Constraints\ExpressionValidator;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\ToString;
class ExpressionValidatorTest extends ConstraintValidatorTestCase

View File

@ -36,12 +36,11 @@ class IsFalseValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
public function testTrueIsInvalid()
/**
* @dataProvider provideInvalidConstraints
*/
public function testTrueIsInvalid(IsFalse $constraint)
{
$constraint = new IsFalse([
'message' => 'myMessage',
]);
$this->validator->validate(true, $constraint);
$this->buildViolation('myMessage')
@ -49,4 +48,15 @@ class IsFalseValidatorTest extends ConstraintValidatorTestCase
->setCode(IsFalse::NOT_FALSE_ERROR)
->assertRaised();
}
public function provideInvalidConstraints(): iterable
{
yield 'Doctrine style' => [new IsFalse([
'message' => 'myMessage',
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named parameters' => [eval('return new \Symfony\Component\Validator\Constraints\IsFalse(message: "myMessage");')];
}
}
}

View File

@ -46,6 +46,22 @@ class IsNullValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getInvalidValues
*/
public function testInvalidValuesNamed($value, $valueAsString)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\IsNull(message: "myMessage");');
$this->validator->validate($value, $constraint);
$this->buildViolation('myMessage')
->setParameter('{{ value }}', $valueAsString)
->setCode(IsNull::NOT_NULL_ERROR)
->assertRaised();
}
public function getInvalidValues()
{
return [

View File

@ -36,12 +36,11 @@ class IsTrueValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
public function testFalseIsInvalid()
/**
* @dataProvider provideInvalidConstraints
*/
public function testFalseIsInvalid(IsTrue $constraint)
{
$constraint = new IsTrue([
'message' => 'myMessage',
]);
$this->validator->validate(false, $constraint);
$this->buildViolation('myMessage')
@ -49,4 +48,15 @@ class IsTrueValidatorTest extends ConstraintValidatorTestCase
->setCode(IsTrue::NOT_TRUE_ERROR)
->assertRaised();
}
public function provideInvalidConstraints(): iterable
{
yield 'Doctrine style' => [new IsTrue([
'message' => 'myMessage',
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named parameters' => [eval('return new \Symfony\Component\Validator\Constraints\IsTrue(message: "myMessage");')];
}
}
}

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests\Constraints;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
/**
* @author Renan Taranto <renantaranto@gmail.com>
@ -26,6 +28,25 @@ class NotBlankTest extends TestCase
$this->assertEquals('trim', $notBlank->normalizer);
}
/**
* @requires PHP 8
*/
public function testAttributes()
{
$metadata = new ClassMetadata(NotBlankDummy::class);
$loader = new AnnotationLoader();
self::assertTrue($loader->loadClassMetadata($metadata));
list($aConstraint) = $metadata->properties['a']->getConstraints();
self::assertFalse($aConstraint->allowNull);
self::assertNull($aConstraint->normalizer);
list($bConstraint) = $metadata->properties['b']->getConstraints();
self::assertTrue($bConstraint->allowNull);
self::assertSame('trim', $bConstraint->normalizer);
self::assertSame('myMessage', $bConstraint->message);
}
public function testInvalidNormalizerThrowsException()
{
$this->expectException('Symfony\Component\Validator\Exception\InvalidArgumentException');
@ -40,3 +61,12 @@ class NotBlankTest extends TestCase
new NotBlank(['normalizer' => new \stdClass()]);
}
}
class NotBlankDummy
{
#[NotBlank]
private $a;
#[NotBlank(normalizer: 'trim', allowNull: true, message: 'myMessage')]
private $b;
}

View File

@ -42,12 +42,11 @@ class NotNullValidatorTest extends ConstraintValidatorTestCase
];
}
public function testNullIsInvalid()
/**
* @dataProvider provideInvalidConstraints
*/
public function testNullIsInvalid(NotNull $constraint)
{
$constraint = new NotNull([
'message' => 'myMessage',
]);
$this->validator->validate(null, $constraint);
$this->buildViolation('myMessage')
@ -55,4 +54,15 @@ class NotNullValidatorTest extends ConstraintValidatorTestCase
->setCode(NotNull::IS_NULL_ERROR)
->assertRaised();
}
public function provideInvalidConstraints(): iterable
{
yield 'Doctrine style' => [new NotNull([
'message' => 'myMessage',
])];
if (\PHP_VERSION_ID >= 80000) {
yield 'named parameters' => [eval('return new \Symfony\Component\Validator\Constraints\NotNull(message: "myMessage");')];
}
}
}

View File

@ -20,6 +20,16 @@ class RangeTest extends TestCase
]);
}
/**
* @requires PHP 8
*/
public function testThrowsConstraintExceptionIfBothMinLimitAndPropertyPathNamed()
{
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
$this->expectExceptionMessage('requires only one of the "min" or "minPropertyPath" options to be set, not both.');
eval('new \Symfony\Component\Validator\Constraints\Range(min: "min", minPropertyPath: "minPropertyPath");');
}
public function testThrowsConstraintExceptionIfBothMaxLimitAndPropertyPath()
{
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
@ -30,6 +40,16 @@ class RangeTest extends TestCase
]);
}
/**
* @requires PHP 8
*/
public function testThrowsConstraintExceptionIfBothMaxLimitAndPropertyPathNamed()
{
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
$this->expectExceptionMessage('requires only one of the "max" or "maxPropertyPath" options to be set, not both.');
eval('new \Symfony\Component\Validator\Constraints\Range(max: "max", maxPropertyPath: "maxPropertyPath");');
}
public function testThrowsConstraintExceptionIfNoLimitNorPropertyPath()
{
$this->expectException('Symfony\Component\Validator\Exception\MissingOptionsException');
@ -39,8 +59,7 @@ class RangeTest extends TestCase
public function testThrowsNoDefaultOptionConfiguredException()
{
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
$this->expectExceptionMessage('No default option is configured');
$this->expectException(\TypeError::class);
new Range('value');
}

View File

@ -76,6 +76,18 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
/**
* @requires PHP 8
* @dataProvider getTenToTwenty
*/
public function testValidValuesMinNamed($value)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(min: 10);');
$this->validator->validate($value, $constraint);
$this->assertNoViolation();
}
/**
* @dataProvider getTenToTwenty
*/
@ -87,6 +99,18 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
/**
* @requires PHP 8
* @dataProvider getTenToTwenty
*/
public function testValidValuesMaxNamed($value)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(max: 20);');
$this->validator->validate($value, $constraint);
$this->assertNoViolation();
}
/**
* @dataProvider getTenToTwenty
*/
@ -98,6 +122,18 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
/**
* @requires PHP 8
* @dataProvider getTenToTwenty
*/
public function testValidValuesMinMaxNamed($value)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(min:10, max: 20);');
$this->validator->validate($value, $constraint);
$this->assertNoViolation();
}
/**
* @dataProvider getLessThanTen
*/
@ -117,6 +153,23 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getLessThanTen
*/
public function testInvalidValuesMinNamed($value, $formattedValue)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(min:10, minMessage: "myMessage");');
$this->validator->validate($value, $constraint);
$this->buildViolation('myMessage')
->setParameter('{{ value }}', $formattedValue)
->setParameter('{{ limit }}', 10)
->setCode(Range::TOO_LOW_ERROR)
->assertRaised();
}
/**
* @dataProvider getMoreThanTwenty
*/
@ -136,6 +189,23 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getMoreThanTwenty
*/
public function testInvalidValuesMaxNamed($value, $formattedValue)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(max:20, maxMessage: "myMessage");');
$this->validator->validate($value, $constraint);
$this->buildViolation('myMessage')
->setParameter('{{ value }}', $formattedValue)
->setParameter('{{ limit }}', 20)
->setCode(Range::TOO_HIGH_ERROR)
->assertRaised();
}
/**
* @dataProvider getMoreThanTwenty
*/
@ -157,6 +227,24 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getMoreThanTwenty
*/
public function testInvalidValuesCombinedMaxNamed($value, $formattedValue)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(min: 10, max:20, notInRangeMessage: "myNotInRangeMessage");');
$this->validator->validate($value, $constraint);
$this->buildViolation('myNotInRangeMessage')
->setParameter('{{ value }}', $formattedValue)
->setParameter('{{ min }}', 10)
->setParameter('{{ max }}', 20)
->setCode(Range::NOT_IN_RANGE_ERROR)
->assertRaised();
}
/**
* @dataProvider getLessThanTen
*/
@ -178,6 +266,24 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getLessThanTen
*/
public function testInvalidValuesCombinedMinNamed($value, $formattedValue)
{
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(min: 10, max:20, notInRangeMessage: "myNotInRangeMessage");');
$this->validator->validate($value, $constraint);
$this->buildViolation('myNotInRangeMessage')
->setParameter('{{ value }}', $formattedValue)
->setParameter('{{ min }}', 10)
->setParameter('{{ max }}', 20)
->setCode(Range::NOT_IN_RANGE_ERROR)
->assertRaised();
}
public function getTenthToTwentiethMarch2014()
{
// The provider runs before setUp(), so we need to manually fix
@ -531,6 +637,19 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
/**
* @requires PHP 8
* @dataProvider getTenToTwenty
*/
public function testValidValuesMinPropertyPathNamed($value)
{
$this->setObject(new Limit(10));
$this->validator->validate($value, eval('return new \Symfony\Component\Validator\Constraints\Range(minPropertyPath: "value");'));
$this->assertNoViolation();
}
/**
* @dataProvider getTenToTwenty
*/
@ -545,6 +664,19 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
$this->assertNoViolation();
}
/**
* @requires PHP 8
* @dataProvider getTenToTwenty
*/
public function testValidValuesMaxPropertyPathNamed($value)
{
$this->setObject(new Limit(20));
$this->validator->validate($value, eval('return new \Symfony\Component\Validator\Constraints\Range(maxPropertyPath: "value");'));
$this->assertNoViolation();
}
/**
* @dataProvider getTenToTwenty
*/
@ -629,6 +761,32 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getMoreThanTwenty
*/
public function testInvalidValuesCombinedMaxPropertyPathNamed($value, $formattedValue)
{
$this->setObject(new MinMax(10, 20));
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(
minPropertyPath: "min",
maxPropertyPath: "max",
notInRangeMessage: "myNotInRangeMessage",
);');
$this->validator->validate($value, $constraint);
$this->buildViolation('myNotInRangeMessage')
->setParameter('{{ value }}', $formattedValue)
->setParameter('{{ min }}', 10)
->setParameter('{{ max }}', 20)
->setParameter('{{ max_limit_path }}', 'max')
->setParameter('{{ min_limit_path }}', 'min')
->setCode(Range::NOT_IN_RANGE_ERROR)
->assertRaised();
}
/**
* @dataProvider getLessThanTen
*/
@ -654,6 +812,32 @@ class RangeValidatorTest extends ConstraintValidatorTestCase
->assertRaised();
}
/**
* @requires PHP 8
* @dataProvider getLessThanTen
*/
public function testInvalidValuesCombinedMinPropertyPathNamed($value, $formattedValue)
{
$this->setObject(new MinMax(10, 20));
$constraint = eval('return new \Symfony\Component\Validator\Constraints\Range(
minPropertyPath: "min",
maxPropertyPath: "max",
notInRangeMessage: "myNotInRangeMessage",
);');
$this->validator->validate($value, $constraint);
$this->buildViolation('myNotInRangeMessage')
->setParameter('{{ value }}', $formattedValue)
->setParameter('{{ min }}', 10)
->setParameter('{{ max }}', 20)
->setParameter('{{ max_limit_path }}', 'max')
->setParameter('{{ min_limit_path }}', 'min')
->setCode(Range::NOT_IN_RANGE_ERROR)
->assertRaised();
}
/**
* @dataProvider getLessThanTen
*/

View File

@ -9,10 +9,11 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Tests\Fixtures;
namespace Symfony\Component\Validator\Tests\Fixtures\Annotation;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceB;
/**
* @Symfony\Component\Validator\Tests\Fixtures\ConstraintA

View File

@ -9,9 +9,10 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Tests\Fixtures;
namespace Symfony\Component\Validator\Tests\Fixtures\Annotation;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceA;
class EntityParent implements EntityInterfaceA
{

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Tests\Fixtures;
namespace Symfony\Component\Validator\Tests\Fixtures\Annotation;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\GroupSequenceProviderInterface;

View File

@ -0,0 +1,145 @@
<?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\Fixtures\Attribute;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceB;
use Symfony\Component\Validator\Tests\Fixtures\CallbackClass;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
#[
ConstraintA,
Assert\GroupSequence(['Foo', 'Entity']),
Assert\Callback([CallbackClass::class, 'callback']),
]
class Entity extends EntityParent implements EntityInterfaceB
{
/**
* @Assert\All({@Assert\NotNull, @Assert\Range(min=3)}),
* @Assert\All(constraints={@Assert\NotNull, @Assert\Range(min=3)})
* @Assert\Collection(fields={
* "foo" = {@Assert\NotNull, @Assert\Range(min=3)},
* "bar" = @Assert\Range(min=5)
* })
* @Assert\Choice(choices={"A", "B"}, message="Must be one of %choices%")
*/
#[
Assert\NotNull,
Assert\Range(min: 3),
]
public $firstName;
#[Assert\Valid]
public $childA;
#[Assert\Valid]
public $childB;
protected $lastName;
public $reference;
public $reference2;
private $internal;
public $data = 'Overridden data';
public $initialized = false;
public function __construct($internal = null)
{
$this->internal = $internal;
}
public function getFirstName()
{
return $this->firstName;
}
public function getInternal()
{
return $this->internal.' from getter';
}
public function setLastName($lastName)
{
$this->lastName = $lastName;
}
#[Assert\NotNull]
public function getLastName()
{
return $this->lastName;
}
public function getValid()
{
}
#[Assert\IsTrue]
public function isValid()
{
return 'valid';
}
#[Assert\IsTrue]
public function hasPermissions()
{
return 'permissions';
}
public function getData()
{
return 'Overridden data';
}
#[Assert\Callback(payload: 'foo')]
public function validateMe(ExecutionContextInterface $context)
{
}
#[Assert\Callback]
public static function validateMeStatic($object, ExecutionContextInterface $context)
{
}
/**
* @return mixed
*/
public function getChildA()
{
return $this->childA;
}
/**
* @param mixed $childA
*/
public function setChildA($childA)
{
$this->childA = $childA;
}
/**
* @return mixed
*/
public function getChildB()
{
return $this->childB;
}
/**
* @param mixed $childB
*/
public function setChildB($childB)
{
$this->childB = $childB;
}
public function getReference()
{
return $this->reference;
}
}

View File

@ -0,0 +1,36 @@
<?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\Fixtures\Attribute;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceA;
class EntityParent implements EntityInterfaceA
{
protected $firstName;
private $internal;
private $data = 'Data';
private $child;
#[NotNull]
protected $other;
public function getData()
{
return 'Data';
}
public function getChild()
{
return $this->child;
}
}

View File

@ -0,0 +1,34 @@
<?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\Fixtures\Attribute;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\GroupSequenceProviderInterface;
#[Assert\GroupSequenceProvider]
class GroupSequenceProviderEntity implements GroupSequenceProviderInterface
{
public $firstName;
public $lastName;
protected $sequence = [];
public function __construct($sequence)
{
$this->sequence = $sequence;
}
public function getGroupSequence()
{
return $this->sequence;
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Tests\Fixtures;
use Symfony\Component\Validator\Constraint;
/** @Annotation */
#[\Attribute]
class ConstraintA extends Constraint
{
public $property1;

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Tests\Fixtures;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity;
class GroupSequenceProviderChildEntity extends GroupSequenceProviderEntity
{
}

View File

@ -19,6 +19,9 @@ use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\CascadingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\EntityParent;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity;
use Symfony\Component\Validator\Tests\Fixtures\CascadingEntity;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
@ -27,9 +30,9 @@ use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
class ClassMetadataTest extends TestCase
{
const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent';
const PROVIDERCLASS = 'Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity';
const CLASSNAME = Entity::class;
const PARENTCLASS = EntityParent::class;
const PROVIDERCLASS = GroupSequenceProviderEntity::class;
const PROVIDERCHILDCLASS = 'Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderChildEntity';
protected $metadata;

View File

@ -19,14 +19,16 @@ use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\EntityParent;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\PropertyGetter;
use Symfony\Component\Validator\Tests\Fixtures\PropertyGetterInterface;
class LazyLoadingMetadataFactoryTest extends TestCase
{
const CLASS_NAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
const PARENT_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent';
const CLASS_NAME = Entity::class;
const PARENT_CLASS = EntityParent::class;
const INTERFACE_A_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceA';
const INTERFACE_B_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceB';
const PARENT_INTERFACE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParentInterface';

View File

@ -13,11 +13,11 @@ namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Mapping\GetterMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
class GetterMetadataTest extends TestCase
{
const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
const CLASSNAME = Entity::class;
public function testInvalidPropertyName()
{
@ -64,7 +64,7 @@ class GetterMetadataTest extends TestCase
public function testUndefinedMethodNameThrowsException()
{
$this->expectException('Symfony\Component\Validator\Exception\ValidatorException');
$this->expectExceptionMessage('The "hasLastName()" method does not exist in class "Symfony\Component\Validator\Tests\Fixtures\Entity".');
$this->expectExceptionMessage('The "hasLastName()" method does not exist in class "Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity".');
new GetterMetadata(self::CLASSNAME, 'lastName', 'hasLastName');
}
}

View File

@ -23,6 +23,7 @@ use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
class AnnotationLoaderTest extends TestCase
@ -31,7 +32,7 @@ class AnnotationLoaderTest extends TestCase
{
$reader = new AnnotationReader();
$loader = new AnnotationLoader($reader);
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$this->assertTrue($loader->loadClassMetadata($metadata));
}
@ -44,14 +45,17 @@ class AnnotationLoaderTest extends TestCase
$this->assertFalse($loader->loadClassMetadata($metadata));
}
public function testLoadClassMetadata()
/**
* @dataProvider provideNamespaces
*/
public function testLoadClassMetadata(string $namespace)
{
$loader = new AnnotationLoader(new AnnotationReader());
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata($namespace.'\Entity');
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected = new ClassMetadata($namespace.'\Entity');
$expected->setGroupSequence(['Foo', 'Entity']);
$expected->addConstraint(new ConstraintA());
$expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback']));
@ -83,16 +87,18 @@ class AnnotationLoaderTest extends TestCase
/**
* Test MetaData merge with parent annotation.
*
* @dataProvider provideNamespaces
*/
public function testLoadParentClassMetadata()
public function testLoadParentClassMetadata(string $namespace)
{
$loader = new AnnotationLoader(new AnnotationReader());
// Load Parent MetaData
$parent_metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent');
$parent_metadata = new ClassMetadata($namespace.'\EntityParent');
$loader->loadClassMetadata($parent_metadata);
$expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent');
$expected_parent = new ClassMetadata($namespace.'\EntityParent');
$expected_parent->addPropertyConstraint('other', new NotNull());
$expected_parent->getReflectionClass();
@ -101,27 +107,29 @@ class AnnotationLoaderTest extends TestCase
/**
* Test MetaData merge with parent annotation.
*
* @dataProvider provideNamespaces
*/
public function testLoadClassMetadataAndMerge()
public function testLoadClassMetadataAndMerge(string $namespace)
{
$loader = new AnnotationLoader(new AnnotationReader());
// Load Parent MetaData
$parent_metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent');
$parent_metadata = new ClassMetadata($namespace.'\EntityParent');
$loader->loadClassMetadata($parent_metadata);
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata($namespace.'\Entity');
// Merge parent metaData.
$metadata->mergeConstraints($parent_metadata);
$loader->loadClassMetadata($metadata);
$expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent');
$expected_parent = new ClassMetadata($namespace.'\EntityParent');
$expected_parent->addPropertyConstraint('other', new NotNull());
$expected_parent->getReflectionClass();
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected = new ClassMetadata($namespace.'\Entity');
$expected->mergeConstraints($expected_parent);
$expected->setGroupSequence(['Foo', 'Entity']);
@ -153,17 +161,29 @@ class AnnotationLoaderTest extends TestCase
$this->assertEquals($expected, $metadata);
}
public function testLoadGroupSequenceProviderAnnotation()
/**
* @dataProvider provideNamespaces
*/
public function testLoadGroupSequenceProviderAnnotation(string $namespace)
{
$loader = new AnnotationLoader(new AnnotationReader());
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity');
$metadata = new ClassMetadata($namespace.'\GroupSequenceProviderEntity');
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity');
$expected = new ClassMetadata($namespace.'\GroupSequenceProviderEntity');
$expected->setGroupSequenceProvider(true);
$expected->getReflectionClass();
$this->assertEquals($expected, $metadata);
}
public function provideNamespaces(): iterable
{
yield 'annotations' => ['Symfony\Component\Validator\Tests\Fixtures\Annotation'];
if (\PHP_VERSION_ID >= 80000) {
yield 'attributes' => ['Symfony\Component\Validator\Tests\Fixtures\Attribute'];
}
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
class FilesLoaderTest extends TestCase
{
@ -29,7 +30,7 @@ class FilesLoaderTest extends TestCase
$fileLoader->expects($this->exactly(4))
->method('loadClassMetadata');
$loader = $this->getFilesLoader($fileLoader);
$loader->loadClassMetadata(new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'));
$loader->loadClassMetadata(new ClassMetadata(Entity::class));
}
public function getFilesLoader(LoaderInterface $loader)

View File

@ -23,7 +23,7 @@ use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderEntity;
use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderNoAutoMappingEntity;
use Symfony\Component\Validator\Validation;

View File

@ -24,6 +24,8 @@ use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
@ -32,7 +34,7 @@ class XmlFileLoaderTest extends TestCase
public function testLoadClassMetadataReturnsTrueIfSuccessful()
{
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$this->assertTrue($loader->loadClassMetadata($metadata));
}
@ -48,11 +50,11 @@ class XmlFileLoaderTest extends TestCase
public function testLoadClassMetadata()
{
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected = new ClassMetadata(Entity::class);
$expected->setGroupSequence(['Foo', 'Entity']);
$expected->addConstraint(new ConstraintA());
$expected->addConstraint(new ConstraintB());
@ -83,11 +85,11 @@ class XmlFileLoaderTest extends TestCase
public function testLoadClassMetadataWithNonStrings()
{
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping-non-strings.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected = new ClassMetadata(Entity::class);
$expected->addPropertyConstraint('firstName', new Regex(['pattern' => '/^1/', 'match' => false]));
$properties = $metadata->getPropertyMetadata('firstName');
@ -99,11 +101,11 @@ class XmlFileLoaderTest extends TestCase
public function testLoadGroupSequenceProvider()
{
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity');
$metadata = new ClassMetadata(GroupSequenceProviderEntity::class);
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity');
$expected = new ClassMetadata(GroupSequenceProviderEntity::class);
$expected->setGroupSequenceProvider(true);
$this->assertEquals($expected, $metadata);
@ -112,7 +114,7 @@ class XmlFileLoaderTest extends TestCase
public function testThrowExceptionIfDocTypeIsSet()
{
$loader = new XmlFileLoader(__DIR__.'/withdoctype.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$this->expectException('\Symfony\Component\Validator\Exception\MappingException');
$loader->loadClassMetadata($metadata);
@ -124,7 +126,7 @@ class XmlFileLoaderTest extends TestCase
public function testDoNotModifyStateIfExceptionIsThrown()
{
$loader = new XmlFileLoader(__DIR__.'/withdoctype.xml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
try {
$loader->loadClassMetadata($metadata);

View File

@ -21,6 +21,8 @@ use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
@ -29,7 +31,7 @@ class YamlFileLoaderTest extends TestCase
public function testLoadClassMetadataReturnsFalseIfEmpty()
{
$loader = new YamlFileLoader(__DIR__.'/empty-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$this->assertFalse($loader->loadClassMetadata($metadata));
@ -45,7 +47,7 @@ class YamlFileLoaderTest extends TestCase
{
$this->expectException('InvalidArgumentException');
$loader = new YamlFileLoader(__DIR__.'/'.$path);
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$loader->loadClassMetadata($metadata);
}
@ -64,7 +66,7 @@ class YamlFileLoaderTest extends TestCase
public function testDoNotModifyStateIfExceptionIsThrown()
{
$loader = new YamlFileLoader(__DIR__.'/nonvalid-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
try {
$loader->loadClassMetadata($metadata);
} catch (\InvalidArgumentException $e) {
@ -77,7 +79,7 @@ class YamlFileLoaderTest extends TestCase
public function testLoadClassMetadataReturnsTrueIfSuccessful()
{
$loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$this->assertTrue($loader->loadClassMetadata($metadata));
}
@ -93,11 +95,11 @@ class YamlFileLoaderTest extends TestCase
public function testLoadClassMetadata()
{
$loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected = new ClassMetadata(Entity::class);
$expected->setGroupSequence(['Foo', 'Entity']);
$expected->addConstraint(new ConstraintA());
$expected->addConstraint(new ConstraintB());
@ -127,11 +129,11 @@ class YamlFileLoaderTest extends TestCase
public function testLoadClassMetadataWithConstants()
{
$loader = new YamlFileLoader(__DIR__.'/mapping-with-constants.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$metadata = new ClassMetadata(Entity::class);
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$expected = new ClassMetadata(Entity::class);
$expected->addPropertyConstraint('firstName', new Range(['max' => \PHP_INT_MAX]));
$this->assertEquals($expected, $metadata);
@ -140,11 +142,11 @@ class YamlFileLoaderTest extends TestCase
public function testLoadGroupSequenceProvider()
{
$loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml');
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity');
$metadata = new ClassMetadata(GroupSequenceProviderEntity::class);
$loader->loadClassMetadata($metadata);
$expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity');
$expected = new ClassMetadata(GroupSequenceProviderEntity::class);
$expected->setGroupSequenceProvider(true);
$this->assertEquals($expected, $metadata);

View File

@ -1,7 +1,7 @@
namespaces:
custom: Symfony\Component\Validator\Tests\Fixtures\
Symfony\Component\Validator\Tests\Fixtures\Entity:
Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity:
constraints:
# Custom constraint
- Symfony\Component\Validator\Tests\Fixtures\ConstraintA: ~

View File

@ -6,7 +6,7 @@
<namespace prefix="custom">Symfony\Component\Validator\Tests\Fixtures\</namespace>
<class name="Symfony\Component\Validator\Tests\Fixtures\Entity">
<class name="Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity">
<property name="firstName">
<!-- Constraint with a Boolean -->
<constraint name="Regex">

View File

@ -6,7 +6,7 @@
<namespace prefix="custom">Symfony\Component\Validator\Tests\Fixtures\</namespace>
<class name="Symfony\Component\Validator\Tests\Fixtures\Entity">
<class name="Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity">
<group-sequence>
<value>Foo</value>
@ -115,7 +115,7 @@
</getter>
</class>
<class name="Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity">
<class name="Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity">
<!-- GROUP SEQUENCE PROVIDER -->
<group-sequence-provider />

View File

@ -1,7 +1,7 @@
namespaces:
custom: Symfony\Component\Validator\Tests\Fixtures\
Symfony\Component\Validator\Tests\Fixtures\Entity:
Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity:
group_sequence:
- Foo
- Entity
@ -58,5 +58,5 @@ Symfony\Component\Validator\Tests\Fixtures\Entity:
permissions:
- "IsTrue": ~
Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity:
Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity:
group_sequence_provider: true

View File

@ -1,7 +1,7 @@
namespaces:
custom: Symfony\Component\Validator\Tests\Fixtures\
Symfony\Component\Validator\Tests\Fixtures\Entity:
Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity:
properties:
firstName:
- Range:

View File

@ -3,5 +3,5 @@
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="Symfony\Component\Validator\Tests\Fixtures\Entity" />
<class name="Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity" />
</constraint-mapping>

View File

@ -18,6 +18,7 @@ use Symfony\Component\Validator\Constraints\Required;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\MemberMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
@ -30,7 +31,7 @@ class MemberMetadataTest extends TestCase
protected function setUp(): void
{
$this->metadata = new TestMemberMetadata(
'Symfony\Component\Validator\Tests\Fixtures\Entity',
Entity::class,
'getLastName',
'lastName'
);

View File

@ -13,16 +13,17 @@ namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\EntityParent;
use Symfony\Component\Validator\Tests\Fixtures\Entity_74;
use Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy;
class PropertyMetadataTest extends TestCase
{
const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
const CLASSNAME = Entity::class;
const CLASSNAME_74 = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74';
const CLASSNAME_74_PROXY = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy';
const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent';
const PARENTCLASS = EntityParent::class;
public function testInvalidPropertyName()
{

View File

@ -24,9 +24,9 @@ use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\CascadedChild;
use Symfony\Component\Validator\Tests\Fixtures\CascadingEntity;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
use Symfony\Component\Validator\Tests\Fixtures\Reference;
use Symfony\Component\Validator\Validator\ValidatorInterface;

View File

@ -18,9 +18,9 @@ use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\GroupSequenceProviderEntity;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity;
use Symfony\Component\Validator\Tests\Fixtures\Reference;
/**
@ -28,7 +28,7 @@ use Symfony\Component\Validator\Tests\Fixtures\Reference;
*/
abstract class AbstractValidatorTest extends TestCase
{
const ENTITY_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
const ENTITY_CLASS = Entity::class;
const REFERENCE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Reference';

View File

@ -30,10 +30,10 @@ use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildA;
use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildB;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Annotation\EntityParent;
use Symfony\Component\Validator\Tests\Fixtures\CascadedChild;
use Symfony\Component\Validator\Tests\Fixtures\CascadingEntity;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\EntityParent;
use Symfony\Component\Validator\Tests\Fixtures\EntityWithGroupedConstraintOnMethods;
use Symfony\Component\Validator\Validator\RecursiveValidator;
use Symfony\Component\Validator\Validator\ValidatorInterface;

View File

@ -11,9 +11,7 @@
namespace Symfony\Component\VarDumper\Tests\Fixtures;
use Attribute;
#[Attribute]
#[\Attribute]
final class MyAttribute
{
public function __construct(

View File

@ -11,9 +11,7 @@
namespace Symfony\Component\VarDumper\Tests\Fixtures;
use Attribute;
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS_CONST | Attribute::TARGET_PROPERTY)]
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS_CONST | \Attribute::TARGET_PROPERTY)]
final class RepeatableAttribute
{
private string $string;

View File

@ -11,8 +11,6 @@
namespace Symfony\Contracts\Service\Attribute;
use Attribute;
/**
* A required dependency.
*
@ -21,7 +19,7 @@ use Attribute;
*
* @author Alexander M. Turek <me@derrabus.de>
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Required
{
}