From 52d55da356fde3b8da5bc4e17206c4367e730577 Mon Sep 17 00:00:00 2001 From: piccoloprincipe Date: Fri, 17 Jul 2009 13:35:44 +0000 Subject: [PATCH] [2.0] working implementation of Association Proxy classes --- .../ORM/Mapping/AssociationMapping.php | 12 ++- .../ORM/Proxy/ProxyClassGenerator.php | 100 ++++-------------- .../ORM/Proxy/ProxyClassGeneratorTest.php | 90 +++++++++++++--- 3 files changed, 108 insertions(+), 94 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index 6bce92d63..378a0f56a 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -378,9 +378,13 @@ abstract class AssociationMapping } /** + * Loads data in $targetEntity domain object using this association. + * The data comes from the association navigated from $owningEntity + * using $em. * - * @param $entity - * @param $entityManager + * @param object $owningEntity + * @param object $targetEntity + * @param EntityManager $em */ - /*abstract public function load($entity, $em) {}*/ -} \ No newline at end of file + public function load($owningEntity, $targetEntity, $em) {} +} diff --git a/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php b/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php index d15b668b3..c93b5f710 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php +++ b/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php @@ -64,9 +64,30 @@ class ProxyClassGenerator public function generateReferenceProxyClass($originalClassName) { $proxyClassName = str_replace('\\', '_', $originalClassName) . 'RProxy'; - //$proxyClassName = $originalClassName . 'RProxy'; + + return $this->_generateClass($originalClassName, $proxyClassName, self::$_proxyClassTemplate); + } + + /** + * Generates an association proxy class. + * This is a proxy class for an object which we have the association where + * it is involved, but no primary key to retrieve it. + * + * @param string $originalClassName + * @return string the proxy class name + */ + public function generateAssociationProxyClass($originalClassName) + { + $proxyClassName = str_replace('\\', '_', $originalClassName) . 'AProxy'; + + return $this->_generateClass($originalClassName, $proxyClassName, self::$_assocProxyClassTemplate); + } + + protected function _generateClass($originalClassName, $proxyClassName, $file) + { + $class = $this->_em->getClassMetadata($originalClassName); $proxyFullyQualifiedClassName = self::$_ns . $proxyClassName; - + if (class_exists($proxyFullyQualifiedClassName, false)) { return $proxyFullyQualifiedClassName; } @@ -80,7 +101,6 @@ class ProxyClassGenerator return $proxyFullyQualifiedClassName; } - $file = self::$_proxyClassTemplate; $methods = $this->_generateMethods($class); $sleepImpl = $this->_generateSleep($class); @@ -96,7 +116,6 @@ class ProxyClassGenerator file_put_contents($fileName, $file); require $fileName; - return $proxyFullyQualifiedClassName; } @@ -167,79 +186,6 @@ class ProxyClassGenerator return $sleepImpl; } - /** - * Generates a proxy class. - * This is a proxy class for an object which we have the association where - * it is involved, but no primary key to retrieve it. - * - * @param string $originalClassName - * @param string $proxyClassName - */ - public function generateAssociationProxyClass($originalClassName, $proxyClassName) - { - $class = $this->_em->getClassMetadata($originalClassName); - $file = self::$_assocProxyClassTemplate; - - $methods = ''; - - foreach ($class->reflClass->getMethods() as $method) { - if ($method->isPublic() && ! $method->isFinal()) { - $methods .= PHP_EOL . 'public function ' . $method->getName() . '('; - $firstParam = true; - $parameterString = ''; - - foreach ($method->getParameters() as $param) { - if ($firstParam) { - $firstParam = false; - } else { - $parameterString .= ', '; - } - - $parameterString .= '$' . $param->getName(); - } - - $methods .= $parameterString . ') {' . PHP_EOL; - $methods .= '$this->_load();' . PHP_EOL; - $methods .= 'return parent::' . $method->getName() . '(' . $parameterString . ');'; - $methods .= '}' . PHP_EOL; - } - } - - $sleepImpl = ''; - - if ($class->reflClass->hasMethod('__sleep')) { - $sleepImpl .= 'return parent::__sleep();'; - } else { - $sleepImpl .= 'return array('; - $first = true; - - foreach ($class->getReflectionProperties() as $name => $prop) { - if ($first) { - $first = false; - } else { - $sleepImpl .= ', '; - } - - $sleepImpl .= "'" . $name . "'"; - } - - $sleepImpl .= ');'; - } - - $placeholders = array( - '', '', - '', '' - ); - $replacements = array( - $proxyClassName, $originalClassName, $methods, $sleepImpl - ); - - $file = str_replace($placeholders, $replacements, $file); - - file_put_contents($fileName, $file); - - return $fileName; - } /** Proxy class code template */ private static $_proxyClassTemplate = diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php index dca11547e..3f03a1c9d 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php @@ -8,8 +8,8 @@ use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Models\ECommerce\ECommerceCart; use Doctrine\Tests\Models\ECommerce\ECommerceCustomer; use Doctrine\Tests\Models\ECommerce\ECommerceFeature; +use Doctrine\Tests\Models\ECommerce\ECommerceProduct; use Doctrine\Tests\Models\ECommerce\ECommerceShipping; -use Doctrine\ORM\Persisters\StandardEntityPersister; require_once __DIR__ . '/../../TestInit.php'; @@ -41,12 +41,6 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase } } - public function testCreatesASubclassOfTheOriginalOne() - { - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceFeature')); - } - public function testCanGuessADefaultTempFolder() { $generator = new ProxyClassGenerator($this->_emMock); @@ -54,26 +48,32 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceShipping')); } - public function testAllowsClassesWithAConstructor() + public function testCreatesReferenceProxyAsSubclassOfTheOriginalOne() + { + $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceFeature')); + } + + public function testAllowsReferenceProxyForClassesWithAConstructor() { $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceCart'); $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceCart')); } - public function testAllowsIdempotentCreationOfProxyClass() + public function testAllowsIdempotentCreationOfReferenceProxyClass() { $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $theSameProxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $this->assertEquals($proxyClass, $theSameProxyClass); } - public function testCreatesClassesThatRequirePersisterInTheConstructor() + public function testReferenceProxyRequiresPersisterInTheConstructor() { $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $proxy = new $proxyClass($this->_getMockPersister(), null); } - public function testCreatesClassesThatDelegateLoadingToThePersister() + public function testReferenceProxyDelegatesLoadingToThePersister() { $identifier = array('id' => 42); $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); @@ -85,7 +85,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $proxy->getDescription(); } - public function testCreatesClassesThatExecutesLoadingOnlyOnce() + public function testReferenceProxyExecutesLoadingOnlyOnce() { $identifier = array('id' => 42); $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); @@ -101,7 +101,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase /** * @expectedException PHPUnit_Framework_Error */ - public function testRespectsMethodsParametersTypeHinting() + public function testReferenceProxyRespectsMethodsParametersTypeHinting() { $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $proxy = new $proxyClass($this->_getMockPersister(), null); @@ -113,4 +113,68 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $persister = $this->getMock('Doctrine\ORM\Persisters\StandardEntityPersister', array('load'), array(), '', false); return $persister; } + + public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne() + { + $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceFeature')); + } + + public function testAllowsAssociationProxyOfClassesWithAConstructor() + { + $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceCart'); + $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceCart')); + } + + public function testAllowsIdempotentCreationOfAssociationProxyClass() + { + $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $theSameProxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $this->assertEquals($proxyClass, $theSameProxyClass); + } + + public function testAllowsConcurrentCreationOfBothProxyTypes() + { + $referenceProxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $associationProxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $this->assertNotEquals($referenceProxyClass, $associationProxyClass); + } + + public function testAssociationProxyRequiresEntityManagerAndAssociationAndOwnerInTheConstructor() + { + $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $product = new ECommerceProduct; + $proxy = new $proxyClass($this->_emMock, $this->_getAssociationMock(), $product); + } + + public function testAssociationProxyDelegatesLoadingToTheAssociation() + { + $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $product = new ECommerceProduct; + $assoc = $this->_getAssociationMock(); + $proxy = new $proxyClass($this->_emMock, $assoc, $product); + $assoc->expects($this->any()) + ->method('load') + ->with($product, $this->isInstanceOf($proxyClass), $this->isInstanceOf('Doctrine\Tests\Mocks\EntityManagerMock')); + $proxy->getDescription(); + } + + public function testAssociationProxyExecutesLoadingOnlyOnce() + { + $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $assoc = $this->_getAssociationMock(); + $proxy = new $proxyClass($this->_emMock, $assoc, null); + $assoc->expects($this->once()) + ->method('load'); + + $proxy->getDescription(); + $proxy->getDescription(); + } + + + protected function _getAssociationMock() + { + $assoc = $this->getMock('Doctrine\ORM\Mapping\AssociationMapping', array('load'), array(), '', false); + return $assoc; + } }