Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circular Reference when injecting Security Context into (Entity Listener) Class

There was 2 questions here saying injecting the whole service container should solve this. But question ... see below (note difference between try 2 & 3) ...

Try 1

public function __construct(SecurityContext $securityContext) {
    $this->securityContext = $securityContext);  
}  

Curcular Reference. Okay ...

Try 2

public function __construct(ContainerInterface $container) {
    $this->securityContext = $container->get('security.context');  
}  

Circular Reference (Why?, I am injecting the container like in try 3 except I got the security context only)

Try 3

public function __construct(ContainerInterface $container) {
    $this->container = $container;  
}  

Works.

like image 322
Jiew Meng Avatar asked Jan 03 '12 06:01

Jiew Meng


3 Answers

This happens because your security context depends on this listener, probably via the entity manager being injected into a user provider. The best solution is to inject the container into the listener and access the security context lazily.

I typically don't like injecting the entire container into a service, but make an exception with Doctrine listeners because they are eagerly loaded and should therefore be as lazy as possible.

like image 172
Kris Wallsmith Avatar answered Oct 30 '22 22:10

Kris Wallsmith


As of Symfony 2.6 this issue should be fixed. A pull request has just been accepted into the master. Your problem is described in here. https://github.com/symfony/symfony/pull/11690

As of Symfony 2.6, you can inject the security.token_storage into your listener. This service will contain the token as used by the SecurityContext in <=2.5. In 3.0 this service will replace the SecurityContext::getToken() altogether. You can see a basic change list here: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service

Example usage in 2.6:

Your configuration:

services:
    my.listener:
        class: EntityListener
        arguments:
            - "@security.token_storage"
        tags:
            - { name: doctrine.event_listener, event: prePersist }


Your Listener

use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class EntityListener
{
    private $token_storage;

    public function __construct(TokenStorageInterface $token_storage)
    {
        $this->token_storage = $token_storage;
    }

    public function prePersist(LifeCycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $entity->setCreatedBy($this->token_storage->getToken()->getUsername());
    }
}
like image 43
Anyone Avatar answered Oct 30 '22 23:10

Anyone


The reason "2" fails and "3" does not is because in option 2 you are trying to access the security context immediately from the container when it is likely not populated yet.

As best I can tell, Symfony2 parses through the config and instantiates the service one after the other and then moves onto the handling the rest of the request.

This means you cannot necessarily access the various parts of the container because it may be loading them in a different order. So you have the memory pointer to the container, and store that, but then let the framework finish building the full container before you try to access parts of it. A notable exception to this is when you directly inject the service into another service, at which point the container is making sure it has that service loaded first.

You can see the effects of this by making two services. A and B. A is passed B, and B is passed A. Now you have a circular reference. If you instead passed the container into both A and B, you could not access A from B and B from A without a problem.

like image 32
Wpigott Avatar answered Oct 30 '22 23:10

Wpigott