From bc4e14a99f26338e83c1e643293074c064bc5bdd Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 20 May 2011 20:50:03 +0200 Subject: [PATCH 01/82] Prototype for a proxy extension that avoids loads when calling for a getter that is named after an identifier. --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 20 +++++++++++++++++++ .../ORM/Functional/LifecycleCallbackTest.php | 6 +++++- .../ORM/Functional/Ticket/DDC381Test.php | 9 +++++++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 23186d7aa..5794e26f9 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -209,6 +209,11 @@ class ProxyFactory $methods .= $parameterString . ')'; $methods .= PHP_EOL . ' {' . PHP_EOL; + if ($this->isShortIdentifierGetter($method, $class)) { + $methods .= ' if ($this->__isInitialized__ === false) {' . PHP_EOL; + $methods .= ' return $this->_identifier["' . lcfirst(substr($method->getName(), 3)) . '"];' . PHP_EOL; + $methods .= ' }' . PHP_EOL; + } $methods .= ' $this->__load();' . PHP_EOL; $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; $methods .= PHP_EOL . ' }' . PHP_EOL; @@ -218,6 +223,21 @@ class ProxyFactory return $methods; } + /** + * @param ReflectionMethod $method + * @param ClassMetadata $class + * @return bool + */ + private function isShortIdentifierGetter($method, $class) + { + return ( + $method->getNumberOfParameters() == 0 && + substr($method->getName(), 0, 3) == "get" && + in_array(lcfirst(substr($method->getName(), 3)), $class->identifier, true) && + (($method->getEndLine() - $method->getStartLine()) <= 4) + ); + } + /** * Generates the code for the __sleep method for a proxy class. * diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index 1ddd7a8ff..a860ecd62 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -78,7 +78,7 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id); $this->assertFalse($reference->postLoadCallbackInvoked); - $reference->getId(); // trigger proxy load + $reference->getValue(); // trigger proxy load $this->assertTrue($reference->postLoadCallbackInvoked); } @@ -210,6 +210,10 @@ class LifecycleCallbackTestEntity return $this->id; } + public function getValue() { + return $this->value; + } + /** @PrePersist */ public function doStuffOnPrePersist() { $this->prePersistCallbackInvoked = true; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php index c25a8aa75..678135daf 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC381Test.php @@ -31,8 +31,8 @@ class DDC381Test extends \Doctrine\Tests\OrmFunctionalTestCase $entity = $this->_em->getReference('Doctrine\Tests\ORM\Functional\Ticket\DDC381Entity', $persistedId); - // explicitly load proxy - $id = $entity->getId(); + // explicitly load proxy (getId() does not trigger reload of proxy) + $id = $entity->getOtherMethod(); $data = serialize($entity); $entity = unserialize($data); @@ -55,4 +55,9 @@ class DDC381Entity { return $this->id; } + + public function getOtherMethod() + { + + } } \ No newline at end of file From d1e9bc64010c87cbfbb1746611ae2b6ec16ae4f9 Mon Sep 17 00:00:00 2001 From: kwiateusz Date: Wed, 27 Jul 2011 15:43:27 +0200 Subject: [PATCH 02/82] Now findByOne really retrieve only one entity adding limit to query. --- lib/Doctrine/ORM/EntityRepository.php | 2 +- lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index de2689db2..2b2bee7ac 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -178,7 +178,7 @@ class EntityRepository implements ObjectRepository */ public function findOneBy(array $criteria) { - return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria); + return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria, null, null, array(), 0, 1); } /** diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 19da2e200..ae16161ff 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -559,12 +559,13 @@ class BasicEntityPersister * @param $assoc The association that connects the entity to load to another entity, if any. * @param array $hints Hints for entity creation. * @param int $lockMode + * @param int $limit Limit number of results * @return object The loaded and managed entity instance or NULL if the entity can not be found. * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ - public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0) + public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null) { - $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode); + $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); From 05fb0b913a3a02a4d21fa406c515185c22bb555c Mon Sep 17 00:00:00 2001 From: Dominik Liebler Date: Thu, 11 Aug 2011 23:03:26 +0200 Subject: [PATCH 03/82] DDC-1278 - EntityManager::clear($entity) support added new parameter $entityName for UnitOfWork::clear() removed not implemented exception in EntityManager:clear() --- lib/Doctrine/ORM/EntityManager.php | 9 ++---- lib/Doctrine/ORM/UnitOfWork.php | 51 +++++++++++++++++++----------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 0379cc435..878cbd489 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -421,16 +421,11 @@ class EntityManager implements ObjectManager * Clears the EntityManager. All entities that are currently managed * by this EntityManager become detached. * - * @param string $entityName + * @param string $entityName if given, only entities of this type will get detached */ public function clear($entityName = null) { - if ($entityName === null) { - $this->unitOfWork->clear(); - } else { - //TODO - throw new ORMException("EntityManager#clear(\$entityName) not yet implemented."); - } + $this->unitOfWork->clear($entityName); } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d62eb62c7..1b29d65f3 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1790,28 +1790,41 @@ class UnitOfWork implements PropertyChangedListener /** * Clears the UnitOfWork. + * + * @param strin $entityName if given, only entities of this type will get detached */ - public function clear() + public function clear($entityName = null) { - $this->identityMap = - $this->entityIdentifiers = - $this->originalEntityData = - $this->entityChangeSets = - $this->entityStates = - $this->scheduledForDirtyCheck = - $this->entityInsertions = - $this->entityUpdates = - $this->entityDeletions = - $this->collectionDeletions = - $this->collectionUpdates = - $this->extraUpdates = - $this->orphanRemovals = array(); - if ($this->commitOrderCalculator !== null) { - $this->commitOrderCalculator->clear(); - } + if ($entityName === null) { + $this->identityMap = + $this->entityIdentifiers = + $this->originalEntityData = + $this->entityChangeSets = + $this->entityStates = + $this->scheduledForDirtyCheck = + $this->entityInsertions = + $this->entityUpdates = + $this->entityDeletions = + $this->collectionDeletions = + $this->collectionUpdates = + $this->extraUpdates = + $this->orphanRemovals = array(); + if ($this->commitOrderCalculator !== null) { + $this->commitOrderCalculator->clear(); + } - if ($this->evm->hasListeners(Events::onClear)) { - $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em)); + if ($this->evm->hasListeners(Events::onClear)) { + $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em)); + } + } else { + $visited = array(); + foreach ($this->identityMap as $className => $entities) { + if ($className === $entityName) { + foreach ($entities as $entity) { + $this->doDetach($entity, $visited); + } + } + } } } From 745535d269c81635ad9ef1c922b7371b44175541 Mon Sep 17 00:00:00 2001 From: Dominik Liebler Date: Fri, 12 Aug 2011 20:15:32 +0200 Subject: [PATCH 04/82] fixed typo --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1b29d65f3..841e515b6 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1791,7 +1791,7 @@ class UnitOfWork implements PropertyChangedListener /** * Clears the UnitOfWork. * - * @param strin $entityName if given, only entities of this type will get detached + * @param string $entityName if given, only entities of this type will get detached */ public function clear($entityName = null) { From 25f5ff0ca1e0da0753dbcc883a67477027f20b51 Mon Sep 17 00:00:00 2001 From: Dominik Liebler Date: Sat, 13 Aug 2011 20:22:23 +0200 Subject: [PATCH 05/82] DDC-1278 - EntityManager::clear($entity) support cascade detach operation only on entity name entities --- lib/Doctrine/ORM/UnitOfWork.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 841e515b6..64bca53cf 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1515,8 +1515,9 @@ class UnitOfWork implements PropertyChangedListener * * @param object $entity * @param array $visited + * @param string $entityName detach only entities of this type when given */ - private function doDetach($entity, array &$visited) + private function doDetach($entity, array &$visited, $entityName = null) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { @@ -1539,7 +1540,7 @@ class UnitOfWork implements PropertyChangedListener return; } - $this->cascadeDetach($entity, $visited); + $this->cascadeDetach($entity, $visited, $entityName); } /** @@ -1617,8 +1618,9 @@ class UnitOfWork implements PropertyChangedListener * * @param object $entity * @param array $visited + * @param string $entityName detach only entities of this type when given */ - private function cascadeDetach($entity, array &$visited) + private function cascadeDetach($entity, array &$visited, $entityName = null) { $class = $this->em->getClassMetadata(get_class($entity)); foreach ($class->associationMappings as $assoc) { @@ -1632,10 +1634,14 @@ class UnitOfWork implements PropertyChangedListener $relatedEntities = $relatedEntities->unwrap(); } foreach ($relatedEntities as $relatedEntity) { - $this->doDetach($relatedEntity, $visited); + if ($entityName === null || get_class($relatedEntity) == $entityName) { + $this->doDetach($relatedEntity, $visited, $entityName); + } } } else if ($relatedEntities !== null) { - $this->doDetach($relatedEntities, $visited); + if ($entityName === null || get_class($relatedEntities) == $entityName) { + $this->doDetach($relatedEntities, $visited, $entityName); + } } } } @@ -1812,20 +1818,20 @@ class UnitOfWork implements PropertyChangedListener if ($this->commitOrderCalculator !== null) { $this->commitOrderCalculator->clear(); } - - if ($this->evm->hasListeners(Events::onClear)) { - $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em)); - } } else { $visited = array(); foreach ($this->identityMap as $className => $entities) { if ($className === $entityName) { foreach ($entities as $entity) { - $this->doDetach($entity, $visited); + $this->doDetach($entity, $visited, $entityName); } } } } + + if ($this->evm->hasListeners(Events::onClear)) { + $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em)); + } } /** From 6e47d7b16d91c2859bf23bdf98c2c6c5854159a6 Mon Sep 17 00:00:00 2001 From: Dominik Liebler Date: Sun, 14 Aug 2011 16:12:12 +0200 Subject: [PATCH 06/82] DDC-1278 - EntityManager::clear($entity) support added test case and modified test data CmsUser to cascade detach address and articles (testing collections and single entites) --- lib/Doctrine/ORM/UnitOfWork.php | 23 ++++----- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 4 +- .../ORM/Functional/BasicFunctionalTest.php | 50 +++++++++++++++++++ 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 64bca53cf..1e4310efc 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1515,9 +1515,9 @@ class UnitOfWork implements PropertyChangedListener * * @param object $entity * @param array $visited - * @param string $entityName detach only entities of this type when given + * @param boolean $noCascade if true, don't cascade detach operation */ - private function doDetach($entity, array &$visited, $entityName = null) + private function doDetach($entity, array &$visited, $noCascade = false) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { @@ -1539,8 +1539,10 @@ class UnitOfWork implements PropertyChangedListener case self::STATE_DETACHED: return; } - - $this->cascadeDetach($entity, $visited, $entityName); + + if (!$noCascade) { + $this->cascadeDetach($entity, $visited); + } } /** @@ -1618,9 +1620,8 @@ class UnitOfWork implements PropertyChangedListener * * @param object $entity * @param array $visited - * @param string $entityName detach only entities of this type when given */ - private function cascadeDetach($entity, array &$visited, $entityName = null) + private function cascadeDetach($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); foreach ($class->associationMappings as $assoc) { @@ -1634,14 +1635,10 @@ class UnitOfWork implements PropertyChangedListener $relatedEntities = $relatedEntities->unwrap(); } foreach ($relatedEntities as $relatedEntity) { - if ($entityName === null || get_class($relatedEntity) == $entityName) { - $this->doDetach($relatedEntity, $visited, $entityName); - } + $this->doDetach($relatedEntity, $visited); } } else if ($relatedEntities !== null) { - if ($entityName === null || get_class($relatedEntities) == $entityName) { - $this->doDetach($relatedEntities, $visited, $entityName); - } + $this->doDetach($relatedEntities, $visited); } } } @@ -1823,7 +1820,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($this->identityMap as $className => $entities) { if ($className === $entityName) { foreach ($entities as $entity) { - $this->doDetach($entity, $visited, $entityName); + $this->doDetach($entity, $visited, true); } } } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index d9ac982ff..6b298c477 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -35,7 +35,7 @@ class CmsUser */ public $phonenumbers; /** - * @OneToMany(targetEntity="CmsArticle", mappedBy="user") + * @OneToMany(targetEntity="CmsArticle", mappedBy="user", cascade={"detach"}) */ public $articles; /** @@ -43,7 +43,7 @@ class CmsUser */ public $address; /** - * @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge"}) + * @ManyToMany(targetEntity="CmsGroup", inversedBy="users", cascade={"persist", "merge", "detach"}) * @JoinTable(name="cms_users_groups", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")} diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index d94aa253d..e189e8408 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -981,4 +981,54 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($article->user->__isInitialized__, "...but its initialized!"); $this->assertEquals($qc+2, $this->getCurrentQueryCount()); } + + /** + * @group DDC-1278 + */ + public function testClearWithEntityName() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $address = new CmsAddress(); + $address->city = "Springfield"; + $address->zip = "12354"; + $address->country = "Germany"; + $address->street = "Foo Street"; + $address->user = $user; + $user->address = $address; + + $article1 = new CmsArticle(); + $article1->topic = 'Foo'; + $article1->text = 'Foo Text'; + + $article2 = new CmsArticle(); + $article2->topic = 'Bar'; + $article2->text = 'Bar Text'; + + $user->addArticle($article1); + $user->addArticle($article2); + + $this->_em->persist($article1); + $this->_em->persist($article2); + $this->_em->persist($address); + $this->_em->persist($user); + $this->_em->flush(); + + $unitOfWork = $this->_em->getUnitOfWork(); + + $this->_em->clear('Doctrine\Tests\Models\CMS\CmsUser'); + + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($user)); + + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($address)); + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($article1)); + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $unitOfWork->getEntityState($article2)); + + $this->_em->clear(); + + $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address)); + } } From 06d56156dd6d5b388edb5b646e1feed4e2c5b76b Mon Sep 17 00:00:00 2001 From: Alain Hippolyte Date: Fri, 19 Aug 2011 06:11:58 +0300 Subject: [PATCH 07/82] Remove trailing spaces --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 8d62ca7ab..aac74444d 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -288,7 +288,7 @@ class extends \ implements \Doctrine\ORM\Proxy\Proxy unset($this->_entityPersister, $this->_identifier); } } - + public function __sleep() From d3143860609b37aa628e6434af73f812cbeec188 Mon Sep 17 00:00:00 2001 From: Nadav Date: Fri, 26 Aug 2011 07:42:16 +0300 Subject: [PATCH 08/82] we can (now) transform it into IS NULL --- lib/Doctrine/ORM/EntityRepository.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index de2689db2..1aa2faef7 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -204,11 +204,6 @@ class EntityRepository implements ObjectRepository ); } - if ( !isset($arguments[0])) { - // we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL. - throw ORMException::findByRequiresParameter($method.$by); - } - $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) { From 5fc6277d3f2b469eb2e4260f7cb6aa0665287009 Mon Sep 17 00:00:00 2001 From: Nadav Date: Fri, 26 Aug 2011 07:51:29 +0300 Subject: [PATCH 09/82] Oops, shouldn't have removed the condition completely... checking a parameter is provided --- lib/Doctrine/ORM/EntityRepository.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 1aa2faef7..3e1b83df6 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -203,6 +203,11 @@ class EntityRepository implements ObjectRepository "either findBy or findOneBy!" ); } + + + if (count($arguments) === 0) { + throw ORMException::findByRequiresParameter($method.$by); + } $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); From 2e389e00d40dd68462ca170ac6ee3942297b412c Mon Sep 17 00:00:00 2001 From: Nadav Date: Fri, 26 Aug 2011 08:15:28 +0300 Subject: [PATCH 10/82] Removed blank line, used empty() instead of the count() check --- lib/Doctrine/ORM/EntityRepository.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 3e1b83df6..28ba1f206 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -203,9 +203,8 @@ class EntityRepository implements ObjectRepository "either findBy or findOneBy!" ); } - - if (count($arguments) === 0) { + if (empty($arguments)) { throw ORMException::findByRequiresParameter($method.$by); } From 90725fa5297a5581b3f045bc6fc480f1f20aa9c8 Mon Sep 17 00:00:00 2001 From: Alan Bem Date: Tue, 20 Sep 2011 11:10:59 +0200 Subject: [PATCH 11/82] fixed wrong on-delete XML Schema mapping --- doctrine-mapping.xsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index 627ddf460..e8e21f193 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -150,7 +150,7 @@ - + From 2b334977f536e0d4f473ed1faa748d8c37de97db Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 20 Sep 2011 14:59:32 +0200 Subject: [PATCH 12/82] Add "return $this" to generated methods to get a fluent Enttity class --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index e53505f98..bfe562fb2 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -115,10 +115,12 @@ public function () * * * @param $ + * @return */ public function ($) { $this-> = $; +return $this; }'; private static $_addMethodTemplate = @@ -734,7 +736,8 @@ public function () '' => $variableType, '' => Inflector::camelize($fieldName), '' => $methodName, - '' => $fieldName + '' => $fieldName, + '' => $this->_getClassName($metadata) ); $method = str_replace( From 944f802d79baaf5eb16aaa86f8b7698e3fa9af17 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 20 Sep 2011 15:35:16 +0200 Subject: [PATCH 13/82] Correct indentation --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index bfe562fb2..96c3b0136 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -737,7 +737,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, - '' => $this->_getClassName($metadata) + '' => $this->_getClassName($metadata) ); $method = str_replace( From 01d900d5d7b4d60616d6c2565ab46eaffb93046b Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 20 Sep 2011 15:50:32 +0200 Subject: [PATCH 14/82] tab <-> spaces --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 96c3b0136..eb00cc8fc 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -737,7 +737,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, - '' => $this->_getClassName($metadata) + '' => $this->_getClassName($metadata) ); $method = str_replace( From f4c5c4ba01fb789f81590b8fa7c2c645f6e3dbfa Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Fri, 23 Sep 2011 18:10:58 -0300 Subject: [PATCH 15/82] branch for DDC-1335 --- lib/Doctrine/ORM/Query/Expr/From.php | 39 ++++++++++-- lib/Doctrine/ORM/QueryBuilder.php | 3 +- .../ORM/Functional/Ticket/DDC1335Test.php | 60 +++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php diff --git a/lib/Doctrine/ORM/Query/Expr/From.php b/lib/Doctrine/ORM/Query/Expr/From.php index 6646e1de2..a4ad22c3f 100644 --- a/lib/Doctrine/ORM/Query/Expr/From.php +++ b/lib/Doctrine/ORM/Query/Expr/From.php @@ -34,27 +34,54 @@ namespace Doctrine\ORM\Query\Expr; */ class From { + /** + * @var string + */ private $_from; + + /** + * @var string + */ private $_alias; - - public function __construct($from, $alias) + + /** + * @var string + */ + private $_indexBy; + + /** + * @param string $from The class name. + * @param string $alias The alias of the class. + * @param string $indexBy The index for the from. + */ + public function __construct($from, $alias, $indexBy = null) { - $this->_from = $from; - $this->_alias = $alias; + $this->_from = $from; + $this->_alias = $alias; + $this->_indexBy = $indexBy; } - + + /** + * @return string + */ public function getFrom() { return $this->_from; } + /** + * @return string + */ public function getAlias() { return $this->_alias; } + /** + * @return string + */ public function __toString() { - return $this->_from . ' ' . $this->_alias; + return $this->_from . ' ' . $this->_alias . ($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : ''); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index 9b2829408..f5914fd57 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -595,9 +595,10 @@ class QueryBuilder * * @param string $from The class name. * @param string $alias The alias of the class. + * @param string $indexBy The index for the from. * @return QueryBuilder This QueryBuilder instance. */ - public function from($from, $alias) + public function from($from, $alias,$indexBy = null) { return $this->add('from', new Expr\From($from, $alias), true); } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php new file mode 100644 index 000000000..3bd478a45 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -0,0 +1,60 @@ +_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User') + ); + + try { + $this->_schemaTool->dropSchema($classes); + $this->_schemaTool->createSchema($classes); + } catch(\Exception $e) { + + } + } + + + public function testTicket() + { + $this->markTestIncomplete(); + + $builder = $this->_em->createQueryBuilder(); + $builder->select('u')->from('Doctrine\Tests\ORM\Functional\Ticket\DDC1135User', 'u', 'u.id'); + + + $sql = $builder->getQuery()->getSQL(); + + $this->assertEquals('SELECT d0_.id AS id0, d0_.name AS name1 FROM DDC1135User INDEX BY d0_.id', $sql); + } + +} + +/** + * @Entity + */ +class DDC1135User +{ + + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + protected $id; + + /** + * @Column(type="string", length=255) + */ + protected $name; + +} \ No newline at end of file From e94b902a9bb6c5a34c1847958de4adc1e6a6da33 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 24 Sep 2011 16:10:10 -0300 Subject: [PATCH 16/82] tests for DDC-1335 --- lib/Doctrine/ORM/QueryBuilder.php | 4 +- .../ORM/Functional/Ticket/DDC1335Test.php | 184 ++++++++++++++++-- 2 files changed, 175 insertions(+), 13 deletions(-) diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index f5914fd57..938a429ae 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -598,9 +598,9 @@ class QueryBuilder * @param string $indexBy The index for the from. * @return QueryBuilder This QueryBuilder instance. */ - public function from($from, $alias,$indexBy = null) + public function from($from, $alias, $indexBy = null) { - return $this->add('from', new Expr\From($from, $alias), true); + return $this->add('from', new Expr\From($from, $alias, $indexBy), true); } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index 3bd478a45..3abd93b17 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -13,29 +13,142 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase parent::setUp(); $classes = array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User') + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135Phone'), ); try { $this->_schemaTool->dropSchema($classes); $this->_schemaTool->createSchema($classes); } catch(\Exception $e) { - + try { + $this->_schemaTool->createSchema($classes); + } catch (Exception $exc) { + $this->fail($exc->getMessage()); + } } + $this->loadFixture(); } + + public function testDql() + { + $dql = 'SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.id'; + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertEquals(sizeof($result), 3); + $this->assertArrayHasKey(1, $result); + $this->assertArrayHasKey(2, $result); + $this->assertArrayHasKey(3, $result); + + + $dql = 'SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id'; + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + $this->assertEquals(sizeof($result), 3); + $this->assertArrayHasKey('foo@foo.com', $result); + $this->assertArrayHasKey('bar@bar.com', $result); + $this->assertArrayHasKey('foobar@foobar.com', $result); + + $this->assertEquals(sizeof($result['foo@foo.com']->phones), 3); + $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); + $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); + + $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); + $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); + $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); + + $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); + $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); + $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); + + $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); + $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); + $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); + } public function testTicket() { - $this->markTestIncomplete(); - $builder = $this->_em->createQueryBuilder(); - $builder->select('u')->from('Doctrine\Tests\ORM\Functional\Ticket\DDC1135User', 'u', 'u.id'); + $builder->select('u')->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.id'); + $dql = $builder->getQuery()->getDQL(); + $result = $builder->getQuery()->getResult(); + + $this->assertEquals(sizeof($result), 3); + $this->assertArrayHasKey(1, $result); + $this->assertArrayHasKey(2, $result); + $this->assertArrayHasKey(3, $result); + $this->assertEquals('SELECT u FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1135User u INDEX BY u.id', $dql); + } + + public function testIndexByUnique() + { + $builder = $this->_em->createQueryBuilder(); + $builder->select('u')->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email'); - $sql = $builder->getQuery()->getSQL(); + $dql = $builder->getQuery()->getDQL(); + $result = $builder->getQuery()->getResult(); + + $this->assertEquals(sizeof($result), 3); + $this->assertArrayHasKey('foo@foo.com', $result); + $this->assertArrayHasKey('bar@bar.com', $result); + $this->assertArrayHasKey('foobar@foobar.com', $result); + $this->assertEquals('SELECT u FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1135User u INDEX BY u.email', $dql); + } + + public function testIndexWithJoin() + { + $builder = $this->_em->createQueryBuilder(); + $builder->select('u','p') + ->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email') + ->join('u.phones', 'p', null, null, 'p.id'); - $this->assertEquals('SELECT d0_.id AS id0, d0_.name AS name1 FROM DDC1135User INDEX BY d0_.id', $sql); + + $dql = $builder->getQuery()->getDQL(); + $result = $builder->getQuery()->getResult(); + + $this->assertEquals(sizeof($result), 3); + $this->assertArrayHasKey('foo@foo.com', $result); + $this->assertArrayHasKey('bar@bar.com', $result); + $this->assertArrayHasKey('foobar@foobar.com', $result); + + $this->assertEquals(sizeof($result['foo@foo.com']->phones), 3); + $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); + $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); + + $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); + $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); + $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); + + $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); + $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); + $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); + + $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); + $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); + $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); + + $this->assertEquals('SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id', $dql); + } + + private function loadFixture() + { + $p1 = array('11 xxxx-xxxx','11 yyyy-yyyy','11 zzzz-zzzz'); + $p2 = array('22 xxxx-xxxx','22 yyyy-yyyy','22 zzzz-zzzz'); + $p3 = array('33 xxxx-xxxx','33 yyyy-yyyy','33 zzzz-zzzz'); + + $u1 = new DDC1135User("foo@foo.com", "Foo",$p1); + $u2 = new DDC1135User("bar@bar.com", "Bar",$p2); + $u3 = new DDC1135User("foobar@foobar.com", "Foo Bar",$p3); + + $this->_em->persist($u1); + $this->_em->persist($u2); + $this->_em->persist($u3); + $this->_em->flush(); + $this->_em->clear(); } } @@ -45,16 +158,65 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase */ class DDC1135User { - /** * @Id @Column(type="integer") * @GeneratedValue */ - protected $id; + public $id; /** - * @Column(type="string", length=255) + * @Column(type="string", unique=true) */ - protected $name; + public $email; + + /** + * @Column(type="string") + */ + public $name; + + /** + * @OneToMany(targetEntity="DDC1135Phone", mappedBy="user", cascade={"persist", "remove"}) + */ + public $phones; + + public function __construct($email, $name,array $numbers) + { + $this->name = $name; + $this->email = $email; + $this->phones = new \Doctrine\Common\Collections\ArrayCollection(); + + foreach ($numbers as $number) { + $this->phones->add(new DDC1135Phone($this,$number)); + } + } +} +/** + * @Entity + */ +class DDC1135Phone +{ + /** + * @Id + * @Column(name="id", type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; + + /** + * @Column(name="number", type="string", nullable = false) + */ + public $number; + + /** + * @ManyToOne(targetEntity="DDC1135User", inversedBy="phones") + * @JoinColumn(name="user_id", referencedColumnName="id", nullable = false) + */ + public $user; + + public function __construct($user, $number) + { + $this->user = $user; + $this->number = $number; + } } \ No newline at end of file From 728724bed5349aa40952c81d3580636036592e23 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 24 Sep 2011 16:16:25 -0300 Subject: [PATCH 17/82] fix DDC-1135 --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index 3abd93b17..ea7f53842 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -6,6 +6,9 @@ use DateTime; require_once __DIR__ . '/../../../TestInit.php'; +/** + * @group DDC-1135 + */ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase { protected function setUp() From c02920762bd3709a0b94b657e65f28b09f65cb90 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 25 Sep 2011 14:41:56 +0200 Subject: [PATCH 18/82] Adjust ClassMetadataFactory to forthcoming interface change --- lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index cef558496..4b43e3070 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -50,7 +50,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface private $targetPlatform; /** - * @var Driver\Driver + * @var \Doctrine\ORM\Mapping\Driver\Driver */ private $driver; @@ -481,4 +481,15 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface throw new ORMException("Unknown generator type: " . $class->generatorType); } } + + /** + * Check if this class is mapped by this EntityManager + ClassMetadata configuration + * + * @param $class + * @return bool + */ + public function isTransient($class) + { + return $this->driver->isTransient($class); + } } From cd7029d266baa9f35888035a6940c550e57a5338 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 25 Sep 2011 16:39:41 +0200 Subject: [PATCH 19/82] DDC-1367 - Bugfix --- lib/Doctrine/ORM/Tools/Setup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Setup.php b/lib/Doctrine/ORM/Tools/Setup.php index 39b6465a3..b4a6a2c7f 100644 --- a/lib/Doctrine/ORM/Tools/Setup.php +++ b/lib/Doctrine/ORM/Tools/Setup.php @@ -97,7 +97,7 @@ class Setup require_once $directory . "/Doctrine/Common/ClassLoader.php"; } - $loader = new ClassLoader("Doctrine"); + $loader = new ClassLoader("Doctrine", $directory); $loader->register(); $loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine"); From 5fe996baf9b6ebde5e9f29fea826cb74fce6c201 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 25 Sep 2011 12:20:48 -0300 Subject: [PATCH 20/82] change tests for DDC-1135 --- lib/Doctrine/ORM/Query/Expr/From.php | 3 ++- .../ORM/Functional/Ticket/DDC1335Test.php | 27 ++++++------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Expr/From.php b/lib/Doctrine/ORM/Query/Expr/From.php index a4ad22c3f..4c0c8175a 100644 --- a/lib/Doctrine/ORM/Query/Expr/From.php +++ b/lib/Doctrine/ORM/Query/Expr/From.php @@ -82,6 +82,7 @@ class From */ public function __toString() { - return $this->_from . ' ' . $this->_alias . ($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : ''); + return $this->_from . ' ' . $this->_alias . + ($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : ''); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index ea7f53842..e12ee9ab7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -14,23 +14,14 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase protected function setUp() { parent::setUp(); - - $classes = array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135Phone'), - ); - try { - $this->_schemaTool->dropSchema($classes); - $this->_schemaTool->createSchema($classes); + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135User'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1135Phone'), + )); + $this->loadFixture(); } catch(\Exception $e) { - try { - $this->_schemaTool->createSchema($classes); - } catch (Exception $exc) { - $this->fail($exc->getMessage()); - } } - $this->loadFixture(); } @@ -45,7 +36,6 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertArrayHasKey(2, $result); $this->assertArrayHasKey(3, $result); - $dql = 'SELECT u, p FROM '.__NAMESPACE__ . '\DDC1135User u INDEX BY u.email INNER JOIN u.phones p INDEX BY p.id'; $query = $this->_em->createQuery($dql); $result = $query->getResult(); @@ -84,7 +74,7 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertArrayHasKey(1, $result); $this->assertArrayHasKey(2, $result); $this->assertArrayHasKey(3, $result); - $this->assertEquals('SELECT u FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1135User u INDEX BY u.id', $dql); + $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.id', $dql); } public function testIndexByUnique() @@ -99,7 +89,7 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertArrayHasKey('foo@foo.com', $result); $this->assertArrayHasKey('bar@bar.com', $result); $this->assertArrayHasKey('foobar@foobar.com', $result); - $this->assertEquals('SELECT u FROM Doctrine\Tests\ORM\Functional\Ticket\DDC1135User u INDEX BY u.email', $dql); + $this->assertEquals('SELECT u FROM ' . __NAMESPACE__ . '\DDC1135User u INDEX BY u.email', $dql); } public function testIndexWithJoin() @@ -108,7 +98,6 @@ class DDC1135Test extends \Doctrine\Tests\OrmFunctionalTestCase $builder->select('u','p') ->from(__NAMESPACE__ . '\DDC1135User', 'u', 'u.email') ->join('u.phones', 'p', null, null, 'p.id'); - $dql = $builder->getQuery()->getDQL(); $result = $builder->getQuery()->getResult(); @@ -182,7 +171,7 @@ class DDC1135User */ public $phones; - public function __construct($email, $name,array $numbers) + public function __construct($email, $name, array $numbers = array()) { $this->name = $name; $this->email = $email; From 9f96d4a31ad86c3a213b25299cad421d7a356e43 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 25 Sep 2011 18:08:41 +0200 Subject: [PATCH 21/82] DDC-1392 - Fix bug with merging unitialized proxies --- lib/Doctrine/ORM/UnitOfWork.php | 4 + .../ORM/Functional/Ticket/DDC1392Test.php | 127 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1392Test.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d5965d2d4..ea516f3d0 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1387,6 +1387,10 @@ class UnitOfWork implements PropertyChangedListener if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) { $managedCopy = $entity; } else { + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { + $entity->__load(); + } + // Try to look the entity up in the identity map. $id = $class->getIdentifierValues($entity); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1392Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1392Test.php new file mode 100644 index 000000000..095b1976c --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1392Test.php @@ -0,0 +1,127 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1392File'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1392Picture'), + )); + } catch (\Exception $ignored) { + } + } + + public function testFailingCase() + { + $file = new DDC1392File; + + $picture = new DDC1392Picture; + $picture->setFile($file); + + $em = $this->_em; + $em->persist($picture); + $em->flush(); + $em->clear(); + + $fileId = $file->getFileId(); + $pictureId = $picture->getPictureId(); + + $this->assertTrue($fileId > 0); + + $picture = $em->find(__NAMESPACE__ . '\DDC1392Picture', $pictureId); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $em->getUnitOfWork()->getEntityState($picture->getFile()), "Lazy Proxy should be marked MANAGED."); + + $file = $picture->getFile(); + + // With this activated there will be no problem + //$file->__load(); + + $picture->setFile(null); + + $em->clear(); + + $em->merge($file); + + $em->flush(); + + $q = $this->_em->createQuery("SELECT COUNT(e) FROM " . __NAMESPACE__ . '\DDC1392File e'); + $result = $q->getSingleScalarResult(); + + self::assertEquals(1, $result); + } +} + +/** + * @Entity + */ +class DDC1392Picture +{ + /** + * @Column(name="picture_id", type="integer") + * @Id @GeneratedValue + */ + private $pictureId; + + /** + * @ManyToOne(targetEntity="DDC1392File", cascade={"persist", "remove"}) + * @JoinColumn(name="file_id", referencedColumnName="file_id") + */ + private $file; + + /** + * Get pictureId + */ + public function getPictureId() + { + return $this->pictureId; + } + + /** + * Set file + */ + public function setFile($value = null) + { + $this->file = $value; + } + + /** + * Get file + */ + public function getFile() + { + return $this->file; + } +} + +/** + * @Entity + */ +class DDC1392File +{ + /** + * @Column(name="file_id", type="integer") + * @Id + * @GeneratedValue(strategy="AUTO") + */ + public $fileId; + + /** + * Get fileId + */ + public function getFileId() + { + return $this->fileId; + } +} \ No newline at end of file From d2cd6560c57e9a1ea9203a6b5362f3d7d4918ae6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 25 Sep 2011 19:01:37 +0200 Subject: [PATCH 22/82] DDC-1337 - Adjust MultiTableUpdateExecutor and MultiTableDeleteExecutor to use AbstractPlatform --- lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php | 2 +- lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php | 2 +- lib/vendor/doctrine-dbal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index 311c818ce..a6c22cecd 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -98,7 +98,7 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; - $this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; + $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php index b45abe83b..2607f582b 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php @@ -137,7 +137,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; - $this->_dropTempTableSql = 'DROP TABLE ' . $tempTable; + $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 82cc92144..f91395b6f 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 82cc921447fde697bf3d9f5285d0f0b8587303d8 +Subproject commit f91395b6f469b5076f52fefd64574c443b076485 From b28af2e5278013eaf508a69127676e2690337e3d Mon Sep 17 00:00:00 2001 From: docteurklein Date: Tue, 27 Sep 2011 10:36:32 +0200 Subject: [PATCH 23/82] added fluent pattern to Query\Expr\Base::add* methods --- lib/Doctrine/ORM/Query/Expr/Base.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php index abe7e54be..abc2c210e 100644 --- a/lib/Doctrine/ORM/Query/Expr/Base.php +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -45,12 +45,14 @@ abstract class Base { $this->addMultiple($args); } - + public function addMultiple($args = array()) { foreach ((array) $args as $arg) { $this->add($arg); } + + return $this; } public function add($arg) @@ -67,6 +69,8 @@ abstract class Base $this->_parts[] = $arg; } + + return $this; } public function count() @@ -79,7 +83,7 @@ abstract class Base if ($this->count() == 1) { return (string) $this->_parts[0]; } - + return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator; } -} \ No newline at end of file +} From d24f2881496fcf0faa6c7a1a22f07e3fc001d935 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Thu, 29 Sep 2011 09:31:06 +0200 Subject: [PATCH 24/82] Better error handling on missing assigned id --- lib/Doctrine/ORM/Id/AssignedGenerator.php | 8 ++++---- lib/Doctrine/ORM/ORMException.php | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php index 05c3790af..0143a157f 100644 --- a/lib/Doctrine/ORM/Id/AssignedGenerator.php +++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php @@ -53,14 +53,14 @@ class AssignedGenerator extends AbstractIdGenerator if (!$em->getUnitOfWork()->isInIdentityMap($value)) { throw ORMException::entityMissingForeignAssignedId($entity, $value); } - + // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); } else { $identifier[$idField] = $value; } } else { - throw ORMException::entityMissingAssignedId($entity); + throw ORMException::entityMissingAssignedIdForField($entity, $idField); } } } else { @@ -71,7 +71,7 @@ class AssignedGenerator extends AbstractIdGenerator if (!$em->getUnitOfWork()->isInIdentityMap($value)) { throw ORMException::entityMissingForeignAssignedId($entity, $value); } - + // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. $identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value)); } else { @@ -81,7 +81,7 @@ class AssignedGenerator extends AbstractIdGenerator throw ORMException::entityMissingAssignedId($entity); } } - + return $identifier; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index aa9657552..b28c8d32d 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -34,7 +34,7 @@ class ORMException extends Exception return new self("It's a requirement to specify a Metadata Driver and pass it ". "to Doctrine\ORM\Configuration::setMetadataDriverImpl()."); } - + public static function entityMissingForeignAssignedId($entity, $relatedEntity) { return new self( @@ -50,11 +50,18 @@ class ORMException extends Exception { return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " . "The identifier generation strategy for this entity requires the ID field to be populated before ". - "EntityManager#persist() is called. If you want automatically generated identifiers instead " . + "EntityManager#persist() is called. If you want automatically generated identifiers instead " . + "you need to adjust the metadata mapping accordingly." + ); + } + public static function entityMissingAssignedIdForField($entity, $field) + { + return new self("Entity of type " . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " . + "The identifier generation strategy for this entity requires the ID field to be populated before ". + "EntityManager#persist() is called. If you want automatically generated identifiers instead " . "you need to adjust the metadata mapping accordingly." ); } - public static function unrecognizedField($field) { return new self("Unrecognized field: $field"); @@ -130,8 +137,8 @@ class ORMException extends Exception "Unknown Entity namespace alias '$entityNamespaceAlias'." ); } - - public static function invalidEntityRepository($className) + + public static function invalidEntityRepository($className) { return new self("Invalid repository class '".$className."'. ". "it must be a Doctrine\ORM\EntityRepository."); From cd2805137056688fc0848f7fa76b479d681a3cb6 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 3 Oct 2011 01:26:43 -0300 Subject: [PATCH 25/82] Fixes DDC-1395 --- .../ORM/Tools/Console/Command/ClearCache/ResultCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php index 5db7fb296..d8efeaa44 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php @@ -78,13 +78,13 @@ EOT protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); - $cacheDriver = $em->getConfiguration()->getQueryCacheImpl(); + $cacheDriver = $em->getConfiguration()->getResultCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); } - $output->write('Clearing ALL Query cache entries' . PHP_EOL); + $output->write('Clearing ALL Result cache entries' . PHP_EOL); $result = $cacheDriver->deleteAll(); $message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; From 8efae0b232210b27200f2709e7fcb24c7f02c5de Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 3 Oct 2011 01:30:20 -0300 Subject: [PATCH 26/82] Fixes DDC-1396. --- .../Tools/Console/Command/ClearCache/MetadataCommand.php | 7 ++++++- .../ORM/Tools/Console/Command/ClearCache/QueryCommand.php | 7 ++++++- .../ORM/Tools/Console/Command/ClearCache/ResultCommand.php | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php index ef7b02e3c..13243a0f7 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, - Symfony\Component\Console; + Symfony\Component\Console, + Doctrine\Common\Cache; /** * Command to clear the metadata cache of the various cache drivers. @@ -83,6 +84,10 @@ EOT if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); } + + if ($cacheDriver instanceof Cache\ApcCache) { + throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); + } $output->write('Clearing ALL Metadata cache entries' . PHP_EOL); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php index becb78a90..6451d4ad7 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, - Symfony\Component\Console; + Symfony\Component\Console, + Doctrine\Common\Cache; /** * Command to clear the query cache of the various cache drivers. @@ -83,6 +84,10 @@ EOT if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); } + + if ($cacheDriver instanceof Cache\ApcCache) { + throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); + } $output->write('Clearing ALL Query cache entries' . PHP_EOL); diff --git a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php index d8efeaa44..1dfacb056 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, - Symfony\Component\Console; + Symfony\Component\Console, + Doctrine\Common\Cache; /** * Command to clear the result cache of the various cache drivers. @@ -83,6 +84,10 @@ EOT if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); } + + if ($cacheDriver instanceof Cache\ApcCache) { + throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); + } $output->write('Clearing ALL Result cache entries' . PHP_EOL); From ebe933810e0f25097c0067525c1b219b35495f5f Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 3 Oct 2011 02:07:07 -0300 Subject: [PATCH 27/82] Implemented HIDDEN support in DQL. Fixes DDC-1363. --- .../ORM/Query/AST/SelectExpression.php | 6 +- lib/Doctrine/ORM/Query/Lexer.php | 67 ++++++++++--------- lib/Doctrine/ORM/Query/Parser.php | 15 ++++- lib/Doctrine/ORM/Query/SqlWalker.php | 35 +++++++--- lib/Doctrine/ORM/Tools/EntityGenerator.php | 3 + .../Tests/ORM/Functional/QueryTest.php | 30 +++++++++ 6 files changed, 110 insertions(+), 46 deletions(-) diff --git a/lib/Doctrine/ORM/Query/AST/SelectExpression.php b/lib/Doctrine/ORM/Query/AST/SelectExpression.php index f1793f0c4..623a325bb 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectExpression.php +++ b/lib/Doctrine/ORM/Query/AST/SelectExpression.php @@ -23,7 +23,7 @@ namespace Doctrine\ORM\Query\AST; /** * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | - * (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable] + * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable] * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org @@ -36,11 +36,13 @@ namespace Doctrine\ORM\Query\AST; class SelectExpression extends Node { public $expression; + public $hiddenAliasResultVariable; public $fieldIdentificationVariable; - public function __construct($expression, $fieldIdentificationVariable) + public function __construct($expression, $hiddenAliasResultVariable, $fieldIdentificationVariable) { $this->expression = $expression; + $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; $this->fieldIdentificationVariable = $fieldIdentificationVariable; } diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index 9d20689cf..00fcffd8c 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -76,39 +76,40 @@ class Lexer extends \Doctrine\Common\Lexer const T_FROM = 122; const T_GROUP = 123; const T_HAVING = 124; - const T_IN = 125; - const T_INDEX = 126; - const T_INNER = 127; - const T_INSTANCE = 128; - const T_IS = 129; - const T_JOIN = 130; - const T_LEADING = 131; - const T_LEFT = 132; - const T_LIKE = 133; - const T_MAX = 134; - const T_MEMBER = 135; - const T_MIN = 136; - const T_NOT = 137; - const T_NULL = 138; - const T_NULLIF = 139; - const T_OF = 140; - const T_OR = 141; - const T_ORDER = 142; - const T_OUTER = 143; - const T_SELECT = 144; - const T_SET = 145; - const T_SIZE = 146; - const T_SOME = 147; - const T_SUM = 148; - const T_THEN = 149; - const T_TRAILING = 150; - const T_TRUE = 151; - const T_UPDATE = 152; - const T_WHEN = 153; - const T_WHERE = 154; - const T_WITH = 155; - const T_PARTIAL = 156; - const T_MOD = 157; + const T_HIDDEN = 125; + const T_IN = 126; + const T_INDEX = 127; + const T_INNER = 128; + const T_INSTANCE = 129; + const T_IS = 130; + const T_JOIN = 131; + const T_LEADING = 132; + const T_LEFT = 133; + const T_LIKE = 134; + const T_MAX = 135; + const T_MEMBER = 136; + const T_MIN = 137; + const T_NOT = 138; + const T_NULL = 139; + const T_NULLIF = 140; + const T_OF = 141; + const T_OR = 142; + const T_ORDER = 143; + const T_OUTER = 144; + const T_SELECT = 145; + const T_SET = 146; + const T_SIZE = 147; + const T_SOME = 148; + const T_SUM = 149; + const T_THEN = 150; + const T_TRAILING = 151; + const T_TRUE = 152; + const T_UPDATE = 153; + const T_WHEN = 154; + const T_WHERE = 155; + const T_WITH = 156; + const T_PARTIAL = 157; + const T_MOD = 158; /** * Creates a new query scanner object. diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index d5b57a00d..35294a0ea 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1831,7 +1831,7 @@ class Parser /** * SelectExpression ::= * IdentificationVariable | StateFieldPathExpression | - * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable] + * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] ["HIDDEN"] AliasResultVariable] * * @return Doctrine\ORM\Query\AST\SelectExpression */ @@ -1839,6 +1839,7 @@ class Parser { $expression = null; $identVariable = null; + $hiddenAliasResultVariable = false; $fieldAliasIdentificationVariable = null; $peek = $this->_lexer->glimpse(); @@ -1900,6 +1901,12 @@ class Parser if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } + + if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) { + $this->match(Lexer::T_HIDDEN); + + $hiddenAliasResultVariable = true; + } if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->_lexer->lookahead; @@ -1914,10 +1921,12 @@ class Parser } } - $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable); - if (!$supportsAlias) { + $expr = new AST\SelectExpression($expression, $hiddenAliasResultVariable, $fieldAliasIdentificationVariable); + + if ( ! $supportsAlias) { $this->_identVariableExpressions[$identVariable] = $expr; } + return $expr; } diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 0f1c79542..9a929c6d6 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -975,8 +975,9 @@ class SqlWalker implements TreeWalker */ public function walkSelectExpression($selectExpression) { - $sql = ''; - $expr = $selectExpression->expression; + $sql = ''; + $expr = $selectExpression->expression; + $hidden = $selectExpression->hiddenAliasResultVariable; if ($expr instanceof AST\PathExpression) { if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { @@ -1006,7 +1007,10 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSQLColumnAlias($columnName); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ($expr instanceof AST\AggregateExpression) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; @@ -1019,7 +1023,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ($expr instanceof AST\Subselect) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; @@ -1032,7 +1039,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ($expr instanceof AST\Functions\FunctionNode) { if ( ! $selectExpression->fieldIdentificationVariable) { $resultAlias = $this->_scalarResultCounter++; @@ -1045,7 +1055,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ( $expr instanceof AST\SimpleArithmeticExpression || $expr instanceof AST\ArithmeticTerm || @@ -1070,7 +1083,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else if ( $expr instanceof AST\NullIfExpression || $expr instanceof AST\CoalesceExpression || @@ -1090,7 +1106,10 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + + if ( ! $hidden) { + $this->_rsm->addScalarResult($columnAlias, $resultAlias); + } } else { // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index e53505f98..cc08d944f 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -399,14 +399,17 @@ public function () } $collections = array(); + foreach ($metadata->associationMappings AS $mapping) { if ($mapping['type'] & ClassMetadataInfo::TO_MANY) { $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();'; } } + if ($collections) { return $this->_prefixCodeWithSpaces(str_replace("", implode("\n", $collections), self::$_constructorMethodTemplate)); } + return ''; } diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 993ce1642..596d929f6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -533,4 +533,34 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(2, count($users)); } + + public function testQueryWithHiddenAsSelectExpression() + { + $userA = new CmsUser; + $userA->name = 'Benjamin'; + $userA->username = 'beberlei'; + $userA->status = 'developer'; + $this->_em->persist($userA); + + $userB = new CmsUser; + $userB->name = 'Roman'; + $userB->username = 'romanb'; + $userB->status = 'developer'; + $this->_em->persist($userB); + + $userC = new CmsUser; + $userC->name = 'Jonathan'; + $userC->username = 'jwage'; + $userC->status = 'developer'; + $this->_em->persist($userC); + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery("SELECT u, (SELECT COUNT(u2.id) FROM Doctrine\Tests\Models\CMS\CmsUser u2) AS HIDDEN total FROM Doctrine\Tests\Models\CMS\CmsUser u"); + $users = $query->execute(); + + $this->assertEquals(3, count($users)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]); + } } \ No newline at end of file From 24042863acbabdcd0fa1432135a9836467f3bce7 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 3 Oct 2011 02:21:14 -0300 Subject: [PATCH 28/82] BasicEntityPersister::exists() was not supporting identifiers that are associations. Fixes DDC-1382. --- .../ORM/Persisters/BasicEntityPersister.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index a093f9a09..700532e3f 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -747,6 +747,7 @@ class BasicEntityPersister * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt + * * @return array */ private function loadArrayFromStatement($assoc, $stmt) @@ -771,6 +772,8 @@ class BasicEntityPersister * @param array $assoc * @param Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll + * + * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) { @@ -784,7 +787,8 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - $hydrator->hydrateAll($stmt, $rsm, $hints); + + return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** @@ -1179,6 +1183,7 @@ class BasicEntityPersister $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); + $this->_rsm->addFieldResult($alias, $columnAlias, $field); return $sql . ' AS ' . $columnAlias; @@ -1228,7 +1233,9 @@ class BasicEntityPersister $sql = 'SELECT 1 ' . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode) . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; + list($params, $types) = $this->expandParameters($criteria); + $stmt = $this->_conn->executeQuery($sql, $params, $types); } @@ -1320,7 +1327,8 @@ class BasicEntityPersister public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - $this->loadCollectionFromStatement($assoc, $stmt, $coll); + + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** @@ -1478,6 +1486,7 @@ class BasicEntityPersister public function exists($entity, array $extraConditions = array()) { $criteria = $this->_class->getIdentifierValues($entity); + if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } @@ -1485,7 +1494,9 @@ class BasicEntityPersister $sql = 'SELECT 1' . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name) . ' WHERE ' . $this->_getSelectConditionSQL($criteria); - - return (bool) $this->_conn->fetchColumn($sql, array_values($criteria)); + + list($params, $types) = $this->expandParameters($criteria); + + return (bool) $this->_conn->fetchColumn($sql, $params); } } From bf44be86a99fe2c55e8128056dc4511bce72bab1 Mon Sep 17 00:00:00 2001 From: Christian Raue Date: Mon, 10 Oct 2011 03:02:55 +0300 Subject: [PATCH 29/82] fixed typo --- lib/Doctrine/ORM/Event/PreUpdateEventArgs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php index c61e26d4c..ab1cc15de 100644 --- a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php +++ b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php @@ -90,7 +90,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs if (!isset($this->_entityChangeSet[$field])) { throw new \InvalidArgumentException( "Field '".$field."' is not a valid field of the entity ". - "'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs." + "'".get_class($this->getEntity())."' in PreUpdateEventArgs." ); } } From 689aaef4dc79fa8207ad2550711db41315a0baab Mon Sep 17 00:00:00 2001 From: Christian Raue Date: Mon, 10 Oct 2011 09:44:17 +0300 Subject: [PATCH 30/82] added missing type hint --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ea516f3d0..85a5a3f2f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -635,7 +635,7 @@ class UnitOfWork implements PropertyChangedListener * @param object $entity The entity for which to (re)calculate the change set. * @throws InvalidArgumentException If the passed entity is not MANAGED. */ - public function recomputeSingleEntityChangeSet($class, $entity) + public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); From 1681d8a8933211c44edff633f69ad5f4cc6f515e Mon Sep 17 00:00:00 2001 From: Christian Raue Date: Mon, 10 Oct 2011 14:04:55 +0200 Subject: [PATCH 31/82] switched 2nd and 3rd argument for SelectExpression's constructor, making the 3rd one optional to keep its signature compatible to previous versions --- lib/Doctrine/ORM/Query/AST/SelectExpression.php | 6 +++--- lib/Doctrine/ORM/Query/Parser.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Query/AST/SelectExpression.php b/lib/Doctrine/ORM/Query/AST/SelectExpression.php index 623a325bb..e3c917200 100644 --- a/lib/Doctrine/ORM/Query/AST/SelectExpression.php +++ b/lib/Doctrine/ORM/Query/AST/SelectExpression.php @@ -36,14 +36,14 @@ namespace Doctrine\ORM\Query\AST; class SelectExpression extends Node { public $expression; - public $hiddenAliasResultVariable; public $fieldIdentificationVariable; + public $hiddenAliasResultVariable; - public function __construct($expression, $hiddenAliasResultVariable, $fieldIdentificationVariable) + public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false) { $this->expression = $expression; - $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; $this->fieldIdentificationVariable = $fieldIdentificationVariable; + $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; } public function dispatch($sqlWalker) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 35294a0ea..08e036c1a 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1921,7 +1921,7 @@ class Parser } } - $expr = new AST\SelectExpression($expression, $hiddenAliasResultVariable, $fieldAliasIdentificationVariable); + $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable, $hiddenAliasResultVariable); if ( ! $supportsAlias) { $this->_identVariableExpressions[$identVariable] = $expr; From de98f3fb305755fbc8dab982eaa6cae77d6e506d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 10 Oct 2011 16:30:49 +0200 Subject: [PATCH 32/82] Added composer.json --- composer.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..4ce635fd4 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "doctrine/orm", + "type": "library", + "description": "Object-Relational-Mapper for PHP", + "keywords": ["orm", "database"], + "homepage": "http://www.doctrine-project.org", + "license": "LGPL", + "authors": [ + {"name": "Guilherme Blanco"}, + {"name": "Roman Borschel"}, + {"name": "Benjamin Eberlei"}, + {"name": "Jonathan Wage"} + ], + "require": { + "php": ">=5.3.2", + "ext-pdo": "*" + } +} From 89470787d8af3f56797286e35e913b640dd1b9fc Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 10 Oct 2011 16:54:57 +0200 Subject: [PATCH 33/82] Add composer.json --- composer.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 4ce635fd4..080885107 100644 --- a/composer.json +++ b/composer.json @@ -6,13 +6,15 @@ "homepage": "http://www.doctrine-project.org", "license": "LGPL", "authors": [ - {"name": "Guilherme Blanco"}, - {"name": "Roman Borschel"}, - {"name": "Benjamin Eberlei"}, - {"name": "Jonathan Wage"} + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"} ], "require": { "php": ">=5.3.2", - "ext-pdo": "*" + "ext-pdo": "*", + "doctrine/common": "master-dev", + "doctrine/dbal": "master-dev" } } From 9058bc3f5cbd3b611e0ad3e9d69dfdd5146f4eba Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 10 Oct 2011 17:52:37 +0200 Subject: [PATCH 34/82] Fix DDC-1402 - No caching for SingleTablePersister::_getSelectColumnList --- lib/Doctrine/ORM/Persisters/SingleTablePersister.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index c9ab27c69..b39d3bf75 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -41,6 +41,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister /** {@inheritdoc} */ protected function _getSelectColumnListSQL() { + if ($this->_selectColumnListSql !== null) { + return $this->_selectColumnListSql; + } + $columnList = parent::_getSelectColumnListSQL(); // Append discriminator column @@ -81,7 +85,8 @@ class SingleTablePersister extends AbstractEntityInheritancePersister } } - return $columnList; + $this->_selectColumnListSql = $columnList; + return $this->_selectColumnListSql; } /** {@inheritdoc} */ From c7c875a06360f28fbcacbcc8f8d7e4ad527155a9 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 10 Oct 2011 19:20:01 +0200 Subject: [PATCH 35/82] Fixed the version check in the entity generator The 3.0.x branch of Common has been merged to become the incoming 2.2 release. --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index cc08d944f..599e35b3a 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -150,7 +150,7 @@ public function () public function __construct() { - if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) { + if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) { $this->_annotationsPrefix = 'ORM\\'; } } From 97a6caf059243643835e4ccd4df311ebbdea67a3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 11 Oct 2011 13:22:26 +0200 Subject: [PATCH 36/82] Update Common to latest to have the current Persistence Interface --- lib/vendor/doctrine-common | 2 +- tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common index ef431a148..b2fd909b4 160000 --- a/lib/vendor/doctrine-common +++ b/lib/vendor/doctrine-common @@ -1 +1 @@ -Subproject commit ef431a14852d7e8f2d0ea789487509ab266e5ce2 +Subproject commit b2fd909b4b5476df01744c9d34c7a23723a687b6 diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index eb7d662d6..13a774b76 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -104,7 +104,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); - $cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave')); + $cache = $this->getMock('Doctrine\Common\Cache\ArrayCache', array('doFetch', 'doSave', 'doGetStats')); $cache->expects($this->at(0)) ->method('doFetch') ->with($this->isType('string')) @@ -135,7 +135,7 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase ->will($this->returnValue($sqlExecMock)); $cache = $this->getMock('Doctrine\Common\Cache\CacheProvider', - array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush')); + array('doFetch', 'doContains', 'doSave', 'doDelete', 'doFlush', 'doGetStats')); $cache->expects($this->once()) ->method('doFetch') ->with($this->isType('string')) From a36a1624fb4deb94d9261abf7aa9bcecf7246fc1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 13 Oct 2011 22:58:22 +0200 Subject: [PATCH 37/82] [DDC-1415] Add EntityEventDelegatee, allowing to restrict emitting events to certain entity classes only. --- .../ORM/Event/EntityEventDelegator.php | 110 ++++++++++++++++++ .../ORM/Event/EntityEventDelegateeTest.php | 91 +++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 lib/Doctrine/ORM/Event/EntityEventDelegator.php create mode 100644 tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php diff --git a/lib/Doctrine/ORM/Event/EntityEventDelegator.php b/lib/Doctrine/ORM/Event/EntityEventDelegator.php new file mode 100644 index 000000000..d7c46e68e --- /dev/null +++ b/lib/Doctrine/ORM/Event/EntityEventDelegator.php @@ -0,0 +1,110 @@ +. +*/ + +namespace Doctrine\ORM\Event; + +use \Doctrine\Common\EventSubscriber; +use \LogicException; + +/** + * Delegate events only for certain entities they are registered for. + * + * @author Benjamin Eberlei + * @since 2.2 + */ +class EntityEventDelegator implements EventSubscriber +{ + /** + * Keeps track of all the event listeners. + * + * @var array + */ + private $listeners = array(); + + /** + * If frozen no new event listeners can be added. + * + * @var bool + */ + private $frozen = false; + + /** + * Adds an event listener that listens on the specified events. + * + * @param string|array $events The event(s) to listen on. + * @param string|array $entities The entities to trigger this listener for + * @param object $listener The listener object. + */ + public function addEventListener($events, $entities, $listener) + { + if ($this->frozen) { + throw new LogicException("Cannot add event listeners after EntityEventDelegator::getSubscribedEvents() " . + "is called once. This happens when you register the delegator with the event manager."); + } + + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->listeners[$event][$hash] = array('listener' => $listener, 'entities' => array_flip((array)$entities)); + } + } + + /** + * Adds an EventSubscriber. The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param Doctrine\Common\EventSubscriber $subscriber The subscriber. + */ + public function addEventSubscriber(EventSubscriber $subscriber, $entities) + { + $this->addEventListener($subscriber->getSubscribedEvents(), $entities, $subscriber); + } + + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + public function getSubscribedEvents() + { + $this->frozen = true; + return array_keys($this->listeners); + } + + /** + * Delegate the event to an appropriate listener + * + * @param $eventName + * @param $event + * @return void + */ + public function __call($eventName, $args) + { + $event = $args[0]; + foreach ($this->listeners[$eventName] AS $listenerData) { + $class = get_class($event->getEntity()); + if (isset($listenerData['entities'][$class])) { + $listenerData['listener']->$eventName($event); + } + } + } +} diff --git a/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php b/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php new file mode 100644 index 000000000..5c4db0101 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Event/EntityEventDelegateeTest.php @@ -0,0 +1,91 @@ +delegator = new \Doctrine\ORM\Event\EntityEventDelegator(); + } + + public function testGetSubscribedEventsWhenEmpty() + { + $this->assertEquals(array(), $this->delegator->getSubscribedEvents()); + } + + public function testAddListener() + { + $this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener()); + $this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents()); + } + + public function testAddSubscriber() + { + $this->delegator->addEventSubscriber(new DelegateeEventListener(), 'stdClass'); + $this->assertEquals(array('postLoad'), $this->delegator->getSubscribedEvents()); + } + + public function testAddListenerAfterFrozenThrowsException() + { + $this->delegator->getSubscribedEvents(); // freezes + + $this->setExpectedException("LogicException", "Cannot add event listeners aft"); + $this->delegator->addEventListener('postLoad', 'stdClass', new DelegateeEventListener()); + } + + public function testDelegateEvent() + { + $delegatee = new DelegateeEventListener(); + $this->delegator->addEventListener('postLoad', 'stdClass', $delegatee); + + $event = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager()); + $this->delegator->postLoad($event); + $this->delegator->postLoad($event); + + $this->assertEquals(2, $delegatee->postLoad); + } + + public function testDelegatePickEntity() + { + $delegatee = new DelegateeEventListener(); + $this->delegator->addEventListener('postLoad', 'stdClass', $delegatee); + + $event1 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \stdClass(), $this->_getTestEntityManager()); + $event2 = new \Doctrine\ORM\Event\LifecycleEventArgs(new \Doctrine\Tests\Models\CMS\CmsUser(), $this->_getTestEntityManager()); + $this->delegator->postLoad($event1); + $this->delegator->postLoad($event2); + + $this->assertEquals(1, $delegatee->postLoad); + } +} + +class DelegateeEventListener implements \Doctrine\Common\EventSubscriber +{ + public $postLoad = 0; + + public function postLoad($args) + { + $this->postLoad++; + } + + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + function getSubscribedEvents() + { + return array('postLoad'); + } +} \ No newline at end of file From 6a72ba5f9788141d988e2e9b491a8287e9b86bbd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 13 Oct 2011 23:39:11 +0200 Subject: [PATCH 38/82] DDC-1418 - Add simplified XML and YAML drivers ported from the Symfony project, thanks Fabien --- .../Mapping/Driver/SimplifiedXmlDriver.php | 176 +++++++++++++++++ .../Mapping/Driver/SimplifiedYamlDriver.php | 182 ++++++++++++++++++ .../Mapping/Symfony/AbstractDriverTest.php | 113 +++++++++++ .../ORM/Mapping/Symfony/XmlDriverTest.php | 40 ++++ .../ORM/Mapping/Symfony/YamlDriverTest.php | 40 ++++ 5 files changed, 551 insertions(+) create mode 100644 lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php create mode 100644 lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php diff --git a/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php new file mode 100644 index 000000000..e60eab779 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php @@ -0,0 +1,176 @@ +. +*/ + +namespace Doctrine\ORM\Mapping\Driver; + +use Doctrine\ORM\Mapping\MappingException; + +/** + * XmlDriver that additionally looks for mapping information in a global file. + * + * @author Fabien Potencier + * @author Benjamin Eberlei + * @license MIT + */ +class SimplifiedXmlDriver extends XmlDriver +{ + protected $_prefixes = array(); + protected $_globalBasename; + protected $_classCache; + protected $_fileExtension = '.orm.xml'; + + public function __construct($prefixes) + { + $this->addNamespacePrefixes($prefixes); + } + + public function setGlobalBasename($file) + { + $this->_globalBasename = $file; + } + + public function getGlobalBasename() + { + return $this->_globalBasename; + } + + public function addNamespacePrefixes($prefixes) + { + $this->_prefixes = array_merge($this->_prefixes, $prefixes); + $this->addPaths(array_flip($prefixes)); + } + + public function getNamespacePrefixes() + { + return $this->_prefixes; + } + + public function isTransient($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + // The mapping is defined in the global mapping file + if (isset($this->_classCache[$className])) { + return false; + } + + try { + $this->_findMappingFile($className); + + return false; + } catch (MappingException $e) { + return true; + } + } + + public function getAllClassNames() + { + if (null === $this->_classCache) { + $this->initialize(); + } + + $classes = array(); + + if ($this->_paths) { + foreach ((array) $this->_paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->_fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->_prefixes[$path])) { + $classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName); + } else { + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + } + + return array_merge($classes, array_keys($this->_classCache)); + } + + public function getElement($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + if (!isset($this->_classCache[$className])) { + $this->_classCache[$className] = parent::getElement($className); + } + + return $this->_classCache[$className]; + } + + protected function initialize() + { + $this->_classCache = array(); + if (null !== $this->_globalBasename) { + foreach ($this->_paths as $path) { + if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) { + $this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file)); + } + } + } + } + + protected function _findMappingFile($className) + { + $defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension; + foreach ($this->_paths as $path) { + if (!isset($this->_prefixes[$path])) { + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return $path.DIRECTORY_SEPARATOR.$defaultFileName; + } + + continue; + } + + $prefix = $this->_prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension; + if (is_file($filename)) { + return $filename; + } + + throw MappingException::mappingFileNotFound($className, $filename); + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension); + } +} diff --git a/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php new file mode 100644 index 000000000..679ee85cc --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php @@ -0,0 +1,182 @@ +. +*/ + +namespace Doctrine\ORM\Mapping\Driver; + +use Doctrine\ORM\Mapping\MappingException; + +/** + * YamlDriver that additionally looks for mapping information in a global file. + * + * @author Fabien Potencier + * @author Benjamin Eberlei + * @license MIT + */ +class SimplifiedYamlDriver extends YamlDriver +{ + protected $_prefixes = array(); + protected $_globalBasename; + protected $_classCache; + protected $_fileExtension = '.orm.yml'; + + public function __construct($prefixes) + { + $this->addNamespacePrefixes($prefixes); + } + + public function setGlobalBasename($file) + { + $this->_globalBasename = $file; + } + + public function getGlobalBasename() + { + return $this->_globalBasename; + } + + public function addNamespacePrefixes($prefixes) + { + $this->_prefixes = array_merge($this->_prefixes, $prefixes); + $this->addPaths(array_flip($prefixes)); + } + + public function addNamespacePrefix($prefix, $path) + { + $this->_prefixes[$path] = $prefix; + } + + + public function getNamespacePrefixes() + { + return $this->_prefixes; + } + + public function isTransient($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + // The mapping is defined in the global mapping file + if (isset($this->_classCache[$className])) { + return false; + } + + try { + $this->_findMappingFile($className); + + return false; + } catch (MappingException $e) { + return true; + } + } + + public function getAllClassNames() + { + if (null === $this->_classCache) { + $this->initialize(); + } + + $classes = array(); + + if ($this->_paths) { + foreach ((array) $this->_paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->_fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $this->_globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->_prefixes[$path])) { + $classes[] = $this->_prefixes[$path].'\\'.str_replace('.', '\\', $fileName); + } else { + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + } + + return array_merge($classes, array_keys($this->_classCache)); + } + + public function getElement($className) + { + if (null === $this->_classCache) { + $this->initialize(); + } + + if (!isset($this->_classCache[$className])) { + $this->_classCache[$className] = parent::getElement($className); + } + + return $this->_classCache[$className]; + } + + protected function initialize() + { + $this->_classCache = array(); + if (null !== $this->_globalBasename) { + foreach ($this->_paths as $path) { + if (is_file($file = $path.'/'.$this->_globalBasename.$this->_fileExtension)) { + $this->_classCache = array_merge($this->_classCache, $this->_loadMappingFile($file)); + } + } + } + } + + protected function _findMappingFile($className) + { + $defaultFileName = str_replace('\\', '.', $className).$this->_fileExtension; + foreach ($this->_paths as $path) { + if (!isset($this->_prefixes[$path])) { + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return $path.DIRECTORY_SEPARATOR.$defaultFileName; + } + + continue; + } + + $prefix = $this->_prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->_fileExtension; + if (is_file($filename)) { + return $filename; + } + + throw MappingException::mappingFileNotFound($className, $filename); + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->_fileExtension); + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php new file mode 100644 index 000000000..d16db4fbb --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/AbstractDriverTest.php @@ -0,0 +1,113 @@ +. +*/ + +namespace Doctrine\Tests\ORM\Mapping\Symfony; + +/** + * @group DDC-1418 + */ +abstract class AbstractDriverTest extends \PHPUnit_Framework_TestCase +{ + public function testFindMappingFile() + { + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\EntityFoo' => 'foo', + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + touch($filename = $this->dir.'/Foo'.$this->getFileExtension()); + $this->assertEquals($filename, $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo'))); + } + + public function testFindMappingFileInSubnamespace() + { + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + touch($filename = $this->dir.'/Foo.Bar'.$this->getFileExtension()); + $this->assertEquals($filename, $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo\Bar'))); + } + + public function testFindMappingFileNamespacedFoundFileNotFound() + { + $this->setExpectedException( + 'Doctrine\ORM\Mapping\MappingException', + "No mapping file found named '".$this->dir."/Foo".$this->getFileExtension()."' for class 'MyNamespace\MySubnamespace\Entity\Foo'." + ); + + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + $this->invoke($driver, '_findMappingFile', array('MyNamespace\MySubnamespace\Entity\Foo')); + } + + public function testFindMappingNamespaceNotFound() + { + $this->setExpectedException( + 'Doctrine\ORM\Mapping\MappingException', + "No mapping file found named 'Foo".$this->getFileExtension()."' for class 'MyOtherNamespace\MySubnamespace\Entity\Foo'." + ); + + $driver = $this->getDriver(array( + 'MyNamespace\MySubnamespace\Entity' => $this->dir, + )); + + $this->invoke($driver, '_findMappingFile', array('MyOtherNamespace\MySubnamespace\Entity\Foo')); + } + + protected function setUp() + { + $this->dir = sys_get_temp_dir().'/abstract_driver_test'; + @mkdir($this->dir, 0777, true); + } + + protected function tearDown() + { + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir), \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $path) { + if ($path->isDir()) { + @rmdir($path); + } else { + @unlink($path); + } + } + + @rmdir($this->dir); + } + + abstract protected function getFileExtension(); + abstract protected function getDriver(array $paths = array()); + + private function setField($obj, $field, $value) + { + $ref = new \ReflectionProperty($obj, $field); + $ref->setAccessible(true); + $ref->setValue($obj, $value); + } + + private function invoke($obj, $method, array $args = array()) { + $ref = new \ReflectionMethod($obj, $method); + $ref->setAccessible(true); + + return $ref->invokeArgs($obj, $args); + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php new file mode 100644 index 000000000..5908b674a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/XmlDriverTest.php @@ -0,0 +1,40 @@ +. +*/ + +namespace Doctrine\Tests\ORM\Mapping\Symfony; + +use \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; + +/** + * @group DDC-1418 + */ +class XmlDriverTest extends AbstractDriverTest +{ + protected function getFileExtension() + { + return '.orm.xml'; + } + + protected function getDriver(array $paths = array()) + { + $driver = new SimplifiedXmlDriver(array_flip($paths)); + + return $driver; + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php new file mode 100644 index 000000000..c5d8d1cd1 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/Symfony/YamlDriverTest.php @@ -0,0 +1,40 @@ +. +*/ + +namespace Doctrine\Tests\ORM\Mapping\Symfony; + +use \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver; + +/** + * @group DDC-1418 + */ +class YamlDriverTest extends AbstractDriverTest +{ + protected function getFileExtension() + { + return '.orm.yml'; + } + + protected function getDriver(array $paths = array()) + { + $driver = new SimplifiedYamlDriver(array_flip($paths)); + + return $driver; + } +} From f86e1ba66cbc529129308378244036cc5ec9dcf3 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 15 Oct 2011 00:18:57 -0300 Subject: [PATCH 39/82] Added tests for DDC-1389. Everything is working in 2.2-DEV. --- .../Tests/Models/Company/CompanyContract.php | 6 +- .../ORM/Query/SelectSqlGenerationTest.php | 154 +++++++++++++++++- 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContract.php b/tests/Doctrine/Tests/Models/Company/CompanyContract.php index fd534f403..655d4fccb 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyContract.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyContract.php @@ -7,7 +7,11 @@ namespace Doctrine\Tests\Models\Company; * @Table(name="company_contracts") * @InheritanceType("SINGLE_TABLE") * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"fix" = "CompanyFixContract", "flexible" = "CompanyFlexContract", "flexultra" = "CompanyFlexUltraContract"}) + * @DiscriminatorMap({ + * "fix" = "CompanyFixContract", + * "flexible" = "CompanyFlexContract", + * "flexultra" = "CompanyFlexUltraContract" + * }) */ abstract class CompanyContract { diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 6ef886035..6f661e00c 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -33,7 +33,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase } $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) - ->useQueryCache(false); + ->useQueryCache(false); foreach ($queryHints AS $name => $value) { $query->setHint($name, $value); @@ -54,7 +54,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase * @param array $queryHints * @param array $queryParams */ - public function assertInvalidSqlGeneration($dqlToBeTested, $expectedException, array $queryHints = array(), array $queryParams = array()) + public function assertInvalidSqlGeneration($dqlToBeTested, $expectedException, array $queryHints = array(Query::HINT_FORCE_PARTIAL_LOAD => true), array $queryParams = array()) { $this->setExpectedException($expectedException); @@ -65,7 +65,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase } $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) - ->useQueryCache(false); + ->useQueryCache(false); foreach ($queryHints AS $name => $value) { $query->setHint($name, $value); @@ -729,12 +729,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = 'true'" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = true" ); $this->assertSqlGeneration( "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false", - "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = 'false'" + "SELECT b0_.id AS id0, b0_.booleanField AS booleanField1 FROM boolean_model b0_ WHERE b0_.booleanField = false" ); $this->_em->getConnection()->setDatabasePlatform($oldPlat); @@ -1095,6 +1095,150 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "Doctrine\ORM\Query\QueryException" ); } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInRootClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInRootClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p', + 'SELECT c0_.id AS id0, c0_.name AS name1, c0_.discr AS discr2 FROM company_persons c0_', + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInChildClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c2_.car_id AS car_id6, c0_.discr AS discr7, c0_.spouse_id AS spouse_id8 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ ON c1_.id = c2_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInChildClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c0_.discr AS discr5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInLeafClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c2_.car_id AS car_id8 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeJoinInLeafClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT m FROM Doctrine\Tests\Models\Company\CompanyManager m', + 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.salary AS salary2, c1_.department AS department3, c1_.startDate AS startDate4, c2_.title AS title5, c0_.discr AS discr6 FROM company_managers c2_ INNER JOIN company_employees c1_ ON c2_.id = c1_.id INNER JOIN company_persons c0_ ON c2_.id = c0_.id', + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInRootClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6, c0_.salesPerson_id AS salesPerson_id7 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInRootClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c0_.hoursWorked AS hoursWorked3, c0_.pricePerHour AS pricePerHour4, c0_.maxPrice AS maxPrice5, c0_.discr AS discr6 FROM company_contracts c0_ WHERE c0_.discr IN ('fix', 'flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInChildClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInChildClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fc FROM Doctrine\Tests\Models\Company\CompanyFlexContract fc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexible', 'flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInLeafClassWithDisabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5, c0_.salesPerson_id AS salesPerson_id6 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } + + /** + * @group DDC-1389 + */ + public function testInheritanceTypeSingleTableInLeafClassWithEnabledForcePartialLoad() + { + $this->assertSqlGeneration( + 'SELECT fuc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract fuc', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.hoursWorked AS hoursWorked2, c0_.pricePerHour AS pricePerHour3, c0_.maxPrice AS maxPrice4, c0_.discr AS discr5 FROM company_contracts c0_ WHERE c0_.discr IN ('flexultra')", + array(Query::HINT_FORCE_PARTIAL_LOAD => true) + ); + } } From 2518f0687cf0edad73a6373b0c085e95a5a57e7b Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 15 Oct 2011 00:21:11 -0300 Subject: [PATCH 40/82] Removed invalid default argument. --- tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 6f661e00c..8e1585819 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -54,7 +54,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase * @param array $queryHints * @param array $queryParams */ - public function assertInvalidSqlGeneration($dqlToBeTested, $expectedException, array $queryHints = array(Query::HINT_FORCE_PARTIAL_LOAD => true), array $queryParams = array()) + public function assertInvalidSqlGeneration($dqlToBeTested, $expectedException, array $queryHints = array(), array $queryParams = array()) { $this->setExpectedException($expectedException); From 772b4135793f4405fc004cd008351a5ddfe8bdf5 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 15 Oct 2011 00:23:55 -0300 Subject: [PATCH 41/82] Fixed bug with boolean values being converted to string. --- lib/Doctrine/ORM/Query/SqlWalker.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 9a929c6d6..188350197 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1126,8 +1126,9 @@ class SqlWalker implements TreeWalker if ( ! isset($this->_selectedClasses[$dqlAlias])) { $this->_selectedClasses[$dqlAlias] = $class; } - + $beginning = true; + // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { @@ -1146,6 +1147,7 @@ class SqlWalker implements TreeWalker . ' AS ' . $columnAlias; $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } @@ -1157,6 +1159,7 @@ class SqlWalker implements TreeWalker foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); + foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; @@ -1178,8 +1181,10 @@ class SqlWalker implements TreeWalker if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($beginning) $beginning = false; else $sql .= ', '; + $columnAlias = $this->getSQLColumnAlias($srcColumn); $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; + $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); } } @@ -1845,12 +1850,16 @@ class SqlWalker implements TreeWalker switch ($literal->type) { case AST\Literal::STRING: return $this->_conn->quote($literal->value); + case AST\Literal::BOOLEAN: $bool = strtolower($literal->value) == 'true' ? true : false; $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); - return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal; + + return $boolVal; + case AST\Literal::NUMERIC: return $literal->value; + default: throw QueryException::invalidLiteral($literal); } From 894bbb020c83786258eedc2521729985c1f46b7f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 09:57:35 +0200 Subject: [PATCH 42/82] DDC-1394 - Enhance test to verify --- tests/Doctrine/Tests/ORM/Functional/TypeTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/TypeTest.php b/tests/Doctrine/Tests/ORM/Functional/TypeTest.php index 25523bb80..8a2eab70e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/TypeTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/TypeTest.php @@ -37,6 +37,10 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(0.1515, $decimal->highScale); } + /** + * @group DDC-1394 + * @return void + */ public function testBoolean() { $bool = new BooleanModel(); @@ -46,7 +50,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b"; + $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = true"; $bool = $this->_em->createQuery($dql)->getSingleResult(); $this->assertTrue($bool->booleanField); @@ -56,7 +60,7 @@ class TypeTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b"; + $dql = "SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b WHERE b.booleanField = false"; $bool = $this->_em->createQuery($dql)->getSingleResult(); $this->assertFalse($bool->booleanField); From 3801e0c230b5bf4060ee72800a270340ffee2355 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 11:52:41 +0200 Subject: [PATCH 43/82] Add way to keep track of read only objects in the UnitOfWork which are never updated during flush. Changed the behavior of EntityManager#getPartialReference to be read-only. No changes are ever done to this entities. Changed UnitOfWork#computeChangeSet to never create a changeset for fields that are partially omitted from a DQL or NativeQuery. To check if an entity is read only use the new API: if ($entityManager->getUnitOfWork()->isReadOnly($entity)) --- lib/Doctrine/ORM/EntityManager.php | 1 + lib/Doctrine/ORM/UnitOfWork.php | 58 ++++++++++++++++++- .../ORM/Functional/BasicFunctionalTest.php | 3 +- .../ORM/Functional/DefaultValuesTest.php | 25 +++++++- 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 9be5b50da..04a30dc46 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -413,6 +413,7 @@ class EntityManager implements ObjectManager $entity = $class->newInstance(); $class->setIdentifierValues($entity, $identifier); $this->unitOfWork->registerManaged($entity, $identifier, array()); + $this->unitOfWork->markReadOnly($entity); return $entity; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ea516f3d0..06541cee0 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -215,8 +215,13 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $orphanRemovals = array(); - - //private $_readOnlyObjects = array(); + + /** + * Read-Only objects are never evaluated + * + * @var array + */ + private $readOnlyObjects = array(); /** * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. @@ -403,6 +408,11 @@ class UnitOfWork implements PropertyChangedListener } $oid = spl_object_hash($entity); + + if (isset($this->readOnlyObjects[$oid])) { + return; + } + $actualData = array(); foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); @@ -459,6 +469,15 @@ class UnitOfWork implements PropertyChangedListener foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + if (isset($originalData[$propName])) { + $orgValue = $originalData[$propName]; + } else if (array_key_exists($propName, $originalData)) { + $orgValue = null; + } else { + // skip field, its a partially omitted one! + continue; + } + if (isset($class->associationMappings[$propName])) { $assoc = $class->associationMappings[$propName]; if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) { @@ -528,7 +547,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects - if (/* $entity is readOnly || */ $entity instanceof Proxy && ! $entity->__isInitialized__) { + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { continue; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. @@ -2407,4 +2426,37 @@ class UnitOfWork implements PropertyChangedListener { return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj); } + + /** + * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit(). + * + * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information + * on this object that might be necessary to perform a correct udpate. + * + * @throws \InvalidArgumentException + * @param $object + * @return void + */ + public function markReadOnly($object) + { + if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { + throw new InvalidArgumentException("Managed entity required"); + } + $this->readOnlyObjects[spl_object_hash($object)] = true; + } + + /** + * Is this entity read only? + * + * @throws \InvalidArgumentException + * @param $object + * @return void + */ + public function isReadOnly($object) + { + if ( ! is_object($object) ) { + throw new InvalidArgumentException("Managed entity required"); + } + return $this->readOnlyObjects[spl_object_hash($object)]; + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index bed8de33d..8d636dffb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -863,7 +863,6 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testGetPartialReferenceToUpdateObjectWithoutLoadingIt() { - //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); $user = new CmsUser(); $user->username = "beberlei"; $user->name = "Benjamin E."; @@ -882,7 +881,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $this->assertEquals('Stephan', $this->_em->find(get_class($user), $userId)->name); + $this->assertEquals('Benjamin E.', $this->_em->find(get_class($user), $userId)->name); } public function testMergePersistsNewEntities() diff --git a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php index 3da6bc09b..2e17ccf12 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php @@ -54,7 +54,30 @@ class DefaultValuesTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($userId, $a2->getUser()->getId()); $this->assertEquals('Poweruser', $a2->getUser()->type); } - + + /** + * @group DDC-1386 + */ + public function testGetPartialReferenceWithDefaultValueNotEvalutedInFlush() + { + $user = new DefaultValueUser; + $user->name = 'romanb'; + $user->type = 'Normaluser'; + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->getPartialReference('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id); + $this->assertTrue($this->_em->getUnitOfWork()->isReadOnly($user)); + + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->find('Doctrine\Tests\ORM\Functional\DefaultValueUser', $user->id); + + $this->assertEquals('Normaluser', $user->type); + } } From 291d2fd6242c4c585f51cf48f8b759dbe7e7d482 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 15:49:41 +0200 Subject: [PATCH 44/82] UPGRADE_TO_2_2 --- UPGRADE_TO_2_2 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2 index 76372b221..220fb39f9 100644 --- a/UPGRADE_TO_2_2 +++ b/UPGRADE_TO_2_2 @@ -1,3 +1,14 @@ +# EntityManager#getPartialReference() creates read-only entity + +Entities returned from EntityManager#getPartialReference() are now marked as read-only if they +haven't been in the identity map before. This means objects of this kind never lead to changes +in the UnitOfWork. + +# Fields omitted in a partial DQL query or a native query are never updated + +Fields of an entity that are not returned from a partial DQL Query or native SQL query +will never be updated through an UPDATE statement. + # Removed support for onUpdate in @JoinColumn The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. From 640facd26aa38e6825e9c75d99b4b67fb38cd758 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 15:51:11 +0200 Subject: [PATCH 45/82] Remove unncessary line --- lib/Doctrine/ORM/UnitOfWork.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 06541cee0..73d9757de 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -468,7 +468,6 @@ class UnitOfWork implements PropertyChangedListener $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); foreach ($actualData as $propName => $actualValue) { - $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if (isset($originalData[$propName])) { $orgValue = $originalData[$propName]; } else if (array_key_exists($propName, $originalData)) { From dd6f6cb097c202becf4b85577b0babe7c87943f1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 16:03:50 +0200 Subject: [PATCH 46/82] Fix notice --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 73d9757de..f53ec67a7 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2456,6 +2456,6 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) ) { throw new InvalidArgumentException("Managed entity required"); } - return $this->readOnlyObjects[spl_object_hash($object)]; + return isset($this->readOnlyObjects[spl_object_hash($object)]); } } From 3b46df68ea2e24760a8c02a5eedcda3f782cc0ec Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 16:36:20 +0200 Subject: [PATCH 47/82] Adjust README.markdown --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index e279f5f06..a0b5f2a20 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,6 @@ # Doctrine 2 ORM -Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides transparent persistence +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility From e38076c19aed1ee7d47e66c5cabcbaef33e147f4 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 16:57:57 +0200 Subject: [PATCH 48/82] DDC-1421 - Fix potential bug and code-smells --- lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 700532e3f..a0382a04d 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1060,10 +1060,10 @@ class BasicEntityPersister if ($columnList) $columnList .= ', '; $columnAlias = $srcColumn . $this->_sqlAliasCounter++; - $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) - . '.' . $srcColumn . ' AS ' . $columnAlias; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); - $this->_rsm->addMetaResult($alias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, isset($assoc['id']) && $assoc['id'] === true); + $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) + . '.' . $srcColumn . ' AS ' . $resultColumnName; + $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); } } From 8d1b852aa22569fc62c0b9a3ea9b13713b08de84 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 15 Oct 2011 17:31:09 +0200 Subject: [PATCH 49/82] Added tests for not loading the entity + fixed a test --- .../Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php | 5 ++++- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index a860ecd62..35eb5443c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -78,6 +78,9 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id); $this->assertFalse($reference->postLoadCallbackInvoked); + $reference->getId(); // doesn't trigger proxy load + $this->assertFalse($reference->postLoadCallbackInvoked); + $reference->getValue(); // trigger proxy load $this->assertTrue($reference->postLoadCallbackInvoked); } @@ -278,4 +281,4 @@ class LifecycleListenerPreUpdate { $eventArgs->setNewValue('name', 'Bob'); } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php index 93a89818e..467577a43 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1238Test.php @@ -52,6 +52,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); + // force proxy load, getId() doesn't work anymore + $user->getName(); $userId = $user->getId(); $this->_em->clear(); @@ -60,6 +62,8 @@ class DDC1238Test extends \Doctrine\Tests\OrmFunctionalTestCase $user2 = $this->_em->getReference(__NAMESPACE__ . '\\DDC1238User', $userId); + // force proxy load, getId() doesn't work anymore + $user->getName(); $this->assertNull($user->getId(), "Now this is null, we already have a user instance of that type"); } } From cb21f3c5ffa731e85293fa2086c72dc61f1e4c5d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 17:47:09 +0200 Subject: [PATCH 50/82] DDC-1414 - Missing push to $newNodes --- lib/Doctrine/ORM/UnitOfWork.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index f53ec67a7..d3cad211c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -871,6 +871,7 @@ class UnitOfWork implements PropertyChangedListener $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ( ! $calc->hasClass($targetClass->name)) { $calc->addClass($targetClass); + $newNodes[] = $targetClass; } $calc->addDependency($targetClass, $class); // If the target class has mapped subclasses, From 52cea015637b0969a1beca96c126cddc0a404f71 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 17:53:04 +0200 Subject: [PATCH 51/82] DDC-1411 - Fixed onDelete handling in EntityGenerator --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 599e35b3a..db353c3c1 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -791,7 +791,7 @@ public function () } if (isset($joinColumn['onDelete'])) { - $joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false'); + $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"'); } if (isset($joinColumn['columnDefinition'])) { From d46352da0106278ce3b271bca907a381254c42d8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 15 Oct 2011 17:58:00 +0200 Subject: [PATCH 52/82] Fixed tests + added dedicated tests for proxy loading and identifiers --- .../ORM/Functional/LifecycleCallbackTest.php | 3 --- .../ORM/Functional/ReferenceProxyTest.php | 24 +++++++++++++++++++ .../ORM/Proxy/ProxyClassGeneratorTest.php | 5 ++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index 35eb5443c..2d71541d2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -78,9 +78,6 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $reference = $this->_em->getReference('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity', $id); $this->assertFalse($reference->postLoadCallbackInvoked); - $reference->getId(); // doesn't trigger proxy load - $this->assertFalse($reference->postLoadCallbackInvoked); - $reference->getValue(); // trigger proxy load $this->assertTrue($reference->postLoadCallbackInvoked); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index 8ecb389af..b89f3d04e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -147,4 +147,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($entity->wakeUp, "Loading the proxy should call __wakeup()."); } + + public function testDoNotInitializeProxyOnGettingTheIdentifier() + { + $id = $this->createProduct(); + + /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */ + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); + + $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); + $this->assertEquals($id, $entity->getId()); + $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); + } + + public function testInitializeProxyOnGettingSomethingOtherThanTheIdentifier() + { + $id = $this->createProduct(); + + /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */ + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); + + $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); + $this->assertEquals('Doctrine Cookbook', $entity->getName()); + $this->assertTrue($entity->__isInitialized__, "Getting something other than the identifier initializes the proxy."); + } } diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php index 6257dbe70..39bdcfe5a 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php @@ -73,12 +73,13 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $persister = $this->_getMockPersister(); $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); $proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier); + $persister->expects($this->atLeastOnce()) ->method('load') ->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass)) ->will($this->returnValue(new \stdClass())); // fake return of entity instance - $proxy->getId(); $proxy->getDescription(); + $proxy->getProduct(); } public function testReferenceProxyRespectsMethodsParametersTypeHinting() @@ -179,4 +180,4 @@ class SleepClass { return array('id'); } -} \ No newline at end of file +} From 6f3667201c981be7e4341435ddde5850edd3116a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 18:11:14 +0200 Subject: [PATCH 53/82] Add @ignore and @internal to UnitOfWork#computeChangeSet --- lib/Doctrine/ORM/UnitOfWork.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d3cad211c..c5c690e1c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -398,6 +398,8 @@ class UnitOfWork implements PropertyChangedListener * If a PersistentCollection has been de-referenced in a fully MANAGED entity, * then this collection is marked for deletion. * + * @ignore + * @internal Don't call from the outside. * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. */ From 08716d9f72996bab8c71a8cc8bda72d3925e8fb1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 19:14:30 +0200 Subject: [PATCH 54/82] DDC-1383 - Proxy Generation in merge was flawed with inheritance --- lib/Doctrine/ORM/UnitOfWork.php | 14 ++- .../ORM/Functional/Ticket/DDC1383Test.php | 99 +++++++++++++++++++ 2 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index c5c690e1c..2f064e975 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1473,11 +1473,17 @@ class UnitOfWork implements PropertyChangedListener if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) { $prop->setValue($managedCopy, $other); } else { + $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); - $id = $targetClass->getIdentifierValues($other); - $proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $id); - $prop->setValue($managedCopy, $proxy); - $this->registerManaged($proxy, $id, array()); + $relatedId = $targetClass->getIdentifierValues($other); + + if ($targetClass->subClasses) { + $entity = $this->em->find($targetClass->name, $relatedId); + } else { + $proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); + $prop->setValue($managedCopy, $proxy); + $this->registerManaged($proxy, $relatedId, array()); + } } } } else { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php new file mode 100644 index 000000000..6a8cf483a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1383Test.php @@ -0,0 +1,99 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1383AbstractEntity'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1383Entity'), + )); + } catch(\Exception $ignored) {} + } + + public function testFailingCase() + { + $parent = new DDC1383Entity(); + $child = new DDC1383Entity(); + + $child->setReference($parent); + + $this->_em->persist($parent); + $this->_em->persist($child); + + $id = $child->getId(); + + $this->_em->flush(); + $this->_em->clear(); + + // Try merging the parent entity + $child = $this->_em->merge($child); + $parent = $child->getReference(); + + // Parent is not instance of the abstract class + self::assertTrue($parent instanceof DDC1383AbstractEntity, + "Entity class is " . get_class($parent) . ', "DDC1383AbstractEntity" was expected'); + + // Parent is NOT instance of entity + self::assertTrue($parent instanceof DDC1383Entity, + "Entity class is " . get_class($parent) . ', "DDC1383Entity" was expected'); + } +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="integer") + * @DiscriminatorMap({1 = "DDC1383Entity"}) + */ +abstract class DDC1383AbstractEntity +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue + */ + protected $id; + + public function getId() + { + return $this->id; + } + + public function setId($id) + { + $this->id = $id; + } +} + +/** + * @Entity + */ +class DDC1383Entity extends DDC1383AbstractEntity +{ + /** + * @ManyToOne(targetEntity="DDC1383AbstractEntity") + */ + protected $reference; + + public function getReference() + { + return $this->reference; + } + + public function setReference(DDC1383AbstractEntity $reference) + { + $this->reference = $reference; + } +} \ No newline at end of file From fdb9fb1c2b5d35974188eb9344d87e0a41c75ca8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 15 Oct 2011 19:23:36 +0200 Subject: [PATCH 55/82] AssignedGenerator can always tell what field is missing an id --- lib/Doctrine/ORM/Id/AssignedGenerator.php | 4 ++-- lib/Doctrine/ORM/ORMException.php | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/Id/AssignedGenerator.php b/lib/Doctrine/ORM/Id/AssignedGenerator.php index 0143a157f..90a35fa12 100644 --- a/lib/Doctrine/ORM/Id/AssignedGenerator.php +++ b/lib/Doctrine/ORM/Id/AssignedGenerator.php @@ -78,10 +78,10 @@ class AssignedGenerator extends AbstractIdGenerator $identifier[$idField] = $value; } } else { - throw ORMException::entityMissingAssignedId($entity); + throw ORMException::entityMissingAssignedIdForField($entity, $idField); } } return $identifier; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index b28c8d32d..c156893c5 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -46,14 +46,6 @@ class ORMException extends Exception ); } - public static function entityMissingAssignedId($entity) - { - return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " . - "The identifier generation strategy for this entity requires the ID field to be populated before ". - "EntityManager#persist() is called. If you want automatically generated identifiers instead " . - "you need to adjust the metadata mapping accordingly." - ); - } public static function entityMissingAssignedIdForField($entity, $field) { return new self("Entity of type " . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " . From cab154b8737ed843e3c425501dd111c5185e8bcf Mon Sep 17 00:00:00 2001 From: lenar Date: Wed, 31 Aug 2011 10:28:02 +0300 Subject: [PATCH 56/82] identifier referencing foreign entity can be defined in parent class too --- lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 4b43e3070..248e4a2f9 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -308,6 +308,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface if ($parent && $parent->isInheritanceTypeSingleTable()) { $class->setPrimaryTable($parent->table); } + + if ($parent && $parent->containsForeignIdentifier) { + $class->containsForeignIdentifier = true; + } $class->setParentClasses($visited); From 3dc30dee1159bbd9598da6c888bbcee6aaa2f132 Mon Sep 17 00:00:00 2001 From: lenar Date: Thu, 1 Sep 2011 11:29:12 +0300 Subject: [PATCH 57/82] use the correct targetEntity --- lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index a0382a04d..db9f731d2 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -823,7 +823,7 @@ class BasicEntityPersister if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; + $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; @@ -847,7 +847,7 @@ class BasicEntityPersister if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; + $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; @@ -1355,7 +1355,7 @@ class BasicEntityPersister if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->_em->getClassMetadata($assoc['targetEntity'])->identifier[0]]; + $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$tableAlias . "." . $targetKeyColumn] = $value; From a82bffbfc99101319883423c4bdfbf88fd7cb92a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 20:31:56 +0200 Subject: [PATCH 58/82] Make SchemaValidator catch errors such as very invalid schema using only part of the primary key for join columns --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index 8acaa0b54..a7f8e3a1c 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -152,6 +152,17 @@ class SchemaValidator "has to be a primary key column."; } } + + if (count($targetClass->identifier) != count($assoc['joinTable']['inverseJoinColumns'])) { + $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . + "have to match to ALL identifier columns of the target entity '". $targetClass->name . "'"; + } + + if (count($class->identifier) != count($assoc['joinTable']['joinColumns'])) { + $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . + "have to match to ALL identifier columns of the source entity '". $class->name . "'"; + } + } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { foreach ($assoc['joinColumns'] AS $joinColumn) { $targetClass = $cmf->getMetadataFor($assoc['targetEntity']); @@ -167,6 +178,11 @@ class SchemaValidator "has to be a primary key column."; } } + + if (count($class->identifier) != count($assoc['joinColumns'])) { + $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . + "have to match to ALL identifier columns of the source entity '". $class->name . "'"; + } } } From 7b71b3284ded7e2b35fc5e8084b20f9f2f424542 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 20:41:07 +0200 Subject: [PATCH 59/82] Fix failing test due to EntityGenerator assuming beginning with 2.2 the AnnotationReader is always used. There is still the simple reader though. --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 3 --- .../Tools/Export/AbstractClassMetadataExporterTest.php | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 22fa6d25f..62efbac85 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -304,9 +304,6 @@ public function () */ public function setAnnotationPrefix($prefix) { - if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) { - return; - } $this->_annotationsPrefix = $prefix; } diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index c9bfdccf0..2571a1b98 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -98,6 +98,8 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest public function testExportDirectoryAndFilesAreCreated() { + $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType()); + $type = $this->_getType(); $metadataDriver = $this->_createMetadataDriver($type, __DIR__ . '/' . $type); $em = $this->_createEntityManager($metadataDriver); @@ -113,6 +115,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest $exporter = $cme->getExporter($type, __DIR__ . '/export/' . $type); if ($type === 'annotation') { $entityGenerator = new EntityGenerator(); + $entityGenerator->setAnnotationPrefix(""); $exporter->setEntityGenerator($entityGenerator); } $this->_extension = $exporter->getExtension(); @@ -139,6 +142,8 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest $cmf = $this->_createClassMetadataFactory($em, $type); $metadata = $cmf->getAllMetadata(); + $this->assertEquals(1, count($metadata)); + $class = current($metadata); $this->assertEquals('Doctrine\Tests\ORM\Tools\Export\ExportedUser', $class->name); @@ -322,8 +327,7 @@ abstract class AbstractClassMetadataExporterTest extends \Doctrine\Tests\OrmTest public function __destruct() { - $type = $this->_getType(); - $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType()); +# $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType()); } protected function _deleteDirectory($path) From 5c78ecaca1d5afe064b6774925528bf228c30e52 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 20:44:25 +0200 Subject: [PATCH 60/82] Fix tests in EntityGenerator due to Annotation prefixes --- tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index fce7d4c20..f2b298159 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -21,6 +21,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->_tmpDir = \sys_get_temp_dir(); \mkdir($this->_tmpDir . \DIRECTORY_SEPARATOR . $this->_namespace); $this->_generator = new EntityGenerator(); + $this->_generator->setAnnotationPrefix(""); $this->_generator->setGenerateAnnotations(true); $this->_generator->setGenerateStubMethods(true); $this->_generator->setRegenerateEntityIfExists(false); @@ -179,14 +180,15 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testLoadPrefixedMetadata() { - $this->_generator->setAnnotationPrefix('orm:'); + $this->_generator->setAnnotationPrefix('ORM\\'); $metadata = $this->generateBookEntityFixture(); + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, array()); $book = $this->newInstance($metadata); $cm = new \Doctrine\ORM\Mapping\ClassMetadata($metadata->name); - $driver = $this->createAnnotationDriver(array(), 'orm'); $driver->loadMetadataForClass($cm->name, $cm); $this->assertEquals($cm->columnNames, $metadata->columnNames); From 4474d305cb89f2b12e7df2cf310065732fc8e50e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 15 Oct 2011 21:47:16 +0200 Subject: [PATCH 61/82] DDC-1210 - Optimize UnitOfWork collection handling internally. --- lib/Doctrine/ORM/UnitOfWork.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index bd48373a4..6851d25e4 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -490,8 +490,9 @@ class UnitOfWork implements PropertyChangedListener } } else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) { // A PersistentCollection was de-referenced, so delete it. - if ( ! in_array($orgValue, $this->collectionDeletions, true)) { - $this->collectionDeletions[] = $orgValue; + $coid = spl_object_hash($orgValue); + if ( ! isset($this->collectionDeletions[$coid]) ) { + $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. } } @@ -569,10 +570,11 @@ class UnitOfWork implements PropertyChangedListener private function computeAssociationChanges($assoc, $value) { if ($value instanceof PersistentCollection && $value->isDirty()) { + $coid = spl_object_hash($value); if ($assoc['isOwningSide']) { - $this->collectionUpdates[] = $value; + $this->collectionUpdates[$coid] = $value; } - $this->visitedCollections[] = $value; + $this->visitedCollections[$coid] = $value; } // Look through the entities, and in any of their associations, for transient (new) @@ -1889,12 +1891,12 @@ class UnitOfWork implements PropertyChangedListener { //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? - $this->collectionDeletions[] = $coll; + $this->collectionDeletions[spl_object_hash($coll)] = $coll; } public function isCollectionScheduledForDeletion(PersistentCollection $coll) { - return in_array($coll, $this->collectionsDeletions, true); + return isset( $this->collectionsDeletions[spl_object_hash($coll)] ); } /** From 33bcf7ad6f67b8641983f51901d0e74cfde8446c Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 16 Oct 2011 01:42:36 -0200 Subject: [PATCH 62/82] Added coverage to DDC-1161. --- .../Tests/ORM/Query/SelectSqlGenerationTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 8e1585819..53002a8fc 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1239,6 +1239,18 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase array(Query::HINT_FORCE_PARTIAL_LOAD => true) ); } + + /** + * @group DDC-1161 + */ + public function testSelfReferenceWithOneToOneDoesNotDuplicateAlias() + { + $this->assertSqlGeneration( + 'SELECT p, pp FROM Doctrine\Tests\Models\Company\CompanyPerson p JOIN p.spouse pp', + "SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c1_.car_id AS car_id3, c2_.salary AS salary4, c2_.department AS department5, c2_.startDate AS startDate6, c3_.id AS id7, c3_.name AS name8, c4_.title AS title9, c4_.car_id AS car_id10, c5_.salary AS salary11, c5_.department AS department12, c5_.startDate AS startDate13, c0_.discr AS discr14, c0_.spouse_id AS spouse_id15, c3_.discr AS discr16, c3_.spouse_id AS spouse_id17 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id INNER JOIN company_persons c3_ ON c0_.spouse_id = c3_.id LEFT JOIN company_managers c4_ ON c3_.id = c4_.id LEFT JOIN company_employees c5_ ON c3_.id = c5_.id", + array(Query::HINT_FORCE_PARTIAL_LOAD => false) + ); + } } From eeba947ea7f0f5f18672c368398f105a93e58545 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 16 Oct 2011 02:10:59 -0200 Subject: [PATCH 63/82] Code optimizations. Fixed unused argument in OrmTestCase as referred in DDC-766. --- lib/Doctrine/ORM/Query/Parser.php | 27 ++++++++++++--------------- tests/Doctrine/Tests/OrmTestCase.php | 26 +++++++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 08e036c1a..9fc30bb8c 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1328,18 +1328,18 @@ class Parser // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression $glimpse = $this->_lexer->glimpse(); - if ($glimpse['type'] != Lexer::T_DOT) { - $token = $this->_lexer->lookahead; - $identVariable = $this->IdentificationVariable(); + if ($glimpse['type'] == Lexer::T_DOT) { + return $this->SingleValuedPathExpression(); + } + + $token = $this->_lexer->lookahead; + $identVariable = $this->IdentificationVariable(); - if (!isset($this->_queryComponents[$identVariable])) { - $this->semanticalError('Cannot group by undefined identification variable.'); - } - - return $identVariable; + if (!isset($this->_queryComponents[$identVariable])) { + $this->semanticalError('Cannot group by undefined identification variable.'); } - return $this->SingleValuedPathExpression(); + return $identVariable; } /** @@ -1354,12 +1354,9 @@ class Parser // We need to check if we are in a ResultVariable or StateFieldPathExpression $glimpse = $this->_lexer->glimpse(); - if ($glimpse['type'] != Lexer::T_DOT) { - $token = $this->_lexer->lookahead; - $expr = $this->ResultVariable(); - } else { - $expr = $this->SingleValuedPathExpression(); - } + $expr = ($glimpse['type'] != Lexer::T_DOT) + ? $this->ResultVariable() + : $this->SingleValuedPathExpression(); $item = new AST\OrderByItem($expr); diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 678478633..fa9938d09 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -11,6 +11,7 @@ abstract class OrmTestCase extends DoctrineTestCase { /** The metadata cache that is shared between all ORM tests (except functional tests). */ private static $_metadataCacheImpl = null; + /** The query cache that is shared between all ORM tests (except functional tests). */ private static $_queryCacheImpl = null; @@ -66,30 +67,31 @@ abstract class OrmTestCase extends DoctrineTestCase */ protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true) { + $metadataCache = $withSharedMetadata + ? self::getSharedMetadataCacheImpl() + : new \Doctrine\Common\Cache\ArrayCache; + $config = new \Doctrine\ORM\Configuration(); - if($withSharedMetadata) { - $config->setMetadataCacheImpl(self::getSharedMetadataCacheImpl()); - } else { - $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); - } - + + $config->setMetadataCacheImpl($metadataCache); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver()); - $config->setQueryCacheImpl(self::getSharedQueryCacheImpl()); $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); - $eventManager = new \Doctrine\Common\EventManager(); + if ($conn === null) { $conn = array( - 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock', + 'driverClass' => 'Doctrine\Tests\Mocks\DriverMock', 'wrapperClass' => 'Doctrine\Tests\Mocks\ConnectionMock', - 'user' => 'john', - 'password' => 'wayne' + 'user' => 'john', + 'password' => 'wayne' ); } + if (is_array($conn)) { $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager); } + return \Doctrine\Tests\Mocks\EntityManagerMock::create($conn, $config, $eventManager); } @@ -98,6 +100,7 @@ abstract class OrmTestCase extends DoctrineTestCase if (self::$_metadataCacheImpl === null) { self::$_metadataCacheImpl = new \Doctrine\Common\Cache\ArrayCache; } + return self::$_metadataCacheImpl; } @@ -106,6 +109,7 @@ abstract class OrmTestCase extends DoctrineTestCase if (self::$_queryCacheImpl === null) { self::$_queryCacheImpl = new \Doctrine\Common\Cache\ArrayCache; } + return self::$_queryCacheImpl; } } From 0252d55c678dff09050de0e7a4f44e4aeb5cfd97 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 Oct 2011 11:08:11 +0200 Subject: [PATCH 64/82] DDC-1358 - Fix bug where multiple NULL root entity combined with scalar results will break the object and array hydrator. This case likeli only occurs when doing native queries. A guard clause that prevents hydration from breaking when RIGHT JOIN queries with null root entities appear has been added aswell. --- .../Internal/Hydration/AbstractHydrator.php | 5 + .../ORM/Internal/Hydration/ArrayHydrator.php | 16 ++ .../ORM/Internal/Hydration/ObjectHydrator.php | 18 ++ .../Tests/ORM/Hydration/ArrayHydratorTest.php | 54 ++++++ .../ORM/Hydration/ObjectHydratorTest.php | 161 ++++++++++++++++++ 5 files changed, 254 insertions(+) diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 181854e36..5899a69ca 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -164,6 +164,11 @@ abstract class AbstractHydrator * field names during this procedure as well as any necessary conversions on * the values applied. * + * @param array $data SQL Result Row + * @param array &$cache Cache for column to field result information + * @param array &$id Dql-Alias => ID-Hash + * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value? + * * @return array An array with all the fields (name => value) of the data row, * grouped by their component alias. */ diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 92eb45c5c..4b1c21c6f 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -92,6 +92,11 @@ class ArrayHydrator extends AbstractHydrator $parent = $this->_rsm->parentAliasMap[$dqlAlias]; $path = $parent . '.' . $dqlAlias; + // missing parent data, skipping as RIGHT JOIN hydration is not supported. + if ( ! isset($nonemptyComponents[$parent]) ) { + continue; + } + // Get a reference to the right element in the result tree. // This element will get the associated element attached. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { @@ -154,6 +159,17 @@ class ArrayHydrator extends AbstractHydrator // It's a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root + + // if this row has a NULL value for the root result id then make it a null result. + if ( ! isset($nonemptyComponents[$dqlAlias]) ) { + if ($this->_rsm->isMixed) { + $result[] = array(0 => null); + } else { + $result[] = null; + } + ++$this->_resultCounter; + continue; + } // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index bb11a7431..1287a138b 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -302,6 +302,12 @@ class ObjectHydrator extends AbstractHydrator // seen for this parent-child relationship $path = $parentAlias . '.' . $dqlAlias; + // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs + if (!isset($nonemptyComponents[$parentAlias])) { + // TODO: Add special case code where we hydrate the right join objects into identity map at least + continue; + } + // Get a reference to the parent object to which the joined element belongs. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { $first = reset($this->_resultPointers); @@ -408,6 +414,18 @@ class ObjectHydrator extends AbstractHydrator // PATH C: Its a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root alias + // if this row has a NULL value for the root result id then make it a null result. + if ( ! isset($nonemptyComponents[$dqlAlias]) ) { + if ($this->_rsm->isMixed) { + $result[] = array(0 => null); + } else { + $result[] = null; + } + ++$this->_resultCounter; + continue; + } + + // check for existing result from the iterations before if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { diff --git a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php index 4fb5b92ac..a318f78af 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ArrayHydratorTest.php @@ -763,4 +763,58 @@ class ArrayHydratorTest extends HydrationTestCase $this->assertEquals(1, count($result)); } + + /** + * @group DDC-1358 + */ + public function testMissingIdForRootEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => null, + 'u__status' => null, + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + ), + array( + 'u__id' => null, + 'u__status' => null, + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ArrayHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(4, count($result), "Should hydrate four results."); + + $this->assertEquals('ROMANB', $result[0]['nameUpper']); + $this->assertEquals('ROMANB', $result[1]['nameUpper']); + $this->assertEquals('JWAGE', $result[2]['nameUpper']); + $this->assertEquals('JWAGE', $result[3]['nameUpper']); + + $this->assertEquals(array('id' => 1, 'status' => 'developer'), $result[0][0]); + $this->assertNull($result[1][0]); + $this->assertEquals(array('id' => 2, 'status' => 'developer'), $result[2][0]); + $this->assertNull($result[3][0]); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index f2673ac70..581a3504a 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -1008,4 +1008,165 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals(4, count($result[1]->groups)); $this->assertEquals(2, count($result[1]->phonenumbers)); } + + /** + * @group DDC-1358 + */ + public function testMissingIdForRootEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => null, + 'u__status' => null, + 'sclr0' => 'ROMANB', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + ), + array( + 'u__id' => null, + 'u__status' => null, + 'sclr0' => 'JWAGE', + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(4, count($result), "Should hydrate four results."); + + $this->assertEquals('ROMANB', $result[0]['nameUpper']); + $this->assertEquals('ROMANB', $result[1]['nameUpper']); + $this->assertEquals('JWAGE', $result[2]['nameUpper']); + $this->assertEquals('JWAGE', $result[3]['nameUpper']); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); + $this->assertNull($result[1][0]); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[2][0]); + $this->assertNull($result[3][0]); + } + + /** + * @group DDC-1358 + * @return void + */ + public function testMissingIdForCollectionValuedChildEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsPhonenumber', + 'p', + 'u', + 'phonenumbers' + ); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + 'p__phonenumber' => '42', + ), + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + 'p__phonenumber' => null + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + 'p__phonenumber' => '91' + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'JWAGE', + 'p__phonenumber' => null + ) + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + $this->assertEquals(1, $result[0][0]->phonenumbers->count()); + $this->assertEquals(1, $result[1][0]->phonenumbers->count()); + } + + /** + * @group DDC-1358 + */ + public function testMissingIdForSingleValuedChildEntity() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + $rsm->addJoinedEntityResult( + 'Doctrine\Tests\Models\CMS\CmsAddress', + 'a', + 'u', + 'address' + ); + $rsm->addFieldResult('u', 'u__id', 'id'); + $rsm->addFieldResult('u', 'u__status', 'status'); + $rsm->addScalarResult('sclr0', 'nameUpper'); + $rsm->addFieldResult('a', 'a__id', 'id'); + $rsm->addFieldResult('a', 'a__city', 'city'); + $rsm->addMetaResult('a', 'user_id', 'user_id'); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'sclr0' => 'ROMANB', + 'a__id' => 1, + 'a__city' => 'Berlin', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'sclr0' => 'BENJAMIN', + 'a__id' => null, + 'a__city' => null, + ), + ); + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm, array(Query::HINT_FORCE_PARTIAL_LOAD => true)); + + $this->assertEquals(2, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][0]->address); + $this->assertNull($result[1][0]->address); + } } From a8e6131e3bc883997b16133e65ee44ca9c59d8db Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sun, 16 Oct 2011 17:00:33 +0200 Subject: [PATCH 65/82] Added the initializeObject method in the EntityManager --- lib/Doctrine/ORM/EntityManager.php | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 04a30dc46..d8deb0589 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -128,7 +128,7 @@ class EntityManager implements ObjectManager $this->metadataFactory = new $metadataFactoryClassName; $this->metadataFactory->setEntityManager($this); $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); - + $this->unitOfWork = new UnitOfWork($this); $this->proxyFactory = new ProxyFactory($this, $config->getProxyDir(), @@ -203,18 +203,18 @@ class EntityManager implements ObjectManager public function transactional(Closure $func) { $this->conn->beginTransaction(); - + try { $return = $func($this); - + $this->flush(); $this->conn->commit(); - + return $return ?: true; } catch (Exception $e) { $this->close(); $this->conn->rollback(); - + throw $e; } } @@ -244,7 +244,7 @@ class EntityManager implements ObjectManager * * The class name must be the fully-qualified class name without a leading backslash * (as it is returned by get_class($obj)) or an aliased class name. - * + * * Examples: * MyProject\Domain\User * sales:PriceRequest @@ -450,7 +450,7 @@ class EntityManager implements ObjectManager * * The entity will be entered into the database at or before transaction * commit or as a result of the flush operation. - * + * * NOTE: The persist operation always considers entities that are not yet known to * this EntityManager as NEW. Do not pass detached entities to the persist operation. * @@ -633,7 +633,7 @@ class EntityManager implements ObjectManager /** * Check if the Entity manager is open or closed. - * + * * @return bool */ public function isOpen() @@ -714,6 +714,18 @@ class EntityManager implements ObjectManager return $this->proxyFactory; } + /** + * Helper method to initialize a lazy loading proxy or persistent collection. + * + * This method is a no-op for other objects + * + * @param object $obj + */ + public function initializeObject($obj) + { + $this->unitOfWork->initializeObject($obj); + } + /** * Factory method to create EntityManager instances. * From 939fbf9c24f45ea01491bf1dd72921994c7f1656 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 16 Oct 2011 22:45:06 +0200 Subject: [PATCH 66/82] DDC-1278 - Clean up event handling of new clear functionality. --- lib/Doctrine/ORM/Event/OnClearEventArgs.php | 28 ++++++++++++++++++++- lib/Doctrine/ORM/UnitOfWork.php | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Event/OnClearEventArgs.php b/lib/Doctrine/ORM/Event/OnClearEventArgs.php index ad89fbc90..60ce4b3eb 100644 --- a/lib/Doctrine/ORM/Event/OnClearEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnClearEventArgs.php @@ -36,12 +36,18 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs */ private $em; + /** + * @var string + */ + private $entityClass; + /** * @param \Doctrine\ORM\EntityManager $em */ - public function __construct($em) + public function __construct($em, $entityClass = null) { $this->em = $em; + $this->entityClass = $entityClass; } /** @@ -51,4 +57,24 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs { return $this->em; } + + /** + * Name of the entity class that is cleared, or empty if all are cleared. + * + * @return string + */ + public function getEntityClass() + { + return $this->entityClass; + } + + /** + * Check if event clears all entities. + * + * @return bool + */ + public function clearsAllEntities() + { + return $this->entityClass === null; + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1b291d717..8f5977e5d 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1879,7 +1879,7 @@ class UnitOfWork implements PropertyChangedListener } if ($this->evm->hasListeners(Events::onClear)) { - $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em)); + $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } From 91bc9c0329aa7d8dfa76f07acb9c9038d2c80986 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 17 Oct 2011 18:54:20 +0200 Subject: [PATCH 67/82] Adjusted test to verify that findBy*(null) is now supported --- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 2 +- .../ORM/Functional/EntityRepositoryTest.php | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 0c7b007d1..3296fffc8 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -19,7 +19,7 @@ class CmsUser */ public $id; /** - * @Column(type="string", length=50) + * @Column(type="string", length=50, nullable=true) */ public $status; /** diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index c5216fb94..7cd37b906 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -38,18 +38,25 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $user2->status = 'dev'; $this->_em->persist($user2); + $user3 = new CmsUser; + $user3->name = 'Benjamin'; + $user3->username = 'beberlei'; + $user3->status = null; + $this->_em->persist($user3); + $this->_em->flush(); - + $user1Id = $user->getId(); - + unset($user); unset($user2); - + unset($user3); + $this->_em->clear(); return $user1Id; } - + public function loadAssociatedFixture() { $address = new CmsAddress(); @@ -189,7 +196,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $users = $repos->findAll(); - $this->assertEquals(2, count($users)); + $this->assertEquals(3, count($users)); } public function testFindByAlias() @@ -202,7 +209,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repos = $this->_em->getRepository('CMS:CmsUser'); $users = $repos->findAll(); - $this->assertEquals(2, count($users)); + $this->assertEquals(3, count($users)); } /** @@ -284,10 +291,11 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testFindMagicCallByNullValue() { $this->loadFixture(); + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); - $this->setExpectedException('Doctrine\ORM\ORMException'); $users = $repos->findByStatus(null); + $this->assertEquals(1, count($users)); } /** @@ -411,7 +419,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $users1 = $repos->findBy(array(), null, 1, 0); $users2 = $repos->findBy(array(), null, 1, 1); - $this->assertEquals(2, count($repos->findBy(array()))); + $this->assertEquals(3, count($repos->findBy(array()))); $this->assertEquals(1, count($users1)); $this->assertEquals(1, count($users2)); $this->assertNotSame($users1[0], $users2[0]); @@ -428,10 +436,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $usersAsc = $repos->findBy(array(), array("username" => "ASC")); $usersDesc = $repos->findBy(array(), array("username" => "DESC")); - $this->assertEquals(2, count($usersAsc), "Pre-condition: only two users in fixture"); - $this->assertEquals(2, count($usersDesc), "Pre-condition: only two users in fixture"); - $this->assertSame($usersAsc[0], $usersDesc[1]); - $this->assertSame($usersAsc[1], $usersDesc[0]); + $this->assertEquals(3, count($usersAsc), "Pre-condition: only three users in fixture"); + $this->assertEquals(3, count($usersDesc), "Pre-condition: only three users in fixture"); + $this->assertSame($usersAsc[0], $usersDesc[2]); + $this->assertSame($usersAsc[2], $usersDesc[0]); } From b8af2415042dd5e3ab8518b8a3ab8775c8dab56a Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 17 Oct 2011 20:53:04 +0200 Subject: [PATCH 68/82] Added a testcase for findBy(.. => null) and renamed 'old' testcase --- .../Tests/ORM/Functional/EntityRepositoryTest.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index 7cd37b906..4f8e11420 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -397,7 +397,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase /** * @group DDC-1087 */ - public function testIsNullCriteria() + public function testIsNullCriteriaDoesNotGenerateAParameter() { $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $users = $repos->findBy(array('status' => null, 'username' => 'romanb')); @@ -407,6 +407,16 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(array('romanb'), $params); } + public function testIsNullCriteria() + { + $this->loadFixture(); + + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + + $users = $repos->findBy(array('status' => null)); + $this->assertEquals(1, count($users)); + } + /** * @group DDC-1094 */ From c3ec6e383c87b6f81dc74bbd9e6799230ece01de Mon Sep 17 00:00:00 2001 From: Sergey Linnik Date: Tue, 18 Oct 2011 01:14:07 +0400 Subject: [PATCH 69/82] Fix isTransient call on uninitialized ClassMetadataFactory --- lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 248e4a2f9..239022b0f 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -494,6 +494,10 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface */ public function isTransient($class) { + if ( ! $this->initialized) { + $this->initialize(); + } + return $this->driver->isTransient($class); } } From 0d57ffbc3be10eacdb77ed23825a23ce576f3b85 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Tue, 18 Oct 2011 15:48:56 +0200 Subject: [PATCH 70/82] Set association-key attribute in xml mapping --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index f9668c78f..01309475b 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -139,6 +139,9 @@ class XmlExporter extends AbstractExporter if (isset($field['columnName'])) { $idXml->addAttribute('column', $field['columnName']); } + if (isset($field['associationKey']) && $field['associationKey']) { + $idXml->addAttribute('association-key', 'true'); + } if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $generatorXml = $idXml->addChild('generator'); $generatorXml->addAttribute('strategy', $idGeneratorType); From ca01065c6ad41eff9b5524522dc3686cf9e8709a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 19 Oct 2011 11:58:59 +0200 Subject: [PATCH 71/82] Bugfix in short identifier shortcut with association ids --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 05a44d992..490c3a119 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -230,10 +230,12 @@ class ProxyFactory */ private function isShortIdentifierGetter($method, $class) { + $identifier = lcfirst(substr($method->getName(), 3)); return ( $method->getNumberOfParameters() == 0 && substr($method->getName(), 0, 3) == "get" && - in_array(lcfirst(substr($method->getName(), 3)), $class->identifier, true) && + in_array($identifier, $class->identifier, true) && + $class->hasField($identifier) && (($method->getEndLine() - $method->getStartLine()) <= 4) ); } From 4a50eb4fa749cfbb59552dfa0abc56323c6930a9 Mon Sep 17 00:00:00 2001 From: armetiz Date: Fri, 21 Oct 2011 11:55:54 +0300 Subject: [PATCH 72/82] Update lib/Doctrine/ORM/Query/Expr.php --- lib/Doctrine/ORM/Query/Expr.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Expr.php b/lib/Doctrine/ORM/Query/Expr.php index 246f92717..6ba6a5d73 100644 --- a/lib/Doctrine/ORM/Query/Expr.php +++ b/lib/Doctrine/ORM/Query/Expr.php @@ -40,10 +40,12 @@ class Expr * * [php] * // (u.type = ?1) AND (u.role = ?2) - * $expr->andX('u.type = ?1', 'u.role = ?2')); + * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2')); * - * @param mixed $x Optional clause. Defaults = null, but requires - * at least one defined when converting to string. + * @param Doctrine\ORM\Query\Expr\Comparison | + * Doctrine\ORM\Query\Expr\Func | + * Doctrine\ORM\Query\Expr\Orx + * $x Optional clause. Defaults = null, but requires at least one defined when converting to string. * @return Expr\Andx */ public function andX($x = null) From baef4f735f403b10a194603380b267cee59a761b Mon Sep 17 00:00:00 2001 From: armetiz Date: Fri, 21 Oct 2011 16:30:06 +0300 Subject: [PATCH 73/82] Update lib/Doctrine/ORM/Query/Expr/Base.php --- lib/Doctrine/ORM/Query/Expr/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/Expr/Base.php b/lib/Doctrine/ORM/Query/Expr/Base.php index abc2c210e..beac2bb9c 100644 --- a/lib/Doctrine/ORM/Query/Expr/Base.php +++ b/lib/Doctrine/ORM/Query/Expr/Base.php @@ -57,7 +57,7 @@ abstract class Base public function add($arg) { - if ( $arg !== null || ($arg instanceof self && $arg->count() > 0)) { + if ( $arg !== null ) { // If we decide to keep Expr\Base instances, we can use this check if ( ! is_string($arg)) { $class = get_class($arg); From f569a2a389264adefbfc9aca03e67a83c131a087 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 22 Oct 2011 13:44:33 +0200 Subject: [PATCH 74/82] DDC-720 - Add support to flush only one entity (within cascade rules) through EntityManager#flush() --- lib/Doctrine/ORM/EntityManager.php | 4 +- lib/Doctrine/ORM/UnitOfWork.php | 53 ++++++- .../ORM/Functional/BasicFunctionalTest.php | 140 ++++++++++++++++++ 3 files changed, 193 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index b951e355e..3dd9047da 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -328,10 +328,10 @@ class EntityManager implements ObjectManager * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. */ - public function flush() + public function flush($entity = null) { $this->errorIfClosed(); - $this->unitOfWork->commit(); + $this->unitOfWork->commit($entity); } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 8f5977e5d..ab5bf25bb 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -255,10 +255,14 @@ class UnitOfWork implements PropertyChangedListener * 5) All entity deletions * */ - public function commit() + public function commit($entity = null) { // Compute changes done since last commit. - $this->computeChangeSets(); + if ($entity === null) { + $this->computeChangeSets(); + } else { + $this->computeSingleEntityChangeSet($entity); + } if ( ! ($this->entityInsertions || $this->entityDeletions || @@ -346,6 +350,51 @@ class UnitOfWork implements PropertyChangedListener $this->scheduledForDirtyCheck = $this->orphanRemovals = array(); } + + /** + * Only flush the given entity according to a rulset that keeps the UoW consistent. + * + * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! + * 2. Read Only entities are skipped. + * 3. Proxies are skipped. + * 4. Only if entity is properly managed. + * + * @param Proxy $entity + * @return type + */ + private function computeSingleEntityChangeSet($entity) + { + if ( ! $this->isInIdentityMap($entity) ) { + throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity)); + } + + $class = $this->em->getClassMetadata(get_class($entity)); + + if ($class->isChangeTrackingDeferredImplicit()) { + $this->persist($entity); + } + + // Compute changes for INSERTed entities first. This must always happen even in this case. + foreach ($this->entityInsertions as $entity) { + $class = $this->em->getClassMetadata(get_class($entity)); + $this->computeChangeSet($class, $entity); + } + + if ( $class->isReadOnly ) { + return; + } + + // Ignore uninitialized proxy objects + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { + return; + } + + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. + $oid = spl_object_hash($entity); + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { + $this->computeChangeSet($class, $entity); + } + } /** * Executes any extra updates that have been scheduled. diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index a2e2d05dd..9eaa088a6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -1030,4 +1030,144 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address)); } + + /** + * @group DDC-720 + */ + public function testFlushSingleManagedEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $user->status = 'administrator'; + $this->_em->flush($user); + $this->_em->clear(); + + $user = $this->_em->find(get_class($user), $user->id); + $this->assertEquals('administrator', $user->status); + } + + /** + * @group DDC-720 + */ + public function testFlushSingleUnmanagedEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->setExpectedException('InvalidArgumentException', 'Entity has to be managed for single computation'); + $this->_em->flush($user); + } + + /** + * @group DDC-720 + */ + public function testFlushSingleAndNewEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $otherUser = new CmsUser; + $otherUser->name = 'Dominik2'; + $otherUser->username = 'domnikl2'; + $otherUser->status = 'developer'; + + $user->status = 'administrator'; + + $this->_em->persist($otherUser); + $this->_em->flush($user); + + $this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager"); + $this->assertTrue($otherUser->id > 0, "other user has an id"); + } + + /** + * @group DDC-720 + */ + public function testFlushAndCascadePersist() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $address = new CmsAddress(); + $address->city = "Springfield"; + $address->zip = "12354"; + $address->country = "Germany"; + $address->street = "Foo Street"; + $address->user = $user; + $user->address = $address; + + $this->_em->flush($user); + + $this->assertTrue($this->_em->contains($address), "Other user is contained in EntityManager"); + $this->assertTrue($address->id > 0, "other user has an id"); + } + + /** + * @group DDC-720 + */ + public function testFlushSingleAndNoCascade() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $article1 = new CmsArticle(); + $article1->topic = 'Foo'; + $article1->text = 'Foo Text'; + $article1->author = $user; + $user->articles[] = $article1; + + $this->setExpectedException('InvalidArgumentException', "A new entity was found through the relationship 'Doctrine\Tests\Models\CMS\CmsUser#articles'"); + $this->_em->flush($user); + } + + /** + * @group DDC-720 + */ + public function testProxyIsIgnored() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->getReference(get_class($user), $user->id); + + $otherUser = new CmsUser; + $otherUser->name = 'Dominik2'; + $otherUser->username = 'domnikl2'; + $otherUser->status = 'developer'; + + $this->_em->persist($otherUser); + $this->_em->flush($user); + + $this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager"); + $this->assertTrue($otherUser->id > 0, "other user has an id"); + } } From b910a487c54b4c3dbe8603fc0857184d5083b13f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 22 Oct 2011 14:31:23 +0200 Subject: [PATCH 75/82] DDC-720 - Wait, we should really test it only changes the passed entity. --- .../ORM/Functional/BasicFunctionalTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 9eaa088a6..feef41c76 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -1170,4 +1170,33 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager"); $this->assertTrue($otherUser->id > 0, "other user has an id"); } + + /** + * @group DDC-720 + */ + public function testFlushSingleSaveOnlySingle() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + $this->_em->persist($user); + + $user2 = new CmsUser; + $user2->name = 'Dominik'; + $user2->username = 'domnikl2'; + $user2->status = 'developer'; + $this->_em->persist($user2); + + $this->_em->flush(); + + $user->status = 'admin'; + $user2->status = 'admin'; + + $this->_em->flush($user); + $this->_em->clear(); + + $user2 = $this->_em->find(get_class($user2), $user2->id); + $this->assertEquals('developer', $user2->status); + } } From 46a3fecb4fb3be0098a3d8efeee7dc080b6dc353 Mon Sep 17 00:00:00 2001 From: Daniel Freudenberger Date: Sat, 22 Oct 2011 18:38:51 +0200 Subject: [PATCH 76/82] added the postFlush event --- lib/Doctrine/ORM/Event/PostFlushEventArgs.php | 57 +++++++++++ lib/Doctrine/ORM/Events.php | 11 +++ lib/Doctrine/ORM/UnitOfWork.php | 5 + .../ORM/Functional/PostFlushEventTest.php | 95 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 lib/Doctrine/ORM/Event/PostFlushEventArgs.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php diff --git a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php new file mode 100644 index 000000000..92e88ae21 --- /dev/null +++ b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php @@ -0,0 +1,57 @@ +. +*/ + +namespace Doctrine\ORM\Event; +use Doctrine\ORM\EntityManager; +use Doctrine\Common\EventArgs; + +/** + * Provides event arguments for the postFlush event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 2.0 + * @version $Revision$ + * @author Daniel Freudenberger + */ +class PostFlushEventArgs extends EventArgs +{ + /** + * @var EntityManager + */ + private $em; + + /** + * @param EntityManager $em + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + /** + * @return EntityManager + */ + public function getEntityManager() + { + return $this->em; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index 8344b07c1..4204f899c 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -120,6 +120,17 @@ final class Events */ const onFlush = 'onFlush'; + /** + * The postFlush event occurs when the EntityManager#flush() operation is invoked and + * after all actual database operations are executed successfully. The event is only raised if there is + * actually something to do for the underlying UnitOfWork. If nothing needs to be done, + * the onFlush event is not raised. The event won't be raised if an error occurs during the + * flush operation. + * + * @var string + */ + const postFlush = 'postFlush'; + /** * The onClear event occurs when the EntityManager#clear() operation is invoked, * after all references to entities have been removed from the unit of work. diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 8f5977e5d..e9cddd035 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -329,6 +329,11 @@ class UnitOfWork implements PropertyChangedListener throw $e; } + // Raise postFlush + if ($this->evm->hasListeners(Events::postFlush)) { + $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); + } + // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); diff --git a/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php new file mode 100644 index 000000000..6981d4784 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php @@ -0,0 +1,95 @@ + + */ +class PostFlushEventTest extends \Doctrine\Tests\OrmFunctionalTestCase +{ + /** + * @var PostFlushListener + */ + private $listener; + + protected function setUp() + { + parent::setUp(); + $this->useModelSet('cms'); + $this->listener = new PostFlushListener(); + $evm = $this->_em->getEventManager(); + $evm->addEventListener(Events::postFlush, $this->listener); + } + + public function testListenerShouldBeNotified() + { + $this->_em->persist($this->createNewValidUser()); + $this->_em->flush(); + $this->assertTrue($this->listener->wasNotified); + } + + public function testListenerShouldNotBeNotifiedWhenFlushThrowsException() + { + $user = new CmsUser(); + $user->username = 'dfreudenberger'; + $this->_em->persist($user); + $exceptionRaised = false; + + try { + $this->_em->flush(); + } catch (\Exception $ex) { + $exceptionRaised = true; + } + + $this->assertTrue($exceptionRaised); + $this->assertFalse($this->listener->wasNotified); + } + + public function testListenerShouldReceiveEntityManagerThroughArgs() + { + $this->_em->persist($this->createNewValidUser()); + $this->_em->flush(); + $receivedEm = $this->listener->receivedArgs->getEntityManager(); + $this->assertSame($this->_em, $receivedEm); + } + + /** + * @return CmsUser + */ + private function createNewValidUser() + { + $user = new CmsUser(); + $user->username = 'dfreudenberger'; + $user->name = 'Daniel Freudenberger'; + return $user; + } +} + +class PostFlushListener +{ + /** + * @var bool + */ + public $wasNotified = false; + + /** + * @var PostFlushEventArgs + */ + public $receivedArgs; + + /** + * @param PostFlushEventArgs $args + */ + public function postFlush(PostFlushEventArgs $args) + { + $this->wasNotified = true; + $this->receivedArgs = $args; + } +} + + From 5e28273548023283290a81dd0502cdd4a384e943 Mon Sep 17 00:00:00 2001 From: Daniel Freudenberger Date: Sat, 22 Oct 2011 18:57:48 +0200 Subject: [PATCH 77/82] fixed typo in docblock --- lib/Doctrine/ORM/Events.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index 4204f899c..e8c350aa6 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -124,7 +124,7 @@ final class Events * The postFlush event occurs when the EntityManager#flush() operation is invoked and * after all actual database operations are executed successfully. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, - * the onFlush event is not raised. The event won't be raised if an error occurs during the + * the postFlush event is not raised. The event won't be raised if an error occurs during the * flush operation. * * @var string From fa8000fa5c1d8a6d689e0df8b9ab4b8953a048e6 Mon Sep 17 00:00:00 2001 From: Daniel Freudenberger Date: Sun, 23 Oct 2011 01:27:09 +0200 Subject: [PATCH 78/82] dispatch the event after the snapshot was taken --- lib/Doctrine/ORM/UnitOfWork.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index e9cddd035..7faaf581e 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -328,17 +328,17 @@ class UnitOfWork implements PropertyChangedListener $conn->rollback(); throw $e; } - - // Raise postFlush - if ($this->evm->hasListeners(Events::postFlush)) { - $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); - } // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); } + // Raise postFlush + if ($this->evm->hasListeners(Events::postFlush)) { + $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); + } + // Clear up $this->entityInsertions = $this->entityUpdates = From 5d3298e706b1457ca8be02469b00ef219afe84e6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Oct 2011 10:05:46 +0200 Subject: [PATCH 79/82] DDC-720 - Correct mentioned issues by @asm89 --- lib/Doctrine/ORM/EntityManager.php | 4 ++++ lib/Doctrine/ORM/UnitOfWork.php | 33 +++++++++++++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 3dd9047da..b3583de8e 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -325,6 +325,10 @@ class EntityManager implements ObjectManager * This effectively synchronizes the in-memory state of managed objects with the * database. * + * If an entity is explicitly passed to this method only this entity and + * the cascade-persist semantics + scheduled inserts/removals are synchronized. + * + * @param object $entity * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. */ diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ab5bf25bb..8c7273856 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -253,7 +253,9 @@ class UnitOfWork implements PropertyChangedListener * 3) All collection deletions * 4) All collection updates * 5) All entity deletions - * + * + * @param object $entity + * @return void */ public function commit($entity = null) { @@ -352,15 +354,28 @@ class UnitOfWork implements PropertyChangedListener } /** - * Only flush the given entity according to a rulset that keeps the UoW consistent. + * Compute the changesets of all entities scheduled for insertion + * + * @return void + */ + private function computeScheduleInsertsChangeSets() + { + foreach ($this->entityInsertions as $entity) { + $class = $this->em->getClassMetadata(get_class($entity)); + $this->computeChangeSet($class, $entity); + } + } + + /** + * Only flush the given entity according to a ruleset that keeps the UoW consistent. * * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! * 2. Read Only entities are skipped. * 3. Proxies are skipped. * 4. Only if entity is properly managed. * - * @param Proxy $entity - * @return type + * @param object $entity + * @return void */ private function computeSingleEntityChangeSet($entity) { @@ -375,10 +390,7 @@ class UnitOfWork implements PropertyChangedListener } // Compute changes for INSERTed entities first. This must always happen even in this case. - foreach ($this->entityInsertions as $entity) { - $class = $this->em->getClassMetadata(get_class($entity)); - $this->computeChangeSet($class, $entity); - } + $this->computeScheduleInsertsChangeSets(); if ( $class->isReadOnly ) { return; @@ -575,10 +587,7 @@ class UnitOfWork implements PropertyChangedListener public function computeChangeSets() { // Compute changes for INSERTed entities first. This must always happen. - foreach ($this->entityInsertions as $entity) { - $class = $this->em->getClassMetadata(get_class($entity)); - $this->computeChangeSet($class, $entity); - } + $this->computeScheduleInsertsChangeSets(); // Compute changes for other MANAGED entities. Change tracking policies take effect here. foreach ($this->identityMap as $className => $entities) { From f1df4ffca482f0269673b4a535781752ac0adcc8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Oct 2011 21:36:29 +0200 Subject: [PATCH 80/82] Fix testsuite --- tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php index 6981d4784..819887196 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PostFlushEventTest.php @@ -19,8 +19,8 @@ class PostFlushEventTest extends \Doctrine\Tests\OrmFunctionalTestCase protected function setUp() { - parent::setUp(); $this->useModelSet('cms'); + parent::setUp(); $this->listener = new PostFlushListener(); $evm = $this->_em->getEventManager(); $evm->addEventListener(Events::postFlush, $this->listener); From 80c96909268b3a28bc8e227ffdcf43acaa3dadef Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 23 Oct 2011 22:06:03 +0200 Subject: [PATCH 81/82] Fix mysql testsuite --- .../Tests/ORM/Functional/DatabaseDriverTest.php | 2 +- .../SchemaTool/MySqlSchemaToolTest.php | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php b/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php index 37ce0b6ea..999d52db8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DatabaseDriverTest.php @@ -98,7 +98,7 @@ class DatabaseDriverTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertArrayHasKey('CmsUsers', $metadatas, 'CmsUsers entity was not detected.'); $this->assertArrayHasKey('CmsGroups', $metadatas, 'CmsGroups entity was not detected.'); - $this->assertEquals(1, count($metadatas['CmsUsers']->associationMappings)); + $this->assertEquals(2, count($metadatas['CmsUsers']->associationMappings)); $this->assertArrayHasKey('group', $metadatas['CmsUsers']->associationMappings); $this->assertEquals(1, count($metadatas['CmsGroups']->associationMappings)); $this->assertArrayHasKey('user', $metadatas['CmsGroups']->associationMappings); diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php index 50e8a95c9..be671bf3d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php @@ -27,15 +27,16 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $tool = new SchemaTool($this->_em); $sql = $tool->getCreateSchemaSql($classes); $this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); - $this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]); + $this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) ENGINE = InnoDB", $sql[1]); $this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) ENGINE = InnoDB", $sql[2]); $this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) ENGINE = InnoDB", $sql[3]); - $this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[4]); - $this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[5]); - $this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id)", $sql[6]); - $this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[7]); - - $this->assertEquals(8, count($sql)); + $this->assertEquals("ALTER TABLE cms_addresses ADD CONSTRAINT FK_ACAC157BA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[4]); + $this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)", $sql[5]); + $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[6]); + $this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id)", $sql[7]); + $this->assertEquals("ALTER TABLE cms_phonenumbers ADD CONSTRAINT FK_F21F790FA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[8]); + + $this->assertEquals(9, count($sql)); } public function testGetCreateSchemaSql2() @@ -63,4 +64,4 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(1, count($sql)); $this->assertEquals("CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); } -} \ No newline at end of file +} From cdb452b27b5a341a372dff833ae2d40a4e03d513 Mon Sep 17 00:00:00 2001 From: Jaik Dean Date: Mon, 24 Oct 2011 10:01:27 +0100 Subject: [PATCH 82/82] Fixed typos of 'discriminator' --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 2 +- lib/Doctrine/ORM/Mapping/MappingException.php | 2 +- .../Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 17255cb23..25a3350d2 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -318,7 +318,7 @@ class ClassMetadataInfo implements ClassMetadata public $discriminatorMap = array(); /** - * READ-ONLY: The definition of the descriminator column used in JOINED and SINGLE_TABLE + * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE * inheritance mappings. * * @var array diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 97fbf94f6..fcdd5c575 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -288,7 +288,7 @@ class MappingException extends \Doctrine\ORM\ORMException public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) { return new self( - "Entity '" . $className . "' has to be part of the descriminator map of '" . $rootClassName . "' " . + "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . "to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " . "to avoid this exception from occuring." ); diff --git a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php index 93504ec53..1d8d6c118 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/BasicInheritanceMappingTest.php @@ -114,7 +114,7 @@ class BasicInheritanceMappingTest extends \Doctrine\Tests\OrmTestCase */ public function testUnmappedEntityInHierachy() { - $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' has to be part of the descriminator map of 'Doctrine\Tests\ORM\Mapping\HierachyBase' to be properly mapped in the inheritance hierachy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' an abstract class to avoid this exception from occuring."); + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException', "Entity 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' has to be part of the discriminator map of 'Doctrine\Tests\ORM\Mapping\HierachyBase' to be properly mapped in the inheritance hierachy. Alternatively you can make 'Doctrine\Tests\ORM\Mapping\HierachyBEntity' an abstract class to avoid this exception from occuring."); $class = $this->_factory->getMetadataFor(__NAMESPACE__ . '\\HierachyE'); }