diff --git a/reference/constraints/Traverse.rst b/reference/constraints/Traverse.rst index 2d51c7548af..134b6a9fad0 100644 --- a/reference/constraints/Traverse.rst +++ b/reference/constraints/Traverse.rst @@ -1,10 +1,10 @@ Traverse ======== -Objects do not validate nested objects by default unless explicitly using -this constraint. -If only specific nested objects should be validated by cascade, consider -using the :doc:`/reference/constraints/Valid` instead. +Object properties are only validated if they are accessible, either by being +public or having public accessor methods (e.g. a public getter). +If your object needs to be traversed to validate its data, you can use this +constraint. +----------------+-------------------------------------------------------------------------------------+ | Applies to | :ref:`class ` | @@ -17,19 +17,18 @@ using the :doc:`/reference/constraints/Valid` instead. Basic Usage ----------- -In the following example, create three classes ``Book``, ``Author`` and -``Editor`` that all have constraints on their properties. Furthermore, -``Book`` stores an ``Author`` and an ``Editor`` instance that must be -valid too. Instead of adding the ``Valid`` constraint to both fields, -configure the ``Traverse`` constraint on the ``Book`` class. +In the following example, create two classes ``BookCollection`` and ``Book`` +that all have constraints on their properties. .. configuration-block:: .. code-block:: php-annotations - // src/AppBundle/Entity/Book.php + // src/AppBundle/Entity/BookCollection.php namespace AppBundle\Entity; + use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -37,31 +36,66 @@ configure the ``Traverse`` constraint on the ``Book`` class. * @ORM\Entity * @Assert\Traverse */ - class Book + class BookCollection implements \IteratorAggregate { /** - * @var Author + * @var string * - * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Author") + * @ORM\Column + * + * @Assert\NotBlank */ - protected $author; + protected $name = ''; /** - * @var Editor + * @var Collection|Book[] * - * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Editor") + * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Book") */ - protected $editor; + protected $books; - // ... + // some other properties + + public function __construct() + { + $this->books = new ArrayCollection(); + } + + // ... setter for name, adder and remover for books + + // the name can be validated by calling the getter + public function getName(): string + { + return $this->name; + } + + /** + * @return \Generator|Book[] The books for a given author + */ + public function getBooksForAuthor(Author $author): iterable + { + foreach ($this->books as $book) { + if ($book->isAuthoredBy($author)) { + yield $book; + } + } + } + + // neither the method above nor any other specific getter + // could be used to validated all nested books; + // this object needs to be traversed to call the iterator + public function getIterator() + { + return $this->books->getIterator(); + } } .. code-block:: yaml # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\Book: + AppBundle\Entity\BookCollection: constraints: - - Symfony\Component\Validator\Constraints\Traverse: ~ + - Traverse: ~ .. code-block:: xml @@ -71,28 +105,105 @@ configure the ``Traverse`` constraint on the ``Book`` class. 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"> - - + + .. code-block:: php - // src/AppBundle/Entity/Book.php + // src/AppBundle/Entity/BookCollection.php namespace AppBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Mapping\ClassMetadata; - class Book + class BookCollection { + // ... + public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addConstraint(new Assert\Traverse()); } } +When the object implements ``\Traversable`` (like here with its child +``\IteratorAggregate``), its traversal strategy will implicitly be set and the +object will be iterated over without defining the constraint. +It's mostly useful to add it to be explicit or to disable the traversal using +the ``traverse`` option. +If a public getter exists to return the inner books collection like +``getBooks(): Collection``, the :doc:`/reference/constraints/Valid` constraint +can be used on the ``$books`` property instead. + Options ------- +The ``groups`` option is not available for this constraint. + + +``traverse`` +~~~~~~~~~~~~ + +**type**: ``bool`` **default**: ``true`` + +Instances of ``\Traversable`` are traversed by default, use this option to +disable validating: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/AppBundle/Entity/BookCollection.php + + // ... same as above + + /** + * ... + * @Assert\Traverse(false) + */ + class BookCollection implements \IteratorAggregate + { + // ... + } + + .. code-block:: yaml + + # src/AppBundle/Resources/config/validation.yml + AppBundle\Entity\BookCollection: + constraints: + - Traverse: false + + .. code-block:: xml + + + + + + + false + + + + .. code-block:: php + + // src/AppBundle/Entity/BookCollection.php + namespace AppBundle\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class BookCollection + { + // ... + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addConstraint(new Assert\Traverse(false)); + } + } + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Valid.rst b/reference/constraints/Valid.rst index 87c87faf51c..02650e0cb23 100644 --- a/reference/constraints/Valid.rst +++ b/reference/constraints/Valid.rst @@ -253,7 +253,7 @@ Options **type**: ``boolean`` **default**: ``true`` -If this constraint is applied to a ``Traversable``, then all containing values +If this constraint is applied to a ``\Traversable``, then all containing values will be validated if this option is set to ``true``. This option is ignored on arrays: Arrays are traversed in either case. Keys are not validated.