Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Validator\Mapping; Chris@0: Chris@0: use Symfony\Component\Validator\Constraint; Chris@0: use Symfony\Component\Validator\Constraints\GroupSequence; Chris@0: use Symfony\Component\Validator\Constraints\Traverse; Chris@0: use Symfony\Component\Validator\Constraints\Valid; Chris@0: use Symfony\Component\Validator\Exception\ConstraintDefinitionException; Chris@0: use Symfony\Component\Validator\Exception\GroupDefinitionException; Chris@0: Chris@0: /** Chris@0: * Default implementation of {@link ClassMetadataInterface}. Chris@0: * Chris@0: * This class supports serialization and cloning. Chris@0: * Chris@0: * @author Bernhard Schussek Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class ClassMetadata extends GenericMetadata implements ClassMetadataInterface Chris@0: { Chris@0: /** Chris@0: * @var string Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getClassName()} instead. Chris@0: */ Chris@0: public $name; Chris@0: Chris@0: /** Chris@0: * @var string Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getDefaultGroup()} instead. Chris@0: */ Chris@0: public $defaultGroup; Chris@0: Chris@0: /** Chris@14: * @var MemberMetadata[][] Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getPropertyMetadata()} instead. Chris@0: */ Chris@17: public $members = []; Chris@0: Chris@0: /** Chris@0: * @var PropertyMetadata[] Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getPropertyMetadata()} instead. Chris@0: */ Chris@17: public $properties = []; Chris@0: Chris@0: /** Chris@0: * @var GetterMetadata[] Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getPropertyMetadata()} instead. Chris@0: */ Chris@17: public $getters = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getGroupSequence()} instead. Chris@0: */ Chris@17: public $groupSequence = []; Chris@0: Chris@0: /** Chris@0: * @var bool Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link isGroupSequenceProvider()} instead. Chris@0: */ Chris@0: public $groupSequenceProvider = false; Chris@0: Chris@0: /** Chris@0: * The strategy for traversing traversable objects. Chris@0: * Chris@0: * By default, only instances of {@link \Traversable} are traversed. Chris@0: * Chris@0: * @var int Chris@0: * Chris@0: * @internal This property is public in order to reduce the size of the Chris@0: * class' serialized representation. Do not access it. Use Chris@0: * {@link getTraversalStrategy()} instead. Chris@0: */ Chris@0: public $traversalStrategy = TraversalStrategy::IMPLICIT; Chris@0: Chris@0: /** Chris@0: * @var \ReflectionClass Chris@0: */ Chris@0: private $reflClass; Chris@0: Chris@0: /** Chris@0: * Constructs a metadata for the given class. Chris@0: * Chris@0: * @param string $class Chris@0: */ Chris@0: public function __construct($class) Chris@0: { Chris@0: $this->name = $class; Chris@0: // class name without namespace Chris@0: if (false !== $nsSep = strrpos($class, '\\')) { Chris@0: $this->defaultGroup = substr($class, $nsSep + 1); Chris@0: } else { Chris@0: $this->defaultGroup = $class; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function __sleep() Chris@0: { Chris@0: $parentProperties = parent::__sleep(); Chris@0: Chris@0: // Don't store the cascading strategy. Classes never cascade. Chris@0: unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]); Chris@0: Chris@17: return array_merge($parentProperties, [ Chris@0: 'getters', Chris@0: 'groupSequence', Chris@0: 'groupSequenceProvider', Chris@0: 'members', Chris@0: 'name', Chris@0: 'properties', Chris@0: 'defaultGroup', Chris@17: ]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getClassName() Chris@0: { Chris@0: return $this->name; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the name of the default group for this class. Chris@0: * Chris@0: * For each class, the group "Default" is an alias for the group Chris@0: * "", where is the non-namespaced name of the Chris@0: * class. All constraints implicitly or explicitly assigned to group Chris@0: * "Default" belong to both of these groups, unless the class defines Chris@0: * a group sequence. Chris@0: * Chris@0: * If a class defines a group sequence, validating the class in "Default" Chris@0: * will validate the group sequence. The constraints assigned to "Default" Chris@0: * can still be validated by validating the class in "". Chris@0: * Chris@0: * @return string The name of the default group Chris@0: */ Chris@0: public function getDefaultGroup() Chris@0: { Chris@0: return $this->defaultGroup; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addConstraint(Constraint $constraint) Chris@0: { Chris@17: if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) { Chris@17: throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint))); Chris@0: } Chris@0: Chris@0: if ($constraint instanceof Valid) { Chris@17: throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint))); Chris@0: } Chris@0: Chris@0: if ($constraint instanceof Traverse) { Chris@0: if ($constraint->traverse) { Chris@0: // If traverse is true, traversal should be explicitly enabled Chris@0: $this->traversalStrategy = TraversalStrategy::TRAVERSE; Chris@0: } else { Chris@0: // If traverse is false, traversal should be explicitly disabled Chris@0: $this->traversalStrategy = TraversalStrategy::NONE; Chris@0: } Chris@0: Chris@0: // The constraint is not added Chris@0: return $this; Chris@0: } Chris@0: Chris@0: $constraint->addImplicitGroupName($this->getDefaultGroup()); Chris@0: Chris@0: parent::addConstraint($constraint); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a constraint to the given property. Chris@0: * Chris@0: * @param string $property The name of the property Chris@0: * @param Constraint $constraint The constraint Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function addPropertyConstraint($property, Constraint $constraint) Chris@0: { Chris@0: if (!isset($this->properties[$property])) { Chris@0: $this->properties[$property] = new PropertyMetadata($this->getClassName(), $property); Chris@0: Chris@0: $this->addPropertyMetadata($this->properties[$property]); Chris@0: } Chris@0: Chris@0: $constraint->addImplicitGroupName($this->getDefaultGroup()); Chris@0: Chris@0: $this->properties[$property]->addConstraint($constraint); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $property Chris@0: * @param Constraint[] $constraints Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function addPropertyConstraints($property, array $constraints) Chris@0: { Chris@0: foreach ($constraints as $constraint) { Chris@0: $this->addPropertyConstraint($property, $constraint); Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a constraint to the getter of the given property. Chris@0: * Chris@0: * The name of the getter is assumed to be the name of the property with an Chris@0: * uppercased first letter and either the prefix "get" or "is". Chris@0: * Chris@0: * @param string $property The name of the property Chris@0: * @param Constraint $constraint The constraint Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function addGetterConstraint($property, Constraint $constraint) Chris@0: { Chris@0: if (!isset($this->getters[$property])) { Chris@0: $this->getters[$property] = new GetterMetadata($this->getClassName(), $property); Chris@0: Chris@0: $this->addPropertyMetadata($this->getters[$property]); Chris@0: } Chris@0: Chris@0: $constraint->addImplicitGroupName($this->getDefaultGroup()); Chris@0: Chris@0: $this->getters[$property]->addConstraint($constraint); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a constraint to the getter of the given property. Chris@0: * Chris@0: * @param string $property The name of the property Chris@0: * @param string $method The name of the getter method Chris@0: * @param Constraint $constraint The constraint Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function addGetterMethodConstraint($property, $method, Constraint $constraint) Chris@0: { Chris@0: if (!isset($this->getters[$property])) { Chris@0: $this->getters[$property] = new GetterMetadata($this->getClassName(), $property, $method); Chris@0: Chris@0: $this->addPropertyMetadata($this->getters[$property]); Chris@0: } Chris@0: Chris@0: $constraint->addImplicitGroupName($this->getDefaultGroup()); Chris@0: Chris@0: $this->getters[$property]->addConstraint($constraint); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $property Chris@0: * @param Constraint[] $constraints Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function addGetterConstraints($property, array $constraints) Chris@0: { Chris@0: foreach ($constraints as $constraint) { Chris@0: $this->addGetterConstraint($property, $constraint); Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $property Chris@0: * @param string $method Chris@0: * @param Constraint[] $constraints Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function addGetterMethodConstraints($property, $method, array $constraints) Chris@0: { Chris@0: foreach ($constraints as $constraint) { Chris@0: $this->addGetterMethodConstraint($property, $method, $constraint); Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Merges the constraints of the given metadata into this object. Chris@0: */ Chris@16: public function mergeConstraints(self $source) Chris@0: { Chris@14: if ($source->isGroupSequenceProvider()) { Chris@14: $this->setGroupSequenceProvider(true); Chris@14: } Chris@14: Chris@0: foreach ($source->getConstraints() as $constraint) { Chris@0: $this->addConstraint(clone $constraint); Chris@0: } Chris@0: Chris@0: foreach ($source->getConstrainedProperties() as $property) { Chris@0: foreach ($source->getPropertyMetadata($property) as $member) { Chris@0: $member = clone $member; Chris@0: Chris@0: foreach ($member->getConstraints() as $constraint) { Chris@17: if (\in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) { Chris@0: $member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint; Chris@0: } Chris@0: Chris@0: $constraint->addImplicitGroupName($this->getDefaultGroup()); Chris@0: } Chris@0: Chris@0: $this->addPropertyMetadata($member); Chris@0: Chris@0: if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) { Chris@0: $property = $member->getPropertyName(); Chris@0: Chris@0: if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) { Chris@0: $this->properties[$property] = $member; Chris@0: } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) { Chris@0: $this->getters[$property] = $member; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasPropertyMetadata($property) Chris@0: { Chris@18: return \array_key_exists($property, $this->members); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getPropertyMetadata($property) Chris@0: { Chris@0: if (!isset($this->members[$property])) { Chris@17: return []; Chris@0: } Chris@0: Chris@0: return $this->members[$property]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getConstrainedProperties() Chris@0: { Chris@0: return array_keys($this->members); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the default group sequence for this class. Chris@0: * Chris@17: * @param string[]|GroupSequence $groupSequence An array of group names Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @throws GroupDefinitionException Chris@0: */ Chris@0: public function setGroupSequence($groupSequence) Chris@0: { Chris@0: if ($this->isGroupSequenceProvider()) { Chris@0: throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider'); Chris@0: } Chris@0: Chris@17: if (\is_array($groupSequence)) { Chris@0: $groupSequence = new GroupSequence($groupSequence); Chris@0: } Chris@0: Chris@17: if (\in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) { Chris@0: throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP)); Chris@0: } Chris@0: Chris@17: if (!\in_array($this->getDefaultGroup(), $groupSequence->groups, true)) { Chris@0: throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence', $this->getDefaultGroup())); Chris@0: } Chris@0: Chris@0: $this->groupSequence = $groupSequence; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasGroupSequence() Chris@0: { Chris@17: return $this->groupSequence && \count($this->groupSequence->groups) > 0; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getGroupSequence() Chris@0: { Chris@0: return $this->groupSequence; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a ReflectionClass instance for this class. Chris@0: * Chris@0: * @return \ReflectionClass Chris@0: */ Chris@0: public function getReflectionClass() Chris@0: { Chris@0: if (!$this->reflClass) { Chris@0: $this->reflClass = new \ReflectionClass($this->getClassName()); Chris@0: } Chris@0: Chris@0: return $this->reflClass; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets whether a group sequence provider should be used. Chris@0: * Chris@0: * @param bool $active Chris@0: * Chris@0: * @throws GroupDefinitionException Chris@0: */ Chris@0: public function setGroupSequenceProvider($active) Chris@0: { Chris@0: if ($this->hasGroupSequence()) { Chris@0: throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence'); Chris@0: } Chris@0: Chris@0: if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) { Chris@0: throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface', $this->name)); Chris@0: } Chris@0: Chris@0: $this->groupSequenceProvider = $active; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isGroupSequenceProvider() Chris@0: { Chris@0: return $this->groupSequenceProvider; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Class nodes are never cascaded. Chris@0: * Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCascadingStrategy() Chris@0: { Chris@0: return CascadingStrategy::NONE; Chris@0: } Chris@0: Chris@0: private function addPropertyMetadata(PropertyMetadataInterface $metadata) Chris@0: { Chris@0: $property = $metadata->getPropertyName(); Chris@0: Chris@0: $this->members[$property][] = $metadata; Chris@0: } Chris@0: }