From 97a6caf059243643835e4ccd4df311ebbdea67a3 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 11 Oct 2011 13:22:26 +0200 Subject: [PATCH 01/28] 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 c3ec6e383c87b6f81dc74bbd9e6799230ece01de Mon Sep 17 00:00:00 2001 From: Sergey Linnik Date: Tue, 18 Oct 2011 01:14:07 +0400 Subject: [PATCH 02/28] 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 03/28] 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 04/28] 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 05/28] 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 06/28] 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 07/28] 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 08/28] 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 09/28] 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 10/28] 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 11/28] 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 12/28] 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 13/28] 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 14/28] 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 adc4840ccecad7e35f97e43c740dd30602ce2241 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Oct 2011 23:28:23 +0200 Subject: [PATCH 15/28] DDC-217 - Add support for QueryCacheProfiles and remove the old result caching code from ORM. Deprecate a bunch of methods in favor of using the AbstractQuery#getQueryCacheProfile method. --- lib/Doctrine/ORM/AbstractQuery.php | 145 ++++++------------ lib/Doctrine/ORM/Configuration.php | 21 --- lib/Doctrine/ORM/NativeQuery.php | 16 +- lib/Doctrine/ORM/Query.php | 3 + .../ORM/Query/Exec/AbstractSqlExecutor.php | 19 ++- .../Query/Exec/MultiTableDeleteExecutor.php | 9 +- .../Query/Exec/MultiTableUpdateExecutor.php | 6 +- .../ORM/Query/Exec/SingleSelectExecutor.php | 8 +- .../Exec/SingleTableDeleteUpdateExecutor.php | 8 +- lib/vendor/doctrine-dbal | 2 +- .../Tests/ORM/Functional/ResultCacheTest.php | 14 +- 11 files changed, 95 insertions(+), 156 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 5d71cf0aa..0f28e71db 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -20,7 +20,8 @@ namespace Doctrine\ORM; use Doctrine\DBAL\Types\Type, - Doctrine\ORM\Query\QueryException; + Doctrine\ORM\Query\QueryException, + Doctrine\DBAL\Cache\QueryCacheProfile; /** * Base contract for ORM queries. Base class for Query and NativeQuery. @@ -91,34 +92,15 @@ abstract class AbstractQuery protected $_hydrationMode = self::HYDRATE_OBJECT; /** - * The locally set cache driver used for caching result sets of this query. - * - * @var CacheDriver + * @param \Doctrine\DBAL\Cache\QueryCacheProfile */ - protected $_resultCacheDriver; - - /** - * Boolean flag for whether or not to cache the results of this query. - * - * @var boolean - */ - protected $_useResultCache; - - /** - * @var string The id to store the result cache entry under. - */ - protected $_resultCacheId; + protected $_queryCacheProfile; /** * @var boolean Boolean value that indicates whether or not expire the result cache. */ protected $_expireResultCache = false; - /** - * @var int Result Cache lifetime. - */ - protected $_resultCacheTTL; - /** * Initializes a new instance of a class derived from AbstractQuery. * @@ -260,7 +242,7 @@ abstract class AbstractQuery } /** - * Defines a cache driver to be used for caching result sets. + * Defines a cache driver to be used for caching result sets and implictly enables caching. * * @param Doctrine\Common\Cache\Cache $driver Cache driver * @return Doctrine\ORM\AbstractQuery @@ -270,9 +252,10 @@ abstract class AbstractQuery if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { throw ORMException::invalidResultCacheDriver(); } - $this->_resultCacheDriver = $resultCacheDriver; - if ($resultCacheDriver) { - $this->_useResultCache = true; + if ($this->_queryCacheProfile) { + $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver); + } else { + $this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver); } return $this; } @@ -280,12 +263,13 @@ abstract class AbstractQuery /** * Returns the cache driver used for caching result sets. * + * @deprecated * @return Doctrine\Common\Cache\Cache Cache driver */ public function getResultCacheDriver() { - if ($this->_resultCacheDriver) { - return $this->_resultCacheDriver; + if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { + return $this->_queryCacheProfile->getResultCacheDriver(); } else { return $this->_em->getConfiguration()->getResultCacheImpl(); } @@ -296,18 +280,17 @@ abstract class AbstractQuery * how long and which ID to use for the cache entry. * * @param boolean $bool - * @param integer $timeToLive + * @param integer $lifetime * @param string $resultCacheId * @return Doctrine\ORM\AbstractQuery This query instance. */ - public function useResultCache($bool, $timeToLive = null, $resultCacheId = null) + public function useResultCache($bool, $lifetime = null, $resultCacheId = null) { - $this->_useResultCache = $bool; - if ($timeToLive) { - $this->setResultCacheLifetime($timeToLive); - } - if ($resultCacheId) { - $this->_resultCacheId = $resultCacheId; + if ($bool) { + $this->setResultCacheLifetime($lifetime); + $this->setResultCacheId($resultCacheId); + } else { + $this->_queryCacheProfile = null; } return $this; } @@ -315,27 +298,33 @@ abstract class AbstractQuery /** * Defines how long the result cache will be active before expire. * - * @param integer $timeToLive How long the cache entry is valid. + * @param integer $lifetime How long the cache entry is valid. * @return Doctrine\ORM\AbstractQuery This query instance. */ - public function setResultCacheLifetime($timeToLive) + public function setResultCacheLifetime($lifetime) { - if ($timeToLive !== null) { - $timeToLive = (int) $timeToLive; + if ($lifetime === null) { + $lifetime = 0; + } else { + $lifetime = (int)$lifetime; + } + if ($this->_queryCacheProfile) { + $this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime); + } else { + $this->_queryCacheProfile = new QueryCacheProfile($lifetime); } - - $this->_resultCacheTTL = $timeToLive; return $this; } /** * Retrieves the lifetime of resultset cache. * + * @deprecated * @return integer */ public function getResultCacheLifetime() { - return $this->_resultCacheTTL; + return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0; } /** @@ -360,6 +349,14 @@ abstract class AbstractQuery return $this->_expireResultCache; } + /** + * @return QueryCacheProfile + */ + public function getQueryCacheProfile() + { + return $this->_queryCacheProfile; + } + /** * Change the default fetch mode of an association for this query. * @@ -584,28 +581,6 @@ abstract class AbstractQuery $this->setParameters($params); } - // Check result cache - if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) { - list($key, $hash) = $this->getResultCacheId(); - $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash); - - if ($cached === false || !isset($cached[$key])) { - // Cache miss. - $stmt = $this->_doExecute(); - - $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( - $stmt, $this->_resultSetMapping, $this->_hints - ); - - $cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL); - - return $result; - } else { - // Cache hit. - return $cached[$key]; - } - } - $stmt = $this->_doExecute(); if (is_numeric($stmt)) { @@ -627,43 +602,23 @@ abstract class AbstractQuery */ public function setResultCacheId($id) { - $this->_resultCacheId = $id; + if ($this->_queryCacheProfile) { + $this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id); + } else { + $this->_queryCacheProfile = new QueryCacheProfile(0, $id); + } return $this; } /** - * Get the result cache id to use to store the result set cache entry. - * Will return the configured id if it exists otherwise a hash will be - * automatically generated for you. + * Get the result cache id to use to store the result set cache entry if set. * - * @return array ($key, $hash) + * @deprecated + * @return string */ - protected function getResultCacheId() + public function getResultCacheId() { - if ($this->_resultCacheId) { - return array($this->_resultCacheId, $this->_resultCacheId); - } else { - $params = $this->_params; - foreach ($params AS $key => $value) { - if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) { - if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) { - $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value); - } else { - $class = $this->_em->getClassMetadata(get_class($value)); - $idValues = $class->getIdentifierValues($value); - } - $params[$key] = $idValues; - } else { - $params[$key] = $value; - } - } - - $sql = $this->getSql(); - ksort($this->_hints); - $key = implode(";", (array)$sql) . var_export($params, true) . - var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode; - return array($key, md5($key)); - } + return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null; } /** diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index ee6496087..eaa4df562 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -209,27 +209,6 @@ class Configuration extends \Doctrine\DBAL\Configuration $this->_attributes['metadataDriverImpl'] : null; } - /** - * Gets the cache driver implementation that is used for query result caching. - * - * @return \Doctrine\Common\Cache\Cache - */ - public function getResultCacheImpl() - { - return isset($this->_attributes['resultCacheImpl']) ? - $this->_attributes['resultCacheImpl'] : null; - } - - /** - * Sets the cache driver implementation that is used for query result caching. - * - * @param \Doctrine\Common\Cache\Cache $cacheImpl - */ - public function setResultCacheImpl(Cache $cacheImpl) - { - $this->_attributes['resultCacheImpl'] = $cacheImpl; - } - /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index 2c0a5ab28..dea223fa3 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -57,17 +57,17 @@ final class NativeQuery extends AbstractQuery */ protected function _doExecute() { - $stmt = $this->_em->getConnection()->prepare($this->_sql); $params = $this->_params; - foreach ($params as $key => $value) { - if (isset($this->_paramTypes[$key])) { - $stmt->bindValue($key, $value, $this->_paramTypes[$key]); - } else { - $stmt->bindValue($key, $value); + $types = $this->_paramTypes; + if ($params) { + if (is_int(key($params))) { + ksort($params); + ksort($types); + $params = array_values($params); + $types = array_values($types); } } - $stmt->execute(); - return $stmt; + return $this->_em->getConnection()->executeQuery($this->_sql, $params, $types, $this->_queryCacheProfile); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index e0bbcec5a..293c64391 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -232,6 +232,9 @@ final class Query extends AbstractQuery protected function _doExecute() { $executor = $this->_parse()->getSqlExecutor(); + if ($this->_queryCacheProfile) { + $executor->setQueryCacheProfile($this->_queryCacheProfile); + } // Prepare parameters $paramMappings = $this->_parserResult->getParameterMappings(); diff --git a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php index 7879b0ff2..f44e383b9 100644 --- a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php @@ -1,7 +1,5 @@ _sqlStatements; } + public function setQueryCacheProfile(QueryCacheProfile $qcp) + { + $this->queryCacheProfile = $qcp; + } + /** * Executes all sql statements. * * @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries. * @param array $params The parameters. + * @param array $types The parameter types. * @return Doctrine\DBAL\Driver\Statement */ - abstract public function execute(Connection $conn, array $params, array $types); + abstract public function execute(Connection $conn, array $params, array $types); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php index a6c22cecd..5b07d4d02 100644 --- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php @@ -1,7 +1,5 @@ - * @version $Revision$ * @link www.doctrine-project.org * @since 2.0 */ @@ -41,8 +38,11 @@ class SingleSelectExecutor extends AbstractSqlExecutor $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } + /** + * {@inheritDoc} + */ public function execute(Connection $conn, array $params, array $types) { - return $conn->executeQuery($this->_sqlStatements, $params, $types); + return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile); } } diff --git a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php index 94db13b05..facccb715 100644 --- a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php +++ b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php @@ -1,7 +1,5 @@ - * @version $Revision$ * @link www.doctrine-project.org * @since 2.0 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. @@ -45,7 +42,10 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); } } - + + /** + * {@inheritDoc} + */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeUpdate($this->_sqlStatements, $params, $types); diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index f91395b6f..dea79e7bf 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit f91395b6f469b5076f52fefd64574c443b076485 +Subproject commit dea79e7bfbabf973e807539e258ccf3b9ee98f45 diff --git a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php index 235b4c91c..3dcae4fab 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php @@ -90,10 +90,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testUseResultCache() { $cache = new \Doctrine\Common\Cache\ArrayCache(); - $this->_em->getConfiguration()->setResultCacheImpl($cache); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux'); $query->useResultCache(true); + $query->setResultCacheDriver($cache); $query->setResultCacheId('testing_result_cache_id'); $users = $query->getResult(); @@ -108,11 +108,11 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testUseResultCacheParams() { $cache = new \Doctrine\Common\Cache\ArrayCache(); - $this->_em->getConfiguration()->setResultCacheImpl($cache); $sqlCount = count($this->_sqlLoggerStack->queries); $query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux WHERE ux.id = ?1'); $query->setParameter(1, 1); + $query->setResultCacheDriver($cache); $query->useResultCache(true); $query->getResult(); @@ -149,10 +149,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase } /** - * @param $query + * @param string $query * @depends testNativeQueryResultCaching */ - public function testResultCacheDependsOnQueryHints($query) + public function testResultCacheNotDependsOnQueryHints($query) { $cache = $query->getResultCacheDriver(); $cacheCount = $this->getCacheSize($cache); @@ -160,7 +160,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $query->setHint('foo', 'bar'); $query->getResult(); - $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache)); + $this->assertEquals($cacheCount, $this->getCacheSize($cache)); } /** @@ -182,7 +182,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase * @param $query * @depends testNativeQueryResultCaching */ - public function testResultCacheDependsOnHydrationMode($query) + public function testResultCacheNotDependsOnHydrationMode($query) { $cache = $query->getResultCacheDriver(); $cacheCount = $this->getCacheSize($cache); @@ -190,7 +190,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode()); $query->getArrayResult(); - $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache)); + $this->assertEquals($cacheCount, $this->getCacheSize($cache)); } /** From a8052dec3775835852344951c4f5e9aa053b6004 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 23 Oct 2011 23:35:04 +0200 Subject: [PATCH 16/28] Added note about result cache changes --- UPGRADE_TO_2_2 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2 index 220fb39f9..757413029 100644 --- a/UPGRADE_TO_2_2 +++ b/UPGRADE_TO_2_2 @@ -1,3 +1,17 @@ +# ResultCache implementation rewritten + +The result cache is completly rewritten and now works on the database result level, not inside the ORM AbstractQuery +anymore. This means that for result cached queries the hydration will now always be performed again, regardless of +the hydration mode. Affected areas are: + +1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork + leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore. +2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result. + +The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now +deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` +instance with access to result cache driver, lifetime and cache key. + # EntityManager#getPartialReference() creates read-only entity Entities returned from EntityManager#getPartialReference() are now marked as read-only if they From cdb452b27b5a341a372dff833ae2d40a4e03d513 Mon Sep 17 00:00:00 2001 From: Jaik Dean Date: Mon, 24 Oct 2011 10:01:27 +0100 Subject: [PATCH 17/28] 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'); } From 3b9312e291b0f730f863c276c131cf2d2d01a68d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 25 Oct 2011 22:54:20 +0200 Subject: [PATCH 18/28] Bump DBAL version and make TestUtil more lenient --- lib/vendor/doctrine-dbal | 2 +- tests/Doctrine/Tests/TestUtil.php | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 82cc92144..537de7ea6 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 82cc921447fde697bf3d9f5285d0f0b8587303d8 +Subproject commit 537de7ea6a34edbcc40bc6ca92e0a3f816b59330 diff --git a/tests/Doctrine/Tests/TestUtil.php b/tests/Doctrine/Tests/TestUtil.php index a77812f7f..b78d06e4e 100644 --- a/tests/Doctrine/Tests/TestUtil.php +++ b/tests/Doctrine/Tests/TestUtil.php @@ -70,10 +70,16 @@ class TestUtil } else { $sm = $realConn->getSchemaManager(); - $tableNames = $sm->listTableNames(); - - foreach ($tableNames AS $tableName) { - $sm->dropTable($tableName); + /* @var $schema Schema */ + $schema = $sm->createSchema(); + $stmts = $schema->toDropSql($realConn->getDatabasePlatform()); + + foreach ($stmts AS $stmt) { + try { + $realConn->exec($stmt); + } catch(\Exception $e) { + // TODO: Now is this a real good idea? + } } } From c5ef21864fa524b9239f698d3918ad06b3265b35 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 26 Oct 2011 15:04:49 -0200 Subject: [PATCH 19/28] Fixed bug with fetch=EAGER associations that have already been hydrated during querying. --- lib/Doctrine/ORM/UnitOfWork.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 4366250dd..e499d3c5f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2054,6 +2054,11 @@ class UnitOfWork implements PropertyChangedListener // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); + if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && + ! $this->eagerLoadingEntities[$class->rootEntityName]) { + unset($this->eagerLoadingEntities[$class->rootEntityName]); + } + // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { foreach ($class->associationMappings as $field => $assoc) { From 4a0227e5f249a591429c4b5c6adb6f35ed33cae2 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 28 Oct 2011 00:24:41 +0200 Subject: [PATCH 20/28] Revert Expr\Base patch --- 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 beac2bb9c..975d450cd 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 ) { + if ( $arg !== null || ($arg instanceof self && $arg->count() > 0) ) { // If we decide to keep Expr\Base instances, we can use this check if ( ! is_string($arg)) { $class = get_class($arg); From f34eb83a7c962f6fead4ba6d72eedf2979a23fe5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 28 Oct 2011 00:50:10 +0200 Subject: [PATCH 21/28] DDC-1454 - Fix exists() for Joined table inheritance --- .../ORM/Persisters/BasicEntityPersister.php | 296 +++++++++--------- .../Persisters/JoinedSubclassPersister.php | 78 ++--- .../ORM/Functional/Ticket/DDC1454Test.php | 69 ++++ 3 files changed, 256 insertions(+), 187 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 0379ccf56..fab80ff29 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -92,7 +92,7 @@ class BasicEntityPersister /** * The database platform. - * + * * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; @@ -110,12 +110,12 @@ class BasicEntityPersister * @var array */ protected $_queuedInserts = array(); - + /** * ResultSetMapping that is used for all queries. Is generated lazily once per request. - * + * * TODO: Evaluate Caching in combination with the other cached SQL snippets. - * + * * @var Query\ResultSetMapping */ protected $_rsm; @@ -123,7 +123,7 @@ class BasicEntityPersister /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. - * + * * @var array * @see _prepareInsertData($entity) * @see _prepareUpdateData($entity) @@ -133,7 +133,7 @@ class BasicEntityPersister /** * The INSERT SQL statement used for entities handled by this persister. * This SQL is only generated once per request, if at all. - * + * * @var string */ private $_insertSql; @@ -141,29 +141,29 @@ class BasicEntityPersister /** * The SELECT column list SQL fragment used for querying entities by this persister. * This SQL fragment is only generated once per request, if at all. - * + * * @var string */ protected $_selectColumnListSql; - + /** * The JOIN SQL fragement used to eagerly load all many-to-one and one-to-one * associations configured as FETCH_EAGER, aswell as all inverse one-to-one associations. - * + * * @var string */ protected $_selectJoinSql; /** * Counter for creating unique SQL table and column aliases. - * + * * @var integer */ protected $_sqlAliasCounter = 0; /** * Map from class names (FQCN) to the corresponding generated SQL table aliases. - * + * * @var array */ protected $_sqlTableAliases = array(); @@ -171,7 +171,7 @@ class BasicEntityPersister /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. - * + * * @param Doctrine\ORM\EntityManager $em * @param Doctrine\ORM\Mapping\ClassMetadata $class */ @@ -205,7 +205,7 @@ class BasicEntityPersister /** * Executes all queued entity insertions and returns any generated post-insert * identifiers that were created as a result of the insertions. - * + * * If no inserts are queued, invoking this method is a NOOP. * * @return array An array of any generated post-insert IDs. This will be an empty array @@ -229,7 +229,7 @@ class BasicEntityPersister if (isset($insertData[$tableName])) { $paramIndex = 1; - + foreach ($insertData[$tableName] as $column => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); } @@ -257,7 +257,7 @@ class BasicEntityPersister /** * Retrieves the default version value which was created - * by the preceding INSERT statement and assigns it back in to the + * by the preceding INSERT statement and assigns it back in to the * entities version field. * * @param object $entity @@ -271,7 +271,7 @@ class BasicEntityPersister /** * Fetch the current version value of a versioned entity. - * + * * @param Doctrine\ORM\Mapping\ClassMetadata $versionedClass * @param mixed $id * @return mixed @@ -280,9 +280,9 @@ class BasicEntityPersister { $versionField = $versionedClass->versionField; $identifier = $versionedClass->getIdentifierColumnNames(); - + $versionFieldColumnName = $versionedClass->getQuotedColumnName($versionField, $this->_platform); - + //FIXME: Order with composite keys might not be correct $sql = 'SELECT ' . $versionFieldColumnName . ' FROM ' . $versionedClass->getQuotedTableName($this->_platform) @@ -299,7 +299,7 @@ class BasicEntityPersister * The data to update is retrieved through {@link _prepareUpdateData}. * Subclasses that override this method are supposed to obtain the update data * in the same way, through {@link _prepareUpdateData}. - * + * * Subclasses are also supposed to take care of versioning when overriding this method, * if necessary. The {@link _updateTable} method can be used to apply the data retrieved * from {@_prepareUpdateData} on the target tables, thereby optionally applying versioning. @@ -310,7 +310,7 @@ class BasicEntityPersister { $updateData = $this->_prepareUpdateData($entity); $tableName = $this->_class->getTableName(); - + if (isset($updateData[$tableName]) && $updateData[$tableName]) { $this->_updateTable( $entity, $this->_class->getQuotedTableName($this->_platform), @@ -338,17 +338,17 @@ class BasicEntityPersister $set = $params = $types = array(); foreach ($updateData as $columnName => $value) { - $set[] = (isset($this->_class->fieldNames[$columnName])) + $set[] = (isset($this->_class->fieldNames[$columnName])) ? $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?' : $columnName . ' = ?'; - + $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); - + foreach ($this->_class->identifier as $idField) { if (isset($this->_class->associationMappings[$idField])) { $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); @@ -366,13 +366,13 @@ class BasicEntityPersister $versionField = $this->_class->versionField; $versionFieldType = $this->_class->fieldMappings[$versionField]['type']; $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); - + if ($versionFieldType == Type::INTEGER) { $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; } else if ($versionFieldType == Type::DATETIME) { $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; } - + $where[] = $versionColumn; $params[] = $this->_class->reflFields[$versionField]->getValue($entity); $types[] = $this->_class->fieldMappings[$versionField]['type']; @@ -401,18 +401,18 @@ class BasicEntityPersister // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierachy? I hope not! $selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']); - + if ( ! $mapping['isOwningSide']) { $relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']); $mapping = $relatedClass->associationMappings[$mapping['mappedBy']]; $keys = array_keys($mapping['relationToTargetKeyColumns']); - + if ($selfReferential) { $otherKeys = array_keys($mapping['relationToSourceKeyColumns']); } } else { $keys = array_keys($mapping['relationToSourceKeyColumns']); - + if ($selfReferential) { $otherKeys = array_keys($mapping['relationToTargetKeyColumns']); } @@ -420,13 +420,13 @@ class BasicEntityPersister if ( ! isset($mapping['isOnDeleteCascade'])) { $this->_conn->delete( - $this->_class->getQuotedJoinTableName($mapping, $this->_platform), + $this->_class->getQuotedJoinTableName($mapping, $this->_platform), array_combine($keys, $identifier) ); if ($selfReferential) { $this->_conn->delete( - $this->_class->getQuotedJoinTableName($mapping, $this->_platform), + $this->_class->getQuotedJoinTableName($mapping, $this->_platform), array_combine($otherKeys, $identifier) ); } @@ -458,7 +458,7 @@ class BasicEntityPersister * Prepares the changeset of an entity for database insertion (UPDATE). * * The changeset is obtained from the currently running UnitOfWork. - * + * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. * @@ -493,7 +493,7 @@ class BasicEntityPersister if (isset($this->_class->associationMappings[$field])) { $assoc = $this->_class->associationMappings[$field]; - + // Only owning side of x-1 associations can have a FK column. if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { continue; @@ -501,7 +501,7 @@ class BasicEntityPersister if ($newVal !== null) { $oid = spl_object_hash($newVal); - + if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { // The associated entity $newVal is not yet persisted, so we must // set $newVal = null, in order to insert a null value and schedule an @@ -528,7 +528,7 @@ class BasicEntityPersister } else { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; } - + $this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); } } else { @@ -537,7 +537,7 @@ class BasicEntityPersister $result[$this->getOwningTable($field)][$columnName] = $newVal; } } - + return $result; } @@ -589,7 +589,7 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); - + if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; @@ -597,7 +597,7 @@ class BasicEntityPersister $hydrator = $this->_em->newHydrator($this->_selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); - + return $entities ? $entities[0] : null; } @@ -626,17 +626,17 @@ class BasicEntityPersister // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). $hints = array(); - + if ($isInverseSingleValued) { $hints['fetched'][$targetClass->name][$assoc['inversedBy']] = true; - + if ($targetClass->subClasses) { foreach ($targetClass->subClasses as $targetSubclassName) { $hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true; } } } - + /* cascade read-only status if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { $hints[Query::HINT_READ_ONLY] = true; @@ -652,7 +652,7 @@ class BasicEntityPersister } else { $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); - + // TRICKY: since the association is specular source and target are flipped foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) { @@ -660,12 +660,12 @@ class BasicEntityPersister $sourceClass->name, $sourceKeyColumn ); } - + // unset the old value and set the new sql aliased value here. By definition // unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method. $identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); - + unset($identifier[$targetKeyColumn]); } @@ -681,7 +681,7 @@ class BasicEntityPersister /** * Refreshes a managed entity. - * + * * @param array $id The identifier of the entity as an associative array from * column or field names to values. * @param object $entity The entity to refresh. @@ -691,16 +691,16 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($id); list($params, $types) = $this->expandParameters($id); $stmt = $this->_conn->executeQuery($sql, $params, $types); - + $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true)); if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) { $this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity); } - + $evm = $this->_em->getEventManager(); - + if ($evm->hasListeners(Events::postLoad)) { $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); } @@ -708,7 +708,7 @@ class BasicEntityPersister /** * Loads a list of entities by a list of field criteria. - * + * * @param array $criteria * @param array $orderBy * @param int $limit @@ -723,13 +723,13 @@ class BasicEntityPersister $stmt = $this->_conn->executeQuery($sql, $params, $types); $hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - + return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true)); } /** * Get (sliced or full) elements of the given collection. - * + * * @param array $assoc * @param object $sourceEntity * @param int|null $offset @@ -739,16 +739,16 @@ class BasicEntityPersister public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); - + return $this->loadArrayFromStatement($assoc, $stmt); } /** * Load an array of entities from a given dbal statement. - * + * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt - * + * * @return array */ private function loadArrayFromStatement($assoc, $stmt) @@ -763,21 +763,21 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - + return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** * Hydrate a collection from a given dbal statement. - * + * * @param array $assoc * @param Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll - * + * * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) - { + { $hints = array('deferEagerLoads' => true, 'collection' => $coll); if (isset($assoc['indexBy'])) { @@ -788,7 +788,7 @@ class BasicEntityPersister } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); - + return $hydrator->hydrateAll($stmt, $rsm, $hints); } @@ -805,7 +805,7 @@ class BasicEntityPersister public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); - + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } @@ -813,15 +813,15 @@ class BasicEntityPersister { $criteria = array(); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); - + if ($assoc['isOwningSide']) { $quotedJoinTable = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform); - + foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; @@ -839,18 +839,18 @@ class BasicEntityPersister } else { $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']]; $quotedJoinTable = $sourceClass->getQuotedJoinTableName($owningAssoc, $this->_platform); - + // TRICKY: since the association is inverted source and target are flipped foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } - + $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $value; } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$quotedJoinTable . "." . $relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); @@ -864,7 +864,7 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); - + return $this->_conn->executeQuery($sql, $params, $types); } @@ -890,7 +890,7 @@ class BasicEntityPersister $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : ''; $lockSql = ''; - + if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { @@ -908,7 +908,7 @@ class BasicEntityPersister /** * Gets the ORDER BY SQL snippet for ordered collections. - * + * * @param array $orderBy * @param string $baseTableAlias * @return string @@ -917,7 +917,7 @@ class BasicEntityPersister protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; - + foreach ($orderBy as $fieldName => $orientation) { if ( ! isset($this->_class->fieldMappings[$fieldName])) { throw ORMException::unrecognizedField($fieldName); @@ -928,7 +928,7 @@ class BasicEntityPersister : $baseTableAlias; $columnName = $this->_class->getQuotedColumnName($fieldName, $this->_platform); - + $orderBySql .= $orderBySql ? ', ' : ' ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } @@ -944,7 +944,7 @@ class BasicEntityPersister * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. * Subclasses may or may not do the same. - * + * * @return string The SQL fragment. * @todo Rename: _getSelectColumnsSQL() */ @@ -961,75 +961,75 @@ class BasicEntityPersister // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { if ($columnList) $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL($field, $this->_class); } $this->_selectJoinSql = ''; $eagerAliasCounter = 0; - + foreach ($this->_class->associationMappings as $assocField => $assoc) { $assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class); - + if ($assocColumnSQL) { if ($columnList) $columnList .= ', '; - + $columnList .= $assocColumnSQL; } - + if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); - + if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } - + $assocAlias = 'e' . ($eagerAliasCounter++); $this->_rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); - + foreach ($eagerEntity->fieldNames AS $field) { if ($columnList) $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias); } - + foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) { $assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias); - + if ($assoc2ColumnSQL) { if ($columnList) $columnList .= ', '; $columnList .= $assoc2ColumnSQL; } } - + $this->_selectJoinSql .= ' LEFT JOIN'; // TODO: Inner join when all join columns are NOT nullable. $first = true; - + if ($assoc['isOwningSide']) { $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; - + foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - - $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' + + $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol . ' '; $first = false; } } else { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); - - $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' + + $this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; foreach ($owningAssoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) { if ( ! $first) { $this->_selectJoinSql .= ' AND '; } - - $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' + + $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol . ' '; $first = false; } @@ -1041,33 +1041,33 @@ class BasicEntityPersister return $this->_selectColumnListSql; } - + /** * Gets the SQL join fragment used when selecting entities from an association. - * + * * @param string $field * @param array $assoc * @param ClassMetadata $class * @param string $alias - * - * @return string + * + * @return string */ protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') { $columnList = ''; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList) $columnList .= ', '; $columnAlias = $srcColumn . $this->_sqlAliasCounter++; $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); - $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) + $columnList .= $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $srcColumn . ' AS ' . $resultColumnName; $this->_rsm->addMetaResult($alias, $resultColumnName, $srcColumn, isset($assoc['id']) && $assoc['id'] === true); } } - + return $columnList; } @@ -1087,10 +1087,10 @@ class BasicEntityPersister $owningAssoc = $this->_em->getClassMetadata($manyToMany['targetEntity'])->associationMappings[$manyToMany['mappedBy']]; $joinClauses = $owningAssoc['relationToSourceKeyColumns']; } - + $joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform); $joinSql = ''; - + foreach ($joinClauses as $joinTableColumn => $sourceColumn) { if ($joinSql != '') $joinSql .= ' AND '; @@ -1109,7 +1109,7 @@ class BasicEntityPersister /** * Gets the INSERT SQL used by the persister to persist a new entity. - * + * * @return string */ protected function _getInsertSQL() @@ -1117,7 +1117,7 @@ class BasicEntityPersister if ($this->_insertSql === null) { $insertSql = ''; $columns = $this->_getInsertColumnList(); - + if (empty($columns)) { $insertSql = $this->_platform->getEmptyIdentityInsertSQL( $this->_class->getQuotedTableName($this->_platform), @@ -1130,10 +1130,10 @@ class BasicEntityPersister $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($this->_platform) . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')'; } - + $this->_insertSql = $insertSql; } - + return $this->_insertSql; } @@ -1148,15 +1148,15 @@ class BasicEntityPersister protected function _getInsertColumnList() { $columns = array(); - + foreach ($this->_class->reflFields as $name => $field) { if ($this->_class->isVersioned && $this->_class->versionField == $name) { continue; } - + if (isset($this->_class->associationMappings[$name])) { $assoc = $this->_class->associationMappings[$name]; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columns[] = $sourceCol; @@ -1181,10 +1181,10 @@ class BasicEntityPersister protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $columnName = $class->columnNames[$field]; - $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) + $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; @@ -1192,7 +1192,7 @@ class BasicEntityPersister /** * Gets the SQL table alias for the given class name. - * + * * @param string $className * @return string The SQL table alias. * @todo Reconsider. Binding table aliases to class names is not such a good idea. @@ -1202,15 +1202,15 @@ class BasicEntityPersister if ($assocName) { $className .= '#' . $assocName; } - + if (isset($this->_sqlTableAliases[$className])) { return $this->_sqlTableAliases[$className]; } - + $tableAlias = 't' . $this->_sqlAliasCounter++; $this->_sqlTableAliases[$className] = $tableAlias; - + return $tableAlias; } @@ -1234,9 +1234,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); } @@ -1265,25 +1265,25 @@ class BasicEntityPersister protected function _getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = ''; - + foreach ($criteria as $field => $value) { $conditionSql .= $conditionSql ? ' AND ' : ''; if (isset($this->_class->columnNames[$field])) { $className = (isset($this->_class->fieldMappings[$field]['inherited'])) - ? $this->_class->fieldMappings[$field]['inherited'] + ? $this->_class->fieldMappings[$field]['inherited'] : $this->_class->name; - + $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->getQuotedColumnName($field, $this->_platform); } else if (isset($this->_class->associationMappings[$field])) { if ( ! $this->_class->associationMappings[$field]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); } - + $className = (isset($this->_class->associationMappings[$field]['inherited'])) ? $this->_class->associationMappings[$field]['inherited'] : $this->_class->name; - + $conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name']; } else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { // very careless developers could potentially open up this normally hidden api for userland attacks, @@ -1294,7 +1294,7 @@ class BasicEntityPersister } else { throw ORMException::unrecognizedField($field); } - + $conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ?'); } return $conditionSql; @@ -1312,7 +1312,7 @@ class BasicEntityPersister public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); - + return $this->loadArrayFromStatement($assoc, $stmt); } @@ -1328,7 +1328,7 @@ class BasicEntityPersister public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } @@ -1353,12 +1353,12 @@ class BasicEntityPersister if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); - + if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } - + $criteria[$tableAlias . "." . $targetKeyColumn] = $value; } else { $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); @@ -1389,13 +1389,13 @@ class BasicEntityPersister $types[] = $this->getType($field, $value); $params[] = $this->getValue($value); } - + return array($params, $types); } - + /** * Infer field type to be used by parameter type casting. - * + * * @param string $field * @param mixed $value * @return integer @@ -1409,11 +1409,11 @@ class BasicEntityPersister case (isset($this->_class->associationMappings[$field])): $assoc = $this->_class->associationMappings[$field]; - + if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw Query\QueryException::associationPathCompositeKeyNotSupported(); } - + $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetColumn = $assoc['joinColumns'][0]['referencedColumnName']; $type = null; @@ -1431,36 +1431,36 @@ class BasicEntityPersister if (is_array($value)) { $type += Connection::ARRAY_PARAM_OFFSET; } - + return $type; } - + /** * Retrieve parameter value - * + * * @param mixed $value - * @return mixed + * @return mixed */ private function getValue($value) { if (is_array($value)) { $newValue = array(); - + foreach ($value as $itemValue) { $newValue[] = $this->getIndividualValue($itemValue); } - + return $newValue; } - + return $this->getIndividualValue($value); } - + /** * Retrieve an invidiual parameter value - * + * * @param mixed $value - * @return mixed + * @return mixed */ private function getIndividualValue($value) { @@ -1471,11 +1471,11 @@ class BasicEntityPersister $class = $this->_em->getClassMetadata(get_class($value)); $idValues = $class->getIdentifierValues($value); } - + $value = $idValues[key($idValues)]; } - - return $value; + + return $value; } /** @@ -1487,17 +1487,17 @@ class BasicEntityPersister public function exists($entity, array $extraConditions = array()) { $criteria = $this->_class->getIdentifierValues($entity); - + if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } - $sql = 'SELECT 1' - . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name) + $sql = 'SELECT 1 ' + . $this->getLockTablesSql() . ' WHERE ' . $this->_getSelectConditionSQL($criteria); - + list($params, $types) = $this->expandParameters($criteria); - + return (bool) $this->_conn->fetchColumn($sql, $params); } } diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index c95fee755..fb60d5e32 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -46,7 +46,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister /** * Map of table to quoted table names. - * + * * @var array */ private $_quotedTableMap = array(); @@ -59,7 +59,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $class = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class; - + return $class->getTableName(); } @@ -73,10 +73,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister { if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; - + return $this->_em->getClassMetadata($definingClassName); } - + return $this->_class; } @@ -92,7 +92,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister if (isset($this->_owningTableMap[$fieldName])) { return $this->_owningTableMap[$fieldName]; } - + if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']); } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { @@ -130,15 +130,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Prepare statements for sub tables. $subTableStmts = array(); - + if ($rootClass !== $this->_class) { $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL()); } - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); - + if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); @@ -153,11 +153,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Execute insert on root table $paramIndex = 1; - + foreach ($insertData[$rootTableName] as $columnName => $value) { $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } - + $rootTableStmt->execute(); if ($isPostInsertId) { @@ -172,23 +172,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister foreach ($subTableStmts as $tableName => $stmt) { $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); $paramIndex = 1; - + foreach ((array) $id as $idName => $idVal) { $type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING; - + $stmt->bindValue($paramIndex++, $idVal, $type); } - + foreach ($data as $columnName => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } - + $stmt->execute(); } } $rootTableStmt->closeCursor(); - + foreach ($subTableStmts as $stmt) { $stmt->closeCursor(); } @@ -220,7 +220,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName ); } - + // Make sure the table with the version column is updated even if no columns on that // table were affected. if ($isVersioned && ! isset($updateData[$versionedTable])) { @@ -251,7 +251,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } else { // Delete from all tables individually, starting from this class' table up to the root table. $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id); - + foreach ($this->_class->parentClasses as $parentClass) { $this->_conn->delete( $this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id @@ -270,16 +270,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Create the column list fragment only once if ($this->_selectColumnListSql === null) { - + $this->_rsm = new ResultSetMapping(); $this->_rsm->addEntityResult($this->_class->name, 'r'); - + // Add regular columns $columnList = ''; - + foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->_getSelectColumnSQL( $fieldName, isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class @@ -290,12 +290,12 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister foreach ($this->_class->associationMappings as $assoc2) { if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) { $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias; - + foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->getSelectJoinColumnSQL( - $tableAlias, + $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name ); @@ -309,23 +309,23 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister $columnList .= ', ' . $tableAlias . '.' . $discrColumn; $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); - + $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); } // INNER JOIN parent tables $joinSql = ''; - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -339,7 +339,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) continue; - + $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } @@ -348,9 +348,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; - + $columnList .= $this->getSelectJoinColumnSQL( - $tableAlias, + $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name ); @@ -362,10 +362,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // Add LEFT JOIN $joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -382,7 +382,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister } $lockSql = ''; - + if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { @@ -408,29 +408,29 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister // INNER JOIN parent tables $joinSql = ''; - + foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; - + $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias . $joinSql; } - + /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); } - + /** {@inheritdoc} */ protected function _getInsertColumnList() { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php new file mode 100644 index 000000000..eaf9dd3f9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1454Test.php @@ -0,0 +1,69 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1454File'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1454Picture'), + )); + } catch (\Exception $ignored) { + + } + } + + public function testFailingCase() + { + $pic = new DDC1454Picture(); + $this->_em->getUnitOfWork()->getEntityState($pic); + } + +} + +/** + * @Entity + */ +class DDC1454Picture extends DDC1454File +{ + +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"picture" = "DDC1454Picture"}) + */ +class DDC1454File +{ + /** + * @Column(name="file_id", type="integer") + * @Id + */ + public $fileId; + + public function __construct() { + $this->fileId = rand(); + } + + /** + * Get fileId + */ + public function getFileId() { + return $this->fileId; + } + +} \ No newline at end of file From 1579c43433788fc9ab0ff3d5a3f54a530e371a51 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 28 Oct 2011 12:49:01 -0200 Subject: [PATCH 22/28] Code beautification and docblocks enhancements. --- .../ORM/Event/EntityEventDelegator.php | 38 ++++++--- lib/Doctrine/ORM/Event/LifecycleEventArgs.php | 39 ++++++--- .../ORM/Event/LoadClassMetadataEventArgs.php | 40 +++++++-- lib/Doctrine/ORM/Event/OnClearEventArgs.php | 20 +++-- lib/Doctrine/ORM/Event/OnFlushEventArgs.php | 28 +++--- lib/Doctrine/ORM/Event/PostFlushEventArgs.php | 16 ++-- lib/Doctrine/ORM/Event/PreUpdateEventArgs.php | 85 +++++++++++++------ 7 files changed, 183 insertions(+), 83 deletions(-) diff --git a/lib/Doctrine/ORM/Event/EntityEventDelegator.php b/lib/Doctrine/ORM/Event/EntityEventDelegator.php index d7c46e68e..09532bc4b 100644 --- a/lib/Doctrine/ORM/Event/EntityEventDelegator.php +++ b/lib/Doctrine/ORM/Event/EntityEventDelegator.php @@ -19,14 +19,16 @@ namespace Doctrine\ORM\Event; -use \Doctrine\Common\EventSubscriber; -use \LogicException; +use Doctrine\Common\EventSubscriber; +use LogicException; /** * Delegate events only for certain entities they are registered for. * - * @author Benjamin Eberlei - * @since 2.2 + * @link www.doctrine-project.org + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @since 2.2 */ class EntityEventDelegator implements EventSubscriber { @@ -54,17 +56,23 @@ class EntityEventDelegator implements EventSubscriber 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."); + 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); + $hash = spl_object_hash($listener); + $entities = array_flip((array) $entities); 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)); + $this->listeners[$event][$hash] = array( + 'listener' => $listener, + 'entities' => $entities + ); } } @@ -73,6 +81,7 @@ class EntityEventDelegator implements EventSubscriber * interested in and added as a listener for these events. * * @param Doctrine\Common\EventSubscriber $subscriber The subscriber. + * @param array $entities */ public function addEventSubscriber(EventSubscriber $subscriber, $entities) { @@ -87,24 +96,27 @@ class EntityEventDelegator implements EventSubscriber public function getSubscribedEvents() { $this->frozen = true; + return array_keys($this->listeners); } /** * Delegate the event to an appropriate listener * - * @param $eventName - * @param $event + * @param string $eventName + * @param array $args * @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); - } + + if ( ! isset($listenerData['entities'][$class])) continue; + + $listenerData['listener']->$eventName($event); } } } diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index a5dd39cfd..0c91d8475 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -19,42 +19,59 @@ namespace Doctrine\ORM\Event; +use Doctrine\Common\EventArgs; +use Doctrine\ORM\EntityManager; + /** * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * of entities. * - * @since 2.0 + * @link www.doctrine-project.org + * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ -class LifecycleEventArgs extends \Doctrine\Common\EventArgs +class LifecycleEventArgs extends EventArgs { /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ - private $_em; + private $em; /** * @var object */ - private $_entity; + private $entity; - public function __construct($entity, $em) + /** + * Constructor + * + * @param object $entity + * @param Doctrine\ORM\EntityManager $em + */ + public function __construct($entity, EntityManager $em) { - $this->_entity = $entity; - $this->_em = $em; + $this->entity = $entity; + $this->em = $em; } + /** + * Retireve associated Entity. + * + * @return object + */ public function getEntity() { - return $this->_entity; + return $this->entity; } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { - return $this->_em; + return $this->em; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php index f00520a20..a87f45cc3 100644 --- a/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php +++ b/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php @@ -1,9 +1,25 @@ . + */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; - use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\EntityManager; @@ -11,32 +27,36 @@ use Doctrine\ORM\EntityManager; * Class that holds event arguments for a loadMetadata event. * * @author Jonathan H. Wage - * @since 2.0 + * @since 2.0 */ class LoadClassMetadataEventArgs extends EventArgs { /** - * @var ClassMetadata + * @var Doctrine\ORM\Mapping\ClassMetadata */ private $classMetadata; /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; /** - * @param ClassMetadataInfo $classMetadata - * @param EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata + * @param Doctrine\ORM\EntityManager $em */ public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em) { $this->classMetadata = $classMetadata; - $this->em = $em; + $this->em = $em; } /** - * @return ClassMetadataInfo + * Retrieve associated ClassMetadata. + * + * @return Doctrine\ORM\Mapping\ClassMetadataInfo */ public function getClassMetadata() { @@ -44,7 +64,9 @@ class LoadClassMetadataEventArgs extends EventArgs } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { diff --git a/lib/Doctrine/ORM/Event/OnClearEventArgs.php b/lib/Doctrine/ORM/Event/OnClearEventArgs.php index 60ce4b3eb..49b5e8695 100644 --- a/lib/Doctrine/ORM/Event/OnClearEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnClearEventArgs.php @@ -15,7 +15,7 @@ * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * . -*/ + */ namespace Doctrine\ORM\Event; @@ -23,16 +23,15 @@ namespace Doctrine\ORM\Event; * Provides event arguments for the onClear event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Roman Borschel * @author Benjamin Eberlei */ class OnClearEventArgs extends \Doctrine\Common\EventArgs { /** - * @var \Doctrine\ORM\EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; @@ -42,16 +41,21 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs private $entityClass; /** - * @param \Doctrine\ORM\EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em + * @param string $entityClass Optional entity class */ public function __construct($em, $entityClass = null) { - $this->em = $em; + $this->em = $em; $this->entityClass = $entityClass; } /** - * @return \Doctrine\ORM\EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { @@ -75,6 +79,6 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs */ public function clearsAllEntities() { - return $this->entityClass === null; + return ($this->entityClass === null); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php index 1b4cb9ba8..5e6e839fe 100644 --- a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php @@ -21,37 +21,45 @@ namespace Doctrine\ORM\Event; +use Doctrine\ORM\EntityManager; + /** * Provides event arguments for the preFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.com + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Roman Borschel * @author Benjamin Eberlei */ class OnFlushEventArgs extends \Doctrine\Common\EventArgs { /** - * @var EntityManager + * @var Doctirne\ORM\EntityManager */ - private $_em; + private $em; - //private $_entitiesToPersist = array(); - //private $_entitiesToRemove = array(); + //private $entitiesToPersist = array(); + //private $entitiesToRemove = array(); - public function __construct($em) + /** + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em + */ + public function __construct(EntityManager $em) { - $this->_em = $em; + $this->em = $em; } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { - return $this->_em; + return $this->em; } /* diff --git a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php index 92e88ae21..f500ad92f 100644 --- a/lib/Doctrine/ORM/Event/PostFlushEventArgs.php +++ b/lib/Doctrine/ORM/Event/PostFlushEventArgs.php @@ -17,9 +17,10 @@ * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * . -*/ + */ namespace Doctrine\ORM\Event; + use Doctrine\ORM\EntityManager; use Doctrine\Common\EventArgs; @@ -27,20 +28,21 @@ 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 + * @link www.doctrine-project.org * @since 2.0 - * @version $Revision$ * @author Daniel Freudenberger */ class PostFlushEventArgs extends EventArgs { /** - * @var EntityManager + * @var Doctrine\ORM\EntityManager */ private $em; /** - * @param EntityManager $em + * Constructor. + * + * @param Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { @@ -48,7 +50,9 @@ class PostFlushEventArgs extends EventArgs } /** - * @return EntityManager + * Retrieve associated EntityManager. + * + * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { diff --git a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php index ab1cc15de..35539591a 100644 --- a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php +++ b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php @@ -1,4 +1,23 @@ . + */ namespace Doctrine\ORM\Event; @@ -8,42 +27,50 @@ use Doctrine\Common\EventArgs, /** * Class that holds event arguments for a preInsert/preUpdate event. * + * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei - * @since 2.0 + * @since 2.0 */ class PreUpdateEventArgs extends LifecycleEventArgs { /** * @var array */ - private $_entityChangeSet; + private $entityChangeSet; /** - * + * Constructor. + * * @param object $entity - * @param EntityManager $em + * @param Doctrine\ORM\EntityManager $em * @param array $changeSet */ - public function __construct($entity, $em, array &$changeSet) + public function __construct($entity, EntityManager $em, array &$changeSet) { parent::__construct($entity, $em); - $this->_entityChangeSet = &$changeSet; - } - - public function getEntityChangeSet() - { - return $this->_entityChangeSet; + + $this->entityChangeSet = &$changeSet; } /** - * Field has a changeset? + * Retrieve entity changeset. + * + * @return array + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Check if field has a changeset. * - * @return bool + * @return boolean */ public function hasChangedField($field) { - return isset($this->_entityChangeSet[$field]); + return isset($this->entityChangeSet[$field]); } /** @@ -54,9 +81,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs */ public function getOldValue($field) { - $this->_assertValidField($field); + $this->assertValidField($field); - return $this->_entityChangeSet[$field][0]; + return $this->entityChangeSet[$field][0]; } /** @@ -67,9 +94,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs */ public function getNewValue($field) { - $this->_assertValidField($field); + $this->assertValidField($field); - return $this->_entityChangeSet[$field][1]; + return $this->entityChangeSet[$field][1]; } /** @@ -80,18 +107,24 @@ class PreUpdateEventArgs extends LifecycleEventArgs */ public function setNewValue($field, $value) { - $this->_assertValidField($field); + $this->assertValidField($field); - $this->_entityChangeSet[$field][1] = $value; + $this->entityChangeSet[$field][1] = $value; } - private function _assertValidField($field) + /** + * Assert the field exists in changeset. + * + * @param string $field + */ + private function assertValidField($field) { - if (!isset($this->_entityChangeSet[$field])) { - throw new \InvalidArgumentException( - "Field '".$field."' is not a valid field of the entity ". - "'".get_class($this->getEntity())."' in PreUpdateEventArgs." - ); + if ( ! isset($this->entityChangeSet[$field])) { + throw new \InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_class($this->getEntity()) + )); } } } From 1f06e9fca5c5f73ff63cf74ce3efea4fa77af315 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 28 Oct 2011 12:56:14 -0200 Subject: [PATCH 23/28] Fixed issue with SimpleSelectExpression containing Literals. This issue is related to a previously fixed ticket DDC-1079. --- lib/Doctrine/ORM/Query/SqlWalker.php | 143 +++++++++--------- .../ORM/Query/SelectSqlGenerationTest.php | 19 ++- 2 files changed, 88 insertions(+), 74 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 188350197..9ada21b0f 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1292,83 +1292,80 @@ class SqlWalker implements TreeWalker */ public function walkSimpleSelectExpression($simpleSelectExpression) { - $sql = ''; $expr = $simpleSelectExpression->expression; - - if ($expr instanceof AST\PathExpression) { - $sql .= $this->walkPathExpression($expr); - } else if ($expr instanceof AST\AggregateExpression) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; - } else if ($expr instanceof AST\Subselect) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else if ($expr instanceof AST\Functions\FunctionNode) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else if ( - $expr instanceof AST\SimpleArithmeticExpression || - $expr instanceof AST\ArithmeticTerm || - $expr instanceof AST\ArithmeticFactor || - $expr instanceof AST\ArithmeticPrimary - ) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else if ( - $expr instanceof AST\NullIfExpression || - $expr instanceof AST\CoalesceExpression || - $expr instanceof AST\GeneralCaseExpression || - $expr instanceof AST\SimpleCaseExpression - ) { - if ( ! $simpleSelectExpression->fieldIdentificationVariable) { - $alias = $this->_scalarResultCounter++; - } else { - $alias = $simpleSelectExpression->fieldIdentificationVariable; - } - - $columnAlias = 'sclr' . $this->_aliasCounter++; - $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; + $sql = ' '; + + switch (true) { + case ($expr instanceof AST\PathExpression): + $sql .= $this->walkPathExpression($expr); + break; - $this->_scalarResultAliasMap[$alias] = $columnAlias; - } else { - // IdentificationVariable - $class = $this->_queryComponents[$expr]['metadata']; - $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); - $first = true; + case ($expr instanceof AST\AggregateExpression): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; + break; + + case ($expr instanceof AST\Subselect): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; + break; + + case ($expr instanceof AST\Functions\FunctionNode): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; + break; + + case ($expr instanceof AST\Literal): + $sql .= $this->walkLiteral($expr); + break; + + case ($expr instanceof AST\SimpleArithmeticExpression): + case ($expr instanceof AST\ArithmeticTerm): + case ($expr instanceof AST\ArithmeticFactor): + case ($expr instanceof AST\ArithmeticPrimary): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; + break; + + case ($expr instanceof AST\NullIfExpression): + case ($expr instanceof AST\CoalesceExpression): + case ($expr instanceof AST\GeneralCaseExpression): + case ($expr instanceof AST\SimpleCaseExpression): + $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; + + $columnAlias = 'sclr' . $this->_aliasCounter++; + $this->_scalarResultAliasMap[$alias] = $columnAlias; + + $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; + break; + + default: // IdentificationVariable + $class = $this->_queryComponents[$expr]['metadata']; + $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); + $sqlParts = array(); - foreach ($class->identifier as $identifier) { - if ($first) $first = false; else $sql .= ', '; - $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); - } + foreach ($class->identifier as $identifier) { + $sqlParts[] = $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); + } + + $sql .= implode(', ', $sqlParts); + break; } - - return ' ' . $sql; + + return $sql; } /** diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 53002a8fc..6e3a52770 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -604,7 +604,24 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase . ' WHERE EXISTS (' . 'SELECT c1_.id FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' . ')' - + ); + } + + public function testExistsExpressionWithSimpleSelectReturningScalar() + { + $this->assertSqlGeneration( + // DQL + // The result of this query consists of all employees whose spouses are also employees. + 'SELECT DISTINCT emp FROM Doctrine\Tests\Models\CMS\CmsEmployee emp + WHERE EXISTS ( + SELECT 1 + FROM Doctrine\Tests\Models\CMS\CmsEmployee spouseEmp + WHERE spouseEmp = emp.spouse)', + // SQL + 'SELECT DISTINCT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_' + . ' WHERE EXISTS (' + . 'SELECT 1 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' + . ')' ); } From 3745e948c6f50c64d53fc3e8f09bef9de0be569e Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 28 Oct 2011 14:25:12 -0200 Subject: [PATCH 24/28] Made SimpleSelectExpression (Literal) be included as a scalar result. More general SQL Walker optimizations. --- lib/Doctrine/ORM/Query/SqlWalker.php | 238 +++++++++--------- .../ORM/Query/SelectSqlGenerationTest.php | 2 +- 2 files changed, 114 insertions(+), 126 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 9ada21b0f..6e6f80ce6 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -157,32 +157,24 @@ class SqlWalker implements TreeWalker */ public function getExecutor($AST) { - $isDeleteStatement = $AST instanceof AST\DeleteStatement; - $isUpdateStatement = $AST instanceof AST\UpdateStatement; - - if ($isDeleteStatement) { - $primaryClass = $this->_em->getClassMetadata( - $AST->deleteClause->abstractSchemaName - ); - - if ($primaryClass->isInheritanceTypeJoined()) { - return new Exec\MultiTableDeleteExecutor($AST, $this); - } else { - return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); - } - } else if ($isUpdateStatement) { - $primaryClass = $this->_em->getClassMetadata( - $AST->updateClause->abstractSchemaName - ); - - if ($primaryClass->isInheritanceTypeJoined()) { - return new Exec\MultiTableUpdateExecutor($AST, $this); - } else { - return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); - } + switch (true) { + case ($AST instanceof AST\DeleteStatement): + $primaryClass = $this->_em->getClassMetadata($AST->deleteClause->abstractSchemaName); + + return ($primaryClass->isInheritanceTypeJoined()) + ? new Exec\MultiTableDeleteExecutor($AST, $this) + : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); + + case ($AST instanceof AST\UpdateStatement): + $primaryClass = $this->_em->getClassMetadata($AST->updateClause->abstractSchemaName); + + return ($primaryClass->isInheritanceTypeJoined()) + ? new Exec\MultiTableUpdateExecutor($AST, $this) + : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); + + default: + return new Exec\SingleSelectExecutor($AST, $this); } - - return new Exec\SingleSelectExecutor($AST, $this); } /** @@ -1324,14 +1316,11 @@ class SqlWalker implements TreeWalker $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; break; - case ($expr instanceof AST\Literal): - $sql .= $this->walkLiteral($expr); - break; - case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\ArithmeticPrimary): + case ($expr instanceof AST\Literal): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->_scalarResultCounter++; $columnAlias = 'sclr' . $this->_aliasCounter++; @@ -1388,25 +1377,24 @@ class SqlWalker implements TreeWalker */ public function walkGroupByClause($groupByClause) { - $sql = ''; + $sqlParts = array(); + foreach ($groupByClause->groupByItems AS $groupByItem) { - if (is_string($groupByItem)) { - foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { - if ($sql != '') { - $sql .= ', '; - } - $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); - $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; - $sql .= $this->walkGroupByItem($groupByItem); - } - } else { - if ($sql != '') { - $sql .= ', '; - } - $sql .= $this->walkGroupByItem($groupByItem); + if ( ! is_string($groupByItem)) { + $sqlParts[] = $this->walkGroupByItem($groupByItem); + + continue; + } + + foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { + $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); + $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; + + $sqlParts[] = $this->walkGroupByItem($groupByItem); } } - return ' GROUP BY ' . $sql; + + return ' GROUP BY ' . implode(', ', $sqlParts); } /** @@ -1428,12 +1416,11 @@ class SqlWalker implements TreeWalker */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { - $sql = 'DELETE FROM '; - $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); - $sql .= $class->getQuotedTableName($this->_platform); - - $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable); - + $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'DELETE FROM ' . $class->getQuotedTableName($this->_platform); + + $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable; return $sql; @@ -1447,17 +1434,14 @@ class SqlWalker implements TreeWalker */ public function walkUpdateClause($updateClause) { - $sql = 'UPDATE '; - $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); - $sql .= $class->getQuotedTableName($this->_platform); - - $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable); - + $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); + $tableName = $class->getTableName(); + $sql = 'UPDATE ' . $class->getQuotedTableName($this->_platform); + + $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable); $this->_rootAliases[] = $updateClause->aliasIdentificationVariable; - $sql .= ' SET ' . implode( - ', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems) - ); + $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)); return $sql; } @@ -1473,16 +1457,21 @@ class SqlWalker implements TreeWalker $useTableAliasesBefore = $this->_useSqlTableAliases; $this->_useSqlTableAliases = false; - $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; - + $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $newValue = $updateItem->newValue; - if ($newValue === null) { - $sql .= 'NULL'; - } else if ($newValue instanceof AST\Node) { - $sql .= $newValue->dispatch($this); - } else { - $sql .= $this->_conn->quote($newValue); + switch (true) { + case ($newValue instanceof AST\Node): + $sql .= $newValue->dispatch($this); + break; + + case ($newValue === null): + $sql .= 'NULL'; + break; + + default: + $sql .= $this->_conn->quote($newValue); + break; } $this->_useSqlTableAliases = $useTableAliasesBefore; @@ -1499,12 +1488,14 @@ class SqlWalker implements TreeWalker */ public function walkWhereClause($whereClause) { - $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; + $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases); if ($condSql) { return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); - } else if ($discrSql) { + } + + if ($discrSql) { return ' WHERE ' . $discrSql; } @@ -1521,11 +1512,11 @@ class SqlWalker implements TreeWalker { // Phase 2 AST optimization: Skip processment of ConditionalExpression // if only one ConditionalTerm is defined - return ( ! ($condExpr instanceof AST\ConditionalExpression)) - ? $this->walkConditionalTerm($condExpr) - : implode( - ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) - ); + if ( ! ($condExpr instanceof AST\ConditionalExpression)) { + return $this->walkConditionalTerm($condExpr); + } + + return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)); } /** @@ -1538,11 +1529,11 @@ class SqlWalker implements TreeWalker { // Phase 2 AST optimization: Skip processment of ConditionalTerm // if only one ConditionalFactor is defined - return ( ! ($condTerm instanceof AST\ConditionalTerm)) - ? $this->walkConditionalFactor($condTerm) - : implode( - ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors) - ); + if ( ! ($condTerm instanceof AST\ConditionalTerm)) { + return $this->walkConditionalFactor($condTerm); + } + + return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)); } /** @@ -1570,7 +1561,9 @@ class SqlWalker implements TreeWalker { if ($primary->isSimpleConditionalExpression()) { return $primary->simpleConditionalExpression->dispatch($this); - } else if ($primary->isConditionalExpression()) { + } + + if ($primary->isConditionalExpression()) { $condExpr = $primary->conditionalExpression; return '(' . $this->walkConditionalExpression($condExpr) . ')'; @@ -1749,14 +1742,12 @@ class SqlWalker implements TreeWalker */ public function walkInExpression($inExpr) { - $sql = $this->walkPathExpression($inExpr->pathExpression) + $sql = $this->walkPathExpression($inExpr->pathExpression) . ($inExpr->not ? ' NOT' : '') . ' IN ('; - if ($inExpr->subselect) { - $sql .= $this->walkSubselect($inExpr->subselect); - } else { - $sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); - } + $sql .= ($inExpr->subselect) + ? $this->walkSubselect($inExpr->subselect) + : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); $sql .= ')'; @@ -1831,9 +1822,9 @@ class SqlWalker implements TreeWalker */ public function walkInParameter($inParam) { - return $inParam instanceof AST\InputParameter ? - $this->walkInputParameter($inParam) : - $this->walkLiteral($inParam); + return $inParam instanceof AST\InputParameter + ? $this->walkInputParameter($inParam) + : $this->walkLiteral($inParam); } /** @@ -1926,23 +1917,19 @@ class SqlWalker implements TreeWalker */ public function walkComparisonExpression($compExpr) { - $sql = ''; - $leftExpr = $compExpr->leftExpression; + $leftExpr = $compExpr->leftExpression; $rightExpr = $compExpr->rightExpression; - - if ($leftExpr instanceof AST\Node) { - $sql .= $leftExpr->dispatch($this); - } else { - $sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr); - } + $sql = ''; + + $sql .= ($leftExpr instanceof AST\Node) + ? $leftExpr->dispatch($this) + : (is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr)); $sql .= ' ' . $compExpr->operator . ' '; - if ($rightExpr instanceof AST\Node) { - $sql .= $rightExpr->dispatch($this); - } else { - $sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr); - } + $sql .= ($rightExpr instanceof AST\Node) + ? $rightExpr->dispatch($this) + : (is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr)); return $sql; } @@ -1981,11 +1968,11 @@ class SqlWalker implements TreeWalker */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { - return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) - ? $this->walkArithmeticTerm($simpleArithmeticExpr) - : implode( - ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms) - ); + if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) { + return $this->walkArithmeticTerm($simpleArithmeticExpr); + } + + return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)); } /** @@ -1997,22 +1984,18 @@ class SqlWalker implements TreeWalker public function walkArithmeticTerm($term) { if (is_string($term)) { - if (isset($this->_queryComponents[$term])) { - $columnName = $this->_queryComponents[$term]['token']['value']; - - return $this->_scalarResultAliasMap[$columnName]; - } - - return $term; + return (isset($this->_queryComponents[$term])) + ? $this->_scalarResultAliasMap[$this->_queryComponents[$term]['token']['value']] + : $term; } // Phase 2 AST optimization: Skip processment of ArithmeticTerm // if only one ArithmeticFactor is defined - return ( ! ($term instanceof AST\ArithmeticTerm)) - ? $this->walkArithmeticFactor($term) - : implode( - ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors) - ); + if ( ! ($term instanceof AST\ArithmeticTerm)) { + return $this->walkArithmeticFactor($term); + } + + return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)); } /** @@ -2029,10 +2012,13 @@ class SqlWalker implements TreeWalker // Phase 2 AST optimization: Skip processment of ArithmeticFactor // if only one ArithmeticPrimary is defined - return ( ! ($factor instanceof AST\ArithmeticFactor)) - ? $this->walkArithmeticPrimary($factor) - : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) - . $this->walkArithmeticPrimary($factor->arithmeticPrimary); + if ( ! ($factor instanceof AST\ArithmeticFactor)) { + return $this->walkArithmeticPrimary($factor); + } + + $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''); + + return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary); } /** @@ -2045,7 +2031,9 @@ class SqlWalker implements TreeWalker { if ($primary instanceof AST\SimpleArithmeticExpression) { return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; - } else if ($primary instanceof AST\Node) { + } + + if ($primary instanceof AST\Node) { return $primary->dispatch($this); } diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 6e3a52770..8b501a50e 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -620,7 +620,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase // SQL 'SELECT DISTINCT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_' . ' WHERE EXISTS (' - . 'SELECT 1 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' + . 'SELECT 1 AS sclr2 FROM cms_employees c1_ WHERE c1_.id = c0_.spouse_id' . ')' ); } From 7be98f475e4a8280ea4477f4a956546841c78428 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 28 Oct 2011 23:45:03 +0200 Subject: [PATCH 25/28] DDC-1410 - Remove code-inlining that caused problems --- .../ORM/Internal/Hydration/ObjectHydrator.php | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 1287a138b..5e0c9c0be 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -62,7 +62,7 @@ class ObjectHydrator extends AbstractHydrator if (!isset($this->_hints['deferEagerLoad'])) { $this->_hints['deferEagerLoad'] = true; } - + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; @@ -116,13 +116,13 @@ class ObjectHydrator extends AbstractHydrator protected function _cleanup() { $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; - + parent::_cleanup(); $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = $this->_resultPointers = array(); - + if ($eagerLoad) { $this->_em->getUnitOfWork()->triggerEagerLoads(); } @@ -192,7 +192,7 @@ class ObjectHydrator extends AbstractHydrator /** * Gets an entity instance. - * + * * @param $data The instance data. * @param $dqlAlias The DQL alias of the entity's class. * @return object The entity. @@ -205,12 +205,12 @@ class ObjectHydrator extends AbstractHydrator $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } - + if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { $class = $this->_ce[$className]; $this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } - + return $this->_uow->createEntity($className, $data, $this->_hints); } @@ -240,7 +240,7 @@ class ObjectHydrator extends AbstractHydrator * Gets a ClassMetadata instance from the local cache. * If the instance is not yet in the local cache, it is loaded into the * local cache. - * + * * @param string $className The name of the class. * @return ClassMetadata */ @@ -254,21 +254,21 @@ class ObjectHydrator extends AbstractHydrator /** * Hydrates a single row in an SQL result set. - * + * * @internal * First, the data of the row is split into chunks where each chunk contains data * that belongs to a particular component/class. Afterwards, all these chunks * are processed, one after the other. For each chunk of class data only one of the * following code paths is executed: - * + * * Path A: The data chunk belongs to a joined/associated object and the association * is collection-valued. * Path B: The data chunk belongs to a joined/associated object and the association * is single-valued. * Path C: The data chunk belongs to a root result element/object that appears in the topmost * level of the hydrated result. A typical example are the objects of the type - * specified by the FROM clause in a DQL query. - * + * specified by the FROM clause in a DQL query. + * * @param array $data The data of the row to process. * @param array $cache The cache to use. * @param array $result The result array to fill. @@ -369,10 +369,7 @@ class ObjectHydrator extends AbstractHydrator $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflField->getValue($parentObject)) { - $coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); - $coll->setOwner($parentObject, $relation); - $reflField->setValue($parentObject, $coll); - $this->_uow->setOriginalEntityProperty($oid, $relationField, $coll); + $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); } } else { // PATH B: Single-valued association From 0ec2cc557f51d6240396689e36101f62d84d2a38 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 29 Oct 2011 02:00:35 -0200 Subject: [PATCH 26/28] Implemented support to entities with association marked as @Id support in many situations. Fixed DDC-1435. --- .../ORM/Mapping/ClassMetadataInfo.php | 72 ++++++++++++++----- lib/Doctrine/ORM/Query/SqlWalker.php | 20 +++--- .../Tests/Models/DDC117/DDC117Article.php | 1 + .../ORM/Query/SelectSqlGenerationTest.php | 11 +++ 4 files changed, 75 insertions(+), 29 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 25a3350d2..10f7bc4b6 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1111,25 +1111,25 @@ class ClassMetadataInfo implements ClassMetadata */ public function getIdentifierColumnNames() { - if ($this->isIdentifierComposite) { - $columnNames = array(); - foreach ($this->identifier as $idField) { - if (isset($this->associationMappings[$idField])) { - // no composite pk as fk entity assumption: - $columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name']; - } else { - $columnNames[] = $this->fieldMappings[$idField]['columnName']; - } + $columnNames = array(); + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $columnNames[] = $this->fieldMappings[$idProperty]['columnName']; + + continue; } - return $columnNames; - } else if(isset($this->fieldMappings[$this->identifier[0]])) { - return array($this->fieldMappings[$this->identifier[0]]['columnName']); - } else { - // no composite pk as fk entity assumption: - return array($this->associationMappings[$this->identifier[0]]['joinColumns'][0]['name']); + + // Association defined as Id field + $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; + $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns); + + $columnNames = array_merge($columnNames, $assocColumnNames); } + + return $columnNames; } - + /** * Sets the type of Id generator to use for the mapped class. */ @@ -1904,6 +1904,42 @@ class ClassMetadataInfo implements ClassMetadata return $this->name; } + /** + * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. + * + * @param AbstractPlatform $platform + * @return array + */ + public function getQuotedIdentifierColumnNames($platform) + { + $quotedColumnNames = array(); + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted']) + ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName']) + : $this->fieldMappings[$idProperty]['columnName']; + + continue; + } + + // Association defined as Id field + $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; + $assocQuotedColumnNames = array_map( + function ($joinColumn) { + return isset($joinColumn['quoted']) + ? $platform->quoteIdentifier($joinColumn['name']) + : $joinColumn['name']; + }, + $joinColumns + ); + + $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); + } + + return $quotedColumnNames; + } + /** * Gets the (possibly quoted) column name of a mapped field for safe use * in an SQL statement. @@ -1914,7 +1950,9 @@ class ClassMetadataInfo implements ClassMetadata */ public function getQuotedColumnName($field, $platform) { - return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName']; + return isset($this->fieldMappings[$field]['quoted']) + ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) + : $this->fieldMappings[$field]['columnName']; } /** diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 6e6f80ce6..8939d198e 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -248,10 +248,9 @@ class SqlWalker implements TreeWalker $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { if ($first) $first = false; else $sql .= ' AND '; - $columnName = $class->getQuotedColumnName($idField, $this->_platform); $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } } @@ -264,10 +263,9 @@ class SqlWalker implements TreeWalker $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { if ($first) $first = false; else $sql .= ' AND '; - $columnName = $class->getQuotedColumnName($idField, $this->_platform); $sql .= $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } } @@ -1346,8 +1344,8 @@ class SqlWalker implements TreeWalker $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); $sqlParts = array(); - foreach ($class->identifier as $identifier) { - $sqlParts[] = $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { + $sqlParts[] = $tableAlias . '.' . $columnName; } $sql .= implode(', ', $sqlParts); @@ -1634,12 +1632,11 @@ class SqlWalker implements TreeWalker $sql .= ' AND '; $first = true; - foreach ($targetClass->identifier as $idField) { + foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { if ($first) $first = false; else $sql .= ' AND '; $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); - $sql .= $targetTableAlias . '.' - . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; + $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?'; } } else { // many-to-many $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); @@ -1684,12 +1681,11 @@ class SqlWalker implements TreeWalker $sql .= ' AND '; $first = true; - foreach ($targetClass->identifier as $idField) { + foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { if ($first) $first = false; else $sql .= ' AND '; $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); - $sql .= $targetTableAlias . '.' - . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; + $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?'; } } diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php index 51ea2278d..70b1c6353 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Article.php @@ -9,6 +9,7 @@ class DDC117Article { /** @Id @Column(type="integer", name="article_id") @GeneratedValue */ private $id; + /** @Column */ private $title; diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 8b501a50e..6589890fc 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1268,6 +1268,17 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase array(Query::HINT_FORCE_PARTIAL_LOAD => false) ); } + + /** + * @group DDC-1435 + */ + public function testForeignKeyAsPrimaryKeySubselect() + { + $this->assertSqlGeneration( + "SELECT s FROM Doctrine\Tests\Models\DDC117\DDC117Article s WHERE EXISTS (SELECT r FROM Doctrine\Tests\Models\DDC117\DDC117Reference r WHERE r.source = s)", + "SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)" + ); + } } From e39bfced4a584ca967fce079385df3d57e176d54 Mon Sep 17 00:00:00 2001 From: Adrien BRAULT Date: Sat, 29 Oct 2011 13:40:01 +0200 Subject: [PATCH 27/28] Fix iterate method doc return type --- lib/Doctrine/ORM/AbstractQuery.php | 2 +- lib/Doctrine/ORM/Query.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 0f28e71db..72eab194b 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -545,7 +545,7 @@ abstract class AbstractQuery * * @param array $params The query parameters. * @param integer $hydrationMode The hydration mode to use. - * @return IterableResult + * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate(array $params = array(), $hydrationMode = null) { diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 293c64391..ac1eb75d7 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -526,7 +526,7 @@ final class Query extends AbstractQuery * * @param array $params The query parameters. * @param integer $hydrationMode The hydration mode to use. - * @return IterableResult + * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) { From 2c036b3185c4da38fb447b9d576e4219fe6af835 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 30 Oct 2011 08:25:19 +0100 Subject: [PATCH 28/28] Fix glitch in testsuites tearDown() --- .../ORM/Functional/EntityRepositoryTest.php | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php index 4f8e11420..719a9f993 100644 --- a/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/EntityRepositoryTest.php @@ -20,7 +20,9 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function tearDown() { - $this->_em->getConfiguration()->setEntityNamespaces(array()); + if ($this->_em) { + $this->_em->getConfiguration()->setEntityNamespaces(array()); + } parent::tearDown(); } @@ -78,7 +80,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase return array($user->id, $address->id); } - + public function buildUser($name, $username, $status, $address) { $user = new CmsUser(); @@ -89,10 +91,10 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($user); $this->_em->flush(); - + return $user; } - + public function buildAddress($country, $city, $street, $zip) { $address = new CmsAddress(); @@ -103,7 +105,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->persist($address); $this->_em->flush(); - + return $address; } @@ -134,22 +136,22 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase { $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); - + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); - + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); - + unset($address1); unset($address2); unset($address3); - + $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $addresses = $repository->findBy(array('user' => array($user1->getId(), $user2->getId()))); - + $this->assertEquals(2, count($addresses)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); } @@ -158,22 +160,22 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase { $address1 = $this->buildAddress('Germany', 'Berlim', 'Foo st.', '123456'); $user1 = $this->buildUser('Benjamin', 'beberlei', 'dev', $address1); - + $address2 = $this->buildAddress('Brazil', 'São Paulo', 'Bar st.', '654321'); $user2 = $this->buildUser('Guilherme', 'guilhermeblanco', 'freak', $address2); - + $address3 = $this->buildAddress('USA', 'Nashville', 'Woo st.', '321654'); $user3 = $this->buildUser('Jonathan', 'jwage', 'dev', $address3); - + unset($address1); unset($address2); unset($address3); - + $this->_em->clear(); - + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); $addresses = $repository->findBy(array('user' => array($user1, $user2))); - + $this->assertEquals(2, count($addresses)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress',$addresses[0]); } @@ -189,7 +191,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Guilherme', $users[0]->name); $this->assertEquals('dev', $users[0]->status); } - + public function testFindAll() { $user1Id = $this->loadFixture(); @@ -280,7 +282,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $userId = $user->id; $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId); - + $this->setExpectedException('Doctrine\ORM\OptimisticLockException'); $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC); } @@ -423,7 +425,7 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testFindByLimitOffset() { $this->loadFixture(); - + $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $users1 = $repos->findBy(array(), null, 1, 0); @@ -451,8 +453,8 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertSame($usersAsc[0], $usersDesc[2]); $this->assertSame($usersAsc[2], $usersDesc[0]); } - - + + /** * @group DDC-753 */ @@ -465,19 +467,19 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithDefaultCustomRepository'); $this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753DefaultRepository", $repos); $this->assertTrue($repos->isDefaultRepository()); - - + + $repos = $this->_em->getRepository('Doctrine\Tests\Models\DDC753\DDC753EntityWithCustomRepository'); $this->assertInstanceOf("Doctrine\Tests\Models\DDC753\DDC753CustomRepository", $repos); $this->assertTrue($repos->isCustomRepository()); - + $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\Tests\Models\DDC753\DDC753DefaultRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\ORM\EntityRepository"); $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository"); } - - + + /** * @group DDC-753 * @expectedException Doctrine\ORM\ORMException @@ -488,6 +490,6 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($this->_em->getConfiguration()->getDefaultRepositoryClassName(), "Doctrine\ORM\EntityRepository"); $this->_em->getConfiguration()->setDefaultRepositoryClassName("Doctrine\Tests\Models\DDC753\DDC753InvalidRepository"); } - + }