Search code examples
phpsymfonysymfony-2.3

Symfony inject EntityManager in SwitchUserListener


My question is about how to inject the entity manager in the SwitchUserListener that already has 9 parameters.

I have a custom switch user flow where I need to set the ExternalClient passed along with the _switch_user parameter (?_switch_user=user1&external_client_id=1) in the session. I first have to fetch the ExternalClient from the database before I can set it.

In parameters.yml I've added

security.authentication.switchuser_listener.class: App\Bundle\Listener\SwitchUserListener

And for the content of App\Bundle\Listener\SwitchUserListener I used the Symfony SwitchUserListener Symfony\Component\Security\Http\Firewall\SwitchUserListener.php

Everything works and when I fetch the external_client_id parameter from the request variable in the listener it is populated. But I can't seem to get access to the entity manager.

Things I've tried:

  1. Add decorator in services.yml

    app.decorating_switch_user:
      class:     App\Bundle\Listener\SwitchUserListener
      decorates: security.authentication.switchuser_listener
      arguments: ['@app.decorating_switch_user.inner', '@doctrine.orm.entity_manager']
      public:    false
    
  2. Overriding parent dependencies in services.yml

    security.authentication.switchuser_listener:
      abstract:  true
    
    test:
      class: "%security.authentication.switchuser_listener.class%"
      parent: security.authentication.switchuser_listener
      public: false
      # appends the '@doctrine.orm.entity_manager' argument to the parent
      # argument list
      arguments: ['@doctrine.orm.entity_manager']
    
  3. Listen to SwitchUserEvent instead

     app.switch_user_listener:
       class: App\Bundle\Listener\SwitchUserListener
       tags:
          - { name: kernel.event_listener, event: security.switch_user, method: onSwitchUser }
    

    Here I've replace the contents of 'App\Bundle\Listener\SwitchUserListener' with:

    class SwitchUserListener
    {
        public function onSwitchUser(SwitchUserEvent $event)
        {
            $request = $event->getRequest();
            echo "<pre>";
            dump($externalClientId = $request->get('external_client_id'));
            echo "</pre>";
            exit;
        }
    }
    

    I'm getting the external_client_id as well with this attempt but I have no idea how to inject the entity manager. And even If I did, I'd have no way of getting the original user that initiated the _switch_user request. SwitchUserEvent only has access to the getTargetUser() method.

Conclusion:

If anybody has experience with this topic and is willing to share it that would be great. Ideally I would add the entity manager service to the previous 9 arguments of the __construct function. I'm expanding that class just like Matt is doing here: Symfony2: Making impersonating a user more friendly


Solution

  • You can override the service as follows. You may need to look up/change the concrete order of service arguments as it changed between symfony versions. Some arguments like $providerKey can be left empty as they will be changed/injected automatically by symfony.

    In order to save some time coding you won't need to override the constructor if you use setter injection.

    A look at Symfony's default SwitchUserListener (switch to the tag/version used in your application) will help when implementing your new handle method.

    # app/config/services.yml
    services:
      # [..]
      security.authentication.switchuser_listener:
        class: 'Your\Namespace\SwitchUserListener'
        public: false
        abstract: true
        arguments: 
          - '@security.context'
          - ~
          - '@security.user_checker' 
          - ~
          - '@security.access.decision_manager' 
          - '@?logger' 
          - '_switch_user' 
          - ~
          - '@?event_dispatcher' 
          - ~
        calls:
          - [ 'setEntityManager', [ '@doctrine.orm.entity_manager' ]]
        tags:
          - { name: monolog.logger, channel: security }
    

    Now your SwitchUserListener might look like this:

    namespace Your\Namespace;
    
    use Symfony\Component\Security\Http\Firewall\SwitchUserListener as DefaultListener;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\HttpKernel\Event\GetResponseEvent;
    
    class SwitchUserListener extends DefaultListener
    {
        /** @var EntityManagerInterface */
        protected $em;
    
        public function setEntityManager(EntityManagerInterface $em)
        {
            $this->em = $em;
        }
    
        /**
         * Handles the switch to another user.
         *
         * @throws \LogicException if switching to a user failed
         */
         public function handle(GetResponseEvent $event)
         {
              // Do your custom switching logic here
         }
    }
    

    Don't forget to clear your cache!