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.
A middleware stack that doesn't double the stack size
<?php
error_reporting(-1);
require 'vendor/autoload.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
{
try {
return $this->bus ? $this->bus->getNextBusDispatcher() : new NullDispatcher();
} 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);
}
}
/**
* @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;
private $middlewares;
public function __construct(iterable $middlewares)
{
if (\is_array($middlewares)) {
$this->middlewareAggregate = new \ArrayObject($middlewares);
} else {
new MiddlewareAggregate($this->middlewareAggregate, $middlewares);
}
}
/**
* @param object|Envelope $message
*/
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;
}
}
$bus = new MessageBus(array(
new BusMiddleware(),
new DumpBus(),
));
$bus->dispatch(123);
$bus->dispatch(234);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment