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:
parent
2cb1937553
commit
27f5ab5dae
@ -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);
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user