From 13b797b666b0fb2ebf018062961c38cfef3086f8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 14 Jul 2021 16:58:59 +0200 Subject: [PATCH] [Validator] Composite constraints as attributes Signed-off-by: Alexander M. Turek --- .../Component/Validator/Constraints/All.php | 23 +++ .../Validator/Constraints/Collection.php | 26 +++- .../Validator/Constraints/Composite.php | 4 +- .../Fixtures/CompositeAttribute/Entity.php | 131 ++++++++++++++++++ .../CompositeAttribute/EntityParent.php | 36 +++++ .../GroupSequenceProviderEntity.php | 34 +++++ .../Mapping/Loader/AnnotationLoaderTest.php | 3 + 7 files changed, 249 insertions(+), 8 deletions(-) create mode 100644 src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/Entity.php create mode 100644 src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/EntityParent.php create mode 100644 src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/GroupSequenceProviderEntity.php diff --git a/src/Symfony/Component/Validator/Constraints/All.php b/src/Symfony/Component/Validator/Constraints/All.php index d3fe49525f6fa..fa24d80547f79 100644 --- a/src/Symfony/Component/Validator/Constraints/All.php +++ b/src/Symfony/Component/Validator/Constraints/All.php @@ -11,16 +11,39 @@ namespace Symfony\Component\Validator\Constraints; +use Symfony\Component\Validator\Constraint; + /** * @Annotation * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class All extends Composite { public $constraints = []; + /** + * {@inheritdoc} + * + * @param mixed[]|Constraint[]|Constraint $constraints The nested constraints or an array of options + */ + public function __construct($constraints = null, array $groups = null) + { + if ($constraints instanceof Constraint) { + $options = ['constraints' => $constraints]; + } elseif (!\is_array($constraints)) { + throw new \TypeError(sprintf('%s: Parameter #1 is extected to be an array or an instance of %s, %s given.', __METHOD__, Constraint::class, get_debug_type($constraints))); + } elseif (\array_key_exists('constraints', $constraints) || \array_key_exists('value', $constraints)) { + $options = $constraints; + } else { + $options = ['constraints' => $constraints]; + } + + parent::__construct($options, $groups); + } + public function getDefaultOption() { return 'constraints'; diff --git a/src/Symfony/Component/Validator/Constraints/Collection.php b/src/Symfony/Component/Validator/Constraints/Collection.php index 6007b13318a56..749a599a71271 100644 --- a/src/Symfony/Component/Validator/Constraints/Collection.php +++ b/src/Symfony/Component/Validator/Constraints/Collection.php @@ -19,6 +19,7 @@ * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Collection extends Composite { public const MISSING_FIELD_ERROR = '2fa2158c-2a7f-484b-98aa-975522539ff8'; @@ -38,15 +39,28 @@ class Collection extends Composite /** * {@inheritdoc} */ - public function __construct($options = null) - { + public function __construct( + array $fields = null, + bool $allowExtraFields = null, + bool $allowMissingFields = null, + string $extraFieldsMessage = null, + string $missingFieldsMessage = null, + array $groups = null + ) { // no known options set? $options is the fields array - if (\is_array($options) - && !array_intersect(array_keys($options), ['groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage'])) { - $options = ['fields' => $options]; + if (\is_array($fields) + && !array_intersect(array_keys($fields), ['groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage'])) { + $options = ['fields' => $fields]; + } else { + $options = $fields; } - parent::__construct($options); + parent::__construct($options, $groups); + + $this->allowExtraFields = $allowExtraFields ?? $this->allowExtraFields; + $this->allowMissingFields = $allowMissingFields ?? $this->allowMissingFields; + $this->extraFieldsMessage = $extraFieldsMessage ?? $this->extraFieldsMessage; + $this->missingFieldsMessage = $missingFieldsMessage ?? $this->missingFieldsMessage; } /** diff --git a/src/Symfony/Component/Validator/Constraints/Composite.php b/src/Symfony/Component/Validator/Constraints/Composite.php index b24da39d22855..61f55c7535192 100644 --- a/src/Symfony/Component/Validator/Constraints/Composite.php +++ b/src/Symfony/Component/Validator/Constraints/Composite.php @@ -51,9 +51,9 @@ abstract class Composite extends Constraint * cached. When constraints are loaded from the cache, no more group * checks need to be done. */ - public function __construct($options = null) + public function __construct($options = null, array $groups = null) { - parent::__construct($options); + parent::__construct($options, $groups); $this->initializeNestedConstraints(); diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/Entity.php new file mode 100644 index 0000000000000..3de908c6ac5d1 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/Entity.php @@ -0,0 +1,131 @@ + + * + * 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\CompositeAttribute; + +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\NotNull, + Assert\Range(min: 3), + Assert\All([new Assert\NotNull(), new Assert\Range(min: 3)]), + Assert\All(constraints: [new Assert\NotNull(), new Assert\Range(min: 3)]), + Assert\Collection(fields: [ + 'foo' => [new Assert\NotNull(), new Assert\Range(min: 3)], + 'bar' => new Assert\Range(min: 5), + ]), + Assert\Choice(choices: ['A', 'B'], message: 'Must be one of %choices%'), + ] + 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) + { + } + + public function getChildA(): mixed + { + return $this->childA; + } + + public function setChildA(mixed $childA) + { + $this->childA = $childA; + } + + public function getChildB(): mixed + { + return $this->childB; + } + + public function setChildB(mixed $childB) + { + $this->childB = $childB; + } + + public function getReference() + { + return $this->reference; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/EntityParent.php b/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/EntityParent.php new file mode 100644 index 0000000000000..7bd7f6b954fe8 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/EntityParent.php @@ -0,0 +1,36 @@ + + * + * 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\CompositeAttribute; + +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; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/GroupSequenceProviderEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/GroupSequenceProviderEntity.php new file mode 100644 index 0000000000000..8159a4a638278 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/CompositeAttribute/GroupSequenceProviderEntity.php @@ -0,0 +1,34 @@ + + * + * 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\CompositeAttribute; + +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; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php index e59bfd0e6e2d5..47aa89f5748a9 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -185,5 +185,8 @@ public function provideNamespaces(): iterable if (\PHP_VERSION_ID >= 80000) { yield 'attributes' => ['Symfony\Component\Validator\Tests\Fixtures\Attribute']; } + if (\PHP_VERSION_ID >= 80100) { + yield 'composite attributes' => ['Symfony\Component\Validator\Tests\Fixtures\CompositeAttribute']; + } } } pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy