PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/private/library/Doctrine/OXM/Mapping/ClassMetadataFactory.php

https://github.com/jameshelly/Sample-Modular-Zend-Framework-Application
PHP | 446 lines | 273 code | 50 blank | 123 comment | 30 complexity | 79d7891ee06603e8af9353ed7625cd84 MD5 | raw file
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the LGPL. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\OXM\Mapping;
  20. use ReflectionException;
  21. use Doctrine\OXM\OXMException;
  22. use Doctrine\OXM\Configuration;
  23. use Doctrine\Common\Util\Debug;
  24. use Doctrine\OXM\Events;
  25. use Doctrine\Common\Cache\Cache;
  26. use Doctrine\Common\EventManager;
  27. use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory as BaseClassMetadataFactory;
  28. use Doctrine\OXM\Types\Type;
  29. /**
  30. * The ClassMetadataFactory is used to create Mapping objects that contain all the
  31. * metadata mapping informations of a class which describes how a class should be mapped
  32. * to a xml node.
  33. *
  34. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  35. * @link www.doctrine-project.org
  36. * @since 2.0
  37. * @version $Revision$
  38. * @author Richard Fullmer <richard.fullmer@opensoftdev.com>
  39. */
  40. class ClassMetadataFactory implements BaseClassMetadataFactory
  41. {
  42. /**
  43. * @var \Doctrine\OXM\Configuration
  44. */
  45. private $configuration;
  46. /**
  47. * @var \Doctrine\OXM\Mapping\Driver\Driver
  48. */
  49. private $driver;
  50. /**
  51. * @var \Doctrine\Common\EventManager
  52. */
  53. private $evm;
  54. /**
  55. * @var \Doctrine\Common\Cache\Cache
  56. */
  57. private $cacheDriver;
  58. /**
  59. * @var \Doctrine\OXM\Mapping\ClassMetadata[]
  60. */
  61. private $loadedMetadata = array();
  62. /**
  63. * Keys are mapped xml node names
  64. *
  65. * @var array
  66. */
  67. private $xmlToClassMap = array();
  68. /**
  69. * @var bool
  70. */
  71. private $initialized = false;
  72. /**
  73. * @param Configuration $configuration
  74. * @param EventManager|null $evm
  75. * @return null
  76. */
  77. public function __construct(Configuration $configuration, EventManager $evm = null)
  78. {
  79. $this->configuration = $configuration;
  80. $this->evm = $evm;
  81. }
  82. /**
  83. * Sets the cache driver used by the factory to cache Mapping instances.
  84. *
  85. * @param \Doctrine\Common\Cache\Cache $cacheDriver
  86. */
  87. public function setCacheDriver(Cache $cacheDriver)
  88. {
  89. $this->cacheDriver = $cacheDriver;
  90. }
  91. /**
  92. * Gets the cache driver used by the factory to cache ClassMetadata instances.
  93. *
  94. * @return Doctrine\Common\Cache\Cache
  95. */
  96. public function getCacheDriver()
  97. {
  98. return $this->cacheDriver;
  99. }
  100. /**
  101. * @return array
  102. */
  103. public function getLoadedMetadata()
  104. {
  105. return $this->loadedMetadata;
  106. }
  107. /**
  108. * Forces the factory to load the metadata of all classes known to the underlying
  109. * mapping driver.
  110. *
  111. * @return array The ClassMetadata instances of all mapped classes.
  112. */
  113. public function getAllMetadata()
  114. {
  115. if (!$this->initialized) {
  116. $this->initialize();
  117. }
  118. $mappings = array();
  119. foreach ($this->driver->getAllClassNames() as $className) {
  120. $mappings[] = $this->getMetadataFor($className);
  121. }
  122. return $mappings;
  123. }
  124. /**
  125. * Preloads all metadata and returns an array of all known mapped node types
  126. *
  127. * @return array
  128. */
  129. public function getAllXmlNodes()
  130. {
  131. if (!$this->initialized) {
  132. $this->initialize();
  133. }
  134. // Load all metadata
  135. $this->getAllMetadata();
  136. return $this->xmlToClassMap;
  137. }
  138. /**
  139. * Lazy initialization of this stuff, especially the metadata driver,
  140. * since these are not needed at all when a metadata cache is active.
  141. */
  142. private function initialize()
  143. {
  144. $this->cacheDriver = $this->configuration->getMetadataCacheImpl();
  145. $this->driver = $this->configuration->getMetadataDriverImpl();
  146. if (null === $this->evm) {
  147. $this->evm = new EventManager();
  148. }
  149. $this->initialized = true;
  150. }
  151. /**
  152. * Gets the class metadata descriptor for a class.
  153. *
  154. * @param string $className The name of the class.
  155. * @return \Doctrine\OXM\Mapping\ClassMetadata
  156. */
  157. public function getMetadataFor($className)
  158. {
  159. if ( ! isset($this->loadedMetadata[$className])) {
  160. // print_r('loading class ' . $className . "\n");
  161. $realClassName = $className;
  162. // Check for namespace alias
  163. if (strpos($className, ':') !== false) {
  164. list($namespaceAlias, $simpleClassName) = explode(':', $className);
  165. $realClassName = $this->configuration->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
  166. if (isset($this->loadedMetadata[$realClassName])) {
  167. // We do not have the alias name in the map, include it
  168. $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
  169. return $this->loadedMetadata[$realClassName];
  170. }
  171. }
  172. if ($this->cacheDriver) {
  173. if (($cached = $this->cacheDriver->fetch("$realClassName\$XMLCLASSMETADATA")) !== false) {
  174. $this->loadedMetadata[$realClassName] = $cached;
  175. if (!$cached->isMappedSuperclass) {
  176. $this->xmlToClassMap[$cached->getXmlName()] = $realClassName;
  177. }
  178. } else {
  179. foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
  180. $this->cacheDriver->save(
  181. "$loadedClassName\$XMLCLASSMETADATA", $this->loadedMetadata[$loadedClassName], null
  182. );
  183. }
  184. }
  185. } else {
  186. $this->loadMetadata($realClassName);
  187. }
  188. if ($className != $realClassName) {
  189. // We do not have the alias name in the map, include it
  190. $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
  191. }
  192. }
  193. return $this->loadedMetadata[$className];
  194. }
  195. /**
  196. * Checks whether the factory has the metadata for a class loaded already.
  197. *
  198. * @param string $className
  199. * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
  200. */
  201. public function hasMetadataFor($className)
  202. {
  203. return isset($this->loadedMetadata[$className]);
  204. }
  205. /**
  206. * Sets the metadata descriptor for a specific class.
  207. *
  208. * NOTE: This is only useful in very special cases, like when generating proxy classes.
  209. *
  210. * @param string $className
  211. * @param ClassMapping $class
  212. */
  213. public function setMetadataFor($className, $class)
  214. {
  215. $this->loadedMetadata[$className] = $class;
  216. }
  217. /**
  218. * Get array of parent classes for the given entity class
  219. *
  220. * @param string $name
  221. * @return array $parentClasses
  222. */
  223. protected function getParentClasses($name)
  224. {
  225. // Collect parent classes, ignoring transient (not-mapped) classes.
  226. $parentClasses = array();
  227. foreach (array_reverse(class_parents($name)) as $parentClass) {
  228. if (!$this->driver->isTransient($parentClass)) {
  229. $parentClasses[] = $parentClass;
  230. }
  231. }
  232. return $parentClasses;
  233. }
  234. /**
  235. * Loads the metadata of the class in question and all it's ancestors whose metadata
  236. * is still not loaded.
  237. *
  238. * @param string $name The name of the class for which the metadata should get loaded.
  239. * @param array $tables The metadata collection to which the loaded metadata is added.
  240. */
  241. protected function loadMetadata($name)
  242. {
  243. if (!$this->initialized) {
  244. $this->initialize();
  245. }
  246. $loaded = array();
  247. $parentClasses = $this->getParentClasses($name);
  248. $parentClasses[] = $name;
  249. // Move down the hierarchy of parent classes, starting from the topmost class
  250. $parent = null;
  251. $visited = array();
  252. foreach ($parentClasses as $className) {
  253. if (isset($this->loadedMetadata[$className])) {
  254. $parent = $this->loadedMetadata[$className];
  255. if ( $parent->isMappedSuperclass) {
  256. array_unshift($visited, $className);
  257. }
  258. continue;
  259. }
  260. $class = $this->newClassMetadataInstance($className);
  261. if ($parent) {
  262. $class->setIdGeneratorType($parent->generatorType);
  263. $this->addInheritedFields($class, $parent);
  264. $class->setXmlNamespaces($parent->xmlNamespaces);
  265. $class->setIdentifier($parent->identifier);
  266. $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
  267. $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
  268. }
  269. // Invoke driver
  270. try {
  271. $this->driver->loadMetadataForClass($className, $class);
  272. } catch (ReflectionException $e) {
  273. throw MappingException::reflectionFailure($className, $e);
  274. }
  275. if ( ! $class->isMappedSuperclass && in_array($class->getXmlName(), array_keys($this->xmlToClassMap))) {
  276. throw MappingException::duplicateXmlNameBinding($className, $class->getXmlName());
  277. }
  278. if ($parent && ! $parent->isMappedSuperclass) {
  279. if ($parent->generatorType) {
  280. $class->setIdGeneratorType($parent->generatorType);
  281. }
  282. if ($parent->idGenerator) {
  283. $class->setIdGenerator($parent->idGenerator);
  284. }
  285. } else {
  286. $this->completeIdGeneratorMapping($class);
  287. }
  288. $class->setParentClasses($visited);
  289. // Todo - ensure that root elements have an ID mapped
  290. if ($this->evm->hasListeners(Events::loadClassMetadata)) {
  291. $eventArgs = new \Doctrine\OXM\Event\LoadClassMetadataEventArgs($class, $this);
  292. $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
  293. }
  294. $this->loadedMetadata[$className] = $class;
  295. $this->completeMappingTypeValidation($className, $class);
  296. if ( ! $class->isMappedSuperclass) {
  297. $this->xmlToClassMap[$class->getXmlName()] = $className;
  298. }
  299. $parent = $class;
  300. if ( $class->isMappedSuperclass) {
  301. array_unshift($visited, $className);
  302. }
  303. $loaded[] = $className;
  304. }
  305. return $loaded;
  306. }
  307. /**
  308. * Complete and validate type mappings
  309. *
  310. * @param string $className
  311. * @param ClassMetadataInfo $class
  312. */
  313. private function completeMappingTypeValidation($className, ClassMetadataInfo $class)
  314. {
  315. foreach ($class->fieldMappings as $fieldName => $mapping) {
  316. if (Type::hasType($mapping['type'])) {
  317. continue;
  318. }
  319. // Support type as a mapped class?
  320. if (!$this->hasMetadataFor($mapping['type']) && !$this->getMetadataFor($mapping['type'])) {
  321. throw MappingException::fieldTypeNotFound($className, $fieldName, $mapping['type']);
  322. }
  323. // Mapped classes must have binding node type XML_ELEMENT
  324. if ($mapping['node'] !== ClassMetadataInfo::XML_ELEMENT) {
  325. throw MappingException::customTypeWithoutNodeElement($className, $fieldName);
  326. }
  327. }
  328. }
  329. /**
  330. * Adds inherited fields to the subclass mapping.
  331. *
  332. * @param ClassMetadata $subClass
  333. * @param ClassMetadata $parentClass
  334. */
  335. private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
  336. {
  337. foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
  338. if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
  339. $mapping['inherited'] = $parentClass->name;
  340. }
  341. if ( ! isset($mapping['declared'])) {
  342. $mapping['declared'] = $parentClass->name;
  343. }
  344. $subClass->addInheritedFieldMapping($mapping);
  345. }
  346. foreach ($parentClass->reflFields as $name => $field) {
  347. $subClass->reflFields[$name] = $field;
  348. }
  349. }
  350. /**
  351. * Completes the ID generator mapping. If "auto" is specified we choose the generator
  352. * most appropriate.
  353. *
  354. * @param Doctrine\OXM\Mapping\ClassMetadataInfo $class
  355. */
  356. private function completeIdGeneratorMapping(ClassMetadataInfo $class)
  357. {
  358. $idGenType = $class->generatorType;
  359. if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
  360. $class->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);
  361. }
  362. // Create & assign an appropriate ID generator instance
  363. switch ($class->generatorType) {
  364. case ClassMetadataInfo::GENERATOR_TYPE_INCREMENT:
  365. throw new OXMException("Increment generator type not implemented yet");
  366. break;
  367. case ClassMetadataInfo::GENERATOR_TYPE_NONE:
  368. $class->setIdGenerator(new \Doctrine\OXM\Id\AssignedGenerator());
  369. break;
  370. case ClassMetadataInfo::GENERATOR_TYPE_UUID:
  371. $class->setIdGenerator(new \Doctrine\OXM\Id\UuidGenerator());
  372. break;
  373. default:
  374. throw new OXMException("Unknown generator type: " . $class->generatorType);
  375. }
  376. }
  377. /**
  378. * Creates a new Mapping instance for the given class name.
  379. *
  380. * @param string $className
  381. * @return ClassMetadata
  382. */
  383. protected function newClassMetadataInstance($className)
  384. {
  385. return new ClassMetadata($className);
  386. }
  387. }