monitaurus
monitaurus

Reputation: 84

Changing priority of a subscriber coming from a vendor's bundle

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 :

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.

Upvotes: 1

Views: 1332

Answers (1)

Will B.
Will B.

Reputation: 18426

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()));
    }

Upvotes: 2

Related Questions