diff --git a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php index 5e958d1865300..269eb517ba1a3 100644 --- a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php +++ b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php @@ -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. @@ -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.'); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index 69b7239655944..85455d222194e 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -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; /** @@ -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(); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index 8bc017bb8df71..bef5e5062bb95 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -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; @@ -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']); } } diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index d141f99ba6ee5..1e4333d8ad96d 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -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\\": "" }, diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index b4c389ff68a99..0e80a238a6e30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -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", diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index fb8519ab9a04a..1788b1685573a 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -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", diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index e72239311e298..bebf228f988c3 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -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 --- diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 799a2a48117d3..7664140bcf715 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -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; @@ -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; /** @@ -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, diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 435a28b6b89a2..fc9a0fdb77e3b 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -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; @@ -89,6 +90,7 @@ class PhpDumper extends Dumper private string $serviceLocatorTag; private array $exportedVariables = []; private string $baseClass; + private string $class; private ProxyDumper $proxyDumper; /** @@ -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); @@ -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() diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php new file mode 100644 index 0000000000000..363a8df9fc875 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php @@ -0,0 +1,43 @@ + + * + * 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
+ */
+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);
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php
index 38ccca525b1ef..c25673422630f 100644
--- a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php
+++ b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php
@@ -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
+ */
+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 <<