-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
349 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
title: PSR-14 Event dispatcher implementation | ||
--- | ||
|
||
# PSR-14 Event dispatcher implementation | ||
|
||
The `Sirius\StackRunner` library comes with an implementation of the [PSR-14 Event Dispatcher](https://www.php-fig.org/psr/psr-14/). | ||
|
||
```php | ||
use Sirius\StackRunner\Invoker; | ||
use Sirius\StackRunner\Event\Dispatcher; | ||
use Sirius\StackRunner\Event\ListenerProvider; | ||
|
||
$invoker = new Invoker($psr11Container); | ||
$listenerProvider = new ListenerProvider(); | ||
$dispatcher = new Dispatcher($listenerProvider, $invoker); | ||
|
||
// event name, listener, priority | ||
$listenerProvider->subscribeTo(Event::class, 'some_callable', 0); | ||
$listenerProvider->subscribeOnceTo(Event::class, 'some_callable', 0); | ||
|
||
// if you use the Sirius\StackRunner\Event\ListenerProvider | ||
// the same results as above can also be achieved with | ||
$dispatcher->subscribeTo(Event::class, 'some_callable', 0); | ||
$dispatcher->subscribeOnceTo(Event::class, 'some_callable', 0); | ||
``` | ||
|
||
### Named events | ||
|
||
If you want to identify the events by something other than the class name you can make the event classes implement the `HasEventname` interface | ||
|
||
```php | ||
use Sirius\StackRunner\Event\HasEventName; | ||
|
||
class EventWithName implements HasEventName { | ||
public function getEventName() : string{ | ||
return 'event_name'; | ||
} | ||
} | ||
``` | ||
|
||
and then you can do something like | ||
|
||
```php | ||
$listenerProvider->subscribeTo('event_name', 'some_callable'); | ||
// later on | ||
$dispatcher->dispatch(new EventWithName()); | ||
``` | ||
|
||
### Stoppable events | ||
|
||
If you want some events to be able to stop the execution of the rest of the callables in the stack you can add the `Stoppable` trait to your event classes | ||
|
||
```php | ||
use Sirius\StackRunner\Event\Stoppable; | ||
|
||
class StoppableEvent { | ||
use Stoppable; | ||
} | ||
``` | ||
|
||
and then you can do something like | ||
|
||
```php | ||
$listenerProvider->subscribeTo(StoppableEvent::class, function(object $event) { | ||
$event->stopPropagation(); | ||
}); | ||
// the subsequent callables won't be executed | ||
$listenerProvider->subscribeTo(StoppableEvent::class, 'some_callable'); | ||
``` | ||
|
||
[Next: Actions a la Wordpress](2_4_wordpress_actions.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
use Psr\EventDispatcher\EventDispatcherInterface; | ||
use Psr\EventDispatcher\ListenerProviderInterface; | ||
use Psr\EventDispatcher\StoppableEventInterface; | ||
use Sirius\StackRunner\Invoker; | ||
use Sirius\StackRunner\Stack; | ||
|
||
class Dispatcher implements EventDispatcherInterface | ||
{ | ||
|
||
public function __construct(public ListenerProviderInterface $registry, public Invoker $invoker) | ||
{ | ||
} | ||
|
||
public function dispatch(object $event): object | ||
{ | ||
/** @var Stack $stack */ | ||
$stack = $this->registry->getListenersForEvent($event); | ||
|
||
/** @var mixed $callable */ | ||
foreach ($stack as $callable) { | ||
$this->invoker->invoke($callable, $event); | ||
if ($event instanceof StoppableEventInterface && | ||
$event->isPropagationStopped()) { | ||
break; | ||
} | ||
} | ||
|
||
return $event; | ||
} | ||
|
||
public function subscribeTo(string $eventName, mixed $callable, int $priority = 0): void | ||
{ | ||
if ( ! $this->registry instanceof ListenerSubscriber) { | ||
throw new \LogicException(sprintf('Unable to subscribe listener because %s is not instace of %s', | ||
get_class($this->registry), | ||
ListenerSubscriber::class | ||
)); | ||
} | ||
|
||
$this->registry->subscribeTo($eventName, $callable, $priority); | ||
} | ||
|
||
public function subscribeOnceTo(string $eventName, mixed $callable, int $priority = 0): void | ||
{ | ||
if ( ! $this->registry instanceof ListenerSubscriber) { | ||
throw new \LogicException(sprintf('Unable to subscribe listener because %s is not instace of %s', | ||
get_class($this->registry), | ||
ListenerSubscriber::class | ||
)); | ||
} | ||
|
||
$this->registry->subscribeOnceTo($eventName, $callable, $priority); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
interface HasEventName | ||
{ | ||
public function getEventName(): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
use Psr\EventDispatcher\ListenerProviderInterface; | ||
use Sirius\StackRunner\Stack; | ||
use function Sirius\StackRunner\once; | ||
|
||
class ListenerProvider implements ListenerProviderInterface, ListenerSubscriber | ||
{ | ||
/** | ||
* @var array<string, Stack|iterable> | ||
*/ | ||
protected array $registry = []; // @phpstan-ignore-line | ||
|
||
/** | ||
* @return iterable|Stack | ||
*/ | ||
public function getListenersForEvent(object $event): iterable // @phpstan-ignore-line | ||
{ | ||
$eventName = get_class($event); | ||
if ($event instanceof HasEventName) { | ||
$eventName = $event->getEventName(); | ||
} | ||
|
||
return $this->registry[$eventName] ?? new Stack(); | ||
} | ||
|
||
public function subscribeTo(string $eventName, mixed $callable, int $priority = 0): void | ||
{ | ||
/** @var Stack $stack */ | ||
$stack = $this->registry[$eventName] ?? new Stack(); | ||
|
||
$stack->add($callable, $priority); | ||
|
||
$this->registry[$eventName] = $stack; | ||
} | ||
|
||
public function subscribeOnceTo(string $eventName, mixed $callable, int $priority = 0): void | ||
{ | ||
/** @var Stack $stack */ | ||
$stack = $this->registry[$eventName] ?? new Stack(); | ||
|
||
$stack->add(once($callable), $priority); | ||
|
||
$this->registry[$eventName] = $stack; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
interface ListenerSubscriber | ||
{ | ||
public function subscribeTo(string $eventName, mixed $callable, int $priority = 0): void; | ||
|
||
public function subscribeOnceTo(string $eventName, mixed $callable, int $priority = 0): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
trait Stoppable | ||
{ | ||
protected bool $propagationStopped = false; | ||
|
||
public function isPropagationStopped(): bool | ||
{ | ||
return $this->propagationStopped; | ||
} | ||
|
||
public function stopPropagation(): void | ||
{ | ||
$this->propagationStopped = true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
use Sirius\StackRunner\TestCase; | ||
|
||
require_once __DIR__ . '/EventWithName.php'; | ||
require_once __DIR__ . '/EventWithoutName.php'; | ||
require_once __DIR__ . '/StoppableEvent.php'; | ||
|
||
class DispatcherTest extends TestCase | ||
{ | ||
public function test_subscribers_are_executed_in_order() | ||
{ | ||
$dispatcher = new Dispatcher(new ListenerProvider(), $this->getInvoker()); | ||
$dispatcher->subscribeTo('event_with_name', function (object $event) { | ||
static::$results[] = 'subscriber 1'; | ||
}); | ||
$dispatcher->subscribeTo('event_with_name', function (object $event) { | ||
static::$results[] = 'subscriber 2'; | ||
}); | ||
$dispatcher->subscribeTo('event_with_name', function (object $event) { | ||
static::$results[] = 'subscriber 3'; | ||
}); | ||
|
||
$dispatcher->dispatch(new EventWithName()); | ||
|
||
$this->assertSame([ | ||
'subscriber 1', | ||
'subscriber 2', | ||
'subscriber 3', | ||
], static::$results); | ||
} | ||
|
||
public function test_stoppable_events() | ||
{ | ||
$dispatcher = new Dispatcher(new ListenerProvider(), $this->getInvoker()); | ||
$dispatcher->subscribeTo(StoppableEvent::class, function (object $event) { | ||
static::$results[] = 'subscriber 1'; | ||
}); | ||
$dispatcher->subscribeTo(StoppableEvent::class, function (object $event) { | ||
static::$results[] = 'subscriber 2'; | ||
$event->stopPropagation(); | ||
}); | ||
$dispatcher->subscribeTo(StoppableEvent::class, function (object $event) { | ||
static::$results[] = 'subscriber 3'; | ||
}); | ||
|
||
$dispatcher->dispatch(new StoppableEvent()); | ||
|
||
$this->assertSame([ | ||
'subscriber 1', | ||
'subscriber 2', | ||
], static::$results); | ||
} | ||
|
||
public function test_once_subscribers() | ||
{ | ||
$dispatcher = new Dispatcher(new ListenerProvider(), $this->getInvoker()); | ||
$dispatcher->subscribeOnceTo(EventWithoutName::class, function (object $event) { | ||
static::$results[] = 'once subscriber'; | ||
}); | ||
|
||
$dispatcher->dispatch(new EventWithoutName()); | ||
$dispatcher->dispatch(new EventWithoutName()); | ||
$dispatcher->dispatch(new EventWithoutName()); | ||
$dispatcher->dispatch(new EventWithoutName()); | ||
|
||
$this->assertSame([ | ||
'once subscriber', | ||
], static::$results); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
class EventWithName implements HasEventName { | ||
|
||
public function getEventName(): string | ||
{ | ||
return 'event_with_name'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?php | ||
|
||
namespace Sirius\StackRunner\Event; | ||
|
||
class EventWithoutName | ||
{ | ||
} |
Oops, something went wrong.