Skip to content
Browse files

Update ProxyFactory to match ORM latest.

  • Loading branch information...
1 parent 6b0c0bf commit e0bfdb34318d9ecb3f6aaba2dc7fde9ef31b30fe @jwage jwage committed Feb 6, 2012
View
7 lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
@@ -21,6 +21,7 @@
use Doctrine\ODM\MongoDB\MongoDBException,
Doctrine\ODM\MongoDB\LockException,
+ Doctrine\ODM\MongoDB\Proxy\Proxy,
ReflectionClass;
/**
@@ -1285,6 +1286,9 @@ public function setIdentifierValue($document, $id)
*/
public function getIdentifierValue($document)
{
+ if ($document instanceof Proxy) {
+ return $document->__identifier__;
+ }
return (string) $this->reflFields[$this->identifier]->getValue($document);
}
@@ -1336,6 +1340,9 @@ public function setFieldValue($document, $field, $value)
*/
public function getFieldValue($document, $field)
{
+ if ($field === $this->identifier) {
+ return $document->__identifier__;
+ }
return $this->reflFields[$field]->getValue($document);
}
View
8 lib/Doctrine/ODM/MongoDB/Proxy/ProxyException.php
@@ -41,13 +41,13 @@ public static function proxyDirectoryRequired()
return new self("You must configure a proxy directory. See docs for details");
}
- public static function proxyNamespaceRequired()
+ public static function proxyDirectoryNotWritable()
{
- return new self("You must configure a proxy namespace. See docs for details");
+ return new self("Your proxy directory must be writable.");
}
- public static function proxyDirectoryMustExist()
+ public static function proxyNamespaceRequired()
{
- return new self("You must create a proxy directory specified");
+ return new self("You must configure a proxy namespace. See docs for details");
}
}
View
175 lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php
@@ -21,7 +21,7 @@
use Doctrine\ODM\MongoDB\DocumentManager,
Doctrine\ODM\MongoDB\Mapping\ClassMetadata,
- Doctrine\ODM\MongoDB\Mapping\AssociationMapping;
+ Doctrine\Common\Util\ClassUtils;
@leek
leek added a note Feb 7, 2012

What is the correct branch/tag to use for this repository to be compatible with 2.0.x? ClassUtils doesn't exist in Doctrine Common 2.1.4.

@stof
Doctrine member
stof added a note Feb 7, 2012

Doctrine Common 2.2

@leek
leek added a note Feb 7, 2012

So I have to version deps to a specific commit hash in order to get it to work with Symfony Standard 2.0.10?

@stof
Doctrine member
stof added a note Feb 7, 2012

well, this is what deps.lock is about

@jwage
Doctrine member
jwage added a note Feb 7, 2012

You should be able to upgrade common without too too much pain.

@stof
Doctrine member
stof added a note Feb 8, 2012

if he does not use the ORM, yes. Otherwise, he will have to update the ORM to 2.2 as well

@Inmovilizame
Inmovilizame added a note Feb 9, 2012

Maybe could be usefull to set a tag for the previous commit? :)

@sebhoerl
sebhoerl added a note Feb 15, 2012

If you update the vendors in Symfony 1.0.9 with the options described at http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html and DoctrineMongoDBBundle locked at version v2.0.0 there are also error messages.
Is there a list of compatible versions/commits of DoctrineMongoDBBundle, doctrine-mongodb-odm, doctrine-mongodb and the other components?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
/**
* This factory is used to create proxy objects for documents at runtime.
@@ -44,6 +44,14 @@ class ProxyFactory
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 <tt>ProxyFactory</tt> class that is
* connected to the given <tt>DocumentManager</tt>.
*
@@ -76,13 +84,12 @@ public function __construct(DocumentManager $dm, $proxyDir, $proxyNs, $autoGener
*/
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->dm->getClassMetadata($className), $proxyClassName, $fileName, self::$proxyClassTemplate);
+ $this->generateProxyClass($this->dm->getClassMetadata($className), $fileName, self::$proxyClassTemplate);
}
require $fileName;
}
@@ -97,6 +104,17 @@ public function getProxy($className, $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.
*
* @param array $classes The classes (ClassMetadata instances) for which to generate proxies.
@@ -109,46 +127,63 @@ public function generateProxyClasses(array $classes, $toDir = null)
$proxyDir = $toDir ?: $this->proxyDir;
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
foreach ($classes as $class) {
- $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
- $proxyFileName = $proxyDir . $proxyClassName . '.php';
- $this->generateProxyClass($class, $proxyClassName, $proxyFileName, self::$proxyClassTemplate);
+ /* @var $class ClassMetadata */
+ if ($class->isMappedSuperclass) {
+ continue;
+ }
+
+ $proxyFileName = $this->getProxyFileName($class->name);
+ $this->generateProxyClass($class, $proxyFileName, self::$proxyClassTemplate);
}
}
/**
* 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);
+ $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
$placeholders = array(
'<namespace>',
'<proxyClassName>', '<className>',
- '<methods>', '<sleepImpl>'
+ '<methods>', '<sleepImpl>', '<cloneImpl>'
);
- 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
+ $proxyClassNamespace,
+ $proxyClassName,
+ $className,
+ $methods,
+ $sleepImpl,
+ $cloneImpl
);
$file = str_replace($placeholders, $replacements, $file);
- file_put_contents($fileName, $file);
+ $parentDirectory = dirname($fileName);
+
+ if ( ! is_dir($parentDirectory)) {
+ if (false === @mkdir($parentDirectory, 0775, true)) {
+ throw ProxyException::proxyDirectoryNotWritable();
+ }
+ } else if ( ! is_writable($parentDirectory)) {
+ throw ProxyException::proxyDirectoryNotWritable();
+ }
+
+ file_put_contents($fileName, $file, LOCK_EX);
}
/**
@@ -161,19 +196,20 @@ private function generateMethods(ClassMetadata $class)
{
$methods = '';
+ $methodNames = array();
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")) || isset($methodNames[$method->getName()])) {
continue;
}
+ $methodNames[$method->getName()] = true;
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
- $methods .= PHP_EOL . ' public function ';
+ $methods .= "\n" . ' public function ';
if ($method->returnsReference()) {
$methods .= '&';
}
$methods .= $method->getName() . '(';
-
$firstParam = true;
$parameterString = $argumentString = '';
@@ -188,7 +224,7 @@ private function generateMethods(ClassMetadata $class)
// We need to pick the type hint class too
if (($paramClass = $param->getClass()) !== null) {
$parameterString .= '\\' . $paramClass->getName() . ' ';
- } elseif ($param->isArray()) {
+ } else if ($param->isArray()) {
$parameterString .= 'array ';
}
@@ -205,17 +241,62 @@ private function generateMethods(ClassMetadata $class)
}
$methods .= $parameterString . ')';
- $methods .= PHP_EOL . ' {' . PHP_EOL;
- $methods .= ' $this->__load();' . PHP_EOL;
+ $methods .= "\n" . ' {' . "\n";
+ if ($this->isShortIdentifierGetter($method, $class)) {
+ $identifier = lcfirst(substr($method->getName(), 3));
+
+ $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
+ $methods .= ' return $this->__identifier__;' . "\n";
+ $methods .= ' }' . "\n";
+ }
+ $methods .= ' $this->__load();' . "\n";
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
- $methods .= PHP_EOL . ' }' . PHP_EOL;
+ $methods .= "\n" . ' }' . "\n";
}
}
return $methods;
}
/**
+ * 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 document, but do not display anything else).
+ *
+ * @param ReflectionMethod $method
+ * @param ClassMetadata $class
+ * @return bool
+ */
+ private function isShortIdentifierGetter($method, $class)
+ {
+ $identifier = lcfirst(substr($method->getName(), 3));
+ $cheapCheck = (
+ $method->getNumberOfParameters() == 0 &&
+ substr($method->getName(), 0, 3) == "get" &&
+ $class->identifier === $identifier &&
+ $class->hasField($identifier) &&
+ (($method->getEndLine() - $method->getStartLine()) <= 4)
+ && in_array($class->fieldMappings[$identifier]['type'], array('id', 'custom_id'))
+ );
+
+ if ($cheapCheck) {
+ $code = file($method->getDeclaringClass()->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;
+ }
+
+ /**
* Generates the code for the __sleep method for a proxy class.
*
* @param $class
@@ -226,9 +307,9 @@ private function generateSleep(ClassMetadata $class)
$sleepImpl = '';
if ($class->reflClass->hasMethod('__sleep')) {
- $sleepImpl .= 'return parent::__sleep();';
+ $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
} else {
- $sleepImpl .= 'return array(';
+ $sleepImpl .= "return array('__isInitialized__', ";
$first = true;
foreach ($class->getReflectionProperties() as $name => $prop) {
@@ -261,29 +342,61 @@ private function generateSleep(ClassMetadata $class)
class <proxyClassName> extends \<className> implements \Doctrine\ODM\MongoDB\Proxy\Proxy
{
private $__documentPersister__;
- private $__identifier__;
+ public $__identifier__;
public $__isInitialized__ = false;
public function __construct(DocumentPersister $documentPersister, $identifier)
{
$this->__documentPersister__ = $documentPersister;
$this->__identifier__ = $identifier;
}
+ /** @private */
public function __load()
{
if (!$this->__isInitialized__ && $this->__documentPersister__) {
$this->__isInitialized__ = true;
+
+ if (method_exists($this, "__wakeup")) {
+ // call this after __isInitialized__to avoid infinite recursion
+ // but before loading to emulate what ClassMetadata::newInstance()
+ // provides.
+ $this->__wakeup();
+ }
+
if ($this->__documentPersister__->load($this->__identifier__, $this) === null) {
throw \Doctrine\ODM\MongoDB\MongoDBException::documentNotFound(get_class($this), $this->__identifier__);
}
unset($this->__documentPersister__, $this->__identifier__);
}
}
+ /** @private */
+ public function __isInitialized()
+ {
+ return $this->__isInitialized__;
+ }
+
<methods>
public function __sleep()
{
<sleepImpl>
}
+
+ public function __clone()
+ {
+ if (!$this->__isInitialized__ && $this->__documentPersister__) {
+ $this->__isInitialized__ = true;
+ $class = $this->__documentPersister__->getClassMetadata();
+ $original = $this->__documentPersister__->load($this->__identifier__);
+ if ($original === null) {
+ throw \Doctrine\ODM\MongoDB\MongoDBException::documentNotFound(get_class($this), $this->__identifier__);
+ }
+ foreach ($class->reflFields AS $field => $reflProperty) {
+ $reflProperty->setValue($this, $reflProperty->getValue($original));
+ }
+ unset($this->__documentPersister__, $this->__identifier__);
+ }
+ <cloneImpl>
+ }
}';
-}
+}
View
30 tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php
@@ -6,6 +6,36 @@
class IdentifiersTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
{
+ public function testGetIdentifierValue()
+ {
+ $user = new \Documents\User();
+ $user->setUsername('jwage');
+ $event = new \Documents\Event();
+ $event->setTitle('test event title');
+ $event->setUser($user);
+ $this->dm->persist($user);
+ $this->dm->persist($event);
+ $this->dm->flush();
+ $this->dm->clear();
+
+ $test = $this->dm->getRepository(get_class($event))->find($event->getId());
+
+ $this->assertEquals($user->getId(), $test->getUser()->getId());
+ $this->assertFalse($test->getUser()->__isInitialized__);
+
+ $this->dm->clear();
+
+ $class = $this->dm->getClassMetadata(get_class($test->getUser()));
+
+ $test = $this->dm->getRepository(get_class($event))->find($event->getId());
+ $this->assertEquals($user->getId(), $class->getIdentifierValue($test->getUser()));
+ $this->assertEquals($user->getId(), $class->getFieldValue($test->getUser(), 'id'));
+ $this->assertFalse($test->getUser()->__isInitialized__);
+
+ $this->assertEquals('jwage', $test->getUser()->getUsername());
+ $this->assertTrue($test->getUser()->__isInitialized__);
+ }
+
public function testIdentifiersAreSet()
{
$user = new \Documents\User();
View
2 tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php
@@ -63,7 +63,7 @@ public function testLazyLoadReference()
$profile = $user->getProfile();
- $this->assertTrue($profile instanceof \Proxies\DocumentsProfileProxy);
+ $this->assertTrue($profile instanceof \Proxies\__CG__\Documents\Profile);
$profile->getFirstName();
View
2 tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php
@@ -69,7 +69,7 @@ public function testProxy()
$this->assertNotNull($test);
$this->assertNotNull($test->getUser());
- $this->assertInstanceOf('Proxies\DocumentsUserProxy', $test->getUser());
+ $this->assertInstanceOf('Proxies\__CG__\Documents\User', $test->getUser());
$this->assertFalse($test->getUser()->__isInitialized__);
$this->assertEquals('jwage', $test->getUser()->getUsername());
$this->assertTrue($test->getUser()->__isInitialized__);
View
34 tests/Doctrine/ODM/MongoDB/Tests/Functional/TestTargetDocument.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\Tests\Functional;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+class TargetDocumentTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
+{
+ public function testMappedSuperClassAsTargetDocument()
+ {
+ $test = new TargetDocumentTestDocument();
+ $test->reference = new TargetDocumentTestReference();
@kusmierz
kusmierz added a note Mar 23, 2012

Really? new Abstract class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $this->dm->persist($test);
+ $this->dm->persist($test->reference);
+ $this->dm->flush();
+ }
+}
+
+/** @ODM\Document */
+class TargetDocumentTestDocument
+{
+ /** @ODM\Id */
+ public $id;
+
+ /** @ODM\ReferenceOne(targetDocument="Doctrine\ODM\MongoDB\Tests\Functional\TargetDocumentTestReference") */
+ public $reference;
+}
+
+/** @ODM\MappedSuperclass */
+abstract class TargetDocumentTestReference
+{
+ /** @ODM\Id */
+ public $id;
+}
View
5 tests/Documents/Event.php
@@ -19,6 +19,11 @@ class Event
/** @ODM\String */
private $type;
+ public function getId()
+ {
+ return $this->id;
+ }
+
public function setUser(User $user)
{
$this->user = $user;

0 comments on commit e0bfdb3

Please sign in to comment.
Something went wrong with that request. Please try again.