Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy-load on a per-property base (removes doctrine proxies, replaced by ProxyManager) #1241

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
],
"minimum-stability": "dev",
"require": {
"php": ">=5.4",
"php": "~7.0",
"ext-pdo": "*",
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.7-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.7-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5|~3.0"
"symfony/console": "~2.5|~3.0",
"ocramius/proxy-manager": "~2.0"
},
"require-dev": {
"symfony/yaml": "~2.3|~3.0",
Expand Down
4 changes: 2 additions & 2 deletions lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use ProxyManager\Proxy\GhostObjectInterface;

/**
* Default query cache implementation.
Expand Down Expand Up @@ -264,7 +264,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $h
$metadata = $this->em->getClassMetadata($rsm->aliasMap[$rsm->parentAliasMap[$alias]]);
$assoc = $metadata->associationMappings[$name];

if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof GhostObjectInterface) {
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PostLoadEventDispatcher;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Proxy\Proxy;
use ProxyManager\Proxy\GhostObjectInterface;

/**
* The ObjectHydrator constructs an object graph out of an SQL result set.
Expand Down Expand Up @@ -430,7 +430,7 @@ protected function hydrateRowData(array $row, array &$result)
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);

if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) {
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof GhostObjectInterface && !$reflFieldValue->isProxyInitialized())) {
// we only need to take action if this value is null,
// we refresh the entity or its an unitialized proxy.
if (isset($nonemptyComponents[$dqlAlias])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
namespace Doctrine\ORM\Persisters\Collection;

use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Utility\PersisterHelper;
Expand Down
175 changes: 136 additions & 39 deletions lib/Doctrine/ORM/Proxy/ProxyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,22 @@

use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\Common\Proxy\Proxy as BaseProxy;
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
use Doctrine\Common\Proxy\Exception\OutOfBoundsException;
use Doctrine\Common\Proxy\ProxyDefinition;
use Doctrine\Common\Proxy\ProxyGenerator;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\EntityNotFoundException;
use Doctrine\ORM\Utility\IdentifierFlattener;
use ProxyManager\Generator\ClassGenerator;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use ProxyManager\Proxy\GhostObjectInterface;
use ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ReflectionClass;
use ReflectionProperty;

/**
* This factory is used to create proxy objects for entities at runtime.
Expand Down Expand Up @@ -76,7 +84,7 @@ public function __construct(EntityManagerInterface $em, $proxyDir, $proxyNs, $au
{
$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);

$proxyGenerator->setPlaceholder('baseProxyInterface', 'Doctrine\ORM\Proxy\Proxy');
$proxyGenerator->setPlaceholder('baseProxyInterface', Proxy::class);
parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate);

$this->em = $em;
Expand All @@ -85,6 +93,84 @@ public function __construct(EntityManagerInterface $em, $proxyDir, $proxyNs, $au
$this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory());
}

/**
* {@inheritDoc}
*
* @return GhostObjectInterface
*/
public function getProxy($className, array $identifier)
{
$definition = $this->createProxyDefinition($className);
$fqcn = $definition->proxyClassName;

if (! class_exists($fqcn, false)) {
$generatorStrategy = new EvaluatingGeneratorStrategy();
$proxyGenerator = new ClassGenerator();
$skippedProperties = array_filter(
Properties::fromReflectionClass(new ReflectionClass($className))->getInstanceProperties(),
function (ReflectionProperty $property) use ($definition) {
return ! in_array(
$property->getName(),
array_map(
function (ReflectionProperty $property) {
return $property->getName();
},
$definition->reflectionFields
)
)
|| in_array($property->getName(), $definition->identifierFields);
}
);

$proxyGenerator->setName($fqcn);

(new LazyLoadingGhostGenerator())->generate(
$this->em->getClassMetadata($className)->getReflectionClass(),
$proxyGenerator,
[
'skippedProperties' => array_map([$this, 'getInternalReflectionPropertyName'], $skippedProperties)
]
);

$generatorStrategy->generate($proxyGenerator);
}

$proxy = $fqcn::staticProxyConstructor($definition->initializer/*, $definition->cloner*/);

foreach ($definition->identifierFields as $idField) {
if (! isset($identifier[$idField])) {
throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField);
}

$definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]);
}

return $proxy;
}

/**
* Reset initialization/cloning logic for an un-initialized proxy
*
* @param GhostObjectInterface $proxy
*
* @return GhostObjectInterface
*
* @throws \Doctrine\Common\Proxy\Exception\InvalidArgumentException
*/
public function resetUninitializedProxy(GhostObjectInterface $proxy)
{
if ($proxy->isProxyInitialized()) {
throw InvalidArgumentException::unitializedProxyExpected($proxy);
}

$className = ClassUtils::getClass($proxy);
$definition = $this->createProxyDefinition($className);

$proxy->setProxyInitializer($definition->initializer);

return $proxy;
}

/**
* {@inheritDoc}
*/
Expand All @@ -101,12 +187,13 @@ protected function createProxyDefinition($className)
{
$classMetadata = $this->em->getClassMetadata($className);
$entityPersister = $this->uow->getEntityPersister($className);
$cloner = $this->createCloner($classMetadata, $entityPersister);

return new ProxyDefinition(
ClassUtils::generateProxyClassName($className, $this->proxyNs),
$classMetadata->getIdentifierFieldNames(),
$classMetadata->getReflectionProperties(),
$this->createInitializer($classMetadata, $entityPersister),
$this->createInitializer($classMetadata, $entityPersister, $cloner),
$this->createCloner($classMetadata, $entityPersister)
);
}
Expand All @@ -116,52 +203,44 @@ protected function createProxyDefinition($className)
*
* @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $entityPersister
* @param \Closure $cloner
*
* @return \Closure
*
* @throws \Doctrine\ORM\EntityNotFoundException
*/
private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister)
private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, \Closure $cloner)
{
$wakeupProxy = $classMetadata->getReflectionClass()->hasMethod('__wakeup');

return function (BaseProxy $proxy) use ($entityPersister, $classMetadata, $wakeupProxy) {
$initializer = $proxy->__getInitializer();
$cloner = $proxy->__getCloner();

$proxy->__setInitializer(null);
$proxy->__setCloner(null);

if ($proxy->__isInitialized()) {
return;
return function (
GhostObjectInterface $proxy,
$method,
$parameters,
& $initializer
) use (
$entityPersister,
$classMetadata,
$cloner
) {
if (! $initializer) {
return false;
}

$properties = $proxy->__getLazyProperties();
if ('__clone' === strtolower($method)) {
$cloner($proxy, $initializer);

foreach ($properties as $propertyName => $property) {
if ( ! isset($proxy->$propertyName)) {
$proxy->$propertyName = $properties[$propertyName];
}
}

$proxy->__setInitialized(true);

if ($wakeupProxy) {
$proxy->__wakeup();
return true;
}

$identifier = $classMetadata->getIdentifierValues($proxy);
$initializerBkp = $initializer;
$initializer = null;

if (null === $entityPersister->loadById($identifier, $proxy)) {
$proxy->__setInitializer($initializer);
$proxy->__setCloner($cloner);
$proxy->__setInitialized(false);
if (null === $entityPersister->loadById($classMetadata->getIdentifierValues($proxy), $proxy)) {
$initializer = $initializerBkp;

throw EntityNotFoundException::fromClassNameAndIdentifier(
$classMetadata->getName(),
$this->identifierFlattener->flattenIdentifier($classMetadata, $identifier)
);
throw new EntityNotFoundException($classMetadata->getName());
}

return true;
};
}

Expand All @@ -177,13 +256,12 @@ private function createInitializer(ClassMetadata $classMetadata, EntityPersister
*/
private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister)
{
return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) {
if ($proxy->__isInitialized()) {
return function (GhostObjectInterface $proxy, & $initializer) use ($entityPersister, $classMetadata) {
if ($proxy->isProxyInitialized()) {
return;
}

$proxy->__setInitialized(true);
$proxy->__setInitializer(null);
$initializer = null;

$class = $entityPersister->getClassMetadata();
$identifier = $classMetadata->getIdentifierValues($proxy);
Expand All @@ -206,4 +284,23 @@ private function createCloner(ClassMetadata $classMetadata, EntityPersister $ent
}
};
}

/**
* @param ReflectionProperty $reflectionProperty
*
* @return string
*/
private function getInternalReflectionPropertyName(ReflectionProperty $reflectionProperty)
{
if ($reflectionProperty->isProtected()) {
return "\0*\0" . $reflectionProperty->getName();
}

if ($reflectionProperty->isPrivate()) {
return "\0" . $reflectionProperty->getDeclaringClass()->getName()
. "\0" . $reflectionProperty->getName();
}

return $reflectionProperty->getName();
}
}
4 changes: 3 additions & 1 deletion lib/Doctrine/ORM/Tools/DebugUnitOfWorkListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\EntityManager;
use ProxyManager\Proxy\GhostObjectInterface;

/**
* Use this logger to dump the identity map during the onFlush event. This is useful for debugging
Expand Down Expand Up @@ -103,7 +105,7 @@ public function dumpIdentityMap(EntityManagerInterface $em)
if ($value === null) {
fwrite($fh, " NULL\n");
} else {
if ($value instanceof Proxy && !$value->__isInitialized()) {
if ($value instanceof GhostObjectInterface && !$value->isProxyInitialized()) {
fwrite($fh, "[PROXY] ");
}

Expand Down
Loading