Xml.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. namespace Gedmo\Tree\Mapping\Driver;
  3. use Gedmo\Mapping\Driver\Xml as BaseXml,
  4. Gedmo\Exception\InvalidMappingException;
  5. /**
  6. * This is a xml mapping driver for Tree
  7. * behavioral extension. Used for extraction of extended
  8. * metadata from xml specificaly for Tree
  9. * extension.
  10. *
  11. * @author Gustavo Falco <comfortablynumb84@gmail.com>
  12. * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  13. * @author Miha Vrhovnik <miha.vrhovnik@gmail.com>
  14. * @package Gedmo.Tree.Mapping.Driver
  15. * @subpackage Xml
  16. * @link http://www.gediminasm.org
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. class Xml extends BaseXml
  20. {
  21. /**
  22. * List of types which are valid for tree fields
  23. *
  24. * @var array
  25. */
  26. private $validTypes = array(
  27. 'integer',
  28. 'smallint',
  29. 'bigint',
  30. 'int'
  31. );
  32. /**
  33. * List of types which are valid for the path (materialized path strategy)
  34. *
  35. * @var array
  36. */
  37. private $validPathTypes = array(
  38. 'string',
  39. 'text'
  40. );
  41. /**
  42. * List of types which are valid for the path source (materialized path strategy)
  43. *
  44. * @var array
  45. */
  46. private $validPathSourceTypes = array(
  47. 'id',
  48. 'integer',
  49. 'smallint',
  50. 'bigint',
  51. 'string',
  52. 'int',
  53. 'float'
  54. );
  55. /**
  56. * List of tree strategies available
  57. *
  58. * @var array
  59. */
  60. private $strategies = array(
  61. 'nested',
  62. 'closure',
  63. 'materializedPath'
  64. );
  65. /**
  66. * {@inheritDoc}
  67. */
  68. public function readExtendedMetadata($meta, array &$config) {
  69. /**
  70. * @var \SimpleXmlElement $xml
  71. */
  72. $xml = $this->_getMapping($meta->name);
  73. $xmlDoctrine = $xml;
  74. $xml = $xml->children(self::GEDMO_NAMESPACE_URI);
  75. if ($xmlDoctrine->getName() == 'entity') {
  76. if (isset($xml->tree) && $this->_isAttributeSet($xml->tree, 'type')) {
  77. $strategy = $this->_getAttribute($xml->tree, 'type');
  78. if (!in_array($strategy, $this->strategies)) {
  79. throw new InvalidMappingException("Tree type: $strategy is not available.");
  80. }
  81. $config['strategy'] = $strategy;
  82. }
  83. if (isset($xml->{'tree-closure'}) && $this->_isAttributeSet($xml->{'tree-closure'}, 'class')) {
  84. $class = $this->_getAttribute($xml->{'tree-closure'}, 'class');
  85. if (!class_exists($class)) {
  86. throw new InvalidMappingException("Tree closure class: {$class} does not exist.");
  87. }
  88. $config['closure'] = $class;
  89. }
  90. }
  91. if (isset($xmlDoctrine->field)) {
  92. foreach ($xmlDoctrine->field as $mapping) {
  93. $mappingDoctrine = $mapping;
  94. $mapping = $mapping->children(self::GEDMO_NAMESPACE_URI);
  95. $field = $this->_getAttribute($mappingDoctrine, 'name');
  96. if (isset($mapping->{'tree-left'})) {
  97. if (!$this->isValidField($meta, $field)) {
  98. throw new InvalidMappingException("Tree left field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  99. }
  100. $config['left'] = $field;
  101. } elseif (isset($mapping->{'tree-right'})) {
  102. if (!$this->isValidField($meta, $field)) {
  103. throw new InvalidMappingException("Tree right field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  104. }
  105. $config['right'] = $field;
  106. } elseif (isset($mapping->{'tree-root'})) {
  107. if (!$meta->getFieldMapping($field)) {
  108. throw new InvalidMappingException("Tree root field - [{$field}] type is not valid in class - {$meta->name}");
  109. }
  110. $config['root'] = $field;
  111. } elseif (isset($mapping->{'tree-level'})) {
  112. if (!$this->isValidField($meta, $field)) {
  113. throw new InvalidMappingException("Tree level field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
  114. }
  115. $config['level'] = $field;
  116. } elseif (isset($mapping->{'tree-path'})) {
  117. if (!$this->isValidFieldForPath($meta, $field)) {
  118. throw new InvalidMappingException("Tree Path field - [{$field}] type is not valid. It must be string or text in class - {$meta->name}");
  119. }
  120. $separator = $this->_getAttribute($mapping->{'tree-path'}, 'separator');
  121. if (strlen($separator) > 1) {
  122. throw new InvalidMappingException("Tree Path field - [{$field}] Separator {$separator} is invalid. It must be only one character long.");
  123. }
  124. $config['path'] = $field;
  125. $config['path_separator'] = $separator;
  126. } elseif (isset($mapping->{'tree-path-source'})) {
  127. if (!$this->isValidFieldForPathSource($meta, $field)) {
  128. throw new InvalidMappingException("Tree PathSource field - [{$field}] type is not valid. It can be any of the integer variants, double, float or string in class - {$meta->name}");
  129. }
  130. $config['path_source'] = $field;
  131. }
  132. }
  133. }
  134. if (isset($xmlDoctrine->{'many-to-one'})) {
  135. foreach ($xmlDoctrine->{'many-to-one'} as $manyToOneMapping) {
  136. /**
  137. * @var \SimpleXMLElement $manyToOneMapping
  138. */
  139. $manyToOneMappingDoctrine = $manyToOneMapping;
  140. $manyToOneMapping = $manyToOneMapping->children(self::GEDMO_NAMESPACE_URI);;
  141. if (isset($manyToOneMapping->{'tree-parent'})) {
  142. $field = $this->_getAttribute($manyToOneMappingDoctrine, 'field');
  143. if ($meta->associationMappings[$field]['targetEntity'] != $meta->name) {
  144. throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
  145. }
  146. $config['parent'] = $field;
  147. }
  148. }
  149. }
  150. if (!$meta->isMappedSuperclass && $config) {
  151. if (isset($config['strategy'])) {
  152. if (is_array($meta->identifier) && count($meta->identifier) > 1) {
  153. throw new InvalidMappingException("Tree does not support composite identifiers in class - {$meta->name}");
  154. }
  155. $method = 'validate' . ucfirst($config['strategy']) . 'TreeMetadata';
  156. $this->$method($meta, $config);
  157. } else {
  158. throw new InvalidMappingException("Cannot find Tree type for class: {$meta->name}");
  159. }
  160. }
  161. }
  162. /**
  163. * Checks if $field type is valid
  164. *
  165. * @param object $meta
  166. * @param string $field
  167. * @return boolean
  168. */
  169. protected function isValidField($meta, $field)
  170. {
  171. $mapping = $meta->getFieldMapping($field);
  172. return $mapping && in_array($mapping['type'], $this->validTypes);
  173. }
  174. /**
  175. * Checks if $field type is valid for Path field
  176. *
  177. * @param object $meta
  178. * @param string $field
  179. * @return boolean
  180. */
  181. protected function isValidFieldForPath($meta, $field)
  182. {
  183. $mapping = $meta->getFieldMapping($field);
  184. return $mapping && in_array($mapping['type'], $this->validPathTypes);
  185. }
  186. /**
  187. * Checks if $field type is valid for PathSource field
  188. *
  189. * @param object $meta
  190. * @param string $field
  191. * @return boolean
  192. */
  193. protected function isValidFieldForPathSource($meta, $field)
  194. {
  195. $mapping = $meta->getFieldMapping($field);
  196. return $mapping && in_array($mapping['type'], $this->validPathSourceTypes);
  197. }
  198. /**
  199. * Validates metadata for nested type tree
  200. *
  201. * @param object $meta
  202. * @param array $config
  203. * @throws InvalidMappingException
  204. * @return void
  205. */
  206. private function validateNestedTreeMetadata($meta, array $config)
  207. {
  208. $missingFields = array();
  209. if (!isset($config['parent'])) {
  210. $missingFields[] = 'ancestor';
  211. }
  212. if (!isset($config['left'])) {
  213. $missingFields[] = 'left';
  214. }
  215. if (!isset($config['right'])) {
  216. $missingFields[] = 'right';
  217. }
  218. if ($missingFields) {
  219. throw new InvalidMappingException("Missing properties: " . implode(', ', $missingFields) . " in class - {$meta->name}");
  220. }
  221. }
  222. /**
  223. * Validates metadata for closure type tree
  224. *
  225. * @param object $meta
  226. * @param array $config
  227. * @throws InvalidMappingException
  228. * @return void
  229. */
  230. private function validateClosureTreeMetadata($meta, array $config)
  231. {
  232. $missingFields = array();
  233. if (!isset($config['parent'])) {
  234. $missingFields[] = 'ancestor';
  235. }
  236. if (!isset($config['closure'])) {
  237. $missingFields[] = 'closure class';
  238. }
  239. if ($missingFields) {
  240. throw new InvalidMappingException("Missing properties: " . implode(', ', $missingFields) . " in class - {$meta->name}");
  241. }
  242. }
  243. /**
  244. * Validates metadata for materialized path type tree
  245. *
  246. * @param object $meta
  247. * @param array $config
  248. * @throws InvalidMappingException
  249. * @return void
  250. */
  251. private function validateMaterializedPathTreeMetadata($meta, array $config)
  252. {
  253. $missingFields = array();
  254. if (!isset($config['parent'])) {
  255. $missingFields[] = 'ancestor';
  256. }
  257. if (!isset($config['path'])) {
  258. $missingFields[] = 'path';
  259. }
  260. if (!isset($config['path_source'])) {
  261. $missingFields[] = 'path_source';
  262. }
  263. if ($missingFields) {
  264. throw new InvalidMappingException("Missing properties: " . implode(', ', $missingFields) . " in class - {$meta->name}");
  265. }
  266. }
  267. }