1
0
mirror of https://github.com/danog/amp.git synced 2024-12-02 09:27:46 +01:00

Add Mediator::unsubscribe() and require explicit subscriber removal

This commit is contained in:
Daniel Lowrey 2018-01-08 08:09:47 -05:00
parent 2cb1937553
commit 27f5ab5dae
2 changed files with 43 additions and 25 deletions

View File

@ -3,25 +3,53 @@
namespace Amp; namespace Amp;
/** /**
* A Promise/Coroutine-aware implementation of the Mediator pattern. * A Promise-aware implementation of the Mediator pattern.
* *
* @link https://en.wikipedia.org/wiki/Mediator_pattern * @link https://en.wikipedia.org/wiki/Mediator_pattern
*/ */
class Mediator { class Mediator {
private $nextId = "a";
private $idEventMap = [];
private $eventSubscriberMap = []; private $eventSubscriberMap = [];
/** /**
* Invoke the callback when the specified event is published to the Mediator. * Attach an event listener callback to the Mediator
* *
* If a subscriber callback returns FALSE (===) or a generator\promise that * Listener callbacks are invoked with the signature:
* resolves to FALSE that callback will be unsubscribed from the event. *
* callback(Mediator $mediator, string $subscriberId, ...$data)
* *
* @param string $eventName The name of the event being subscribed to * @param string $eventName The name of the event being subscribed to
* @param callable $callback The callback to invoke when the event is published * @param callable $callback The callback to invoke when the event is published
* @return void * @return string Returns listener's subscriber ID
*/ */
public function subscribe(string $eventName, callable $callback) { public function subscribe(string $eventName, callable $callback): string {
$this->eventSubscriberMap[$eventName][] = $callback; $subscriberId = $this->nextId++;
$this->idEventMap[$subscriberId] = $eventName;
$this->eventSubscriberMap[$eventName][$subscriberId] = $callback;
return $subscriberId;
}
/**
* Detach an event listener from the Mediator
*
* @param string $subscriberId The subscriber ID generated registering the listener
* @return bool Returns TRUE if a listener was removed, FALSE otherwise
*/
public function unsubscribe(string $subscriberId): bool {
if (!isset($this->idEventMap[$subscriberId])) {
return false;
}
$eventName = $this->idEventMap[$subscriberId];
unset(
$this->idEventMap[$subscriberId],
$this->eventSubscriberMap[$eventName][$subscriberId]
);
// don't leak memory even if it's just an empty array
if (empty($this->eventSubscriberMap[$eventName])) {
unset($this->eventSubscriberMap[$eventName]);
}
return true;
} }
/** /**
@ -42,24 +70,14 @@ class Mediator {
} }
private function doPublish(string $eventName, array $data): \Generator { private function doPublish(string $eventName, array $data): \Generator {
foreach ($this->eventSubscriberMap[$eventName] as $id => $callback) { foreach ($this->eventSubscriberMap[$eventName] as $subscriberId => $callback) {
$promises[$id] = call($callback, ...$data); $promises[$subscriberId] = call($callback, $this, $subscriberId, ...$data);
} }
list($errors, $results) = yield Promise\any($promises); list($errors, $results) = yield Promise\any($promises);
// remove subscribers that returned FALSE (===)
foreach ($results as $id => $result) {
if ($result === false) {
unset($this->eventSubscriberMap[$eventName][$id]);
}
}
// don't leak memory even if it's just an empty array
if (empty($this->eventSubscriberMap[$eventName])) {
unset($this->eventSubscriberMap[$eventName]);
}
if ($errors) { if ($errors) {
throw new MultiReasonException( throw new MultiReasonException(
$errors, $errors,
"Event subscriber(s) threw uncaught exceptions while reacting to {$eventName}" "Mediator subscriber(s) threw uncaught exception(s) while reacting to {$eventName}"
); );
} }
return \count($results); return \count($results);

View File

@ -86,10 +86,10 @@ class MediatorTest extends TestCase {
$mediator = new Mediator; $mediator = new Mediator;
Loop::run(function () use ($mediator) { Loop::run(function () use ($mediator) {
$mediator->subscribe("event.foo", static function () {}); $mediator->subscribe("event.foo", function () {});
$mediator->subscribe("event.foo", static function () { return false; }); $mediator->subscribe("event.foo", function ($m, $id) { $m->unsubscribe($id); });
$mediator->subscribe("event.bar", static function () {}); $mediator->subscribe("event.bar", function () {});
$mediator->subscribe("event.baz", static function () { return false; }); $mediator->subscribe("event.baz", function ($m, $id) { $m->unsubscribe($id); });
yield $mediator->publish("event.foo", 42); yield $mediator->publish("event.foo", 42);
yield $mediator->publish("event.bar", 42); yield $mediator->publish("event.bar", 42);
yield $mediator->publish("event.baz", 42); yield $mediator->publish("event.baz", 42);