Skip to content

Instantly share code, notes, and snippets.

@dkraczkowski
Last active August 19, 2017 17:01
Show Gist options
  • Save dkraczkowski/79020b7b557dbd9dc73db1b1a3026953 to your computer and use it in GitHub Desktop.
Save dkraczkowski/79020b7b557dbd9dc73db1b1a3026953 to your computer and use it in GitHub Desktop.
PHP middleware composer
<?php namespace Octopus;
use \Closure;
class MiddlewareComposer
{
protected $pipe;
protected $context;
protected $errorHandler;
protected $successHandler;
protected $finallyHandler;
public function __construct(array $pipe = [], $context = null)
{
$this->pipe = $pipe;
$this->context = $context;
}
public function __invoke(...$args)
{
return call_user_func_array([$this, 'run'], $args);
}
public function error(Closure $handler)
{
$this->errorHandler = $handler;
return $this;
}
public function success(Closure $handler)
{
$this->successHandler = $handler;
return $this;
}
public function finally(Closure $handler)
{
$this->finallyHandler = $handler;
return $this;
}
public function context(object $context)
{
$this->context = $context;
return $this;
}
public function run(...$args)
{
$iterate = false;
if (1 < count($this->pipe)) {
$iterate = true;
}
$stack = []; // Result stack of generators
$result = null;
$next = function (...$newArgs) use (&$args) {
$args = $newArgs;
return $this;
};
try {
if (!$iterate) {
list($handler) = $this->pipe;
return $this->call($handler, array_merge($args, [$next]));
}
// Iterate through the pipe
foreach ($this->pipe as $result) {
// Infinite loop for generators processing and closure results
for (;;) {
// If result is type of Generator we have to add it to stack so later on we will call it reversely
// to close the execution stack
if ($result instanceof \Iterator) {
// Generator has returned next statement
$value = $result->current();
if ($value === $this) {
$stack[] = $result;
break;
}
// Generator has finished its job
if (!$result->valid()) {
break;
}
// Iterate through generator.
$result->next();
} elseif (is_callable($result)) {
$result = $this->call($result, array_merge($args, [$next]));
} else {
break;
}
}
}
} catch (\Exception $exception) {
$caught = false;
// Rethrow exception in all generators in case they contain try/catch block
while (!$caught && $generator = array_pop($stack)) {
try {
$generator->throw($exception);
$caught = true;
} catch (\Exception $exception) {
continue;
}
}
// None of generators has catch the exception
if (!$caught) {
if ($this->errorHandler) {
$result = call_user_func($this->errorHandler, $exception);
} else {
throw $exception;
}
}
} finally {
while ($generator = array_pop($stack)) {
while ($generator->valid()) {
$generator->next();
}
}
if (!isset($exception) && $this->successHandler) {
call_user_func_array($this->successHandler, $args);
}
if ($this->finallyHandler) {
if (isset($exception)) {
$args += [$exception];
}
call_user_func_array($this->finallyHandler, $args);
}
}
return $result;
}
protected function call($handler, array $args = [])
{
$result = null;
if (!is_callable($handler)) {
$result = $handler;
} else {
if ($handler instanceof \Closure) {
$handler = \Closure::bind($handler, $this->context);
}
$result = call_user_func_array($handler, $args);
}
return $result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment