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
- Disable the vendor subscriber from the Ekino's configuration
- Create your own subscriber / listener with high priority (you said 10 is enough for you)
- Inject the vendor subscriber to your subscriber / listener
- 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 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']
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()));
}