ducts
A PSR-7 compliant middleware dispatcher for Hack/HHVM., (*1)
It's the same idea as Connect, Relay, Stratigility, Middleman, etc., but in strict mode Hack., (*2)
, (*3)
Installation
You can install this library using Composer:, (*4)
$ composer require appertly/ducts
- The master branch (version 0.x) of this project requires HHVM 3.12 and has no dependencies.
Compliance
Releases of this library will conform to Semantic Versioning., (*5)
Our code is intended to comply with PSR-1, PSR-2, and PSR-4. If you find any issues related to standards compliance, please send a pull request!, (*6)
Layers and Dispatching
The idea here is that you supply a list of functions which:
1. Accept a request, a response, and a next function
2. Possibly modify the request and response
3. Possibly invoke the next function, which returns a response
4. Possibly modify the returned response
5. Return the response, (*7)
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
function foobar(Request $req, Response $res, (function (Request,Response): Response) $next): Response
{
// 2. possibly modify the request and response
// 3. invoke next
$res = $next($req, $res);
// 4. possibly modify response
// 5.
return $res;
}
Layers end up interacting like this:
* โ First layer
* โ Second layer
* โ Third layer
* โ Third layer
* โ Second layer
* โ First layer, (*8)
Dispatch a Request
use Ducts\Runner;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
/**
* @var \Psr\Http\Message\ServerRequestInterface $request The PSR-7 HTTP request
* @var \Psr\Http\Message\ResponseInterface $response The PSR-7 HTTP response
*/
// any Traversable will do. Use an array, Vector, etc.
$runner = new Runner([
// you can use closures
function(Request $req, Response $res, (function (Request,Response): Response) $next): Response {
$req = $req->withAttribute('foo', 'bar');
return $next($req, $res);
},
// or lambdas
($req, $res, $next) ==> $next($req, $res)->withHeader('X-Ducts', 'Yes')
// or objects with an __invoke method
new MyInvokableThing(),
// or Hack-style callables
fun('myLayer'),
class_meth('MyLayer', 'staticMethod'),
inst_meth($myObject, 'layer')
]);
$response = $runner->run($request, $response);
// Runner::run can be called multiple times if needed
$anotherResponse = $runner->run($request, $response);
You can also make use of the ResolvingRunner
if you'd like to look up the layer callable., (*9)
// a function to look up names
$resolver = function(mixed $name): (function (Request,Response,(function(Request,Response): Response)): Response) {
// look up your callable thing in a dependency injection container
$container = MyClass::getSomeContainer();
return $container->get($name);
};
// Items in this traversable can be a combination of functions or items to resolve
$runner = new ResolvingRunner([
($req, $res, $next) ==> $next($req, $res)->withHeader('X-Ducts', 'Yes'),
'MyClassName',
'AnotherClass'
], $resolver);
A Runner as a Layer
You can use a Runner
as a layer by calling $runner->layer()
!
For example, you declare Runner
$bar
to be the second layer of three in Runner
$foo
.
If $bar
has three layers, it will participate in $foo
like this:, (*10)
- โ
$foo
layer 1
- โ
$bar
layer 1
- โ
$bar
layer 2
- โ
$bar
layer 3
- โ
$foo
layer 3
- โ
$foo
layer 3
- โ
$bar
layer 3
- โ
$bar
layer 2
- โ
$bar
layer 1
- โ
$foo
layer 1