Commit 52d55da3 authored by piccoloprincipe's avatar piccoloprincipe

[2.0] working implementation of Association Proxy classes

parent 66f377fb
......@@ -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 <type> $entity
* @param <type> $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) {}
}
......@@ -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(
'<proxyClassName>', '<className>',
'<methods>', '<sleepImpl>'
);
$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 =
......
......@@ -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;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment