4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Validator\Mapping;
14 use Symfony\Component\Validator\Constraint;
15 use Symfony\Component\Validator\Constraints\GroupSequence;
16 use Symfony\Component\Validator\Constraints\Traverse;
17 use Symfony\Component\Validator\Constraints\Valid;
18 use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
19 use Symfony\Component\Validator\Exception\GroupDefinitionException;
22 * Default implementation of {@link ClassMetadataInterface}.
24 * This class supports serialization and cloning.
26 * @author Bernhard Schussek <bschussek@gmail.com>
27 * @author Fabien Potencier <fabien@symfony.com>
29 class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
34 * @internal This property is public in order to reduce the size of the
35 * class' serialized representation. Do not access it. Use
36 * {@link getClassName()} instead.
43 * @internal This property is public in order to reduce the size of the
44 * class' serialized representation. Do not access it. Use
45 * {@link getDefaultGroup()} instead.
50 * @var MemberMetadata[][]
52 * @internal This property is public in order to reduce the size of the
53 * class' serialized representation. Do not access it. Use
54 * {@link getPropertyMetadata()} instead.
56 public $members = array();
59 * @var PropertyMetadata[]
61 * @internal This property is public in order to reduce the size of the
62 * class' serialized representation. Do not access it. Use
63 * {@link getPropertyMetadata()} instead.
65 public $properties = array();
68 * @var GetterMetadata[]
70 * @internal This property is public in order to reduce the size of the
71 * class' serialized representation. Do not access it. Use
72 * {@link getPropertyMetadata()} instead.
74 public $getters = array();
79 * @internal This property is public in order to reduce the size of the
80 * class' serialized representation. Do not access it. Use
81 * {@link getGroupSequence()} instead.
83 public $groupSequence = array();
88 * @internal This property is public in order to reduce the size of the
89 * class' serialized representation. Do not access it. Use
90 * {@link isGroupSequenceProvider()} instead.
92 public $groupSequenceProvider = false;
95 * The strategy for traversing traversable objects.
97 * By default, only instances of {@link \Traversable} are traversed.
101 * @internal This property is public in order to reduce the size of the
102 * class' serialized representation. Do not access it. Use
103 * {@link getTraversalStrategy()} instead.
105 public $traversalStrategy = TraversalStrategy::IMPLICIT;
108 * @var \ReflectionClass
113 * Constructs a metadata for the given class.
115 * @param string $class
117 public function __construct($class)
119 $this->name = $class;
120 // class name without namespace
121 if (false !== $nsSep = strrpos($class, '\\')) {
122 $this->defaultGroup = substr($class, $nsSep + 1);
124 $this->defaultGroup = $class;
131 public function __sleep()
133 $parentProperties = parent::__sleep();
135 // Don't store the cascading strategy. Classes never cascade.
136 unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]);
138 return array_merge($parentProperties, array(
141 'groupSequenceProvider',
152 public function getClassName()
158 * Returns the name of the default group for this class.
160 * For each class, the group "Default" is an alias for the group
161 * "<ClassName>", where <ClassName> is the non-namespaced name of the
162 * class. All constraints implicitly or explicitly assigned to group
163 * "Default" belong to both of these groups, unless the class defines
166 * If a class defines a group sequence, validating the class in "Default"
167 * will validate the group sequence. The constraints assigned to "Default"
168 * can still be validated by validating the class in "<ClassName>".
170 * @return string The name of the default group
172 public function getDefaultGroup()
174 return $this->defaultGroup;
180 public function addConstraint(Constraint $constraint)
182 if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) {
183 throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
186 if ($constraint instanceof Valid) {
187 throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
190 if ($constraint instanceof Traverse) {
191 if ($constraint->traverse) {
192 // If traverse is true, traversal should be explicitly enabled
193 $this->traversalStrategy = TraversalStrategy::TRAVERSE;
195 // If traverse is false, traversal should be explicitly disabled
196 $this->traversalStrategy = TraversalStrategy::NONE;
199 // The constraint is not added
203 $constraint->addImplicitGroupName($this->getDefaultGroup());
205 parent::addConstraint($constraint);
211 * Adds a constraint to the given property.
213 * @param string $property The name of the property
214 * @param Constraint $constraint The constraint
218 public function addPropertyConstraint($property, Constraint $constraint)
220 if (!isset($this->properties[$property])) {
221 $this->properties[$property] = new PropertyMetadata($this->getClassName(), $property);
223 $this->addPropertyMetadata($this->properties[$property]);
226 $constraint->addImplicitGroupName($this->getDefaultGroup());
228 $this->properties[$property]->addConstraint($constraint);
234 * @param string $property
235 * @param Constraint[] $constraints
239 public function addPropertyConstraints($property, array $constraints)
241 foreach ($constraints as $constraint) {
242 $this->addPropertyConstraint($property, $constraint);
249 * Adds a constraint to the getter of the given property.
251 * The name of the getter is assumed to be the name of the property with an
252 * uppercased first letter and either the prefix "get" or "is".
254 * @param string $property The name of the property
255 * @param Constraint $constraint The constraint
259 public function addGetterConstraint($property, Constraint $constraint)
261 if (!isset($this->getters[$property])) {
262 $this->getters[$property] = new GetterMetadata($this->getClassName(), $property);
264 $this->addPropertyMetadata($this->getters[$property]);
267 $constraint->addImplicitGroupName($this->getDefaultGroup());
269 $this->getters[$property]->addConstraint($constraint);
275 * Adds a constraint to the getter of the given property.
277 * @param string $property The name of the property
278 * @param string $method The name of the getter method
279 * @param Constraint $constraint The constraint
283 public function addGetterMethodConstraint($property, $method, Constraint $constraint)
285 if (!isset($this->getters[$property])) {
286 $this->getters[$property] = new GetterMetadata($this->getClassName(), $property, $method);
288 $this->addPropertyMetadata($this->getters[$property]);
291 $constraint->addImplicitGroupName($this->getDefaultGroup());
293 $this->getters[$property]->addConstraint($constraint);
299 * @param string $property
300 * @param Constraint[] $constraints
304 public function addGetterConstraints($property, array $constraints)
306 foreach ($constraints as $constraint) {
307 $this->addGetterConstraint($property, $constraint);
314 * @param string $property
315 * @param string $method
316 * @param Constraint[] $constraints
320 public function addGetterMethodConstraints($property, $method, array $constraints)
322 foreach ($constraints as $constraint) {
323 $this->addGetterMethodConstraint($property, $method, $constraint);
330 * Merges the constraints of the given metadata into this object.
332 public function mergeConstraints(self $source)
334 if ($source->isGroupSequenceProvider()) {
335 $this->setGroupSequenceProvider(true);
338 foreach ($source->getConstraints() as $constraint) {
339 $this->addConstraint(clone $constraint);
342 foreach ($source->getConstrainedProperties() as $property) {
343 foreach ($source->getPropertyMetadata($property) as $member) {
344 $member = clone $member;
346 foreach ($member->getConstraints() as $constraint) {
347 if (\in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) {
348 $member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint;
351 $constraint->addImplicitGroupName($this->getDefaultGroup());
354 $this->addPropertyMetadata($member);
356 if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) {
357 $property = $member->getPropertyName();
359 if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) {
360 $this->properties[$property] = $member;
361 } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) {
362 $this->getters[$property] = $member;
372 public function hasPropertyMetadata($property)
374 return array_key_exists($property, $this->members);
380 public function getPropertyMetadata($property)
382 if (!isset($this->members[$property])) {
386 return $this->members[$property];
392 public function getConstrainedProperties()
394 return array_keys($this->members);
398 * Sets the default group sequence for this class.
400 * @param string[]|GroupSequence $groupSequence An array of group names
404 * @throws GroupDefinitionException
406 public function setGroupSequence($groupSequence)
408 if ($this->isGroupSequenceProvider()) {
409 throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');
412 if (\is_array($groupSequence)) {
413 $groupSequence = new GroupSequence($groupSequence);
416 if (\in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) {
417 throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP));
420 if (!\in_array($this->getDefaultGroup(), $groupSequence->groups, true)) {
421 throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence', $this->getDefaultGroup()));
424 $this->groupSequence = $groupSequence;
432 public function hasGroupSequence()
434 return $this->groupSequence && \count($this->groupSequence->groups) > 0;
440 public function getGroupSequence()
442 return $this->groupSequence;
446 * Returns a ReflectionClass instance for this class.
448 * @return \ReflectionClass
450 public function getReflectionClass()
452 if (!$this->reflClass) {
453 $this->reflClass = new \ReflectionClass($this->getClassName());
456 return $this->reflClass;
460 * Sets whether a group sequence provider should be used.
462 * @param bool $active
464 * @throws GroupDefinitionException
466 public function setGroupSequenceProvider($active)
468 if ($this->hasGroupSequence()) {
469 throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence');
472 if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) {
473 throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface', $this->name));
476 $this->groupSequenceProvider = $active;
482 public function isGroupSequenceProvider()
484 return $this->groupSequenceProvider;
488 * Class nodes are never cascaded.
492 public function getCascadingStrategy()
494 return CascadingStrategy::NONE;
497 private function addPropertyMetadata(PropertyMetadataInterface $metadata)
499 $property = $metadata->getPropertyName();
501 $this->members[$property][] = $metadata;