Last active
October 18, 2018 07:21
-
-
Save nicolas-grekas/1d08b6d5ecbb2504b788e378583920a0 to your computer and use it in GitHub Desktop.
Revisions
-
nicolas-grekas revised this gist
Oct 18, 2018 . 1 changed file with 0 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -18,11 +18,6 @@ public function dispatch($message): void; * so that implementing this is not a requirement to write a middleware) * * It *extends* MessageBusInterface, which is the core requirement all this is built around. */ interface MiddlewareInterface extends MessageBusInterface { -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 45 additions and 55 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -8,10 +8,7 @@ */ interface MessageBusInterface { public function dispatch($message): void; } /** @@ -27,30 +24,33 @@ public function dispatch($message, string $name = null): void; * and middlewareHandlers are cloned before being used then destroyed just after, so that they are * (and their prototype service also) still stateless. */ interface MiddlewareInterface extends MessageBusInterface { /** * @return static */ public function withBus(BusInterface $bus): self; } /** * This interface is mostly internal - only one implementation of it is needed really, see below. * * It's purpose is to chain stackable middlewareHandlers together at runtime. */ interface BusInterface { public function next(): MessageBusInterface; } /** * Nothing fancy here, just the null design pattern. */ class NullBus implements MessageBusInterface { /** * {@inheritdoc} */ public function dispatch($message): void { // no-op } @@ -62,47 +62,43 @@ public function dispatch($message, string $name = null): void * By contract, the injected bus must be consumed once and can thus be * unset after usage. *Internally*, a next() method is provided. */ abstract class AbstractMiddleware implements MiddlewareInterface { protected $bus; /** * @return static */ public function withBus(BusInterface $bus): MiddlewareInterface { $self = clone $this; $self->bus = $bus; return $self; } protected function next(): MessageBusInterface { try { return $this->bus ? $this->bus->next() : new NullBus(); } finally { $this->bus = null; } } } /** * Now we talk: this is an example implementation of a middleware that dumps something around a decorated dispatcher. */ class DumpMiddleware extends AbstractMiddleware { /** * {@inheritdoc} */ public function dispatch($message): void { dump(1); $this->next()->dispatch($message); dump(2); } } @@ -115,19 +111,19 @@ class DumpBus implements MessageBusInterface /** * {@inheritdoc} */ public function dispatch($message): void { dump($message); } } /** * This is the implementation of a message bus: it gets a list of middleware handlers as arguments. */ class MessageBus implements MessageBusInterface, BusInterface { private $middlewareAggregate; private $middlewareIterator; public function __construct(iterable $middlewareHandlers) { @@ -153,52 +149,46 @@ public function getIterator() /** * {@inheritdoc} */ public function dispatch($message): void { $bus = clone $this; $bus->middlewareIterator = $bus->middlewareAggregate->getIterator(); while ($bus->middlewareIterator instanceof \IteratorAggregate) { $bus->middlewareIterator = $bus->middlewareIterator->getIterator(); } foreach ($bus->middlewareIterator as $middleware) { if ($middleware instanceof MiddlewareInterface) { $middleware = $middleware->withBus($bus); } $middleware->dispatch($message); return; } } public function next(): MessageBusInterface { if (null === $middlewareIterator = $this->middlewareIterator) { throw new \LogicException('Cannot get next middleware on uninitialized message bus.'); } $middlewareIterator->next(); if (!$middlewareIterator->valid()) { return new NullBus(); } $middleware = $middlewareIterator->current(); return $middleware instanceof MiddlewareInterface ? $middleware->withBus($this) : $middleware; } } // Let's demo all this $bus = new MessageBus(new \ArrayObject(array( new DumpMiddleware(), new DumpBus(), ))); -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 28 additions and 28 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -4,7 +4,7 @@ require 'vendor/autoload.php'; /** * This is the core interface. It already allows building dispatchers *and middlewareHandlers*. */ interface MessageBusInterface { @@ -15,16 +15,16 @@ public function dispatch($message, string $name = null): void; } /** * This allows writting *stackable* middlewareHandlers that are *still* dispatchers. * * (there is no reason to force all middlewareHandlers be stackable BTW * so that implementing this is not a requirement to write a middleware) * * It *extends* MessageBusInterface, which is the core requirement all this is built around. * * While this requires a setter, it's not of the same kind as we use on services: * here, the implementation must work when no bus has ever been set (using the null pattern) * and middlewareHandlers are cloned before being used then destroyed just after, so that they are * (and their prototype service also) still stateless. */ interface BusMiddlewareInterface extends MessageBusInterface @@ -35,7 +35,7 @@ public function setMiddlewareBus(MiddlewareBusInterface $bus): void; /** * This interface is mostly internal - only one implementation of it is needed really, see below. * * It's purpose is to chain stackable middlewareHandlers together at runtime. */ interface MiddlewareBusInterface { @@ -82,7 +82,7 @@ private function next(): MessageBusInterface } /** * A helper class to ease implementing stackable middlewareHandlers. */ abstract class AbstractBusMiddleware implements BusMiddlewareInterface { @@ -122,19 +122,19 @@ public function dispatch($message, string $name = null): void } /** * This is the implementation of a message bus: it gets a list of middlewareHandlers as arguments. */ class MessageBus implements MessageBusInterface, MiddlewareBusInterface { private $middlewareAggregate; private $middlewareHandlers; public function __construct(iterable $middlewareHandlers) { if ($middlewareHandlers instanceof \IteratorAggregate) { $this->middlewareAggregate = $middlewareHandlers; } elseif (\is_array($middlewareHandlers)) { $this->middlewareAggregate = new \ArrayObject($middlewareHandlers); } else { $this->middlewareAggregate = new class() { public $aggregate; @@ -146,7 +146,7 @@ public function getIterator() } }; $this->middlewareAggregate->aggregate = &$this->middlewareAggregate; $this->middlewareAggregate->iterator = $middlewareHandlers; } } @@ -157,12 +157,12 @@ public function dispatch($message, string $name = null): void { $bus = clone $this; $middlewareHandlers = $bus->middlewareAggregate->getIterator(); while ($middlewareHandlers instanceof \IteratorAggregate) { $middlewareHandlers = $middlewares->getIterator(); } foreach ($bus->middlewareHandlers = $middlewares as $middleware) { if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($bus); } @@ -175,17 +175,17 @@ public function dispatch($message, string $name = null): void public function getNextBusDispatcher(): MessageBusInterface { if (null === $middlewareHandlers = $this->middlewares) { throw new \LogicException('Cannot get next middleware on uninitialized message bus.'); } $middlewareHandlers->next(); if (!$middlewareHandlers->valid()) { return new NullDispatcher(); } $middleware = clone $middlewareHandlers->current(); if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($this); @@ -208,23 +208,23 @@ public function getNextBusDispatcher(): MessageBusInterface /** * Discussion * * This design opposes PSR15-style middleware, that build around two *separate* * interfaces: one for dispatchers and a separate one for stackable middleware. * * Here, we seek for a core requirement: a middleware *should* be first a dispatcher * and then it *could* be stackable. On the contrary, PSR15 requires putting stackable * middleware under a separate uncompatible interface than the core decorated one. * This is design overhead we'd better get rid of. * * On the DX side, PSR15-style middleware have to be built around a generic indirection * object/callback (their $next argument). This has the nasty side effect of polluting * stack traces, thus making things harder to debug. That's not a minor issue. * * The design in this gist fixes all these flaws: * - middleware *are* dispatchers * - they can be made stackable using an *optional* additional *extending* interface * - stack frames are clean, they embed no indirections * * In terms of implementation complexity, both solutions are similar: they both require * the same amount of logic to call stackable middleware. */ -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 16 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -131,12 +131,22 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface public function __construct(iterable $middlewares) { if ($middlewares instanceof \IteratorAggregate) { $this->middlewareAggregate = $middlewares; } elseif (\is_array($middlewares)) { $this->middlewareAggregate = new \ArrayObject($middlewares); } else { $this->middlewareAggregate = new class() { public $aggregate; public $iterator; public function getIterator() { return $this->aggregate = new \ArrayObject(iterator_to_array($this->iterator, false)); } }; $this->middlewareAggregate->aggregate = &$this->middlewareAggregate; $this->middlewareAggregate->iterator = $middlewares; } } @@ -148,7 +158,7 @@ public function dispatch($message, string $name = null): void $bus = clone $this; $middlewares = $bus->middlewareAggregate->getIterator(); while (!$middlewares instanceof \Iterator) { $middlewares = $middlewares->getIterator(); } @@ -187,10 +197,10 @@ public function getNextBusDispatcher(): MessageBusInterface // Let's demo all this $bus = new MessageBus(new \ArrayObject(array( new BusMiddleware(), new DumpBus(), ))); $bus->dispatch(123); $bus->dispatch(234); -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -148,7 +148,7 @@ public function dispatch($message, string $name = null): void $bus = clone $this; $middlewares = $bus->middlewareAggregate->getIterator(); while ($middlewares instanceof \IteratorAggregate) { $middlewares = $middlewares->getIterator(); } -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 7 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -147,7 +147,12 @@ public function dispatch($message, string $name = null): void { $bus = clone $this; $middlewares = $bus->middlewareAggregate->getIterator(); while (!$middlewares instanceof \Iterator) { $middlewares = $middlewares->getIterator(); } foreach ($bus->middlewares = $middlewares as $middleware) { if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($bus); } @@ -201,7 +206,7 @@ public function getNextBusDispatcher(): MessageBusInterface * middlewares under a separate uncompatible interface than the core decorated one. * This is design overhead we'd better get rid of. * * On the DX side, PSR15-style middlewares have to be built around a generic indirection * object/callback (their $next argument). This has the nasty side effect of polluting * stack traces, thus making things harder to debug. That's not a minor issue. * -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 4 additions and 30 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -121,33 +121,6 @@ public function dispatch($message, string $name = null): void } } /** * This is the implementation of a message bus: it gets a list of middlewares as arguments. */ @@ -158,10 +131,12 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface public function __construct(iterable $middlewares) { if ($middlewareHandlers instanceof \IteratorAggregate) { $this->middlewareAggregate = $middlewares; } elseif (\is_array($middlewareHandlers)) { $this->middlewareAggregate = new \ArrayObject($middlewares); } else { $this->middlewareAggregate = new \ArrayObject(iterator_to_array($middlewares, false)); } } @@ -178,7 +153,6 @@ public function dispatch($message, string $name = null): void } $middleware->dispatch($message, $name); return; } -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -236,6 +236,6 @@ public function getNextBusDispatcher(): MessageBusInterface * - they can be made stackable using an *optional* additional *extending* interface * - stack frames are clean, they embed no indirections * * In terms of implementation complexity, both solutions are similar: they both require * the same amount of logic to call stackable middlewares. */ -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -166,7 +166,7 @@ public function __construct(iterable $middlewares) } /** * {@inheritdoc} */ public function dispatch($message, string $name = null): void { -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -24,8 +24,8 @@ public function dispatch($message, string $name = null): void; * * While this requires a setter, it's not of the same kind as we use on services: * here, the implementation must work when no bus has ever been set (using the null pattern) * and middlewares are cloned before being used then destroyed just after, so that they are * (and their prototype service also) still stateless. */ interface BusMiddlewareInterface extends MessageBusInterface { -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -24,8 +24,8 @@ public function dispatch($message, string $name = null): void; * * While this requires a setter, it's not of the same kind as we use on services: * here, the implementation must work when no bus has ever been set (using the null pattern) * and middlewares are cloned before being used and destroyed just after so that they prototype * service is still stateless. */ interface BusMiddlewareInterface extends MessageBusInterface { -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -24,8 +24,8 @@ public function dispatch($message, string $name = null): void; * * While this requires a setter, it's not of the same kind as we use on services: * here, the implementation must work when no bus has ever been set (using the null pattern) * and the injected should be used only once, so that middlewares are still stateless. * For additional statelessness, middlewares are cloned before usage. */ interface BusMiddlewareInterface extends MessageBusInterface { @@ -196,7 +196,7 @@ public function getNextBusDispatcher(): MessageBusInterface return new NullDispatcher(); } $middleware = clone $middlewares->current(); if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($this); -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -219,7 +219,7 @@ public function getNextBusDispatcher(): MessageBusInterface /** * Discussion * * This design opposes PSR15-style middlewares, that build around two *separate* * interfaces: one for dispatchers and a separate one for stackable middlewares. * * Here, we seek for a core requirement: a middleware *should* be first a dispatcher -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -158,7 +158,7 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface public function __construct(iterable $middlewares) { if (\is_array($middlewares)) { $this->middlewareAggregate = new \ArrayObject($middlewares); } else { new MiddlewareAggregate($this->middlewareAggregate, $middlewares); -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 3 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -129,7 +129,7 @@ class MiddlewareAggregate implements \IteratorAggregate private $aggregate; private $iterator; public function __construct(&$aggregate, iterable $middlewares) { $this->aggregate = &$aggregate; $this->iterator = $middlewares; @@ -158,7 +158,7 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface public function __construct(iterable $middlewares) { if (0&&\is_array($middlewares)) { $this->middlewareAggregate = new \ArrayObject($middlewares); } else { new MiddlewareAggregate($this->middlewareAggregate, $middlewares); @@ -178,6 +178,7 @@ public function dispatch($message, string $name = null): void } $middleware->dispatch($message, $name); $bus->middlewares->next(); return; } @@ -199,8 +200,6 @@ public function getNextBusDispatcher(): MessageBusInterface if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($this); } return $middleware; -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -23,9 +23,9 @@ public function dispatch($message, string $name = null): void; * It *extends* MessageBusInterface, which is the core requirement all this is built around. * * While this requires a setter, it's not of the same kind as we use on services: * here, the implementation must work when no bus has ever been set (using the null pattern) * and the injected bus must be unset after use (it's "use-once") so that middlewares are * still stateless - see implementation below. */ interface BusMiddlewareInterface extends MessageBusInterface { -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 7 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -220,21 +220,21 @@ public function getNextBusDispatcher(): MessageBusInterface /** * Discussion * * This design opposes PSR15-style middlewares, that builds around two *separate* * interfaces: one for dispatchers and a separate one for stackable middlewares. * * Here, we seek for a core requirement: a middleware *should* be first a dispatcher * and then it *could* be stackable. On the contrary, PSR15 requires putting stackable * middlewares under a separate uncompatible interface than the core decorated one. * This is design overhead we'd better get rid of. * * On the DX side, PSR15-style middlewares have to be build around a generic indirection * object/callback (their $next argument). This has the nasty side effect of polluting * stack traces, thus making things harder to debug. That's not a minor issue. * * The design in this gist fixes all these flaws: * - middlewares *are* dispatchers * - they can be made stackable using an *optional* additional *extending* interface * - stack frames are clean, they embed no indirections * * In term of implementation complexity, both solutions are similar: they both require -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 8 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -4,7 +4,7 @@ require 'vendor/autoload.php'; /** * This is the core interface. It already allows building dispatchers *and middlewares*. */ interface MessageBusInterface { @@ -17,10 +17,15 @@ public function dispatch($message, string $name = null): void; /** * This allows writting *stackable* middlewares that are *still* dispatchers. * * (there is no reason to force all middlewares be stackable BTW * so that implementing this is not a requirement to write a middleware) * * It *extends* MessageBusInterface, which is the core requirement all this is built around. * * While this requires a setter, it's not of the same kind as we use on services: * here, the implementation must work when no bus has ever be set (using the null pattern) * and the injected bus must be unset after use (it's "use-once") so that this creates * no state middlewares - see implementation below. */ interface BusMiddlewareInterface extends MessageBusInterface { -
nicolas-grekas revised this gist
Oct 17, 2018 . 1 changed file with 70 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,9 @@ error_reporting(-1); require 'vendor/autoload.php'; /** * This is the core interface. It already allows building dispatcher *and middlewares*. */ interface MessageBusInterface { /** @@ -11,16 +14,32 @@ interface MessageBusInterface public function dispatch($message, string $name = null): void; } /** * This allows writting *stackable* middlewares that are *still* dispatchers. * * (there is no reason to force all middlewares be stack-able BTW * so this implementing this is not a requirement to write a middleware) * * It *extends* MessageBusInterface, which is the core requirement all this is built around. */ interface BusMiddlewareInterface extends MessageBusInterface { public function setMiddlewareBus(MiddlewareBusInterface $bus): void; } /** * This interface is mostly internal - only one implementation of it is needed really, see below. * * It's purpose is to chain stackable middlewares together at runtime. */ interface MiddlewareBusInterface { public function getNextBusDispatcher(): MessageBusInterface; } /** * Nothing fancy here, just the null design pattern. */ class NullDispatcher implements MessageBusInterface { /** @@ -32,6 +51,12 @@ public function dispatch($message, string $name = null): void } } /** * This is the typical implementation of a BusMiddlewareInterface. * * By contract, the injected bus must be consumed once and can thus be * unset after usage. *Internally*, a next() method is provided. */ trait BusMiddlewareTrait { private $bus; @@ -51,10 +76,21 @@ private function next(): MessageBusInterface } } /** * A helper class to ease implementing stackable middlewares. */ abstract class AbstractBusMiddleware implements BusMiddlewareInterface { use BusMiddlewareTrait { next as protected; } } /** * Now we talk: this is an example implementation of a middleware that dumps something around a decorated dispatcher. */ class BusMiddleware extends AbstractBusMiddleware { /** * {@inheritdoc} */ @@ -66,6 +102,9 @@ public function dispatch($message, string $name = null): void } } /** * This is an example dispatcher that does just dump() the passed message. */ class DumpBus implements MessageBusInterface { /** @@ -104,6 +143,9 @@ public function getIterator() } } /** * This is the implementation of a message bus: it gets a list of middlewares as arguments. */ class MessageBus implements MessageBusInterface, MiddlewareBusInterface { private $middlewareAggregate; @@ -160,10 +202,36 @@ public function getNextBusDispatcher(): MessageBusInterface } } // Let's demo all this $bus = new MessageBus(array( new BusMiddleware(), new DumpBus(), )); $bus->dispatch(123); $bus->dispatch(234); /** * Discussion * * This design opposes PSR-15-style middlewares, that builds around two separate * interfaces for dispatchers on ne side and stackable middlewares on the other. * * Here, we see for a core requirement: a middleware *should* be first a dispatcher * and then it *could* be stackable. On the contrary, PSR-15 requires putting stackable * middlewares under a separate uncompatible interface than the core thing the decorate. * This is design overhead we'd better get rid of. * * On the DX side, PSR15-style middlewares have to be build around a generic indirection * object/callback (their $next argument). This has the nasty side effect of polluting * stack traces, thus making things harder to debug. That's not a minor issue. * * The design in this gist fixes both flaws: * - middlewares *are* dispatchers * - then can be made stackable using an *optional* additional *extending* interface * - stack frames are clean, they embed no indirections * * In term of implementation complexity, both solutions are similar: they both require * the same amount of logic to call stackable middlewares. */ -
nicolas-grekas revised this gist
Oct 16, 2018 . 1 changed file with 49 additions and 23 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,8 @@ <?php error_reporting(-1); require 'vendor/autoload.php'; interface MessageBusInterface { /** @@ -40,12 +43,8 @@ public function setMiddlewareBus(MiddlewareBusInterface $bus): void private function next(): MessageBusInterface { try { return $this->bus ? $this->bus->getNextBusDispatcher() : new NullDispatcher(); } finally { $this->bus = null; } @@ -78,6 +77,33 @@ public function dispatch($message, string $name = null): void } } /** * @internal */ class MiddlewareAggregate implements \IteratorAggregate { private $aggregate; private $iterator; public function __construct(&$aggregate, \Traversable $middlewares) { $this->aggregate = &$aggregate; $this->iterator = $middlewares; $aggregate = $this; } public function getIterator() { $middlewares = array(); foreach ($this->iterator as $middleware) { yield $middlewares[] = $middleware; } $this->aggregate = new \ArrayObject($middlewares); } } class MessageBus implements MessageBusInterface, MiddlewareBusInterface { private $middlewareAggregate; @@ -86,12 +112,10 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface public function __construct(iterable $middlewares) { if (\is_array($middlewares)) { $this->middlewareAggregate = new \ArrayObject($middlewares); } else { new MiddlewareAggregate($this->middlewareAggregate, $middlewares); } } /** @@ -100,34 +124,36 @@ public function __construct(iterable $middlewares) public function dispatch($message, string $name = null): void { $bus = clone $this; foreach ($bus->middlewares = $bus->middlewareAggregate->getIterator() as $middleware) { if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($bus); } $middleware->dispatch($message, $name); return; } } public function getNextBusDispatcher(): MessageBusInterface { if (null === $middlewares = $this->middlewares) { throw new \LogicException('Cannot get next middleware on uninitialized message bus.'); } $middlewares->next(); if (!$middlewares->valid()) { return new NullDispatcher(); } $middleware = $middlewares->current(); if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($this); } else { $middlewares->next(); } return $middleware; -
nicolas-grekas created this gist
Oct 16, 2018 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,143 @@ <?php interface MessageBusInterface { /** * @param object|Envelope $message */ public function dispatch($message, string $name = null): void; } interface BusMiddlewareInterface extends MessageBusInterface { public function setMiddlewareBus(MiddlewareBusInterface $bus): void; } interface MiddlewareBusInterface { public function getNextBusDispatcher(): MessageBusInterface; } class NullDispatcher implements MessageBusInterface { /** * {@inheritdoc} */ public function dispatch($message, string $name = null): void { // no-op } } trait BusMiddlewareTrait { private $bus; public function setMiddlewareBus(MiddlewareBusInterface $bus): void { $this->bus = $bus; } private function next(): MessageBusInterface { if (!$this->bus) { return new NullDispatcher(); } try { return $this->bus->getNextBusDispatcher(); } finally { $this->bus = null; } } } class BusMiddleware implements BusMiddlewareInterface { use BusMiddlewareTrait; /** * {@inheritdoc} */ public function dispatch($message, string $name = null): void { dump(1); $this->next()->dispatch($message, $name); dump(2); } } class DumpBus implements MessageBusInterface { /** * {@inheritdoc} */ public function dispatch($message, string $name = null): void { dump($message); } } class MessageBus implements MessageBusInterface, MiddlewareBusInterface { private $middlewareAggregate; private $middlewares; public function __construct(iterable $middlewares) { if (\is_array($middlewares)) { $middlewares = new \ArrayObject($middlewares); } elseif (!$middlewares instanceof \IteratorAggregate) { $middlewares = new \ArrayObject(iterator_to_array($middlewares, false)); } $this->middlewareAggregate = $middlewares; } /** * @param object|Envelope $message */ public function dispatch($message, string $name = null): void { $bus = clone $this; $bus->middlewares = $this->middlewareAggregate->getIterator(); $bus->middlewares->rewind(); if (!$bus->middlewares->valid()) { return; } $middleware = $bus->middlewares->current(); if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($bus); } $middleware->dispatch($message, $name); } public function getNextBusDispatcher(): MessageBusInterface { if (null === $this->middlewares) { throw new \LogicException('Cannot get next middleware on uninitialized message bus.'); } $this->middlewares->next(); $middleware = $this->middlewares->valid() ? $this->middlewares->current() : new NullDispatcher(); if ($middleware instanceof BusMiddlewareInterface) { $middleware->setMiddlewareBus($this); } return $middleware; } } $bus = new MessageBus(array( new BusMiddleware(), new DumpBus(), )); $bus->dispatch(123); $bus->dispatch(234);