source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/Mapping/ClassMetadataFactory.php @ 356

Last change on this file since 356 was 345, checked in by quyenla, 11 years ago

collaborator page

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