diff --git a/lib/Doctrine/ClassMetadata.php b/lib/Doctrine/ClassMetadata.php index 14b865cc8..f1bbba4a3 100644 --- a/lib/Doctrine/ClassMetadata.php +++ b/lib/Doctrine/ClassMetadata.php @@ -24,9 +24,8 @@ /** * A ClassMetadata instance holds all the information (metadata) of an entity and * it's associations and how they're mapped to the relational database. + * It is the backbone of Doctrine's metadata mapping. * - * @package Doctrine - * @subpackage ClassMetadata * @author Roman Borschel * @since 2.0 */ @@ -115,8 +114,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable protected $_generators = array(); /** - * The mapped columns and their mapping definitions. - * Keys are column names and values are mapping definitions. + * The field mappings of the class. + * Keys are field names and values are mapping definitions. * * The mapping definition array has at least the following values: * @@ -128,9 +127,9 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * -- values enum values * ... many more * - * @var array $columns - */ - protected $_mappedColumns = array(); + * @var array + */ + protected $_fieldMappings = array(); /** * The mapped embedded values (value objects). @@ -282,7 +281,29 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * @var array $_invokedMethods method invoker cache */ protected $_invokedMethods = array(); + + /** + * The cached lifecycle listeners. There is only one instance of each + * listener class at any time. + * + * @var array + */ + protected $_lifecycleListenerInstances = array(); + /** + * The registered lifecycle callbacks for Entities of this class. + * + * @var array + */ + protected $_lifecycleCallbacks = array(); + + /** + * The registered lifecycle listeners for Entities of this class. + * + * @var array + */ + protected $_lifecycleListeners = array(); + /** * Constructs a new ClassMetadata instance. @@ -374,7 +395,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function isUniqueField($fieldName) { - $mapping = $this->getColumnMapping($fieldName); + $mapping = $this->getFieldMapping($fieldName); if ($mapping !== false) { return isset($mapping['unique']) && $mapping['unique'] == true; @@ -391,7 +412,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function isNotNull($fieldName) { - $mapping = $this->getColumnMapping($fieldName); + $mapping = $this->getFieldMapping($fieldName); if ($mapping !== false) { return isset($mapping['notnull']) && $mapping['notnull'] == true; @@ -531,7 +552,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable public function getColumnName($fieldName) { return isset($this->_columnNames[$fieldName]) ? - $this->_columnNames[$fieldName] : $fieldName; + $this->_columnNames[$fieldName] : $fieldName; } /** @@ -542,10 +563,10 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable return $this->getColumnMapping($columnName); } - public function getColumnMapping($columnName) + public function getFieldMapping($fieldName) { - return isset($this->_mappedColumns[$columnName]) ? - $this->_mappedColumns[$columnName] : false; + return isset($this->_fieldMappings[$fieldName]) ? + $this->_fieldMappings[$fieldName] : false; } /** @@ -593,6 +614,10 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * found on this class mapping. * This lookup on subclasses is costly but happens only *once* for a column * during hydration because the hydrator caches effectively. + * + * @return string The field name. + * @throws Doctrine::ORM::Exceptions::ClassMetadataException if the field name could + * not be found. */ public function lookupFieldName($lcColumnName) { @@ -626,7 +651,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * Maps a column of the class' database table to a field of the entity. + * Maps a field of the class to a database column. * * @param string $name The name of the column to map. Syntax: columnName [as propertyName]. * The property name is optional. If not used the column will be @@ -639,7 +664,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * * @throws Doctrine_ClassMetadata_Exception If trying use wrongly typed parameter. */ - public function mapColumn($name, $type, $length = null, $options = array(), $prepend = false) + public function mapColumn($name, $type, $length = null, $options = array()) { // converts 0 => 'primary' to 'primary' => true etc. foreach ($options as $k => $option) { @@ -661,18 +686,14 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $columnName = $parts[0]; $lcColumnName = strtolower($parts[0]); - if (isset($this->_mappedColumns[$columnName])) { + if (isset($this->_fieldMappings[$fieldName])) { return; } // Fill column name <-> field name lookup maps - if ($prepend) { - $this->_columnNames = array_merge(array($fieldName => $columnName), $this->_columnNames); - $this->_fieldNames = array_merge(array($columnName => $fieldName), $this->_fieldNames); - } else { - $this->_columnNames[$fieldName] = $columnName; - $this->_fieldNames[$columnName] = $fieldName; - } + $this->_columnNames[$fieldName] = $columnName; + $this->_fieldNames[$columnName] = $fieldName; + $this->_lcColumnToFieldNames[$lcColumnName] = $fieldName; $this->_lcColumnToFieldNames[$lcColumnName] = $fieldName; @@ -701,11 +722,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $options['immutable'] = false; }*/ - if ($prepend) { - $this->_mappedColumns = array_merge(array($columnName => $options), $this->_mappedColumns); - } else { - $this->_mappedColumns[$columnName] = $options; - } + $this->_fieldMappings[$fieldName] = $options; $this->_columnCount++; } @@ -753,24 +770,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable //... } - /** - * setColumn - * - * @param string $name - * @param string $type - * @param integer $length - * @param mixed $options - * @param boolean $prepend Whether to prepend or append the new column to the column list. - * By default the column gets appended. - * @throws Doctrine_Table_Exception if trying use wrongly typed parameter - * @return void - * @deprecated Use mapColumn() - */ - public function setColumn($name, $type, $length = null, $options = array(), $prepend = false) - { - return $this->mapColumn($name, $type, $length, $options, $prepend); - } - /** * Gets the names of all validators that are applied on a field. * @@ -779,9 +778,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getFieldValidators($fieldName) { - $columnName = $this->getColumnName($fieldName); - return isset($this->_mappedColumns[$columnName]['validators']) ? - $this->_mappedColumns[$columnName]['validators'] : array(); + return isset($this->_fieldMappings[$fieldName]['validators']) ? + $this->_fieldMappings[$fieldName]['validators'] : array(); } /** @@ -803,12 +801,11 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getDefaultValueOf($fieldName) { - $columnName = $this->getColumnName($fieldName); - if ( ! isset($this->_mappedColumns[$columnName])) { - throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$columnName." doesn't exist."); + if ( ! isset($this->_fieldMappings[$fieldName])) { + throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$fieldName." doesn't exist."); } - if (isset($this->_mappedColumns[$columnName]['default'])) { - return $this->_mappedColumns[$columnName]['default']; + if (isset($this->_fieldMappings[$fieldName]['default'])) { + return $this->_fieldMappings[$fieldName]['default']; } else { return null; } @@ -867,12 +864,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function hasColumn($columnName) { - return isset($this->_mappedColumns[$columnName]); + return isset($this->_fieldNames[$columnName]); } public function hasMappedColumn($columnName) { - return isset($this->_mappedColumns[$columnName]); + return isset($this->_fieldNames[$columnName]); } /** @@ -890,9 +887,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getEnumValues($fieldName) { - $columnName = $this->getColumnName($fieldName); - if (isset($this->_mappedColumns[$columnName]['values'])) { - return $this->_mappedColumns[$columnName]['values']; + if (isset($this->_fieldMappings[$fieldName]['values'])) { + return $this->_fieldMappings[$fieldName]['values']; } else { return array(); } @@ -917,8 +913,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable $columnName = $this->getColumnName($fieldName); if ( ! $this->_em->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM) && - isset($this->_mappedColumns[$columnName]['values'][$index])) { - $enumValue = $this->_mappedColumns[$columnName]['values'][$index]; + isset($this->_fieldMappings[$fieldName]['values'][$index])) { + $enumValue = $this->_fieldMappings[$fieldName]['values'][$index]; } else { $enumValue = $index; } @@ -973,9 +969,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getCustomAccessor($fieldName) { - $columnName = $this->getColumnName($fieldName); - return isset($this->_mappedColumns[$columnName]['accessor']) ? - $this->_mappedColumns[$columnName]['accessor'] : null; + return isset($this->_fieldMappings[$fieldName]['accessor']) ? + $this->_fieldMappings[$fieldName]['accessor'] : null; } /** @@ -985,9 +980,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getCustomMutator($fieldName) { - $columnName = $this->getColumnName($fieldName); - return isset($this->_mappedColumns[$columnName]['mutator']) ? - $this->_mappedColumns[$columnName]['mutator'] : null; + return isset($this->_fieldMappings[$fieldName]['mutator']) ? + $this->_fieldMappings[$fieldName]['mutator'] : null; } /** @@ -998,7 +992,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getColumns() { - return $this->_mappedColumns; + return $this->_fieldMappings; } /** @@ -1008,7 +1002,12 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getMappedColumns() { - return $this->_mappedColumns; + return $this->_fieldMappings; + } + + public function getFieldMappings() + { + return $this->_fieldMappings; } /** @@ -1023,8 +1022,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable unset($this->_fieldNames[$columnName]); - if (isset($this->_mappedColumns[$columnName])) { - unset($this->_mappedColumns[$columnName]); + if (isset($this->_fieldMappings[$fieldName])) { + unset($this->_fieldMappings[$fieldName]); return true; } $this->_columnCount--; @@ -1040,7 +1039,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable public function getColumnNames(array $fieldNames = null) { if ($fieldNames === null) { - return array_keys($this->_mappedColumns); + return array_keys($this->_fieldNames); } else { $columnNames = array(); foreach ($fieldNames as $fieldName) { @@ -1092,8 +1091,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getMappingForField($fieldName) { - $columnName = $this->getColumnName($fieldName); - return $this->getColumnDefinition($columnName); + return $this->_fieldMappings[$fieldName]; } /** @@ -1104,6 +1102,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getTypeOf($fieldName) { + return $this->getTypeOfColumn($this->getColumnName($fieldName)); } @@ -1115,7 +1114,8 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getTypeOfField($fieldName) { - return $this->getTypeOfColumn($this->getColumnName($fieldName)); + return isset($this->_fieldMappings[$fieldName]) ? + $this->_fieldMappings[$fieldName]['type'] : false; } /** @@ -1125,7 +1125,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getTypeOfColumn($columnName) { - return isset($this->_mappedColumns[$columnName]) ? $this->_mappedColumns[$columnName]['type'] : false; + return $this->getTypeOfField($this->getFieldName($columnName)); } /** @@ -1133,7 +1133,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function getFieldLength($fieldName) { - return $this->_mappedColumns[$this->getColumnName($fieldName)]['length']; + return $this->_fieldMappings[$fieldName]['length']; } /** @@ -1502,7 +1502,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable // Collect foreign keys from the relations $options['foreignKeys'] = array(); if ($parseForeignKeys && $this->getAttribute(Doctrine::ATTR_EXPORT) - & Doctrine::EXPORT_CONSTRAINTS) { + & Doctrine::EXPORT_CONSTRAINTS) { $constraints = array(); $emptyIntegrity = array('onUpdate' => null, 'onDelete' => null); foreach ($this->getRelations() as $name => $relation) { @@ -1683,7 +1683,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable */ public function isInheritedField($fieldName) { - return isset($this->_mappedColumns[$this->getColumnName($fieldName)]['inherited']); + return isset($this->_fieldMappings[$fieldName]['inherited']); } /** @@ -1912,6 +1912,86 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable { } + + /** + * Dispatches the lifecycle event of the given Entity to the registered + * lifecycle callbacks and lifecycle listeners. + * + * @param string $event The lifecycle event. + * @param Entity $entity The Entity on which the event occured. + */ + public function invokeLifecycleCallbacks($lifecycleEvent, Doctrine_Entity $entity) + { + foreach ($this->getLifecycleCallbacks($lifecycleEvent) as $callback) { + $entity->$callback(); + } + foreach ($this->getLifecycleListeners($lifecycleEvent) as $className => $callback) { + if ( ! isset($this->_lifecycleListenerInstances[$className])) { + $this->_lifecycleListenerInstances[$className] = new $className; + } + $this->_lifecycleListenerInstances[$className]->$callback($entity); + } + } + + /** + * Gets the registered lifecycle callbacks for an event. + * + * @param string $event + * @return array + */ + public function getLifecycleCallbacks($event) + { + return isset($this->_lifecycleCallbacks[$event]) ? + $this->_lifecycleCallbacks[$event] : array(); + } + + /** + * Gets the registered lifecycle listeners for an event. + * + * @param string $event + * @return array + */ + public function getLifecycleListeners($event) + { + return isset($this->_lifecycleListeners[$event]) ? + $this->_lifecycleListeners[$event] : array(); + } + + /** + * Adds a lifecycle listener for Entities this class. + * + * Note: If the same listener class is registered more than once, the old + * one will be overridden. + * + * @param string $listenerClass + * @param array $callbacks + */ + public function addLifecycleListener($listenerClass, array $callbacks) + { + $this->_lifecycleListeners[$event][$listenerClass] = array(); + foreach ($callbacks as $method => $event) { + $this->_lifecycleListeners[$event][$listenerClass][] = $method; + } + } + + /** + * Adds a lifecycle callback for Entities of this class. + * + * Note: If a the same callback is registered more than once, the old one + * will be overridden. + * + * @param string $callback + * @param string $event + */ + public function addLifecycleCallback($callback, $event) + { + if ( ! isset($this->_lifecycleCallbacks[$event])) { + $this->_lifecycleCallbacks[$event] = array(); + } + if ( ! in_array($callback, $this->_lifecycleCallbacks[$event])) { + $this->_lifecycleCallbacks[$event][$callback] = $callback; + } + } /** * @todo Implementation. Immutable entities can not be updated or deleted once diff --git a/lib/Doctrine/ClassMetadata/CodeDriver.php b/lib/Doctrine/ClassMetadata/CodeDriver.php index c60477bb2..426e85644 100644 --- a/lib/Doctrine/ClassMetadata/CodeDriver.php +++ b/lib/Doctrine/ClassMetadata/CodeDriver.php @@ -34,12 +34,19 @@ */ class Doctrine_ClassMetadata_CodeDriver { + /** + * Name of the callback method. + * + * @todo We could make the name of the callback methods customizable for users. + */ + const CALLBACK_METHOD = 'initMetadata'; + /** * Loads the metadata for the specified class into the provided container. */ public function loadMetadataForClass($className, Doctrine_ClassMetadata $metadata) { - if ( ! method_exists($className, 'initMetadata')) { + if ( ! method_exists($className, self::CALLBACK_METHOD)) { throw new Doctrine_ClassMetadata_Exception("Unable to load metadata for class" . " '$className'. Callback method 'initMetadata' not found."); } diff --git a/lib/Doctrine/ClassMetadata/Factory.php b/lib/Doctrine/ClassMetadata/Factory.php index 23134a850..d19eca367 100644 --- a/lib/Doctrine/ClassMetadata/Factory.php +++ b/lib/Doctrine/ClassMetadata/Factory.php @@ -133,7 +133,10 @@ class Doctrine_ClassMetadata_Factory foreach ($parentClass->getColumns() as $name => $definition) { $fullName = "$name as " . $parentClass->getFieldName($name); $definition['inherited'] = true; - $subClass->mapColumn($fullName, $definition['type'], $definition['length'], + $subClass->mapColumn( + $fullName, + $definition['type'], + $definition['length'], $definition); } } @@ -154,11 +157,6 @@ class Doctrine_ClassMetadata_Factory protected function _loadMetadata(Doctrine_ClassMetadata $class, $name) { if ( ! class_exists($name) || empty($name)) { - /*try { - throw new Exception(); - } catch (Exception $e) { - echo $e->getTraceAsString(); - }*/ throw new Doctrine_Exception("Couldn't find class " . $name . "."); } @@ -175,11 +173,6 @@ class Doctrine_ClassMetadata_Factory } while ($className = get_parent_class($className)); if ($className === false) { - try { - throw new Exception(); - } catch (Exception $e) { - echo $e->getTraceAsString() . "
"; - } throw new Doctrine_ClassMetadata_Factory_Exception("Unknown component '$className'."); } @@ -207,10 +200,11 @@ class Doctrine_ClassMetadata_Factory protected function _initIdentifier(Doctrine_ClassMetadata $class) { switch (count((array)$class->getIdentifier())) { - case 0: + 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) { - + count($class->getParentClasses()) > 0) { $parents = $class->getParentClasses(); $root = end($parents); $rootClass = $class->getConnection()->getMetadata($root); @@ -237,17 +231,20 @@ class Doctrine_ClassMetadata_Factory $definition, true); } } else { + throw Doctrine_MappingException::identifierRequired($class->getClassName()); + /* Legacy behavior of auto-adding an id field $definition = array('type' => 'integer', 'length' => 20, 'autoincrement' => true, 'primary' => true); - $class->setColumn('id', $definition['type'], $definition['length'], $definition, true); + $class->mapColumn('id', $definition['type'], $definition['length'], $definition, true); $class->setIdentifier(array('id')); $class->setIdentifierType(Doctrine::IDENTIFIER_AUTOINC); + */ } break; - case 1: - foreach ((array)$class->getIdentifier() as $pk) { + case 1: // A single identifier is in the mapping + foreach ($class->getIdentifier() as $pk) { $columnName = $class->getColumnName($pk); $thisColumns = $class->getColumns(); $e = $thisColumns[$columnName]; @@ -294,7 +291,7 @@ class Doctrine_ClassMetadata_Factory $class->setIdentifier(array($pk)); break; - default: + default: // Multiple identifiers are in the mapping so its a composite id $class->setIdentifierType(Doctrine::IDENTIFIER_COMPOSITE); } } diff --git a/lib/Doctrine/Connection/UnitOfWork.php b/lib/Doctrine/Connection/UnitOfWork.php index 9eb7a4494..864cd918f 100644 --- a/lib/Doctrine/Connection/UnitOfWork.php +++ b/lib/Doctrine/Connection/UnitOfWork.php @@ -83,7 +83,7 @@ class Doctrine_Connection_UnitOfWork protected $_removedEntities = array(); /** - * The EntityManager the unit of work belongs to. + * The EntityManager the UnitOfWork belongs to. */ protected $_em; @@ -98,7 +98,7 @@ class Doctrine_Connection_UnitOfWork /** * Constructor. - * Created a new UnitOfWork. + * Creates a new UnitOfWork. * * @param Doctrine_EntityManager $em */ @@ -110,7 +110,8 @@ class Doctrine_Connection_UnitOfWork /** * Commits the unit of work, executing all operations that have been postponed * up to this point. - * + * + * @todo Impl */ public function commit() { @@ -191,7 +192,7 @@ class Doctrine_Connection_UnitOfWork */ public function registerRemoved(Doctrine_Entity $entity) { - if ($entity->isTransient()) { + if ($entity->isNew()) { return; } $this->unregisterIdentity($entity); @@ -511,4 +512,229 @@ class Doctrine_Connection_UnitOfWork return isset($this->_identityMap[$rootClassName][$idHash]); } + public function save(Doctrine_Entity $entity) + { + switch ($entity->_state()) { + case Doctrine_Entity::STATE_CLEAN: + //nothing to do + // ignore $entity but cascade + break; + case Doctrine_Entity::STATE_DIRTY: + // update + $this->registerDirty($entity); + // todo:cascade + break; + case Doctrine_Entity::STATE_TCLEAN: + case Doctrine_Entity::STATE_TDIRTY: + // insert + // if identifier type IDENTITY: + // cascade + // if no transaction is started yet, do it + // force insert (directly to persister) + // else + // cascade + // get & assign the identifier, then registerNew() + break; + } + } + + private function _cascadeSave(Doctrine_Entity $entity) + { + + } + + private function _cascadeDelete(Doctrine_Entity $entity) + { + + } + + + // Stuff from 0.11/1.0 that we will need later (need to modify it though) + + /** + * Collects all records that need to be deleted by applying defined + * application-level delete cascades. + * + * @param array $deletions Map of the records to delete. Keys=Oids Values=Records. + */ + /*private function _collectDeletions(Doctrine_Record $record, array &$deletions) + { + if ( ! $record->exists()) { + return; + } + + $deletions[$record->getOid()] = $record; + $this->_cascadeDelete($record, $deletions); + }*/ + + /** + * Cascades an ongoing delete operation to related objects. Applies only on relations + * that have 'delete' in their cascade options. + * This is an application-level cascade. Related objects that participate in the + * cascade and are not yet loaded are fetched from the database. + * Exception: many-valued relations are always (re-)fetched from the database to + * make sure we have all of them. + * + * @param Doctrine_Record The record for which the delete operation will be cascaded. + * @throws PDOException If something went wrong at database level + * @return void + */ + /*protected function _cascadeDelete(Doctrine_Record $record, array &$deletions) + { + foreach ($record->getTable()->getRelations() as $relation) { + if ($relation->isCascadeDelete()) { + $fieldName = $relation->getAlias(); + // if it's a xToOne relation and the related object is already loaded + // we don't need to refresh. + if ( ! ($relation->getType() == Doctrine_Relation::ONE && isset($record->$fieldName))) { + $record->refreshRelated($relation->getAlias()); + } + $relatedObjects = $record->get($relation->getAlias()); + if ($relatedObjects instanceof Doctrine_Record && $relatedObjects->exists() + && ! isset($deletions[$relatedObjects->getOid()])) { + $this->_collectDeletions($relatedObjects, $deletions); + } else if ($relatedObjects instanceof Doctrine_Collection && count($relatedObjects) > 0) { + // cascade the delete to the other objects + foreach ($relatedObjects as $object) { + if ( ! isset($deletions[$object->getOid()])) { + $this->_collectDeletions($object, $deletions); + } + } + } + } + } + }*/ + + /** + * Executes the deletions for all collected records during a delete operation + * (usually triggered through $record->delete()). + * + * @param array $deletions Map of the records to delete. Keys=Oids Values=Records. + */ + /*private function _executeDeletions(array $deletions) + { + // collect class names + $classNames = array(); + foreach ($deletions as $record) { + $classNames[] = $record->getTable()->getComponentName(); + } + $classNames = array_unique($classNames); + + // order deletes + $executionOrder = $this->buildFlushTree($classNames); + + // execute + try { + $this->conn->beginInternalTransaction(); + + for ($i = count($executionOrder) - 1; $i >= 0; $i--) { + $className = $executionOrder[$i]; + $table = $this->conn->getTable($className); + + // collect identifiers + $identifierMaps = array(); + $deletedRecords = array(); + foreach ($deletions as $oid => $record) { + if ($record->getTable()->getComponentName() == $className) { + $veto = $this->_preDelete($record); + if ( ! $veto) { + $identifierMaps[] = $record->identifier(); + $deletedRecords[] = $record; + unset($deletions[$oid]); + } + } + } + + if (count($deletedRecords) < 1) { + continue; + } + + // extract query parameters (only the identifier values are of interest) + $params = array(); + $columnNames = array(); + foreach ($identifierMaps as $idMap) { + while (list($fieldName, $value) = each($idMap)) { + $params[] = $value; + $columnNames[] = $table->getColumnName($fieldName); + } + } + $columnNames = array_unique($columnNames); + + // delete + $tableName = $table->getTableName(); + $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE "; + + if ($table->isIdentifierComposite()) { + $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps)); + $this->conn->exec($sql, $params); + } else { + $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params)); + $this->conn->exec($sql, $params); + } + + // adjust state, remove from identity map and inform postDelete listeners + foreach ($deletedRecords as $record) { + // currently just for bc! + $this->_deleteCTIParents($table, $record); + //-- + $record->state(Doctrine_Record::STATE_TCLEAN); + $record->getTable()->removeRecord($record); + $this->_postDelete($record); + } + } + + $this->conn->commit(); + // trigger postDelete for records skipped during the deletion (veto!) + foreach ($deletions as $skippedRecord) { + $this->_postDelete($skippedRecord); + } + + return true; + } catch (Exception $e) { + $this->conn->rollback(); + throw $e; + } + }*/ + + /** + * Builds the SQL condition to target multiple records who have a single-column + * primary key. + * + * @param Doctrine_Table $table The table from which the records are going to be deleted. + * @param integer $numRecords The number of records that are going to be deleted. + * @return string The SQL condition "pk = ? OR pk = ? OR pk = ? ..." + */ + /*private function _buildSqlSingleKeyCondition($columnNames, $numRecords) + { + $idColumn = $this->conn->quoteIdentifier($columnNames[0]); + return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?")); + }*/ + + /** + * Builds the SQL condition to target multiple records who have a composite primary key. + * + * @param Doctrine_Table $table The table from which the records are going to be deleted. + * @param integer $numRecords The number of records that are going to be deleted. + * @return string The SQL condition "(pk1 = ? AND pk2 = ?) OR (pk1 = ? AND pk2 = ?) ..." + */ + /*private function _buildSqlCompositeKeyCondition($columnNames, $numRecords) + { + $singleCondition = ""; + foreach ($columnNames as $columnName) { + $columnName = $this->conn->quoteIdentifier($columnName); + if ($singleCondition === "") { + $singleCondition .= "($columnName = ?"; + } else { + $singleCondition .= " AND $columnName = ?"; + } + } + $singleCondition .= ")"; + $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition)); + + return $fullCondition; + }*/ } + + + + diff --git a/lib/Doctrine/Entity.php b/lib/Doctrine/Entity.php index a82e6ba4c..2fcfe95d8 100644 --- a/lib/Doctrine/Entity.php +++ b/lib/Doctrine/Entity.php @@ -22,8 +22,8 @@ #namespace Doctrine::ORM; /** - * Doctrine_Entity - * All record classes should inherit this super class. + * Base class for all Entities (objects with persistent state in a RDBMS that are + * managed by Doctrine). * * NOTE: Methods that are intended for internal use only but must be public * are marked INTERNAL: and begin with an underscore "_" to indicate that they @@ -37,8 +37,10 @@ * @link www.phpdoctrine.org * @since 2.0 * @version $Revision: 4342 $ - * @todo Split up into "Entity" and "ActiveEntity (extends Entity)"??? - * @todo Remove as many methods as possible. + * @todo Split up into "Entity" and "ActiveEntity" (extends Entity) + * @todo Move entity states into a separate enumeration (EntityStates). + * They do not need to be exposed to users in such a way. The states are mainly + * for internal use. */ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable { @@ -47,6 +49,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * An Entity is in dirty state when its properties are changed. */ const STATE_DIRTY = 1; + const STATE_MANAGED_DIRTY = 1; /** * TDIRTY STATE @@ -54,6 +57,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * fields are modified but it is NOT yet persisted into database. */ const STATE_TDIRTY = 2; + const STATE_NEW_DIRTY = 2; /** * CLEAN STATE @@ -61,19 +65,17 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * and none of its properties are changed. */ const STATE_CLEAN = 3; - - /** - * PROXY STATE - * An Entity is in proxy state when its properties are not fully loaded. - */ - //const STATE_PROXY = 4; + const STATE_MANAGED_CLEAN = 3; /** * NEW TCLEAN * An Entity is in transient clean state when it is created and none of its * fields are modified. + * @todo Do we need this state? Just STATE_NEW may be enough without differentiating + * clean/dirty. A new entity is always "dirty". */ const STATE_TCLEAN = 5; + const STATE_NEW_CLEAN = 5; /** * LOCKED STATE @@ -81,9 +83,25 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * * This state is used internally to ensure that circular deletes * and saves will not cause infinite loops. + * @todo Not sure this is a good idea. It is a problematic solution because + * it hides the original state while the locked state is active. */ const STATE_LOCKED = 6; + /** + * A detached Entity is an instance with a persistent identity that is not + * (or no longer) associated with an EntityManager (and a UnitOfWork). + * This means its no longer in the identity map. + */ + const STATE_DETACHED = 7; + + /** + * A removed Entity instance is an instance with a persistent identity, + * associated with an EntityManager, that is scheduled for removal from the + * database. + */ + const STATE_DELETED = 8; + /** * Index used for creating object identifiers (oid's). * @@ -204,114 +222,16 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable // @todo read from attribute the first time and move this initialization elsewhere. self::$_useAutoAccessorOverride = true; } - - /** - * _index - * - * @return integer - */ - /*public static function _index() - { - return self::$_index; - }*/ /** * Returns the object identifier. * * @return integer */ - public function getOid() + final public function getOid() { return $this->_oid; } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the serializing procedure. - */ - public function preSerialize() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the serializing procedure. - */ - public function postSerialize() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the serializing procedure. - */ - public function preUnserialize() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the serializing procedure. - */ - public function postUnserialize() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the saving procedure. - */ - public function preSave() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the saving procedure. - */ - public function postSave() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the deletion procedure. - */ - public function preDelete() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the deletion procedure. - */ - public function postDelete() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the saving procedure only when the record is going to be - * updated. - */ - public function preUpdate() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the saving procedure only when the record is going to be - * updated. - */ - public function postUpdate() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the saving procedure only when the record is going to be - * inserted into the data store the first time. - */ - public function preInsert() - { } - - /** - * Empty template method to provide concrete Record classes with the possibility - * to hook into the saving procedure only when the record is going to be - * inserted into the data store the first time. - */ - public function postInsert() - { } /** * setDefaultValues @@ -345,46 +265,15 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable }*/ /** - * cleanData - * leaves the $data array only with values whose key is a field inside this - * record and returns the values that were removed from $data. Also converts - * any values of 'null' to objects of type Doctrine_Null. - * - * @param array $data data array to be cleaned - * @return array $tmp values cleaned from data - * @todo Remove. Should not be necessary. Slows down instantiation. - */ - /*public function cleanData(&$data) - { - $tmp = $data; - $data = array(); - - $fieldNames = $this->_em->getEntityPersister($this->_entityName)->getFieldNames(); - foreach ($fieldNames as $fieldName) { - if (isset($tmp[$fieldName])) { - $data[$fieldName] = $tmp[$fieldName]; - } else if (array_key_exists($fieldName, $tmp)) { - $data[$fieldName] = Doctrine_Null::$INSTANCE; - } else if ( ! isset($this->_data[$fieldName])) { - $data[$fieldName] = Doctrine_Null::$INSTANCE; - } - unset($tmp[$fieldName]); - } - - return $tmp; - }*/ - - /** - * hydrate * hydrates this object from given array * * @param array $data * @return boolean */ - public function hydrate(array $data) + final public function hydrate(array $data) { $this->_data = array_merge($this->_data, $data); - //$this->_extractIdentifier(true); + $this->_extractIdentifier(); } /** @@ -437,8 +326,8 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable */ public function serialize() { - //$event = new Doctrine_Event($this, Doctrine_Event::RECORD_SERIALIZE); - //$this->preSerialize($event); + //$this->_em->getEventManager()->dispatchEvent(Event::preSerialize); + //$this->_class->dispatchLifecycleEvent(Event::preSerialize, $this); $vars = get_object_vars($this); @@ -533,7 +422,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable /** * INTERNAL: - * returns / assigns the state of this record + * Gets or sets the state of this Entity. * * @param integer|string $state if set, this method tries to set the record state to $state * @see Doctrine_Entity::STATE_* constants @@ -577,7 +466,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * this record represents does not exist anymore) * @return boolean * @todo Implementation to EntityManager. - * @todo ActiveEntity method. + * @todo Move to ActiveEntity (extends Entity). */ public function refresh($deep = false) { @@ -674,9 +563,10 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable }*/ /** - * INTERNAL: (Usage from within extending classes is fine) + * INTERNAL: (Usage from within extending classes is intended) + * * Gets the value of a field (regular field or reference). - * If the property is not yet loaded this method does NOT load it. + * If the field is not yet loaded this method does NOT load it. * * NOTE: Use of this method from outside the scope of an extending class * is strongly discouraged. @@ -684,6 +574,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * @param $name name of the property * @throws Doctrine_Entity_Exception if trying to get an unknown field * @return mixed + * @todo Rename to _get() */ final public function _rawGet($fieldName) { @@ -697,7 +588,8 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** - * INTERNAL: (Usage from within extending classes is fine) + * INTERNAL: (Usage from within extending classes is intended) + * * Sets the value of a field (regular field or reference). * If the field is not yet loaded this method does NOT load it. * @@ -707,6 +599,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * @param $name name of the field * @throws Doctrine_Entity_Exception if trying to get an unknown field * @return mixed + * @todo Rename to _set */ final public function _rawSet($fieldName, $value) { @@ -805,7 +698,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } if (isset($this->_references[$name])) { $this->_references[$name]->setData($value->getData()); - return $this; + return; } } else { $relatedTable = $value->getTable(); @@ -819,12 +712,12 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable if ($rel instanceof Doctrine_Relation_LocalKey) { $idFieldNames = $value->getTable()->getIdentifier(); if ( ! empty($foreignFieldName) && $foreignFieldName != $idFieldNames[0]) { - $this->set($localFieldName, $value->_rawGet($foreignFieldName), false); + $this->set($localFieldName, $value->_rawGet($foreignFieldName)); } else { - $this->set($localFieldName, $value, false); + $this->set($localFieldName, $value); } } else { - $value->set($foreignFieldName, $this, false); + $value->set($foreignFieldName, $this); } } } else if ($rel instanceof Doctrine_Relation_Association) { @@ -894,6 +787,13 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } } + /** + * Gets the custom mutator method for a field, if it exists. + * + * @param string $fieldName The field name. + * @return mixed The name of the custom mutator or FALSE, if the field does + * not have a custom mutator. + */ private function _getCustomMutator($fieldName) { if ( ! isset(self::$_mutatorCache[$this->_entityName][$fieldName])) { @@ -916,6 +816,13 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable return self::$_mutatorCache[$this->_entityName][$fieldName]; } + /** + * Gets the custom accessor method of a field, if it exists. + * + * @param string $fieldName The field name. + * @return mixed The name of the custom accessor method, or FALSE if the + * field does not have a custom accessor. + */ private function _getCustomAccessor($fieldName) { if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { @@ -952,10 +859,6 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * * @param mixed $name name of the property or reference * @param mixed $value value of the property or reference - * @param boolean $load whether or not to refresh / load the uninitialized record data - * - * @throws Doctrine_Record_Exception if trying to set a value for unknown property / related component - * @throws Doctrine_Record_Exception if trying to set a value of wrong type for related component */ final public function set($fieldName, $value) { @@ -978,9 +881,9 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable if ($old != $value) { $this->_data[$fieldName] = $value; - $this->_modified[] = $fieldName; + $this->_modified[$fieldName] = array($old => $value); - if ($this->isTransient() && $this->_class->isIdentifier($fieldName)) { + if ($this->isNew() && $this->_class->isIdentifier($fieldName)) { $this->_id[$fieldName] = $value; } @@ -1001,7 +904,9 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** - * contains + * Checks whether a field is set (not null). + * + * NOTE: Invoked by Doctrine::ORM::Access#__isset(). * * @param string $name * @return boolean @@ -1025,6 +930,10 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** + * Clears the value of a field. + * + * NOTE: Invoked by Doctrine::ORM::Access#__unset(). + * * @param string $name * @return void */ @@ -1220,8 +1129,8 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** - * Merges this record with an array of values - * or with another existing instance of this object + * Merges this Entity with an array of values + * or with another existing instance of. * * @param mixed $data Data to merge. Either another instance of this model or an array * @param bool $deep Bool value for whether or not to merge the data deep @@ -1341,41 +1250,17 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * Checks whether the entity already has a persistent state. * * @return boolean TRUE if the object is new, FALSE otherwise. - * @deprecated Use isTransient() */ final public function isNew() { return $this->_state == self::STATE_TCLEAN || $this->_state == self::STATE_TDIRTY; } - - /** - * Checks whether the entity already has a persistent state. - * - * @return boolean TRUE if the object is new, FALSE otherwise. - */ - final public function isTransient() - { - return $this->_state == self::STATE_TCLEAN || $this->_state == self::STATE_TDIRTY; - } - - /** - * Checks whether the entity has been modified since it was last synchronized - * with the database. - * - * @return boolean TRUE if the object has been modified, FALSE otherwise. - */ - final public function isDirty() - { - return ($this->_state === Doctrine_Entity::STATE_DIRTY || - $this->_state === Doctrine_Entity::STATE_TDIRTY); - } /** * Checks whether the entity has been modified since it was last synchronized * with the database. * * @return boolean TRUE if the object has been modified, FALSE otherwise. - * @deprecated Use isDirty() */ final public function isModified() { @@ -1512,7 +1397,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * @return boolean * @todo Better name? hasAssociation() ? */ - public function hasReference($name) + final public function hasReference($name) { return isset($this->_references[$name]); } @@ -1523,7 +1408,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable * @param string $name * @throws Doctrine_Record_Exception if trying to get an unknown related component */ - public function obtainReference($name) + final public function obtainReference($name) { if (isset($this->_references[$name])) { return $this->_references[$name]; @@ -1774,6 +1659,8 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable /** * Gets the ClassMetadata object that describes the entity class. + * + * @return Doctrine::ORM::Mapping::ClassMetadata */ final public function getClassMetadata() { @@ -1781,9 +1668,10 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** - * Enter description here... + * Gets the EntityManager that is responsible for the persistence of + * the Entity. * - * @return unknown + * @return Doctrine::ORM::EntityManager */ final public function getEntityManager() { @@ -1791,9 +1679,9 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Serializable } /** - * Enter description here... + * Gets the EntityRepository of the Entity. * - * @return unknown + * @return Doctrine::ORM::EntityRepository */ final public function getRepository() { diff --git a/lib/Doctrine/EntityManager.php b/lib/Doctrine/EntityManager.php index f00041b86..973d2cd92 100644 --- a/lib/Doctrine/EntityManager.php +++ b/lib/Doctrine/EntityManager.php @@ -31,10 +31,8 @@ /** - * The EntityManager is a central access point to ORM functionality. + * The EntityManager is the central access point to ORM functionality. * - * @package Doctrine - * @subpackage EntityManager * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 2.0 @@ -88,7 +86,6 @@ class Doctrine_EntityManager /** * The EntityPersister instances. - * @todo Implementation. * * @var array */ @@ -109,7 +106,7 @@ class Doctrine_EntityManager private $_flushMode = 'commit'; /** - * The unit of work. + * The unit of work used to coordinate object-level transactions. * * @var UnitOfWork */ @@ -122,13 +119,6 @@ class Doctrine_EntityManager */ private $_eventManager; - /** - * Enter description here... - * - * @var unknown_type - */ - //private $_dataTemplates = array(); - /** * Container that is used temporarily during hydration. * @@ -163,7 +153,7 @@ class Doctrine_EntityManager } /** - * Returns the metadata for a class. Alias for getClassMetadata(). + * Gets the metadata for a class. Alias for getClassMetadata(). * * @return Doctrine_Metadata * @todo package:orm @@ -172,6 +162,17 @@ class Doctrine_EntityManager { return $this->getClassMetadata($className); } + + /** + * Gets the transaction object used by the EntityManager to manage + * database transactions. + * + * @return Doctrine::DBAL::Transaction + */ + public function getTransaction() + { + return $this->_conn->getTransaction(); + } /** * Returns the metadata for a class. @@ -184,8 +185,8 @@ class Doctrine_EntityManager } /** - * Sets the driver that is used to obtain metadata informations about entity - * classes. + * Sets the driver that is used to obtain metadata mapping information + * about Entities. * * @param $driver The driver to use. */ @@ -197,7 +198,8 @@ class Doctrine_EntityManager /** * Creates a new Doctrine_Query object that operates on this connection. * - * @return Doctrine_Query + * @param string The DQL string. + * @return Doctrine::ORM::Query * @todo package:orm */ public function createQuery($dql = "") @@ -211,10 +213,12 @@ class Doctrine_EntityManager } /** - * Enter description here... + * Gets the EntityPersister for an Entity. + * + * This is usually not of interest for users, mainly for internal use. * - * @param unknown_type $entityName - * @return unknown + * @param string $entityName The name of the Entity. + * @return Doctrine::ORM::Internal::EntityPersister */ public function getEntityPersister($entityName) { @@ -241,30 +245,6 @@ class Doctrine_EntityManager return $this->_unitOfWork->unregisterIdentity($entity); } - /** - * Returns the current internal transaction nesting level. - * - * @return integer The nesting level. A value of 0 means theres no active transaction. - * @todo package:orm??? - */ - public function getInternalTransactionLevel() - { - return $this->transaction->getInternalTransactionLevel(); - } - - /** - * Initiates a transaction. - * - * This method must only be used by Doctrine itself to initiate transactions. - * Userland-code must use {@link beginTransaction()}. - * - * @todo package:orm??? - */ - public function beginInternalTransaction($savepoint = null) - { - return $this->transaction->beginInternalTransaction($savepoint); - } - /** * Creates a query with the specified name. * @@ -307,16 +287,16 @@ class Doctrine_EntityManager */ public function flush() { - $this->beginInternalTransaction(); $this->_unitOfWork->flush(); - $this->commit(); } /** - * Enter description here... + * Finds an Entity by its identifier. + * This is just a convenient shortcut for getRepository()->find(). * - * @param unknown_type $entityName - * @param unknown_type $identifier + * @param string $entityName + * @param mixed $identifier + * @return Doctrine::ORM::Entity */ public function find($entityName, $identifier) { @@ -356,11 +336,8 @@ class Doctrine_EntityManager { if ($entityName === null) { $this->_unitOfWork->detachAll(); - foreach ($this->_mappers as $mapper) { - $mapper->clear(); // clear identity map of each mapper - } } else { - $this->getMapper($entityName)->clear(); + //... } } @@ -370,7 +347,7 @@ class Doctrine_EntityManager */ public function close() { - + //Doctrine_EntityManagerFactory::releaseManager($this); } /** @@ -408,24 +385,7 @@ class Doctrine_EntityManager */ public function save(Doctrine_Entity $entity) { - $state = $entity->_state(); - if ($state == Doctrine_Entity::STATE_CLEAN || $state == Doctrine_Entity::STATE_LOCKED) { - return; - } - - //... - //$this->_unitOfWork-> - switch ($entity->_state()) { - case Doctrine_Entity::STATE_CLEAN: - //nothing to do - break; - case Doctrine_Entity::STATE_DIRTY: - $this->_unitOfWork->registerDirty($entity); - break; - case Doctrine_Entity::STATE_TCLEAN: - case Doctrine_Entity::STATE_TDIRTY: - //... - } + $this->_unitOfWork->save($entity); } /** @@ -433,14 +393,14 @@ class Doctrine_EntityManager */ public function delete(Doctrine_Entity $entity) { - //... + $this->_unitOfWork->delete($entity); } /** - * Gets the repository for the given entity name. + * Gets the repository for an Entity. * - * @return Doctrine_EntityRepository The repository. - * @todo Implementation. + * @param string $entityName The name of the Entity. + * @return Doctrine::ORM::EntityRepository The repository. */ public function getRepository($entityName) { diff --git a/lib/Doctrine/EntityPersister/Abstract.php b/lib/Doctrine/EntityPersister/Abstract.php index adaa86e9a..d078ff725 100644 --- a/lib/Doctrine/EntityPersister/Abstract.php +++ b/lib/Doctrine/EntityPersister/Abstract.php @@ -62,21 +62,11 @@ abstract class Doctrine_EntityPersister_Abstract */ protected $_em; - /** - * The concrete mapping strategy that is used. - */ - protected $_mappingStrategy; - /** * Null object. */ private $_nullObject; - /** - * A list of registered entity listeners. - */ - private $_entityListeners = array(); - /** * Enter description here... * @@ -318,8 +308,8 @@ abstract class Doctrine_EntityPersister_Abstract */ protected function _insertOrUpdate(Doctrine_Entity $record) { - $record->preSave(); - $this->notifyEntityListeners($record, 'preSave', Doctrine_Event::RECORD_SAVE); + //$record->preSave(); + //$this->notifyEntityListeners($record, 'preSave', Doctrine_Event::RECORD_SAVE); switch ($record->_state()) { case Doctrine_Entity::STATE_TDIRTY: @@ -335,8 +325,8 @@ abstract class Doctrine_EntityPersister_Abstract break; } - $record->postSave(); - $this->notifyEntityListeners($record, 'postSave', Doctrine_Event::RECORD_SAVE); + //$record->postSave(); + //$this->notifyEntityListeners($record, 'postSave', Doctrine_Event::RECORD_SAVE); } /** @@ -351,7 +341,6 @@ abstract class Doctrine_EntityPersister_Abstract } /** - * _saveRelated * saves all related records to $record * * @throws PDOException if something went wrong at database level diff --git a/lib/Doctrine/EntityPersister/JoinedSubclass.php b/lib/Doctrine/EntityPersister/JoinedSubclass.php index 9b8b706a2..1e48ce1a6 100644 --- a/lib/Doctrine/EntityPersister/JoinedSubclass.php +++ b/lib/Doctrine/EntityPersister/JoinedSubclass.php @@ -20,12 +20,10 @@ */ /** - * The joined mapping strategy maps a single entity instance to several tables in the + * 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 - * @package Doctrine - * @subpackage JoinedSubclass * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ * @link www.phpdoctrine.org @@ -33,7 +31,7 @@ */ class Doctrine_EntityPersister_JoinedSubclass extends Doctrine_EntityPersister_Abstract { - protected $_columnNameFieldNameMap = array(); + //protected $_columnNameFieldNameMap = array(); /** * Inserts an entity that is part of a Class Table Inheritance hierarchy. @@ -94,7 +92,6 @@ class Doctrine_EntityPersister_JoinedSubclass extends Doctrine_EntityPersister_A * * @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). */ protected function _doUpdate(Doctrine_Entity $record) { @@ -223,34 +220,6 @@ class Doctrine_EntityPersister_JoinedSubclass extends Doctrine_EntityPersister_A return $fieldNames; } - /** - * - */ - /*public function getFieldName($columnName) - { - if (isset($this->_columnNameFieldNameMap[$columnName])) { - return $this->_columnNameFieldNameMap[$columnName]; - } - - $classMetadata = $this->_classMetadata; - $conn = $this->_conn; - - 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. diff --git a/lib/Doctrine/EntityRepository.php b/lib/Doctrine/EntityRepository.php index a037721b4..496addf48 100644 --- a/lib/Doctrine/EntityRepository.php +++ b/lib/Doctrine/EntityRepository.php @@ -22,6 +22,7 @@ #namespace Doctrine::ORM; /** + * A repository provides the illusion of an in-memory Entity store. * Base class for all custom user-defined repositories. * Provides basic finder methods, common to all repositories. * @@ -48,7 +49,6 @@ class Doctrine_EntityRepository } /** - * createQuery * creates a new Doctrine_Query object and adds the component name * of this table as the query 'from' part * diff --git a/lib/Doctrine/EventListener.php b/lib/Doctrine/EventListener.php index 7afc68f21..6a4c4c9bc 100644 --- a/lib/Doctrine/EventListener.php +++ b/lib/Doctrine/EventListener.php @@ -29,93 +29,11 @@ * @subpackage EventListener * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org - * @since 1.0 + * @since 2.0 * @version $Revision$ * @todo Remove. The 2.0 event system has no listener interfaces. */ -class Doctrine_EventListener implements Doctrine_EventListener_Interface +interface Doctrine_EventSubscriber { - public function preClose(Doctrine_Event $event) - { } - public function postClose(Doctrine_Event $event) - { } - - public function onCollectionDelete(Doctrine_Collection $collection) - { } - public function onPreCollectionDelete(Doctrine_Collection $collection) - { } - - public function onOpen(Doctrine_Connection $connection) - { } - - public function preTransactionCommit(Doctrine_Event $event) - { } - public function postTransactionCommit(Doctrine_Event $event) - { } - - public function preTransactionRollback(Doctrine_Event $event) - { } - public function postTransactionRollback(Doctrine_Event $event) - { } - - public function preTransactionBegin(Doctrine_Event $event) - { } - public function postTransactionBegin(Doctrine_Event $event) - { } - - - public function preSavepointCommit(Doctrine_Event $event) - { } - public function postSavepointCommit(Doctrine_Event $event) - { } - - public function preSavepointRollback(Doctrine_Event $event) - { } - public function postSavepointRollback(Doctrine_Event $event) - { } - - public function preSavepointCreate(Doctrine_Event $event) - { } - public function postSavepointCreate(Doctrine_Event $event) - { } - - public function postConnect(Doctrine_Event $event) - { } - public function preConnect(Doctrine_Event $event) - { } - - public function preQuery(Doctrine_Event $event) - { } - public function postQuery(Doctrine_Event $event) - { } - - public function prePrepare(Doctrine_Event $event) - { } - public function postPrepare(Doctrine_Event $event) - { } - - public function preExec(Doctrine_Event $event) - { } - public function postExec(Doctrine_Event $event) - { } - - public function preError(Doctrine_Event $event) - { } - public function postError(Doctrine_Event $event) - { } - - public function preFetch(Doctrine_Event $event) - { } - public function postFetch(Doctrine_Event $event) - { } - - public function preFetchAll(Doctrine_Event $event) - { } - public function postFetchAll(Doctrine_Event $event) - { } - - public function preStmtExecute(Doctrine_Event $event) - { } - public function postStmtExecute(Doctrine_Event $event) - { } + public function getSubscribedEvents(); } diff --git a/lib/Doctrine/EventManager.php b/lib/Doctrine/EventManager.php index 1dee726f6..46ec435a3 100644 --- a/lib/Doctrine/EventManager.php +++ b/lib/Doctrine/EventManager.php @@ -56,9 +56,9 @@ class Doctrine_EventManager foreach ($this->_listeners[$callback] as $listener) { $listener->$callback($event); } + return ! $event->getDefaultPrevented(); } - - return ! $event->getDefaultPrevented(); + return true; } /** diff --git a/lib/Doctrine/HydratorNew.php b/lib/Doctrine/HydratorNew.php index 78ab83d82..0b25a64f8 100644 --- a/lib/Doctrine/HydratorNew.php +++ b/lib/Doctrine/HydratorNew.php @@ -227,6 +227,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract } else if (isset($resultPointers[$parent])) { $baseElement =& $resultPointers[$parent]; } else { + unset($prev[$dqlAlias]); // Ticket #1228 continue; } @@ -256,7 +257,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract } else { // x-1 relation $oneToOne = true; - if ( ! isset($nonemptyComponents[$dqlAlias])) { + if ( ! isset($nonemptyComponents[$dqlAlias]) && + ! $driver->isFieldSet($baseElement, $relationAlias)) { $driver->setRelatedElement($baseElement, $relationAlias, $driver->getNullPointer()); } else if ( ! $driver->isFieldSet($baseElement, $relationAlias)) { @@ -293,8 +295,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract } /** - * _setLastElement - * * sets the last element of given data array / collection * as previous element * @@ -308,7 +308,8 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract */ protected function _setLastElement(&$resultPointers, &$coll, $index, $dqlAlias, $oneToOne) { - if ($coll === $this->_nullObject) { + if ($coll === $this->_nullObject || $coll === null) { + unset($resultPointers[$dqlAlias]); // Ticket #1228 return false; } @@ -330,8 +331,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $resultPointers[$dqlAlias] = $coll; } else if (count($coll) > 0) { $resultPointers[$dqlAlias] = $coll->getLast(); - } else if (isset($resultPointers[$dqlAlias])) { - unset($resultPointers[$dqlAlias]); } } diff --git a/lib/Doctrine/Query/Production/IndexBy.php b/lib/Doctrine/Query/Production/IndexBy.php index 6bde673f9..794446109 100644 --- a/lib/Doctrine/Query/Production/IndexBy.php +++ b/lib/Doctrine/Query/Production/IndexBy.php @@ -77,7 +77,7 @@ class Doctrine_Query_Production_IndexBy extends Doctrine_Query_Production } // The INDEXBY field must be either the (primary && not part of composite pk) || (unique && notnull) - $columnMapping = $classMetadata->getColumnMapping($this->_fieldName); + $columnMapping = $classMetadata->getFieldMapping($this->_fieldName); if ( ! $classMetadata->isIdentifier($this->_fieldName) && ! $classMetadata->isUniqueField($this->_fieldName) && ! $classMetadata->isNotNull($this->_fieldName)) { $this->_parser->semanticalError( diff --git a/tests/Orm/Entity/AccessorTestCase.php b/tests/Orm/Entity/AccessorTestCase.php index 0bee542de..863d282f0 100644 --- a/tests/Orm/Entity/AccessorTestCase.php +++ b/tests/Orm/Entity/AccessorTestCase.php @@ -22,6 +22,7 @@ class CustomAccessorMutatorTestEntity extends Doctrine_Entity { public static function initMetadata($class) { + $class->mapColumn('id', 'integer', 4, array('primary')); $class->mapColumn('username', 'string', 50, array( 'accessor' => 'getUsernameCustom', 'mutator' => 'setUsernameCustom')); @@ -42,6 +43,7 @@ class MagicAccessorMutatorTestEntity extends Doctrine_Entity { public static function initMetadata($class) { + $class->mapColumn('id', 'integer', 4, array('primary')); $class->mapColumn('username', 'string', 50, array()); } diff --git a/tests/Orm/Entity/ConstructorTest.php b/tests/Orm/Entity/ConstructorTest.php index 5e0c2f99e..137027bd3 100644 --- a/tests/Orm/Entity/ConstructorTest.php +++ b/tests/Orm/Entity/ConstructorTest.php @@ -6,7 +6,7 @@ class Orm_Entity_ConstructorTest extends Doctrine_OrmTestCase public function testFieldInitializationInConstructor() { $entity = new ConstructorTestEntity1("romanb"); - $this->assertTrue($entity->isTransient()); + $this->assertTrue($entity->isNew()); $this->assertEquals("romanb", $entity->username); } } @@ -16,7 +16,7 @@ class ConstructorTestEntity1 extends Doctrine_Entity public function __construct($username = null) { parent::__construct(); - if ($this->isTransient()) { + if ($this->isNew()) { $this->username = $username; } } @@ -24,6 +24,7 @@ class ConstructorTestEntity1 extends Doctrine_Entity /* The mapping definition */ public static function initMetadata($class) { + $class->mapColumn('id', 'integer', 4, array('primary')); $class->mapColumn('username', 'string', 50, array()); } } diff --git a/tests/models/forum/ForumBoard.php b/tests/models/forum/ForumBoard.php index f68eeca8c..11dc974ca 100755 --- a/tests/models/forum/ForumBoard.php +++ b/tests/models/forum/ForumBoard.php @@ -1,9 +1,24 @@ mapColumn('position', 'integer'); - $class->mapColumn('category_id', 'integer'); - $class->hasOne('ForumCategory as category', + public static function initMetadata($metadata) { + /*$metadata->mapField(array( + 'fieldName' => 'id', + 'id' => true, + 'type' => 'integer', + 'length' => 4 + )); + */ + $metadata->mapColumn('id', 'integer', 4, array('primary')); + $metadata->mapColumn('position', 'integer'); + $metadata->mapColumn('category_id', 'integer'); + $metadata->hasOne('ForumCategory as category', array('local' => 'category_id', 'foreign' => 'id')); + /* + $metadata->mapOneToOne(array( + 'fieldName' => 'category', // optional, defaults to targetEntity + 'targetEntity' => 'ForumCategory', + 'joinColumns' => array('category_id' => 'id') + )); + */ } } diff --git a/tests/models/forum/ForumCategory.php b/tests/models/forum/ForumCategory.php index a5f166d31..0b1bbb340 100755 --- a/tests/models/forum/ForumCategory.php +++ b/tests/models/forum/ForumCategory.php @@ -1,6 +1,7 @@ mapColumn('id', 'integer', 4, array('primary')); $class->mapColumn('position', 'integer'); $class->mapColumn('name', 'string', 255); $class->hasMany('ForumBoard as boards', array(