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