Home > front end >  Changing priority of a subscriber coming from a vendor's bundle
Changing priority of a subscriber coming from a vendor's bundle

Time:10-24

To set some context, I'm working on a Symfony 4.4 API, which use a vendor named EkinoNewRelicBundle to communicate data to the New Relic API. This vendor uses a subscriber named RequestListener to subscribe on the kernel.request event of Symonfy to define data to send to the New Relic API.

I've got an issue in a specific case, when there is an authentication issue resulting in a 498, another subscriber from the SecurityBundle throw an exception, stopping the request processing. Unfortunately, a method of the EkinoNewRelicBundle subscriber, named setTransactionName, has a lower priority than the SecurityBundle subscriber, resulting in data not properly set by Ekino as the process is stop.

By editing by hand the vendor, I found that a priority 10 on the setTransactionName would be enough to be executed before the SecurityBundle.

I'm looking for a way to edit the priority of the RequestListener priority at runtime. So far, I've tried to :

  • override the configuration, but as the priority is defined in a public static method from a class and seems to be loaded directly in the EventDispatcher, therefore it's no configuration to override ;
  • use a compiler pass to manipulate the definition, but same as above, there is no definition to edit as it seems to be loaded directly in the EventDispatcher ;
  • extend the Ekino subscriber to edit the priority, disabling the listner from the Ekino's configuration, but this results in configuration issue of my newly define subscriber as it can't be autowired.

Isn't there an easy way to change a priority of a subscribed event in a vendor's subscriber?

The maintener did talk about this seven years ago, when the configuration was still accessible with the compiler pass in this EkinoNewRelicBundle issue.

CodePudding user response:

you can do the following

  1. Disable the vendor subscriber from the Ekino's configuration
  2. Create your own subscriber / listener with high priority (you said 10 is enough for you)
  3. Inject the vendor subscriber to your subscriber / listener
  4. On your subscriber / listener event method, call the vendor subscriber method and pass the event to it

CodePudding user response:

As per the How to Override any Part of a Bundle Symfony Documentation.

If you want to modify the services created by a bundle, you can use service decoration.

As the Ekino\NewRelicBundle\Listener\RequestListener is a service that is registered in the configs and autoconfigured by Symfony, you can create a decorator and add a custom getSubscribedEvents() method to override the priorities.

Create the Decorator

// /src/Decorator/EkinoRequestListenerDecorator.php

namespace App\Decorator;

use Ekino\NewRelicBundle\Listener\KernelRequestEvent;
use Ekino\NewRelicBundle\Listener\RequestListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class EkinoRequestListenerDecorator implements EventSubscriberInterface
{

    /** 
     * @var RequestListener
     */
    private $decorated;

    public function __construct(RequestListener $decorated)
    {
        $this->decorated = $decorated;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => [
                ['setApplicationName', 255],
                ['setIgnoreTransaction', 31],
                ['setTransactionName', 10],
            ],
        ];
    }

    public function setApplicationName(KernelRequestEvent $event): void
    {
        $this->decorated->setApplicationName($event);
    }

    public function setIgnoreTransaction(KernelRequestEvent $event): void
    {
        $this->decorated->setIgnoreTransaction($event);
    }

    public function setTransactionName(KernelRequestEvent $event): void
    {
        $this->decorated->setTransactionName($event);
    }
}

Configure the Decorator

The decorates option tells the container that the App\Decorator\EkinoRequestListenerDecorator service replaces the Ekino\NewRelicBundle\Listener\RequestListener service.

This configuration replaces Ekino\NewRelicBundle\Listener\RequestListener with a new one, but keeps a reference of the old one as App\Decorator\EkinoRequestListenerDecorator.inner

# /config/services.yaml

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # ...

    App\Decorator\EkinoRequestListenerDecorator:
        decorates: Ekino\NewRelicBundle\Listener\RequestListener
        arguments: ['@App\Decorator\EkinoRequestListenerDecorator.inner']

Debug the event dispatcher

php bin/console debug:event-dispatcher kernel.request

Resulting kernel.request Event Dispatcher

Before

Registered Listeners for "kernel.request" Event
===============================================

 ------- ------------------------------------------------------------------------------------------------------- ---------- 
  Order   Callable                                                                                                Priority  
 ------- ------------------------------------------------------------------------------------------------------- ---------- 
  #3      Ekino\NewRelicBundle\Listener\RequestListener::setApplicationName()                                     255       
  #8      Ekino\NewRelicBundle\Listener\RequestListener::setIgnoreTransaction()                                   31            
  #21     Ekino\NewRelicBundle\Listener\RequestListener::setTransactionName()                                     -10       
 ------- ------------------------------------------------------------------------------------------------------- ---------- 

After

Registered Listeners for "kernel.request" Event
===============================================

 ------- ------------------------------------------------------------------------------------------------------- ---------- 
  Order   Callable                                                                                                Priority  
 ------- ------------------------------------------------------------------------------------------------------- ----------     
  #3      App\Decorator\EkinoRequestListenerDecorator::setApplicationName()                                       255       
  #8      App\Decorator\EkinoRequestListenerDecorator::setIgnoreTransaction()                                     31
  #13     App\Decorator\EkinoRequestListenerDecorator::setTransactionName()                                       10        
 ------- ------------------------------------------------------------------------------------------------------- ---------- 

Resulting Container Event Listeners

        $instance->addListener('kernel.request', [0 => function () {
            return ($this->privates['App\\Decorator\\EkinoRequestListenerDecorator'] ?? $this->getEkinoRequestListenerDecoratorService());
        }, 1 => 'setApplicationName'], 255);
        $instance->addListener('kernel.request', [0 => function () {
            return ($this->privates['App\\Decorator\\EkinoRequestListenerDecorator'] ?? $this->getEkinoRequestListenerDecoratorService());
        }, 1 => 'setIgnoreTransaction'], 31);
        $instance->addListener('kernel.request', [0 => function () {
            return ($this->privates['App\\Decorator\\EkinoRequestListenerDecorator'] ?? $this->getEkinoRequestListenerDecoratorService());
        }, 1 => 'setTransactionName'], 10);


//...

    protected function getEkinoRequestListenerDecoratorService()
    {
        return $this->privates['App\\Decorator\\EkinoRequestListenerDecorator'] = new \App\Decorator\EkinoRequestListenerDecorator(new \Ekino\NewRelicBundle\Listener\RequestListener(($this->privates['Ekino\\NewRelicBundle\\NewRelic\\Config'] ?? $this->getConfigService()), ($this->privates['Ekino\\NewRelicBundle\\NewRelic\\BlackholeInteractor'] ?? ($this->privates['Ekino\\NewRelicBundle\\NewRelic\\BlackholeInteractor'] = new \Ekino\NewRelicBundle\NewRelic\BlackholeInteractor())), [], [], new \Ekino\NewRelicBundle\TransactionNamingStrategy\RouteNamingStrategy()));
    }
  • Related