diff --git a/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php new file mode 100644 index 0000000000000..2989d0b61f228 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests; + +use PHPUnit\Framework\TestCase; +use ProxyManager\Proxy\LazyLoadingInterface; +use ProxyManager\Proxy\ValueHolderInterface; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\Filesystem\Filesystem; + +/** + * @group legacy + */ +class LegacyManagerRegistryTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + $test = new PhpDumperTest(); + $test->testDumpContainerWithProxyServiceWillShareProxies(); + } + + public function testResetService() + { + $container = new \LazyServiceProjectServiceContainer(); + + $registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName'); + $registry->setTestContainer($container); + + $foo = $container->get('foo'); + $foo->bar = 123; + $this->assertTrue(isset($foo->bar)); + + $registry->resetManager(); + + $this->assertSame($foo, $container->get('foo')); + $this->assertObjectNotHasAttribute('bar', $foo); + } + + /** + * When performing an entity manager lazy service reset, the reset operations may re-use the container + * to create a "fresh" service: when doing so, it can happen that the "fresh" service is itself a proxy. + * + * Because of that, the proxy will be populated with a wrapped value that is itself a proxy: repeating + * the reset operation keeps increasing this nesting until the application eventually runs into stack + * overflow or memory overflow operations, which can happen for long-running processes that rely on + * services that are reset very often. + */ + public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() + { + // This test scenario only applies to containers composed as a set of generated sources + $this->dumpLazyServiceProjectAsFilesServiceContainer(); + + /** @var ContainerInterface $container */ + $container = new \LazyServiceProjectAsFilesServiceContainer(); + + $registry = new TestManagerRegistry( + 'irrelevant', + [], + ['defaultManager' => 'foo'], + 'irrelevant', + 'defaultManager', + 'irrelevant' + ); + $registry->setTestContainer($container); + + $service = $container->get('foo'); + + self::assertInstanceOf(\stdClass::class, $service); + self::assertInstanceOf(LazyLoadingInterface::class, $service); + self::assertInstanceOf(ValueHolderInterface::class, $service); + self::assertFalse($service->isProxyInitialized()); + + $service->initializeProxy(); + + self::assertTrue($container->initialized('foo')); + self::assertTrue($service->isProxyInitialized()); + + $registry->resetManager(); + $service->initializeProxy(); + + $wrappedValue = $service->getWrappedValueHolderValue(); + self::assertInstanceOf(\stdClass::class, $wrappedValue); + self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue); + self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue); + } + + private function dumpLazyServiceProjectAsFilesServiceContainer() + { + if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) { + return; + } + + $container = new ContainerBuilder(); + + $container->register('foo', \stdClass::class) + ->setPublic(true) + ->setLazy(true); + $container->compile(); + + $fileSystem = new Filesystem(); + + $temporaryPath = $fileSystem->tempnam(sys_get_temp_dir(), 'symfonyManagerRegistryTest'); + $fileSystem->remove($temporaryPath); + $fileSystem->mkdir($temporaryPath); + + $dumper = new PhpDumper($container); + + $dumper->setProxyDumper(new ProxyDumper()); + $containerFiles = $dumper->dump([ + 'class' => 'LazyServiceProjectAsFilesServiceContainer', + 'as_files' => true, + ]); + + array_walk( + $containerFiles, + static function (string $containerSources, string $fileName) use ($temporaryPath): void { + (new Filesystem())->dumpFile($temporaryPath.'/'.$fileName, $containerSources); + } + ); + + require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php index dd7dabcc87db1..e1948356df648 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php @@ -12,27 +12,29 @@ namespace Symfony\Bridge\Doctrine\Tests; use PHPUnit\Framework\TestCase; -use ProxyManager\Proxy\LazyLoadingInterface; -use ProxyManager\Proxy\ValueHolderInterface; -use Symfony\Bridge\Doctrine\ManagerRegistry; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; -use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\VarExporter\LazyObjectInterface; class ManagerRegistryTest extends TestCase { public static function setUpBeforeClass(): void { - $test = new PhpDumperTest(); - $test->testDumpContainerWithProxyServiceWillShareProxies(); + $container = new ContainerBuilder(); + + $container->register('foo', \stdClass::class)->setPublic(true); + $container->getDefinition('foo')->setLazy(true)->addTag('proxy', ['interface' => \stdClass::class]); + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(['class' => 'LazyServiceDoctrineBridgeContainer'])); } public function testResetService() { - $container = new \LazyServiceProjectServiceContainer(); + $container = new \LazyServiceDoctrineBridgeContainer(); $registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName'); $registry->setTestContainer($container); @@ -59,10 +61,10 @@ public function testResetService() public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() { // This test scenario only applies to containers composed as a set of generated sources - $this->dumpLazyServiceProjectAsFilesServiceContainer(); + $this->dumpLazyServiceDoctrineBridgeContainerAsFiles(); /** @var ContainerInterface $container */ - $container = new \LazyServiceProjectAsFilesServiceContainer(); + $container = new \LazyServiceDoctrineBridgeContainerAsFiles(); $registry = new TestManagerRegistry( 'irrelevant', @@ -77,27 +79,25 @@ public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() $service = $container->get('foo'); self::assertInstanceOf(\stdClass::class, $service); - self::assertInstanceOf(LazyLoadingInterface::class, $service); - self::assertInstanceOf(ValueHolderInterface::class, $service); - self::assertFalse($service->isProxyInitialized()); + self::assertInstanceOf(LazyObjectInterface::class, $service); + self::assertFalse($service->isLazyObjectInitialized()); - $service->initializeProxy(); + $service->initializeLazyObject(); self::assertTrue($container->initialized('foo')); - self::assertTrue($service->isProxyInitialized()); + self::assertTrue($service->isLazyObjectInitialized()); $registry->resetManager(); - $service->initializeProxy(); + $service->initializeLazyObject(); - $wrappedValue = $service->getWrappedValueHolderValue(); + $wrappedValue = $service->initializeLazyObject(); self::assertInstanceOf(\stdClass::class, $wrappedValue); - self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue); - self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue); + self::assertNotInstanceOf(LazyObjectInterface::class, $wrappedValue); } - private function dumpLazyServiceProjectAsFilesServiceContainer() + private function dumpLazyServiceDoctrineBridgeContainerAsFiles() { - if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) { + if (class_exists(\LazyServiceDoctrineBridgeContainerAsFiles::class, false)) { return; } @@ -105,7 +105,8 @@ private function dumpLazyServiceProjectAsFilesServiceContainer() $container->register('foo', \stdClass::class) ->setPublic(true) - ->setLazy(true); + ->setLazy(true) + ->addTag('proxy', ['interface' => \stdClass::class]); $container->compile(); $fileSystem = new Filesystem(); @@ -116,9 +117,8 @@ private function dumpLazyServiceProjectAsFilesServiceContainer() $dumper = new PhpDumper($container); - $dumper->setProxyDumper(new ProxyDumper()); $containerFiles = $dumper->dump([ - 'class' => 'LazyServiceProjectAsFilesServiceContainer', + 'class' => 'LazyServiceDoctrineBridgeContainerAsFiles', 'as_files' => true, ]); @@ -129,19 +129,6 @@ static function (string $containerSources, string $fileName) use ($temporaryPath } ); - require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php'; - } -} - -class TestManagerRegistry extends ManagerRegistry -{ - public function setTestContainer($container) - { - $this->container = $container; - } - - public function getAliasNamespace($alias): string - { - return 'Foo'; + require $temporaryPath.'/LazyServiceDoctrineBridgeContainerAsFiles.php'; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php b/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php new file mode 100644 index 0000000000000..aae7ca27df6b2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests; + +use Symfony\Bridge\Doctrine\ManagerRegistry; + +class TestManagerRegistry extends ManagerRegistry +{ + public function setTestContainer($container) + { + $this->container = $container; + } + + public function getAliasNamespace($alias): string + { + return 'Foo'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 5cc4a280a47b0..318fc379711e1 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -28,14 +28,13 @@ "symfony/stopwatch": "^5.4|^6.0", "symfony/cache": "^5.4|^6.0", "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", "symfony/form": "^5.4.9|^6.0.9", "symfony/http-kernel": "^6.2", "symfony/messenger": "^5.4|^6.0", "symfony/doctrine-messenger": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", - "symfony/proxy-manager-bridge": "^5.4|^6.0", "symfony/security-core": "^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", @@ -55,7 +54,7 @@ "doctrine/orm": "<2.7.4", "phpunit/phpunit": "<5.4.3", "symfony/cache": "<5.4", - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.2", "symfony/form": "<5.4", "symfony/http-kernel": "<6.2", "symfony/messenger": "<5.4", diff --git a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md index 3435a4a186494..5ba6cdaf730a1 100644 --- a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md +++ b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate the bridge + 4.2.0 ----- diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 59fcdc022efce..590dc2108e372 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -21,10 +21,14 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; +trigger_deprecation('symfony/proxy-manager-bridge', '6.3', 'The "symfony/proxy-manager-bridge" package is deprecated and can be removed from your dependencies.'); + /** * Runtime lazy loading proxy generator. * * @author Marco Pivetta + * + * @deprecated since Symfony 6.3 */ class RuntimeInstantiator implements InstantiatorInterface { diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 82ff95dc4cc93..b5f3560a4f401 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -17,11 +17,15 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; +trigger_deprecation('symfony/proxy-manager-bridge', '6.3', 'The "symfony/proxy-manager-bridge" package is deprecated and can be removed from your dependencies.'); + /** * Generates dumped PHP code of proxies via reflection. * * @author Marco Pivetta * + * @deprecated since Symfony 6.3 + * * @final */ class ProxyDumper implements DumperInterface diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index 0967f1b1d5b23..b3a470ac0490a 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -24,6 +24,8 @@ * with the ProxyManager bridge. * * @author Marco Pivetta + * + * @group legacy */ class ContainerBuilderTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index aedfff33c56c5..32992796c0ebf 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -22,6 +22,8 @@ * with the ProxyManager bridge. * * @author Marco Pivetta + * + * @group legacy */ class PhpDumperTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index fc04526ced826..fe76f50c53284 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -22,6 +22,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator}. * * @author Marco Pivetta + * + * @group legacy */ class RuntimeInstantiatorTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index 2d7428fac8d4d..61630b3e46938 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -20,6 +20,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper}. * * @author Marco Pivetta + * + * @group legacy */ class ProxyDumperTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 1e4333d8ad96d..23218d85df43b 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -18,7 +18,8 @@ "require": { "php": ">=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", - "symfony/dependency-injection": "^6.2" + "symfony/dependency-injection": "^6.2", + "symfony/deprecation-contracts": "^2.1|^3" }, "require-dev": { "symfony/config": "^6.1"