Author: romanb
Date: 2008-09-12 10:44:51 +0100 (Fri, 12 Sep 2008)
New Revision: 4921
Added:
trunk/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php
trunk/lib/Doctrine/ORM/Persisters/Exception.php
trunk/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
trunk/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
trunk/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php
trunk/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php
Removed:
trunk/lib/Doctrine/EntityPersister/
Modified:
trunk/tests/Orm/EntityPersisterTest.php
trunk/tests/lib/mocks/Doctrine_EntityPersisterMock.php
Log:
moved entitypersisters
Added: trunk/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php
===================================================================
--- trunk/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php
(rev 0)
+++ trunk/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php
2008-09-12 09:44:51 UTC (rev 4921)
@@ -0,0 +1,654 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+#namespace Doctrine::ORM::Persisters;
+
+/**
+ * Base class for all EntityPersisters.
+ *
+ * @author Roman Borschel <[EMAIL PROTECTED]>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version $Revision: 3406 $
+ * @link www.phpdoctrine.org
+ * @since 2.0
+ * @todo Rename to AbstractEntityPersister
+ */
+abstract class Doctrine_ORM_Persisters_AbstractEntityPersister
+{
+ /**
+ * The names of all the fields that are available on entities.
+ */
+ protected $_fieldNames = array();
+
+ /**
+ * Metadata object that descibes the mapping of the mapped entity class.
+ *
+ * @var Doctrine_ClassMetadata
+ */
+ protected $_classMetadata;
+
+ /**
+ * The name of the Entity the persister is used for.
+ *
+ * @var string
+ */
+ protected $_entityName;
+
+ /**
+ * The Doctrine_Connection object that the database connection of this
mapper.
+ *
+ * @var Doctrine::DBAL::Connection $conn
+ */
+ protected $_conn;
+
+ /**
+ * The EntityManager.
+ *
+ * @var Doctrine::ORM::EntityManager
+ */
+ protected $_em;
+
+ /**
+ * Null object.
+ */
+ private $_nullObject;
+
+ /**
+ * Constructs a new EntityPersister.
+ */
+ public function __construct(Doctrine_EntityManager $em,
Doctrine_ClassMetadata $classMetadata)
+ {
+ $this->_em = $em;
+ $this->_entityName = $classMetadata->getClassName();
+ $this->_conn = $em->getConnection();
+ $this->_classMetadata = $classMetadata;
+ $this->_nullObject = Doctrine_ORM_Internal_Null::$INSTANCE;
+ }
+
+ /**
+ * Inserts an entity.
+ *
+ * @param Doctrine::ORM::Entity $entity The entity to insert.
+ * @return void
+ */
+ public function insert(Doctrine_Entity $entity)
+ {
+ $insertData = array();
+ $class = $entity->getClass();
+
+ $referenceChangeSet = $entity->_getReferenceChangeSet();
+ foreach ($referenceChangeSet as $field => $change) {
+ list($old, $new) = each($change);
+ $assocMapping = $class->getAssociationMapping($field);
+ if ( ! $assocMapping->isOneToOne() ||
$assocMapping->isInverseSide()) {
+ //echo "NOT TO-ONE OR INVERSE!";
+ continue;
+ }
+
+ foreach ($assocMapping->getSourceToTargetKeyColumns() as
$sourceColumn => $targetColumn) {
+ //TODO: What if both join columns (local/foreign) are just
db-only
+ // columns (no fields in models) ? Currently we assume the
foreign column
+ // is mapped to a field in the foreign entity.
+ //TODO: throw exc if field not set
+ $insertData[$sourceColumn] = $new->_internalGetField(
+ $new->getClass()->getFieldName($targetColumn)
+ );
+ }
+ //...
+ }
+
+ $this->_prepareData($entity, $insertData, true);
+
+ //TODO: perform insert
+ $this->_conn->insert($class->getTableName(), $insertData);
+ }
+
+ /*protected function _fillJoinColumns($entity, array &$data)
+ {
+ $referenceChangeSet = $entity->_getReferenceChangeSet();
+ foreach ($referenceChangeSet as $field => $change) {
+ list($old, $new) = each($change);
+ $assocMapping = $entity->getClass()->getAssociationMapping($field);
+ if ( ! $assocMapping->isOneToOne() ||
$assocMapping->isInverseSide()) {
+ //echo "NOT TO-ONE OR INVERSE!";
+ continue;
+ }
+
+ foreach ($assocMapping->getSourceToTargetKeyColumns() as
$sourceColumn => $targetColumn) {
+ //TODO: What if both join columns (local/foreign) are just
db-only
+ // columns (no fields in models) ? Currently we assume the
foreign column
+ // is mapped to a field in the foreign entity.
+ $insertData[$sourceColumn] = $new->_internalGetField(
+ $new->getClass()->getFieldName($targetColumn)
+ );
+ }
+ }
+ }*/
+
+ /**
+ * Updates an entity.
+ *
+ * @param Doctrine::ORM::Entity $entity The entity to update.
+ * @return void
+ */
+ public function update(Doctrine_Entity $entity)
+ {
+ $dataChangeSet = $entity->_getDataChangeSet();
+ $referenceChangeSet = $entity->_getReferenceChangeSet();
+
+ foreach ($referenceChangeSet as $field => $change) {
+ $assocMapping = $entity->getClass()->getAssociationMapping($field);
+ if ($assocMapping instanceof Doctrine_Association_OneToOneMapping)
{
+ if ($assocMapping->isInverseSide()) {
+ continue; // ignore inverse side
+ }
+ // ... null out the foreign key
+
+ }
+ //...
+ }
+
+ //TODO: perform update
+ }
+
+ /**
+ * Deletes an entity.
+ *
+ * @param Doctrine::ORM::Entity $entity The entity to delete.
+ * @return void
+ */
+ public function delete(Doctrine_Entity $entity)
+ {
+ //TODO: perform delete
+ }
+
+ /**
+ * 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->_conn->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->_conn->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->_conn->update($tableName, $data, $identifierToMatch);
+ }
+
+ public function getClassMetadata()
+ {
+ return $this->_classMetadata;
+ }
+
+ /**
+ * @todo Move to ClassMetadata?
+ */
+ public function getFieldNames()
+ {
+ if ($this->_fieldNames) {
+ return $this->_fieldNames;
+ }
+ $this->_fieldNames = $this->_classMetadata->getFieldNames();
+ return $this->_fieldNames;
+ }
+
+ /**
+ * @todo Move to ClassMetadata?
+ */
+ public function getOwningClass($fieldName)
+ {
+ return $this->_classMetadata;
+ }
+
+ /**
+ * Callback that is invoked during the SQL construction process.
+ * @todo Move to ClassMetadata?
+ */
+ public function getCustomJoins()
+ {
+ return array();
+ }
+
+ /**
+ * Callback that is invoked during the SQL construction process.
+ * @todo Move to ClassMetadata?
+ */
+ public function getCustomFields()
+ {
+ return array();
+ }
+
+ /**
+ * 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;
+ }*/
+
+ /**
+ * Returns an array of modified fields and values with data preparation
+ * adds column aggregation inheritance and converts Records into primary
key values
+ *
+ * @param array $array
+ * @return void
+ * @todo Move to EntityPersister. There call _getChangeSet() and apply
this logic.
+ */
+ protected function _prepareData($entity, array &$result, $isInsert = false)
+ {
+ foreach ($entity->_getDataChangeSet() as $field => $change) {
+ list ($oldVal, $newVal) = each($change);
+ $type = $entity->getClass()->getTypeOfField($field);
+ $columnName = $entity->getClass()->getColumnName($field);
+
+ if ($newVal === Doctrine_ORM_Internal_Null::$INSTANCE) {
+ $result[$columnName] = null;
+ continue;
+ }
+
+ switch ($type) {
+ case 'array':
+ case 'object':
+ $result[$columnName] = serialize($newVal);
+ break;
+ case 'gzip':
+ $result[$columnName] = gzcompress($newVal, 5);
+ break;
+ case 'boolean':
+ $result[$columnName] =
$this->_em->getConnection()->convertBooleans($newVal);
+ break;
+ default:
+ $result[$columnName] = $newVal;
+ }
+ /*$result[$columnName] = $type->convertToDatabaseValue(
+ $newVal,
$this->_em->getConnection()->getDatabasePlatform());*/
+ }
+
+ // @todo Cleanup
+ // populates the discriminator column on insert in Single & Class
Table Inheritance
+ if ($isInsert && ($entity->getClass()->isInheritanceTypeJoined() ||
+ $entity->getClass()->isInheritanceTypeSingleTable())) {
+ $discColumn =
$entity->getClass()->getInheritanceOption('discriminatorColumn');
+ $discMap =
$entity->getClass()->getInheritanceOption('discriminatorMap');
+ $result[$discColumn] = array_search($this->_entityName, $discMap);
+ }
+ }
+
+
+
+ #############################################################
+
+ # The following is old code that needs to be removed/ported
+
+
+ /**
+ * 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->_classMetadata;
+ 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());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the connection the mapper is currently using.
+ *
+ * @return Doctrine_Connection|null The connection object.
+ */
+ public function getConnection()
+ {
+ return $this->_conn;
+ }
+
+ public function getEntityManager()
+ {
+ return $this->_em;
+ }
+
+ /**
+ * getComponentName
+ *
+ * @return void
+ * @deprecated Use getMappedClassName()
+ */
+ public function getComponentName()
+ {
+ return $this->_domainClassName;
+ }
+
+ /**
+ * Saves an entity.
+ *
+ * @param Doctrine_Entity $record The entity to save.
+ * @param Doctrine_Connection $conn The connection to use. Will default
to the mapper's
+ * connection.
+ */
+ public function save(Doctrine_Entity $record)
+ {
+ 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->getTransaction()->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);
+ }
+
+ /**
+ * 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();
+
+ /** 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->_doUpdate($record);
+
+ $record->postUpdate();
+ $this->notifyEntityListeners($record, 'postUpdate',
Doctrine_Event::RECORD_UPDATE);
+
+ return true;
+ }
+
+ abstract protected function _doUpdate(Doctrine_Entity $entity);
+
+ /**
+ * 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->_doInsert($record);
+ $this->addRecord($record);
+
+ $record->postInsert();
+ $this->notifyEntityListeners($record, 'postInsert',
Doctrine_Event::RECORD_INSERT);
+
+ return true;
+ }
+
+ abstract protected function _doInsert(Doctrine_Entity $entity);
+
+ /**
+ * 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_old(Doctrine_Entity $record)
+ {
+ 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->_doDelete($record);
+
+ $record->postDelete();
+ $this->notifyEntityListeners($record, 'postDelete',
Doctrine_Event::RECORD_DELETE);
+
+ return true;
+ }
+
+
+}
Added: trunk/lib/Doctrine/ORM/Persisters/Exception.php
===================================================================
--- trunk/lib/Doctrine/ORM/Persisters/Exception.php
(rev 0)
+++ trunk/lib/Doctrine/ORM/Persisters/Exception.php 2008-09-12 09:44:51 UTC
(rev 4921)
@@ -0,0 +1,38 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+#namespace Doctrine::ORM;
+
+/**
+ * Doctrine_EntityPersister_Exception
+ *
+ * @package Doctrine
+ * @subpackage Entity
+ * @author Konsta Vesterinen <[EMAIL PROTECTED]>
+ * @author Roman Borschel <[EMAIL PROTECTED]>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.phpdoctrine.org
+ * @since 2.0
+ * @version $Revision$
+ */
+class Doctrine_EntityPersister_Exception extends Doctrine_Exception
+{
+}
\ No newline at end of file
Added: trunk/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
===================================================================
--- trunk/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
(rev 0)
+++ trunk/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php
2008-09-12 09:44:51 UTC (rev 4921)
@@ -0,0 +1,279 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+/**
+ * The joined subclass persister maps a single entity instance to several
tables in the
+ * database as it is defined by <tt>Class Table Inheritance</tt>.
+ *
+ * @author Roman Borschel <[EMAIL PROTECTED]>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version $Revision$
+ * @link www.phpdoctrine.org
+ * @since 2.0
+ */
+class Doctrine_ORM_Persisters_JoinedSubclassPersister extends
Doctrine_ORM_Persisters_AbstractEntityPersister
+{
+ /**
+ * Inserts an entity that is part of a Class Table Inheritance hierarchy.
+ *
+ * @param Doctrine_Entity $record record to be inserted
+ * @return boolean
+ * @override
+ */
+ public function insert(Doctrine_Entity $entity)
+ {
+ $class = $entity->getClass();
+
+ $dataSet = array();
+
+ $this->_prepareData($entity, $dataSet, true);
+
+ $dataSet = $this->_groupFieldsByDefiningClass($class, $dataSet);
+
+ $component = $class->getClassName();
+ $classes = $class->getParentClasses();
+ array_unshift($classes, $component);
+
+ $identifier = null;
+ foreach (array_reverse($classes) as $k => $parent) {
+ $parentClass = $this->_em->getClassMetadata($parent);
+ if ($k == 0) {
+ if ($parentClass->isIdGeneratorIdentity()) {
+ $this->_insertRow($parentClass->getTableName(),
$dataSet[$parent]);
+ $identifier = $this->_conn->lastInsertId();
+ } else if ($parentClass->isIdGeneratorSequence()) {
+ $seq =
$entity->getClassMetadata()->getTableOption('sequenceName');
+ if ( ! empty($seq)) {
+ $id = $this->_conn->getSequenceManager()->nextId($seq);
+ $identifierFields = $parentClass->getIdentifier();
+ $dataSet[$parent][$identifierFields[0]] = $id;
+ $this->_insertRow($parentClass->getTableName(),
$dataSet[$parent]);
+ }
+ } else {
+ throw new Doctrine_Mapper_Exception("Unsupported
identifier type '$identifierType'.");
+ }
+ $entity->_assignIdentifier($identifier);
+ } else {
+ foreach ($entity->_identifier() as $id => $value) {
+ $dataSet[$parent][$parentClass->getColumnName($id)] =
$value;
+ }
+ $this->_insertRow($parentClass->getTableName(),
$dataSet[$parent]);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Updates an entity that is part of a Class Table Inheritance hierarchy.
+ *
+ * @param Doctrine_Entity $record record to be updated
+ * @return boolean whether or not the update was
successful
+ */
+ protected function _doUpdate(Doctrine_Entity $record)
+ {
+ $conn = $this->_conn;
+ $classMetadata = $this->_classMetadata;
+ $identifier = $this->_convertFieldToColumnNames($record->identifier(),
$classMetadata);
+ $dataSet = $this->_groupFieldsByDefiningClass($record);
+ $component = $classMetadata->getClassName();
+ $classes = $classMetadata->getParentClasses();
+ array_unshift($classes, $component);
+
+ foreach ($record as $field => $value) {
+ if ($value instanceof Doctrine_Entity) {
+ if ( ! $value->exists()) {
+ $value->save();
+ }
+ $idValues = $value->identifier();
+ $record->set($field, $idValues[0]);
+ }
+ }
+
+ foreach (array_reverse($classes) as $class) {
+ $parentTable = $conn->getClassMetadata($class);
+ $this->_updateRow($parentTable->getTableName(), $dataSet[$class],
$identifier);
+ }
+
+ $record->assignIdentifier(true);
+
+ return true;
+ }
+
+ /**
+ * Deletes an entity that is part of a Class Table Inheritance hierarchy.
+ *
+ */
+ protected function _doDelete(Doctrine_Entity $record)
+ {
+ $conn = $this->_conn;
+ try {
+ $class = $this->_classMetadata;
+ $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->removeRecord($record); // @todo should be done in the
unitofwork
+ $conn->commit();
+ } catch (Exception $e) {
+ $conn->rollback();
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds all parent classes as INNER JOINs and subclasses as OUTER JOINs
+ * to the query.
+ *
+ * Callback that is invoked during the SQL construction process.
+ *
+ * @return array The custom joins in the format <className> => <joinType>
+ */
+ public function getCustomJoins()
+ {
+ $customJoins = array();
+ $classMetadata = $this->_classMetadata;
+ foreach ($classMetadata->getParentClasses() as $parentClass) {
+ $customJoins[$parentClass] = 'INNER';
+ }
+ foreach ($classMetadata->getSubclasses() as $subClass) {
+ if ($subClass != $this->getComponentName()) {
+ $customJoins[$subClass] = 'LEFT';
+ }
+ }
+
+ return $customJoins;
+ }
+
+ /**
+ * Adds the discriminator column to the selected fields in a query as well
as
+ * all fields of subclasses. In Class Table Inheritance the default
behavior is that
+ * all subclasses are joined in through OUTER JOINs when querying a base
class.
+ *
+ * Callback that is invoked during the SQL construction process.
+ *
+ * @return array An array with the field names that will get added to the
query.
+ */
+ public function getCustomFields()
+ {
+ $classMetadata = $this->_classMetadata;
+ $conn = $this->_conn;
+ $fields =
array($classMetadata->getInheritanceOption('discriminatorColumn'));
+ if ($classMetadata->getSubclasses()) {
+ foreach ($classMetadata->getSubclasses() as $subClass) {
+ $fields =
array_merge($conn->getClassMetadata($subClass)->getFieldNames(), $fields);
+ }
+ }
+
+ return array_unique($fields);
+ }
+
+ /**
+ *
+ */
+ public function getFieldNames()
+ {
+ if ($this->_fieldNames) {
+ return $this->_fieldNames;
+ }
+
+ $fieldNames = $this->_classMetadata->getFieldNames();
+ $this->_fieldNames = array_unique($fieldNames);
+
+ return $fieldNames;
+ }
+
+ /**
+ *
+ * @todo Looks like this better belongs into the ClassMetadata class.
+ */
+ public function getOwningClass($fieldName)
+ {
+ $conn = $this->_conn;
+ $classMetadata = $this->_classMetadata;
+ if ($classMetadata->hasField($fieldName) && !
$classMetadata->isInheritedField($fieldName)) {
+ return $classMetadata;
+ }
+
+ foreach ($classMetadata->getParentClasses() as $parentClass) {
+ $parentTable = $conn->getClassMetadata($parentClass);
+ if ($parentTable->hasField($fieldName) && !
$parentTable->isInheritedField($fieldName)) {
+ return $parentTable;
+ }
+ }
+
+ foreach ((array)$classMetadata->getSubclasses() as $subClass) {
+ $subTable = $conn->getClassMetadata($subClass);
+ if ($subTable->hasField($fieldName) && !
$subTable->isInheritedField($fieldName)) {
+ return $subTable;
+ }
+ }
+
+ throw new Doctrine_Mapper_Exception("Unable to find defining class of
field '$fieldName'.");
+ }
+
+ /**
+ * Analyzes the fields of the entity and creates a map in which the field
names
+ * are grouped by the class names they belong to.
+ *
+ * @return array
+ */
+ protected function _groupFieldsByDefiningClass(Doctrine_ClassMetadata
$class, array $fields)
+ {
+ $dataSet = array();
+ $component = $class->getClassName();
+
+ $classes = array_merge(array($component), $class->getParentClasses());
+
+ foreach ($classes as $class) {
+ $dataSet[$class] = array();
+ $parentClassMetadata = $this->_em->getClassMetadata($class);
+ foreach ($parentClassMetadata->getFieldMappings() as $fieldName =>
$mapping) {
+ if ((isset($mapping['id']) && $mapping['id'] === true) ||
+ (isset($mapping['inherited']) && $mapping['inherited']
=== true)) {
+ continue;
+ }
+ if ( ! array_key_exists($fieldName, $fields)) {
+ continue;
+ }
+ $columnName = $parentClassMetadata->getColumnName($fieldName);
+ $dataSet[$class][$columnName] = $fields[$fieldName];
+ }
+ }
+
+ return $dataSet;
+ }
+}
+
Added: trunk/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
===================================================================
--- trunk/lib/Doctrine/ORM/Persisters/SingleTablePersister.php
(rev 0)
+++ trunk/lib/Doctrine/ORM/Persisters/SingleTablePersister.php 2008-09-12
09:44:51 UTC (rev 4921)
@@ -0,0 +1,3 @@
+<?php
+
+?>
\ No newline at end of file
Added: trunk/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php
===================================================================
--- trunk/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php
(rev 0)
+++ trunk/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php
2008-09-12 09:44:51 UTC (rev 4921)
@@ -0,0 +1,117 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+/**
+ * The default persister 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 <[EMAIL PROTECTED]>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version $Revision$
+ * @link www.phpdoctrine.org
+ * @since 2.0
+ */
+class Doctrine_ORM_Persisters_StandardEntityPersister extends
Doctrine_ORM_Persisters_AbstractEntityPersister
+{
+ /**
+ * Deletes an entity.
+ */
+ protected function _doDelete(Doctrine_Entity $record)
+ {
+ $conn = $this->_conn;
+ $metadata = $this->_classMetadata;
+ 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->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.
+ */
+ protected function _doInsert(Doctrine_Entity $record)
+ {
+ $conn = $this->_conn;
+
+ $fields = $record->getPrepared();
+ if (empty($fields)) {
+ return false;
+ }
+
+ $class = $this->_classMetadata;
+ $identifier = $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.
+ */
+ protected function _doUpdate(Doctrine_Entity $record)
+ {
+ $conn = $this->_conn;
+ $classMetadata = $this->_classMetadata;
+ $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
Added: trunk/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php
===================================================================
--- trunk/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php
(rev 0)
+++ trunk/lib/Doctrine/ORM/Persisters/UnionSubclassPersister.php
2008-09-12 09:44:51 UTC (rev 4921)
@@ -0,0 +1,3 @@
+<?php
+
+?>
\ No newline at end of file
Modified: trunk/tests/Orm/EntityPersisterTest.php
===================================================================
--- trunk/tests/Orm/EntityPersisterTest.php 2008-09-12 09:39:43 UTC (rev
4920)
+++ trunk/tests/Orm/EntityPersisterTest.php 2008-09-12 09:44:51 UTC (rev
4921)
@@ -23,7 +23,7 @@
$this->_classMetadataMock = new
Doctrine_ClassMetadataMock("ForumUser", $this->_emMock);
$this->_classMetadataMock->setIdGenerator($this->_idGenMock);
$this->_connMock->setDatabasePlatform(new
Doctrine_DatabasePlatformMock());
- $this->_persister = new Doctrine_EntityPersister_Standard(
+ $this->_persister = new
Doctrine_ORM_Persisters_StandardEntityPersister(
$this->_emMock, $this->_emMock->getClassMetadata("ForumUser"));
$this->_emMock->activate();
Modified: trunk/tests/lib/mocks/Doctrine_EntityPersisterMock.php
===================================================================
--- trunk/tests/lib/mocks/Doctrine_EntityPersisterMock.php 2008-09-12
09:39:43 UTC (rev 4920)
+++ trunk/tests/lib/mocks/Doctrine_EntityPersisterMock.php 2008-09-12
09:44:51 UTC (rev 4921)
@@ -1,6 +1,6 @@
<?php
-class Doctrine_EntityPersisterMock extends Doctrine_EntityPersister_Standard
+class Doctrine_EntityPersisterMock extends
Doctrine_ORM_Persisters_StandardEntityPersister
{
private $_inserts = array();
private $_updates = array();
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"doctrine-svn" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.co.uk/group/doctrine-svn?hl=en-GB
-~----------~----~----~----~------~----~------~--~---