Proxy generation as of doctrine/common#168 - DCOM-96
This commit is contained in:
parent
35fda90473
commit
8272ffd23f
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,4 +8,5 @@ lib/Doctrine/Common
|
||||
lib/Doctrine/DBAL
|
||||
/.settings/
|
||||
.buildpath
|
||||
.project
|
||||
.project
|
||||
.idea
|
||||
|
@ -52,7 +52,7 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
$identifier = array();
|
||||
|
||||
foreach ($idFields as $idField) {
|
||||
$value = $class->reflFields[$idField]->getValue($entity);
|
||||
$value = $class->getFieldValue($entity, $idField);
|
||||
|
||||
if ( ! isset($value)) {
|
||||
throw ORMException::entityMissingAssignedIdForField($entity, $idField);
|
||||
|
@ -605,7 +605,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
/**
|
||||
* The ReflectionProperty instances of the mapped class.
|
||||
*
|
||||
* @var array
|
||||
* @var \ReflectionProperty[]
|
||||
*/
|
||||
public $reflFields = array();
|
||||
|
||||
@ -693,13 +693,14 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
return $id;
|
||||
}
|
||||
|
||||
$value = $this->reflFields[$this->identifier[0]]->getValue($entity);
|
||||
$id = $this->identifier[0];
|
||||
$value = $this->reflFields[$id]->getValue($entity);
|
||||
|
||||
if ($value !== null) {
|
||||
return array($this->identifier[0] => $value);
|
||||
if (null === $value) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array();
|
||||
return array($id => $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -853,9 +853,10 @@ final class PersistentCollection implements Collection, Selectable
|
||||
$newObjects = $this->coll->matching($criteria)->toArray();
|
||||
}
|
||||
|
||||
$targetClass = $this->em->getClassMetadata(get_class($this->owner));
|
||||
|
||||
$id = $targetClass->getSingleIdReflectionProperty()->getValue($this->owner);
|
||||
$id = $this->em
|
||||
->getClassMetadata(get_class($this->owner))
|
||||
->getSingleIdReflectionProperty()
|
||||
->getValue($this->owner);
|
||||
$builder = Criteria::expr();
|
||||
$ownerExpression = $builder->eq($this->backRefFieldName, $id);
|
||||
$expression = $criteria->getWhereExpression();
|
||||
|
@ -19,82 +19,11 @@
|
||||
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Closure;
|
||||
use Doctrine\Common\Proxy\Autoloader as BaseAutoloader;
|
||||
|
||||
/**
|
||||
* Special Autoloader for Proxy classes because them not being PSR-0 compatible.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @deprecated use \Doctrine\Common\Proxy\Autoloader instead
|
||||
*/
|
||||
class Autoloader
|
||||
class Autoloader extends BaseAutoloader
|
||||
{
|
||||
/**
|
||||
* Resolves proxy class name to a filename based on the following pattern.
|
||||
*
|
||||
* 1. Remove Proxy namespace from class name
|
||||
* 2. Remove namespace seperators from remaining class name.
|
||||
* 3. Return PHP filename from proxy-dir with the result from 2.
|
||||
*
|
||||
* @param string $proxyDir
|
||||
* @param string $proxyNamespace
|
||||
* @param string $className
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws ProxyException
|
||||
*/
|
||||
static public function resolveFile($proxyDir, $proxyNamespace, $className)
|
||||
{
|
||||
if (0 !== strpos($className, $proxyNamespace)) {
|
||||
throw ProxyException::notProxyClass($className, $proxyNamespace);
|
||||
}
|
||||
|
||||
$className = str_replace('\\', '', substr($className, strlen($proxyNamespace) +1));
|
||||
return $proxyDir . DIRECTORY_SEPARATOR . $className.'.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers and returns autoloader callback for the given proxy dir and
|
||||
* namespace.
|
||||
*
|
||||
* @param string $proxyDir
|
||||
* @param string $proxyNamespace
|
||||
* @param \Closure $notFoundCallback Invoked when the proxy file is not found.
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
static public function register($proxyDir, $proxyNamespace, Closure $notFoundCallback = null)
|
||||
{
|
||||
$proxyNamespace = ltrim($proxyNamespace, "\\");
|
||||
$autoloader = function($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) {
|
||||
if (0 === strpos($className, $proxyNamespace)) {
|
||||
$file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);
|
||||
|
||||
if ($notFoundCallback && ! file_exists($file)) {
|
||||
$notFoundCallback($proxyDir, $proxyNamespace, $className);
|
||||
}
|
||||
|
||||
require $file;
|
||||
}
|
||||
};
|
||||
|
||||
spl_autoload_register($autoloader, true, true);
|
||||
|
||||
return $autoloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers and returns autoloader callback from a Configuration instance
|
||||
*
|
||||
* @param Configuration $config
|
||||
* @param \Closure $notFoundCallback
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
static public function registerFromConfiguration(Configuration $configuration, Closure $notFoundCallback)
|
||||
{
|
||||
return self::register($configuration->getProxyDir(), $configuration->getProxyNamespace(), $notFoundCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Doctrine\Common\Persistence\Proxy as BaseProxy;
|
||||
use Doctrine\Common\Proxy\Proxy as BaseProxy;
|
||||
|
||||
/**
|
||||
* Interface for proxy classes.
|
||||
|
@ -1,69 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
/**
|
||||
* ORM Proxy Exception.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.com
|
||||
* @since 1.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class ProxyException extends \Doctrine\ORM\ORMException
|
||||
{
|
||||
/**
|
||||
* @return ProxyException
|
||||
*/
|
||||
public static function proxyDirectoryRequired()
|
||||
{
|
||||
return new self("You must configure a proxy directory. See docs for details");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProxyException
|
||||
*/
|
||||
public static function proxyDirectoryNotWritable()
|
||||
{
|
||||
return new self("Your proxy directory must be writable.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProxyException
|
||||
*/
|
||||
public static function proxyNamespaceRequired()
|
||||
{
|
||||
return new self("You must configure a proxy namespace. See docs for details");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $className
|
||||
* @param $proxyNamespace
|
||||
*
|
||||
* @return ProxyException
|
||||
*/
|
||||
public static function notProxyClass($className, $proxyNamespace)
|
||||
{
|
||||
return new self(sprintf(
|
||||
"The class %s is not part of the proxy namespace %s",
|
||||
$className, $proxyNamespace
|
||||
));
|
||||
}
|
||||
}
|
@ -20,53 +20,62 @@
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\Common\Proxy\ProxyGenerator;
|
||||
|
||||
/**
|
||||
* This factory is used to create proxy objects for entities at runtime.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
* @author Marco Pivetta <ocramius@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class ProxyFactory
|
||||
{
|
||||
/**
|
||||
* The EntityManager this factory is bound to.
|
||||
*
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
* @var EntityManager The EntityManager this factory is bound to.
|
||||
*/
|
||||
private $_em;
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* Whether to automatically (re)generate proxy classes.
|
||||
*
|
||||
* @var bool
|
||||
* @var \Doctrine\ORM\UnitOfWork The UnitOfWork this factory uses to retrieve persisters
|
||||
*/
|
||||
private $_autoGenerate;
|
||||
private $uow;
|
||||
|
||||
/**
|
||||
* @var ProxyGenerator the proxy generator responsible for creating the proxy classes/files.
|
||||
*/
|
||||
private $proxyGenerator;
|
||||
|
||||
/**
|
||||
* @var bool Whether to automatically (re)generate proxy classes.
|
||||
*/
|
||||
private $autoGenerate;
|
||||
|
||||
/**
|
||||
* The namespace that contains all proxy classes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_proxyNamespace;
|
||||
private $proxyNs;
|
||||
|
||||
/**
|
||||
* The directory that contains all proxy classes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_proxyDir;
|
||||
private $proxyDir;
|
||||
|
||||
/**
|
||||
* Used to match very simple id methods that don't need
|
||||
* to be proxied since the identifier is known.
|
||||
*
|
||||
* @var string
|
||||
* @var array definitions (indexed by requested class name) for the proxy classes.
|
||||
* Each element is an array containing following items:
|
||||
* "fqcn" - FQCN of the proxy class
|
||||
* "initializer" - Closure to be used as proxy __initializer__
|
||||
* "cloner" - Closure to be used as proxy __cloner__
|
||||
* "identifierFields" - list of field names for the identifiers
|
||||
* "reflectionFields" - ReflectionProperties for the fields
|
||||
*/
|
||||
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';
|
||||
private $definitions = array();
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
|
||||
@ -76,365 +85,195 @@ class ProxyFactory
|
||||
* @param string $proxyDir The directory to use for the proxy classes. It must exist.
|
||||
* @param string $proxyNs The namespace to use for the proxy classes.
|
||||
* @param boolean $autoGenerate Whether to automatically generate proxy classes.
|
||||
*
|
||||
* @throws ProxyException
|
||||
*/
|
||||
public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false)
|
||||
{
|
||||
if ( ! $proxyDir) {
|
||||
throw ProxyException::proxyDirectoryRequired();
|
||||
}
|
||||
if ( ! $proxyNs) {
|
||||
throw ProxyException::proxyNamespaceRequired();
|
||||
}
|
||||
$this->_em = $em;
|
||||
$this->_proxyDir = $proxyDir;
|
||||
$this->_autoGenerate = $autoGenerate;
|
||||
$this->_proxyNamespace = $proxyNs;
|
||||
$this->em = $em;
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->proxyDir = $proxyDir;
|
||||
$this->proxyNs = $proxyNs;
|
||||
$this->autoGenerate = $autoGenerate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference proxy instance for the entity of the given type and identified by
|
||||
* the given identifier.
|
||||
*
|
||||
* @param string $className
|
||||
* @param mixed $identifier
|
||||
*
|
||||
* @param string $className
|
||||
* @param mixed $identifier
|
||||
* @return object
|
||||
*/
|
||||
public function getProxy($className, $identifier)
|
||||
{
|
||||
$fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace);
|
||||
|
||||
if (! class_exists($fqn, false)) {
|
||||
$fileName = $this->getProxyFileName($className);
|
||||
if ($this->_autoGenerate) {
|
||||
$this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate);
|
||||
}
|
||||
require $fileName;
|
||||
if ( ! isset($this->definitions[$className])) {
|
||||
$this->initProxyDefinitions($className);
|
||||
}
|
||||
|
||||
$entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
|
||||
$definition = $this->definitions[$className];
|
||||
$fqcn = $definition['fqcn'];
|
||||
$identifierFields = $definition['identifierFields'];
|
||||
/* @var $reflectionFields \ReflectionProperty[] */
|
||||
$reflectionFields = $definition['reflectionFields'];
|
||||
$proxy = new $fqcn($definition['initializer'], $definition['cloner']);
|
||||
|
||||
return new $fqn($entityPersister, $identifier);
|
||||
}
|
||||
foreach ($identifierFields as $idField) {
|
||||
$reflectionFields[$idField]->setValue($proxy, $identifier[$idField]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the Proxy file name.
|
||||
*
|
||||
* @param string $className
|
||||
* @param string|null $baseDir Optional base directory for proxy file name generation.
|
||||
* If not specified, the directory configured on the Configuration of the
|
||||
* EntityManager will be used by this factory.
|
||||
* @return string
|
||||
*/
|
||||
private function getProxyFileName($className, $baseDir = null)
|
||||
{
|
||||
$proxyDir = $baseDir ?: $this->_proxyDir;
|
||||
|
||||
return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
|
||||
return $proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates proxy classes for all given classes.
|
||||
*
|
||||
* @param array $classes The classes (ClassMetadata instances) for which to generate proxies.
|
||||
* @param string|null $toDir The target directory of the proxy classes. If not specified, the
|
||||
* directory configured on the Configuration of the EntityManager used
|
||||
* by this factory is used.
|
||||
*
|
||||
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadata[] $classes The classes (ClassMetadata instances)
|
||||
* for which to generate proxies.
|
||||
* @param string $proxyDir The target directory of the proxy classes. If not specified, the
|
||||
* directory configured on the Configuration of the EntityManager used
|
||||
* by this factory is used.
|
||||
* @return int Number of generated proxies.
|
||||
*/
|
||||
public function generateProxyClasses(array $classes, $toDir = null)
|
||||
public function generateProxyClasses(array $classes, $proxyDir = null)
|
||||
{
|
||||
$proxyDir = $toDir ?: $this->_proxyDir;
|
||||
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
|
||||
$num = 0;
|
||||
$generated = 0;
|
||||
|
||||
foreach ($classes as $class) {
|
||||
/* @var $class ClassMetadata */
|
||||
if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) {
|
||||
/* @var $class \Doctrine\ORM\Mapping\ClassMetadataInfo */
|
||||
if ($class->isMappedSuperclass || $class->getReflectionClass()->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
|
||||
$generator = $this->getProxyGenerator();
|
||||
|
||||
$this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
|
||||
$num++;
|
||||
$proxyFileName = $generator->getProxyFileName($class->getName(), $proxyDir);
|
||||
$generator->generateProxyClass($class, $proxyFileName);
|
||||
$generated += 1;
|
||||
}
|
||||
|
||||
return $num;
|
||||
return $generated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a proxy class file.
|
||||
*
|
||||
* @param ClassMetadata $class Metadata for the original class.
|
||||
* @param string $fileName Filename (full path) for the generated class.
|
||||
* @param string $file The proxy class template data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ProxyException
|
||||
* @param ProxyGenerator $proxyGenerator
|
||||
*/
|
||||
private function _generateProxyClass(ClassMetadata $class, $fileName, $file)
|
||||
public function setProxyGenerator(ProxyGenerator $proxyGenerator)
|
||||
{
|
||||
$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>', '<cloneImpl>'
|
||||
);
|
||||
|
||||
$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(
|
||||
$proxyClassNamespace,
|
||||
$proxyClassName,
|
||||
$className,
|
||||
$methods,
|
||||
$sleepImpl,
|
||||
$cloneImpl
|
||||
);
|
||||
|
||||
$file = str_replace($placeholders, $replacements, $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();
|
||||
}
|
||||
|
||||
$tmpFileName = $fileName . '.' . uniqid("", true);
|
||||
file_put_contents($tmpFileName, $file);
|
||||
rename($tmpFileName, $fileName);
|
||||
$this->proxyGenerator = $proxyGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the methods of a proxy class.
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
*
|
||||
* @return string The code of the generated methods.
|
||||
* @return ProxyGenerator
|
||||
*/
|
||||
private function _generateMethods(ClassMetadata $class)
|
||||
public function getProxyGenerator()
|
||||
{
|
||||
$methods = '';
|
||||
if (null === $this->proxyGenerator) {
|
||||
$this->proxyGenerator = new ProxyGenerator($this->proxyDir, $this->proxyNs);
|
||||
$this->proxyGenerator->setPlaceholder('<baseProxyInterface>', 'Doctrine\ORM\Proxy\Proxy');
|
||||
}
|
||||
|
||||
$methodNames = array();
|
||||
foreach ($class->reflClass->getMethods() as $method) {
|
||||
/* @var $method \ReflectionMethod */
|
||||
if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
|
||||
continue;
|
||||
return $this->proxyGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*/
|
||||
private function initProxyDefinitions($className)
|
||||
{
|
||||
$fqcn = ClassUtils::generateProxyClassName($className, $this->proxyNs);
|
||||
$classMetadata = $this->em->getClassMetadata($className);
|
||||
|
||||
if ( ! class_exists($fqcn, false)) {
|
||||
$generator = $this->getProxyGenerator();
|
||||
$fileName = $generator->getProxyFileName($className);
|
||||
|
||||
if ($this->autoGenerate) {
|
||||
$generator->generateProxyClass($classMetadata);
|
||||
}
|
||||
$methodNames[$method->getName()] = true;
|
||||
|
||||
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
|
||||
$methods .= "\n" . ' public function ';
|
||||
if ($method->returnsReference()) {
|
||||
$methods .= '&';
|
||||
require $fileName;
|
||||
}
|
||||
|
||||
$entityPersister = $this->uow->getEntityPersister($className);
|
||||
|
||||
if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) {
|
||||
$initializer = function (Proxy $proxy) use ($entityPersister, $classMetadata) {
|
||||
$proxy->__setInitializer(null);
|
||||
$proxy->__setCloner(null);
|
||||
|
||||
if ($proxy->__isInitialized()) {
|
||||
return;
|
||||
}
|
||||
$methods .= $method->getName() . '(';
|
||||
$firstParam = true;
|
||||
$parameterString = $argumentString = '';
|
||||
|
||||
foreach ($method->getParameters() as $param) {
|
||||
if ($firstParam) {
|
||||
$firstParam = false;
|
||||
} else {
|
||||
$parameterString .= ', ';
|
||||
$argumentString .= ', ';
|
||||
}
|
||||
$properties = $proxy->__getLazyProperties();
|
||||
|
||||
// We need to pick the type hint class too
|
||||
if (($paramClass = $param->getClass()) !== null) {
|
||||
$parameterString .= '\\' . $paramClass->getName() . ' ';
|
||||
} else if ($param->isArray()) {
|
||||
$parameterString .= 'array ';
|
||||
}
|
||||
|
||||
if ($param->isPassedByReference()) {
|
||||
$parameterString .= '&';
|
||||
}
|
||||
|
||||
$parameterString .= '$' . $param->getName();
|
||||
$argumentString .= '$' . $param->getName();
|
||||
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
|
||||
foreach ($properties as $propertyName => $property) {
|
||||
if (!isset($proxy->$propertyName)) {
|
||||
$proxy->$propertyName = $properties[$propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
$methods .= $parameterString . ')';
|
||||
$methods .= "\n" . ' {' . "\n";
|
||||
if ($this->isShortIdentifierGetter($method, $class)) {
|
||||
$identifier = lcfirst(substr($method->getName(), 3));
|
||||
$proxy->__setInitialized(true);
|
||||
$proxy->__wakeup();
|
||||
|
||||
$cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : '';
|
||||
|
||||
$methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
|
||||
$methods .= ' return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n";
|
||||
$methods .= ' }' . "\n";
|
||||
if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) {
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
$methods .= ' $this->__load();' . "\n";
|
||||
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
|
||||
$methods .= "\n" . ' }' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks 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
|
||||
*/
|
||||
private function isShortIdentifierGetter($method, ClassMetadata $class)
|
||||
{
|
||||
$identifier = lcfirst(substr($method->getName(), 3));
|
||||
$cheapCheck = (
|
||||
$method->getNumberOfParameters() == 0 &&
|
||||
substr($method->getName(), 0, 3) == "get" &&
|
||||
in_array($identifier, $class->identifier, true) &&
|
||||
$class->hasField($identifier) &&
|
||||
(($method->getEndLine() - $method->getStartLine()) <= 4)
|
||||
&& in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
|
||||
);
|
||||
|
||||
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 ClassMetadata $class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function _generateSleep(ClassMetadata $class)
|
||||
{
|
||||
$sleepImpl = '';
|
||||
|
||||
if ($class->reflClass->hasMethod('__sleep')) {
|
||||
$sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
|
||||
};
|
||||
} else {
|
||||
$sleepImpl .= "return array('__isInitialized__', ";
|
||||
$first = true;
|
||||
$initializer = function (Proxy $proxy) use ($entityPersister, $classMetadata) {
|
||||
$proxy->__setInitializer(null);
|
||||
$proxy->__setCloner(null);
|
||||
|
||||
foreach ($class->getReflectionProperties() as $name => $prop) {
|
||||
if ($first) {
|
||||
$first = false;
|
||||
} else {
|
||||
$sleepImpl .= ', ';
|
||||
if ($proxy->__isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sleepImpl .= "'" . $name . "'";
|
||||
}
|
||||
$properties = $proxy->__getLazyProperties();
|
||||
|
||||
$sleepImpl .= ');';
|
||||
foreach ($properties as $propertyName => $property) {
|
||||
if (!isset($proxy->$propertyName)) {
|
||||
$proxy->$propertyName = $properties[$propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
$proxy->__setInitialized(true);
|
||||
|
||||
if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) {
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return $sleepImpl;
|
||||
}
|
||||
|
||||
/** Proxy class code template */
|
||||
private static $_proxyClassTemplate =
|
||||
'<?php
|
||||
|
||||
namespace <namespace>;
|
||||
|
||||
/**
|
||||
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
|
||||
*/
|
||||
class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
|
||||
{
|
||||
private $_entityPersister;
|
||||
private $_identifier;
|
||||
public $__isInitialized__ = false;
|
||||
public function __construct($entityPersister, $identifier)
|
||||
{
|
||||
$this->_entityPersister = $entityPersister;
|
||||
$this->_identifier = $identifier;
|
||||
}
|
||||
/** @private */
|
||||
public function __load()
|
||||
{
|
||||
if (!$this->__isInitialized__ && $this->_entityPersister) {
|
||||
$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();
|
||||
$cloner = function (Proxy $proxy) use ($entityPersister, $classMetadata) {
|
||||
if ($proxy->__isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->_entityPersister->load($this->_identifier, $this) === null) {
|
||||
throw new \Doctrine\ORM\EntityNotFoundException();
|
||||
$proxy->__setInitialized(true);
|
||||
$proxy->__setInitializer(null);
|
||||
$class = $entityPersister->getClassMetadata();
|
||||
$original = $entityPersister->load($classMetadata->getIdentifierValues($proxy));
|
||||
|
||||
if (null === $original) {
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
unset($this->_entityPersister, $this->_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
public function __isInitialized()
|
||||
{
|
||||
return $this->__isInitialized__;
|
||||
}
|
||||
foreach ($class->getReflectionClass()->getProperties() as $reflectionProperty) {
|
||||
$propertyName = $reflectionProperty->getName();
|
||||
|
||||
<methods>
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
<sleepImpl>
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if (!$this->__isInitialized__ && $this->_entityPersister) {
|
||||
$this->__isInitialized__ = true;
|
||||
$class = $this->_entityPersister->getClassMetadata();
|
||||
$original = $this->_entityPersister->load($this->_identifier);
|
||||
if ($original === null) {
|
||||
throw new \Doctrine\ORM\EntityNotFoundException();
|
||||
if ($class->hasField($propertyName) || $class->hasAssociation($propertyName)) {
|
||||
$reflectionProperty->setAccessible(true);
|
||||
$reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original));
|
||||
}
|
||||
}
|
||||
foreach ($class->reflFields as $field => $reflProperty) {
|
||||
$reflProperty->setValue($this, $reflProperty->getValue($original));
|
||||
}
|
||||
unset($this->_entityPersister, $this->_identifier);
|
||||
}
|
||||
<cloneImpl>
|
||||
};
|
||||
|
||||
$this->definitions[$className] = array(
|
||||
'fqcn' => $fqcn,
|
||||
'initializer' => $initializer,
|
||||
'cloner' => $cloner,
|
||||
'identifierFields' => $classMetadata->getIdentifierFieldNames(),
|
||||
'reflectionFields' => $classMetadata->getReflectionProperties(),
|
||||
);
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class DebugUnitOfWorkListener
|
||||
|
||||
foreach ($cm->associationMappings as $field => $assoc) {
|
||||
fwrite($fh, " " . $field . " ");
|
||||
$value = $cm->reflFields[$field]->getValue($entity);
|
||||
$value = $cm->getFieldValue($entity, $field);
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
if ($value === null) {
|
||||
|
@ -666,7 +666,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
// Look for changes in associations of the entity
|
||||
foreach ($class->associationMappings as $field => $assoc) {
|
||||
if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
|
||||
$val = $class->reflFields[$field]->getValue($entity);
|
||||
|
||||
if (null !== $val) {
|
||||
$this->computeAssociationChanges($assoc, $val);
|
||||
if (!isset($this->entityChangeSets[$oid]) &&
|
||||
$assoc['isOwningSide'] &&
|
||||
@ -1815,8 +1817,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
if ($class->isVersioned) {
|
||||
$managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
|
||||
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
||||
$reflField = $class->reflFields[$class->versionField];
|
||||
$managedCopyVersion = $reflField->getValue($managedCopy);
|
||||
$entityVersion = $reflField->getValue($entity);
|
||||
|
||||
// Throw exception if versions dont match.
|
||||
if ($managedCopyVersion != $entityVersion) {
|
||||
@ -1828,14 +1831,17 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($class->reflClass->getProperties() as $prop) {
|
||||
$name = $prop->name;
|
||||
$prop->setAccessible(true);
|
||||
|
||||
if ( ! isset($class->associationMappings[$name])) {
|
||||
if ( ! $class->isIdentifier($name)) {
|
||||
$prop->setValue($managedCopy, $prop->getValue($entity));
|
||||
}
|
||||
} else {
|
||||
$assoc2 = $class->associationMappings[$name];
|
||||
|
||||
if ($assoc2['type'] & ClassMetadata::TO_ONE) {
|
||||
$other = $prop->getValue($entity);
|
||||
|
||||
if ($other === null) {
|
||||
$prop->setValue($managedCopy, null);
|
||||
} else if ($other instanceof Proxy && !$other->__isInitialized__) {
|
||||
@ -1857,6 +1863,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
} else {
|
||||
$mergeCol = $prop->getValue($entity);
|
||||
|
||||
if ($mergeCol instanceof PersistentCollection && !$mergeCol->isInitialized()) {
|
||||
// do not merge fields marked lazy that have not been fetched.
|
||||
// keep the lazy persistent collection of the managed copy.
|
||||
@ -1864,6 +1871,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$managedCol = $prop->getValue($managedCopy);
|
||||
|
||||
if (!$managedCol) {
|
||||
$managedCol = new PersistentCollection($this->em,
|
||||
$this->em->getClassMetadata($assoc2['targetEntity']),
|
||||
@ -2468,8 +2476,26 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$entity = $this->identityMap[$class->rootEntityName][$idHash];
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||
$entity->__isInitialized__ = true;
|
||||
if (
|
||||
isset($hints[Query::HINT_REFRESH])
|
||||
&& isset($hints[Query::HINT_REFRESH_ENTITY])
|
||||
&& ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
|
||||
&& $unmanagedProxy instanceof Proxy
|
||||
) {
|
||||
// DDC-1238 - we have a managed instance, but it isn't the provided one.
|
||||
// Therefore we clear its identifier. Also, we must re-fetch metadata since the
|
||||
// refreshed object may be anything
|
||||
$class = $this->em->getClassMetadata(get_class($unmanagedProxy));
|
||||
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
$class->reflFields[$fieldName]->setValue($unmanagedProxy, null);
|
||||
}
|
||||
|
||||
return $unmanagedProxy;
|
||||
}
|
||||
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
|
||||
$entity->__setInitialized(true);
|
||||
$overrideLocalValues = true;
|
||||
|
||||
if ($entity instanceof NotifyPropertyChanged) {
|
||||
|
2
lib/vendor/doctrine-common
vendored
2
lib/vendor/doctrine-common
vendored
@ -1 +1 @@
|
||||
Subproject commit d514e3920656921ba1148f16a4089222c58bc83a
|
||||
Subproject commit c2b45fdb2757492e75abaab119164aaf311cd395
|
@ -2,9 +2,8 @@
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils,
|
||||
Doctrine\Tests\Models\CMS\CmsUser,
|
||||
Doctrine\Tests\Proxies\__CG__\Doctrine\Tests\Models\CMS\CmsUser as Proxy;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
|
||||
/**
|
||||
* Test that Doctrine ORM correctly works with proxy instances exactly like with ordinary Entities
|
||||
@ -12,7 +11,7 @@ use Doctrine\Common\Util\ClassUtils,
|
||||
* The test considers two possible cases:
|
||||
* a) __initialized__ = true and no identifier set in proxy
|
||||
* b) __initialized__ = false and identifier set in proxy and in property
|
||||
* @todo All other cases would cause lazy loading issues
|
||||
* @todo All other cases would cause lazy loading
|
||||
*/
|
||||
class ProxiesLikeEntitiesTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
@ -27,6 +26,11 @@ class ProxiesLikeEntitiesTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
try {
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsArticle'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsEmail'),
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'),
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
@ -44,8 +48,7 @@ class ProxiesLikeEntitiesTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
public function testPersistUpdate()
|
||||
{
|
||||
// Considering case (a)
|
||||
$persister = $this->_em->getUnitOfWork()->getEntityPersister('Doctrine\Tests\Models\CMS\CmsUser');
|
||||
$proxy = new Proxy($persister, array());
|
||||
$proxy = $this->_em->getProxyFactory()->getProxy('Doctrine\Tests\Models\CMS\CmsUser', array('id' => null));
|
||||
$proxy->__isInitialized__ = true;
|
||||
$proxy->username = 'ocra';
|
||||
$proxy->name = 'Marco';
|
||||
@ -65,15 +68,15 @@ class ProxiesLikeEntitiesTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
|
||||
public function testEntityWithIdentifier()
|
||||
{
|
||||
// Considering case (b)
|
||||
$persister = $this->_em->getUnitOfWork()->getEntityPersister('Doctrine\Tests\Models\CMS\CmsUser');
|
||||
$uninitializedProxy = new Proxy($persister, array('id' => $this->user->getId()));
|
||||
$uninitializedProxy->id = $this->user->getId();
|
||||
$uninitializedProxy->username = 'ocra';
|
||||
$uninitializedProxy->name = 'Marco Pivetta';
|
||||
$userId = $this->user->getId();
|
||||
/* @var $uninitializedProxy \Doctrine\Tests\Proxies\__CG__\Doctrine\Tests\Models\CMS\CmsUser */
|
||||
$uninitializedProxy = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $userId);
|
||||
$this->assertInstanceOf('Doctrine\Tests\Proxies\__CG__\Doctrine\Tests\Models\CMS\CmsUser', $uninitializedProxy);
|
||||
|
||||
$this->_em->persist($uninitializedProxy);
|
||||
$this->_em->flush();
|
||||
$this->assertEquals($this->user->getId(), $uninitializedProxy->getId());
|
||||
$this->_em->flush($uninitializedProxy);
|
||||
$this->assertFalse($uninitializedProxy->__isInitialized(), 'Proxy didn\'t get initialized during flush operations');
|
||||
$this->assertEquals($userId, $uninitializedProxy->getId());
|
||||
$this->_em->remove($uninitializedProxy);
|
||||
$this->_em->flush();
|
||||
}
|
||||
@ -83,8 +86,7 @@ class ProxiesLikeEntitiesTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
*/
|
||||
public function testProxyAsDqlParameterPersist()
|
||||
{
|
||||
$persister = $this->_em->getUnitOfWork()->getEntityPersister('Doctrine\Tests\Models\CMS\CmsUser');
|
||||
$proxy = new Proxy($persister, array('id' => $this->user->getId()));
|
||||
$proxy = $this->_em->getProxyFactory()->getProxy('Doctrine\Tests\Models\CMS\CmsUser', array('id' => $this->user->getId()));
|
||||
$proxy->id = $this->user->getId();
|
||||
$result = $this
|
||||
->_em
|
||||
|
165
tests/Doctrine/Tests/ORM/Performance/ProxyPerformanceTest.php
Normal file
165
tests/Doctrine/Tests/ORM/Performance/ProxyPerformanceTest.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\Tests\ORM\Performance;
|
||||
|
||||
use Doctrine\Tests\OrmPerformanceTestCase;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Persisters\BasicEntityPersister;
|
||||
|
||||
/**
|
||||
* Performance test used to measure performance of proxy instantiation
|
||||
*
|
||||
* @author Marco Pivetta <ocramius@gmail.com>
|
||||
* @group performance
|
||||
*/
|
||||
class ProxyPerformanceTest extends OrmPerformanceTestCase
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function entitiesProvider()
|
||||
{
|
||||
return array(
|
||||
array('Doctrine\Tests\Models\CMS\CmsEmployee'),
|
||||
array('Doctrine\Tests\Models\CMS\CmsUser'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider entitiesProvider
|
||||
*/
|
||||
public function testProxyInstantiationPerformance($entityName)
|
||||
{
|
||||
$proxyFactory = $this->_getEntityManager()->getProxyFactory();
|
||||
$this->setMaxRunningTime(5);
|
||||
$start = microtime(true);
|
||||
|
||||
for ($i = 0; $i < 100000; $i += 1) {
|
||||
$user = $proxyFactory->getProxy($entityName, array('id' => $i));
|
||||
}
|
||||
|
||||
echo __FUNCTION__ . " - " . (microtime(true) - $start) . " seconds with " . $entityName . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider entitiesProvider
|
||||
*/
|
||||
public function testProxyForcedInitializationPerformance($entityName)
|
||||
{
|
||||
$em = new MockEntityManager($this->_getEntityManager());
|
||||
$proxyFactory = $em->getProxyFactory();
|
||||
/* @var $user \Doctrine\Common\Proxy\Proxy */
|
||||
$user = $proxyFactory->getProxy($entityName, array('id' => 1));
|
||||
$initializer = $user->__getInitializer();
|
||||
|
||||
$this->setMaxRunningTime(5);
|
||||
$start = microtime(true);
|
||||
|
||||
for ($i = 0; $i < 100000; $i += 1) {
|
||||
$user->__setInitialized(false);
|
||||
$user->__setInitializer($initializer);
|
||||
$user->__load();
|
||||
$user->__load();
|
||||
}
|
||||
|
||||
echo __FUNCTION__ . " - " . (microtime(true) - $start) . " seconds with " . $entityName . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock entity manager to fake `getPersister()`
|
||||
*/
|
||||
class MockEntityManager extends EntityManager
|
||||
{
|
||||
/** @var EntityManager */
|
||||
private $em;
|
||||
|
||||
/** @param EntityManager $em */
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function getProxyFactory()
|
||||
{
|
||||
$config = $this->em->getConfiguration();
|
||||
|
||||
return new ProxyFactory(
|
||||
$this,
|
||||
$config->getProxyDir(),
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses()
|
||||
);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function getClassMetadata($className)
|
||||
{
|
||||
return $this->em->getClassMetadata($className);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function getUnitOfWork()
|
||||
{
|
||||
return new MockUnitOfWork();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock UnitOfWork manager to fake `getPersister()`
|
||||
*/
|
||||
class MockUnitOfWork extends UnitOfWork
|
||||
{
|
||||
/** @var PersisterMock */
|
||||
private $entityPersister;
|
||||
|
||||
/** */
|
||||
public function __construct()
|
||||
{
|
||||
$this->entityPersister = new PersisterMock();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function getEntityPersister($entityName)
|
||||
{
|
||||
return $this->entityPersister;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock persister (we don't want PHPUnit comparator API to play a role in here)
|
||||
*/
|
||||
class PersisterMock extends BasicEntityPersister
|
||||
{
|
||||
/** */
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null)
|
||||
{
|
||||
return $entity;
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\Tests\ORM\Proxy;
|
||||
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Doctrine\ORM\Proxy\Autoloader;
|
||||
|
||||
/**
|
||||
* @group DDC-1698
|
||||
*/
|
||||
class AutoloaderTest extends OrmTestCase
|
||||
{
|
||||
static public function dataResolveFile()
|
||||
{
|
||||
return array(
|
||||
array('/tmp', 'MyProxy', 'MyProxy\__CG__\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__RealClass.php'),
|
||||
array('/tmp', 'MyProxy\Subdir', 'MyProxy\Subdir\__CG__\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__RealClass.php'),
|
||||
array('/tmp', 'MyProxy', 'MyProxy\__CG__\Other\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__OtherRealClass.php'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataResolveFile
|
||||
*/
|
||||
public function testResolveFile($proxyDir, $proxyNamespace, $className, $expectedProxyFile)
|
||||
{
|
||||
$actualProxyFile = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);
|
||||
$this->assertEquals($expectedProxyFile, $actualProxyFile);
|
||||
}
|
||||
|
||||
public function testAutoload()
|
||||
{
|
||||
if (file_exists(sys_get_temp_dir() ."/AutoloaderTestClass.php")) {
|
||||
unlink(sys_get_temp_dir() ."/AutoloaderTestClass.php");
|
||||
}
|
||||
|
||||
$autoloader = Autoloader::register(sys_get_temp_dir(), 'ProxyAutoloaderTest', function($proxyDir, $proxyNamespace, $className) {
|
||||
file_put_contents(sys_get_temp_dir() . "/AutoloaderTestClass.php", "<?php namespace ProxyAutoloaderTest; class AutoloaderTestClass {} ");
|
||||
});
|
||||
|
||||
$this->assertTrue(class_exists('ProxyAutoloaderTest\AutoloaderTestClass', true));
|
||||
unlink(sys_get_temp_dir() ."/AutoloaderTestClass.php");
|
||||
}
|
||||
}
|
||||
|
89
tests/Doctrine/Tests/ORM/Proxy/ProxtFactoryTest.php
Normal file
89
tests/Doctrine/Tests/ORM/Proxy/ProxtFactoryTest.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Proxy;
|
||||
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\Common\Proxy\ProxyGenerator;
|
||||
use Doctrine\Tests\Mocks\ConnectionMock;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\Mocks\UnitOfWorkMock;
|
||||
use Doctrine\Tests\Mocks\DriverMock;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
/**
|
||||
* Test the proxy generator. Its work is generating on-the-fly subclasses of a given model, which implement the Proxy pattern.
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
*/
|
||||
class ProxtFactoryTest extends \Doctrine\Tests\OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var ConnectionMock
|
||||
*/
|
||||
private $connectionMock;
|
||||
|
||||
/**
|
||||
* @var UnitOfWorkMock
|
||||
*/
|
||||
private $uowMock;
|
||||
|
||||
/**
|
||||
* @var EntityManagerMock
|
||||
*/
|
||||
private $emMock;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Proxy\ProxyFactory
|
||||
*/
|
||||
private $proxyFactory;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->connectionMock = new ConnectionMock(array(), new DriverMock());
|
||||
$this->emMock = EntityManagerMock::create($this->connectionMock);
|
||||
$this->uowMock = new UnitOfWorkMock($this->emMock);
|
||||
$this->emMock->setUnitOfWork($this->uowMock);
|
||||
$this->proxyFactory = new ProxyFactory($this->emMock, sys_get_temp_dir(), 'Proxies', true);
|
||||
}
|
||||
|
||||
public function testReferenceProxyDelegatesLoadingToThePersister()
|
||||
{
|
||||
$identifier = array('id' => 42);
|
||||
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
|
||||
$persister = $this->getMock('Doctrine\ORM\Persisters\BasicEntityPersister', array('load'), array(), '', false);
|
||||
$this->uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
|
||||
|
||||
$proxy = $this->proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
|
||||
|
||||
$persister
|
||||
->expects($this->atLeastOnce())
|
||||
->method('load')
|
||||
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
|
||||
->will($this->returnValue(new \stdClass()));
|
||||
|
||||
$proxy->getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1771
|
||||
*/
|
||||
public function testSkipAbstractClassesOnGeneration()
|
||||
{
|
||||
$cm = new \Doctrine\ORM\Mapping\ClassMetadata(__NAMESPACE__ . '\\AbstractClass');
|
||||
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
|
||||
$this->assertNotNull($cm->reflClass);
|
||||
|
||||
$num = $this->proxyFactory->generateProxyClasses(array($cm));
|
||||
|
||||
$this->assertEquals(0, $num, "No proxies generated.");
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractClass
|
||||
{
|
||||
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Proxy;
|
||||
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\Tests\Mocks\ConnectionMock;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\Mocks\UnitOfWorkMock;
|
||||
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;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
/**
|
||||
* Test the proxy generator. Its work is generating on-the-fly subclasses of a given model, which implement the Proxy pattern.
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
*/
|
||||
class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
|
||||
{
|
||||
private $_connectionMock;
|
||||
private $_uowMock;
|
||||
private $_emMock;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Proxy\ProxyFactory
|
||||
*/
|
||||
private $_proxyFactory;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->_connectionMock = new ConnectionMock(array(), new \Doctrine\Tests\Mocks\DriverMock());
|
||||
$this->_emMock = EntityManagerMock::create($this->_connectionMock);
|
||||
$this->_uowMock = new UnitOfWorkMock($this->_emMock);
|
||||
$this->_emMock->setUnitOfWork($this->_uowMock);
|
||||
// SUT
|
||||
$this->_proxyFactory = new ProxyFactory($this->_emMock, __DIR__ . '/generated', 'Proxies', true);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
foreach (new \DirectoryIterator(__DIR__ . '/generated') as $file) {
|
||||
if (strstr($file->getFilename(), '.php')) {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testReferenceProxyDelegatesLoadingToThePersister()
|
||||
{
|
||||
$identifier = array('id' => 42);
|
||||
$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);
|
||||
|
||||
$persister->expects($this->atLeastOnce())
|
||||
->method('load')
|
||||
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
|
||||
->will($this->returnValue(new \stdClass())); // fake return of entity instance
|
||||
|
||||
$proxy->getDescription();
|
||||
}
|
||||
|
||||
public function testReferenceProxyExecutesLoadingOnlyOnce()
|
||||
{
|
||||
$identifier = array('id' => 42);
|
||||
$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);
|
||||
|
||||
$persister->expects($this->atLeastOnce())
|
||||
->method('load')
|
||||
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
|
||||
->will($this->returnValue(new \stdClass())); // fake return of entity instance
|
||||
$proxy->getDescription();
|
||||
$proxy->getProduct();
|
||||
}
|
||||
|
||||
public function testReferenceProxyRespectsMethodsParametersTypeHinting()
|
||||
{
|
||||
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
|
||||
$persister = $this->_getMockPersister();
|
||||
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
|
||||
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', null);
|
||||
|
||||
$method = new \ReflectionMethod(get_class($proxy), 'setProduct');
|
||||
$params = $method->getParameters();
|
||||
|
||||
$this->assertEquals(1, count($params));
|
||||
$this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $params[0]->getClass()->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the proxy behaves in regard to methods like &foo() correctly
|
||||
*/
|
||||
public function testProxyRespectsMethodsWhichReturnValuesByReference() {
|
||||
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\Forum\ForumEntry', null);
|
||||
$method = new \ReflectionMethod(get_class($proxy), 'getTopicByReference');
|
||||
|
||||
$this->assertTrue($method->returnsReference());
|
||||
}
|
||||
|
||||
public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne()
|
||||
{
|
||||
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
|
||||
$this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'));
|
||||
}
|
||||
|
||||
|
||||
public function testAllowsConcurrentCreationOfBothProxyTypes()
|
||||
{
|
||||
$referenceProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
|
||||
$associationProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy';
|
||||
$this->assertNotEquals($referenceProxyClass, $associationProxyClass);
|
||||
}
|
||||
|
||||
public function testNonNamespacedProxyGeneration()
|
||||
{
|
||||
require_once dirname(__FILE__)."/fixtures/NonNamespacedProxies.php";
|
||||
|
||||
$className = "\DoctrineOrmTestEntity";
|
||||
$proxyName = "DoctrineOrmTestEntity";
|
||||
$classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className);
|
||||
$classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
|
||||
$classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer'));
|
||||
$classMetadata->setIdentifier(array('id'));
|
||||
|
||||
$this->_proxyFactory->generateProxyClasses(array($classMetadata));
|
||||
|
||||
$classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php");
|
||||
|
||||
$this->assertNotContains("class DoctrineOrmTestEntity extends \\\\DoctrineOrmTestEntity", $classCode);
|
||||
$this->assertContains("class DoctrineOrmTestEntity extends \\DoctrineOrmTestEntity", $classCode);
|
||||
}
|
||||
|
||||
public function testClassWithSleepProxyGeneration()
|
||||
{
|
||||
$className = "\Doctrine\Tests\ORM\Proxy\SleepClass";
|
||||
$proxyName = "DoctrineTestsORMProxySleepClass";
|
||||
$classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className);
|
||||
$classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
|
||||
$classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer'));
|
||||
$classMetadata->setIdentifier(array('id'));
|
||||
|
||||
$this->_proxyFactory->generateProxyClasses(array($classMetadata));
|
||||
|
||||
$classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php");
|
||||
|
||||
$this->assertEquals(1, substr_count($classCode, 'function __sleep'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1771
|
||||
*/
|
||||
public function testSkipAbstractClassesOnGeneration()
|
||||
{
|
||||
$cm = new \Doctrine\ORM\Mapping\ClassMetadata(__NAMESPACE__ . '\\AbstractClass');
|
||||
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
|
||||
$this->assertNotNull($cm->reflClass);
|
||||
|
||||
$num = $this->_proxyFactory->generateProxyClasses(array($cm));
|
||||
|
||||
$this->assertEquals(0, $num, "No proxies generated.");
|
||||
}
|
||||
|
||||
public function testNoConfigDir_ThrowsException()
|
||||
{
|
||||
$this->setExpectedException('Doctrine\ORM\Proxy\ProxyException');
|
||||
new ProxyFactory($this->_getTestEntityManager(), null, null);
|
||||
}
|
||||
|
||||
public function testNoNamespace_ThrowsException()
|
||||
{
|
||||
$this->setExpectedException('Doctrine\ORM\Proxy\ProxyException');
|
||||
new ProxyFactory($this->_getTestEntityManager(), __DIR__ . '/generated', null);
|
||||
}
|
||||
|
||||
protected function _getMockPersister()
|
||||
{
|
||||
$persister = $this->getMock('Doctrine\ORM\Persisters\BasicEntityPersister', array('load'), array(), '', false);
|
||||
return $persister;
|
||||
}
|
||||
}
|
||||
|
||||
class SleepClass
|
||||
{
|
||||
public $id;
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
return array('id');
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractClass
|
||||
{
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @entity
|
||||
*/
|
||||
class DoctrineOrmTestEntity
|
||||
{
|
||||
/**
|
||||
* @column(type="integer")
|
||||
* @id
|
||||
*/
|
||||
public $id;
|
||||
}
|
Loading…
Reference in New Issue
Block a user