. */ #namespace Doctrine::ORM::Internal; /** * The metadata factory is used to create ClassMetadata objects that contain all the * metadata of a class. * * @author Konsta Vesterinen * @author Roman Borschel * @package Doctrine * @subpackage ClassMetadata * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ * @link www.phpdoctrine.org * @since 2.0 * @todo Rename to ClassMetadataFactory. */ class Doctrine_ClassMetadata_Factory { protected $_em; protected $_driver; /** * The already loaded metadata objects. */ protected $_loadedMetadata = array(); /** * Constructor. * Creates a new factory instance that uses the given connection and metadata driver * implementations. * * @param $conn The connection to use. * @param $driver The metadata driver to use. */ public function __construct(Doctrine_EntityManager $em, $driver) { $this->_em = $em; $this->_driver = $driver; } /** * Returns the metadata object for a class. * * @param string $className The name of the class. * @return Doctrine_Metadata */ public function getMetadataFor($className) { if (isset($this->_loadedMetadata[$className])) { return $this->_loadedMetadata[$className]; } $this->_loadClasses($className, $this->_loadedMetadata); return $this->_loadedMetadata[$className]; } /** * 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 _loadClasses($name, array &$classes) { $parentClass = $name; $parentClasses = array(); $loadedParentClass = false; while ($parentClass = get_parent_class($parentClass)) { if ($parentClass == 'Doctrine_Entity') { break; } if (isset($classes[$parentClass])) { $loadedParentClass = $parentClass; break; } $parentClasses[] = $parentClass; } $parentClasses = array_reverse($parentClasses); $parentClasses[] = $name; if ($loadedParentClass) { $class = $classes[$loadedParentClass]; } else { $rootClassOfHierarchy = count($parentClasses) > 0 ? array_shift($parentClasses) : $name; $class = new Doctrine_ClassMetadata($rootClassOfHierarchy, $this->_em); $this->_loadMetadata($class, $rootClassOfHierarchy); $classes[$rootClassOfHierarchy] = $class; } if (count($parentClasses) == 0) { return $class; } // load metadata of subclasses // -> child1 -> child2 -> $name $parent = $class; foreach ($parentClasses as $subclassName) { $subClass = new Doctrine_ClassMetadata($subclassName, $this->_em); $subClass->setInheritanceType($parent->getInheritanceType(), $parent->getInheritanceOptions()); $this->_addInheritedFields($subClass, $parent); $this->_addInheritedRelations($subClass, $parent); $this->_loadMetadata($subClass, $subclassName); if ($parent->getInheritanceType() == Doctrine::INHERITANCE_TYPE_SINGLE_TABLE) { $subClass->setTableName($parent->getTableName()); } $classes[$subclassName] = $subClass; $parent = $subClass; } } protected function _addInheritedFields($subClass, $parentClass) { foreach ($parentClass->getFieldMappings() as $fieldName => $mapping) { $fullName = "$name as " . $parentClass->getFieldName($name); $mapping['inherited'] = true; $subClass->mapField($mapping); } } protected function _addInheritedRelations($subClass, $parentClass) { foreach ($parentClass->getRelationParser()->getRelationDefinitions() as $name => $definition) { $subClass->getRelationParser()->addRelationDefinition($name, $definition); } } /** * Loads the metadata of a specified class. * * @param Doctrine_ClassMetadata $class The container for the metadata. * @param string $name The name of the class for which the metadata will be loaded. */ protected function _loadMetadata(Doctrine_ClassMetadata $class, $name) { if ( ! class_exists($name) || empty($name)) { throw new Doctrine_Exception("Couldn't find class " . $name . "."); } $names = array(); $className = $name; // get parent classes //TODO: Skip Entity types MappedSuperclass/Transient do { if ($className === 'Doctrine_Entity') { break; } else if ($className == $name) { continue; } $names[] = $className; } while ($className = get_parent_class($className)); if ($className === false) { throw new Doctrine_ClassMetadata_Factory_Exception("Unknown component '$className'."); } // save parents $class->setParentClasses($names); // load further metadata $this->_driver->loadMetadataForClass($name, $class); // set default table name, if necessary $tableName = $class->getTableName(); if ( ! isset($tableName)) { $class->setTableName(Doctrine::tableize($class->getClassName())); } // complete identifier mapping $this->_initIdentifier($class); return $class; } /** * Initializes the class identifier(s)/primary key(s). * * @param Doctrine_Metadata The metadata container of the class in question. */ protected function _initIdentifier(Doctrine_ClassMetadata $class) { /*switch (count($class->getIdentifier())) { case 0: // No identifier in the class mapping yet // If its a subclass, inherit the identifier from the parent. if ($class->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED && count($class->getParentClasses()) > 0) { $parents = $class->getParentClasses(); $root = end($parents); $rootClass = $class->getConnection()->getMetadata($root); $class->setIdentifier($rootClass->getIdentifier()); if ($class->getIdentifierType() !== Doctrine::IDENTIFIER_AUTOINC) { $class->setIdentifierType($rootClass->getIdentifierType()); } else { $class->setIdentifierType(Doctrine::IDENTIFIER_NATURAL); } // add all inherited primary keys foreach ($class->getIdentifier() as $id) { $definition = $rootClass->getDefinitionOf($id); // inherited primary keys shouldn't contain autoinc // and sequence definitions unset($definition['autoincrement']); unset($definition['sequence']); // add the inherited primary key column $fullName = $rootClass->getColumnName($id) . ' as ' . $id; $class->setColumn($fullName, $definition['type'], $definition['length'], $definition, true); } } else { throw Doctrine_MappingException::identifierRequired($class->getClassName()); } break; case 1: // A single identifier is in the mapping foreach ($class->getIdentifier() as $pk) { $columnName = $class->getColumnName($pk); $thisColumns = $class->getFieldMappings(); $e = $thisColumns[$columnName]; $found = false; foreach ($e as $option => $value) { if ($found) { break; } $e2 = explode(':', $option); switch (strtolower($e2[0])) { case 'autoincrement': case 'autoinc': $class->setIdentifierType(Doctrine::IDENTIFIER_AUTOINC); $found = true; break; case 'seq': case 'sequence': $class->setIdentifierType(Doctrine::IDENTIFIER_SEQUENCE); $found = true; if ($value) { $class->setTableOption('sequenceName', $value); } else { if (($sequence = $class->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) { $class->setTableOption('sequenceName', $sequence); } else { $class->setTableOption('sequenceName', $class->getConnection() ->getSequenceName($class->getTableName())); } } break; } } $identifierType = $class->getIdentifierType(); if ( ! isset($identifierType)) { $class->setIdentifierType(Doctrine::IDENTIFIER_NATURAL); } } $class->setIdentifier(array($pk)); break; default: // Multiple identifiers are in the mapping so its a composite id $class->setIdentifierType(Doctrine::IDENTIFIER_COMPOSITE); }*/ // If the chosen generator type is "auto", then pick the one appropriate for // the database. // FIXME: This is very ugly here. Such switch()es on the database driver // are unnecessary as we can easily replace them with polymorphic calls on // the connection (or another) object. We just need to decide where to put // the id generation types. if ($class->getIdGeneratorType() == Doctrine_ClassMetadata::GENERATOR_TYPE_AUTO) { switch (strtolower($this->_em->getConnection()->getDriverName())) { case 'mysql': // pick IDENTITY $class->setIdGeneratorType(Doctrine_ClassMetadata::GENERATOR_TYPE_IDENTITY); break; case 'oracle': //pick SEQUENCE break; case 'postgres': //pick SEQUENCE break; case 'firebird': //pick what? break; case 'mssql': //pick what? default: throw new Doctrine_Exception("Encountered unknown database driver: " . $this->_em->getConnection()->getDriverName()); } } } }