diff --git a/lib/Doctrine/ORM/Proxy/Proxy.php b/lib/Doctrine/ORM/Proxy/Proxy.php
index 5eaff19fe..09e2b33ef 100644
--- a/lib/Doctrine/ORM/Proxy/Proxy.php
+++ b/lib/Doctrine/ORM/Proxy/Proxy.php
@@ -1,7 +1,5 @@
* @since 2.0
*/
-interface Proxy {}
\ No newline at end of file
+interface Proxy extends BaseProxy {}
diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php
index cf7048549..958a0c7e6 100644
--- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php
+++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php
@@ -21,7 +21,8 @@ namespace Doctrine\ORM\Proxy;
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Mapping\ClassMetadata,
- Doctrine\ORM\Mapping\AssociationMapping;
+ Doctrine\ORM\Mapping\AssociationMapping,
+ Doctrine\Common\Util\ClassUtils;
/**
* This factory is used to create proxy objects for entities at runtime.
@@ -41,6 +42,14 @@ class ProxyFactory
/** The directory that contains all proxy classes. */
private $_proxyDir;
+ /**
+ * Used to match very simple id methods that don't need
+ * to be proxied since the identifier is known.
+ *
+ * @var string
+ */
+ const PATTERN_MATCH_ID_METHOD = '((public\s)?(function\s{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})i';
+
/**
* Initializes a new instance of the ProxyFactory class that is
* connected to the given EntityManager.
@@ -74,13 +83,12 @@ class ProxyFactory
*/
public function getProxy($className, $identifier)
{
- $proxyClassName = str_replace('\\', '', $className) . 'Proxy';
- $fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
+ $fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace);
if (! class_exists($fqn, false)) {
- $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
+ $fileName = $this->getProxyFileName($className);
if ($this->_autoGenerate) {
- $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
+ $this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate);
}
require $fileName;
}
@@ -94,6 +102,17 @@ class ProxyFactory
return new $fqn($entityPersister, $identifier);
}
+ /**
+ * Generate the Proxy file name
+ *
+ * @param string $className
+ * @return string
+ */
+ private function getProxyFileName($className)
+ {
+ return $this->_proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
+ }
+
/**
* Generates proxy classes for all given classes.
*
@@ -112,9 +131,8 @@ class ProxyFactory
continue;
}
- $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
- $proxyFileName = $proxyDir . $proxyClassName . '.php';
- $this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
+ $proxyFileName = $this->getProxyFileName($class->name);
+ $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
}
}
@@ -122,11 +140,10 @@ class ProxyFactory
* Generates a proxy class file.
*
* @param $class
- * @param $originalClassName
* @param $proxyClassName
* @param $file The path of the file to write to.
*/
- private function _generateProxyClass($class, $proxyClassName, $fileName, $file)
+ private function _generateProxyClass($class, $fileName, $file)
{
$methods = $this->_generateMethods($class);
$sleepImpl = $this->_generateSleep($class);
@@ -138,16 +155,19 @@ class ProxyFactory
'', '', ''
);
- if(substr($class->name, 0, 1) == "\\") {
- $className = substr($class->name, 1);
- } else {
- $className = $class->name;
- }
+ $className = ltrim($class->name, '\\');
+ $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace);
+ $parts = explode('\\', strrev($proxyClassName), 2);
+ $proxyClassNamespace = strrev($parts[1]);
+ $proxyClassName = strrev($parts[0]);
$replacements = array(
- $this->_proxyNamespace,
- $proxyClassName, $className,
- $methods, $sleepImpl, $cloneImpl
+ $proxyClassNamespace,
+ $proxyClassName,
+ $className,
+ $methods,
+ $sleepImpl,
+ $cloneImpl
);
$file = str_replace($placeholders, $replacements, $file);
@@ -230,6 +250,14 @@ class ProxyFactory
}
/**
+ * Check if the method is a short identifier getter.
+ *
+ * What does this mean? For proxy objects the identifier is already known,
+ * however accessing the getter for this identifier usually triggers the
+ * lazy loading, leading to a query that may not be necessary if only the
+ * ID is interesting for the userland code (for example in views that
+ * generate links to the entity, but do not display anything else).
+ *
* @param ReflectionMethod $method
* @param ClassMetadata $class
* @return bool
@@ -237,7 +265,7 @@ class ProxyFactory
private function isShortIdentifierGetter($method, $class)
{
$identifier = lcfirst(substr($method->getName(), 3));
- return (
+ $cheapCheck = (
$method->getNumberOfParameters() == 0 &&
substr($method->getName(), 0, 3) == "get" &&
in_array($identifier, $class->identifier, true) &&
@@ -245,6 +273,18 @@ class ProxyFactory
(($method->getEndLine() - $method->getStartLine()) <= 4)
&& in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
);
+
+ if ($cheapCheck) {
+ $code = file($class->reflClass->getFileName());
+ $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
+
+ $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
+
+ if (preg_match($pattern, $code)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -318,6 +358,12 @@ class extends \ implements \Doctrine\ORM\Proxy\Proxy
}
}
+ /** @private */
+ public function __isInitialized()
+ {
+ return $this->__isInitialized__;
+ }
+
public function __sleep()
diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php
index 9cd216066..676d41878 100644
--- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php
@@ -195,4 +195,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('Doctrine Cookbook', $entity->getName());
$this->assertTrue($entity->__isInitialized__, "Getting something other than the identifier initializes the proxy.");
}
+
+ /**
+ * @group DDC-1604
+ */
+ public function testCommonPersistenceProxy()
+ {
+ $id = $this->createProduct();
+
+ /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */
+ $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id);
+ $className = \Doctrine\Common\Util\ClassUtils::getClass($entity);
+
+ $this->assertInstanceOf('Doctrine\Common\Persistence\Proxy', $entity);
+ $this->assertFalse($entity->__isInitialized());
+ $this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $className);
+
+ $restName = str_replace($this->_em->getConfiguration()->getProxyNamespace(), "", get_class($entity));
+ $restName = substr(get_class($entity), strlen($this->_em->getConfiguration()->getProxyNamespace()) +1);
+ $proxyFileName = $this->_em->getConfiguration()->getProxyDir() . DIRECTORY_SEPARATOR . str_replace("\\", "", $restName) . ".php";
+ $this->assertTrue(file_exists($proxyFileName), "Proxy file name cannot be found generically.");
+
+ $entity->__load();
+ $this->assertTrue($entity->__isInitialized());
+ }
}
diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php
index a45ca2c1f..ace2bb57b 100644
--- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php
+++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php
@@ -52,7 +52,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyDelegatesLoadingToThePersister()
{
$identifier = array('id' => 42);
- $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
+ $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
@@ -69,7 +69,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyExecutesLoadingOnlyOnce()
{
$identifier = array('id' => 42);
- $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
+ $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
@@ -108,7 +108,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne()
{
- $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
+ $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'));
}
@@ -125,7 +125,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
require_once dirname(__FILE__)."/fixtures/NonNamespacedProxies.php";
$className = "\DoctrineOrmTestEntity";
- $proxyName = "DoctrineOrmTestEntityProxy";
+ $proxyName = "DoctrineOrmTestEntity";
$classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className);
$classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer'));
@@ -133,16 +133,16 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->_proxyFactory->generateProxyClasses(array($classMetadata));
- $classCode = file_get_contents(dirname(__FILE__)."/generated/".$proxyName.".php");
+ $classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php");
- $this->assertNotContains("class DoctrineOrmTestEntityProxy extends \\\\DoctrineOrmTestEntity", $classCode);
- $this->assertContains("class DoctrineOrmTestEntityProxy extends \\DoctrineOrmTestEntity", $classCode);
+ $this->assertNotContains("class DoctrineOrmTestEntity extends \\\\DoctrineOrmTestEntity", $classCode);
+ $this->assertContains("class DoctrineOrmTestEntity extends \\DoctrineOrmTestEntity", $classCode);
}
public function testClassWithSleepProxyGeneration()
{
$className = "\Doctrine\Tests\ORM\Proxy\SleepClass";
- $proxyName = "DoctrineTestsORMProxySleepClassProxy";
+ $proxyName = "DoctrineTestsORMProxySleepClass";
$classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className);
$classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer'));
@@ -150,7 +150,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$this->_proxyFactory->generateProxyClasses(array($classMetadata));
- $classCode = file_get_contents(dirname(__FILE__)."/generated/".$proxyName.".php");
+ $classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php");
$this->assertEquals(1, substr_count($classCode, 'function __sleep'));
}