Skip to content

Commit e2e15f9

Browse files
bug #60535 [DoctrineBridge] Fix resetting the manager when using native lazy objects (HypeMC)
This PR was merged into the 7.3 branch. Discussion ---------- [DoctrineBridge] Fix resetting the manager when using native lazy objects | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | - | License | MIT PR #59913 doesn't seem to work as expected. For example, a simple test like this: ```php class DoctrineTest extends KernelTestCase { public function testManagerRegistryReset(): void { $entityManager = self::getContainer()->get(EntityManagerInterface::class); $entityManager->close(); self::assertFalse($entityManager->isOpen()); $managerRegistry = self::getContainer()->get(ManagerRegistry::class); $managerRegistry->resetManager(); self::assertTrue($entityManager->isOpen()); } } ``` fails with: > There was 1 error: > > 1) App\Tests\DoctrineTest::testManagerRegistryReset > Error: Call to a member function __construct() on false > > /project/var/cache/test/ContainerTVZSKa5/App_KernelTestDebugContainer.php:709 > /project/vendor/symfony/doctrine-bridge/ManagerRegistry.php:91 > /project/vendor/doctrine/orm/src/EntityManager.php:534 > /project/tests/DoctrineTest.php:21 Here's the dumped container for reference: ```php protected static function getDoctrine_Orm_DefaultEntityManagerService($container, $lazyLoad = true) { if (true === $lazyLoad) { return $container->services['doctrine.orm.default_entity_manager'] = new \ReflectionClass('Doctrine\ORM\EntityManager')->newLazyGhost(static function ($proxy) use ($container) { self::getDoctrine_Orm_DefaultEntityManagerService($container, $proxy); }); } include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/Proxy/Autoloader.php'; include_once \dirname(__DIR__, 4).'/vendor/doctrine/persistence/src/Persistence/ObjectManager.php'; include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/EntityManagerInterface.php'; include_once \dirname(__DIR__, 4).'/vendor/doctrine/orm/src/EntityManager.php'; $instance = ($lazyLoad->__construct(($container->services['doctrine.dbal.default_connection'] ?? self::getDoctrine_Dbal_DefaultConnectionService($container)), ($container->privates['doctrine.orm.default_configuration'] ?? self::getDoctrine_Orm_DefaultConfigurationService($container)), ($container->privates['doctrine.dbal.default_connection.event_manager'] ?? self::getDoctrine_Dbal_DefaultConnection_EventManagerService($container))) && false ?: $lazyLoad); ($container->privates['doctrine.orm.default_manager_configurator'] ??= new \Doctrine\Bundle\DoctrineBundle\ManagerConfigurator([], []))->configure($instance); return $instance; } ``` cc `@nicolas`-grekas Commits ------- 5972d98 [DoctrineBridge] Fix resetting the manager when using native lazy objects
2 parents f136542 + 5972d98 commit e2e15f9

File tree

3 files changed

+99
-18
lines changed

3 files changed

+99
-18
lines changed

src/Symfony/Bridge/Doctrine/ManagerRegistry.php

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,35 @@ function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) {
8080
return;
8181
}
8282

83-
try {
84-
$r->resetAsLazyProxy($manager, \Closure::bind(
85-
function () use ($name) {
86-
$name = $this->aliases[$name] ?? $name;
83+
$asProxy = $r->initializeLazyObject($manager) !== $manager;
84+
$initializer = \Closure::bind(
85+
function ($manager) use ($name, $asProxy) {
86+
$name = $this->aliases[$name] ?? $name;
87+
if ($asProxy) {
88+
$manager = false;
89+
}
90+
91+
$manager = match (true) {
92+
isset($this->fileMap[$name]) => $this->load($this->fileMap[$name], $manager),
93+
!$method = $this->methodMap[$name] ?? null => throw new \LogicException(\sprintf('The "%s" service is synthetic and cannot be reset.', $name)),
94+
(new \ReflectionMethod($this, $method))->isStatic() => $this->{$method}($this, $manager),
95+
default => $this->{$method}($manager),
96+
};
97+
98+
if ($asProxy) {
99+
return $manager;
100+
}
101+
},
102+
$this->container,
103+
Container::class
104+
);
87105

88-
return match (true) {
89-
isset($this->fileMap[$name]) => $this->load($this->fileMap[$name], false),
90-
!$method = $this->methodMap[$name] ?? null => throw new \LogicException(\sprintf('The "%s" service is synthetic and cannot be reset.', $name)),
91-
(new \ReflectionMethod($this, $method))->isStatic() => $this->{$method}($this, false),
92-
default => $this->{$method}(false),
93-
};
94-
},
95-
$this->container,
96-
Container::class
97-
));
106+
try {
107+
if ($asProxy) {
108+
$r->resetAsLazyProxy($manager, $initializer);
109+
} else {
110+
$r->resetAsLazyGhost($manager, $initializer);
111+
}
98112
} catch (\Error $e) {
99113
if (__FILE__ !== $e->getFile()) {
100114
throw $e;

src/Symfony/Bridge/Doctrine/Tests/Fixtures/DummyManager.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
413

514
use Doctrine\Persistence\Mapping\ClassMetadata;
@@ -11,6 +20,10 @@ class DummyManager implements ObjectManager
1120
{
1221
public $bar;
1322

23+
public function __construct()
24+
{
25+
}
26+
1427
public function find($className, $id): ?object
1528
{
1629
}

src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
class ManagerRegistryTest extends TestCase
2424
{
25-
public static function setUpBeforeClass(): void
25+
public function testResetService()
2626
{
2727
$container = new ContainerBuilder();
2828

@@ -32,10 +32,7 @@ public static function setUpBeforeClass(): void
3232

3333
$dumper = new PhpDumper($container);
3434
eval('?>'.$dumper->dump(['class' => 'LazyServiceDoctrineBridgeContainer']));
35-
}
3635

37-
public function testResetService()
38-
{
3936
$container = new \LazyServiceDoctrineBridgeContainer();
4037

4138
$registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName');
@@ -52,6 +49,63 @@ public function testResetService()
5249
$this->assertFalse(isset($foo->bar));
5350
}
5451

52+
/**
53+
* @requires PHP 8.4
54+
*
55+
* @dataProvider provideResetServiceWithNativeLazyObjectsCases
56+
*/
57+
public function testResetServiceWithNativeLazyObjects(string $class)
58+
{
59+
$container = new $class();
60+
61+
$registry = new TestManagerRegistry(
62+
'irrelevant',
63+
[],
64+
['defaultManager' => 'foo'],
65+
'irrelevant',
66+
'defaultManager',
67+
'irrelevant',
68+
);
69+
$registry->setTestContainer($container);
70+
71+
$foo = $container->get('foo');
72+
self::assertSame(DummyManager::class, $foo::class);
73+
74+
$foo->bar = 123;
75+
self::assertTrue(isset($foo->bar));
76+
77+
$registry->resetManager();
78+
79+
self::assertSame($foo, $container->get('foo'));
80+
self::assertSame(DummyManager::class, $foo::class);
81+
self::assertFalse(isset($foo->bar));
82+
}
83+
84+
public static function provideResetServiceWithNativeLazyObjectsCases(): iterable
85+
{
86+
$container = new ContainerBuilder();
87+
88+
$container->register('foo', DummyManager::class)->setPublic(true);
89+
$container->getDefinition('foo')->setLazy(true);
90+
$container->compile();
91+
92+
$dumper = new PhpDumper($container);
93+
94+
eval('?>'.$dumper->dump(['class' => 'NativeLazyServiceDoctrineBridgeContainer']));
95+
96+
yield ['NativeLazyServiceDoctrineBridgeContainer'];
97+
98+
$dumps = $dumper->dump(['class' => 'NativeLazyServiceDoctrineBridgeContainerAsFiles', 'as_files' => true]);
99+
100+
$lastDump = array_pop($dumps);
101+
foreach (array_reverse($dumps) as $dump) {
102+
eval('?>'.$dump);
103+
}
104+
eval('?>'.$lastDump);
105+
106+
yield ['NativeLazyServiceDoctrineBridgeContainerAsFiles'];
107+
}
108+
55109
/**
56110
* When performing an entity manager lazy service reset, the reset operations may re-use the container
57111
* to create a "fresh" service: when doing so, it can happen that the "fresh" service is itself a proxy.

0 commit comments

Comments
 (0)