Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Deprecate FinalHandler and ErrorMiddlewareInterface #66

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cache:
directories:
- $HOME/.composer/cache
- $HOME/.local
- vendor
- zf-mkdoc-theme

env:
Expand All @@ -18,8 +19,6 @@ env:

matrix:
include:
- php: 5.4
- php: 5.5
- php: 5.6
env:
- EXECUTE_CS_CHECK=true
Expand All @@ -30,9 +29,12 @@ matrix:
allow_failures:
- php: hhvm

before_script:
- composer self-update
- composer install --prefer-source
before_install:
- travis_retry composer self-update

install:
- travis_retry composer install
- composer show --installed

script:
- composer test
Expand Down
10 changes: 5 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
}
},
"require": {
"php": "^5.4.8 || ^7.0",
"psr/http-message": "~1.0.0",
"zendframework/zend-escaper": "~2.3"
"php": "^5.6 || ^7.0",
"psr/http-message": "^1.0",
"zendframework/zend-escaper": "^2.3"
},
"require-dev": {
"zendframework/zend-diactoros": "~1.0",
"zendframework/zend-diactoros": "^1.0",
"phpunit/phpunit": "^4.7 || ^5.5",
"squizlabs/php_codesniffer": "^2.3.1"
"squizlabs/php_codesniffer": "^2.6.2"
},
"suggest": {
"psr/http-message-implementation": "Please install a psr/http-message-implementation to consume Stratigility; e.g., zendframework/zend-diactoros"
Expand Down
38 changes: 27 additions & 11 deletions doc/book/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MiddlewarePipe implements MiddlewareInterface
public function __invoke(
Psr\Http\Message\ServerRequestInterface $request = null,
Psr\Http\Message\ResponseInterface $response = null,
callable $out = null
callable $out
) : Psr\Http\Message\ResponseInterface;
}
```
Expand All @@ -31,18 +31,25 @@ Middleware is executed in the order in which it is piped to the `MiddlewarePipe`
exhausted (`MiddlewarePipe` passes the `$response` instance it receives to `FinalHandler` as well,
so that the latter can determine if the response it receives is new).

> ### $out is no longer optional
>
> Starting in version 1.3.0, we now raise a deprecation notice if no argument is
> passed for `$out`; starting in version 2.0.0, the argument will be required.
> Always pass a `Next` instance, a `Zend\Stratigility\NoopFinalHandler`
> instance, or a custom callback; we no longer recommend the `FinalHandler`
> implementation.
The callable should use the same signature as `Next()`:

```php
function (
Psr\Http\Message\ServerRequestInterface $request,
Psr\Http\Message\ResponseInterface $response,
$err = null
Psr\Http\Message\ResponseInterface $response
) : Psr\Http\Message\ResponseInterface
```

Internally, `MiddlewarePipe` creates an instance of `Zend\Stratigility\Next`, feeding it its queue,
executes it, and returns a response.
Internally, `MiddlewarePipe` creates an instance of `Zend\Stratigility\Next`,
feeding it its queue, executes it, and returns a response.

## Next

Expand All @@ -60,8 +67,7 @@ class Next
{
public function __invoke(
Psr\Http\Message\ServerRequestInterface $request,
Psr\Http\Message\ResponseInterface $response,
$err = null
Psr\Http\Message\ResponseInterface $response
) : Psr\Http\Message\ResponseInterface;
}
```
Expand All @@ -70,6 +76,13 @@ You should **always** either capture or return the return value of `$next()` whe
application. The expected return value is a response instance, but if it is not, you may want to
return the response provided to you.

> ### $err argument
>
> Technically, `Next::__invoke()` accepts a third, optional argument, `$err`.
> However, as of version 1.3.0, this argument is deprecated, and usage will
> raise a deprecation notice during runtime. We will be removing the argument
> entirely starting with version 2.0.0.
As examples:

### Providing an altered request:
Expand Down Expand Up @@ -150,12 +163,12 @@ And, if not calling `$next()`, returning the response instance:
return $response;
```

The `FinalHandler` implementation will check the `$response` instance passed when invoking it
against the instance passed during instantiation, and, if different, return it. As such, `return
$next(/* ... */)` is the recommended workflow.

### Raising an error condition

- Deprecated as of 1.3.0; please use exceptions and a error handling middleware
such as the [ErrorHandler](error-handlers.md#handling-php-errors-and-exceptions)
to handle error conditions in your application instead.

To raise an error condition, pass a non-null value as the third argument to `$next()`:

```php
Expand All @@ -171,6 +184,9 @@ function ($request, $response, $next)

## FinalHandler

- Deprecated starting with 1.3.0. Use `Zend\Stratigility\NoopFinalHandler` or a
custom handler guaranteed to return a response instead.

`Zend\Stratigility\FinalHandler` is a default implementation of middleware to execute when the stack
exhausts itself. It expects three arguments when invoked: a request instance, a response instance,
and an error condition (or `null` for no error). It returns a response.
Expand Down
34 changes: 21 additions & 13 deletions doc/book/creating-middleware.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Creating Middleware

To create middleware, write a callable capable of receiving minimally PSR-7 ServerRequest and Response
objects, and optionally a callback to call the next in the chain. In your middleware, you can handle
as much or as little of the request as you want, including delegating to other middleware. If your
middleware accepts a third argument, `$next`, it can allow further processing or return handling to
the parent middleware by calling it.
To create middleware, write a callable capable of receiving minimally PSR-7
ServerRequest and Response objects, and a callback to call the next middleware
in the chain. In your middleware, you can handle as much or as little of the
request as you want, including delegating to other middleware. By accepting the
third argument, `$next`, it can allow further processing via invoking that
argument, or return handling to the parent middleware by returning a response.

As an example, consider the following middleware which will use an external router to map the
incoming request path to a handler; if unable to map the request, it returns processing to the next
middleware.
As an example, consider the following middleware which will use an external
router to map the incoming request path to a handler; if unable to map the
request, it returns processing to the next middleware.

```php
function ($req, $res, $next) use ($router) {
Expand Down Expand Up @@ -41,14 +42,21 @@ In all cases, if you wish to implement typehinting, the signature is:
function (
Psr\Http\Message\ServerRequestInterface $request,
Psr\Http\Message\ResponseInterface $response,
callable $next = null
callable $next
) : Psr\Http\Message\ResponseInterface
```

The implementation Stratigility offers also allows you to write specialized error handler
middleware. The signature is the same as for normal middleware, except that it expects an additional
argument prepended to the signature, `$error`. (Alternately, you can implement
`Zend\Stratigility\ErrorMiddlewareInterface`.) The signature is:
### Legacy error middleware

- Deprecated since 1.3.0; to be removed in version 2.0.0. Please use the the
`NotFoundHandler` and `ErrorHandler` detailed in the
[error handling chapter](error-handlers.md), or equivalents.

The implementation Stratigility offers also allows you to write specialized
error handler middleware. The signature is the same as for normal middleware,
except that it expects an additional argument prepended to the signature,
`$error`. (Alternately, you can implement `Zend\Stratigility\ErrorMiddlewareInterface`.)
The signature is:

```php
function (
Expand Down
178 changes: 178 additions & 0 deletions doc/book/error-handlers.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,183 @@
# Error Handlers

In your application, you may need to handle error conditions:

- Errors raised by PHP itself (e.g., inability to open a file or database
connection).
- Exceptions/throwables raised by PHP and/or code you write or consume.
- Inability of any middleware to handle a request.

You can typically handle these conditions via middleware itself.

## Handling 404 conditions

If no middleware is able to handle the incoming request, this is typically
representative of an HTTP 404 status. Stratigility provides a barebones
middleware that you may register in an innermost layer that will return a 404
condition, `Zend\Stratigility\Middleware\NotFoundHandler`. The class requires a
response prototype instance that it will use to provide the 404 status and a
message indicating the request method and URI used:

```php
// setup layers
$app->pipe(/* ... */);
$app->pipe(/* ... */);
$app->pipe(new NotFoundHandler(new Response());

// execute application
```

Note that it is the last middleware piped into the application! Since it returns
a response, no deeper neseted layers will execute once it has been invoked.

If you would like a templated response, you will need to write your own
middleware; such middleware might look like the following:

```php
class NotFoundMiddleware
{
private $renderer;

public function __construct(
TemplateRendererInterface $renderer,
ResponseInterface $response
) {
$this->renderer = $renderer;
$this->response = $response;
}

public function __invoke($request)
{
$response = $this->response->withStatus(404);
$response->getBody()->write(
$this->renderer->render('error::404')
);
return $response;
}
}
```

## Handling PHP errors and exceptions

`Zend\Stratigility\Middleware\ErrorHandler` is a middleware implementation to
register as the *outermost layer* of your application (or one of the outermost
layers). It does the following:

- Creates a PHP error handler that catches any errors in the `error_handling()`
mask and throws them as `ErrorException` instances.
- Wraps the call to `$next()` in a try/catch block:
- if no exception is caught, and the result is a response, it returns it.
- if no exception is caught, it raises an exception, which will be caught.
- any caught exception is transformed into an error response.

The error response will have a 5XX series status code, and the message will be
derived from the reason phrase, if any is present. You may pass a boolean flag
to the constructor indicating the application is in development mode; if so, the
response will have the stack trace included in the body.

In order to work, it needs a prototype response instance, and, optionally, a
flag indicating the status of development mode (default is production mode):

```php
// setup error handling
$app->pipe(new ErrorHandler(new Response(), $isDevelopmentMode);

// setup layers
$app->pipe(/* ... */);
$app->pipe(/* ... */);
```

As a full example, you can combine the two middleware into the same application
as separate layers:

```php
// setup error handling
$app->pipe(new ErrorHandler(new Response(), $isDevelopmentMode);

// setup layers
$app->pipe(/* ... */);
$app->pipe(/* ... */);

// setup 404 handling
$app->pipe(new NotFoundHandler(new Response());

// execute application
```

The `ErrorHandler` provides no templating facilities, and only responds as text
and/or HTML. If you want to provide a templated response, or a different
serialization and/or markup format, you will need to write your own
implementation, or extend the `ErrorHandler`.

The `ErrorHandler` provides one extension point useful to this:
`createErrorResponse()`; this method is passed the exception representing the
error, the request that resulted in the error, and the response prototype; you
may then use these to generate a response to return.

As an example:

```php
use ErrorException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use Zend\Stratigility\Exception\MissingResponseException;
use Zend\Stratigility\Middleware\ErrorHandler;

class TemplatedErrorHandler extends ErrorHandler
{
private $renderer;

public function __construct(
TemplateRendererInterface $renderer,
ResponseInterface $responsePrototype,
$isDevelopmentMode = false
) {
parent::__construct($responsePrototype, $isDevelopmentMode);
$this->renderer = $renderer;
}

protected function createErrorResponse($e, ServerRequestInterface $request, ResponseInterface $response)
{
$response = $response->withStatus(500);
$response->write($this->renderer->render('error::error', [
'exception' => $e,
'development_mode' => $this->isDevelopmentMode,
]);
return $response;
}
}
```

### ErrorHandler Listeners

`Zend\Stratigility\ErrorHandler` provides the ability to attach *listeners*;
these are triggered when an error or exception is caught, and provided with the
exception/throwable raised, the original request, and the final response. These
instances are considered immutable, so listeners are for purposes of
logging/monitoring only.

Attach listeners using `ErrorHandler::attachListener()`:

```php
$errorHandler->attachListener(function ($throwable, $request, $response) use ($logger) {
$message = sprintf(
'[%s] %s %s: %s',
date('Y-m-d H:i:s'),
$request->getMethod(),
(string) $request->getUri(),
$throwable->getMessage()
);
$logger->error($message);
});
```

## Legacy error middleware

- Deprecated starting in 1.3.0, to be removed in 2.0.0. Please see the
[migration guide](migration/to-v2.md#error-handling) for more details, as well
as the preceding section.

To handle errors, you can write middleware that accepts **exactly** four arguments:

```php
Expand Down
4 changes: 2 additions & 2 deletions doc/book/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Stratigility has the following dependencies (which are managed by Composer):
implementation of PSR-7; one such package is
[Diactoros](https://zendframework.github.io/zend-diactoros/).

- `zendframework/zend-escaper`, used by the `FinalHandler` for escaping error messages prior to
passing them to the response.
- `zendframework/zend-escaper`, used by the `ErrorHandler` middleware and the
for escaping error messages prior to passing them to the response.

You can provide your own request and response implementations if desired as long as they implement
the PSR-7 HTTP message interfaces.
Loading