zendframework/zend-expressive-helpers
Helper/Utility classes for Expressive
Requires
- php: ^7.1
- psr/container: ^1.0
- psr/http-message: ^1.0.1
- psr/http-server-middleware: ^1.0
- zendframework/zend-expressive-router: ^3.0
Requires (Dev)
- malukenho/docheader: ^0.1.6
- mockery/mockery: ^1.0
- phpunit/phpunit: ^7.0.2
- zendframework/zend-coding-standard: ~1.0.0
- zendframework/zend-diactoros: ^1.7.1
README
Helper classes for Expressive.
Installation
Install this library using composer:
$ composer require zendframework/zend-expressive-helpers
Helpers Provided
UrlHelper
Zend\Expressive\Helper\UrlHelper
provides the ability to generate a URI path
based on a given route defined in the Zend\Expressive\Router\RouterInterface
.
The provided Zend\Expressive\Helper\UrlHelperMiddleware
can look for a
Zend\Expressive\Router\RouteResult
request attribute, and, if present, inject
the UrlHelper
with it; when this occurs, if the route being used to generate
a URI was also the one matched during routing, you can provide a subset of
routing parameters, and any not provided will be pulled from those matched.
To register the UrlHelperMiddleware
:
use Zend\Expressive\Helper\UrlHelperMiddleware; use Zend\Expressive\Router\Middleware\DispatchMiddleware; use Zend\Expressive\Router\Middleware\RouteMiddleware; $app->pipe(RouteMiddleware::class); $app->pipe(UrlHelperMiddleware::class); $app->pipe(DispatchMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ // 'routing' => [ // 'middleware' => [ // RouteMiddleware::class, // UrlHelperMiddleware::class, // DispatchMiddleware::class, // ], // 'priority' => 1, // ], // ], // ]
Compose the helper in your handler (or elsewhere), and then use it to generate URI paths:
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Diactoros\Response; use Zend\Expressive\Helper\UrlHelper; class FooHandler implements RequestHandlerInterface { private $helper; public function __construct(UrlHelper $helper) { $this->helper = $helper; } public function handle(ServerRequestInterface $request) : ResponseInterface { $response = new Response(); return $response->withHeader( 'Link', $this->helper->generate('resource', ['id' => 'sha1']) ); } }
You can use the methods generate()
and __invoke()
interchangeably (i.e., you
can use the helper as a function if desired). The signature is:
function ( string $routeName = null, array $routeParams = [], array $queryParams = [], string $fragmentIdentifier = null, array $options = [] ) : string
Where:
$routeName
is the name of a route defined in the composed router. You may omit this argument if you want to generate the path for the currently matched request.$routeParams
is an array of substitutions to use for the provided route, with the following behavior:- If a
RouteResult
is composed in the helper, and the$routeName
matches it, the provided$params
will be merged with any matched parameters, with those provided taking precedence. - If a
RouteResult
is not composed, or if the composed result does not match the provided$routeName
, then only the$params
provided will be used for substitutions. - If no
$params
are provided, and the$routeName
matches the currently matched route, then any matched parameters found will be used. parameters found will be used. - If no
$params
are provided, and the$routeName
does not match the currently matched route, or if no route result is present, then no substitutions will be made.
- If a
Each method will raise an exception if:
- No
$routeName
is provided, and noRouteResult
is composed. - No
$routeName
is provided, aRouteResult
is composed, but that result represents a matching failure. - The given
$routeName
is not defined in the router.
Base Path support
If your application is running under a subdirectory, or if you are running
pipeline middleware that is intercepting on a subpath, the paths generated
by the router may not reflect the base path, and thus be invalid. To
accommodate this, the UrlHelper
supports injection of the base path; when
present, it will be prepended to the path generated by the router.
As an example, perhaps you have middleware running to intercept a language
prefix in the URL; this middleware could then inject the UrlHelper
with the
detected language, before stripping it off the request URI instance to pass on
to the router:
use Locale; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Expressive\Helper\UrlHelper; class LocaleMiddleware implements MiddlewareInterface { private $helper; public function __construct(UrlHelper $helper) { $this->helper = $helper; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface { $uri = $request->getUri(); $path = $uri->getPath(); if (! preg_match('#^/(?P<lang>[a-z]{2})/#', $path, $matches)) { return $handler->handle($request); } $lang = $matches['lang']; Locale::setDefault($lang); $this->helper->setBasePath($lang); return $handler->handle( $request->withUri( $uri->withPath(substr($path, 3)) ) ); } }
(Note: if the base path injected is not prefixed with /
, the helper will add
the slash.)
Paths generated by the UriHelper
from this point forward will have the
detected language prefix.
ServerUrlHelper
Zend\Expressive\Helper\ServerUrlHelper
provides the ability to generate a full
URI by passing only the path to the helper; it will then use that path with the
current Psr\Http\Message\UriInterface
instance provided to it in order to
generate a fully qualified URI.
In order to use the helper, you will need to inject it with the current
UriInterface
from the request instance. To automate this, we provide
Zend\Expressive\Helper\ServerUrlMiddleware
, which composes a ServerUrl
instance, and, when invoked, injects it with the URI instance.
As such, you will need to:
- Register the
ServerUrlHelper
as a service in your container. - Register the
ServerUrlMiddleware
as a service in your container. - Register the
ServerUrlMiddleware
early in your middleware pipeline.
To register the ServerUrlMiddleware
in your middleware pipeline:
use Zend\Expressive\Helper\ServerUrlMiddleware; use Zend\Expressive\Router\Middleware\DispatchMiddleware; use Zend\Expressive\Router\Middleware\RouteMiddleware; // Do this early, before piping other middleware or routes: $app->pipe(ServerUrlMiddleware::class); /* ... */ $app->pipe(RouteMiddleware::class); $app->pipe(DispatchMiddleware::class); // Or use configuration: // [ // 'middleware_pipeline' => [ // [ // 'middleware' => ServerUrlMiddleware::class, // 'priority' => PHP_INT_MAX, // ], // ], // ]
Compose the helper in your hanlder (or elsewhere), and then use it to generate URI paths:
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Diactoros\Response; use Zend\Expressive\Helper\ServerUrlHelper; class FooHandler implements RequestHandlerInterface { private $helper; public function __construct(ServerUrlHelper $helper) { $this->helper = $helper; } public function handle(ServerRequestInterface $request) : ResponseInterface { $response = new Response(); return $response->withHeader( 'Link', $this->helper->generate() . '; rel="self"' ); } }
You can use the methods generate()
and __invoke()
interchangeably (i.e., you
can use the helper as a function if desired). The signature is:
function (string $path = null) : string
Where:
$path
, when provided, can be a string path to use to generate a URI.
BodyParams middleware
One aspect of PSR-7 is that it allows you to parse the raw request body, and
then create a new instance with the results of parsing that later processes can
fetch via getParsedBody()
. It does not provide any actual facilities for
parsing, which means you must write middleware to do so.
This package provides such facilities via Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware
.
By default, this middleware will detect the following content types:
application/x-www-form-urlencoded
(standard web-based forms, without file uploads)application/json
,application/*+json
(JSON payloads)
You can register it manually:
use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware; $app->pipe(BodyParamsMiddleware::class);
or, if using Expressive, as pipeline middleware:
// config/autoload/middleware-pipeline.global.php use Zend\Expressive\Helper; return [ 'dependencies' => [ 'invokables' => [ Helper\BodyParams\BodyParamsMiddleware::class => Helper\BodyParams\BodyParamsMiddleware::class, /* ... */ ], 'factories' => [ /* ... */ ], ], 'middleware_pipeline' => [ [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class, 'priority' => 1000, ], 'routing' => [ /* ... */ ], ], ];
Strategies
If you want to intercept and parse other payload types, you can add strategies
to the middleware. Strategies implement Zend\Expressive\Helper\BodyParams\StrategyInterface
:
namespace Zend\Expressive\Helper\BodyParams; use Psr\Http\Message\ServerRequestInterface; interface StrategyInterface { /** * Match the content type to the strategy criteria. * * @param string $contentType * @return bool Whether or not the strategy matches. */ public function match($contentType); /** * Parse the body content and return a new response. * * @param ServerRequestInterface $request * @return ServerRequestInterface */ public function parse(ServerRequestInterface $request); }
You then register them with the middleware using the addStrategy()
method:
$bodyParams->addStrategy(new MyCustomBodyParamsStrategy());
To automate the registration, we recommend writing a factory for the
BodyParamsMiddleware
, and replacing the invokables
registration with a
registration in the factories
section of the middleware-pipeline.config.php
file:
use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware; class MyCustomBodyParamsStrategyFactory { public function __invoke($container) { $bodyParams = new BodyParamsMiddleware(); $bodyParams->addStrategy(new MyCustomBodyParamsStrategy()); return $bodyParams; } } // In config/autoload/middleware-pipeline.config.php: use Zend\Expressive\Helper; return [ 'dependencies' => [ 'invokables' => [ // Remove this line: Helper\BodyParams\BodyParamsMiddleware::class => Helper\BodyParams\BodyParamsMiddleware::class, /* ... */ ], 'factories' => [ // Add this line: Helper\BodyParams\BodyParamsMiddleware::class => MyCustomBodyParamsStrategy::class, /* ... */ ], ], ];
Removing the default strategies
If you do not want to use the default strategies (form data and JSON), you can
clear them from the middleware using clearStrategies()
:
$bodyParamsMiddleware->clearStrategies();
Note: if you do this, all strategies will be removed! As such, we recommend doing this only immediately before registering any custom strategies you might be using.
Content-Length middleware
In some cases, you may want to include an explicit Content-Length
response
header, without having to inject it manually. To facilitate this, we provide
Zend\Expressive\Helper\ContentLengthMiddleware
.
This middleware delegates the request, and operates on the returned response. It
will return a new response with the Content-Length
header injected under the
following conditions:
- No
Content-Length
header is already present AND - the body size is non-null.
To register it in your application, you will need to do two things: register the middleware with the container, and register the middleware in either your application pipeline, or within routed middleware.
To add it to your container, add the following configuration:
// In a `config/autoload/*.global.php` file, or a `ConfigProvider` class: use Zend\Expressive\Helper; return [ 'dependencies' => [ 'invokables' => [ Helper\ContentLengthMiddleware::class => Helper\ContentLengthMiddleware::class, ], ], ];
To register it as pipeline middleware to execute on any request:
// In `config/pipeline.php`: use Zend\Expressive\Helper; $app->pipe(Helper\ContentLengthMiddleware::class);
To register it within a routed middleware pipeline:
// In `config/routes.php`: use Zend\Expressive\Helper; $app->get('/download/tarball', [ Helper\ContentLengthMiddleware::class, Download\Tarball::class, ], 'download-tar');
Documentation
See the zend-expressive documentation tree, or browse online at https://docs.zendframework.com/zend-expressive/features/helpers/intro/