diff --git a/.github/patch-types.php b/.github/patch-types.php index 33ba6347a3ef0..05076c06e10c1 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -34,6 +34,7 @@ case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'): case false !== strpos($file, '/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php'): case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'): + case false !== strpos($file, '/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/Entity.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php'): continue 2; diff --git a/src/Symfony/Component/Validator/Constraints/All.php b/src/Symfony/Component/Validator/Constraints/All.php index d3fe49525f6fa..5b4297647da32 100644 --- a/src/Symfony/Component/Validator/Constraints/All.php +++ b/src/Symfony/Component/Validator/Constraints/All.php @@ -17,10 +17,16 @@ * * @author Bernhard Schussek */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class All extends Composite { public $constraints = []; + public function __construct($constraints = null, array $groups = null, $payload = null) + { + parent::__construct($constraints ?? [], $groups, $payload); + } + public function getDefaultOption() { return 'constraints'; diff --git a/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php b/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php index ca726ae369102..f01ed9cf4cff9 100644 --- a/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php +++ b/src/Symfony/Component/Validator/Constraints/AtLeastOneOf.php @@ -17,6 +17,7 @@ * * @author Przemysław Bogusz */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AtLeastOneOf extends Composite { public const AT_LEAST_ONE_OF_ERROR = 'f27e6d6c-261a-4056-b391-6673a623531c'; @@ -30,6 +31,15 @@ class AtLeastOneOf extends Composite public $messageCollection = 'Each element of this collection should satisfy its own set of constraints.'; public $includeInternalMessages = true; + public function __construct($constraints = null, array $groups = null, $payload = null, string $message = null, string $messageCollection = null, bool $includeInternalMessages = null) + { + parent::__construct($constraints ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + $this->messageCollection = $messageCollection ?? $this->messageCollection; + $this->includeInternalMessages = $includeInternalMessages ?? $this->includeInternalMessages; + } + 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..3f4adb5ac5286 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_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Collection extends Composite { public const MISSING_FIELD_ERROR = '2fa2158c-2a7f-484b-98aa-975522539ff8'; @@ -38,15 +39,20 @@ class Collection extends Composite /** * {@inheritdoc} */ - public function __construct($options = null) + public function __construct($fields = null, array $groups = null, $payload = null, bool $allowExtraFields = null, bool $allowMissingFields = null, string $extraFieldsMessage = null, string $missingFieldsMessage = 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]; + // no known options set? $fields is the fields array + if (\is_array($fields) + && !array_intersect(array_keys($fields), ['groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage'])) { + $fields = ['fields' => $fields]; } - parent::__construct($options); + parent::__construct($fields, $groups, $payload); + + $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..dd73fd581421e 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, $payload = null) { - parent::__construct($options); + parent::__construct($options, $groups, $payload); $this->initializeNestedConstraints(); diff --git a/src/Symfony/Component/Validator/Constraints/Sequentially.php b/src/Symfony/Component/Validator/Constraints/Sequentially.php index 0bae6f82b7424..53a0a3b912050 100644 --- a/src/Symfony/Component/Validator/Constraints/Sequentially.php +++ b/src/Symfony/Component/Validator/Constraints/Sequentially.php @@ -20,10 +20,16 @@ * * @author Maxime Steinhausser */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Sequentially extends Composite { public $constraints = []; + public function __construct($constraints = null, array $groups = null, $payload = null) + { + parent::__construct($constraints ?? [], $groups, $payload); + } + public function getDefaultOption() { return 'constraints'; diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Annotation/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Annotation/Entity.php index c818062f56af4..0e07611b0f260 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/Annotation/Entity.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Annotation/Entity.php @@ -29,9 +29,13 @@ class Entity extends EntityParent implements EntityInterfaceB * @Assert\All(constraints={@Assert\NotNull, @Assert\Range(min=3)}) * @Assert\Collection(fields={ * "foo" = {@Assert\NotNull, @Assert\Range(min=3)}, - * "bar" = @Assert\Range(min=5) - * }) + * "bar" = @Assert\Range(min=5), + * "baz" = @Assert\Required({@Assert\Email()}), + * "qux" = @Assert\Optional({@Assert\NotBlank()}) + * }, allowExtraFields=true) * @Assert\Choice(choices={"A", "B"}, message="Must be one of %choices%") + * @Assert\AtLeastOneOf({@Assert\NotNull, @Assert\Range(min=3)}, message="foo", includeInternalMessages=false) + * @Assert\Sequentially({@Assert\NotBlank, @Assert\Range(min=5)}) */ public $firstName; /** diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/Entity.php index c4b2a7a88370f..bb069b49e0ddf 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/Entity.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Attribute/Entity.php @@ -29,9 +29,13 @@ class Entity extends EntityParent implements EntityInterfaceB * @Assert\All(constraints={@Assert\NotNull, @Assert\Range(min=3)}) * @Assert\Collection(fields={ * "foo" = {@Assert\NotNull, @Assert\Range(min=3)}, - * "bar" = @Assert\Range(min=5) - * }) + * "bar" = @Assert\Range(min=5), + * "baz" = @Assert\Required({@Assert\Email()}), + * "qux" = @Assert\Optional({@Assert\NotBlank()}) + * }, allowExtraFields=true) * @Assert\Choice(choices={"A", "B"}, message="Must be one of %choices%") + * @Assert\AtLeastOneOf({@Assert\NotNull, @Assert\Range(min=3)}, message="foo", includeInternalMessages=false) + * @Assert\Sequentially({@Assert\NotBlank, @Assert\Range(min=5)}) */ #[ Assert\NotNull, diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/Entity.php new file mode 100644 index 0000000000000..c55796824a800 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/Entity.php @@ -0,0 +1,172 @@ + + * + * 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\NestedAttribute; + +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Tests\Fixtures\Attribute\EntityParent; +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), + 'baz' => new Assert\Required([new Assert\Email()]), + 'qux' => new Assert\Optional([new Assert\NotBlank()]), + ], + allowExtraFields: true + ), + Assert\Choice(choices: ['A', 'B'], message: 'Must be one of %choices%'), + Assert\AtLeastOneOf( + constraints: [ + new Assert\NotNull(), + new Assert\Range(min: 3), + ], + message: 'foo', + includeInternalMessages: false, + ), + Assert\Sequentially([ + new Assert\NotBlank(), + new Assert\Range(min: 5), + ]), + ] + 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; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/EntityParent.php b/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/EntityParent.php new file mode 100644 index 0000000000000..5284b15f5f08c --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/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\NestedAttribute; + +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/NestedAttribute/GroupSequenceProviderEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/GroupSequenceProviderEntity.php new file mode 100644 index 0000000000000..1a88ed11b0fc2 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/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\NestedAttribute; + +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..93638412b1263 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -14,12 +14,18 @@ use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\AtLeastOneOf; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\IsTrue; +use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Range; +use Symfony\Component\Validator\Constraints\Required; +use Symfony\Component\Validator\Constraints\Sequentially; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; @@ -65,14 +71,24 @@ public function testLoadClassMetadata(string $namespace) $expected->addPropertyConstraint('firstName', new Range(['min' => 3])); $expected->addPropertyConstraint('firstName', new All([new NotNull(), new Range(['min' => 3])])); $expected->addPropertyConstraint('firstName', new All(['constraints' => [new NotNull(), new Range(['min' => 3])]])); - $expected->addPropertyConstraint('firstName', new Collection(['fields' => [ + $expected->addPropertyConstraint('firstName', new Collection([ 'foo' => [new NotNull(), new Range(['min' => 3])], 'bar' => new Range(['min' => 5]), - ]])); + 'baz' => new Required([new Email()]), + 'qux' => new Optional([new NotBlank()]), + ], null, null, true)); $expected->addPropertyConstraint('firstName', new Choice([ 'message' => 'Must be one of %choices%', 'choices' => ['A', 'B'], ])); + $expected->addPropertyConstraint('firstName', new AtLeastOneOf([ + new NotNull(), + new Range(['min' => 3]), + ], null, null, 'foo', null, false)); + $expected->addPropertyConstraint('firstName', new Sequentially([ + new NotBlank(), + new Range(['min' => 5]), + ])); $expected->addPropertyConstraint('childA', new Valid()); $expected->addPropertyConstraint('childB', new Valid()); $expected->addGetterConstraint('lastName', new NotNull()); @@ -141,14 +157,24 @@ public function testLoadClassMetadataAndMerge(string $namespace) $expected->addPropertyConstraint('firstName', new Range(['min' => 3])); $expected->addPropertyConstraint('firstName', new All([new NotNull(), new Range(['min' => 3])])); $expected->addPropertyConstraint('firstName', new All(['constraints' => [new NotNull(), new Range(['min' => 3])]])); - $expected->addPropertyConstraint('firstName', new Collection(['fields' => [ + $expected->addPropertyConstraint('firstName', new Collection([ 'foo' => [new NotNull(), new Range(['min' => 3])], 'bar' => new Range(['min' => 5]), - ]])); + 'baz' => new Required([new Email()]), + 'qux' => new Optional([new NotBlank()]), + ], null, null, true)); $expected->addPropertyConstraint('firstName', new Choice([ 'message' => 'Must be one of %choices%', 'choices' => ['A', 'B'], ])); + $expected->addPropertyConstraint('firstName', new AtLeastOneOf([ + new NotNull(), + new Range(['min' => 3]), + ], null, null, 'foo', null, false)); + $expected->addPropertyConstraint('firstName', new Sequentially([ + new NotBlank(), + new Range(['min' => 5]), + ])); $expected->addPropertyConstraint('childA', new Valid()); $expected->addPropertyConstraint('childB', new Valid()); $expected->addGetterConstraint('lastName', new NotNull()); @@ -185,5 +211,9 @@ public function provideNamespaces(): iterable if (\PHP_VERSION_ID >= 80000) { yield 'attributes' => ['Symfony\Component\Validator\Tests\Fixtures\Attribute']; } + + if (\PHP_VERSION_ID >= 80100) { + yield 'nested_attributes' => ['Symfony\Component\Validator\Tests\Fixtures\NestedAttribute']; + } } } 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