diff --git a/doc/book/helpers/body-parse.md b/doc/book/helpers/body-parse.md new file mode 100644 index 00000000..5473530d --- /dev/null +++ b/doc/book/helpers/body-parse.md @@ -0,0 +1,187 @@ +# BodyParse Middleware + +`Zend\Expressive\Helper\BodyParse\BodyParseMiddleware` provides generic PSR-7 +middleware for parsing the request body into parameters, and returning a new +request instance that composes them. The subcomponent provides a strategy +pattern around matching the request `Content-Type`, and then parsing it, giving +you a flexibile approach that can grow with your accepted content types. + +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) + +## Registering the middleware + +You can register it manually: + +```php +use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware; + +$app->pipe(BodyParamsMiddleware::class); + +// register other middleware +// register routing middleware +$app->run(); +``` + +or as `pre_routing` pipeline middleware: + +```php +// config/autoload/middleware-pipeline.global.php +use Zend\Expressive\Helper; + +return [ + 'dependencies' => [ + 'invokables' => [ + Helper\BodyParams\BodyParamsMiddleware::class => Helper\BodyParams\BodyParamsMiddleware::class, + /* ... */ + ], + 'factories' => [ + /* ... */ + ], + ], + 'middleware_pipeline' => [ + 'pre_routing' => [ + [ 'middleware' => Helper\BodyParams\BodyParamsMiddleware::class ], + /* ... */ + ], + 'post_routing' => [ + /* ... */ + ], + ], +]; +``` + +Another option is to incorporate it in route-specific middleware queues: + +```php +// config/autoload/routes.global.php +use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware; + +return [ + 'dependencies' => [ + 'invokables' => [ + Helper\BodyParams\BodyParamsMiddleware::class => Helper\BodyParams\BodyParamsMiddleware::class, + /* ... */ + ], + 'factories' => [ + /* ... */ + ], + ], + 'routes' => [ + [ + 'name' => 'contact:process', + 'path' => '/contact/process', + 'middleware' => [ + BodyParamsMiddleware::class, + Contact\Process::class, + ], + 'allowed_methods' => ['POST'], + ] + ], +]; +``` + +This latter approach has a slight advantage: the middleware will only execute +for routes that require the processing. While the middleware has some checks to +ensure it only triggers for HTTP methods that accept bodies, those checks are +still overhead that you might want to avoid; the above strategy of using the +middleware only with specific routes can accomplish that. + +## 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`: + +```php +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: + +```php +$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: + +```php +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 + +By default, `BodyParamsMiddleware` composes the following strategies: + +- `Zend\Expressive\Helper\BodyParams\FormUrlEncodedStrategy` +- `Zend\Expressive\Helper\BodyParams\JsonStrategy` + +These provide the most basic approaches to parsing the request body. They +operate in the order they do to ensure the most common content type — +`application/x-www-form-urlencoded` — matches first, as the middleware +delegates parsing to the first match. + +If you do not want to use these default strategies, you can clear them from the +middleware using `clearStrategies()`: + +```php +$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. diff --git a/doc/book/helpers/bookdown.json b/doc/book/helpers/bookdown.json index 59744fcf..61f2c583 100644 --- a/doc/book/helpers/bookdown.json +++ b/doc/book/helpers/bookdown.json @@ -3,6 +3,7 @@ "content": [ {"Introduction": "intro.md"}, {"UrlHelper": "url-helper.md"}, - {"ServerUrlHelper": "server-url-helper.md"} + {"ServerUrlHelper": "server-url-helper.md"}, + {"Body Parsing Middleware": "body-parse.md"} ] } diff --git a/mkdocs.yml b/mkdocs.yml index c65fe2c3..acf73f4c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,7 +10,7 @@ pages: - { 'Routing Adapters': [{ Introduction: router/intro.md }, { 'Routing Interface': router/interface.md }, { 'URI Generation': router/uri-generation.md }, { 'Route Result Observers': router/result-observers.md }, { 'Routing vs Piping': router/piping.md }, { 'Using Aura': router/aura.md }, { 'Using FastRoute': router/fast-route.md }, { 'Using the ZF2 Router': router/zf2.md }] } - { Templating: [{ Introduction: template/intro.md }, { 'Template Renderer Interface': template/interface.md }, { 'Templated Middleware': template/middleware.md }, { 'Using Plates': template/plates.md }, { 'Using Twig': template/twig.md }, { 'Using zend-view': template/zend-view.md }] } - { 'Error Handling': error-handling.md } - - { Helpers: [{ Introduction: helpers/intro.md }, { UrlHelper: helpers/url-helper.md }, { ServerUrlHelper: helpers/server-url-helper.md }] } + - { Helpers: [{ Introduction: helpers/intro.md }, { UrlHelper: helpers/url-helper.md }, { ServerUrlHelper: helpers/server-url-helper.md }, { 'Body Parsing Middleware': helpers/body-parse.md }] } - { Emitters: emitters.md } - { Examples: usage-examples.md } - { Cookbook: [{ 'Prepending a common path to all routes': cookbook/common-prefix-for-routes.md }, { 'Route-specific middleware pipelines': cookbook/route-specific-pipeline.md }, { 'Setting custom 404 page handling': cookbook/custom-404-page-handling.md }, { 'Registering custom view helpers when using zend-view': cookbook/using-custom-view-helpers.md }, { 'Using zend-form view helpers': cookbook/using-zend-form-view-helpers.md }, { 'Using Expressive from a subdirectory': cookbook/using-a-base-path.md }, { 'Building modular applications': cookbook/modular-layout.md }] }