From c144df9be39fc780be77d043fc437a5335f15d79 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 26 Feb 2011 12:47:59 +0100 Subject: [PATCH] DDC-1033 - Fix cloning of not initialized proxies. --- .../ORM/Persisters/BasicEntityPersister.php | 8 +++ lib/Doctrine/ORM/Proxy/ProxyFactory.php | 24 +++++++-- .../Models/ECommerce/ECommerceProduct.php | 7 +++ .../ORM/Functional/ReferenceProxyTest.php | 54 +++++++++++++++++-- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 56e91e49a..c9ae5ddd9 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -172,6 +172,14 @@ class BasicEntityPersister $this->_platform = $this->_conn->getDatabasePlatform(); } + /** + * @return Doctrine\ORM\Mapping\ClassMetadata + */ + public function getClassMetadata() + { + return $this->_class; + } + /** * Adds an entity to the queued insertions. * The entity remains queued until {@link executeInserts} is invoked. diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 8be75d996..956b06503 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -130,11 +130,12 @@ class ProxyFactory { $methods = $this->_generateMethods($class); $sleepImpl = $this->_generateSleep($class); + $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive $placeholders = array( '', '', '', - '', '' + '', '', '' ); if(substr($class->name, 0, 1) == "\\") { @@ -146,7 +147,7 @@ class ProxyFactory $replacements = array( $this->_proxyNamespace, $proxyClassName, $className, - $methods, $sleepImpl + $methods, $sleepImpl, $cloneImpl ); $file = str_replace($placeholders, $replacements, $file); @@ -166,7 +167,7 @@ class ProxyFactory foreach ($class->reflClass->getMethods() as $method) { /* @var $method ReflectionMethod */ - if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") { + if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone"))) { continue; } @@ -285,5 +286,22 @@ class extends \ implements \Doctrine\ORM\Proxy\Proxy { } + + public function __clone() + { + if (!$this->__isInitialized__ && $this->_entityPersister) { + $this->__isInitialized__ = true; + $class = $this->_entityPersister->getClassMetadata(); + $original = $this->_entityPersister->load($this->_identifier); + if ($original === null) { + throw new \Doctrine\ORM\EntityNotFoundException(); + } + foreach ($class->reflFields AS $field => $reflProperty) { + $reflProperty->setValue($this, $reflProperty->getValue($original)); + } + unset($this->_entityPersister, $this->_identifier); + } + + } }'; } diff --git a/tests/Doctrine/Tests/Models/ECommerce/ECommerceProduct.php b/tests/Doctrine/Tests/Models/ECommerce/ECommerceProduct.php index 75f0f17c6..198e16720 100644 --- a/tests/Doctrine/Tests/Models/ECommerce/ECommerceProduct.php +++ b/tests/Doctrine/Tests/Models/ECommerce/ECommerceProduct.php @@ -55,6 +55,8 @@ class ECommerceProduct */ private $related; + public $isCloned = false; + public function __construct() { $this->features = new ArrayCollection; @@ -159,4 +161,9 @@ class ECommerceProduct $related->removeRelated($this); } } + + public function __clone() + { + $this->isCloned = true; + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index f144c7885..d37442171 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -26,7 +26,7 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase true); } - public function testLazyLoadsFieldValuesFromDatabase() + public function createProduct() { $product = new ECommerceProduct(); $product->setName('Doctrine Cookbook'); @@ -34,8 +34,13 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - - $id = $product->getId(); + + return $product->getId(); + } + + public function testLazyLoadsFieldValuesFromDatabase() + { + $id = $this->createProduct(); $productProxy = $this->_factory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceProduct', array('id' => $id)); $this->assertEquals('Doctrine Cookbook', $productProxy->getName()); @@ -46,9 +51,50 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testAccessMetatadaForProxy() { - $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , 1); + $id = $this->createProduct(); + + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); $class = $this->_em->getClassMetadata(get_class($entity)); $this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $class->name); } + + /** + * @group DDC-1033 + */ + public function testReferenceFind() + { + $id = $this->createProduct(); + + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); + $entity2 = $this->_em->find('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); + + $this->assertSame($entity, $entity2); + $this->assertEquals('Doctrine Cookbook', $entity2->getName()); + } + + /** + * @group DDC-1033 + */ + public function testCloneProxy() + { + $id = $this->createProduct(); + + /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */ + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); + + /* @var $clone Doctrine\Tests\Models\ECommerce\ECommerceProduct */ + $clone = clone $entity; + + $this->assertEquals($id, $entity->getId()); + $this->assertEquals('Doctrine Cookbook', $entity->getName()); + + $this->assertFalse($this->_em->contains($clone), "Cloning a reference proxy should return an unmanaged/detached entity."); + $this->assertEquals($id, $clone->getId(), "Cloning a reference proxy should return same id."); + $this->assertEquals('Doctrine Cookbook', $clone->getName(), "Cloning a reference proxy should return same product name."); + + // domain logic, Product::__clone sets isCloned public property + $this->assertTrue($clone->isCloned); + $this->assertFalse($entity->isCloned); + } }