-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[QUESTION / PROPOSAL] Is there a way to lazy load listeners? #36
Comments
@andreigabreanu sorry for the late reply. I'd propose a different approach for this. In your situation I'd create a DI specific listener factory which would produce listeners that implement the ListenerInterface, which would resolve the dependencies in the |
How to pass factory product to event? this way below its not lazy load, since it will create object of $emitter->addListener("some.event", $container->get("listener"); |
@Atriedes I don't think it is possible. When the emitter starts to call the listeners, it creates a list of them in the right order. There is no way to add listeners WHILE they are being invoked. You would have to create a decorator/extend the emitter, to make sure those listeners are added before the emitting starts. Something like this: <?php
use League\Container\ContainerInterface;
use League\Event\EmitterTrait;
use League\Event\EmitterInterface;
class LazyEmitter implements EmitterInterface
{
use EmitterTrait;
protected $container;
/**
* You have to add listeners to your container
*
* You should register them as singletons
*
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function emit($event)
{
// ensure event is an event object
// get its name: $name
if (!$this->container->isSingleton($name)) {
$listeners = $this->container[$name];
is_array($listeners) or $listeners = [$listeners];
foreach ($listeners as $listener) {
$this->getEmitter()->addListener($name, $listener);
}
}
return call_user_func_array([$this->getEmitter(), 'emit'], func_get_args());
}
} |
My proposed solution was a little bit different. I'd make the listener resolving the responsibility of a listener decorator instead of a Emitter replacement. A littler decorator would look something like this: <?php
class LazyListener implements ListenerInterface
{
protected $container;
protected $containerKey;
protected $listener;
public function __construct(Container $container, $containerKey,)
{
$this->container = $container;
$this->containerKey = $containerKey;
}
public function handle(EventInterface $event)
{
$arguments = [$event] + func_get_args();
$listener = $this->resolveListener();
return call_user_func_array([$listener, 'handle'], $arguments);
}
protected function resolveListener()
{
if (! $this->listener) {
$this->listener = $this->container->get($this->containerKey);
}
return $this->listener;
}
public function getContainerKey()
{
return $this->containerKey;
}
public function isListener($listener)
{
if ($listeners instanceof LazyListener && $this->containerKey === $listener->getContainerKey) {
return true;
}
return false;
}
} You'd use this, with changing the container implementation details to the one you're using, like so: $container = new Container();
// .. configure container to have the listener which needs deps
$emitter = new Emitter;
$emitter->addListener('event.name', new LazyListener($container, 'some.dependency.key'));
$emitter->emit('event.name'); |
Thanks all for comments, right now I decided to extend Emitter and make them container aware. I need to override 3 methods addListener, InvokeListener, ensureListener public function addListener($event, $listener, $priority = self::P_NORMAL)
{
// I just remove this to make sure I can pass reference only
// $listener = $this->ensureListener($listener);
if (! isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = [$listener, $priority];
$this->clearSortedListeners($event);
return $this;
} We check and resolve listener later when there are invoked protected function invokeListeners($name, EventInterface $event, array $arguments)
{
$listeners = $this->getListeners($name);
foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
break;
}
// resolve listener
$listener = $this->ensureListener($listener);
call_user_func_array([$listener, 'handle'], $arguments);
}
} Finally if pass reference, resolve it in container otherwise throw exception protected function ensureListener($listener)
{
if ($listener instanceof ListenerInterface) {
return $listener;
}
if (is_callable($listener)) {
return CallbackListener::fromCallable($listener);
}
// If we pass reference in containter then resolve it
if (($listener = $this->container->get($listener)) != null) {
return $listener
}
throw new InvalidArgumentException('Listeners should be be ListenerInterface, Closure or callable. Received type: '.gettype($listener));
} With this way all listener can resolve when event get invoked. |
I hate the idea of having "new's" anywhere except my DiC.
So something like
$emitter->addListener('some event', 'Some Class Name which Implements the Listener Interface');
$emitter->setListenerResolver(new ClassThatImplementsAListenerResolverInterface());
class ClassThatImplementsAListenerResolverInterface {
public function __construct('my DiC goes here') ...
public function getListener($className)
{
return $this->MyDiC->get($className);
}
It's just some meta code but you get the ideea. This way you don't do any instantiations unless the event listener MUST actually be called. Otherwise you work just with strings.
What do you think ? I don't see a way to do this right now (w/o customizing stuff quite a bit)
The text was updated successfully, but these errors were encountered: