ClassMetadata.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Validator\Mapping;
  11. use Symfony\Component\Validator\Constraint;
  12. use Symfony\Component\Validator\Constraints\GroupSequence;
  13. use Symfony\Component\Validator\Constraints\Traverse;
  14. use Symfony\Component\Validator\Constraints\Valid;
  15. use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
  16. use Symfony\Component\Validator\Exception\GroupDefinitionException;
  17. use Symfony\Component\Validator\ValidationVisitorInterface;
  18. /**
  19. * Default implementation of {@link ClassMetadataInterface}.
  20. *
  21. * This class supports serialization and cloning.
  22. *
  23. * @author Bernhard Schussek <bschussek@gmail.com>
  24. * @author Fabien Potencier <fabien@symfony.com>
  25. */
  26. class ClassMetadata extends ElementMetadata implements ClassMetadataInterface
  27. {
  28. /**
  29. * @var string
  30. *
  31. * @internal This property is public in order to reduce the size of the
  32. * class' serialized representation. Do not access it. Use
  33. * {@link getClassName()} instead.
  34. */
  35. public $name;
  36. /**
  37. * @var string
  38. *
  39. * @internal This property is public in order to reduce the size of the
  40. * class' serialized representation. Do not access it. Use
  41. * {@link getDefaultGroup()} instead.
  42. */
  43. public $defaultGroup;
  44. /**
  45. * @var MemberMetadata[]
  46. *
  47. * @internal This property is public in order to reduce the size of the
  48. * class' serialized representation. Do not access it. Use
  49. * {@link getPropertyMetadata()} instead.
  50. */
  51. public $members = array();
  52. /**
  53. * @var PropertyMetadata[]
  54. *
  55. * @internal This property is public in order to reduce the size of the
  56. * class' serialized representation. Do not access it. Use
  57. * {@link getPropertyMetadata()} instead.
  58. */
  59. public $properties = array();
  60. /**
  61. * @var GetterMetadata[]
  62. *
  63. * @internal This property is public in order to reduce the size of the
  64. * class' serialized representation. Do not access it. Use
  65. * {@link getPropertyMetadata()} instead.
  66. */
  67. public $getters = array();
  68. /**
  69. * @var array
  70. *
  71. * @internal This property is public in order to reduce the size of the
  72. * class' serialized representation. Do not access it. Use
  73. * {@link getGroupSequence()} instead.
  74. */
  75. public $groupSequence = array();
  76. /**
  77. * @var bool
  78. *
  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 isGroupSequenceProvider()} instead.
  82. */
  83. public $groupSequenceProvider = false;
  84. /**
  85. * The strategy for traversing traversable objects.
  86. *
  87. * By default, only instances of {@link \Traversable} are traversed.
  88. *
  89. * @var int
  90. *
  91. * @internal This property is public in order to reduce the size of the
  92. * class' serialized representation. Do not access it. Use
  93. * {@link getTraversalStrategy()} instead.
  94. */
  95. public $traversalStrategy = TraversalStrategy::IMPLICIT;
  96. /**
  97. * @var \ReflectionClass
  98. */
  99. private $reflClass;
  100. /**
  101. * Constructs a metadata for the given class.
  102. *
  103. * @param string $class
  104. */
  105. public function __construct($class)
  106. {
  107. $this->name = $class;
  108. // class name without namespace
  109. if (false !== $nsSep = strrpos($class, '\\')) {
  110. $this->defaultGroup = substr($class, $nsSep + 1);
  111. } else {
  112. $this->defaultGroup = $class;
  113. }
  114. }
  115. /**
  116. * {@inheritdoc}
  117. *
  118. * @deprecated since version 2.5, to be removed in 3.0.
  119. */
  120. public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath, $propagatedGroup = null)
  121. {
  122. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);
  123. if (null === $propagatedGroup && Constraint::DEFAULT_GROUP === $group
  124. && ($this->hasGroupSequence() || $this->isGroupSequenceProvider())) {
  125. if ($this->hasGroupSequence()) {
  126. $groups = $this->getGroupSequence()->groups;
  127. } else {
  128. $groups = $value->getGroupSequence();
  129. }
  130. foreach ($groups as $group) {
  131. $this->accept($visitor, $value, $group, $propertyPath, Constraint::DEFAULT_GROUP);
  132. if (count($visitor->getViolations()) > 0) {
  133. break;
  134. }
  135. }
  136. return;
  137. }
  138. $visitor->visit($this, $value, $group, $propertyPath);
  139. if (null !== $value) {
  140. $pathPrefix = empty($propertyPath) ? '' : $propertyPath.'.';
  141. foreach ($this->getConstrainedProperties() as $property) {
  142. foreach ($this->getPropertyMetadata($property) as $member) {
  143. $member->accept($visitor, $member->getPropertyValue($value), $group, $pathPrefix.$property, $propagatedGroup);
  144. }
  145. }
  146. }
  147. }
  148. /**
  149. * {@inheritdoc}
  150. */
  151. public function __sleep()
  152. {
  153. $parentProperties = parent::__sleep();
  154. // Don't store the cascading strategy. Classes never cascade.
  155. unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]);
  156. return array_merge($parentProperties, array(
  157. 'getters',
  158. 'groupSequence',
  159. 'groupSequenceProvider',
  160. 'members',
  161. 'name',
  162. 'properties',
  163. 'defaultGroup',
  164. ));
  165. }
  166. /**
  167. * {@inheritdoc}
  168. */
  169. public function getClassName()
  170. {
  171. return $this->name;
  172. }
  173. /**
  174. * Returns the name of the default group for this class.
  175. *
  176. * For each class, the group "Default" is an alias for the group
  177. * "<ClassName>", where <ClassName> is the non-namespaced name of the
  178. * class. All constraints implicitly or explicitly assigned to group
  179. * "Default" belong to both of these groups, unless the class defines
  180. * a group sequence.
  181. *
  182. * If a class defines a group sequence, validating the class in "Default"
  183. * will validate the group sequence. The constraints assigned to "Default"
  184. * can still be validated by validating the class in "<ClassName>".
  185. *
  186. * @return string The name of the default group
  187. */
  188. public function getDefaultGroup()
  189. {
  190. return $this->defaultGroup;
  191. }
  192. /**
  193. * {@inheritdoc}
  194. */
  195. public function addConstraint(Constraint $constraint)
  196. {
  197. if (!in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) {
  198. throw new ConstraintDefinitionException(sprintf(
  199. 'The constraint "%s" cannot be put on classes.',
  200. get_class($constraint)
  201. ));
  202. }
  203. if ($constraint instanceof Valid) {
  204. throw new ConstraintDefinitionException(sprintf(
  205. 'The constraint "%s" cannot be put on classes.',
  206. get_class($constraint)
  207. ));
  208. }
  209. if ($constraint instanceof Traverse) {
  210. if ($constraint->traverse) {
  211. // If traverse is true, traversal should be explicitly enabled
  212. $this->traversalStrategy = TraversalStrategy::TRAVERSE;
  213. } else {
  214. // If traverse is false, traversal should be explicitly disabled
  215. $this->traversalStrategy = TraversalStrategy::NONE;
  216. }
  217. // The constraint is not added
  218. return $this;
  219. }
  220. $constraint->addImplicitGroupName($this->getDefaultGroup());
  221. parent::addConstraint($constraint);
  222. return $this;
  223. }
  224. /**
  225. * Adds a constraint to the given property.
  226. *
  227. * @param string $property The name of the property
  228. * @param Constraint $constraint The constraint
  229. *
  230. * @return $this
  231. */
  232. public function addPropertyConstraint($property, Constraint $constraint)
  233. {
  234. if (!isset($this->properties[$property])) {
  235. $this->properties[$property] = new PropertyMetadata($this->getClassName(), $property);
  236. $this->addPropertyMetadata($this->properties[$property]);
  237. }
  238. $constraint->addImplicitGroupName($this->getDefaultGroup());
  239. $this->properties[$property]->addConstraint($constraint);
  240. return $this;
  241. }
  242. /**
  243. * @param string $property
  244. * @param Constraint[] $constraints
  245. *
  246. * @return $this
  247. */
  248. public function addPropertyConstraints($property, array $constraints)
  249. {
  250. foreach ($constraints as $constraint) {
  251. $this->addPropertyConstraint($property, $constraint);
  252. }
  253. return $this;
  254. }
  255. /**
  256. * Adds a constraint to the getter of the given property.
  257. *
  258. * The name of the getter is assumed to be the name of the property with an
  259. * uppercased first letter and either the prefix "get" or "is".
  260. *
  261. * @param string $property The name of the property
  262. * @param Constraint $constraint The constraint
  263. * @param string|null $method The method that is called to retrieve the value being validated (null for auto-detection)
  264. *
  265. * @return $this
  266. */
  267. public function addGetterConstraint($property, Constraint $constraint)
  268. {
  269. if (!isset($this->getters[$property])) {
  270. $this->getters[$property] = new GetterMetadata($this->getClassName(), $property);
  271. $this->addPropertyMetadata($this->getters[$property]);
  272. }
  273. $constraint->addImplicitGroupName($this->getDefaultGroup());
  274. $this->getters[$property]->addConstraint($constraint);
  275. return $this;
  276. }
  277. /**
  278. * Adds a constraint to the getter of the given property.
  279. *
  280. * @param string $property The name of the property
  281. * @param string $method The name of the getter method
  282. * @param Constraint $constraint The constraint
  283. *
  284. * @return $this
  285. */
  286. public function addGetterMethodConstraint($property, $method, Constraint $constraint)
  287. {
  288. if (!isset($this->getters[$property])) {
  289. $this->getters[$property] = new GetterMetadata($this->getClassName(), $property, $method);
  290. $this->addPropertyMetadata($this->getters[$property]);
  291. }
  292. $constraint->addImplicitGroupName($this->getDefaultGroup());
  293. $this->getters[$property]->addConstraint($constraint);
  294. return $this;
  295. }
  296. /**
  297. * @param string $property
  298. * @param Constraint[] $constraints
  299. *
  300. * @return $this
  301. */
  302. public function addGetterConstraints($property, array $constraints)
  303. {
  304. foreach ($constraints as $constraint) {
  305. $this->addGetterConstraint($property, $constraint);
  306. }
  307. return $this;
  308. }
  309. /**
  310. * @param string $property
  311. * @param string $method
  312. * @param Constraint[] $constraints
  313. *
  314. * @return $this
  315. */
  316. public function addGetterMethodConstraints($property, $method, array $constraints)
  317. {
  318. foreach ($constraints as $constraint) {
  319. $this->addGetterMethodConstraint($property, $method, $constraint);
  320. }
  321. return $this;
  322. }
  323. /**
  324. * Merges the constraints of the given metadata into this object.
  325. *
  326. * @param ClassMetadata $source The source metadata
  327. */
  328. public function mergeConstraints(ClassMetadata $source)
  329. {
  330. foreach ($source->getConstraints() as $constraint) {
  331. $this->addConstraint(clone $constraint);
  332. }
  333. foreach ($source->getConstrainedProperties() as $property) {
  334. foreach ($source->getPropertyMetadata($property) as $member) {
  335. $member = clone $member;
  336. foreach ($member->getConstraints() as $constraint) {
  337. if (in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) {
  338. $member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint;
  339. }
  340. $constraint->addImplicitGroupName($this->getDefaultGroup());
  341. }
  342. $this->addPropertyMetadata($member);
  343. if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) {
  344. $property = $member->getPropertyName();
  345. if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) {
  346. $this->properties[$property] = $member;
  347. } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) {
  348. $this->getters[$property] = $member;
  349. }
  350. }
  351. }
  352. }
  353. }
  354. /**
  355. * Adds a member metadata.
  356. *
  357. * @param MemberMetadata $metadata
  358. *
  359. * @deprecated since version 2.6, to be removed in 3.0.
  360. */
  361. protected function addMemberMetadata(MemberMetadata $metadata)
  362. {
  363. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the addPropertyMetadata() method instead.', E_USER_DEPRECATED);
  364. $this->addPropertyMetadata($metadata);
  365. }
  366. /**
  367. * Returns true if metadatas of members is present for the given property.
  368. *
  369. * @param string $property The name of the property
  370. *
  371. * @return bool
  372. *
  373. * @deprecated since version 2.6, to be removed in 3.0. Use {@link hasPropertyMetadata} instead.
  374. */
  375. public function hasMemberMetadatas($property)
  376. {
  377. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the hasPropertyMetadata() method instead.', E_USER_DEPRECATED);
  378. return $this->hasPropertyMetadata($property);
  379. }
  380. /**
  381. * Returns all metadatas of members describing the given property.
  382. *
  383. * @param string $property The name of the property
  384. *
  385. * @return MemberMetadata[] An array of MemberMetadata
  386. *
  387. * @deprecated since version 2.6, to be removed in 3.0. Use {@link getPropertyMetadata} instead.
  388. */
  389. public function getMemberMetadatas($property)
  390. {
  391. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getPropertyMetadata() method instead.', E_USER_DEPRECATED);
  392. return $this->getPropertyMetadata($property);
  393. }
  394. /**
  395. * {@inheritdoc}
  396. */
  397. public function hasPropertyMetadata($property)
  398. {
  399. return array_key_exists($property, $this->members);
  400. }
  401. /**
  402. * {@inheritdoc}
  403. */
  404. public function getPropertyMetadata($property)
  405. {
  406. if (!isset($this->members[$property])) {
  407. return array();
  408. }
  409. return $this->members[$property];
  410. }
  411. /**
  412. * {@inheritdoc}
  413. */
  414. public function getConstrainedProperties()
  415. {
  416. return array_keys($this->members);
  417. }
  418. /**
  419. * Sets the default group sequence for this class.
  420. *
  421. * @param array $groupSequence An array of group names
  422. *
  423. * @return $this
  424. *
  425. * @throws GroupDefinitionException
  426. */
  427. public function setGroupSequence($groupSequence)
  428. {
  429. if ($this->isGroupSequenceProvider()) {
  430. throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');
  431. }
  432. if (is_array($groupSequence)) {
  433. $groupSequence = new GroupSequence($groupSequence);
  434. }
  435. if (in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) {
  436. throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP));
  437. }
  438. if (!in_array($this->getDefaultGroup(), $groupSequence->groups, true)) {
  439. throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence', $this->getDefaultGroup()));
  440. }
  441. $this->groupSequence = $groupSequence;
  442. return $this;
  443. }
  444. /**
  445. * {@inheritdoc}
  446. */
  447. public function hasGroupSequence()
  448. {
  449. return $this->groupSequence && count($this->groupSequence->groups) > 0;
  450. }
  451. /**
  452. * {@inheritdoc}
  453. */
  454. public function getGroupSequence()
  455. {
  456. return $this->groupSequence;
  457. }
  458. /**
  459. * Returns a ReflectionClass instance for this class.
  460. *
  461. * @return \ReflectionClass
  462. */
  463. public function getReflectionClass()
  464. {
  465. if (!$this->reflClass) {
  466. $this->reflClass = new \ReflectionClass($this->getClassName());
  467. }
  468. return $this->reflClass;
  469. }
  470. /**
  471. * Sets whether a group sequence provider should be used.
  472. *
  473. * @param bool $active
  474. *
  475. * @throws GroupDefinitionException
  476. */
  477. public function setGroupSequenceProvider($active)
  478. {
  479. if ($this->hasGroupSequence()) {
  480. throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence');
  481. }
  482. if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) {
  483. throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface', $this->name));
  484. }
  485. $this->groupSequenceProvider = $active;
  486. }
  487. /**
  488. * {@inheritdoc}
  489. */
  490. public function isGroupSequenceProvider()
  491. {
  492. return $this->groupSequenceProvider;
  493. }
  494. /**
  495. * Class nodes are never cascaded.
  496. *
  497. * {@inheritdoc}
  498. */
  499. public function getCascadingStrategy()
  500. {
  501. return CascadingStrategy::NONE;
  502. }
  503. /**
  504. * Adds a property metadata.
  505. *
  506. * @param PropertyMetadataInterface $metadata
  507. */
  508. private function addPropertyMetadata(PropertyMetadataInterface $metadata)
  509. {
  510. $property = $metadata->getPropertyName();
  511. $this->members[$property][] = $metadata;
  512. }
  513. }