Skip to content

Instantly share code, notes, and snippets.

@nicolas-grekas
Last active October 18, 2018 07:21
Show Gist options
  • Select an option

  • Save nicolas-grekas/1d08b6d5ecbb2504b788e378583920a0 to your computer and use it in GitHub Desktop.

Select an option

Save nicolas-grekas/1d08b6d5ecbb2504b788e378583920a0 to your computer and use it in GitHub Desktop.

Revisions

  1. nicolas-grekas revised this gist Oct 18, 2018. 1 changed file with 0 additions and 5 deletions.
    5 changes: 0 additions & 5 deletions message-bus.php
    Original 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.
    *
    * 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 MiddlewareInterface extends MessageBusInterface
    {
  2. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 45 additions and 55 deletions.
    100 changes: 45 additions & 55 deletions message-bus.php
    Original file line number Diff line number Diff line change
    @@ -8,10 +8,7 @@
    */
    interface MessageBusInterface
    {
    /**
    * @param object|Envelope $message
    */
    public function dispatch($message, string $name = null): void;
    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 BusMiddlewareInterface extends MessageBusInterface
    interface MiddlewareInterface extends MessageBusInterface
    {
    public function setMiddlewareBus(MiddlewareBusInterface $bus): void;
    /**
    * @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 MiddlewareBusInterface
    interface BusInterface
    {
    public function getNextBusDispatcher(): MessageBusInterface;
    public function next(): MessageBusInterface;
    }

    /**
    * Nothing fancy here, just the null design pattern.
    */
    class NullDispatcher implements MessageBusInterface
    class NullBus implements MessageBusInterface
    {
    /**
    * {@inheritdoc}
    */
    public function dispatch($message, string $name = null): void
    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.
    */
    trait BusMiddlewareTrait
    abstract class AbstractMiddleware implements MiddlewareInterface
    {
    private $bus;
    protected $bus;

    public function setMiddlewareBus(MiddlewareBusInterface $bus): void
    /**
    * @return static
    */
    public function withBus(BusInterface $bus): MiddlewareInterface
    {
    $this->bus = $bus;
    $self = clone $this;
    $self->bus = $bus;

    return $self;
    }

    private function next(): MessageBusInterface
    protected function next(): MessageBusInterface
    {
    try {
    return $this->bus ? $this->bus->getNextBusDispatcher() : new NullDispatcher();
    return $this->bus ? $this->bus->next() : new NullBus();
    } finally {
    $this->bus = null;
    }
    }
    }

    /**
    * A helper class to ease implementing stackable middlewareHandlers.
    */
    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
    class DumpMiddleware extends AbstractMiddleware
    {
    /**
    * {@inheritdoc}
    */
    public function dispatch($message, string $name = null): void
    public function dispatch($message): void
    {
    dump(1);
    $this->next()->dispatch($message, $name);
    $this->next()->dispatch($message);
    dump(2);
    }
    }
    @@ -115,19 +111,19 @@ class DumpBus implements MessageBusInterface
    /**
    * {@inheritdoc}
    */
    public function dispatch($message, string $name = null): void
    public function dispatch($message): void
    {
    dump($message);
    }
    }

    /**
    * This is the implementation of a message bus: it gets a list of middlewareHandlers as arguments.
    * This is the implementation of a message bus: it gets a list of middleware handlers as arguments.
    */
    class MessageBus implements MessageBusInterface, MiddlewareBusInterface
    class MessageBus implements MessageBusInterface, BusInterface
    {
    private $middlewareAggregate;
    private $middlewareHandlers;
    private $middlewareIterator;

    public function __construct(iterable $middlewareHandlers)
    {
    @@ -153,52 +149,46 @@ public function getIterator()
    /**
    * {@inheritdoc}
    */
    public function dispatch($message, string $name = null): void
    public function dispatch($message): void
    {
    $bus = clone $this;

    $middlewareHandlers = $bus->middlewareAggregate->getIterator();
    while ($middlewareHandlers instanceof \IteratorAggregate) {
    $middlewareHandlers = $middlewares->getIterator();
    $bus->middlewareIterator = $bus->middlewareAggregate->getIterator();
    while ($bus->middlewareIterator instanceof \IteratorAggregate) {
    $bus->middlewareIterator = $bus->middlewareIterator->getIterator();
    }

    foreach ($bus->middlewareHandlers = $middlewares as $middleware) {
    if ($middleware instanceof BusMiddlewareInterface) {
    $middleware->setMiddlewareBus($bus);
    foreach ($bus->middlewareIterator as $middleware) {
    if ($middleware instanceof MiddlewareInterface) {
    $middleware = $middleware->withBus($bus);
    }

    $middleware->dispatch($message, $name);
    $middleware->dispatch($message);

    return;
    }
    }

    public function getNextBusDispatcher(): MessageBusInterface
    public function next(): MessageBusInterface
    {
    if (null === $middlewareHandlers = $this->middlewares) {
    if (null === $middlewareIterator = $this->middlewareIterator) {
    throw new \LogicException('Cannot get next middleware on uninitialized message bus.');
    }
    $middlewareIterator->next();

    $middlewareHandlers->next();

    if (!$middlewareHandlers->valid()) {
    return new NullDispatcher();
    }

    $middleware = clone $middlewareHandlers->current();

    if ($middleware instanceof BusMiddlewareInterface) {
    $middleware->setMiddlewareBus($this);
    if (!$middlewareIterator->valid()) {
    return new NullBus();
    }
    $middleware = $middlewareIterator->current();

    return $middleware;
    return $middleware instanceof MiddlewareInterface ? $middleware->withBus($this) : $middleware;
    }
    }

    // Let's demo all this

    $bus = new MessageBus(new \ArrayObject(array(
    new BusMiddleware(),
    new DumpMiddleware(),
    new DumpBus(),
    )));

  3. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 28 additions and 28 deletions.
    56 changes: 28 additions & 28 deletions message-bus.php
    Original 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*.
    * 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* middlewares that are *still* dispatchers.
    * This allows writting *stackable* middlewareHandlers that are *still* dispatchers.
    *
    * (there is no reason to force all middlewares be stackable BTW
    * (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 middlewares are cloned before being used then destroyed just after, so that they are
    * 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 middlewares together at runtime.
    * 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 middlewares.
    * 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 middlewares as arguments.
    * This is the implementation of a message bus: it gets a list of middlewareHandlers as arguments.
    */
    class MessageBus implements MessageBusInterface, MiddlewareBusInterface
    {
    private $middlewareAggregate;
    private $middlewares;
    private $middlewareHandlers;

    public function __construct(iterable $middlewares)
    public function __construct(iterable $middlewareHandlers)
    {
    if ($middlewares instanceof \IteratorAggregate) {
    $this->middlewareAggregate = $middlewares;
    } elseif (\is_array($middlewares)) {
    $this->middlewareAggregate = new \ArrayObject($middlewares);
    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 = $middlewares;
    $this->middlewareAggregate->iterator = $middlewareHandlers;
    }
    }

    @@ -157,12 +157,12 @@ public function dispatch($message, string $name = null): void
    {
    $bus = clone $this;

    $middlewares = $bus->middlewareAggregate->getIterator();
    while (!$middlewares instanceof \Iterator) {
    $middlewares = $middlewares->getIterator();
    $middlewareHandlers = $bus->middlewareAggregate->getIterator();
    while ($middlewareHandlers instanceof \IteratorAggregate) {
    $middlewareHandlers = $middlewares->getIterator();
    }

    foreach ($bus->middlewares = $middlewares as $middleware) {
    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 === $middlewares = $this->middlewares) {
    if (null === $middlewareHandlers = $this->middlewares) {
    throw new \LogicException('Cannot get next middleware on uninitialized message bus.');
    }

    $middlewares->next();
    $middlewareHandlers->next();

    if (!$middlewares->valid()) {
    if (!$middlewareHandlers->valid()) {
    return new NullDispatcher();
    }

    $middleware = clone $middlewares->current();
    $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 middlewares, that build around two *separate*
    * interfaces: one for dispatchers and a separate one for stackable middlewares.
    * 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
    * middlewares under a separate uncompatible interface than the core decorated one.
    * 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 middlewares have to be built around a generic indirection
    * 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:
    * - middlewares *are* dispatchers
    * - 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 middlewares.
    * the same amount of logic to call stackable middleware.
    */
  4. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 16 additions and 6 deletions.
    22 changes: 16 additions & 6 deletions message-bus.php
    Original file line number Diff line number Diff line change
    @@ -131,12 +131,22 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface

    public function __construct(iterable $middlewares)
    {
    if ($middlewareHandlers instanceof \IteratorAggregate) {
    if ($middlewares instanceof \IteratorAggregate) {
    $this->middlewareAggregate = $middlewares;
    } elseif (\is_array($middlewareHandlers)) {
    } elseif (\is_array($middlewares)) {
    $this->middlewareAggregate = new \ArrayObject($middlewares);
    } else {
    $this->middlewareAggregate = new \ArrayObject(iterator_to_array($middlewares, false));
    $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 \IteratorAggregate) {
    while (!$middlewares instanceof \Iterator) {
    $middlewares = $middlewares->getIterator();
    }

    @@ -187,10 +197,10 @@ public function getNextBusDispatcher(): MessageBusInterface

    // Let's demo all this

    $bus = new MessageBus(array(
    $bus = new MessageBus(new \ArrayObject(array(
    new BusMiddleware(),
    new DumpBus(),
    ));
    )));

    $bus->dispatch(123);
    $bus->dispatch(234);
  5. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion message-bus.php
    Original 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 \Iterator) {
    while ($middlewares instanceof \IteratorAggregate) {
    $middlewares = $middlewares->getIterator();
    }

  6. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 7 additions and 2 deletions.
    9 changes: 7 additions & 2 deletions message-bus.php
    Original file line number Diff line number Diff line change
    @@ -147,7 +147,12 @@ public function dispatch($message, string $name = null): void
    {
    $bus = clone $this;

    foreach ($bus->middlewares = $bus->middlewareAggregate->getIterator() as $middleware) {
    $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 build around a generic indirection
    * 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.
    *
  7. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 4 additions and 30 deletions.
    34 changes: 4 additions & 30 deletions message-bus.php
    Original file line number Diff line number Diff line change
    @@ -121,33 +121,6 @@ public function dispatch($message, string $name = null): void
    }
    }

    /**
    * @internal
    */
    class MiddlewareAggregate implements \IteratorAggregate
    {
    private $aggregate;
    private $iterator;

    public function __construct(&$aggregate, iterable $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);
    }
    }

    /**
    * 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 (\is_array($middlewares)) {
    if ($middlewareHandlers instanceof \IteratorAggregate) {
    $this->middlewareAggregate = $middlewares;
    } elseif (\is_array($middlewareHandlers)) {
    $this->middlewareAggregate = new \ArrayObject($middlewares);
    } else {
    new MiddlewareAggregate($this->middlewareAggregate, $middlewares);
    $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);
    $bus->middlewares->next();

    return;
    }
  8. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion message-bus.php
    Original 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 term of implementation complexity, both solutions are similar: they both require
    * In terms of implementation complexity, both solutions are similar: they both require
    * the same amount of logic to call stackable middlewares.
    */
  9. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion message-bus.php
    Original file line number Diff line number Diff line change
    @@ -166,7 +166,7 @@ public function __construct(iterable $middlewares)
    }

    /**
    * @param object|Envelope $message
    * {@inheritdoc}
    */
    public function dispatch($message, string $name = null): void
    {
  10. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions message-bus.php
    Original 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.
    * 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
    {
  11. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions message-bus.php
    Original 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.
    * and middlewares are cloned before being used and destroyed just after so that they prototype
    * service is still stateless.
    */
    interface BusMiddlewareInterface extends MessageBusInterface
    {
  12. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions message-bus.php
    Original 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 bus must be unset after use (it's "use-once") so that middlewares are
    * still stateless - see implementation below.
    * 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 = $middlewares->current();
    $middleware = clone $middlewares->current();

    if ($middleware instanceof BusMiddlewareInterface) {
    $middleware->setMiddlewareBus($this);
  13. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion message-bus.php
    Original 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 builds around two *separate*
    * 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
  14. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion message-bus.php
    Original file line number Diff line number Diff line change
    @@ -158,7 +158,7 @@ class MessageBus implements MessageBusInterface, MiddlewareBusInterface

    public function __construct(iterable $middlewares)
    {
    if (0&&\is_array($middlewares)) {
    if (\is_array($middlewares)) {
    $this->middlewareAggregate = new \ArrayObject($middlewares);
    } else {
    new MiddlewareAggregate($this->middlewareAggregate, $middlewares);
  15. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 3 additions and 4 deletions.
    7 changes: 3 additions & 4 deletions message-bus.php
    Original 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, \Traversable $middlewares)
    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 (\is_array($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);
    } else {
    $middlewares->next();
    }

    return $middleware;
  16. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions message-bus.php
    Original 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 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.
    * 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
    {
  17. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions message-bus.php
    Original file line number Diff line number Diff line change
    @@ -220,21 +220,21 @@ public function getNextBusDispatcher(): MessageBusInterface
    /**
    * 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.
    * This design opposes PSR15-style middlewares, that builds around two *separate*
    * interfaces: one for dispatchers and a separate one for stackable middlewares.
    *
    * 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.
    * 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 both flaws:
    * The design in this gist fixes all these flaws:
    * - middlewares *are* dispatchers
    * - then can be made stackable using an *optional* additional *extending* interface
    * - 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
  18. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 8 additions and 3 deletions.
    11 changes: 8 additions & 3 deletions message-bus.php
    Original 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 dispatcher *and middlewares*.
    * 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 stack-able BTW
    * so this implementing this is not a requirement to write a middleware)
    * (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
    {
  19. nicolas-grekas revised this gist Oct 17, 2018. 1 changed file with 70 additions and 2 deletions.
    72 changes: 70 additions & 2 deletions message-bus.php
    Original 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
    }
    }

    class BusMiddleware implements BusMiddlewareInterface
    /**
    * A helper class to ease implementing stackable middlewares.
    */
    abstract class AbstractBusMiddleware implements BusMiddlewareInterface
    {
    use BusMiddlewareTrait;
    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.
    */
  20. nicolas-grekas revised this gist Oct 16, 2018. 1 changed file with 49 additions and 23 deletions.
    72 changes: 49 additions & 23 deletions message-bus.php
    Original 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
    {
    if (!$this->bus) {
    return new NullDispatcher();
    }

    try {
    return $this->bus->getNextBusDispatcher();
    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)) {
    $middlewares = new \ArrayObject($middlewares);
    } elseif (!$middlewares instanceof \IteratorAggregate) {
    $middlewares = new \ArrayObject(iterator_to_array($middlewares, false));
    $this->middlewareAggregate = new \ArrayObject($middlewares);
    } else {
    new MiddlewareAggregate($this->middlewareAggregate, $middlewares);
    }

    $this->middlewareAggregate = $middlewares;
    }

    /**
    @@ -100,34 +124,36 @@ public function __construct(iterable $middlewares)
    public function dispatch($message, string $name = null): void
    {
    $bus = clone $this;
    $bus->middlewares = $this->middlewareAggregate->getIterator();
    $bus->middlewares->rewind();

    if (!$bus->middlewares->valid()) {
    return;
    }
    foreach ($bus->middlewares = $bus->middlewareAggregate->getIterator() as $middleware) {
    if ($middleware instanceof BusMiddlewareInterface) {
    $middleware->setMiddlewareBus($bus);
    }

    $middleware = $bus->middlewares->current();
    $middleware->dispatch($message, $name);

    if ($middleware instanceof BusMiddlewareInterface) {
    $middleware->setMiddlewareBus($bus);
    return;
    }

    $middleware->dispatch($message, $name);
    }

    public function getNextBusDispatcher(): MessageBusInterface
    {
    if (null === $this->middlewares) {
    if (null === $middlewares = $this->middlewares) {
    throw new \LogicException('Cannot get next middleware on uninitialized message bus.');
    }

    $this->middlewares->next();
    $middlewares->next();

    if (!$middlewares->valid()) {
    return new NullDispatcher();
    }

    $middleware = $this->middlewares->valid() ? $this->middlewares->current() : new NullDispatcher();
    $middleware = $middlewares->current();

    if ($middleware instanceof BusMiddlewareInterface) {
    $middleware->setMiddlewareBus($this);
    } else {
    $middlewares->next();
    }

    return $middleware;
  21. nicolas-grekas created this gist Oct 16, 2018.
    143 changes: 143 additions & 0 deletions message-bus.php
    Original 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);