/**
  * 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);
 }