1
0
mirror of https://github.com/danog/amp.git synced 2024-11-30 04:29:08 +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;
/**
* 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
*/
class Mediator {
private $nextId = "a";
private $idEventMap = [];
private $eventSubscriberMap = [];
/**
* Invoke the callback when the specified event is published to the Mediator.
*
* If a subscriber callback returns FALSE (===) or a generator\promise that
* resolves to FALSE that callback will be unsubscribed from the event.
* Attach an event listener callback to the Mediator
*
* Listener callbacks are invoked with the signature:
*
* callback(Mediator $mediator, string $subscriberId, ...$data)
*
* @param string $eventName The name of the event being subscribed to
* @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) {
$this->eventSubscriberMap[$eventName][] = $callback;
public function subscribe(string $eventName, callable $callback): string {
$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 {
foreach ($this->eventSubscriberMap[$eventName] as $id => $callback) {
$promises[$id] = call($callback, ...$data);
foreach ($this->eventSubscriberMap[$eventName] as $subscriberId => $callback) {
$promises[$subscriberId] = call($callback, $this, $subscriberId, ...$data);
}
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) {
throw new MultiReasonException(
$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);

View File

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