5

I got a problem to add custom logic to some Symfony classes.

SwitchUserListener

I want to add a check, that a user cannot switch to a another user, which have more rights/roles, than the initial user.

First attempt

Overwrite the parameter in the security_listeners.xml with the key: security.authentication.switchuser_listener.class But where can I overwrite it?
In the security.yml it didn't work:

security:
    ...
    authentication:
        switchuser_listener:
             class:  Symfony\Component\Security\Http\Firewall\SwitchUserListener

Second attempt

Overwrite the service for the SwitchUserListner service id: security.authentication.switchuser_listener

I create the same service in my service.xml of my bundle, but my class was not used / called.

Another idea was to overwrite only the class, but that only works for bundles, but the SwitchUserListener was not in the SecurityBundle, it was in the symfony component directory and that seemed to me as a really bad idea to overwrite the SecurityBundle

Third attempt

Now I get the solution: First time I didn't realize that the dispatcher call listener for the SWTICH_USER event in the SwitchUserListener:

$switchEvent = new SwitchUserEvent($request, $token->getUser());
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);

So I need only to create a service with the special tag for this event type:

<tag name="kernel.event_listener" event="security.switch_user" method="onSecuritySwitchUser" />

And do the check in the given method.

This seems to be a better solution thatn the other two. But there is still a problem. In my listener for the SwitchUserEvent I need to ignore my custom check if the user wants to exit the switched user. So I need to check the requested path: ignore if path containts '?switch_user=_exit'
But the path (URL parameter) can be changed:

# app/config/security.yml
security:
    firewalls:
        main:
            # ...
            switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }

But in my bundle I can't read this parameter, because it will not be passed to the service container. It will be passed to the constructor of the SwitchUserListner class and will be saved there as private attribute, never accessable (without Reflection) from outside. (that happens here: SecurityExtension.php line 591) So what to do? Define the parameter twice go against DRY. Use Reflection?

And the other point is that there aren' every time events that will be fired on which I write a subscriber class. So what would be another / best solution for it?
I ask this question because I will get some similar problem where I want to add or overwrite something of the symfony intern components.

TemplateGuesser

I wanted to modify the TemplateGuesser: For a specific bundle all Templates which has the annotation @Tempalte the tempate file should be located with the controller TestController#showAction at this path:

Resources/views/customDir/Test/show.html.twig

So the guesser should be put and locate everything into a additional folder customDir instead of using only views. When using the render function with a specific template, the guesser should ignore the annotation.

I created my own Guesser and overwrite the service id: sensio_framework_extra.view.guesser and in comparision to the SwitchUserListener this time my class is really called instead of the original guesser. Why it works here but not with the SwitchUserListener?

Is this a good solution at all? I also tried to add a second listener, which calls the TemplateGuesser, its the service sensio_framework_extra.view.listener with the class Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener But that didn't work.

timaschew
  • 16,254
  • 6
  • 61
  • 78

1 Answers1

0

Whenever you need to add custom logic or extend the framework behaviour, you can use and abuse the container configuration. That means you can overwrite pretty much every service Symfony defines by just creating a new class that extends that service – or not, really – and creating the service definition for it with the same key as the original service you wanted to extend or change behaviour.

For instance, Symfony has a base template guesser registered as a service with the sensio_framework_extra.view.guesser id. If you want to extend that or change behaviour, you only need to create your own class and register it with the same id of the original service – remember that the bundles loading order affects the service definitons with the same id, where the last one loaded is the one that will be created.

That should solve both of your problems.

Daniel Ribeiro
  • 10,156
  • 12
  • 47
  • 79