Skip to content

Commit

Permalink
Allow enums to be used as event names, add proper template types (#34)
Browse files Browse the repository at this point in the history
* Allow enums to be used as event names, add proper template types

* Fix code linting
  • Loading branch information
cspray authored May 31, 2024
1 parent 932791a commit f7a625c
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 21 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## v4.1.0

#### Added

- Added the `Labrador\AsyncEvent\EventName` interface to allow for enums and other
objects to be used as event names when appropriate

#### Changed

- Updated the `Labrador\AsyncEvent\Emitter::register()` and `Emitter::listeners`
methods to accept a `non-empty-string|EventName`.
- Added proper template types to the `Emitter::emit` and `Emitter::queue` methods

## v4.0.0

#### Added
Expand Down
12 changes: 2 additions & 10 deletions src/AmpEmitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Labrador\AsyncEvent\Internal\ListenerInvocationContext;
use Labrador\AsyncEvent\Internal\NotInvoked;
use Labrador\CompositeFuture\CompositeFuture;
use Random\RandomException;
use Revolt\EventLoop;
use Throwable;

Expand All @@ -23,11 +22,7 @@ final class AmpEmitter implements Emitter {
*/
private array $listenerInvocationContexts = [];

/**
* @param non-empty-string $eventName
* @throws RandomException
*/
public function register(string $eventName, Listener $listener) : ListenerRegistration {
public function register(string|EventName $eventName, Listener $listener) : ListenerRegistration {
$listenerKey = random_bytes(16);
$context = new ListenerInvocationContext(
$listener,
Expand Down Expand Up @@ -94,10 +89,7 @@ public function finished(callable $callable) : void {
};
}

/**
* @inheritDoc
*/
public function listeners(string $event) : array {
public function listeners(string|EventName $event) : array {
return array_values(
array_map(
static fn(ListenerInvocationContext $context) => $context->listener,
Expand Down
19 changes: 12 additions & 7 deletions src/Emitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Labrador\AsyncEvent;

use Labrador\CompositeFuture\CompositeFuture;
use Stringable;

/**
* Represents an object that allows listeners to respond to emitted events asynchronously.
Expand All @@ -15,11 +16,12 @@ interface Emitter {
* Register a Listener to respond to emitted events; the ListenerRegistration returned can be used to remove the
* Listener.
*
* @param non-empty-string $eventName
* @param Listener $listener
* @template Payload of object
* @param non-empty-string|EventName $eventName
* @param Listener<Event<Payload>> $listener
* @return ListenerRegistration
*/
public function register(string $eventName, Listener $listener) : ListenerRegistration;
public function register(string|EventName $eventName, Listener $listener) : ListenerRegistration;

/**
* Immediately invokes all registered listeners that can handle the given $event.
Expand All @@ -32,8 +34,8 @@ public function register(string $eventName, Listener $listener) : ListenerRegist
* It is important that the CompositeFuture returned has a method invoked that awaits completion! If you don't
* explicitly call a method on the CompositeFuture the behavior for how Listeners will behave is undefined.
*
* @param Event $event
* @return CompositeFuture
* @template Payload of object
* @param Event<Payload> $event
*/
public function emit(Event $event) : CompositeFuture;

Expand All @@ -43,14 +45,17 @@ public function emit(Event $event) : CompositeFuture;
*
* On the next tick of the loop, the $event will be passed to Emitter::emit. The CompositeFuture that results
* will be handled by calling awaitAll. The Future returned from this method will
*
* @template Payload of object
* @param Event<Payload> $event
*/
public function queue(Event $event) : FinishedNotifier;

/**
* Returns a list of Listener implementations that can handle the provided event name.
*
* @param string $event
* @param non-empty-string|EventName $event
* @return list<Listener>
*/
public function listeners(string $event) : array;
public function listeners(string|EventName $event) : array;
}
11 changes: 11 additions & 0 deletions src/EventName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace Labrador\AsyncEvent;

interface EventName {

/**
* @return non-empty-string
*/
public function name() : string;
}
21 changes: 17 additions & 4 deletions src/Internal/ListenerInvocationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Amp\Future;
use Labrador\AsyncEvent\Event;
use Labrador\AsyncEvent\EventName;
use Labrador\AsyncEvent\ListenerRemovableBasedOnHandleCount;
use Labrador\AsyncEvent\Listener;
use Labrador\AsyncEvent\ListenerRegistration;
Expand All @@ -17,13 +18,21 @@ final class ListenerInvocationContext {
private int $handledCount = 0;

/**
* @param non-empty-string $registeredEvent
* @var non-empty-string
*/
private readonly string $registeredEvent;

/**
* @template Payload of object
* @param Listener<Payload> $listener
* @param non-empty-string|EventName $registeredEvent
*/
public function __construct(
public readonly Listener $listener,
private readonly ListenerRegistration $registration,
private readonly string $registeredEvent
string|EventName $registeredEvent
) {
$this->registeredEvent = $registeredEvent instanceof EventName ? $registeredEvent->name() : $registeredEvent;
}

public function handle(Event $event) : CompositeFuture|NotInvoked {
Expand All @@ -48,8 +57,12 @@ public function handle(Event $event) : CompositeFuture|NotInvoked {
return $value;
}

public function isRegisteredEventName(string $eventName) : bool {
return $eventName === $this->registeredEvent;
/**
* @param non-empty-string|EventName $eventName
* @return bool
*/
public function isRegisteredEventName(string|EventName $eventName) : bool {
return ($eventName instanceof EventName ? $eventName->name() : $eventName) === $this->registeredEvent;
}

private function isHandleLimitReached() : bool {
Expand Down
21 changes: 21 additions & 0 deletions test/Unit/AmpEventEmitterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Labrador\AsyncEvent\Event;
use Labrador\AsyncEvent\ListenerRemovableBasedOnHandleCount;
use Labrador\AsyncEvent\Listener;
use Labrador\AsyncEvent\Test\Unit\Helper\MyEnum;
use Labrador\CompositeFuture\CompositeFuture;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
Expand Down Expand Up @@ -218,4 +219,24 @@ public function testQueueingFailure() : void {
self::assertSame([], $data->values);
self::assertSame([$exception], $data->exception->getReasons());
}

public function testRegisterEventWithStringBackedEnumAddsToListeners() : void {
$subject = new AmpEmitter();

$a = Mockery::mock(Listener::class);

$subject->register(MyEnum::Foo, $a);

self::assertSame([$a], $subject->listeners('foo'));
}

public function testRetrieveListenersWithSameEnumUsedToRegister() : void {
$subject = new AmpEmitter();

$a = Mockery::mock(Listener::class);

$subject->register(MyEnum::Foo, $a);

self::assertSame([$a], $subject->listeners(MyEnum::Foo));
}
}
13 changes: 13 additions & 0 deletions test/Unit/Helper/MyEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);

namespace Labrador\AsyncEvent\Test\Unit\Helper;

use Labrador\AsyncEvent\EventName;

enum MyEnum : string implements EventName {
case Foo = 'foo';

public function name() : string {
return $this->value;
}
}

0 comments on commit f7a625c

Please sign in to comment.