vendor/doctrine/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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 }