From 7b711ae70e768f1b54bc20926c2ca868432808f0 Mon Sep 17 00:00:00 2001 From: romanb Date: Sat, 24 May 2008 17:10:45 +0000 Subject: [PATCH] refactorings. merged hydration bugfix from 0.11. --- lib/Doctrine/ClassMetadata.php | 2 +- lib/Doctrine/Entity.php | 44 +- lib/Doctrine/EntityRepository.php | 9 +- lib/Doctrine/HydratorNew.php | 46 +- lib/Doctrine/Mapper.php | 747 ------------------------ lib/Doctrine/Mapper/DefaultStrategy.php | 120 ---- lib/Doctrine/Mapper/Exception.php | 3 - lib/Doctrine/Mapper/JoinedStrategy.php | 318 ---------- lib/Doctrine/Mapper/Strategy.php | 156 ----- lib/Doctrine/Null.php | 15 +- tests/models/forum/ForumUser.php | 10 +- 11 files changed, 51 insertions(+), 1419 deletions(-) delete mode 100644 lib/Doctrine/Mapper.php delete mode 100644 lib/Doctrine/Mapper/DefaultStrategy.php delete mode 100644 lib/Doctrine/Mapper/Exception.php delete mode 100644 lib/Doctrine/Mapper/JoinedStrategy.php delete mode 100644 lib/Doctrine/Mapper/Strategy.php diff --git a/lib/Doctrine/ClassMetadata.php b/lib/Doctrine/ClassMetadata.php index ef1d28e1c..c47fa72b1 100644 --- a/lib/Doctrine/ClassMetadata.php +++ b/lib/Doctrine/ClassMetadata.php @@ -867,7 +867,7 @@ class Doctrine_ClassMetadata extends Doctrine_Configurable implements Serializab { $columnName = $this->getColumnName($fieldName); return isset($this->_mappedColumns[$columnName]['accessor']) ? - $this->_mappedColumns[$columnName]['accessor'] : null; + $this->_mappedColumns[$columnName]['accessor'] : null; } /** diff --git a/lib/Doctrine/Entity.php b/lib/Doctrine/Entity.php index c0621d55f..6e25f4cde 100644 --- a/lib/Doctrine/Entity.php +++ b/lib/Doctrine/Entity.php @@ -994,26 +994,7 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Countable, Ite */ public function get($fieldName, $load = false) { - /*// check for custom accessor, if not done yet. - if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { - if (self::$_useAutoAccessorOverride) { - $getterMethod = 'get' . Doctrine::classify($fieldName); - if (method_exists($this, $getterMethod)) { - self::$_accessorCache[$this->_entityName][$fieldName] = $getterMethod; - } else { - self::$_accessorCache[$this->_entityName][$fieldName] = false; - } - } - if ($getter = $this->_class->getCustomAccessor($fieldName)) { - self::$_accessorCache[$this->_entityName][$fieldName] = $getter; - } else if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { - self::$_accessorCache[$this->_entityName][$fieldName] = false; - } - } - // invoke custom accessor, if it exists. - if ($getter = self::$_accessorCache[$this->_entityName][$fieldName]) { - return $this->$getter(); - }*/ + $this->_invokeCustomAccessor($fieldName); // Use built-in accessor functionality $nullObj = Doctrine_Null::$INSTANCE; @@ -1046,6 +1027,29 @@ abstract class Doctrine_Entity extends Doctrine_Access implements Countable, Ite } } + private function _invokeCustomAccessor($fieldName) + { + if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { + if (self::$_useAutoAccessorOverride) { + $getterMethod = 'get' . Doctrine::classify($fieldName); + if (method_exists($this, $getterMethod)) { + self::$_accessorCache[$this->_entityName][$fieldName] = $getterMethod; + } else { + self::$_accessorCache[$this->_entityName][$fieldName] = false; + } + } + if ($getter = $this->_class->getCustomAccessor($fieldName)) { + self::$_accessorCache[$this->_entityName][$fieldName] = $getter; + } else if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { + self::$_accessorCache[$this->_entityName][$fieldName] = false; + } + } + // invoke custom accessor, if it exists. + if ($getter = self::$_accessorCache[$this->_entityName][$fieldName]) { + return $this->$getter(); + } + } + public function getClassName() { return $this->_entityName; diff --git a/lib/Doctrine/EntityRepository.php b/lib/Doctrine/EntityRepository.php index 4d6a3c1c9..07773f84b 100644 --- a/lib/Doctrine/EntityRepository.php +++ b/lib/Doctrine/EntityRepository.php @@ -59,7 +59,7 @@ class Doctrine_EntityRepository if ( ! empty($alias)) { $alias = ' ' . trim($alias); } - return Doctrine_Query::create($this->_em)->from($this->_entityName . $alias); + return $this->_em->createQuery()->from($this->_entityName . $alias); } /** @@ -101,11 +101,10 @@ class Doctrine_EntityRepository } /** - * Finds all entities of the mapper's class. - * Use with care. + * Finds all entities in the repository. * - * @param int $hydrationMode Doctrine::HYDRATE_ARRAY or Doctrine::HYDRATE_RECORD - * @return Doctrine_Collection + * @param int $hydrationMode + * @return mixed */ public function findAll($hydrationMode = null) { diff --git a/lib/Doctrine/HydratorNew.php b/lib/Doctrine/HydratorNew.php index e3b981e2f..5c85bae06 100644 --- a/lib/Doctrine/HydratorNew.php +++ b/lib/Doctrine/HydratorNew.php @@ -102,8 +102,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $driver = new Doctrine_Hydrator_RecordDriver($this->_em); } - $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null); - $s = microtime(true); // Used variables during hydration @@ -112,8 +110,6 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getClassName(); // if only one class is involved we can make our lives easier $isSimpleQuery = count($this->_queryComponents) <= 1; - // Holds hydration listeners that get called during hydration - $listeners = array(); // Lookup map to quickly discover/lookup existing records in the result // It's the identifier "memory" $identifierMap = array(); @@ -142,13 +138,11 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract // disable lazy-loading of related elements during hydration $component['table']->setAttribute(Doctrine::ATTR_LOAD_REFERENCES, false); $componentName = $component['table']->getClassName(); - //$listeners[$componentName] = $component['table']->getRecordListener(); $identifierMap[$dqlAlias] = array(); $resultPointers[$dqlAlias] = array(); $idTemplate[$dqlAlias] = ''; } - $cache = array(); // Evaluate HYDRATE_SINGLE_SCALAR if ($hydrationMode == Doctrine::HYDRATE_SINGLE_SCALAR) { @@ -177,20 +171,10 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $class = $this->_queryComponents[$rootAlias]['table']; $componentName = $class->getComponentName(); - // just event stuff - //$event->set('data', $rowData[$rootAlias]); - //$listeners[$componentName]->preHydrate($event); - //-- - // Check for an existing element $index = false; if ($isSimpleQuery || ! isset($identifierMap[$rootAlias][$id[$rootAlias]])) { $element = $driver->getElement($rowData[$rootAlias], $componentName); - - // just event stuff - //$event->set('data', $element); - //$listeners[$componentName]->postHydrate($event); - //-- // do we need to index by a custom field? if ($field = $this->_getCustomIndexField($rootAlias)) { @@ -230,17 +214,11 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract } // now hydrate the rest of the data found in the current row, that belongs to other - // (related) components. + // (related) classes. foreach ($rowData as $dqlAlias => $data) { $index = false; $map = $this->_queryComponents[$dqlAlias]; - $componentName = $map['table']->getComponentName(); - - // just event stuff - //$event->set('data', $data); - //$listeners[$componentName]->preHydrate($event); - //-- - + $componentName = $map['table']->getClassName(); $parent = $map['parent']; $relation = $map['relation']; $relationAlias = $relation->getAlias(); @@ -263,30 +241,18 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $oneToOne = false; if (isset($nonemptyComponents[$dqlAlias])) { $driver->initRelatedCollection($baseElement, $relationAlias); - if ( ! isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]])) { + $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); + $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; + $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; + if ( ! $indexExists || ! $indexIsValid) { $element = $driver->getElement($data, $componentName); - - // just event stuff - //$event->set('data', $element); - //$listeners[$componentName]->postHydrate($event); - //-- - if ($field = $this->_getCustomIndexField($dqlAlias)) { - // TODO: must be checked in the parser. fields used in INDEXBY - // must be a) the primary key or b) unique & notnull - /*if ($driver->isIndexKeyInUse($baseElement, $relationAlias, $field)) { - throw Doctrine_Hydrator_Exception::nonUniqueKeyMapping(); - } else if ( ! $driver->isFieldSet($element, $field)) { - throw Doctrine_Hydrator_Exception::nonExistantFieldUsedAsIndex($field); - }*/ $driver->addRelatedIndexedElement($baseElement, $relationAlias, $element, $field); } else { $driver->addRelatedElement($baseElement, $relationAlias, $element); } $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey( $driver->getReferenceValue($baseElement, $relationAlias)); - } else { - $index = $identifierMap[$path][$id[$parent]][$id[$dqlAlias]]; } } else if ( ! isset($baseElement[$relationAlias])) { $driver->setRelatedElement($baseElement, $relationAlias, diff --git a/lib/Doctrine/Mapper.php b/lib/Doctrine/Mapper.php deleted file mode 100644 index 6a1b57182..000000000 --- a/lib/Doctrine/Mapper.php +++ /dev/null @@ -1,747 +0,0 @@ -. - */ - -/** - * A Mapper is responsible for mapping between the domain model and the database - * back and forth. Each entity in the domain model has a corresponding mapper. - * - * @author Konsta Vesterinen - * @author Roman Borschel - * @package Doctrine - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @version $Revision: 3406 $ - * @link www.phpdoctrine.org - * @since 2.0 - * @todo Rename to "EntityPersister" or similar. - */ -class Doctrine_Mapper -{ - /** - * Metadata object that descibes the mapping of the mapped entity class. - * - * @var Doctrine_ClassMetadata - */ - protected $_classMetadata; - - /** - * The name of the domain class this mapper is used for. - */ - protected $_domainClassName; - - /** - * The Doctrine_Connection object that the database connection of this mapper. - * - * @var Doctrine_Connection $conn - */ - protected $_conn; - - /** - * 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... - * - * @var unknown_type - * @todo To EntityManager. - */ - private $_dataTemplate = array(); - - - /** - * Constructs a new mapper. - * - * @param string $name The name of the domain class this mapper is used for. - * @param Doctrine_Table $table The table object used for the mapping procedure. - * @throws Doctrine_Connection_Exception if there are no opened connections - */ - public function __construct($name, Doctrine_ClassMetadata $classMetadata) - { - $this->_domainClassName = $name; - $this->_conn = $classMetadata->getConnection(); - $this->_classMetadata = $classMetadata; - $this->_nullObject = Doctrine_Null::$INSTANCE; - if ($classMetadata->getInheritanceType() == Doctrine::INHERITANCE_TYPE_JOINED) { - $this->_mappingStrategy = new Doctrine_Mapper_JoinedStrategy($this); - } else { - $this->_mappingStrategy = new Doctrine_Mapper_DefaultStrategy($this); - } - } - - /** - * sets the connection for this class - * - * @params Doctrine_Connection a connection object - * @return Doctrine_Table this object - * @todo refactor - */ - public function setConnection(Doctrine_Connection $conn) - { - $this->_conn = $conn; - return $this; - } - - /** - * Returns the connection the mapper is currently using. - * - * @return Doctrine_Connection|null The connection object. - */ - public function getConnection() - { - return $this->_conn; - } - - /** - * creates a new record - * - * @param $array an array where keys are field names and - * values representing field values - * @return Doctrine_Entity the created record object - * @todo To EntityManager. - */ - public function create(array $array = array()) - { - $record = new $this->_domainClassName(); - $record->fromArray($array); - - return $record; - } - - public function addEntityListener(Doctrine_Record_Listener $listener) - { - if ( ! in_array($listener, $this->_entityListeners)) { - $this->_entityListeners[] = $listener; - return true; - } - return false; - } - - public function removeEntityListener(Doctrine_Record_Listener $listener) - { - if ($key = array_search($listener, $this->_entityListeners, true)) { - unset($this->_entityListeners[$key]); - return true; - } - return false; - } - - public function notifyEntityListeners(Doctrine_Entity $entity, $callback, $eventType) - { - if ($this->_entityListeners) { - $event = new Doctrine_Event($entity, $eventType); - foreach ($this->_entityListeners as $listener) { - $listener->$callback($event); - } - } - } - - /** - * Enter description here... - * - * @param Doctrine_Entity $entity - * @return unknown - * @todo To EntityManager - */ - public function detach(Doctrine_Entity $entity) - { - return $this->_conn->unitOfWork->detach($entity); - } - - /** - * clear - * clears the first level cache (identityMap) - * - * @return void - * @todo what about a more descriptive name? clearIdentityMap? - * @todo To EntityManager - */ - public function clear() - { - $this->_conn->unitOfWork->clearIdentitiesForEntity($this->_classMetadata->getRootClassName()); - } - - /** - * addRecord - * adds a record to identity map - * - * @param Doctrine_Entity $record record to be added - * @return boolean - * @todo Better name? registerRecord? Move elsewhere to the new location of the identity maps. - * @todo Remove. - */ - public function addRecord(Doctrine_Entity $record) - { - if ($this->_conn->unitOfWork->contains($record)) { - return false; - } - $this->_conn->unitOfWork->registerIdentity($record); - - return true; - } - - /** - * Tells the mapper to manage the entity if it's not already managed. - * - * @return boolean TRUE if the entity was previously not managed and is now managed, - * FALSE otherwise (the entity is already managed). - * @todo Remove. - */ - public function manage(Doctrine_Entity $record) - { - return $this->_conn->unitOfWork->manage($record); - } - - /** - * removeRecord - * removes a record from the identity map, returning true if the record - * was found and removed and false if the record wasn't found. - * - * @param Doctrine_Entity $record record to be removed - * @return boolean - * @todo Move elsewhere to the new location of the identity maps. - */ - public function removeRecord(Doctrine_Entity $record) - { - if ($this->_conn->unitOfWork->contains($record)) { - $this->_conn->unitOfWork->unregisterIdentity($record); - return true; - } - - return false; - } - - /** - * getRecord - * First checks if record exists in identityMap, if not - * returns a new record. - * - * @return Doctrine_Entity - * @todo To EntityManager. - */ - public function getRecord(array $data) - { - if ( ! empty($data)) { - $identifierFieldNames = $this->_classMetadata->getIdentifier(); - - $found = false; - foreach ($identifierFieldNames as $fieldName) { - if ( ! isset($data[$fieldName])) { - // primary key column not found return new record - $found = true; - break; - } - $id[] = $data[$fieldName]; - } - - if ($found) { - return new $this->_domainClassName(true, $data); - } - - $idHash = $this->_conn->unitOfWork->getIdentifierHash($id); - - if ($record = $this->_conn->unitOfWork->tryGetByIdHash($idHash, - $this->_classMetadata->getRootClassName())) { - $record->hydrate($data); - } else { - $record = new $this->_domainClassName(false, $data); - $this->_conn->unitOfWork->registerIdentity($record); - } - $data = array(); - } else { - $record = new $this->_domainClassName(true, $data); - } - - return $record; - } - - /** - * @param $id database row id - * @todo Looks broken. Figure out an implementation and decide whether its needed. - */ - final public function getProxy($id = null) - { - if ($id !== null) { - $identifierColumnNames = $this->_classMetadata->getIdentifierColumnNames(); - $query = 'SELECT ' . implode(', ', $identifierColumnNames) - . ' FROM ' . $this->_classMetadata->getTableName() - . ' WHERE ' . implode(' = ? && ', $identifierColumnNames) . ' = ?'; - $query = $this->applyInheritance($query); - - $params = array_merge(array($id),array()); - - $data = $this->_conn->execute($query, $params)->fetch(PDO::FETCH_ASSOC); - - if ($data === false) { - return false; - } - } - - return $this->getRecord($data); - } - - /** - * applyInheritance - * @param $where query where part to be modified - * @return string query where part with column aggregation inheritance added - * @todo What to do with this? Remove if possible. - */ - final public function applyInheritance($where) - { - $discCol = $this->_classMetadata->getInheritanceOption('discriminatorColumn'); - if ( ! $discCol) { - return $where; - } - - $discMap = $this->_classMetadata->getInheritanceOption('discriminatorMap'); - $inheritanceMap = array($discCol => array_search($this->_domainClassName, $discMap)); - if ( ! empty($inheritanceMap)) { - $a = array(); - foreach ($inheritanceMap as $column => $value) { - $a[] = $column . ' = ?'; - } - $i = implode(' AND ', $a); - $where .= ' AND ' . $i; - } - - return $where; - } - - /** - * prepareValue - * this method performs special data preparation depending on - * the type of the given column - * - * 1. It unserializes array and object typed columns - * 2. Uncompresses gzip typed columns - * 3. Gets the appropriate enum values for enum typed columns - * 4. Initializes special null object pointer for null values (for fast column existence checking purposes) - * - * example: - * - * $field = 'name'; - * $value = null; - * $table->prepareValue($field, $value); // Doctrine_Null - * - * - * @throws Doctrine_Table_Exception if unserialization of array/object typed column fails or - * @throws Doctrine_Table_Exception if uncompression of gzip typed column fails * - * @param string $field the name of the field - * @param string $value field value - * @param string $typeHint A hint on the type of the value. If provided, the type lookup - * for the field can be skipped. Used i.e. during hydration to - * improve performance on large and/or complex results. - * @return mixed prepared value - * @todo To EntityManager. Make private and use in createEntity(). - * .. Or, maybe better: Move to hydrator for performance reasons. - */ - public function prepareValue($fieldName, $value, $typeHint = null) - { - if ($value === $this->_nullObject) { - return $this->_nullObject; - } else if ($value === null) { - return null; - } else { - $type = is_null($typeHint) ? $this->_classMetadata->getTypeOf($fieldName) : $typeHint; - switch ($type) { - case 'integer': - case 'string'; - // don't do any casting here PHP INT_MAX is smaller than what the databases support - break; - case 'enum': - return $this->_classMetadata->enumValue($fieldName, $value); - break; - case 'boolean': - return (boolean) $value; - break; - case 'array': - case 'object': - if (is_string($value)) { - $value = unserialize($value); - if ($value === false) { - throw new Doctrine_Mapper_Exception('Unserialization of ' . $fieldName . ' failed.'); - } - return $value; - } - break; - case 'gzip': - $value = gzuncompress($value); - if ($value === false) { - throw new Doctrine_Mapper_Exception('Uncompressing of ' . $fieldName . ' failed.'); - } - return $value; - break; - } - } - return $value; - } - - /** - * getComponentName - * - * @return void - * @deprecated Use getMappedClassName() - */ - public function getComponentName() - { - return $this->_domainClassName; - } - - /** - * Gets the name of the class the mapper is used for. - */ - public function getMappedClassName() - { - return $this->_domainClassName; - } - - /** - * Saves an entity and all it's related entities. - * - * @param Doctrine_Entity $record The entity to save. - * @param Doctrine_Connection $conn The connection to use. Will default to the mapper's - * connection. - * @throws Doctrine_Mapper_Exception If the mapper is unable to save the given entity. - */ - public function save(Doctrine_Entity $record, Doctrine_Connection $conn = null) - { - if ( ! ($record instanceof $this->_domainClassName)) { - throw new Doctrine_Mapper_Exception("Mapper of type " . $this->_domainClassName . " - can't save instances of type" . get_class($record) . "."); - } - - if ($conn === null) { - $conn = $this->_conn; - } - - $state = $record->state(); - if ($state === Doctrine_Entity::STATE_LOCKED) { - return false; - } - - $record->state(Doctrine_Entity::STATE_LOCKED); - - try { - $conn->beginInternalTransaction(); - $saveLater = $this->_saveRelated($record); - - $record->state($state); - - if ($record->isValid()) { - $this->_insertOrUpdate($record); - } else { - $conn->transaction->addInvalid($record); - } - - $state = $record->state(); - $record->state(Doctrine_Entity::STATE_LOCKED); - - foreach ($saveLater as $fk) { - $alias = $fk->getAlias(); - if ($record->hasReference($alias)) { - $obj = $record->$alias; - // check that the related object is not an instance of Doctrine_Null - if ( ! ($obj instanceof Doctrine_Null)) { - $obj->save($conn); - } - } - } - - // save the MANY-TO-MANY associations - $this->saveAssociations($record); - // reset state - $record->state($state); - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - - return true; - } - - /** - * Inserts or updates an entity, depending on it's state. - * - * @param Doctrine_Entity $record The entity to insert/update. - */ - protected function _insertOrUpdate(Doctrine_Entity $record) - { - $record->preSave(); - $this->notifyEntityListeners($record, 'preSave', Doctrine_Event::RECORD_SAVE); - - switch ($record->state()) { - case Doctrine_Entity::STATE_TDIRTY: - $this->_insert($record); - break; - case Doctrine_Entity::STATE_DIRTY: - case Doctrine_Entity::STATE_PROXY: - $this->_update($record); - break; - case Doctrine_Entity::STATE_CLEAN: - case Doctrine_Entity::STATE_TCLEAN: - // do nothing - break; - } - - $record->postSave(); - $this->notifyEntityListeners($record, 'postSave', Doctrine_Event::RECORD_SAVE); - } - - /** - * saves the given record - * - * @param Doctrine_Entity $record - * @return void - */ - public function saveSingleRecord(Doctrine_Entity $record) - { - $this->_insertOrUpdate($record); - } - - /** - * _saveRelated - * saves all related records to $record - * - * @throws PDOException if something went wrong at database level - * @param Doctrine_Entity $record - */ - protected function _saveRelated(Doctrine_Entity $record) - { - $saveLater = array(); - foreach ($record->getReferences() as $k => $v) { - $rel = $record->getTable()->getRelation($k); - - $local = $rel->getLocal(); - $foreign = $rel->getForeign(); - - if ($rel instanceof Doctrine_Relation_ForeignKey) { - $saveLater[$k] = $rel; - } else if ($rel instanceof Doctrine_Relation_LocalKey) { - // ONE-TO-ONE relationship - $obj = $record->get($rel->getAlias()); - - // Protection against infinite function recursion before attempting to save - if ($obj instanceof Doctrine_Entity && $obj->isModified()) { - $obj->save($this->_conn); - - /** Can this be removed? - $id = array_values($obj->identifier()); - - foreach ((array) $rel->getLocal() as $k => $field) { - $record->set($field, $id[$k]); - } - */ - } - } - } - - return $saveLater; - } - - /** - * saveAssociations - * - * this method takes a diff of one-to-many / many-to-many original and - * current collections and applies the changes - * - * for example if original many-to-many related collection has records with - * primary keys 1,2 and 3 and the new collection has records with primary keys - * 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then - * save new associations to 4 and 5 - * - * @throws Doctrine_Connection_Exception if something went wrong at database level - * @param Doctrine_Entity $record - * @return void - */ - public function saveAssociations(Doctrine_Entity $record) - { - foreach ($record->getReferences() as $relationName => $relatedObject) { - if ($relatedObject === Doctrine_Null::$INSTANCE) { - continue; - } - $rel = $record->getTable()->getRelation($relationName); - - if ($rel instanceof Doctrine_Relation_Association) { - $relatedObject->save($this->_conn); - $assocTable = $rel->getAssociationTable(); - - foreach ($relatedObject->getDeleteDiff() as $r) { - $query = 'DELETE FROM ' . $assocTable->getTableName() - . ' WHERE ' . $rel->getForeign() . ' = ?' - . ' AND ' . $rel->getLocal() . ' = ?'; - // FIXME: composite key support - $ids1 = $r->identifier(); - $id1 = count($ids1) > 0 ? array_pop($ids1) : null; - $ids2 = $record->identifier(); - $id2 = count($ids2) > 0 ? array_pop($ids2) : null; - $this->_conn->execute($query, array($id1, $id2)); - } - - $assocMapper = $this->_conn->getMapper($assocTable->getComponentName()); - foreach ($relatedObject->getInsertDiff() as $r) { - $assocRecord = $assocMapper->create(); - $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r); - $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record); - $assocMapper->save($assocRecord); - } - } - } - } - - /** - * Updates an entity. - * - * @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 _update(Doctrine_Entity $record) - { - $record->preUpdate(); - $this->notifyEntityListeners($record, 'preUpdate', Doctrine_Event::RECORD_UPDATE); - - $table = $this->_classMetadata; - $this->_mappingStrategy->doUpdate($record); - - $record->postUpdate(); - $this->notifyEntityListeners($record, 'postUpdate', Doctrine_Event::RECORD_UPDATE); - - return true; - } - - /** - * Inserts an entity. - * - * @param Doctrine_Entity $record record to be inserted - * @return boolean - */ - protected function _insert(Doctrine_Entity $record) - { - $record->preInsert(); - $this->notifyEntityListeners($record, 'preInsert', Doctrine_Event::RECORD_INSERT); - - $this->_mappingStrategy->doInsert($record); - $this->addRecord($record); - - $record->postInsert(); - $this->notifyEntityListeners($record, 'postInsert', Doctrine_Event::RECORD_INSERT); - - return true; - } - - /** - * Deletes given entity and all it's related entities. - * - * Triggered Events: onPreDelete, onDelete. - * - * @return boolean true on success, false on failure - * @throws Doctrine_Mapper_Exception - */ - public function delete(Doctrine_Entity $record, Doctrine_Connection $conn = null) - { - if ( ! $record->exists()) { - return false; - } - - if ( ! ($record instanceof $this->_domainClassName)) { - throw new Doctrine_Mapper_Exception("Mapper of type " . $this->_domainClassName . " - can't save instances of type" . get_class($record) . "."); - } - - if ($conn == null) { - $conn = $this->_conn; - } - - $record->preDelete(); - $this->notifyEntityListeners($record, 'preDelete', Doctrine_Event::RECORD_DELETE); - - $table = $this->_classMetadata; - - $state = $record->state(); - $record->state(Doctrine_Entity::STATE_LOCKED); - - $this->_mappingStrategy->doDelete($record); - - $record->postDelete(); - $this->notifyEntityListeners($record, 'postDelete', Doctrine_Event::RECORD_DELETE); - - return true; - } - - public function getClassMetadata() - { - return $this->_classMetadata; - } - - public function free() - { - $this->_mappingStrategy = null; - } - - public function getMapping() - { - return $this->_mappingStrategy; - } - - public function getFieldName($columnName) - { - return $this->_mappingStrategy->getFieldName($columnName); - } - - public function getFieldNames() - { - return $this->_mappingStrategy->getFieldNames(); - } - - public function getOwningClass($fieldName) - { - return $this->_mappingStrategy->getOwningClass($fieldName); - } - - /* Hooks used during SQL query construction to manipulate the query. */ - - /** - * Callback that is invoked during the SQL construction process. - */ - public function getCustomJoins() - { - return $this->_mappingStrategy->getCustomJoins(); - } - - /** - * Callback that is invoked during the SQL construction process. - */ - public function getCustomFields() - { - return $this->_mappingStrategy->getCustomFields(); - } -} diff --git a/lib/Doctrine/Mapper/DefaultStrategy.php b/lib/Doctrine/Mapper/DefaultStrategy.php deleted file mode 100644 index bdd4aa6e8..000000000 --- a/lib/Doctrine/Mapper/DefaultStrategy.php +++ /dev/null @@ -1,120 +0,0 @@ -. - */ - -/** - * The default mapping strategy maps a single entity instance to a single database table, - * as is the case in Single Table Inheritance & Concrete 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_DefaultStrategy extends Doctrine_Mapper_Strategy -{ - /** - * Deletes an entity. - */ - public function doDelete(Doctrine_Entity $record) - { - $conn = $this->_mapper->getConnection(); - $metadata = $this->_mapper->getClassMetadata(); - try { - $conn->beginInternalTransaction(); - $this->_deleteComposites($record); - - $record->state(Doctrine_Entity::STATE_TDIRTY); - - $identifier = $this->_convertFieldToColumnNames($record->identifier(), $metadata); - $this->_deleteRow($metadata->getTableName(), $identifier); - $record->state(Doctrine_Entity::STATE_TCLEAN); - - $this->_mapper->removeRecord($record); - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - } - - /** - * Inserts a single entity into the database, without any related entities. - * - * @param Doctrine_Entity $record The entity to insert. - */ - public function doInsert(Doctrine_Entity $record) - { - $conn = $this->_mapper->getConnection(); - - $fields = $record->getPrepared(); - if (empty($fields)) { - return false; - } - - //$class = $record->getClassMetadata(); - $class = $this->_mapper->getClassMetadata(); - $identifier = (array) $class->getIdentifier(); - $fields = $this->_convertFieldToColumnNames($fields, $class); - - $seq = $class->getTableOption('sequenceName'); - if ( ! empty($seq)) { - $id = $conn->sequence->nextId($seq); - $seqName = $identifier[0]; - $fields[$seqName] = $id; - $record->assignIdentifier($id); - } - - $this->_insertRow($class->getTableName(), $fields); - - if (empty($seq) && count($identifier) == 1 && - $class->getIdentifierType() != Doctrine::IDENTIFIER_NATURAL) { - if (strtolower($conn->getName()) == 'pgsql') { - $seq = $class->getTableName() . '_' . $identifier[0]; - } - - $id = $conn->sequence->lastInsertId($seq); - - if ( ! $id) { - throw new Doctrine_Mapper_Exception("Couldn't get last insert identifier."); - } - - $record->assignIdentifier($id); - } else { - $record->assignIdentifier(true); - } - } - - /** - * Updates an entity. - */ - public function doUpdate(Doctrine_Entity $record) - { - $conn = $this->_mapper->getConnection(); - $classMetadata = $this->_mapper->getClassMetadata(); - $identifier = $this->_convertFieldToColumnNames($record->identifier(), $classMetadata); - $data = $this->_convertFieldToColumnNames($record->getPrepared(), $classMetadata); - $this->_updateRow($classMetadata->getTableName(), $data, $identifier); - $record->assignIdentifier(true); - } -} \ No newline at end of file diff --git a/lib/Doctrine/Mapper/Exception.php b/lib/Doctrine/Mapper/Exception.php deleted file mode 100644 index b47cefe0f..000000000 --- a/lib/Doctrine/Mapper/Exception.php +++ /dev/null @@ -1,3 +0,0 @@ -. - */ - -/** - * 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; - } -} - diff --git a/lib/Doctrine/Mapper/Strategy.php b/lib/Doctrine/Mapper/Strategy.php deleted file mode 100644 index fb444afaa..000000000 --- a/lib/Doctrine/Mapper/Strategy.php +++ /dev/null @@ -1,156 +0,0 @@ -. - */ - -/** - * Base class for all mapping strategies used by mappers. - * - * @author Roman Borschel - * @package Doctrine - * @subpackage Strategy - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @version $Revision$ - * @link www.phpdoctrine.org - * @since 1.0 - */ -abstract class Doctrine_Mapper_Strategy -{ - protected $_mapper; - - /** - * The names of all the fields that are available on entities created by this mapper. - */ - protected $_fieldNames = array(); - - public function __construct(Doctrine_Mapper $mapper) - { - $this->_mapper = $mapper; - } - - /** - * Assumes that the keys of the given field array are field names and converts - * them to column names. - * - * @return array - */ - protected function _convertFieldToColumnNames(array $fields, Doctrine_ClassMetadata $class) - { - $converted = array(); - foreach ($fields as $fieldName => $value) { - $converted[$class->getColumnName($fieldName)] = $value; - } - - return $converted; - } - - /** - * deletes all related composites - * this method is always called internally when a record is deleted - * - * @throws PDOException if something went wrong at database level - * @return void - */ - protected function _deleteComposites(Doctrine_Entity $record) - { - $classMetadata = $this->_mapper->getClassMetadata(); - foreach ($classMetadata->getRelations() as $fk) { - if ($fk->isComposite()) { - $obj = $record->get($fk->getAlias()); - if ($obj instanceof Doctrine_Entity && - $obj->state() != Doctrine_Entity::STATE_LOCKED) { - $obj->delete($this->_mapper->getConnection()); - } - } - } - } - - /** - * Callback that is invoked during the SQL construction process. - */ - public function getCustomJoins() - { - return array(); - } - - /** - * Callback that is invoked during the SQL construction process. - */ - public function getCustomFields() - { - return array(); - } - - public function getFieldName($columnName) - { - return $this->_mapper->getClassMetadata()->getFieldName($columnName); - } - - public function getFieldNames() - { - if ($this->_fieldNames) { - return $this->_fieldNames; - } - $this->_fieldNames = $this->_mapper->getClassMetadata()->getFieldNames(); - return $this->_fieldNames; - } - - public function getOwningClass($fieldName) - { - return $this->_mapper->getClassMetadata(); - } - - abstract public function doDelete(Doctrine_Entity $record); - abstract public function doInsert(Doctrine_Entity $record); - abstract public function doUpdate(Doctrine_Entity $record); - - /** - * Inserts a row into a table. - * - * @todo This method could be used to allow mapping to secondary table(s). - * @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable - */ - protected function _insertRow($tableName, array $data) - { - $this->_mapper->getConnection()->insert($tableName, $data); - } - - /** - * Deletes rows of a table. - * - * @todo This method could be used to allow mapping to secondary table(s). - * @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable - */ - protected function _deleteRow($tableName, array $identifierToMatch) - { - $this->_mapper->getConnection()->delete($tableName, $identifierToMatch); - } - - /** - * Deletes rows of a table. - * - * @todo This method could be used to allow mapping to secondary table(s). - * @see http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#SecondaryTable - */ - protected function _updateRow($tableName, array $data, array $identifierToMatch) - { - $this->_mapper->getConnection()->update($tableName, $data, $identifierToMatch); - } - -} \ No newline at end of file diff --git a/lib/Doctrine/Null.php b/lib/Doctrine/Null.php index 3e5a32aa0..0b6ecb7c0 100644 --- a/lib/Doctrine/Null.php +++ b/lib/Doctrine/Null.php @@ -20,10 +20,17 @@ */ /** - * Doctrine_Null - * - * Simple empty class representing a null value. - * Used for extra fast null value testing with isset() rather than array_key_exists(). + * Null class representing a null value that has been fetched from + * the database or a fetched, empty association. This is for internal use only. + * User code should never deal with this null object. + * + * Semantics are as follows: + * + * Regular PHP null : Value is undefined. When a field with that value is accessed + * and lazy loading is used the database is queried. + * + * Null object: Null valued of a field or empty association that has already been loaded. + * On access, the database is not queried. * * @package Doctrine * @subpackage Null diff --git a/tests/models/forum/ForumUser.php b/tests/models/forum/ForumUser.php index f684fe0fe..216e9023c 100644 --- a/tests/models/forum/ForumUser.php +++ b/tests/models/forum/ForumUser.php @@ -20,13 +20,13 @@ class ForumUser extends Doctrine_Entity $class->mapColumn('id', 'integer', 4, array( 'primary' => true, 'autoincrement' => true)); - $class->mapColumn('username', 'string', 50); + $class->mapColumn('username', 'string', 50, array('accessor' => 'getUsernameCustom')); } - /* - public function getUsername() + + public function getUsernameCustom() { - return $this->rawGet('username') . "!"; + return $this->rawGetField('username') . "!"; } - */ + } \ No newline at end of file