Annotation.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. namespace Gedmo\Tree\Mapping\Driver;
  3. use Gedmo\Mapping\Driver,
  4. Doctrine\Common\Annotations\AnnotationReader,
  5. Doctrine\Common\Persistence\Mapping\ClassMetadata,
  6. Gedmo\Exception\InvalidMappingException;
  7. /**
  8. * This is an annotation mapping driver for Tree
  9. * behavioral extension. Used for extraction of extended
  10. * metadata from Annotations specificaly for Tree
  11. * extension.
  12. *
  13. * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  14. * @package Gedmo.Tree.Mapping.Driver
  15. * @subpackage Annotation
  16. * @link http://www.gediminasm.org
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. class Annotation implements Driver
  20. {
  21. /**
  22. * Annotation to define the tree type
  23. */
  24. const ANNOTATION_TREE = 'Gedmo\Tree\Mapping\Tree';
  25. /**
  26. * Annotation to mark field as one which will store left value
  27. */
  28. const ANNOTATION_LEFT = 'Gedmo\Tree\Mapping\TreeLeft';
  29. /**
  30. * Annotation to mark field as one which will store right value
  31. */
  32. const ANNOTATION_RIGHT = 'Gedmo\Tree\Mapping\TreeRight';
  33. /**
  34. * Annotation to mark relative parent field
  35. */
  36. const ANNOTATION_PARENT = 'Gedmo\Tree\Mapping\TreeParent';
  37. /**
  38. * Annotation to mark node level
  39. */
  40. const ANNOTATION_LEVEL = 'Gedmo\Tree\Mapping\TreeLevel';
  41. /**
  42. * Annotation to mark field as tree root
  43. */
  44. const ANNOTATION_ROOT = 'Gedmo\Tree\Mapping\TreeRoot';
  45. /**
  46. * List of types which are valid for tree fields
  47. *
  48. * @var array
  49. */
  50. private $validTypes = array(
  51. 'integer',
  52. 'smallint',
  53. 'bigint'
  54. );
  55. /**
  56. * List of tree strategies available
  57. *
  58. * @var array
  59. */
  60. private $strategies = array(
  61. 'nested'
  62. );
  63. /**
  64. * {@inheritDoc}
  65. */
  66. public function validateFullMetadata(ClassMetadata $meta, array $config)
  67. {
  68. if (isset($config['strategy'])) {
  69. $method = 'validate' . ucfirst($config['strategy']) . 'TreeMetadata';
  70. $this->$method($meta, $config);
  71. } elseif ($config) {
  72. throw new InvalidMappingException("Cannot find Tree type for class: {$meta->name}");
  73. }
  74. }
  75. /**
  76. * {@inheritDoc}
  77. */
  78. public function readExtendedMetadata(ClassMetadata $meta, array &$config) {
  79. require_once __DIR__ . '/../Annotations.php';
  80. $reader = new AnnotationReader();
  81. $reader->setAnnotationNamespaceAlias('Gedmo\Tree\Mapping\\', 'gedmo');
  82. $class = $meta->getReflectionClass();
  83. // class annotations
  84. $classAnnotations = $reader->getClassAnnotations($class);
  85. if (isset($classAnnotations[self::ANNOTATION_TREE])) {
  86. $annot = $classAnnotations[self::ANNOTATION_TREE];
  87. if (!in_array($annot->type, $this->strategies)) {
  88. throw new InvalidMappingException("Tree type: {$annot->type} is not available.");
  89. }
  90. $config['strategy'] = $annot->type;
  91. }
  92. // property annotations
  93. foreach ($class->getProperties() as $property) {
  94. if ($meta->isMappedSuperclass && !$property->isPrivate() ||
  95. $meta->isInheritedField($property->name) ||
  96. isset($meta->associationMappings[$property->name]['inherited'])
  97. ) {
  98. continue;
  99. }
  100. // left
  101. if ($left = $reader->getPropertyAnnotation($property, self::ANNOTATION_LEFT)) {
  102. $field = $property->getName();
  103. if (!$meta->hasField($field)) {
  104. throw new InvalidMappingException("Unable to find 'left' - [{$field}] as mapped property in entity - {$meta->name}");
  105. }
  106. if (!$this->isValidField($meta, $field)) {
  107. throw new InvalidMappingException("Tree left field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  108. }
  109. $config['left'] = $field;
  110. }
  111. // right
  112. if ($right = $reader->getPropertyAnnotation($property, self::ANNOTATION_RIGHT)) {
  113. $field = $property->getName();
  114. if (!$meta->hasField($field)) {
  115. throw new InvalidMappingException("Unable to find 'right' - [{$field}] as mapped property in entity - {$meta->name}");
  116. }
  117. if (!$this->isValidField($meta, $field)) {
  118. throw new InvalidMappingException("Tree right field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  119. }
  120. $config['right'] = $field;
  121. }
  122. // ancestor/parent
  123. if ($parent = $reader->getPropertyAnnotation($property, self::ANNOTATION_PARENT)) {
  124. $field = $property->getName();
  125. if (!$meta->isSingleValuedAssociation($field)) {
  126. throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
  127. }
  128. $config['parent'] = $field;
  129. }
  130. // root
  131. if ($root = $reader->getPropertyAnnotation($property, self::ANNOTATION_ROOT)) {
  132. $field = $property->getName();
  133. if (!$meta->hasField($field)) {
  134. throw new InvalidMappingException("Unable to find 'root' - [{$field}] as mapped property in entity - {$meta->name}");
  135. }
  136. if (!$this->isValidField($meta, $field)) {
  137. throw new InvalidMappingException("Tree root field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  138. }
  139. $config['root'] = $field;
  140. }
  141. // level
  142. if ($parent = $reader->getPropertyAnnotation($property, self::ANNOTATION_LEVEL)) {
  143. $field = $property->getName();
  144. if (!$meta->hasField($field)) {
  145. throw new InvalidMappingException("Unable to find 'level' - [{$field}] as mapped property in entity - {$meta->name}");
  146. }
  147. if (!$this->isValidField($meta, $field)) {
  148. throw new InvalidMappingException("Tree level field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  149. }
  150. $config['level'] = $field;
  151. }
  152. }
  153. }
  154. /**
  155. * Checks if $field type is valid
  156. *
  157. * @param ClassMetadata $meta
  158. * @param string $field
  159. * @return boolean
  160. */
  161. protected function isValidField(ClassMetadata $meta, $field)
  162. {
  163. $mapping = $meta->getFieldMapping($field);
  164. return $mapping && in_array($mapping['type'], $this->validTypes);
  165. }
  166. /**
  167. * Validates metadata for nested type tree
  168. *
  169. * @param ClassMetadata $meta
  170. * @param array $config
  171. * @throws InvalidMappingException
  172. * @return void
  173. */
  174. private function validateNestedTreeMetadata(ClassMetadata $meta, array $config)
  175. {
  176. $missingFields = array();
  177. if (!isset($config['parent'])) {
  178. $missingFields[] = 'ancestor';
  179. }
  180. if (!isset($config['left'])) {
  181. $missingFields[] = 'left';
  182. }
  183. if (!isset($config['right'])) {
  184. $missingFields[] = 'right';
  185. }
  186. if ($missingFields) {
  187. throw new InvalidMappingException("Missing properties: " . implode(', ', $missingFields) . " in class - {$meta->name}");
  188. }
  189. }
  190. }