Skip to content

Instantly share code, notes, and snippets.

@dlundgren
Created May 18, 2016 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dlundgren/37d11d030267e98c3bcf1f5dd9aaa64c to your computer and use it in GitHub Desktop.
Save dlundgren/37d11d030267e98c3bcf1f5dd9aaa64c to your computer and use it in GitHub Desktop.
Middleware Stack with PSR-7
<?php
/**
* @file
* Contains \dlundgren\MiddlewareStack\PriorityQueue
*/
namespace dlundgren\MiddlewareStack;
use dlundgren\MiddlewareStack\PriorityQueue;
use Psr\Http\Message\ServerRequestInterface;
/**
* Middleware Stack
*
* @package dlundgren\MiddlewareStack
*/
class MiddlewareStack
{
const EARLY = 100;
const NORMAL = 0;
const LATE = -100;
/**
* List of middleware
*
* @var PriorityQueue
*/
private $stack;
/**
* Whether or not the stack is locked
*
* @var bool
*/
private $locked = false;
public function __construct()
{
$this->stack = new PriorityQueue();
}
/**
* Adds the middleware to the stack at the given priority
*
* @param callable $callable
* @param int $priority
*/
public function add(callable $callable, $priority = self::NORMAL)
{
if ($this->locked) {
throw new \RuntimeException("Middleware can't be added once the stack is dequeing");
}
$this->stack->after($callable, $priority);
}
/**
* Allows to retrieve the stack
*
* @seam
* @return PriorityQueue
*/
public function stack()
{
return $this->stack;
}
/**
* Resolves the Middlware Stack out so it can be run
*
* @param callable $app
* @return \Closure
*/
public function resolve(callable $app)
{
$this->locked = true;
$next = $app;
$this->stack->setReverseQueues(true);
foreach ($this->stack as $mw) {
$next = function (ServerRequestInterface $request) use ($mw, $next) {
return $mw($request, $next);
};
}
$this->stack->setReverseQueues(false);
return $next;
}
}
<?php
/**
* @file
* Contains \dlundgren\MiddlewareStack\PriorityQueue
*/
namespace NetId\Application\Shared\Utility;
// PHP < 5.6 can't use constant expressions
define('PRIORITY_QUEUE_MODE_NORMAL', \SplDoublyLinkedList::IT_MODE_FIFO | \SplDoublyLinkedList::IT_MODE_KEEP);
define('PRIORITY_QUEUE_MODE_REVERSE', \SplDoublyLinkedList::IT_MODE_LIFO | \SplDoublyLinkedList::IT_MODE_KEEP);
/**
* Priority Queue
*
* Unlike SplPriorityQueue this does not empty itself. Making it more easily reusable
*
* @package dlundgren\MiddlewareStack
*/
class PriorityQueue
implements \Countable, \IteratorAggregate
{
const AFTER = 1;
const BEFORE = -1;
const MODE_NORMAL = PRIORITY_QUEUE_MODE_NORMAL;
const MODE_REVERSE = PRIORITY_QUEUE_MODE_REVERSE;
/**
* List of values
*
* @var array[<priority>][]
*/
private $values = [];
/**
* How many items are in the queue
*
* @var int
*/
private $count = 0;
/**
* Whether the queues should be reversed
*
* @var bool
*/
private $reverseQueues = false;
/**
* Inserts the value into the queue
*
* @param $value
* @param $priority
*/
private function insert($value, $priority, $position)
{
if (!isset($this->values[$priority])) {
$queue = new \SplDoublyLinkedList();
$queue->setIteratorMode(self::MODE_NORMAL);
$this->values[$priority] = $queue;
krsort($this->values);
}
if ($position === self::AFTER) {
$this->values[$priority]->push($value);
}
else {
$this->values[$priority]->unshift($value);
}
$this->count++;
}
/**
* Inserts the value at the beginning of the given priority queue
*
* @param $value
* @param $priority
*/
public function before($value, $priority)
{
$this->insert($value, $priority, self::BEFORE);
}
/**
* Inserts the value at the end of the given priority queue
*
* @param $value
* @param $priority
*/
public function after($value, $priority)
{
$this->insert($value, $priority, self::AFTER);
}
/**
* Returns a count of the number of items in the queue
*
* @param int|null $priority The priority to count. Default is all
* @return int
*/
public function count($priority = null)
{
return $priority === null ? $this->count : $this->values[$priority]->count();
}
/**
* Sets whether or not the queues should be reversed
*
* @param bool $reverse
*/
public function setReverseQueues($reverse = false)
{
$this->reverseQueues = $reverse;
}
/**
* Generates the values
*
* @return \Generator
*/
public function getIterator()
{
foreach ($this->values as $queue) {
/** @var \SplDoublyLinkedList $queue */
$queue->setIteratorMode($this->reverseQueues ? PRIORITY_QUEUE_MODE_REVERSE : PRIORITY_QUEUE_MODE_NORMAL);
foreach ($queue as $value) {
yield $value;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment