From e202cb1ce18b4fabb62c6ce5a5593e5bc8f13329 Mon Sep 17 00:00:00 2001 From: romanb Date: Mon, 2 Feb 2009 11:55:50 +0000 Subject: [PATCH] [2.0] Progress on UnitOfWork, persisters and basic functional tests. --- lib/Doctrine/DBAL/Connection.php | 43 +--- lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php | 16 +- .../DBAL/Platforms/SqlitePlatform.php | 37 +--- .../DBAL/Schema/AbstractSchemaManager.php | 2 +- .../DBAL/Schema/SqliteSchemaManager.php | 1 - lib/Doctrine/ORM/EntityManager.php | 33 +-- lib/Doctrine/ORM/Export/ClassExporter.php | 1 + lib/Doctrine/ORM/Id/Assigned.php | 5 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 5 +- .../ORM/Mapping/Driver/AnnotationDriver.php | 1 + .../Mapping/Driver/DoctrineAnnotations.php | 4 +- lib/Doctrine/ORM/Mapping/OneToOneMapping.php | 6 +- ...ollection.php => PersistentCollection.php} | 42 ++-- .../AbstractCollectionPersister.php | 84 ++++++-- .../Persisters/AbstractEntityPersister.php | 28 +-- .../Persisters/JoinedSubclassPersister.php | 12 +- .../ORM/Persisters/ManyToManyPersister.php | 26 +++ .../ORM/Persisters/OneToManyPersister.php | 44 +++- lib/Doctrine/ORM/Query.php | 37 +++- lib/Doctrine/ORM/UnitOfWork.php | 200 ++++++++++++------ .../Tests/Mocks/EntityManagerMock.php | 22 -- tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php | 22 ++ .../Doctrine/Tests/Models/CMS/CmsAddress.php | 42 ++++ tests/Doctrine/Tests/Models/CMS/CmsUser.php | 7 +- .../Tests/ORM/EntityPersisterTest.php | 4 +- .../Tests/ORM/Functional/BasicCRUDTest.php | 67 +++++- .../ORM/Hydration/ObjectHydratorTest.php | 26 +-- tests/Doctrine/Tests/ORM/UnitOfWorkTest.php | 10 +- .../Doctrine/Tests/OrmFunctionalTestCase.php | 2 +- 29 files changed, 533 insertions(+), 296 deletions(-) rename lib/Doctrine/ORM/{Collection.php => PersistentCollection.php} (90%) create mode 100644 lib/Doctrine/ORM/Persisters/ManyToManyPersister.php create mode 100644 tests/Doctrine/Tests/Models/CMS/CmsAddress.php diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index dec5157c4..322567b2e 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -23,7 +23,6 @@ namespace Doctrine\DBAL; use Doctrine\Common\EventManager; use Doctrine\Common\DoctrineException; -#use Doctrine\DBAL\Exceptions\ConnectionException; /** * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like @@ -251,7 +250,6 @@ class Connection /** * Deletes table row(s) matching the specified identifier. * - * @throws Doctrine_Connection_Exception if something went wrong at the database level * @param string $table The table to delete data from * @param array $identifier An associateve array containing identifier fieldname-value pairs. * @return integer The number of affected rows @@ -289,12 +287,7 @@ class Connection $set = array(); foreach ($data as $columnName => $value) { - if ($value instanceof Doctrine_Expression) { - $set[] = $this->quoteIdentifier($columnName) . ' = ' . $value->getSql(); - unset($data[$columnName]); - } else { - $set[] = $this->quoteIdentifier($columnName) . ' = ?'; - } + $set[] = $this->quoteIdentifier($columnName) . ' = ?'; } $params = array_merge(array_values($data), array_values($identifier)); @@ -328,12 +321,7 @@ class Connection $a = array(); foreach ($data as $columnName => $value) { $cols[] = $this->quoteIdentifier($columnName); - if ($value instanceof Doctrine_DBAL_Expression) { - $a[] = $value->getSql(); - unset($data[$columnName]); - } else { - $a[] = '?'; - } + $a[] = '?'; } $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName) @@ -495,13 +483,8 @@ class Connection */ public function prepare($statement) { - echo $statement . PHP_EOL; $this->connect(); - try { - return $this->_conn->prepare($statement); - } catch (PDOException $e) { - $this->rethrowException($e, $this); - } + return $this->_conn->prepare($statement); } /** @@ -571,6 +554,7 @@ class Connection return $count; } } catch (PDOException $e) { + //TODO: Wrap throw $e; } } @@ -688,7 +672,7 @@ class Connection { $this->connect(); if ($this->_transactionNestingLevel == 0) { - return $this->_conn->beginTransaction(); + $this->_conn->beginTransaction(); } ++$this->_transactionNestingLevel; return true; @@ -710,7 +694,7 @@ class Connection $this->connect(); if ($this->_transactionNestingLevel == 1) { - return $this->_conn->commit(); + $this->_conn->commit(); } --$this->_transactionNestingLevel; @@ -740,7 +724,7 @@ class Connection if ($this->_transactionNestingLevel == 1) { $this->_transactionNestingLevel = 0; - return $this->_conn->rollback(); + $this->_conn->rollback(); } --$this->_transactionNestingLevel; @@ -807,7 +791,7 @@ class Connection protected function _getSequenceName($sqn) { return sprintf($this->conn->getAttribute(Doctrine::ATTR_SEQNAME_FORMAT), - preg_replace('/[^a-z0-9_\$.]/i', '_', $sqn)); + preg_replace('/[^a-z0-9_\$.]/i', '_', $sqn)); } /** @@ -819,7 +803,7 @@ class Connection protected function _getIndexName($idx) { return sprintf($this->conn->getAttribute(Doctrine::ATTR_IDXNAME_FORMAT), - preg_replace('/[^a-z0-9_\$]/i', '_', $idx)); + preg_replace('/[^a-z0-9_\$]/i', '_', $idx)); } /** @@ -835,15 +819,6 @@ class Connection return sprintf($this->conn->getAttribute(Doctrine::ATTR_TBLNAME_FORMAT), $table);*/ } - - /** - * returns a string representation of this object - * @return string - */ - public function __toString() - { - return Doctrine_Lib::getConnectionAsString($this); - } /** * Gets the wrapped driver connection. diff --git a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php index 32a1122d9..4875cf0c8 100644 --- a/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php +++ b/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php @@ -1,10 +1,8 @@ _constructPdoDsn($params), $username, $password, $driverOptions); - $conn->setAttribute(PDO::ATTR_AUTOCOMMIT, false); + $conn->setAttribute(\PDO::ATTR_AUTOCOMMIT, false); return $conn; } @@ -54,12 +52,12 @@ class Doctrine_DBAL_Driver_PDOMySql_Driver implements Doctrine_DBAL_Driver public function getDatabasePlatform() { - return new Doctrine_DBAL_Platforms_MySqlPlatform(); + return new \Doctrine\DBAL\Platforms\MySqlPlatform(); } - public function getSchemaManager(Doctrine_DBAL_Connection $conn) + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { - return new Doctrine_DBAL_Schema_MySqlSchemaManager($conn); + return new \Doctrine\DBAL\Schema\MySqlSchemaManager($conn); } } diff --git a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php index 784d8e41f..4dbf12dff 100644 --- a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php @@ -400,56 +400,42 @@ class SqlitePlatform extends AbstractPlatform /** @override */ public function getIntegerTypeDeclarationSql(array $field) { - return 'INT ' . $this->_getCommonIntegerTypeDeclarationSql($field); + return $this->_getCommonIntegerTypeDeclarationSql($field); } /** @override */ public function getBigIntTypeDeclarationSql(array $field) { - return 'BIGINT ' . $this->_getCommonIntegerTypeDeclarationSql($field); + return $this->_getCommonIntegerTypeDeclarationSql($field); } /** @override */ public function getTinyIntTypeDeclarationSql(array $field) { - return 'TINYINT ' . $this->_getCommonIntegerTypeDeclarationSql($field); + return $this->_getCommonIntegerTypeDeclarationSql($field); } /** @override */ public function getSmallIntTypeDeclarationSql(array $field) { - return 'SMALLINT ' . $this->_getCommonIntegerTypeDeclarationSql($field); + return $this->_getCommonIntegerTypeDeclarationSql($field); } /** @override */ public function getMediumIntTypeDeclarationSql(array $field) { - return 'MEDIUMINT ' . $this->_getCommonIntegerTypeDeclarationSql($field); + return $this->_getCommonIntegerTypeDeclarationSql($field); } /** @override */ protected function _getCommonIntegerTypeDeclarationSql(array $columnDef) { - $default = $autoinc = ''; - if ( ! empty($columnDef['autoincrement'])) { - $autoinc = ' AUTO_INCREMENT'; - } else if (array_key_exists('default', $columnDef)) { - if ($field['default'] === '') { - $field['default'] = empty($columnDef['notnull']) ? null : 0; - } - if (is_null($columnDef['default'])) { - $default = ' DEFAULT NULL'; - } else { - $default = ' DEFAULT ' . $this->quote($columnDef['default']); - } - } else if (empty($columnDef['notnull'])) { - $default = ' DEFAULT NULL'; - } + $autoinc = ! empty($columnDef['autoincrement']) ? 'AUTOINCREMENT' : ''; + $pk = ! empty($columnDef['primary']) ? 'PRIMARY KEY' : ''; - $notnull = (isset($columnDef['notnull']) && $columnDef['notnull']) ? ' NOT NULL' : ''; - $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; + //$unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; - return $unsigned . $default . $notnull . $autoinc; + return "INTEGER $pk $autoinc"; } /** @@ -494,14 +480,13 @@ class SqlitePlatform extends AbstractPlatform $autoinc = false; foreach($fields as $field) { - if (isset($field['autoincrement']) && $field['autoincrement'] || - (isset($field['autoinc']) && $field['autoinc'])) { + if (isset($field['autoincrement']) && $field['autoincrement']) { $autoinc = true; break; } } - if (isset($options['primary']) && ! empty($options['primary'])) { + if ( ! $autoinc && isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_values($options['primary']); $keyColumns = array_map(array($this, 'quoteIdentifier'), $keyColumns); $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')'; diff --git a/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php b/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php index 0236a0a5b..cc1b3c5f2 100644 --- a/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php @@ -269,7 +269,7 @@ abstract class AbstractSchemaManager if ($count == 0) { $options['primary'] = array(); } - $count++; + ++$count; $options['primary'][] = $columnName; } } diff --git a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php index 0057b348a..f3c93628d 100644 --- a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -589,4 +589,3 @@ class SqliteSchemaManager extends AbstractSchemaManager } } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index b2604ef89..5a0d2aa86 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -21,12 +21,12 @@ namespace Doctrine\ORM; -#use Doctrine\Common\Configuration; use Doctrine\Common\EventManager; use Doctrine\Common\DoctrineException; use Doctrine\DBAL\Connection; use Doctrine\ORM\Exceptions\EntityManagerException; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataFactory; /** * The EntityManager is the central access point to ORM functionality. @@ -96,13 +96,6 @@ class EntityManager */ private $_metadataFactory; - /** - * The EntityPersister instances used to persist entity instances. - * - * @var array - */ - private $_persisters = array(); - /** * The EntityRepository instances. * @@ -167,7 +160,7 @@ class EntityManager $this->_name = $name; $this->_config = $config; $this->_eventManager = $eventManager; - $this->_metadataFactory = new \Doctrine\ORM\Mapping\ClassMetadataFactory( + $this->_metadataFactory = new ClassMetadataFactory( $this->_config->getMetadataDriverImpl(), $this->_conn->getDatabasePlatform()); $this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl()); @@ -285,28 +278,6 @@ class EntityManager return $query; } - /** - * Gets the EntityPersister for an Entity. - * - * This is usually not of interest for users, mainly for internal use. - * - * @param string $entityName The name of the Entity. - * @return Doctrine\ORM\Persister\AbstractEntityPersister - */ - public function getEntityPersister($entityName) - { - if ( ! isset($this->_persisters[$entityName])) { - $class = $this->getClassMetadata($entityName); - if ($class->isInheritanceTypeJoined()) { - $persister = new \Doctrine\ORM\Persisters\JoinedSubclassPersister($this, $class); - } else { - $persister = new \Doctrine\ORM\Persisters\StandardEntityPersister($this, $class); - } - $this->_persisters[$entityName] = $persister; - } - return $this->_persisters[$entityName]; - } - /** * Detaches an entity from the manager. It's lifecycle is no longer managed. * diff --git a/lib/Doctrine/ORM/Export/ClassExporter.php b/lib/Doctrine/ORM/Export/ClassExporter.php index ce704d2c8..0508ecd52 100644 --- a/lib/Doctrine/ORM/Export/ClassExporter.php +++ b/lib/Doctrine/ORM/Export/ClassExporter.php @@ -69,6 +69,7 @@ class ClassExporter $column['name'] = $mapping['columnName']; $column['type'] = $mapping['type']; $column['length'] = $mapping['length']; + $column['notnull'] = ! $mapping['nullable']; if ($class->isIdentifier($fieldName)) { $column['primary'] = true; diff --git a/lib/Doctrine/ORM/Id/Assigned.php b/lib/Doctrine/ORM/Id/Assigned.php index 936f29301..feea25f6d 100644 --- a/lib/Doctrine/ORM/Id/Assigned.php +++ b/lib/Doctrine/ORM/Id/Assigned.php @@ -2,6 +2,8 @@ namespace Doctrine\ORM\Id; +use Doctrine\Common\DoctrineException; + /** * Special generator for application-assigned identifiers (doesnt really generate anything). * @@ -20,6 +22,7 @@ class Assigned extends AbstractIdGenerator public function generate($entity) { $class = $this->_em->getClassMetadata(get_class($entity)); + $identifier = null; if ($class->isIdentifierComposite()) { $identifier = array(); $idFields = $class->getIdentifierFieldNames(); @@ -39,7 +42,7 @@ class Assigned extends AbstractIdGenerator } if ( ! $identifier) { - throw new Doctrine_Exception("Entity '$entity' is missing an assigned ID."); + throw new DoctrineException("Entity of type '" . get_class($entity) . "' is missing an assigned ID."); } return $identifier; diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 26b4c1d07..68842f1dd 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -66,7 +66,6 @@ class ObjectHydrator extends AbstractHydrator foreach ($this->_collections as $coll) { $coll->_takeSnapshot(); $coll->_setHydrationFlag(false); - //$this->_uow->addManagedCollection($coll); } // Clean up @@ -116,7 +115,7 @@ class ObjectHydrator extends AbstractHydrator private function getCollection($component) { - $coll = new \Doctrine\ORM\Collection($this->_em, $component); + $coll = new \Doctrine\ORM\PersistentCollection($this->_em, $component); $this->_collections[] = $coll; return $coll; } @@ -338,7 +337,7 @@ class ObjectHydrator extends AbstractHydrator ->getValue($baseElement)); } } else if ( ! $this->isFieldSet($baseElement, $relationAlias)) { - $coll = new \Doctrine\ORM\Collection($this->_em, $entityName); + $coll = new \Doctrine\ORM\PersistentCollection($this->_em, $entityName); $this->_collections[] = $coll; $this->setRelatedElement($baseElement, $relationAlias, $coll); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index def8a1207..f260dc21f 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -62,6 +62,7 @@ class AnnotationDriver } $mapping['type'] = $columnAnnot->type; $mapping['length'] = $columnAnnot->length; + $mapping['nullable'] = $columnAnnot->nullable; if ($idAnnot = $property->getAnnotation('DoctrineId')) { $mapping['id'] = true; } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 768e8c3bf..e518bd9bd 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -32,8 +32,8 @@ final class DoctrineJoinColumn extends \Addendum\Annotation { final class DoctrineColumn extends \Addendum\Annotation { public $type; public $length; - public $unique; - public $nullable; + public $unique = false; + public $nullable = false; } final class DoctrineOneToOne extends \Addendum\Annotation { public $targetEntity; diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 5770fab64..33edb1208 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -128,12 +128,10 @@ class OneToOneMapping extends AssociationMapping $sourceClass = $entityManager->getClassMetadata($this->_sourceEntityName); $targetClass = $entityManager->getClassMetadata($this->_targetEntityName); - $dql = 'SELECT t.* FROM ' . $targetClass->getClassName() . ' t WHERE '; + $dql = 'SELECT t FROM ' . $targetClass->getClassName() . ' t WHERE '; $params = array(); foreach ($this->_sourceToTargetKeyFields as $sourceKeyField => $targetKeyField) { - if ($params) { - $dql .= " AND "; - } + if ($params) $dql .= " AND "; $dql .= "t.$targetKeyField = ?"; $params[] = $sourceClass->getReflectionProperty($sourceKeyField)->getValue($entity); } diff --git a/lib/Doctrine/ORM/Collection.php b/lib/Doctrine/ORM/PersistentCollection.php similarity index 90% rename from lib/Doctrine/ORM/Collection.php rename to lib/Doctrine/ORM/PersistentCollection.php index 0254b8456..4ea1e24c8 100644 --- a/lib/Doctrine/ORM/Collection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -42,9 +42,8 @@ use Doctrine\ORM\Mapping\AssociationMapping; * @version $Revision: 4930 $ * @author Konsta Vesterinen * @author Roman Borschel - * @todo Rename to PersistentCollection */ -final class Collection extends \Doctrine\Common\Collections\Collection +final class PersistentCollection extends \Doctrine\Common\Collections\Collection { /** * The base type of the collection. @@ -111,6 +110,8 @@ final class Collection extends \Doctrine\Common\Collections\Collection */ private $_ownerClass; + private $_isDirty = false; + /** * Creates a new persistent collection. */ @@ -122,7 +123,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection $this->_ownerClass = $em->getClassMetadata($entityBaseType); if ($keyField !== null) { if ( ! $this->_ownerClass->hasField($keyField)) { - throw new Doctrine_Exception("Invalid field '$keyField' can't be uses as key."); + throw new DoctrineException("Invalid field '$keyField' can't be used as key."); } $this->_keyField = $keyField; } @@ -155,21 +156,21 @@ final class Collection extends \Doctrine\Common\Collections\Collection * Sets the collection owner. Used (only?) during hydration. * * @param object $entity - * @param AssociationMapping $relation + * @param AssociationMapping $assoc */ - public function _setOwner($entity, AssociationMapping $relation) + public function _setOwner($entity, AssociationMapping $assoc) { $this->_owner = $entity; - $this->_association = $relation; - if ($relation->isInverseSide()) { + $this->_association = $assoc; + if ($assoc->isInverseSide()) { // for sure bidirectional - $this->_backRefFieldName = $relation->getMappedByFieldName(); + $this->_backRefFieldName = $assoc->getMappedByFieldName(); } else { - $targetClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); - if ($targetClass->hasInverseAssociationMapping($relation->getSourceFieldName())) { + $targetClass = $this->_em->getClassMetadata($assoc->getTargetEntityName()); + if ($targetClass->hasInverseAssociationMapping($assoc->getSourceFieldName())) { // bidirectional $this->_backRefFieldName = $targetClass->getInverseAssociationMapping( - $relation->getSourceFieldName())->getSourceFieldName(); + $assoc->getSourceFieldName())->getSourceFieldName(); } } } @@ -327,7 +328,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection * * @return integer */ - protected function _compareRecords($a, $b) + private function _compareRecords($a, $b) { if ($a === $b) { return 0; @@ -342,7 +343,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection */ public function getMapping() { - return $this->relation; + return $this->_association; } /** @@ -362,10 +363,21 @@ final class Collection extends \Doctrine\Common\Collections\Collection private function _changed() { - if ( ! $this->_em->getUnitOfWork()->isCollectionScheduledForUpdate($this)) { + $this->_isDirty = true; + /*if ( ! $this->_em->getUnitOfWork()->isCollectionScheduledForUpdate($this)) { //var_dump(get_class($this->_snapshot[0])); //echo "NOT!"; //$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); - } + }*/ + } + + public function isDirty() + { + return $this->_isDirty; + } + + public function setDirty($dirty) + { + $this->_isDirty = $dirty; } } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index e7c564e17..57c9d28fc 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -2,12 +2,21 @@ namespace Doctrine\ORM\Persisters; -use Doctrine\ORM\Collection; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\PersistentCollection; -class AbstractCollectionPersister +abstract class AbstractCollectionPersister { - - public function recreate(Doctrine_Collection $coll) + protected $_em; + protected $_conn; + + public function __construct(EntityManager $em) + { + $this->_em = $em; + $this->_conn = $em->getConnection(); + } + + public function recreate(PersistentCollection $coll) { if ($coll->getRelation()->isInverseSide()) { return; @@ -15,44 +24,75 @@ class AbstractCollectionPersister //... } - public function delete(Doctrine_Collection $coll) + public function delete(PersistentCollection $coll) { if ($coll->getRelation()->isInverseSide()) { return; } //... } + + public function update(PersistentCollection $coll) + { + $this->deleteRows($coll); + $this->updateRows($coll); + $this->insertRows($coll); + } /* collection update actions */ - public function deleteRows(Collection $coll) + public function deleteRows(PersistentCollection $coll) { - //$collection->getDeleteDiff(); + if ($coll->getMapping()->isInverseSide()) { + return; // ignore inverse side + } + + $deleteDiff = $coll->getDeleteDiff(); + $sql = $this->_getDeleteRowSql($coll); + $uow = $this->_em->getUnitOfWork(); + foreach ($deleteDiff as $element) { + $this->_conn->exec($sql, $uow->getEntityIdentifier($element)); + } } - public function updateRows(Collection $coll) + public function updateRows(PersistentCollection $coll) { } - public function insertRows(Collection $coll) + public function insertRows(PersistentCollection $coll) { - //$collection->getInsertDiff(); + if ($coll->getMapping()->isInverseSide()) { + return; // ignore inverse side + } + + $insertDiff = $coll->getInsertDiff(); + $sql = $this->_getInsertRowSql($coll); + $uow = $this->_em->getUnitOfWork(); + foreach ($insertDiff as $element) { + $this->_conn->exec($sql/*, $uow->getEntityIdentifier($element)*/); + } } - protected function _getDeleteRowSql() - { - - } + /** + * Gets the SQL statement used for deleting a row from the collection. + * + * @param PersistentCollection $coll + */ + abstract protected function _getDeleteRowSql(PersistentCollection $coll); - protected function _getUpdateRowSql() - { - - } + /** + * Gets the SQL statement used for updating a row in the collection. + * + * @param PersistentCollection $coll + */ + abstract protected function _getUpdateRowSql(); - protected function _getDeleteRowSql() - { - - } + /** + * Gets the SQL statement used for inserting a row from to the collection. + * + * @param PersistentCollection $coll + */ + abstract protected function _getInsertRowSql(); } diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php index b164399dc..c51da552c 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php @@ -172,7 +172,8 @@ abstract class AbstractEntityPersister { foreach ($this->_em->getUnitOfWork()->getEntityChangeSet($entity) as $field => $change) { if (is_array($change)) { - list ($oldVal, $newVal) = each($change); + $oldVal = $change[0]; + $newVal = $change[1]; } else { $oldVal = null; $newVal = $change; @@ -182,20 +183,21 @@ abstract class AbstractEntityPersister $columnName = $this->_classMetadata->getColumnName($field); if ($this->_classMetadata->hasAssociation($field)) { - if ($newVal !== null) { - $assocMapping = $this->_classMetadata->getAssociationMapping($field); - if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { - //echo "NOT TO-ONE OR INVERSE!"; - continue; - } - foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) { - //TODO: throw exc if field not set - $otherClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName()); + $assocMapping = $this->_classMetadata->getAssociationMapping($field); + if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { + //echo "NOT TO-ONE OR INVERSE!"; + continue; + } + foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) { + //TODO: throw exc if field not set + $otherClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName()); + if (is_null($newVal)) { + $result[$sourceColumn] = null; + } else { $result[$sourceColumn] = $otherClass->getReflectionProperty( - $otherClass->getFieldName($targetColumn))->getValue($newVal); + $otherClass->getFieldName($targetColumn))->getValue($newVal); } - } else if ( ! $isInsert) { - echo "NO INSERT AND NEWVAL NULL ON 1-1 ASSOC, OWNING SIDE"; + } } else if (is_null($newVal)) { $result[$columnName] = null; diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 6b4dccc64..10d363bc6 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -19,6 +19,8 @@ * . */ +namespace Doctrine\ORM\Persisters; + /** * The joined subclass persister maps a single entity instance to several tables in the * database as it is defined by Class Table Inheritance. @@ -26,19 +28,19 @@ * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version $Revision$ - * @link www.phpdoctrine.org + * @link www.doctrine-project.org * @since 2.0 */ -class Doctrine_ORM_Persisters_JoinedSubclassPersister extends Doctrine_ORM_Persisters_AbstractEntityPersister +class JoinedSubclassPersister extends AbstractEntityPersister { /** * Inserts an entity that is part of a Class Table Inheritance hierarchy. * - * @param Doctrine_Entity $record record to be inserted + * @param object $record record to be inserted * @return boolean * @override */ - public function insert(Doctrine_ORM_Entity $entity) + public function insert($entity) { $class = $entity->getClass(); @@ -88,7 +90,7 @@ class Doctrine_ORM_Persisters_JoinedSubclassPersister extends Doctrine_ORM_Persi * @param Doctrine_Entity $record record to be updated * @return boolean whether or not the update was successful */ - protected function _doUpdate(Doctrine_ORM_Entity $record) + protected function _doUpdate($entity) { $conn = $this->_conn; $classMetadata = $this->_classMetadata; diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php new file mode 100644 index 000000000..d160102be --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -0,0 +1,26 @@ + $coll + * @override + */ + protected function _getDeleteRowSql(PersistentCollection $coll) + { + + } +} + diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index cd69b0d14..a1aa02bd9 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -1,10 +1,48 @@ getMapping(); + $targetClass = $this->_em->getClassMetadata($mapping->getTargetEntityName()); + $table = $targetClass->getTableName(); + + $ownerMapping = $targetClass->getAssociationMapping($mapping->getMappedByFieldName()); + + $setClause = ''; + foreach ($ownerMapping->getSourceToTargetKeyColumns() as $sourceCol => $targetCol) { + if ($setClause != '') $setClause .= ', '; + $setClause .= "$sourceCol = NULL"; + } + + $whereClause = ''; + foreach ($targetClass->getIdentifierColumnNames() as $idColumn) { + if ($whereClause != '') $whereClause .= ' AND '; + $whereClause .= "$idColumn = ?"; + } + + return "UPDATE $table SET $setClause WHERE $whereClause"; + } + + protected function _getInsertRowSql() + { + return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz"; + } + + /* Not used for OneToManyPersister */ + protected function _getUpdateRowSql() + { + return; + } } -?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 3a23df242..6de9ba1ac 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -508,14 +508,38 @@ class Query extends AbstractQuery /** * Gets the list of results for the query. * - * Alias for execute(array(), $hydrationMode). + * Alias for execute(array(), HYDRATE_OBJECT). * - * @param integer $hydrationMode - * @return mixed + * @return Collection */ - public function getResultList($hydrationMode = null) + public function getResultList() { - return $this->execute(array(), $hydrationMode); + return $this->execute(array(), self::HYDRATE_OBJECT); + } + + /** + * Gets the array of results for the query. + * Object graphs are represented as nested array structures. + * + * Alias for execute(array(), HYDRATE_ARRAY). + * + * @return array + */ + public function getResultArray() + { + return $this->execute(array(), self::HYDRATE_ARRAY); + } + + /** + * Gets the scalar results for the query. + * + * Alias for execute(array(), HYDRATE_SCALAR). + * + * @return array + */ + public function getScalarResult() + { + return $this->execute(array(), self::HYDRATE_SCALAR); } /** @@ -532,8 +556,7 @@ class Query extends AbstractQuery $result = $this->execute(array(), $hydrationMode); if (count($result) > 1) { throw QueryException::nonUniqueResult(); - } - + } return is_array($result) ? array_shift($result) : $result->getFirst(); } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 4ea2c1b46..4048bd84f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -23,7 +23,7 @@ namespace Doctrine\ORM; use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Internal\CommitOrderNode; -use Doctrine\ORM\Collection; +use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Exceptions\UnitOfWorkException; @@ -77,7 +77,7 @@ class UnitOfWork * * @var array */ - protected $_identityMap = array(); + private $_identityMap = array(); /** * Map of all identifiers. Keys are object ids. @@ -94,7 +94,7 @@ class UnitOfWork * * @var array */ - protected $_originalEntityData = array(); + private $_originalEntityData = array(); /** * Map of data changes. Keys are object ids. @@ -102,20 +102,20 @@ class UnitOfWork * * @var array */ - protected $_entityChangeSets = array(); + private $_entityChangeSets = array(); /** * The states of entities in this UnitOfWork. * * @var array */ - protected $_entityStates = array(); + private $_entityStates = array(); /** * Map of entities that are scheduled for dirty checking at commit time. * This is only used if automatic dirty checking is disabled. */ - protected $_scheduledForDirtyCheck = array(); + private $_scheduledForDirtyCheck = array(); /** * A list of all new entities that need to be INSERTed. @@ -124,7 +124,7 @@ class UnitOfWork * @todo Index by class name. * @todo Rename to _inserts? */ - protected $_newEntities = array(); + private $_newEntities = array(); /** * A list of all dirty entities that need to be UPDATEd. @@ -132,7 +132,7 @@ class UnitOfWork * @var array * @todo Rename to _updates? */ - protected $_dirtyEntities = array(); + private $_dirtyEntities = array(); /** * A list of all deleted entities. @@ -142,35 +142,44 @@ class UnitOfWork * @var array * @todo Rename to _deletions? */ - protected $_deletedEntities = array(); + private $_deletedEntities = array(); /** * All collection deletions. * * @var array */ - protected $_collectionDeletions = array(); + private $_collectionDeletions = array(); /** * All collection creations. * * @var array */ - protected $_collectionCreations = array(); + private $_collectionCreations = array(); /** * All collection updates. * * @var array */ - protected $_collectionUpdates = array(); + private $_collectionUpdates = array(); + + /** + * List of collections visited during a commit-phase of a UnitOfWork. + * At the end of the UnitOfWork all these collections will make new snapshots + * of their data. + * + * @var array + */ + private $_visitedCollections = array(); /** * The EntityManager that "owns" this UnitOfWork instance. * * @var Doctrine\ORM\EntityManager */ - protected $_em; + private $_em; /** * The calculator used to calculate the order in which changes to @@ -178,7 +187,21 @@ class UnitOfWork * * @var Doctrine\ORM\Internal\CommitOrderCalculator */ - protected $_commitOrderCalculator; + private $_commitOrderCalculator; + + /** + * The entity persister instances used to persist entity instances. + * + * @var array + */ + private $_persisters = array(); + + /** + * The collection persister instances used to persist collections. + * + * @var array + */ + private $_collectionPersisters = array(); /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. @@ -198,18 +221,13 @@ class UnitOfWork */ public function commit() { - // Compute changes in managed entities + // Compute changes done since last commit $this->computeChangeSets(); - /*foreach ($this->_managedCollections as $coll) { - if ($coll->isDirty()) { - - } - }*/ - if (empty($this->_newEntities) && empty($this->_deletedEntities) && - empty($this->_dirtyEntities)) { + empty($this->_dirtyEntities) && + empty($this->_collectionUpdates)) { return; // Nothing to do. } @@ -226,7 +244,11 @@ class UnitOfWork } //TODO: collection deletions (deletions of complete collections) - //TODO: collection updates (deleteRows, updateRows, insertRows on join tables) + //TODO: collection updates (deleteRows, updateRows, insertRows) + foreach ($this->_collectionUpdates as $collectionToUpdate) { + $this->getCollectionPersister($collectionToUpdate->getMapping()) + ->update($collectionToUpdate); + } //TODO: collection recreations (insertions of complete collections) // Entity deletions come last and need to be in reverse commit order @@ -236,11 +258,18 @@ class UnitOfWork //TODO: commit transaction here? + // Take new snapshots from visited collections + foreach ($this->_visitedCollections as $coll) { + $coll->_takeSnapshot(); + } + // Clear up $this->_newEntities = array(); $this->_dirtyEntities = array(); $this->_deletedEntities = array(); $this->_entityChangeSets = array(); + $this->_collectionUpdates = array(); + $this->_visitedCollections = array(); } /** @@ -258,7 +287,7 @@ class UnitOfWork } /** - * Computes all the changes that have been done to entities + * Computes all the changes that have been done to entities and collections * since the last commit and stores these changes in the _entityChangeSet map * temporarily for access by the persisters, until the UoW commit is finished. * @@ -289,10 +318,27 @@ class UnitOfWork foreach ($entities as $entity) { $oid = spl_object_hash($entity); $state = $this->getEntityState($entity); + + // Look for changes in the entity itself by comparing against the + // original data we have. if ($state == self::STATE_MANAGED || $state == self::STATE_NEW) { $actualData = array(); foreach ($class->getReflectionProperties() as $name => $refProp) { - $actualData[$name] = $refProp->getValue($entity); + if ( ! $class->isIdentifier($name) || $class->isIdentifierNatural()) { + $actualData[$name] = $refProp->getValue($entity); + } + + if ($class->isCollectionValuedAssociation($name) && ! ($actualData[$name] instanceof PersistentCollection)) { + // Inject PersistentCollection + //TODO: If $actualData[$name] is Collection then unwrap the array + $assoc = $class->getAssociationMapping($name); + $coll = new PersistentCollection($this->_em, $assoc->getTargetEntityName(), + $actualData[$name] ? $actualData[$name] : array()); + $coll->_setOwner($entity, $assoc); + if ( ! $coll->isEmpty()) $coll->setDirty(true); + $class->getReflectionProperty($name)->setValue($entity, $coll); + $actualData[$name] = $coll; + } } if ( ! isset($this->_originalEntityData[$oid])) { @@ -308,11 +354,11 @@ class UnitOfWork $entityIsDirty = false; foreach ($actualData as $propName => $actualValue) { - $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if (is_object($orgValue) && $orgValue !== $actualValue) { - $changeSet[$propName] = array($orgValue => $actualValue); + $changeSet[$propName] = array($orgValue, $actualValue); } else if ($orgValue != $actualValue || (is_null($orgValue) xor is_null($actualValue))) { - $changeSet[$propName] = array($orgValue => $actualValue); + $changeSet[$propName] = array($orgValue, $actualValue); } if (isset($changeSet[$propName])) { @@ -320,16 +366,7 @@ class UnitOfWork $assoc = $class->getAssociationMapping($propName); if ($assoc->isOneToOne() && $assoc->isOwningSide()) { $entityIsDirty = true; - } else if ( ! $assoc->isOneToOne()) { - if ( ! $actualValue instanceof Collection) { - // Inject PersistentCollection - $coll = new Collection($this->_em, $assoc->getTargetEntityName(), $actualValue); - //$coll->_takeSnapshot(); - $class->getReflectionProperty($propName)->setValue($entity, $coll); - $actualData[$propName] = $coll; - } } - //$this->_handleAssociationValueChanged($assoc, $actualValue); } else { $entityIsDirty = true; } @@ -344,7 +381,7 @@ class UnitOfWork } } - // Look for changes in associations + // Look for changes in associations of the entity if ($state == self::STATE_MANAGED) { foreach ($class->getAssociationMappings() as $assoc) { $val = $actualData[$assoc->getSourceFieldName()]; @@ -359,10 +396,7 @@ class UnitOfWork } /** - * Handles the case during changeset computation where an association value - * has changed. For a to-one association this means a new associated instance - * has been set. For a to-many association this means a new associated collection/array - * of entities has been set. + * Computes the changes of an association. * * @param $assoc * @param $value @@ -397,6 +431,7 @@ class UnitOfWork foreach ($targetClass->getReflectionProperties() as $name => $refProp) { $data[$name] = $refProp->getValue($entry); } + $oid = spl_object_hash($entry); $this->_newEntities[$oid] = $entry; $this->_entityChangeSets[$oid] = $data; @@ -407,6 +442,13 @@ class UnitOfWork // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } + + if ($value instanceof PersistentCollection && $value->isDirty()) { + if ($assoc->isOwningSide()) { + $this->_collectionUpdates[] = $value; + } + $this->_visitedCollections[] = $value; + } } /** @@ -421,7 +463,7 @@ class UnitOfWork // statement reuse and maybe bulk operations in the persister. // Same for update/delete. $className = $class->getClassName(); - $persister = $this->_em->getEntityPersister($className); + $persister = $this->getEntityPersister($className); foreach ($this->_newEntities as $entity) { if (get_class($entity) == $className) { $returnVal = $persister->insert($entity); @@ -439,6 +481,11 @@ class UnitOfWork } } + private function _executeCollectionUpdate($collectionToUpdate) + { + //... + } + /** * Executes all entity updates for entities of the specified type. * @@ -447,7 +494,7 @@ class UnitOfWork private function _executeUpdates($class) { $className = $class->getClassName(); - $persister = $this->_em->getEntityPersister($className); + $persister = $this->getEntityPersister($className); foreach ($this->_dirtyEntities as $entity) { if (get_class($entity) == $className) { $persister->update($entity); @@ -463,7 +510,7 @@ class UnitOfWork private function _executeDeletions($class) { $className = $class->getClassName(); - $persister = $this->_em->getEntityPersister($className); + $persister = $this->getEntityPersister($className); foreach ($this->_deletedEntities as $entity) { if (get_class($entity) == $className) { $persister->delete($entity); @@ -1055,34 +1102,34 @@ class UnitOfWork $this->_commitOrderCalculator->clear(); } - public function scheduleCollectionUpdate(Collection $coll) + public function scheduleCollectionUpdate(PersistentCollection $coll) { $this->_collectionUpdates[] = $coll; } - public function isCollectionScheduledForUpdate(Collection $coll) + public function isCollectionScheduledForUpdate(PersistentCollection $coll) { //... } - public function scheduleCollectionDeletion(Collection $coll) + public function scheduleCollectionDeletion(PersistentCollection $coll) { //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? $this->_collectionDeletions[] = $coll; } - public function isCollectionScheduledForDeletion(Collection $coll) + public function isCollectionScheduledForDeletion(PersistentCollection $coll) { //... } - public function scheduleCollectionRecreation(Collection $coll) + public function scheduleCollectionRecreation(PersistentCollection $coll) { $this->_collectionRecreations[] = $coll; } - public function isCollectionScheduledForRecreation(Collection $coll) + public function isCollectionScheduledForRecreation(PersistentCollection $coll) { //... } @@ -1219,21 +1266,6 @@ class UnitOfWork $this->_originalEntityData[$oid][$property] = $value; } - /** - * INTERNAL: - * For hydration purposes only. - * - * Adds a managed collection to the UnitOfWork. On commit time, the UnitOfWork - * checks all these managed collections for modifications and then initiates - * the appropriate database synchronization. - * - * @param Doctrine\ORM\Collection $coll - */ - /*public function addManagedCollection(Collection $coll) - { - - }*/ - /** * Gets the identifier of an entity. * The returned value is always an array of identifier values. If the entity @@ -1278,6 +1310,42 @@ class UnitOfWork foreach ($this->_identityMap as $entitySet) $count += count($entitySet); return $count; } + + /** + * Gets the EntityPersister for an Entity. + * + * This is usually not of interest for users, mainly for internal use. + * + * @param string $entityName The name of the Entity. + * @return Doctrine\ORM\Persister\AbstractEntityPersister + */ + public function getEntityPersister($entityName) + { + if ( ! isset($this->_persisters[$entityName])) { + $class = $this->_em->getClassMetadata($entityName); + if ($class->isInheritanceTypeJoined()) { + $persister = new \Doctrine\ORM\Persisters\JoinedSubclassPersister($this->_em, $class); + } else { + $persister = new \Doctrine\ORM\Persisters\StandardEntityPersister($this->_em, $class); + } + $this->_persisters[$entityName] = $persister; + } + return $this->_persisters[$entityName]; + } + + public function getCollectionPersister($association) + { + $type = get_class($association); + if ( ! isset($this->_collectionPersisters[$type])) { + if ($association instanceof \Doctrine\ORM\Mapping\OneToManyMapping) { + $persister = new \Doctrine\ORM\Persisters\OneToManyPersister($this->_em); + } else if ($association instanceof \Doctrine\ORM\Mapping\ManyToManyMapping) { + $persister = new \Doctrine\ORM\Persisters\ManyToManyPersister($this->_em); + } + $this->_collectionPersisters[$type] = $persister; + } + return $this->_collectionPersisters[$type]; + } } diff --git a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php index 8486d3282..b09e1f8ca 100644 --- a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php @@ -7,18 +7,8 @@ namespace Doctrine\Tests\Mocks; */ class EntityManagerMock extends \Doctrine\ORM\EntityManager { - private $_persisterMock; private $_uowMock; private $_idGenerators = array(); - - /** - * @override - */ - public function getEntityPersister($entityName) - { - return isset($this->_persisterMock[$entityName]) ? - $this->_persisterMock[$entityName] : parent::getEntityPersister($entityName); - } /** * @override @@ -39,18 +29,6 @@ class EntityManagerMock extends \Doctrine\ORM\EntityManager { $this->_uowMock = $uow; } - - /** - * Sets a (mock) persister for an entity class that will be returned when - * getEntityPersister() is invoked for that class. - * - * @param $entityName - * @param $persister - */ - public function setEntityPersister($entityName, $persister) - { - $this->_persisterMock[$entityName] = $persister; - } /** * Mock factory method to create an EntityManager. diff --git a/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php b/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php index 0829b9890..8766df2d5 100644 --- a/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php +++ b/tests/Doctrine/Tests/Mocks/UnitOfWorkMock.php @@ -9,6 +9,16 @@ namespace Doctrine\Tests\Mocks; */ class UnitOfWorkMock extends \Doctrine\ORM\UnitOfWork { private $_mockDataChangeSets = array(); + private $_persisterMock; + + /** + * @override + */ + public function getEntityPersister($entityName) + { + return isset($this->_persisterMock[$entityName]) ? + $this->_persisterMock[$entityName] : parent::getEntityPersister($entityName); + } /** * @param $entity @@ -22,6 +32,18 @@ class UnitOfWorkMock extends \Doctrine\ORM\UnitOfWork { /* MOCK API */ + /** + * Sets a (mock) persister for an entity class that will be returned when + * getEntityPersister() is invoked for that class. + * + * @param $entityName + * @param $persister + */ + public function setEntityPersister($entityName, $persister) + { + $this->_persisterMock[$entityName] = $persister; + } + public function setDataChangeSet($entity, array $mockChangeSet) { $this->_mockDataChangeSets[spl_object_hash($entity)] = $mockChangeSet; } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php new file mode 100644 index 000000000..acf6b6bef --- /dev/null +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -0,0 +1,42 @@ + $phone + * @param CmsPhonenumber $phone */ public function addPhonenumber(CmsPhonenumber $phone) { $this->phonenumbers[] = $phone; diff --git a/tests/Doctrine/Tests/ORM/EntityPersisterTest.php b/tests/Doctrine/Tests/ORM/EntityPersisterTest.php index aeaf0da09..ec2caa5e4 100644 --- a/tests/Doctrine/Tests/ORM/EntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/EntityPersisterTest.php @@ -50,8 +50,8 @@ class EntityPersisterTest extends \Doctrine\Tests\OrmTestCase $user->avatar = new ForumAvatar(); $this->_uowMock->setDataChangeSet($user, array( - 'username' => array('' => 'romanb'), - 'avatar' => array('' => $user->avatar))); + 'username' => array('', 'romanb'), + 'avatar' => array('', $user->avatar))); //insert diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicCRUDTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicCRUDTest.php index 4c2dddcfc..e273d68cc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicCRUDTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicCRUDTest.php @@ -5,6 +5,9 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\ORM\Export\ClassExporter; use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsPhonenumber; +use Doctrine\Tests\Models\CMS\CmsAddress; +use Doctrine\Tests\Models\Forum\ForumUser; +use Doctrine\Tests\Models\Forum\ForumAvatar; require_once __DIR__ . '/../../TestInit.php'; @@ -15,18 +18,21 @@ require_once __DIR__ . '/../../TestInit.php'; */ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase { - public function testSingleEntityCRUD() { + public function testBasicUnitsOfWorkWithOneToManyAssociation() { $em = $this->_em; $exporter = new ClassExporter($this->_em); $exporter->exportClasses(array( $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), - $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber') + $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'), + $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress') )); // Create $user = new CmsUser; - $user->name = 'romanb'; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'developer'; $em->save($user); $this->assertTrue(is_numeric($user->id)); $this->assertTrue($em->contains($user)); @@ -42,7 +48,7 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase { $em->flush(); $this->assertTrue($em->contains($ph)); $this->assertTrue($em->contains($user)); - $this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); // Update name $user->name = 'guilherme'; @@ -67,14 +73,57 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase { $this->assertFalse($em->getUnitOfWork()->isRegisteredRemoved($ph2)); } - /*public function testMore() { + public function testOneToManyAssociationModification() { + $user = new CmsUser; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'developer'; - $ph = new CmsPhonenumber; - $ph->phonenumber = 123456; + $ph1 = new CmsPhonenumber; + $ph1->phonenumber = "0301234"; + $ph2 = new CmsPhonenumber; + $ph2->phonenumber = "987654321"; - $this->_em->save($ph); + $user->addPhonenumber($ph1); + $user->addPhonenumber($ph2); + + $this->_em->save($user); + $this->_em->flush(); + + $this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); + + // Remove the first element from the collection + unset($user->phonenumbers[0]); + $ph1->user = null; // owning side! $this->_em->flush(); - }*/ + + $this->assertEquals(1, count($user->phonenumbers)); + $this->assertNull($ph1->user); + } + + public function testBasicOneToOne() + { + $user = new CmsUser; + $user->name = 'Roman'; + $user->username = 'romanb'; + $user->status = 'developer'; + + $address = new CmsAddress; + $address->country = 'Germany'; + $address->city = 'Berlin'; + $address->zip = '12345'; + + $user->address = $address; // inverse side + $address->user = $user; // owning side! + + $this->_em->save($user); + $this->_em->flush(); + + // Check that the foreign key has been set + $userId = $this->_em->getConnection()->execute("SELECT user_id FROM cms_addresses WHERE id=?", + array($address->id))->fetchColumn(); + $this->assertTrue(is_numeric($userId)); + } } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index ca99521d3..c10c1f749 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -128,11 +128,11 @@ class ObjectHydratorTest extends HydrationTest $this->assertTrue(is_array($result[1])); $this->assertTrue($result[0][0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); - $this->assertTrue($result[0][0]->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[0][0]->phonenumbers[0] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); $this->assertTrue($result[0][0]->phonenumbers[1] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); $this->assertTrue($result[1][0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); - $this->assertTrue($result[1][0]->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[1][0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); // first user => 2 phonenumbers $this->assertEquals(2, count($result[0][0]->phonenumbers)); @@ -284,7 +284,7 @@ class ObjectHydratorTest extends HydrationTest $this->assertTrue($result[0]['1'] instanceof \Doctrine\Tests\Models\CMS\CmsUser); $this->assertTrue($result[1]['2'] instanceof \Doctrine\Tests\Models\CMS\CmsUser); - $this->assertTrue($result[0]['1']->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0]['1']->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); // first user => 2 phonenumbers. notice the custom indexing by user id $this->assertEquals(2, count($result[0]['1']->phonenumbers)); // second user => 1 phonenumber. notice the custom indexing by user id @@ -403,14 +403,14 @@ class ObjectHydratorTest extends HydrationTest $this->assertTrue(is_array($result[1])); $this->assertTrue($result[0][0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); - $this->assertTrue($result[0][0]->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[0][0]->phonenumbers[0] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); $this->assertTrue($result[0][0]->phonenumbers[1] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); - $this->assertTrue($result[0][0]->articles instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->articles instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[0][0]->articles[0] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); $this->assertTrue($result[0][0]->articles[1] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); $this->assertTrue($result[1][0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); - $this->assertTrue($result[1][0]->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[1][0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[1][0]->phonenumbers[0] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); $this->assertTrue($result[1][0]->articles[0] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); $this->assertTrue($result[1][0]->articles[1] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); @@ -549,26 +549,26 @@ class ObjectHydratorTest extends HydrationTest $this->assertTrue($result[0][0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); $this->assertTrue($result[1][0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); // phonenumbers - $this->assertTrue($result[0][0]->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[0][0]->phonenumbers[0] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); $this->assertTrue($result[0][0]->phonenumbers[1] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); - $this->assertTrue($result[1][0]->phonenumbers instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[1][0]->phonenumbers instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[1][0]->phonenumbers[0] instanceof \Doctrine\Tests\Models\CMS\CmsPhonenumber); // articles - $this->assertTrue($result[0][0]->articles instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->articles instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[0][0]->articles[0] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); $this->assertTrue($result[0][0]->articles[1] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); $this->assertTrue($result[1][0]->articles[0] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); $this->assertTrue($result[1][0]->articles[1] instanceof \Doctrine\Tests\Models\CMS\CmsArticle); // article comments - $this->assertTrue($result[0][0]->articles[0]->comments instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->articles[0]->comments instanceof \Doctrine\ORM\PersistentCollection); $this->assertTrue($result[0][0]->articles[0]->comments[0] instanceof \Doctrine\Tests\Models\CMS\CmsComment); // empty comment collections - $this->assertTrue($result[0][0]->articles[1]->comments instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[0][0]->articles[1]->comments instanceof \Doctrine\ORM\PersistentCollection); $this->assertEquals(0, count($result[0][0]->articles[1]->comments)); - $this->assertTrue($result[1][0]->articles[0]->comments instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[1][0]->articles[0]->comments instanceof \Doctrine\ORM\PersistentCollection); $this->assertEquals(0, count($result[1][0]->articles[0]->comments)); - $this->assertTrue($result[1][0]->articles[1]->comments instanceof \Doctrine\ORM\Collection); + $this->assertTrue($result[1][0]->articles[1]->comments instanceof \Doctrine\ORM\PersistentCollection); $this->assertEquals(0, count($result[1][0]->articles[1]->comments)); } diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 01db72934..ff179a30a 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -58,7 +58,7 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase { // Setup fake persister and id generator for identity generation $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser")); - $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); $idGeneratorMock = new IdentityIdGeneratorMock($this->_emMock); $this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumUser', $idGeneratorMock); $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); @@ -99,13 +99,13 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase // Setup fake persister and id generator for identity generation //ForumUser $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser")); - $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); + $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister); $userIdGeneratorMock = new IdentityIdGeneratorMock($this->_emMock); $this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumUser', $userIdGeneratorMock); $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); // ForumAvatar $avatarPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumAvatar")); - $this->_emMock->setEntityPersister('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarPersister); + $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarPersister); $avatarIdGeneratorMock = new IdentityIdGeneratorMock($this->_emMock); $this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarIdGeneratorMock); $avatarPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY); @@ -129,7 +129,7 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(0, count($avatarPersister->getDeletes())); } - public function testComputeEntityChangeSets() + /*public function testComputeEntityChangeSets() { // We need an ID generator for ForumAvatar, because we attach a NEW ForumAvatar // to a (faked) MANAGED instance. During changeset computation this will result @@ -176,7 +176,7 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue(isset($user2ChangeSet['username'])); $this->assertEquals(array('jon' => 'jwage'), $user2ChangeSet['username']); } - + */ /* public function testSavingSingleEntityWithSequenceIdGeneratorSchedulesInsert() { diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 29dac2567..7338f3336 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -111,7 +111,7 @@ class OrmFunctionalTestCase extends OrmTestCase protected function setUp() { if ( ! isset($this->sharedFixture['conn'])) { - echo " --- CREATE CONNECTION ----"; + echo PHP_EOL . " --- CREATE CONNECTION ----" . PHP_EOL; $this->sharedFixture['conn'] = TestUtil::getConnection(); } if ( ! $this->_em) {