Skip to content

Commit

Permalink
Added strategies to the BodyParamsMiddleware
Browse files Browse the repository at this point in the history
This patch builds on the one in zendframework#3, moving the act of parsing a Content-Type as
well as parsing the request body to *strategies*, which implement
`Zend\Expressive\BodyParams\StrategyInterface`. These define two methods:

- `match($contentType)`, which should accept a Content-Type header value, and
  return a boolean value indicating whether or not the strategy matches.
- `parse(ServerRequestInterface $request)`, which should accept the request
  instance, parse its raw body, and return a new request instance with the
  results of parsing injected as the parsed body.

The form and json matching/parsing were rewritten as strategies, and added as
default strategies to the middleware. Custom strategies can be written and then
attached using `addStrategy()`.
  • Loading branch information
weierophinney committed Dec 22, 2015
1 parent fe9916b commit 368e8ed
Show file tree
Hide file tree
Showing 9 changed files with 576 additions and 121 deletions.
136 changes: 136 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,142 @@ 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:

```php
use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware;

$app->pipe(BodyParamsMiddleware::class);
```

or, if using Expressive, 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' => [
/* ... */
],
],
];
```

#### 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

If you do not want to use the default strategies (form data and JSON), 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.

## Documentation

See the [zend-expressive](https://github.com/zendframework/zend-expressive/blob/master/doc/book)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\Helper;
namespace Zend\Expressive\Helper\BodyParams;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class BodyParamsMiddleware
{
/**
* @var StrategyInterface[]
*/
private $strategies = [];

/**
* List of request methods that do not have any defined body semantics, and thus
* will not have the body parsed.
Expand All @@ -26,6 +31,35 @@ class BodyParamsMiddleware
'OPTIONS',
];

/**
* Constructor
*
* Registers the form and json strategies.
*/
public function __construct()
{
$this->addStrategy(new FormUrlEncodedStrategy());
$this->addStrategy(new JsonStrategy());
}

/**
* Add a body parsing strategy to the middleware.
*
* @param StrategyInterface $strategy
*/
public function addStrategy(StrategyInterface $strategy)
{
$this->strategies[] = $strategy;
}

/**
* Clear all strategies from the middleware.
*/
public function clearStrategies()
{
$this->strategies = [];
}

/**
* Adds JSON decoded request body to the request, where appropriate.
*
Expand All @@ -41,38 +75,20 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
return $next($request, $response);
}

$header = $request->getHeaderLine('Content-Type');
$priorities = [
'form' => 'application/x-www-form-urlencoded',
'json' => '[/+]json',
];

$matched = false;
foreach ($priorities as $type => $pattern) {
$pattern = sprintf('#%s#', $pattern);
if (! preg_match($pattern, $header)) {
$header = $request->getHeaderLine('Content-Type');
foreach ($this->strategies as $strategy) {
if (! $strategy->match($header)) {
continue;
}
$matched = $type;
break;
}

switch ($matched) {
case 'form':
// $_POST is injected by default into the request body parameters.
break;
case 'json':
$rawBody = $request->getBody()->getContents();
return $next(
$request
->withAttribute('rawBody', $rawBody)
->withParsedBody(json_decode($rawBody, true)),
$response
);
default:
break;
// Matched! Parse and pass on to the next
return $next(
$strategy->parse($request),
$response
);
}

// No match; continue
return $next($request, $response);
}
}
29 changes: 29 additions & 0 deletions src/BodyParams/FormUrlEncodedStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* @see http://github.com/zendframework/zend-expressive-helpers for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-helpers/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\Helper\BodyParams;

use Psr\Http\Message\ServerRequestInterface;

class FormUrlEncodedStrategy implements StrategyInterface
{
/**
* {@inheritDoc}
*/
public function match($contentType)
{
return (bool) preg_match('#^application/x-www-form-urlencoded($|[ ;])#', $contentType);
}

/**
* {@inheritDoc}
*/
public function parse(ServerRequestInterface $request)
{
return $request;
}
}
34 changes: 34 additions & 0 deletions src/BodyParams/JsonStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* @see http://github.com/zendframework/zend-expressive-helpers for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-helpers/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\Helper\BodyParams;

use Psr\Http\Message\ServerRequestInterface;

class JsonStrategy implements StrategyInterface
{
/**
* {@inheritDoc}
*/
public function match($contentType)
{
$parts = explode(';', $contentType);
$mime = array_shift($parts);
return (bool) preg_match('#[/+]json$#', trim($mime));
}

/**
* {@inheritDoc}
*/
public function parse(ServerRequestInterface $request)
{
$rawBody = $request->getBody()->getContents();
return $request
->withAttribute('rawBody', $rawBody)
->withParsedBody(json_decode($rawBody, true));
}
}
32 changes: 32 additions & 0 deletions src/BodyParams/StrategyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* @see http://github.com/zendframework/zend-expressive-helpers for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-helpers/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Expressive\Helper\BodyParams;

use Psr\Http\Message\ServerRequestInterface;

/**
* Interface defining a body parameter strategy.
*/
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);
}
Loading

0 comments on commit 368e8ed

Please sign in to comment.