module-symfony
module-symfony copied to clipboard
User from Security service is not available in Doctrine Listeners
Hello,
When trying to test a part of our code with an Event Listener like:
<?php
namespace App\EventListener\Doctrine;
use Symfony\Component\Security\Core\Security;
class Listener
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function prePersist(Entity $entity)
{
$user = $this->security->getUser();
$entity->setUser($user);
}
}
$user is always null
I found this SO question with the exact same problem: https://stackoverflow.com/questions/57609821/make-security-available-for-doctrine-onflush-within-functional-test
Do you recommend using the workaround given in the SO answers or something else ?
Hey, @BastienLedon Hmm, in my applications i have a code very similar to yours, but it works perfectly for me.
I don't think what those answers suggest is necessary.
However, there are several things that should be verified, the best way i could help you is in a real application. Can you create a demo of your problem based on this repo?
You can choose a branch for the version of symfony you are using, and from there we can see what the real problem is.
@TavoNiievez Of course, I think you can reproduce here, https://github.com/BastienLedon/codeception-symfony-tests
I've added one other user and just added the amLoggedAs inside those test. (And created a fake Listener)
You can see that inside the controller the user of Security is available but not inside the Listener.
Yep, i can confirm that there is unexpected behavior here.
The Security object requires the symfony container as a parameter.
For some reason in the tests the 'real' container: 'service_container'
is injected instead of the test container: 'test.service_container'
.
In fact, if you manually inject the test container everything works correctly:
public function prePersist(LifecycleEventArgs $entity)
{
$user = $entity->getObject();
if($user instanceof User) {
/** @var ContainerInterface $testContainer */
$testContainer = $this->container->get('test.service_container');
$security = new Security($testContainer);
$user->setUser($security->getUser());
}
}
I'm still not sure if it's Codeception behavior or Symfony itself.
But at least there should be documentation on this.
Lastly, I added an Entity Listener to the test project to be able to test this easily in the future.
In theory, a workaround would be to inject the correct service into the env-specific services config file: services_test.yaml
.
This is most likely related to:
- https://github.com/doctrine/DoctrineBundle/issues/1166
- https://github.com/Codeception/Codeception/issues/3497
Any news on this? I have faced the same issue, spent whole day debugging my tests and I give up :disappointed:
Ok. Problem is in wrong container being used for doctrine subscribers (Maybe for all persistent services? Didn't check)
Let's say there is a service.
<?php
namespace App\Service;
use App\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class LocatorConsumerService implements ServiceSubscriberInterface
{
/**
* @var ContainerInterface
*/
private $locator;
public function __construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public function getUserFromLocator(): ?User
{
$token = $this->locator->get('security.token_storage')->getToken();
$user = null;
if($token) {
$user = $token->getUser();
}
return $user;
}
public static function getSubscribedServices(): array
{
return [
'security.token_storage' => TokenStorageInterface::class
];
}
}
If service is injected into controller, user is logged in:
public function my(LocatorConsumerService $service): Response
{
// ...
$user = $service->getUserFromLocator(); // $user is not null
// ...
}
If service injected into any doctrine subscriber - $user
is null
<?php
namespace App\EventSubscriber;
use App\Service\LocatorConsumerService;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
class MySubscriber implements EventSubscriber
{
/**
* @var LocatorConsumerService
*/
private $service;
public function __construct(LocatorConsumerService $service) {
$this->service = $service;
}
public function postLoad(LifecycleEventArgs $lifecycleEventArgs): void
{
// ...
$user = $this->service->getUserFromLocator(); // $user is null here
// ...
}
public function getSubscribedEvents(): array
{
return [
Events::postLoad
];
}
}
However, if I configure LocatorConsumerService
manually in services_test.yaml
like this:
App\Service\LocatorConsumerService:
arguments:
- '@test.service_container'
$user
is not null in both cases.
I can't figure out which service I have to re-declare this way though. And I'm not sure if such workaround can help at all
@request_stack
also empty from doctrine subscriber. Frustration.
same problem. no solution?
Hello
My simple solution:
services_test.yaml
services:
security.helper:
class: Symfony\Component\Security\Core\Security
arguments:
- '@test.service_container'