[2.0] working implementation of Association Proxy classes
This commit is contained in:
parent
66f377fb12
commit
52d55da356
3 changed files with 108 additions and 94 deletions
|
@ -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) {}*/
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue