. */ /** * The joined mapping strategy maps a single entity instance to several tables in the * database as it is defined by Class Table Inheritance. * * @author Roman Borschel * @package Doctrine * @subpackage DefaultStrategy * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ * @link www.phpdoctrine.org * @since 1.0 */ class Doctrine_Mapper_JoinedStrategy extends Doctrine_Mapper_Strategy { protected $_columnNameFieldNameMap = array(); /** * Inserts an entity that is part of a Class Table Inheritance hierarchy. * * @param Doctrine_Entity $record record to be inserted * @return boolean */ public function doInsert(Doctrine_Entity $record) { $class = $this->_mapper->getClassMetadata(); $conn = $this->_mapper->getConnection(); $dataSet = $this->_groupFieldsByDefiningClass($record); $component = $class->getClassName(); $classes = $class->getParentClasses(); array_unshift($classes, $component); try { $conn->beginInternalTransaction(); $identifier = null; foreach (array_reverse($classes) as $k => $parent) { $parentClass = $conn->getClassMetadata($parent); if ($k == 0) { $identifierType = $parentClass->getIdentifierType(); if ($identifierType == Doctrine::IDENTIFIER_AUTOINC) { $this->_insertRow($parentClass->getTableName(), $dataSet[$parent]); $identifier = $conn->sequence->lastInsertId(); } else if ($identifierType == Doctrine::IDENTIFIER_SEQUENCE) { $seq = $record->getClassMetadata()->getTableOption('sequenceName'); if ( ! empty($seq)) { $id = $conn->sequence->nextId($seq); $identifierFields = (array)$parentClass->getIdentifier(); $dataSet[$parent][$identifierFields[0]] = $id; $this->_insertRow($parentClass->getTableName(), $dataSet[$parent]); } } else { throw new Doctrine_Mapper_Exception("Unsupported identifier type '$identifierType'."); } $record->assignIdentifier($identifier); } else { foreach ((array) $record->identifier() as $id => $value) { $dataSet[$parent][$parentClass->getColumnName($id)] = $value; } $this->_insertRow($parentClass->getTableName(), $dataSet[$parent]); } } $conn->commit(); } catch (Exception $e) { $conn->rollback(); throw $e; } 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 * @todo Move to Doctrine_Table (which will become Doctrine_Mapper). */ public function doUpdate(Doctrine_Entity $record) { $conn = $this->_mapper->getConnection(); $classMetadata = $this->_mapper->getClassMetadata(); $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_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. * */ public function doDelete(Doctrine_Entity $record) { $conn = $this->_mapper->getConnection(); try { $class = $this->_mapper->getClassMetadata(); $conn->beginInternalTransaction(); $this->_deleteComposites($record); $record->state(Doctrine_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_Entity::STATE_TCLEAN); $this->_mapper->removeRecord($record); $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->_mapper->getClassMetadata(); foreach ($classMetadata->getParentClasses() as $parentClass) { $customJoins[$parentClass] = 'INNER'; } foreach ($classMetadata->getSubclasses() as $subClass) { if ($subClass != $this->_mapper->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->_mapper->getClassMetadata(); $conn = $this->_mapper->getConnection(); $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->_mapper->getClassMetadata()->getFieldNames(); $this->_fieldNames = array_unique($fieldNames); return $fieldNames; } /** * */ public function getFieldName($columnName) { if (isset($this->_columnNameFieldNameMap[$columnName])) { return $this->_columnNameFieldNameMap[$columnName]; } $classMetadata = $this->_mapper->getClassMetadata(); $conn = $this->_mapper->getConnection(); if ($classMetadata->hasColumn($columnName)) { $this->_columnNameFieldNameMap[$columnName] = $classMetadata->getFieldName($columnName); return $this->_columnNameFieldNameMap[$columnName]; } foreach ($classMetadata->getSubclasses() as $subClass) { $subTable = $conn->getClassMetadata($subClass); if ($subTable->hasColumn($columnName)) { $this->_columnNameFieldNameMap[$columnName] = $subTable->getFieldName($columnName); return $this->_columnNameFieldNameMap[$columnName]; } } throw new Doctrine_Mapper_Exception("No field name found for column name '$columnName'."); } /** * * @todo Looks like this better belongs into the ClassMetadata class. */ public function getOwningClass($fieldName) { $conn = $this->_mapper->getConnection(); $classMetadata = $this->_mapper->getClassMetadata(); 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_Entity $record) { $conn = $this->_mapper->getConnection(); $classMetadata = $this->_mapper->getClassMetadata(); $dataSet = array(); $component = $classMetadata->getClassName(); $array = $record->getPrepared(); $classes = array_merge(array($component), $classMetadata->getParentClasses()); foreach ($classes as $class) { $dataSet[$class] = array(); $parentClassMetadata = $conn->getClassMetadata($class); foreach ($parentClassMetadata->getColumns() as $columnName => $definition) { if ((isset($definition['primary']) && $definition['primary'] === true) || (isset($definition['inherited']) && $definition['inherited'] === true)) { continue; } $fieldName = $classMetadata->getFieldName($columnName); if ( ! array_key_exists($fieldName, $array)) { continue; } $dataSet[$class][$columnName] = $array[$fieldName]; } } return $dataSet; } }