ClassMetadata.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  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 Symfony 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('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
  199. }
  200. if ($constraint instanceof Valid) {
  201. throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
  202. }
  203. if ($constraint instanceof Traverse) {
  204. if ($constraint->traverse) {
  205. // If traverse is true, traversal should be explicitly enabled
  206. $this->traversalStrategy = TraversalStrategy::TRAVERSE;
  207. } else {
  208. // If traverse is false, traversal should be explicitly disabled
  209. $this->traversalStrategy = TraversalStrategy::NONE;
  210. }
  211. // The constraint is not added
  212. return $this;
  213. }
  214. $constraint->addImplicitGroupName($this->getDefaultGroup());
  215. parent::addConstraint($constraint);
  216. return $this;
  217. }
  218. /**
  219. * Adds a constraint to the given property.
  220. *
  221. * @param string $property The name of the property
  222. * @param Constraint $constraint The constraint
  223. *
  224. * @return $this
  225. */
  226. public function addPropertyConstraint($property, Constraint $constraint)
  227. {
  228. if (!isset($this->properties[$property])) {
  229. $this->properties[$property] = new PropertyMetadata($this->getClassName(), $property);
  230. $this->addPropertyMetadata($this->properties[$property]);
  231. }
  232. $constraint->addImplicitGroupName($this->getDefaultGroup());
  233. $this->properties[$property]->addConstraint($constraint);
  234. return $this;
  235. }
  236. /**
  237. * @param string $property
  238. * @param Constraint[] $constraints
  239. *
  240. * @return $this
  241. */
  242. public function addPropertyConstraints($property, array $constraints)
  243. {
  244. foreach ($constraints as $constraint) {
  245. $this->addPropertyConstraint($property, $constraint);
  246. }
  247. return $this;
  248. }
  249. /**
  250. * Adds a constraint to the getter of the given property.
  251. *
  252. * The name of the getter is assumed to be the name of the property with an
  253. * uppercased first letter and either the prefix "get" or "is".
  254. *
  255. * @param string $property The name of the property
  256. * @param Constraint $constraint The constraint
  257. *
  258. * @return $this
  259. */
  260. public function addGetterConstraint($property, Constraint $constraint)
  261. {
  262. if (!isset($this->getters[$property])) {
  263. $this->getters[$property] = new GetterMetadata($this->getClassName(), $property);
  264. $this->addPropertyMetadata($this->getters[$property]);
  265. }
  266. $constraint->addImplicitGroupName($this->getDefaultGroup());
  267. $this->getters[$property]->addConstraint($constraint);
  268. return $this;
  269. }
  270. /**
  271. * Adds a constraint to the getter of the given property.
  272. *
  273. * @param string $property The name of the property
  274. * @param string $method The name of the getter method
  275. * @param Constraint $constraint The constraint
  276. *
  277. * @return $this
  278. */
  279. public function addGetterMethodConstraint($property, $method, Constraint $constraint)
  280. {
  281. if (!isset($this->getters[$property])) {
  282. $this->getters[$property] = new GetterMetadata($this->getClassName(), $property, $method);
  283. $this->addPropertyMetadata($this->getters[$property]);
  284. }
  285. $constraint->addImplicitGroupName($this->getDefaultGroup());
  286. $this->getters[$property]->addConstraint($constraint);
  287. return $this;
  288. }
  289. /**
  290. * @param string $property
  291. * @param Constraint[] $constraints
  292. *
  293. * @return $this
  294. */
  295. public function addGetterConstraints($property, array $constraints)
  296. {
  297. foreach ($constraints as $constraint) {
  298. $this->addGetterConstraint($property, $constraint);
  299. }
  300. return $this;
  301. }
  302. /**
  303. * @param string $property
  304. * @param string $method
  305. * @param Constraint[] $constraints
  306. *
  307. * @return $this
  308. */
  309. public function addGetterMethodConstraints($property, $method, array $constraints)
  310. {
  311. foreach ($constraints as $constraint) {
  312. $this->addGetterMethodConstraint($property, $method, $constraint);
  313. }
  314. return $this;
  315. }
  316. /**
  317. * Merges the constraints of the given metadata into this object.
  318. */
  319. public function mergeConstraints(ClassMetadata $source)
  320. {
  321. if ($source->isGroupSequenceProvider()) {
  322. $this->setGroupSequenceProvider(true);
  323. }
  324. foreach ($source->getConstraints() as $constraint) {
  325. $this->addConstraint(clone $constraint);
  326. }
  327. foreach ($source->getConstrainedProperties() as $property) {
  328. foreach ($source->getPropertyMetadata($property) as $member) {
  329. $member = clone $member;
  330. foreach ($member->getConstraints() as $constraint) {
  331. if (\in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) {
  332. $member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint;
  333. }
  334. $constraint->addImplicitGroupName($this->getDefaultGroup());
  335. }
  336. $this->addPropertyMetadata($member);
  337. if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) {
  338. $property = $member->getPropertyName();
  339. if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) {
  340. $this->properties[$property] = $member;
  341. } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) {
  342. $this->getters[$property] = $member;
  343. }
  344. }
  345. }
  346. }
  347. }
  348. /**
  349. * Adds a member metadata.
  350. *
  351. * @param MemberMetadata $metadata
  352. *
  353. * @deprecated since version 2.6, to be removed in 3.0.
  354. */
  355. protected function addMemberMetadata(MemberMetadata $metadata)
  356. {
  357. @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the addPropertyMetadata() method instead.', E_USER_DEPRECATED);
  358. $this->addPropertyMetadata($metadata);
  359. }
  360. /**
  361. * Returns true if metadatas of members is present for the given property.
  362. *
  363. * @param string $property The name of the property
  364. *
  365. * @return bool
  366. *
  367. * @deprecated since version 2.6, to be removed in 3.0. Use {@link hasPropertyMetadata} instead.
  368. */
  369. public function hasMemberMetadatas($property)
  370. {
  371. @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the hasPropertyMetadata() method instead.', E_USER_DEPRECATED);
  372. return $this->hasPropertyMetadata($property);
  373. }
  374. /**
  375. * Returns all metadatas of members describing the given property.
  376. *
  377. * @param string $property The name of the property
  378. *
  379. * @return MemberMetadata[] An array of MemberMetadata
  380. *
  381. * @deprecated since version 2.6, to be removed in 3.0. Use {@link getPropertyMetadata} instead.
  382. */
  383. public function getMemberMetadatas($property)
  384. {
  385. @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the getPropertyMetadata() method instead.', E_USER_DEPRECATED);
  386. return $this->getPropertyMetadata($property);
  387. }
  388. /**
  389. * {@inheritdoc}
  390. */
  391. public function hasPropertyMetadata($property)
  392. {
  393. return array_key_exists($property, $this->members);
  394. }
  395. /**
  396. * {@inheritdoc}
  397. */
  398. public function getPropertyMetadata($property)
  399. {
  400. if (!isset($this->members[$property])) {
  401. return array();
  402. }
  403. return $this->members[$property];
  404. }
  405. /**
  406. * {@inheritdoc}
  407. */
  408. public function getConstrainedProperties()
  409. {
  410. return array_keys($this->members);
  411. }
  412. /**
  413. * Sets the default group sequence for this class.
  414. *
  415. * @param string[]|GroupSequence $groupSequence An array of group names
  416. *
  417. * @return $this
  418. *
  419. * @throws GroupDefinitionException
  420. */
  421. public function setGroupSequence($groupSequence)
  422. {
  423. if ($this->isGroupSequenceProvider()) {
  424. throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');
  425. }
  426. if (\is_array($groupSequence)) {
  427. $groupSequence = new GroupSequence($groupSequence);
  428. }
  429. if (\in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) {
  430. throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP));
  431. }
  432. if (!\in_array($this->getDefaultGroup(), $groupSequence->groups, true)) {
  433. throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence', $this->getDefaultGroup()));
  434. }
  435. $this->groupSequence = $groupSequence;
  436. return $this;
  437. }
  438. /**
  439. * {@inheritdoc}
  440. */
  441. public function hasGroupSequence()
  442. {
  443. return $this->groupSequence && \count($this->groupSequence->groups) > 0;
  444. }
  445. /**
  446. * {@inheritdoc}
  447. */
  448. public function getGroupSequence()
  449. {
  450. return $this->groupSequence;
  451. }
  452. /**
  453. * Returns a ReflectionClass instance for this class.
  454. *
  455. * @return \ReflectionClass
  456. */
  457. public function getReflectionClass()
  458. {
  459. if (!$this->reflClass) {
  460. $this->reflClass = new \ReflectionClass($this->getClassName());
  461. }
  462. return $this->reflClass;
  463. }
  464. /**
  465. * Sets whether a group sequence provider should be used.
  466. *
  467. * @param bool $active
  468. *
  469. * @throws GroupDefinitionException
  470. */
  471. public function setGroupSequenceProvider($active)
  472. {
  473. if ($this->hasGroupSequence()) {
  474. throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence');
  475. }
  476. if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) {
  477. throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface', $this->name));
  478. }
  479. $this->groupSequenceProvider = $active;
  480. }
  481. /**
  482. * {@inheritdoc}
  483. */
  484. public function isGroupSequenceProvider()
  485. {
  486. return $this->groupSequenceProvider;
  487. }
  488. /**
  489. * Class nodes are never cascaded.
  490. *
  491. * {@inheritdoc}
  492. */
  493. public function getCascadingStrategy()
  494. {
  495. return CascadingStrategy::NONE;
  496. }
  497. private function addPropertyMetadata(PropertyMetadataInterface $metadata)
  498. {
  499. $property = $metadata->getPropertyName();
  500. $this->members[$property][] = $metadata;
  501. }
  502. }