/**
  * Validates a Typed Data node in the validation tree.
  *
  * If no constraints are passed, the data is validated against the
  * constraints specified in its data definition. If the data is complex or a
  * list and no constraints are passed, the contained properties or list items
  * are validated recursively.
  *
  * @param \Drupal\Core\TypedData\TypedDataInterface $data
  *   The data to validated.
  * @param \Symfony\Component\Validator\Constraint[]|null $constraints
  *   (optional) If set, an array of constraints to validate.
  * @param bool $is_root_call
  *   (optional) Whether its the most upper call in the type data tree.
  *
  * @return $this
  */
 protected function validateNode(TypedDataInterface $data, $constraints = NULL, $is_root_call = FALSE)
 {
     $previous_value = $this->context->getValue();
     $previous_object = $this->context->getObject();
     $previous_metadata = $this->context->getMetadata();
     $previous_path = $this->context->getPropertyPath();
     $metadata = $this->metadataFactory->getMetadataFor($data);
     $cache_key = spl_object_hash($data);
     $property_path = $is_root_call ? '' : PropertyPath::append($previous_path, $data->getName());
     // Pass the canonical representation of the data as validated value to
     // constraint validators, such that they do not have to care about Typed
     // Data.
     $value = $this->typedDataManager->getCanonicalRepresentation($data);
     $this->context->setNode($value, $data, $metadata, $property_path);
     if (isset($constraints) || !$this->context->isGroupValidated($cache_key, Constraint::DEFAULT_GROUP)) {
         if (!isset($constraints)) {
             $this->context->markGroupAsValidated($cache_key, Constraint::DEFAULT_GROUP);
             $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP);
         }
         $this->validateConstraints($value, $cache_key, $constraints);
     }
     // If the data is a list or complex data, validate the contained list items
     // or properties. However, do not recurse if the data is empty.
     if (($data instanceof ListInterface || $data instanceof ComplexDataInterface) && !$data->isEmpty()) {
         foreach ($data as $name => $property) {
             $this->validateNode($property);
         }
     }
     $this->context->setNode($previous_value, $previous_object, $previous_metadata, $previous_path);
     return $this;
 }
 /**
  * Validates a class node.
  *
  * A class node is a combination of an object with a {@link ClassMetadataInterface}
  * instance. Each class node (conceptionally) has zero or more succeeding
  * property nodes:
  *
  *     (Article:class node)
  *                \
  *        ($title:property node)
  *
  * This method validates the passed objects against all constraints defined
  * at class level. It furthermore triggers the validation of each of the
  * class' properties against the constraints for that property.
  *
  * If the selected traversal strategy allows traversal, the object is
  * iterated and each nested object is validated against its own constraints.
  * The object is not traversed if traversal is disabled in the class
  * metadata.
  *
  * If the passed groups contain the group "Default", the validator will
  * check whether the "Default" group has been replaced by a group sequence
  * in the class metadata. If this is the case, the group sequence is
  * validated instead.
  *
  * @param object                    $object            The validated object
  * @param string                    $cacheKey          The key for caching
  *                                                     the validated object
  * @param ClassMetadataInterface    $metadata          The class metadata of
  *                                                     the object
  * @param string                    $propertyPath      The property path leading
  *                                                     to the object
  * @param string[]                  $groups            The groups in which the
  *                                                     object should be validated
  * @param string[]|null             $cascadedGroups    The groups in which
  *                                                     cascaded objects should
  *                                                     be validated
  * @param int                       $traversalStrategy The strategy used for
  *                                                     traversing the object
  * @param ExecutionContextInterface $context           The current execution context
  *
  * @throws UnsupportedMetadataException  If a property metadata does not
  *                                       implement {@link PropertyMetadataInterface}
  * @throws ConstraintDefinitionException If traversal was enabled but the
  *                                       object does not implement
  *                                       {@link \Traversable}
  *
  * @see TraversalStrategy
  */
 private function validateClassNode($object, $cacheKey, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
 {
     $context->setNode($object, $object, $metadata, $propertyPath);
     if (!$context->isObjectInitialized($cacheKey)) {
         foreach ($this->objectInitializers as $initializer) {
             $initializer->initialize($object);
         }
         $context->markObjectAsInitialized($cacheKey);
     }
     foreach ($groups as $key => $group) {
         // If the "Default" group is replaced by a group sequence, remember
         // to cascade the "Default" group when traversing the group
         // sequence
         $defaultOverridden = false;
         // Use the object hash for group sequences
         $groupHash = is_object($group) ? spl_object_hash($group) : $group;
         if ($context->isGroupValidated($cacheKey, $groupHash)) {
             // Skip this group when validating the properties and when
             // traversing the object
             unset($groups[$key]);
             continue;
         }
         $context->markGroupAsValidated($cacheKey, $groupHash);
         // Replace the "Default" group by the group sequence defined
         // for the class, if applicable.
         // This is done after checking the cache, so that
         // spl_object_hash() isn't called for this sequence and
         // "Default" is used instead in the cache. This is useful
         // if the getters below return different group sequences in
         // every call.
         if (Constraint::DEFAULT_GROUP === $group) {
             if ($metadata->hasGroupSequence()) {
                 // The group sequence is statically defined for the class
                 $group = $metadata->getGroupSequence();
                 $defaultOverridden = true;
             } elseif ($metadata->isGroupSequenceProvider()) {
                 // The group sequence is dynamically obtained from the validated
                 // object
                 /** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */
                 $group = $object->getGroupSequence();
                 $defaultOverridden = true;
                 if (!$group instanceof GroupSequence) {
                     $group = new GroupSequence($group);
                 }
             }
         }
         // If the groups (=[<G1,G2>,G3,G4]) contain a group sequence
         // (=<G1,G2>), then call validateClassNode() with each entry of the
         // group sequence and abort if necessary (G1, G2)
         if ($group instanceof GroupSequence) {
             $this->stepThroughGroupSequence($object, $object, $cacheKey, $metadata, $propertyPath, $traversalStrategy, $group, $defaultOverridden ? Constraint::DEFAULT_GROUP : null, $context);
             // Skip the group sequence when validating properties, because
             // stepThroughGroupSequence() already validates the properties
             unset($groups[$key]);
             continue;
         }
         $this->validateInGroup($object, $cacheKey, $metadata, $group, $context);
     }
     // If no more groups should be validated for the property nodes,
     // we can safely quit
     if (0 === count($groups)) {
         return;
     }
     // Validate all properties against their constraints
     foreach ($metadata->getConstrainedProperties() as $propertyName) {
         // If constraints are defined both on the getter of a property as
         // well as on the property itself, then getPropertyMetadata()
         // returns two metadata objects, not just one
         foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
             if (!$propertyMetadata instanceof PropertyMetadataInterface) {
                 throw new UnsupportedMetadataException(sprintf('The property metadata instances should implement ' . '"Symfony\\Component\\Validator\\Mapping\\PropertyMetadataInterface", ' . 'got: "%s".', is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)));
             }
             $propertyValue = $propertyMetadata->getPropertyValue($object);
             $this->validateGenericNode($propertyValue, $object, $cacheKey . ':' . $propertyName, $propertyMetadata, $propertyPath ? $propertyPath . '.' . $propertyName : $propertyName, $groups, $cascadedGroups, TraversalStrategy::IMPLICIT, $context);
         }
     }
     // If no specific traversal strategy was requested when this method
     // was called, use the traversal strategy of the class' metadata
     if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
         // Keep the STOP_RECURSION flag, if it was set
         $traversalStrategy = $metadata->getTraversalStrategy() | $traversalStrategy & TraversalStrategy::STOP_RECURSION;
     }
     // Traverse only if IMPLICIT or TRAVERSE
     if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
         return;
     }
     // If IMPLICIT, stop unless we deal with a Traversable
     if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) {
         return;
     }
     // If TRAVERSE, fail if we have no Traversable
     if (!$object instanceof \Traversable) {
         // Must throw a ConstraintDefinitionException for backwards
         // compatibility reasons with Symfony < 2.5
         throw new ConstraintDefinitionException(sprintf('Traversal was enabled for "%s", but this class ' . 'does not implement "\\Traversable".', get_class($object)));
     }
     $this->validateEachObjectIn($object, $propertyPath, $groups, $traversalStrategy & TraversalStrategy::STOP_RECURSION, $context);
 }