|
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 |
|
20 namespace Doctrine\ORM\Mapping; |
|
21 |
|
22 use ReflectionException, |
|
23 Doctrine\ORM\ORMException, |
|
24 Doctrine\ORM\EntityManager, |
|
25 Doctrine\DBAL\Platforms, |
|
26 Doctrine\ORM\Events, |
|
27 Doctrine\Common\Persistence\Mapping\ClassMetadataFactory as ClassMetadataFactoryInterface; |
|
28 |
|
29 /** |
|
30 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the |
|
31 * metadata mapping informations of a class which describes how a class should be mapped |
|
32 * to a relational database. |
|
33 * |
|
34 * @since 2.0 |
|
35 * @author Benjamin Eberlei <kontakt@beberlei.de> |
|
36 * @author Guilherme Blanco <guilhermeblanco@hotmail.com> |
|
37 * @author Jonathan Wage <jonwage@gmail.com> |
|
38 * @author Roman Borschel <roman@code-factory.org> |
|
39 */ |
|
40 class ClassMetadataFactory implements ClassMetadataFactoryInterface |
|
41 { |
|
42 /** |
|
43 * @var EntityManager |
|
44 */ |
|
45 private $em; |
|
46 |
|
47 /** |
|
48 * @var AbstractPlatform |
|
49 */ |
|
50 private $targetPlatform; |
|
51 |
|
52 /** |
|
53 * @var Driver\Driver |
|
54 */ |
|
55 private $driver; |
|
56 |
|
57 /** |
|
58 * @var \Doctrine\Common\EventManager |
|
59 */ |
|
60 private $evm; |
|
61 |
|
62 /** |
|
63 * @var \Doctrine\Common\Cache\Cache |
|
64 */ |
|
65 private $cacheDriver; |
|
66 |
|
67 /** |
|
68 * @var array |
|
69 */ |
|
70 private $loadedMetadata = array(); |
|
71 |
|
72 /** |
|
73 * @var bool |
|
74 */ |
|
75 private $initialized = false; |
|
76 |
|
77 /** |
|
78 * @param EntityManager $$em |
|
79 */ |
|
80 public function setEntityManager(EntityManager $em) |
|
81 { |
|
82 $this->em = $em; |
|
83 } |
|
84 |
|
85 /** |
|
86 * Sets the cache driver used by the factory to cache ClassMetadata instances. |
|
87 * |
|
88 * @param Doctrine\Common\Cache\Cache $cacheDriver |
|
89 */ |
|
90 public function setCacheDriver($cacheDriver) |
|
91 { |
|
92 $this->cacheDriver = $cacheDriver; |
|
93 } |
|
94 |
|
95 /** |
|
96 * Gets the cache driver used by the factory to cache ClassMetadata instances. |
|
97 * |
|
98 * @return Doctrine\Common\Cache\Cache |
|
99 */ |
|
100 public function getCacheDriver() |
|
101 { |
|
102 return $this->cacheDriver; |
|
103 } |
|
104 |
|
105 public function getLoadedMetadata() |
|
106 { |
|
107 return $this->loadedMetadata; |
|
108 } |
|
109 |
|
110 /** |
|
111 * Forces the factory to load the metadata of all classes known to the underlying |
|
112 * mapping driver. |
|
113 * |
|
114 * @return array The ClassMetadata instances of all mapped classes. |
|
115 */ |
|
116 public function getAllMetadata() |
|
117 { |
|
118 if ( ! $this->initialized) { |
|
119 $this->initialize(); |
|
120 } |
|
121 |
|
122 $metadata = array(); |
|
123 foreach ($this->driver->getAllClassNames() as $className) { |
|
124 $metadata[] = $this->getMetadataFor($className); |
|
125 } |
|
126 |
|
127 return $metadata; |
|
128 } |
|
129 |
|
130 /** |
|
131 * Lazy initialization of this stuff, especially the metadata driver, |
|
132 * since these are not needed at all when a metadata cache is active. |
|
133 */ |
|
134 private function initialize() |
|
135 { |
|
136 $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); |
|
137 $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); |
|
138 $this->evm = $this->em->getEventManager(); |
|
139 $this->initialized = true; |
|
140 } |
|
141 |
|
142 /** |
|
143 * Gets the class metadata descriptor for a class. |
|
144 * |
|
145 * @param string $className The name of the class. |
|
146 * @return Doctrine\ORM\Mapping\ClassMetadata |
|
147 */ |
|
148 public function getMetadataFor($className) |
|
149 { |
|
150 if ( ! isset($this->loadedMetadata[$className])) { |
|
151 $realClassName = $className; |
|
152 |
|
153 // Check for namespace alias |
|
154 if (strpos($className, ':') !== false) { |
|
155 list($namespaceAlias, $simpleClassName) = explode(':', $className); |
|
156 $realClassName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; |
|
157 |
|
158 if (isset($this->loadedMetadata[$realClassName])) { |
|
159 // We do not have the alias name in the map, include it |
|
160 $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; |
|
161 |
|
162 return $this->loadedMetadata[$realClassName]; |
|
163 } |
|
164 } |
|
165 |
|
166 if ($this->cacheDriver) { |
|
167 if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) { |
|
168 $this->loadedMetadata[$realClassName] = $cached; |
|
169 } else { |
|
170 foreach ($this->loadMetadata($realClassName) as $loadedClassName) { |
|
171 $this->cacheDriver->save( |
|
172 "$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null |
|
173 ); |
|
174 } |
|
175 } |
|
176 } else { |
|
177 $this->loadMetadata($realClassName); |
|
178 } |
|
179 |
|
180 if ($className != $realClassName) { |
|
181 // We do not have the alias name in the map, include it |
|
182 $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; |
|
183 } |
|
184 } |
|
185 |
|
186 return $this->loadedMetadata[$className]; |
|
187 } |
|
188 |
|
189 /** |
|
190 * Checks whether the factory has the metadata for a class loaded already. |
|
191 * |
|
192 * @param string $className |
|
193 * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. |
|
194 */ |
|
195 public function hasMetadataFor($className) |
|
196 { |
|
197 return isset($this->loadedMetadata[$className]); |
|
198 } |
|
199 |
|
200 /** |
|
201 * Sets the metadata descriptor for a specific class. |
|
202 * |
|
203 * NOTE: This is only useful in very special cases, like when generating proxy classes. |
|
204 * |
|
205 * @param string $className |
|
206 * @param ClassMetadata $class |
|
207 */ |
|
208 public function setMetadataFor($className, $class) |
|
209 { |
|
210 $this->loadedMetadata[$className] = $class; |
|
211 } |
|
212 |
|
213 /** |
|
214 * Get array of parent classes for the given entity class |
|
215 * |
|
216 * @param string $name |
|
217 * @return array $parentClasses |
|
218 */ |
|
219 protected function getParentClasses($name) |
|
220 { |
|
221 // Collect parent classes, ignoring transient (not-mapped) classes. |
|
222 $parentClasses = array(); |
|
223 foreach (array_reverse(class_parents($name)) as $parentClass) { |
|
224 if ( ! $this->driver->isTransient($parentClass)) { |
|
225 $parentClasses[] = $parentClass; |
|
226 } |
|
227 } |
|
228 return $parentClasses; |
|
229 } |
|
230 |
|
231 /** |
|
232 * Loads the metadata of the class in question and all it's ancestors whose metadata |
|
233 * is still not loaded. |
|
234 * |
|
235 * @param string $name The name of the class for which the metadata should get loaded. |
|
236 * @param array $tables The metadata collection to which the loaded metadata is added. |
|
237 */ |
|
238 protected function loadMetadata($name) |
|
239 { |
|
240 if ( ! $this->initialized) { |
|
241 $this->initialize(); |
|
242 } |
|
243 |
|
244 $loaded = array(); |
|
245 |
|
246 $parentClasses = $this->getParentClasses($name); |
|
247 $parentClasses[] = $name; |
|
248 |
|
249 // Move down the hierarchy of parent classes, starting from the topmost class |
|
250 $parent = null; |
|
251 $rootEntityFound = false; |
|
252 $visited = array(); |
|
253 foreach ($parentClasses as $className) { |
|
254 if (isset($this->loadedMetadata[$className])) { |
|
255 $parent = $this->loadedMetadata[$className]; |
|
256 if ( ! $parent->isMappedSuperclass) { |
|
257 $rootEntityFound = true; |
|
258 array_unshift($visited, $className); |
|
259 } |
|
260 continue; |
|
261 } |
|
262 |
|
263 $class = $this->newClassMetadataInstance($className); |
|
264 |
|
265 if ($parent) { |
|
266 $class->setInheritanceType($parent->inheritanceType); |
|
267 $class->setDiscriminatorColumn($parent->discriminatorColumn); |
|
268 $class->setIdGeneratorType($parent->generatorType); |
|
269 $this->addInheritedFields($class, $parent); |
|
270 $this->addInheritedRelations($class, $parent); |
|
271 $class->setIdentifier($parent->identifier); |
|
272 $class->setVersioned($parent->isVersioned); |
|
273 $class->setVersionField($parent->versionField); |
|
274 $class->setDiscriminatorMap($parent->discriminatorMap); |
|
275 $class->setLifecycleCallbacks($parent->lifecycleCallbacks); |
|
276 $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); |
|
277 } |
|
278 |
|
279 // Invoke driver |
|
280 try { |
|
281 $this->driver->loadMetadataForClass($className, $class); |
|
282 } catch (ReflectionException $e) { |
|
283 throw MappingException::reflectionFailure($className, $e); |
|
284 } |
|
285 |
|
286 // If this class has a parent the id generator strategy is inherited. |
|
287 // However this is only true if the hierachy of parents contains the root entity, |
|
288 // if it consinsts of mapped superclasses these don't necessarily include the id field. |
|
289 if ($parent && $rootEntityFound) { |
|
290 if ($parent->isIdGeneratorSequence()) { |
|
291 $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); |
|
292 } else if ($parent->isIdGeneratorTable()) { |
|
293 $class->getTableGeneratorDefinition($parent->tableGeneratorDefinition); |
|
294 } |
|
295 if ($parent->generatorType) { |
|
296 $class->setIdGeneratorType($parent->generatorType); |
|
297 } |
|
298 if ($parent->idGenerator) { |
|
299 $class->setIdGenerator($parent->idGenerator); |
|
300 } |
|
301 } else { |
|
302 $this->completeIdGeneratorMapping($class); |
|
303 } |
|
304 |
|
305 if ($parent && $parent->isInheritanceTypeSingleTable()) { |
|
306 $class->setPrimaryTable($parent->table); |
|
307 } |
|
308 |
|
309 $class->setParentClasses($visited); |
|
310 |
|
311 if ($this->evm->hasListeners(Events::loadClassMetadata)) { |
|
312 $eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em); |
|
313 $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); |
|
314 } |
|
315 |
|
316 $this->validateRuntimeMetadata($class, $parent); |
|
317 |
|
318 $this->loadedMetadata[$className] = $class; |
|
319 |
|
320 $parent = $class; |
|
321 |
|
322 if ( ! $class->isMappedSuperclass) { |
|
323 $rootEntityFound = true; |
|
324 array_unshift($visited, $className); |
|
325 } |
|
326 |
|
327 $loaded[] = $className; |
|
328 } |
|
329 |
|
330 return $loaded; |
|
331 } |
|
332 |
|
333 /** |
|
334 * Validate runtime metadata is correctly defined. |
|
335 * |
|
336 * @param ClassMetadata $class |
|
337 * @param ClassMetadata $parent |
|
338 */ |
|
339 protected function validateRuntimeMetadata($class, $parent) |
|
340 { |
|
341 // Verify & complete identifier mapping |
|
342 if ( ! $class->identifier && ! $class->isMappedSuperclass) { |
|
343 throw MappingException::identifierRequired($className); |
|
344 } |
|
345 |
|
346 // verify inheritance |
|
347 if (!$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) { |
|
348 if (!$parent) { |
|
349 if (count($class->discriminatorMap) == 0) { |
|
350 throw MappingException::missingDiscriminatorMap($class->name); |
|
351 } |
|
352 if (!$class->discriminatorColumn) { |
|
353 throw MappingException::missingDiscriminatorColumn($class->name); |
|
354 } |
|
355 } else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) { |
|
356 // enforce discriminator map for all entities of an inheritance hierachy, otherwise problems will occur. |
|
357 throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); |
|
358 } |
|
359 } else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { |
|
360 // second condition is necessary for mapped superclasses in the middle of an inheritance hierachy |
|
361 throw MappingException::noInheritanceOnMappedSuperClass($class->name); |
|
362 } |
|
363 } |
|
364 |
|
365 /** |
|
366 * Creates a new ClassMetadata instance for the given class name. |
|
367 * |
|
368 * @param string $className |
|
369 * @return Doctrine\ORM\Mapping\ClassMetadata |
|
370 */ |
|
371 protected function newClassMetadataInstance($className) |
|
372 { |
|
373 return new ClassMetadata($className); |
|
374 } |
|
375 |
|
376 /** |
|
377 * Adds inherited fields to the subclass mapping. |
|
378 * |
|
379 * @param Doctrine\ORM\Mapping\ClassMetadata $subClass |
|
380 * @param Doctrine\ORM\Mapping\ClassMetadata $parentClass |
|
381 */ |
|
382 private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) |
|
383 { |
|
384 foreach ($parentClass->fieldMappings as $fieldName => $mapping) { |
|
385 if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { |
|
386 $mapping['inherited'] = $parentClass->name; |
|
387 } |
|
388 if ( ! isset($mapping['declared'])) { |
|
389 $mapping['declared'] = $parentClass->name; |
|
390 } |
|
391 $subClass->addInheritedFieldMapping($mapping); |
|
392 } |
|
393 foreach ($parentClass->reflFields as $name => $field) { |
|
394 $subClass->reflFields[$name] = $field; |
|
395 } |
|
396 } |
|
397 |
|
398 /** |
|
399 * Adds inherited association mappings to the subclass mapping. |
|
400 * |
|
401 * @param Doctrine\ORM\Mapping\ClassMetadata $subClass |
|
402 * @param Doctrine\ORM\Mapping\ClassMetadata $parentClass |
|
403 */ |
|
404 private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) |
|
405 { |
|
406 foreach ($parentClass->associationMappings as $field => $mapping) { |
|
407 if ($parentClass->isMappedSuperclass) { |
|
408 if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) { |
|
409 throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field); |
|
410 } |
|
411 $mapping['sourceEntity'] = $subClass->name; |
|
412 } |
|
413 |
|
414 //$subclassMapping = $mapping; |
|
415 if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { |
|
416 $mapping['inherited'] = $parentClass->name; |
|
417 } |
|
418 if ( ! isset($mapping['declared'])) { |
|
419 $mapping['declared'] = $parentClass->name; |
|
420 } |
|
421 $subClass->addInheritedAssociationMapping($mapping); |
|
422 } |
|
423 } |
|
424 |
|
425 /** |
|
426 * Completes the ID generator mapping. If "auto" is specified we choose the generator |
|
427 * most appropriate for the targeted database platform. |
|
428 * |
|
429 * @param Doctrine\ORM\Mapping\ClassMetadata $class |
|
430 */ |
|
431 private function completeIdGeneratorMapping(ClassMetadataInfo $class) |
|
432 { |
|
433 $idGenType = $class->generatorType; |
|
434 if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) { |
|
435 if ($this->targetPlatform->prefersSequences()) { |
|
436 $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE); |
|
437 } else if ($this->targetPlatform->prefersIdentityColumns()) { |
|
438 $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY); |
|
439 } else { |
|
440 $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE); |
|
441 } |
|
442 } |
|
443 |
|
444 // Create & assign an appropriate ID generator instance |
|
445 switch ($class->generatorType) { |
|
446 case ClassMetadata::GENERATOR_TYPE_IDENTITY: |
|
447 // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to |
|
448 // <table>_<column>_seq in PostgreSQL for SERIAL columns. |
|
449 // Not pretty but necessary and the simplest solution that currently works. |
|
450 $seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ? |
|
451 $class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' : |
|
452 null; |
|
453 $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName)); |
|
454 break; |
|
455 case ClassMetadata::GENERATOR_TYPE_SEQUENCE: |
|
456 // If there is no sequence definition yet, create a default definition |
|
457 $definition = $class->sequenceGeneratorDefinition; |
|
458 if ( ! $definition) { |
|
459 $sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq'; |
|
460 $definition['sequenceName'] = $this->targetPlatform->fixSchemaElementName($sequenceName); |
|
461 $definition['allocationSize'] = 1; |
|
462 $definition['initialValue'] = 1; |
|
463 $class->setSequenceGeneratorDefinition($definition); |
|
464 } |
|
465 $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator( |
|
466 $definition['sequenceName'], |
|
467 $definition['allocationSize'] |
|
468 ); |
|
469 $class->setIdGenerator($sequenceGenerator); |
|
470 break; |
|
471 case ClassMetadata::GENERATOR_TYPE_NONE: |
|
472 $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator()); |
|
473 break; |
|
474 case ClassMetadata::GENERATOR_TYPE_TABLE: |
|
475 throw new ORMException("TableGenerator not yet implemented."); |
|
476 break; |
|
477 default: |
|
478 throw new ORMException("Unknown generator type: " . $class->generatorType); |
|
479 } |
|
480 } |
|
481 } |