Skip to content

Commit

Permalink
Added Link and Context
Browse files Browse the repository at this point in the history
- Created Link object to represent a chain of
  middleware
- Added Context to allow clean up how you can add
  extra state to a middleware.
- Updated the middleware api to accept a Mw\Link
  as the last arg instead of a closure.

Signed-off-by: RJ Garcia <rj@bighead.net>
  • Loading branch information
ragboyjr committed Jan 4, 2017
1 parent 07cabb8 commit 3b208ff
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 25 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Changed

- Added new Link and Context entities to provide further customization and features
to middleware

## [0.3.3] - 2017-01-03
### Changed
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ Or build them:
make doc
```


## Tests and Examples

Run tests via:
Expand Down
2 changes: 1 addition & 1 deletion doc/cookbook/custom-method-middleware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ Here's an example using classes with a method of ``handle``
$handler = mw\compose([
new IdMw(),
new AppendMw('b')
], null, mw\methodInvoke('handle'));
], null, new Mw\Context\StdContext(mw\methodInvoke('handle')));
assert($handler('a') == 'ab');
9 changes: 9 additions & 0 deletions src/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Krak\Mw;

/** returns the Invoke Instance */
interface Context
{
public function getInvoke();
}
37 changes: 37 additions & 0 deletions src/Context/PimpleContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Krak\Mw\Context;

use Pimple\Container,
Krak\Mw;

class PimpleContext implements \ArrayAccess, Mw\Context
{
private $container;
private $invoke;

public function __construct(Container $container, $invoke = null) {
$this->container = $container;
$this->invoke = $invoke ?: Mw\pimpleAwareInvoke($container);
}

public function getInvoke() {
return $this->invoke;
}

public function offsetSet($offset, $value) {
return $this->container->offsetSet($offset, $value);
}

public function offsetGet($offset) {
return $this->container->offsetGet($offset);
}

public function offsetExists($offset) {
return $this->container->offsetExists($offset);
}

public function offsetUnset($offset) {
return $this->container->offsetUnset($offset);
}
}
18 changes: 18 additions & 0 deletions src/Context/StdContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Krak\Mw\Context;

use Krak\Mw\Context;

class StdContext implements Context
{
private $invoke;

public function __construct($invoke = 'call_user_func') {
$this->invoke = $invoke;
}

public function getInvoke() {
return $this->invoke;
}
}
36 changes: 36 additions & 0 deletions src/Link.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Krak\Mw;

/** Represents a link in the middleware chain. A link instance is passed to every middleware
as the last parameter which allows the next middleware to be called */
class Link
{
private $mw;
private $next;
private $ctx;

public function __construct($mw, Context $ctx, Link $next = null) {
$this->mw = $mw;
$this->ctx = $ctx;
$this->next = $next;
}

public function __invoke(...$params) {
$mw = $this->mw;
$invoke = $this->ctx->getInvoke();
$params[] = $this->next;
return $invoke($mw, ...$params);

return $this->invoke($this->mw, ...$params);
}

/** Chains a middleware to the current link */
public function chain($mw) {
return new static($mw, $this->ctx, $this);
}

public function getContext() {
return $this->ctx;
}
}
16 changes: 12 additions & 4 deletions src/MwStack.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
class MwStack implements Countable
{
private $name;
private $ctx;
private $link_class;
private $entries;
private $heap;
private $name_map;

public function __construct($name) {
public function __construct($name, Context $ctx = null, $link_class = Link::class) {
$this->name = $name;
$this->ctx = $ctx;
$this->link_class = $link_class;
$this->entries = [];
$this->heap = new SplMinHeap();
$this->name_map = [];
Expand Down Expand Up @@ -160,7 +164,11 @@ public function compose($last = null) {
throw new \RuntimeException(sprintf('Middleware stack "%s" was not able to return a response. No middleware in the stack returned a response.', $this->getName()));
};

return compose($this->normalize(), $last);
return compose($this->normalize(), $this->ctx, $last, $this->link_class);
}

public function withEntries($entries) {
return static::createFromEntries($this->name, $entries, $this->ctx, $this->link_class);
}

public function getEntries() {
Expand All @@ -171,8 +179,8 @@ public function getEntries() {
}
}

public static function createFromEntries($name, $entries) {
$stack = new self($name);
public static function createFromEntries($name, $entries, Context $ctx = null, $link_class = Link::class) {
$stack = new static($name, $ctx, $link_class);
foreach ($entries as $entry) {
$stack->insertEntry($entry, 'array_push');
}
Expand Down
45 changes: 26 additions & 19 deletions src/mw.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,27 @@ function($a, $b, $next) {
```
@param array $mws The set of middleware to compose
@param Context|null $ctx The context for the middleware
@param callable $last The final handler in case no middleware resolves the arguments
@return \Closure the composed set of middleware as a handler
*/
function compose(array $mws, callable $last = null) {
$last = $last ?: function() {
function compose(array $mws, Context $ctx = null, callable $last = null, $link_class = Link::class) {
$ctx = $ctx ?: new Context\StdContext();
$last = $last ?: new $link_class(function() {
throw new RuntimeException("No middleware returned a response.");
};
}, $ctx);

return array_reduce($mws, function($acc, $mw) {
return function(...$params) use ($acc, $mw) {
$params[] = $acc;
if (!$last instanceof Link) {
$last = new $link_class($last, $ctx);
}

return $mw(...$params);
};
$head = array_reduce($mws, function($acc, $mw) {
return $acc->chain($mw);
}, $last);

return function(...$params) use ($head) {
return $head(...$params);
};
}

/** Group a set of middleware into one. This internally just
Expand Down Expand Up @@ -68,9 +74,8 @@ function($v) { return $v; },
*/
function group(array $mws) {
return function(...$params) use ($mws) {
list($params, $next) = _splitArgs($params);

$handle = compose($mws, $next);
list($params, $link) = splitArgs($params);
$handle = compose($mws, $link->getContext(), $link);
return $handle(...$params);
};
}
Expand Down Expand Up @@ -101,7 +106,8 @@ function lazy(callable $mw_gen) {
$mw = $mw_gen();
}

return $mw(...$params);
$link = end($params)->chain($mw);
return $link(...$params);
};
}

Expand All @@ -121,12 +127,12 @@ function() { return 1; },
*/
function filter(callable $mw, callable $predicate) {
return function(...$all_params) use ($mw, $predicate) {
list($params, $next) = _splitArgs($all_params);
list($params, $link) = splitArgs($all_params);
if ($predicate(...$params)) {
return $mw(...$all_params);
$link = $link->chain($mw);
}

return $next(...$params);
return $link(...$params);
};
}

Expand All @@ -135,8 +141,8 @@ function stackEntry($mw, $sort = 0, $name = null) {
return [$mw, $sort, $name];
}

function stack($name, array $entries = []) {
return MwStack::createFromEntries($name, $entries);
function stack($name, array $entries = [], Context $context = null, $link_class = Link::class) {
return MwStack::createFromEntries($name, $entries, $context, $link_class);
}

/** merges multiple stacks together into a new stack */
Expand All @@ -146,7 +152,7 @@ function stackMerge(...$stacks) {
return $stack->getEntries();
}, $stacks));

return MwStack::createFromEntries($stacks[0]->getName(), $entries);
return $stacks[0]->withEntries($entries);
}

/** invokes middleware while checking if the mw is a service defined in the pimple
Expand Down Expand Up @@ -178,7 +184,8 @@ function methodInvoke($method, $allow_callable = true, $invoke = 'call_user_func
};
}

function _splitArgs($params) {
/** utility method for splitting the parameters into the params and the next */
function splitArgs($params) {
return [array_slice($params, 0, -1), end($params)];
}

Expand Down
60 changes: 60 additions & 0 deletions test/mw.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ function() { return 1; },
$handler = $stack->compose();
assert($handler('') == 'cba');
});
it('allows custom context', function() {
$stack = mw\stack('stack', [], new Mw\Context\StdContext(function() { return 1; }));
$stack->push(id());
$handler = $stack->compose();
assert($handler(2) === 1);
});
});
describe('#stackMerge', function() {
it('merges stacks together into a new stack', function() {
Expand All @@ -185,4 +191,58 @@ function() { return 1; },
assert($handler('') == 'cba');
});
});
describe('#pimpleAwareInvoke', function() {
it('uses container if the mw is a service definition before invoking', function() {
$c = new \Pimple\Container();
$c['a'] = function() { return function() {return 'abc';}; };
$handler = mw\compose([
'a',
], new Mw\Context\StdContext(mw\pimpleAwareInvoke($c)));
assert('abc' == $handler());
});
});
describe('#methodInvoke', function() {
it('will invoke a specific method instead of using a callable', function() {
$handler = mw\compose([
new IdMw(),
new AppendMw('b')
], new Mw\Context\StdContext(mw\methodInvoke('handle', false)));

assert($handler('a') == 'ab');
});
it('will allow mixed callable and methods', function() {
$handler = mw\compose([
id(),
new AppendMw('b')
], new Mw\Context\StdContext(mw\methodInvoke('handle', true)));

assert($handler('a') == 'ab');
});
it('will throw an exception if it cannot invoke', function() {
$handler = mw\compose([
id(),
new StdClass(),
new AppendMw('b')
], new Mw\Context\StdContext(mw\methodInvoke('handle')));

try {
$handler('a');
assert(false);
} catch (LogicException $e) {
assert(true);
}
});
});
describe('Context\PimpleContext', function() {
it('allows pimple access via context', function() {
$container = new Pimple\Container();
$container['a'] = 1;
$handler = mw\compose([
function($v, $next) {
return $v + $next->getContext()['a'];
}
], new Mw\Context\PimpleContext($container));
assert($handler(1) == 2);
});
});
});

0 comments on commit 3b208ff

Please sign in to comment.