Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Mapping / ClassMetadataFactory.php
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 MIT license. 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\ReflectionService,
28     Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface,
29     Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory,
30     Doctrine\ORM\Id\IdentityGenerator,
31     Doctrine\ORM\Event\LoadClassMetadataEventArgs;
32
33 /**
34  * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
35  * metadata mapping information of a class which describes how a class should be mapped
36  * to a relational database.
37  *
38  * @since   2.0
39  * @author  Benjamin Eberlei <kontakt@beberlei.de>
40  * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
41  * @author  Jonathan Wage <jonwage@gmail.com>
42  * @author  Roman Borschel <roman@code-factory.org>
43  */
44 class ClassMetadataFactory extends AbstractClassMetadataFactory
45 {
46     /**
47      * @var EntityManager
48      */
49     private $em;
50
51     /**
52      * @var \Doctrine\DBAL\Platforms\AbstractPlatform
53      */
54     private $targetPlatform;
55
56     /**
57      * @var \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver
58      */
59     private $driver;
60
61     /**
62      * @var \Doctrine\Common\EventManager
63      */
64     private $evm;
65
66     /**
67      * @param EntityManager $em
68      */
69     public function setEntityManager(EntityManager $em)
70     {
71         $this->em = $em;
72     }
73
74     /**
75      * {@inheritDoc}.
76      */
77     protected function initialize()
78     {
79         $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
80         $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
81         $this->evm = $this->em->getEventManager();
82         $this->initialized = true;
83     }
84
85     /**
86      * {@inheritDoc}
87      */
88     protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents)
89     {
90         /* @var $class ClassMetadata */
91         /* @var $parent ClassMetadata */
92         if ($parent) {
93             $class->setInheritanceType($parent->inheritanceType);
94             $class->setDiscriminatorColumn($parent->discriminatorColumn);
95             $class->setIdGeneratorType($parent->generatorType);
96             $this->addInheritedFields($class, $parent);
97             $this->addInheritedRelations($class, $parent);
98             $class->setIdentifier($parent->identifier);
99             $class->setVersioned($parent->isVersioned);
100             $class->setVersionField($parent->versionField);
101             $class->setDiscriminatorMap($parent->discriminatorMap);
102             $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
103             $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
104
105             if ($parent->isMappedSuperclass) {
106                 $class->setCustomRepositoryClass($parent->customRepositoryClassName);
107             }
108         }
109
110         // Invoke driver
111         try {
112             $this->driver->loadMetadataForClass($class->getName(), $class);
113         } catch (ReflectionException $e) {
114             throw MappingException::reflectionFailure($class->getName(), $e);
115         }
116
117         // If this class has a parent the id generator strategy is inherited.
118         // However this is only true if the hierarchy of parents contains the root entity,
119         // if it consists of mapped superclasses these don't necessarily include the id field.
120         if ($parent && $rootEntityFound) {
121             if ($parent->isIdGeneratorSequence()) {
122                 $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
123             } else if ($parent->isIdGeneratorTable()) {
124                 $class->tableGeneratorDefinition = $parent->tableGeneratorDefinition;
125             }
126
127             if ($parent->generatorType) {
128                 $class->setIdGeneratorType($parent->generatorType);
129             }
130
131             if ($parent->idGenerator) {
132                 $class->setIdGenerator($parent->idGenerator);
133             }
134         } else {
135             $this->completeIdGeneratorMapping($class);
136         }
137
138         if ($parent && $parent->isInheritanceTypeSingleTable()) {
139             $class->setPrimaryTable($parent->table);
140         }
141
142         if ($parent && $parent->containsForeignIdentifier) {
143             $class->containsForeignIdentifier = true;
144         }
145
146         if ($parent && !empty($parent->namedQueries)) {
147             $this->addInheritedNamedQueries($class, $parent);
148         }
149
150         if ($parent && !empty($parent->namedNativeQueries)) {
151             $this->addInheritedNamedNativeQueries($class, $parent);
152         }
153
154         if ($parent && !empty($parent->sqlResultSetMappings)) {
155             $this->addInheritedSqlResultSetMappings($class, $parent);
156         }
157
158         $class->setParentClasses($nonSuperclassParents);
159
160         if ( $class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) {
161             $this->addDefaultDiscriminatorMap($class);
162         }
163
164         if ($this->evm->hasListeners(Events::loadClassMetadata)) {
165             $eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
166             $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
167         }
168
169         $this->wakeupReflection($class, $this->getReflectionService());
170         $this->validateRuntimeMetadata($class, $parent);
171     }
172
173     /**
174      * Validate runtime metadata is correctly defined.
175      *
176      * @param ClassMetadata $class
177      * @param $parent
178      * @throws MappingException
179      */
180     protected function validateRuntimeMetadata($class, $parent)
181     {
182         if ( ! $class->reflClass ) {
183             // only validate if there is a reflection class instance
184             return;
185         }
186
187         $class->validateIdentifier();
188         $class->validateAssocations();
189         $class->validateLifecycleCallbacks($this->getReflectionService());
190
191         // verify inheritance
192         if ( ! $class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
193             if ( ! $parent) {
194                 if (count($class->discriminatorMap) == 0) {
195                     throw MappingException::missingDiscriminatorMap($class->name);
196                 }
197                 if ( ! $class->discriminatorColumn) {
198                     throw MappingException::missingDiscriminatorColumn($class->name);
199                 }
200             } else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) {
201                 // enforce discriminator map for all entities of an inheritance hierarchy, otherwise problems will occur.
202                 throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName);
203             }
204         } else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
205             // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
206             throw MappingException::noInheritanceOnMappedSuperClass($class->name);
207         }
208     }
209
210     /**
211      * {@inheritDoc}
212      */
213     protected function newClassMetadataInstance($className)
214     {
215         return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy());
216     }
217
218     /**
219      * Adds a default discriminator map if no one is given
220      *
221      * If an entity is of any inheritance type and does not contain a
222      * discriminator map, then the map is generated automatically. This process
223      * is expensive computation wise.
224      *
225      * The automatically generated discriminator map contains the lowercase short name of
226      * each class as key.
227      *
228      * @param \Doctrine\ORM\Mapping\ClassMetadata $class
229      * @throws MappingException
230      */
231     private function addDefaultDiscriminatorMap(ClassMetadata $class)
232     {
233         $allClasses = $this->driver->getAllClassNames();
234         $fqcn = $class->getName();
235         $map = array($this->getShortName($class->name) => $fqcn);
236
237         $duplicates = array();
238         foreach ($allClasses as $subClassCandidate) {
239             if (is_subclass_of($subClassCandidate, $fqcn)) {
240                 $shortName = $this->getShortName($subClassCandidate);
241
242                 if (isset($map[$shortName])) {
243                     $duplicates[] = $shortName;
244                 }
245
246                 $map[$shortName] = $subClassCandidate;
247             }
248         }
249
250         if ($duplicates) {
251             throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
252         }
253
254         $class->setDiscriminatorMap($map);
255     }
256
257     /**
258      * Get the lower-case short name of a class.
259      *
260      * @param string $className
261      * @return string
262      */
263     private function getShortName($className)
264     {
265         if (strpos($className, "\\") === false) {
266             return strtolower($className);
267         }
268
269         $parts = explode("\\", $className);
270         return strtolower(end($parts));
271     }
272
273     /**
274      * Adds inherited fields to the subclass mapping.
275      *
276      * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
277      * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
278      */
279     private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
280     {
281         foreach ($parentClass->fieldMappings as $mapping) {
282             if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
283                 $mapping['inherited'] = $parentClass->name;
284             }
285             if ( ! isset($mapping['declared'])) {
286                 $mapping['declared'] = $parentClass->name;
287             }
288             $subClass->addInheritedFieldMapping($mapping);
289         }
290         foreach ($parentClass->reflFields as $name => $field) {
291             $subClass->reflFields[$name] = $field;
292         }
293     }
294
295     /**
296      * Adds inherited association mappings to the subclass mapping.
297      *
298      * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
299      * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
300      * @throws MappingException
301      */
302     private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
303     {
304         foreach ($parentClass->associationMappings as $field => $mapping) {
305             if ($parentClass->isMappedSuperclass) {
306                 if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) {
307                     throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field);
308                 }
309                 $mapping['sourceEntity'] = $subClass->name;
310             }
311
312             //$subclassMapping = $mapping;
313             if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
314                 $mapping['inherited'] = $parentClass->name;
315             }
316             if ( ! isset($mapping['declared'])) {
317                 $mapping['declared'] = $parentClass->name;
318             }
319             $subClass->addInheritedAssociationMapping($mapping);
320         }
321     }
322
323     /**
324      * Adds inherited named queries to the subclass mapping.
325      *
326      * @since 2.2
327      * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
328      * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
329      */
330     private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
331     {
332         foreach ($parentClass->namedQueries as $name => $query) {
333             if ( ! isset ($subClass->namedQueries[$name])) {
334                 $subClass->addNamedQuery(array(
335                     'name'  => $query['name'],
336                     'query' => $query['query']
337                 ));
338             }
339         }
340     }
341
342     /**
343      * Adds inherited named native queries to the subclass mapping.
344      *
345      * @since 2.3
346      * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
347      * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
348      */
349     private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
350     {
351         foreach ($parentClass->namedNativeQueries as $name => $query) {
352             if ( ! isset ($subClass->namedNativeQueries[$name])) {
353                 $subClass->addNamedNativeQuery(array(
354                     'name'              => $query['name'],
355                     'query'             => $query['query'],
356                     'isSelfClass'       => $query['isSelfClass'],
357                     'resultSetMapping'  => $query['resultSetMapping'],
358                     'resultClass'       => $query['isSelfClass'] ? $subClass->name : $query['resultClass'],
359                 ));
360             }
361         }
362     }
363
364     /**
365      * Adds inherited sql result set mappings to the subclass mapping.
366      *
367      * @since 2.3
368      * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
369      * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
370      */
371     private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass)
372     {
373         foreach ($parentClass->sqlResultSetMappings as $name => $mapping) {
374             if ( ! isset ($subClass->sqlResultSetMappings[$name])) {
375                 $entities = array();
376                 foreach ($mapping['entities'] as $entity) {
377                     $entities[] = array(
378                         'fields'                => $entity['fields'],
379                         'isSelfClass'           => $entity['isSelfClass'],
380                         'discriminatorColumn'   => $entity['discriminatorColumn'],
381                         'entityClass'           => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'],
382                     );
383                 }
384
385                 $subClass->addSqlResultSetMapping(array(
386                     'name'          => $mapping['name'],
387                     'columns'       => $mapping['columns'],
388                     'entities'      => $entities,
389                 ));
390             }
391         }
392     }
393
394     /**
395      * Completes the ID generator mapping. If "auto" is specified we choose the generator
396      * most appropriate for the targeted database platform.
397      *
398      * @param ClassMetadataInfo $class
399      * @throws ORMException
400      */
401     private function completeIdGeneratorMapping(ClassMetadataInfo $class)
402     {
403         $idGenType = $class->generatorType;
404         if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
405             if ($this->targetPlatform->prefersSequences()) {
406                 $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE);
407             } else if ($this->targetPlatform->prefersIdentityColumns()) {
408                 $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
409             } else {
410                 $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE);
411             }
412         }
413
414         // Create & assign an appropriate ID generator instance
415         switch ($class->generatorType) {
416             case ClassMetadata::GENERATOR_TYPE_IDENTITY:
417                 // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to
418                 // <table>_<column>_seq in PostgreSQL for SERIAL columns.
419                 // Not pretty but necessary and the simplest solution that currently works.
420                 $sequenceName = null;
421
422                 if ($this->targetPlatform instanceof Platforms\PostgreSQLPlatform) {
423                     $fieldName      = $class->getSingleIdentifierFieldName();
424                     $columnName     = $class->getSingleIdentifierColumnName();
425                     $quoted         = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
426                     $sequenceName   = $class->getTableName() . '_' . $columnName . '_seq';
427                     $definition     = array(
428                         'sequenceName' => $this->targetPlatform->fixSchemaElementName($sequenceName)
429                     );
430
431                     if ($quoted) {
432                         $definition['quoted'] = true;
433                     }
434
435                     $sequenceName = $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->targetPlatform);
436                 }
437
438                 $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($sequenceName));
439                 break;
440
441             case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
442                 // If there is no sequence definition yet, create a default definition
443                 $definition = $class->sequenceGeneratorDefinition;
444
445                 if ( ! $definition) {
446                     $fieldName      = $class->getSingleIdentifierFieldName();
447                     $columnName     = $class->getSingleIdentifierColumnName();
448                     $quoted         = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
449                     $sequenceName   = $class->getTableName() . '_' . $columnName . '_seq';
450                     $definition     = array(
451                         'sequenceName'      => $this->targetPlatform->fixSchemaElementName($sequenceName),
452                         'allocationSize'    => 1,
453                         'initialValue'      => 1,
454                     );
455
456                     if ($quoted) {
457                         $definition['quoted'] = true;
458                     }
459
460                     $class->setSequenceGeneratorDefinition($definition);
461                 }
462
463                 $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator(
464                     $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->targetPlatform),
465                     $definition['allocationSize']
466                 );
467                 $class->setIdGenerator($sequenceGenerator);
468                 break;
469
470             case ClassMetadata::GENERATOR_TYPE_NONE:
471                 $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
472                 break;
473
474             case ClassMetadata::GENERATOR_TYPE_UUID:
475                 $class->setIdGenerator(new \Doctrine\ORM\Id\UuidGenerator());
476                 break;
477
478             case ClassMetadata::GENERATOR_TYPE_TABLE:
479                 throw new ORMException("TableGenerator not yet implemented.");
480                 break;
481
482             case ClassMetadata::GENERATOR_TYPE_CUSTOM:
483                 $definition = $class->customGeneratorDefinition;
484                 if ( ! class_exists($definition['class'])) {
485                     throw new ORMException("Can't instantiate custom generator : " .
486                         $definition['class']);
487                 }
488                 $class->setIdGenerator(new $definition['class']);
489                 break;
490
491             default:
492                 throw new ORMException("Unknown generator type: " . $class->generatorType);
493         }
494     }
495
496     /**
497      * {@inheritDoc}
498      */
499     protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService)
500     {
501         /* @var $class ClassMetadata */
502         $class->wakeupReflection($reflService);
503     }
504
505     /**
506      * {@inheritDoc}
507      */
508     protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService)
509     {
510         /* @var $class ClassMetadata */
511         $class->initializeReflection($reflService);
512     }
513
514     /**
515      * {@inheritDoc}
516      */
517     protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
518     {
519         return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
520     }
521
522     /**
523      * {@inheritDoc}
524      */
525     protected function getDriver()
526     {
527         return $this->driver;
528     }
529
530     /**
531      * {@inheritDoc}
532      */
533     protected function isEntity(ClassMetadataInterface $class)
534     {
535         return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
536     }
537 }