diff -r 000000000000 -r 7f95f8617b0b vendor/doctrine/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/doctrine/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php Sat Sep 24 15:40:41 2011 +0200 @@ -0,0 +1,481 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +use ReflectionException, + Doctrine\ORM\ORMException, + Doctrine\ORM\EntityManager, + Doctrine\DBAL\Platforms, + Doctrine\ORM\Events, + Doctrine\Common\Persistence\Mapping\ClassMetadataFactory as ClassMetadataFactoryInterface; + +/** + * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the + * metadata mapping informations of a class which describes how a class should be mapped + * to a relational database. + * + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ClassMetadataFactory implements ClassMetadataFactoryInterface +{ + /** + * @var EntityManager + */ + private $em; + + /** + * @var AbstractPlatform + */ + private $targetPlatform; + + /** + * @var Driver\Driver + */ + private $driver; + + /** + * @var \Doctrine\Common\EventManager + */ + private $evm; + + /** + * @var \Doctrine\Common\Cache\Cache + */ + private $cacheDriver; + + /** + * @var array + */ + private $loadedMetadata = array(); + + /** + * @var bool + */ + private $initialized = false; + + /** + * @param EntityManager $$em + */ + public function setEntityManager(EntityManager $em) + { + $this->em = $em; + } + + /** + * Sets the cache driver used by the factory to cache ClassMetadata instances. + * + * @param Doctrine\Common\Cache\Cache $cacheDriver + */ + public function setCacheDriver($cacheDriver) + { + $this->cacheDriver = $cacheDriver; + } + + /** + * Gets the cache driver used by the factory to cache ClassMetadata instances. + * + * @return Doctrine\Common\Cache\Cache + */ + public function getCacheDriver() + { + return $this->cacheDriver; + } + + public function getLoadedMetadata() + { + return $this->loadedMetadata; + } + + /** + * Forces the factory to load the metadata of all classes known to the underlying + * mapping driver. + * + * @return array The ClassMetadata instances of all mapped classes. + */ + public function getAllMetadata() + { + if ( ! $this->initialized) { + $this->initialize(); + } + + $metadata = array(); + foreach ($this->driver->getAllClassNames() as $className) { + $metadata[] = $this->getMetadataFor($className); + } + + return $metadata; + } + + /** + * Lazy initialization of this stuff, especially the metadata driver, + * since these are not needed at all when a metadata cache is active. + */ + private function initialize() + { + $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); + $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); + $this->evm = $this->em->getEventManager(); + $this->initialized = true; + } + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * @return Doctrine\ORM\Mapping\ClassMetadata + */ + public function getMetadataFor($className) + { + if ( ! isset($this->loadedMetadata[$className])) { + $realClassName = $className; + + // Check for namespace alias + if (strpos($className, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $className); + $realClassName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; + + if (isset($this->loadedMetadata[$realClassName])) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + + return $this->loadedMetadata[$realClassName]; + } + } + + if ($this->cacheDriver) { + if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) { + $this->loadedMetadata[$realClassName] = $cached; + } else { + foreach ($this->loadMetadata($realClassName) as $loadedClassName) { + $this->cacheDriver->save( + "$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null + ); + } + } + } else { + $this->loadMetadata($realClassName); + } + + if ($className != $realClassName) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + } + + return $this->loadedMetadata[$className]; + } + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className) + { + return isset($this->loadedMetadata[$className]); + } + + /** + * Sets the metadata descriptor for a specific class. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. + * + * @param string $className + * @param ClassMetadata $class + */ + public function setMetadataFor($className, $class) + { + $this->loadedMetadata[$className] = $class; + } + + /** + * Get array of parent classes for the given entity class + * + * @param string $name + * @return array $parentClasses + */ + protected function getParentClasses($name) + { + // Collect parent classes, ignoring transient (not-mapped) classes. + $parentClasses = array(); + foreach (array_reverse(class_parents($name)) as $parentClass) { + if ( ! $this->driver->isTransient($parentClass)) { + $parentClasses[] = $parentClass; + } + } + return $parentClasses; + } + + /** + * Loads the metadata of the class in question and all it's ancestors whose metadata + * is still not loaded. + * + * @param string $name The name of the class for which the metadata should get loaded. + * @param array $tables The metadata collection to which the loaded metadata is added. + */ + protected function loadMetadata($name) + { + if ( ! $this->initialized) { + $this->initialize(); + } + + $loaded = array(); + + $parentClasses = $this->getParentClasses($name); + $parentClasses[] = $name; + + // Move down the hierarchy of parent classes, starting from the topmost class + $parent = null; + $rootEntityFound = false; + $visited = array(); + foreach ($parentClasses as $className) { + if (isset($this->loadedMetadata[$className])) { + $parent = $this->loadedMetadata[$className]; + if ( ! $parent->isMappedSuperclass) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + continue; + } + + $class = $this->newClassMetadataInstance($className); + + if ($parent) { + $class->setInheritanceType($parent->inheritanceType); + $class->setDiscriminatorColumn($parent->discriminatorColumn); + $class->setIdGeneratorType($parent->generatorType); + $this->addInheritedFields($class, $parent); + $this->addInheritedRelations($class, $parent); + $class->setIdentifier($parent->identifier); + $class->setVersioned($parent->isVersioned); + $class->setVersionField($parent->versionField); + $class->setDiscriminatorMap($parent->discriminatorMap); + $class->setLifecycleCallbacks($parent->lifecycleCallbacks); + $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); + } + + // Invoke driver + try { + $this->driver->loadMetadataForClass($className, $class); + } catch (ReflectionException $e) { + throw MappingException::reflectionFailure($className, $e); + } + + // If this class has a parent the id generator strategy is inherited. + // However this is only true if the hierachy of parents contains the root entity, + // if it consinsts of mapped superclasses these don't necessarily include the id field. + if ($parent && $rootEntityFound) { + if ($parent->isIdGeneratorSequence()) { + $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); + } else if ($parent->isIdGeneratorTable()) { + $class->getTableGeneratorDefinition($parent->tableGeneratorDefinition); + } + if ($parent->generatorType) { + $class->setIdGeneratorType($parent->generatorType); + } + if ($parent->idGenerator) { + $class->setIdGenerator($parent->idGenerator); + } + } else { + $this->completeIdGeneratorMapping($class); + } + + if ($parent && $parent->isInheritanceTypeSingleTable()) { + $class->setPrimaryTable($parent->table); + } + + $class->setParentClasses($visited); + + if ($this->evm->hasListeners(Events::loadClassMetadata)) { + $eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em); + $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); + } + + $this->validateRuntimeMetadata($class, $parent); + + $this->loadedMetadata[$className] = $class; + + $parent = $class; + + if ( ! $class->isMappedSuperclass) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + + $loaded[] = $className; + } + + return $loaded; + } + + /** + * Validate runtime metadata is correctly defined. + * + * @param ClassMetadata $class + * @param ClassMetadata $parent + */ + protected function validateRuntimeMetadata($class, $parent) + { + // Verify & complete identifier mapping + if ( ! $class->identifier && ! $class->isMappedSuperclass) { + throw MappingException::identifierRequired($className); + } + + // verify inheritance + if (!$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) { + if (!$parent) { + if (count($class->discriminatorMap) == 0) { + throw MappingException::missingDiscriminatorMap($class->name); + } + if (!$class->discriminatorColumn) { + throw MappingException::missingDiscriminatorColumn($class->name); + } + } else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) { + // enforce discriminator map for all entities of an inheritance hierachy, otherwise problems will occur. + throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); + } + } else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { + // second condition is necessary for mapped superclasses in the middle of an inheritance hierachy + throw MappingException::noInheritanceOnMappedSuperClass($class->name); + } + } + + /** + * Creates a new ClassMetadata instance for the given class name. + * + * @param string $className + * @return Doctrine\ORM\Mapping\ClassMetadata + */ + protected function newClassMetadataInstance($className) + { + return new ClassMetadata($className); + } + + /** + * Adds inherited fields to the subclass mapping. + * + * @param Doctrine\ORM\Mapping\ClassMetadata $subClass + * @param Doctrine\ORM\Mapping\ClassMetadata $parentClass + */ + private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->fieldMappings as $fieldName => $mapping) { + if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { + $mapping['inherited'] = $parentClass->name; + } + if ( ! isset($mapping['declared'])) { + $mapping['declared'] = $parentClass->name; + } + $subClass->addInheritedFieldMapping($mapping); + } + foreach ($parentClass->reflFields as $name => $field) { + $subClass->reflFields[$name] = $field; + } + } + + /** + * Adds inherited association mappings to the subclass mapping. + * + * @param Doctrine\ORM\Mapping\ClassMetadata $subClass + * @param Doctrine\ORM\Mapping\ClassMetadata $parentClass + */ + private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->associationMappings as $field => $mapping) { + if ($parentClass->isMappedSuperclass) { + if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) { + throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field); + } + $mapping['sourceEntity'] = $subClass->name; + } + + //$subclassMapping = $mapping; + if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { + $mapping['inherited'] = $parentClass->name; + } + if ( ! isset($mapping['declared'])) { + $mapping['declared'] = $parentClass->name; + } + $subClass->addInheritedAssociationMapping($mapping); + } + } + + /** + * Completes the ID generator mapping. If "auto" is specified we choose the generator + * most appropriate for the targeted database platform. + * + * @param Doctrine\ORM\Mapping\ClassMetadata $class + */ + private function completeIdGeneratorMapping(ClassMetadataInfo $class) + { + $idGenType = $class->generatorType; + if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) { + if ($this->targetPlatform->prefersSequences()) { + $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE); + } else if ($this->targetPlatform->prefersIdentityColumns()) { + $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY); + } else { + $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE); + } + } + + // Create & assign an appropriate ID generator instance + switch ($class->generatorType) { + case ClassMetadata::GENERATOR_TYPE_IDENTITY: + // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to + // __seq in PostgreSQL for SERIAL columns. + // Not pretty but necessary and the simplest solution that currently works. + $seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ? + $class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' : + null; + $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName)); + break; + case ClassMetadata::GENERATOR_TYPE_SEQUENCE: + // If there is no sequence definition yet, create a default definition + $definition = $class->sequenceGeneratorDefinition; + if ( ! $definition) { + $sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq'; + $definition['sequenceName'] = $this->targetPlatform->fixSchemaElementName($sequenceName); + $definition['allocationSize'] = 1; + $definition['initialValue'] = 1; + $class->setSequenceGeneratorDefinition($definition); + } + $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator( + $definition['sequenceName'], + $definition['allocationSize'] + ); + $class->setIdGenerator($sequenceGenerator); + break; + case ClassMetadata::GENERATOR_TYPE_NONE: + $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator()); + break; + case ClassMetadata::GENERATOR_TYPE_TABLE: + throw new ORMException("TableGenerator not yet implemented."); + break; + default: + throw new ORMException("Unknown generator type: " . $class->generatorType); + } + } +}