ClassMetadata.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  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 ClassMetadata This object
  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 ClassMetadata
  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. *
  264. * @return ClassMetadata This object
  265. */
  266. public function addGetterConstraint($property, Constraint $constraint)
  267. {
  268. if (!isset($this->getters[$property])) {
  269. $this->getters[$property] = new GetterMetadata($this->getClassName(), $property);
  270. $this->addPropertyMetadata($this->getters[$property]);
  271. }
  272. $constraint->addImplicitGroupName($this->getDefaultGroup());
  273. $this->getters[$property]->addConstraint($constraint);
  274. return $this;
  275. }
  276. /**
  277. * @param string $property
  278. * @param Constraint[] $constraints
  279. *
  280. * @return ClassMetadata
  281. */
  282. public function addGetterConstraints($property, array $constraints)
  283. {
  284. foreach ($constraints as $constraint) {
  285. $this->addGetterConstraint($property, $constraint);
  286. }
  287. return $this;
  288. }
  289. /**
  290. * Merges the constraints of the given metadata into this object.
  291. *
  292. * @param ClassMetadata $source The source metadata
  293. */
  294. public function mergeConstraints(ClassMetadata $source)
  295. {
  296. foreach ($source->getConstraints() as $constraint) {
  297. $this->addConstraint(clone $constraint);
  298. }
  299. foreach ($source->getConstrainedProperties() as $property) {
  300. foreach ($source->getPropertyMetadata($property) as $member) {
  301. $member = clone $member;
  302. foreach ($member->getConstraints() as $constraint) {
  303. $constraint->addImplicitGroupName($this->getDefaultGroup());
  304. }
  305. $this->addPropertyMetadata($member);
  306. if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) {
  307. $property = $member->getPropertyName();
  308. if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) {
  309. $this->properties[$property] = $member;
  310. } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) {
  311. $this->getters[$property] = $member;
  312. }
  313. }
  314. }
  315. }
  316. }
  317. /**
  318. * Adds a member metadata.
  319. *
  320. * @param MemberMetadata $metadata
  321. *
  322. * @deprecated since version 2.6, to be removed in 3.0.
  323. */
  324. protected function addMemberMetadata(MemberMetadata $metadata)
  325. {
  326. @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);
  327. $this->addPropertyMetadata($metadata);
  328. }
  329. /**
  330. * Returns true if metadatas of members is present for the given property.
  331. *
  332. * @param string $property The name of the property
  333. *
  334. * @return bool
  335. *
  336. * @deprecated since version 2.6, to be removed in 3.0. Use {@link hasPropertyMetadata} instead.
  337. */
  338. public function hasMemberMetadatas($property)
  339. {
  340. @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);
  341. return $this->hasPropertyMetadata($property);
  342. }
  343. /**
  344. * Returns all metadatas of members describing the given property.
  345. *
  346. * @param string $property The name of the property
  347. *
  348. * @return MemberMetadata[] An array of MemberMetadata
  349. *
  350. * @deprecated since version 2.6, to be removed in 3.0. Use {@link getPropertyMetadata} instead.
  351. */
  352. public function getMemberMetadatas($property)
  353. {
  354. @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);
  355. return $this->getPropertyMetadata($property);
  356. }
  357. /**
  358. * {@inheritdoc}
  359. */
  360. public function hasPropertyMetadata($property)
  361. {
  362. return array_key_exists($property, $this->members);
  363. }
  364. /**
  365. * {@inheritdoc}
  366. */
  367. public function getPropertyMetadata($property)
  368. {
  369. if (!isset($this->members[$property])) {
  370. return array();
  371. }
  372. return $this->members[$property];
  373. }
  374. /**
  375. * {@inheritdoc}
  376. */
  377. public function getConstrainedProperties()
  378. {
  379. return array_keys($this->members);
  380. }
  381. /**
  382. * Sets the default group sequence for this class.
  383. *
  384. * @param array $groupSequence An array of group names
  385. *
  386. * @return ClassMetadata
  387. *
  388. * @throws GroupDefinitionException
  389. */
  390. public function setGroupSequence($groupSequence)
  391. {
  392. if ($this->isGroupSequenceProvider()) {
  393. throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');
  394. }
  395. if (is_array($groupSequence)) {
  396. $groupSequence = new GroupSequence($groupSequence);
  397. }
  398. if (in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) {
  399. throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP));
  400. }
  401. if (!in_array($this->getDefaultGroup(), $groupSequence->groups, true)) {
  402. throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence', $this->getDefaultGroup()));
  403. }
  404. $this->groupSequence = $groupSequence;
  405. return $this;
  406. }
  407. /**
  408. * {@inheritdoc}
  409. */
  410. public function hasGroupSequence()
  411. {
  412. return $this->groupSequence && count($this->groupSequence->groups) > 0;
  413. }
  414. /**
  415. * {@inheritdoc}
  416. */
  417. public function getGroupSequence()
  418. {
  419. return $this->groupSequence;
  420. }
  421. /**
  422. * Returns a ReflectionClass instance for this class.
  423. *
  424. * @return \ReflectionClass
  425. */
  426. public function getReflectionClass()
  427. {
  428. if (!$this->reflClass) {
  429. $this->reflClass = new \ReflectionClass($this->getClassName());
  430. }
  431. return $this->reflClass;
  432. }
  433. /**
  434. * Sets whether a group sequence provider should be used.
  435. *
  436. * @param bool $active
  437. *
  438. * @throws GroupDefinitionException
  439. */
  440. public function setGroupSequenceProvider($active)
  441. {
  442. if ($this->hasGroupSequence()) {
  443. throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence');
  444. }
  445. if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) {
  446. throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface', $this->name));
  447. }
  448. $this->groupSequenceProvider = $active;
  449. }
  450. /**
  451. * {@inheritdoc}
  452. */
  453. public function isGroupSequenceProvider()
  454. {
  455. return $this->groupSequenceProvider;
  456. }
  457. /**
  458. * Class nodes are never cascaded.
  459. *
  460. * {@inheritdoc}
  461. */
  462. public function getCascadingStrategy()
  463. {
  464. return CascadingStrategy::NONE;
  465. }
  466. /**
  467. * Adds a property metadata.
  468. *
  469. * @param PropertyMetadataInterface $metadata
  470. */
  471. private function addPropertyMetadata(PropertyMetadataInterface $metadata)
  472. {
  473. $property = $metadata->getPropertyName();
  474. $this->members[$property][] = $metadata;
  475. }
  476. }