. */ /** * The joined subclass persister maps a single entity instance to several tables in the * database as it is defined by Class Table Inheritance. * * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ * @link www.phpdoctrine.org * @since 2.0 */ class Doctrine_ORM_Persisters_JoinedSubclassPersister extends Doctrine_ORM_Persisters_AbstractEntityPersister { /** * Inserts an entity that is part of a Class Table Inheritance hierarchy. * * @param Doctrine_Entity $record record to be inserted * @return boolean * @override */ public function insert(Doctrine_ORM_Entity $entity) { $class = $entity->getClass(); $dataSet = array(); $this->_prepareData($entity, $dataSet, true); $dataSet = $this->_groupFieldsByDefiningClass($class, $dataSet); $component = $class->getClassName(); $classes = $class->getParentClasses(); array_unshift($classes, $component); $identifier = null; foreach (array_reverse($classes) as $k => $parent) { $parentClass = $this->_em->getClassMetadata($parent); if ($k == 0) { if ($parentClass->isIdGeneratorIdentity()) { $this->_insertRow($parentClass->getTableName(), $dataSet[$parent]); $identifier = $this->_conn->lastInsertId(); } else if ($parentClass->isIdGeneratorSequence()) { $seq = $entity->getClassMetadata()->getTableOption('sequenceName'); if ( ! empty($seq)) { $id = $this->_conn->getSequenceManager()->nextId($seq); $identifierFields = $parentClass->getIdentifier(); $dataSet[$parent][$identifierFields[0]] = $id; $this->_insertRow($parentClass->getTableName(), $dataSet[$parent]); } } else { throw new Doctrine_Mapper_Exception("Unsupported identifier type '$identifierType'."); } $entity->_assignIdentifier($identifier); } else { foreach ($entity->_identifier() as $id => $value) { $dataSet[$parent][$parentClass->getColumnName($id)] = $value; } $this->_insertRow($parentClass->getTableName(), $dataSet[$parent]); } } return true; } /** * Updates an entity that is part of a Class Table Inheritance hierarchy. * * @param Doctrine_Entity $record record to be updated * @return boolean whether or not the update was successful */ protected function _doUpdate(Doctrine_ORM_Entity $record) { $conn = $this->_conn; $classMetadata = $this->_classMetadata; $identifier = $this->_convertFieldToColumnNames($record->identifier(), $classMetadata); $dataSet = $this->_groupFieldsByDefiningClass($record); $component = $classMetadata->getClassName(); $classes = $classMetadata->getParentClasses(); array_unshift($classes, $component); foreach ($record as $field => $value) { if ($value instanceof Doctrine_ORM_Entity) { if ( ! $value->exists()) { $value->save(); } $idValues = $value->identifier(); $record->set($field, $idValues[0]); } } foreach (array_reverse($classes) as $class) { $parentTable = $conn->getClassMetadata($class); $this->_updateRow($parentTable->getTableName(), $dataSet[$class], $identifier); } $record->assignIdentifier(true); return true; } /** * Deletes an entity that is part of a Class Table Inheritance hierarchy. * */ protected function _doDelete(Doctrine_ORM_Entity $record) { $conn = $this->_conn; try { $class = $this->_classMetadata; $conn->beginInternalTransaction(); $this->_deleteComposites($record); $record->_state(Doctrine_ORM_Entity::STATE_TDIRTY); $identifier = $this->_convertFieldToColumnNames($record->identifier(), $class); // run deletions, starting from the class, upwards the hierarchy $conn->delete($class->getTableName(), $identifier); foreach ($class->getParentClasses() as $parent) { $parentClass = $conn->getClassMetadata($parent); $this->_deleteRow($parentClass->getTableName(), $identifier); } $record->_state(Doctrine_ORM_Entity::STATE_TCLEAN); $this->removeRecord($record); // @todo should be done in the unitofwork $conn->commit(); } catch (Exception $e) { $conn->rollback(); throw $e; } return true; } /** * Adds all parent classes as INNER JOINs and subclasses as OUTER JOINs * to the query. * * Callback that is invoked during the SQL construction process. * * @return array The custom joins in the format => */ public function getCustomJoins() { $customJoins = array(); $classMetadata = $this->_classMetadata; foreach ($classMetadata->getParentClasses() as $parentClass) { $customJoins[$parentClass] = 'INNER'; } foreach ($classMetadata->getSubclasses() as $subClass) { if ($subClass != $this->getComponentName()) { $customJoins[$subClass] = 'LEFT'; } } return $customJoins; } /** * Adds the discriminator column to the selected fields in a query as well as * all fields of subclasses. In Class Table Inheritance the default behavior is that * all subclasses are joined in through OUTER JOINs when querying a base class. * * Callback that is invoked during the SQL construction process. * * @return array An array with the field names that will get added to the query. */ public function getCustomFields() { $classMetadata = $this->_classMetadata; $conn = $this->_conn; $fields = array($classMetadata->getInheritanceOption('discriminatorColumn')); if ($classMetadata->getSubclasses()) { foreach ($classMetadata->getSubclasses() as $subClass) { $fields = array_merge($conn->getClassMetadata($subClass)->getFieldNames(), $fields); } } return array_unique($fields); } /** * */ public function getFieldNames() { if ($this->_fieldNames) { return $this->_fieldNames; } $fieldNames = $this->_classMetadata->getFieldNames(); $this->_fieldNames = array_unique($fieldNames); return $fieldNames; } /** * * @todo Looks like this better belongs into the ClassMetadata class. */ public function getOwningClass($fieldName) { $conn = $this->_conn; $classMetadata = $this->_classMetadata; if ($classMetadata->hasField($fieldName) && ! $classMetadata->isInheritedField($fieldName)) { return $classMetadata; } foreach ($classMetadata->getParentClasses() as $parentClass) { $parentTable = $conn->getClassMetadata($parentClass); if ($parentTable->hasField($fieldName) && ! $parentTable->isInheritedField($fieldName)) { return $parentTable; } } foreach ((array)$classMetadata->getSubclasses() as $subClass) { $subTable = $conn->getClassMetadata($subClass); if ($subTable->hasField($fieldName) && ! $subTable->isInheritedField($fieldName)) { return $subTable; } } throw new Doctrine_Mapper_Exception("Unable to find defining class of field '$fieldName'."); } /** * Analyzes the fields of the entity and creates a map in which the field names * are grouped by the class names they belong to. * * @return array */ protected function _groupFieldsByDefiningClass(Doctrine_ClassMetadata $class, array $fields) { $dataSet = array(); $component = $class->getClassName(); $classes = array_merge(array($component), $class->getParentClasses()); foreach ($classes as $class) { $dataSet[$class] = array(); $parentClassMetadata = $this->_em->getClassMetadata($class); foreach ($parentClassMetadata->getFieldMappings() as $fieldName => $mapping) { if ((isset($mapping['id']) && $mapping['id'] === true) || (isset($mapping['inherited']) && $mapping['inherited'] === true)) { continue; } if ( ! array_key_exists($fieldName, $fields)) { continue; } $columnName = $parentClassMetadata->getColumnName($fieldName); $dataSet[$class][$columnName] = $fields[$fieldName]; } } return $dataSet; } }