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.
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.
// /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);
}
}
The decorates option tells the container that the
App\Decorator\EkinoRequestListenerDecorator
service replaces theEkino\NewRelicBundle\Listener\RequestListener
service.
This configuration replaces
Ekino\NewRelicBundle\Listener\RequestListener
with a new one, but keeps a reference of the old one asApp\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']
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()));
}