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

Skip to content

[DependencyInjection] Use lazy-loading ghost object proxies out of the box #46752

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
Jul 12, 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
10 changes: 9 additions & 1 deletion src/Symfony/Bridge/Doctrine/ManagerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ProxyManager\Proxy\LazyLoadingInterface;
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\VarExporter\LazyGhostObjectInterface;

/**
* References Doctrine connections and entity/document managers.
Expand Down Expand Up @@ -47,8 +48,15 @@ protected function resetService($name): void
}
$manager = $this->container->get($name);

if ($manager instanceof LazyGhostObjectInterface) {
if (!$manager->resetLazyGhostObject()) {
throw new \LogicException(sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name));
}

return;
}
if (!$manager instanceof LazyLoadingInterface) {
throw new \LogicException('Resetting a non-lazy manager service is not supported. '.(interface_exists(LazyLoadingInterface::class) && class_exists(RuntimeInstantiator::class) ? sprintf('Declare the "%s" service as lazy.', $name) : 'Try running "composer require symfony/proxy-manager-bridge".'));
throw new \LogicException(sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name));
}
if ($manager instanceof GhostObjectInterface) {
throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use PHPUnit\Framework\TestCase;
use ProxyManager\Proxy\LazyLoadingInterface;
use ProxyManagerBridgeFooClass;
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
Expand All @@ -31,10 +30,8 @@ public function testCreateProxyServiceWithRuntimeInstantiator()
{
$builder = new ContainerBuilder();

$builder->setProxyInstantiator(new RuntimeInstantiator());

$builder->register('foo1', ProxyManagerBridgeFooClass::class)->setFile(__DIR__.'/Fixtures/includes/foo.php')->setPublic(true);
$builder->getDefinition('foo1')->setLazy(true);
$builder->getDefinition('foo1')->setLazy(true)->addTag('proxy', ['interface' => ProxyManagerBridgeFooClass::class]);

$builder->compile();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

use PHPUnit\Framework\TestCase;
use ProxyManager\Proxy\LazyLoadingInterface;
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;

Expand Down Expand Up @@ -62,14 +61,12 @@ private function dumpLazyServiceProjectServiceContainer()
{
$container = new ContainerBuilder();

$container->register('foo', 'stdClass')->setPublic(true);
$container->getDefinition('foo')->setLazy(true);
$container->register('foo', \stdClass::class)->setPublic(true);
$container->getDefinition('foo')->setLazy(true)->addTag('proxy', ['interface' => \stdClass::class]);
$container->compile();

$dumper = new PhpDumper($container);

$dumper->setProxyDumper(new ProxyDumper());

return $dumper->dump(['class' => 'LazyServiceProjectServiceContainer']);
}
}
4 changes: 2 additions & 2 deletions src/Symfony/Bridge/ProxyManager/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
"require": {
"php": ">=8.1",
"friendsofphp/proxy-manager-lts": "^1.0.2",
"symfony/dependency-injection": "^5.4|^6.0"
"symfony/dependency-injection": "^6.2"
},
"require-dev": {
"symfony/config": "^5.4|^6.0"
"symfony/config": "^6.1"
},
"autoload": {
"psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" },
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"ext-xml": "*",
"symfony/cache": "^5.4|^6.0",
"symfony/config": "^6.1",
"symfony/dependency-injection": "^6.1",
"symfony/dependency-injection": "^6.2",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/error-handler": "^6.1",
"symfony/event-dispatcher": "^5.4|^6.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/SecurityBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"composer-runtime-api": ">=2.1",
"ext-xml": "*",
"symfony/config": "^6.1",
"symfony/dependency-injection": "^6.1",
"symfony/dependency-injection": "^6.2",
"symfony/event-dispatcher": "^5.4|^6.0",
"symfony/http-kernel": "^6.2",
"symfony/http-foundation": "^5.4|^6.0",
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ CHANGELOG
6.2
---

* Use lazy-loading ghost object proxies out of the box
* Add argument `&$asGhostObject` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services
* Add `enum` env var processor
* Add `shuffle` env var processor
* Deprecate `RealServiceInstantiator`

6.1
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\LazyServiceInstantiator;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
Expand Down Expand Up @@ -86,7 +87,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface

private Compiler $compiler;
private bool $trackResources;
private ?InstantiatorInterface $proxyInstantiator = null;
private InstantiatorInterface $proxyInstantiator;
private ExpressionLanguage $expressionLanguage;

/**
Expand Down Expand Up @@ -994,7 +995,7 @@ private function createService(Definition $definition, array &$inlineServices, b
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
}

if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
if (true === $tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator ??= new LazyServiceInstantiator()) || $proxy instanceof RealServiceInstantiator) {
$proxy = $proxy->instantiateProxy(
$this,
$definition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
use Symfony\Component\DependencyInjection\Parameter;
Expand Down Expand Up @@ -89,6 +90,7 @@ class PhpDumper extends Dumper
private string $serviceLocatorTag;
private array $exportedVariables = [];
private string $baseClass;
private string $class;
private ProxyDumper $proxyDumper;

/**
Expand Down Expand Up @@ -154,6 +156,7 @@ public function dump(array $options = []): string|array
$this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']);
$this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : $options['debug']);
$this->serviceLocatorTag = $options['service_locator_tag'];
$this->class = $options['class'];

if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
$baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
Expand Down Expand Up @@ -401,7 +404,7 @@ class %s extends {$options['class']}
*/
private function getProxyDumper(): ProxyDumper
{
return $this->proxyDumper ??= new NullDumper();
return $this->proxyDumper ??= new LazyServiceDumper($this->class);
}

private function analyzeReferences()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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\Component\DependencyInjection\LazyProxy\Instantiator;

use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
use Symfony\Component\VarExporter\LazyGhostObjectInterface;
use Symfony\Component\VarExporter\LazyGhostObjectTrait;

/**
* @author Nicolas Grekas <[email protected]>
*/
final class LazyServiceInstantiator implements InstantiatorInterface
{
/**
* {@inheritdoc}
*/
public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object
{
$dumper = new LazyServiceDumper();

if ($dumper->useProxyManager($definition)) {
return (new RuntimeInstantiator())->instantiateProxy($container, $definition, $id, $realInstantiator);
}

if (!class_exists($proxyClass = $dumper->getProxyClass($definition), false)) {
eval(sprintf('class %s extends %s implements %s { use %s; }', $proxyClass, $definition->getClass(), LazyGhostObjectInterface::class, LazyGhostObjectTrait::class));
}

return $proxyClass::createLazyGhostObject($realInstantiator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;

trigger_deprecation('symfony/dependency-injection', '6.2', 'The "%s" class is deprecated, use "%s" instead.', RealServiceInstantiator::class, LazyServiceInstantiator::class);

/**
* {@inheritdoc}
*
* Noop proxy instantiator - produces the real service instead of a proxy instance.
*
* @author Marco Pivetta <[email protected]>
*
* @deprecated since Symfony 6.2, use LazyServiceInstantiator instead.
*/
class RealServiceInstantiator implements InstantiatorInterface
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?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\Component\DependencyInjection\LazyProxy\PhpDumper;

use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\VarExporter\LazyGhostObjectInterface;
use Symfony\Component\VarExporter\LazyGhostObjectTrait;

/**
* @author Nicolas Grekas <[email protected]>
*/
final class LazyServiceDumper implements DumperInterface
{
public function __construct(
private string $salt = '',
) {
}

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

if ($definition->hasTag('proxy')) {
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()));
}

return true;
}

if (!$definition->isLazy()) {
return false;
}

if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) {
return false;
}

$class = new \ReflectionClass($class);

if ($class->isFinal()) {
throw new InvalidArgumentException(sprintf('Cannot make service of class "%s" lazy because the class is final.', $definition->getClass()));
}

if ($asGhostObject = !$class->isAbstract() && !$class->isInterface() && (\stdClass::class === $class->name || !$class->isInternal())) {
while ($class = $class->getParentClass()) {
if (!$asGhostObject = \stdClass::class === $class->name || !$class->isInternal()) {
break;
}
}
}

return true;
}

/**
* {@inheritdoc}
*/
public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string
{
if ($dumper = $this->useProxyManager($definition)) {
return $dumper->getProxyFactoryCode($definition, $id, $factoryCode);
}

$instantiation = 'return';

if ($definition->isShared()) {
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
}

$proxyClass = $this->getProxyClass($definition);

if (preg_match('/^\$this->\w++\(\$proxy\)$/', $factoryCode)) {
$factoryCode = substr_replace($factoryCode, '(...)', -8);
} else {
$factoryCode = sprintf('function ($proxy) { return %s; }', $factoryCode);
}

return <<<EOF
if (true === \$lazyLoad) {
$instantiation \$this->createProxy('$proxyClass', function () {
return \\$proxyClass::createLazyGhostObject($factoryCode);
});
}


EOF;
}

/**
* {@inheritdoc}
*/
public function getProxyCode(Definition $definition): string
{
if ($dumper = $this->useProxyManager($definition)) {
return $dumper->getProxyCode($definition);
}

$proxyClass = $this->getProxyClass($definition);

return sprintf(<<<EOF
class %s extends \%s implements \%s
{
use \%s;
}

EOF,
$proxyClass,
$definition->getClass(),
LazyGhostObjectInterface::class,
LazyGhostObjectTrait::class
);
}

public function getProxyClass(Definition $definition): string
{
$class = (new \ReflectionClass($definition->getClass()))->name;

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

public function useProxyManager(Definition $definition): ?ProxyDumper
{
if (!$this->isProxyCandidate($definition, $asGhostObject)) {
throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service of class "%s".', $definition->getClass()));
}

if ($asGhostObject) {
return null;
}

if (!class_exists(ProxyDumper::class)) {
throw new LogicException('You cannot use virtual proxies for lazy services as the ProxyManager bridge is not installed. Try running "composer require symfony/proxy-manager-bridge".');
}

return new ProxyDumper($this->salt);
}
}
Loading