Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[DependencyInjection] Allow using ghost objects for lazy loading services #46741

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

Merged
merged 1 commit into from
Jun 22, 2022
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\ProxyManager\Internal;

use ProxyManager\Configuration;

/**
* @internal
*/
trait LazyLoadingFactoryTrait
{
private readonly ProxyGenerator $generator;

public function __construct(Configuration $config, ProxyGenerator $generator)
{
parent::__construct($config);
$this->generator = $generator;
}

public function getGenerator(): ProxyGenerator
{
return $this->generator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
namespace Symfony\Bridge\ProxyManager\Internal;

use Laminas\Code\Generator\ClassGenerator;
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator;
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use Symfony\Component\DependencyInjection\Definition;

/**
* @internal
*/
class LazyLoadingValueHolderGenerator extends BaseGenerator
class ProxyGenerator implements ProxyGeneratorInterface
{
/**
* {@inheritdoc}
*/
public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []): void
{
parent::generate($originalClass, $classGenerator, $proxyOptions);
(new LazyLoadingValueHolderGenerator())->generate($originalClass, $classGenerator, $proxyOptions);

foreach ($classGenerator->getMethods() as $method) {
if (str_starts_with($originalClass->getFilename(), __FILE__)) {
Expand All @@ -43,7 +44,11 @@ public function generate(\ReflectionClass $originalClass, ClassGenerator $classG
public function getProxifiedClass(Definition $definition): ?string
{
if (!$definition->hasTag('proxy')) {
return ($class = $definition->getClass()) && (class_exists($class) || interface_exists($class, false)) ? $class : null;
if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) {
return null;
}

return (new \ReflectionClass($class))->name;
}
if (!$definition->isLazy()) {
throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": setting the "proxy" tag on a service requires it to be "lazy".', $definition->getClass()));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator;

use ProxyManager\Configuration;
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use ProxyManager\Proxy\LazyLoadingInterface;
use Symfony\Bridge\ProxyManager\Internal\LazyLoadingFactoryTrait;
use Symfony\Bridge\ProxyManager\Internal\ProxyGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
Expand All @@ -25,34 +28,37 @@
*/
class RuntimeInstantiator implements InstantiatorInterface
{
private LazyLoadingValueHolderFactory $factory;
private Configuration $config;
private ProxyGenerator $generator;

public function __construct()
{
$config = new Configuration();
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());

$this->factory = new LazyLoadingValueHolderFactory($config);
$this->config = new Configuration();
$this->config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
$this->generator = new ProxyGenerator();
}

/**
* {@inheritdoc}
*/
public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object
{
return $this->factory->createProxy(
$this->factory->getGenerator()->getProxifiedClass($definition),
function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
$wrappedInstance = $realInstantiator();

$proxy->setProxyInitializer(null);

return true;
},
[
'fluentSafe' => $definition->hasTag('proxy'),
'skipDestructor' => true,
]
);
$proxifiedClass = new \ReflectionClass($this->generator->getProxifiedClass($definition));

$factory = new class($this->config, $this->generator) extends LazyLoadingValueHolderFactory {
use LazyLoadingFactoryTrait;
};

$initializer = static function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
$wrappedInstance = $realInstantiator();
$proxy->setProxyInitializer(null);

return true;
};

return $factory->createProxy($proxifiedClass->name, $initializer, [
'fluentSafe' => $definition->hasTag('proxy'),
'skipDestructor' => true,
]);
}
}
32 changes: 12 additions & 20 deletions src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Laminas\Code\Generator\ClassGenerator;
use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
use Symfony\Bridge\ProxyManager\Internal\ProxyGenerator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;

Expand All @@ -26,21 +27,23 @@
class ProxyDumper implements DumperInterface
{
private string $salt;
private LazyLoadingValueHolderGenerator $proxyGenerator;
private ProxyGenerator $proxyGenerator;
private BaseGeneratorStrategy $classGenerator;

public function __construct(string $salt = '')
{
$this->salt = $salt;
$this->proxyGenerator = new LazyLoadingValueHolderGenerator();
$this->proxyGenerator = new ProxyGenerator();
$this->classGenerator = new BaseGeneratorStrategy();
}

/**
* {@inheritdoc}
*/
public function isProxyCandidate(Definition $definition): bool
public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null): bool
{
$asGhostObject = false;

return ($definition->isLazy() || $definition->hasTag('proxy')) && $this->proxyGenerator->getProxifiedClass($definition);
}

Expand All @@ -55,10 +58,11 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
}

$proxyClass = $this->getProxyClassName($definition);
$proxifiedClass = new \ReflectionClass($this->proxyGenerator->getProxifiedClass($definition));
$proxyClass = $this->getProxyClassName($proxifiedClass->name);

return <<<EOF
if (\$lazyLoad) {
if (true === \$lazyLoad) {
$instantiation \$this->createProxy('$proxyClass', function () {
return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
\$wrappedInstance = $factoryCode;
Expand All @@ -85,20 +89,15 @@ public function getProxyCode(Definition $definition): string
return $code;
}

/**
* Produces the proxy class name for the given definition.
*/
private function getProxyClassName(Definition $definition): string
private function getProxyClassName(string $class): string
{
$class = $this->proxyGenerator->getProxifiedClass($definition);

return preg_replace('/^.*\\\\/', '', $class).'_'.$this->getIdentifierSuffix($definition);
return preg_replace('/^.*\\\\/', '', $class).'_'.substr(hash('sha256', $class.$this->salt), -7);
}

private function generateProxyClass(Definition $definition): ClassGenerator
{
$generatedClass = new ClassGenerator($this->getProxyClassName($definition));
$class = $this->proxyGenerator->getProxifiedClass($definition);
$generatedClass = new ClassGenerator($this->getProxyClassName($class));

$this->proxyGenerator->generate(new \ReflectionClass($class), $generatedClass, [
'fluentSafe' => $definition->hasTag('proxy'),
Expand All @@ -107,11 +106,4 @@ private function generateProxyClass(Definition $definition): ClassGenerator

return $generatedClass;
}

private function getIdentifierSuffix(Definition $definition): string
{
$class = $this->proxyGenerator->getProxifiedClass($definition);

return substr(hash('sha256', $class.$this->salt), -7);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class LazyServiceProjectServiceContainer extends Container
{%a
protected function getFooService($lazyLoad = true)
{
if ($lazyLoad) {
if (true === $lazyLoad) {
return $this->services['foo'] = $this->createProxy('stdClass_%s', function () {
return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
$wrappedInstance = $this->getFooService(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

public function getFooService($lazyLoad = true)
{
if ($lazyLoad) {
if (true === $lazyLoad) {
return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () {
return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
$wrappedInstance = $this->getFooService(false);
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.2
---

* Add argument `&$asGhostObject` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services

6.1
---

Expand Down
33 changes: 25 additions & 8 deletions src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Symfony\Component\VarExporter\Hydrator;

/**
* ContainerBuilder is a DI container that provides an API to easily describe services.
Expand Down Expand Up @@ -974,7 +975,7 @@ public function findDefinition(string $id): Definition
* @throws RuntimeException When the service is a synthetic service
* @throws InvalidArgumentException When configure callable is not callable
*/
private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool $tryProxy = true): mixed
private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool|object $tryProxy = true): mixed
{
if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) {
return $inlineServices[$h];
Expand All @@ -993,12 +994,12 @@ private function createService(Definition $definition, array &$inlineServices, b
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
}

if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
$proxy = $proxy->instantiateProxy(
$this,
$definition,
$id, function () use ($definition, &$inlineServices, $id) {
return $this->createService($definition, $inlineServices, true, $id, false);
$id, function ($proxy = false) use ($definition, &$inlineServices, $id) {
return $this->createService($definition, $inlineServices, true, $id, $proxy);
}
);
$this->shareService($definition, $proxy, $id, $inlineServices);
Expand Down Expand Up @@ -1029,13 +1030,21 @@ private function createService(Definition $definition, array &$inlineServices, b

$arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($arguments)), $inlineServices, $isConstructorArgument);

if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) {
if (null !== $id && $definition->isShared() && isset($this->services[$id]) && (true === $tryProxy || !$definition->isLazy())) {
return $this->services[$id];
}

if (null !== $factory) {
$service = $factory(...$arguments);

if (\is_object($tryProxy)) {
if (\get_class($service) !== $definition->getClass()) {
throw new LogicException(sprintf('Lazy service of type "%s" cannot be hydrated because its factory returned an unexpected instance of "%s". Try adding the "proxy" tag to the corresponding service definition with attribute "interface" set to "%1$s".', $definition->getClass(), get_debug_type($service)));
}

$tryProxy = Hydrator::hydrate($tryProxy, (array) $service);
}

if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) {
$r = new \ReflectionClass($factory[0]);

Expand All @@ -1046,7 +1055,15 @@ private function createService(Definition $definition, array &$inlineServices, b
} else {
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));

$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments));
if (\is_object($tryProxy)) {
if ($r->getConstructor()) {
$tryProxy->__construct(...array_values($arguments));
}

$service = $tryProxy;
} else {
$service = $r->getConstructor() ? $r->newInstanceArgs(array_values($arguments)) : $r->newInstance();
}

if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name);
Expand All @@ -1060,7 +1077,7 @@ private function createService(Definition $definition, array &$inlineServices, b
}
}

if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) {
if (null === $lastWitherIndex && (true === $tryProxy || !$definition->isLazy())) {
// share only if proxying failed, or if not a proxy, and if no withers are found
$this->shareService($definition, $service, $id, $inlineServices);
}
Expand All @@ -1073,7 +1090,7 @@ private function createService(Definition $definition, array &$inlineServices, b
foreach ($definition->getMethodCalls() as $k => $call) {
$service = $this->callMethod($service, $call, $inlineServices);

if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) {
if ($lastWitherIndex === $k && (true === $tryProxy || !$definition->isLazy())) {
// share only if proxying failed, or if not a proxy, and this is the last wither
$this->shareService($definition, $service, $id, $inlineServices);
}
Expand Down
Loading