Skip to content

Commit 6cd2150

Browse files
authored
Fix security context missing in Doctrine entity listeners after kernel reboot (#236)
_getEntityManager() persisted the EntityManager as a permanent service, so it was carried across kernel reboots and re-injected into the freshly booted container. An EntityManager cannot survive a reboot: its ListenersInvoker is bound (readonly) to the container it was built in, so lazy entity listeners resolve dependencies like security.token_storage from that now-stale container. The logged-in user was therefore invisible inside the listener while visible in the controller. In pure Symfony (WebTestCase) there is no reboot, one container, so listener and controller share the same context. Fix: resolve the EntityManager fresh from the current container on every call instead of caching it as a permanent service. Only doctrine.dbal.default_connection stays persistent, which is what keeps the open test transaction alive across reboots; the freshly rebuilt EntityManager runs on top of it, with every dependency (security included) wired to the current request. This also keeps the module's Doctrine helpers and the application on a single identity map, matching pure Symfony. Fixes #34, #90, #150, #151
1 parent 81c0365 commit 6cd2150

1 file changed

Lines changed: 15 additions & 8 deletions

File tree

src/Codeception/Module/Symfony.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -272,24 +272,31 @@ protected function onReconfigure(array $settings = []): void
272272

273273
/**
274274
* Retrieve the Doctrine EntityManager.
275-
* EntityManager service is retrieved once and then reused.
275+
*
276+
* The EntityManager is resolved fresh from the current container on every
277+
* call instead of being cached, so it always matches the one the kernel
278+
* uses for the request in flight. After a reboot a persisted EntityManager
279+
* would keep an immutable ListenersInvoker bound to the old container,
280+
* splitting the identity map (and the security context lazy entity
281+
* listeners see) from the application's. Only the DBAL connection is kept
282+
* persistent: that preserves the open test transaction across reboots while
283+
* the freshly rebuilt EntityManager runs on top of it.
284+
*
285+
* @see https://github.com/Codeception/module-symfony/issues/34
276286
*/
277287
public function _getEntityManager(): EntityManagerInterface
278288
{
279289
/** @var non-empty-string $emService */
280290
$emService = $this->config['em_service'];
281291

282-
if (!isset($this->permanentServices[$emService])) {
283-
$this->persistPermanentService($emService);
292+
if (!isset($this->permanentServices['doctrine.dbal.default_connection'])) {
284293
$container = $this->_getContainer();
285-
foreach (['doctrine', 'doctrine.orm.default_entity_manager', 'doctrine.dbal.default_connection'] as $service) {
286-
if ($container->has($service)) {
287-
$this->persistPermanentService($service);
288-
}
294+
if ($container->has('doctrine.dbal.default_connection')) {
295+
$this->persistPermanentService('doctrine.dbal.default_connection');
289296
}
290297
}
291298

292-
$em = $this->permanentServices[$emService];
299+
$em = $this->getService($emService);
293300
if (!$em instanceof EntityManagerInterface) {
294301
Assert::fail(sprintf('Service "%s" is not an instance of EntityManagerInterface.', $emService));
295302
}

0 commit comments

Comments
 (0)