diff --git a/src/Middleware/ErrorHandler.php b/src/Middleware/ErrorHandler.php index 7588733..853bf57 100644 --- a/src/Middleware/ErrorHandler.php +++ b/src/Middleware/ErrorHandler.php @@ -23,178 +23,8 @@ use function set_error_handler; /** - * Error handler middleware. - * - * Use this middleware as the outermost (or close to outermost) middleware - * layer, and use it to intercept PHP errors and exceptions. - * - * The class offers two extension points: - * - * - Error response generators. - * - Listeners. - * - * Error response generators are callables with the following signature: - * - * - * function ( - * Throwable $e, - * ServerRequestInterface $request, - * ResponseInterface $response - * ) : ResponseInterface - * - * - * These are provided the error, and the request responsible; the response - * provided is the response prototype provided to the ErrorHandler instance - * itself, and can be used as the basis for returning an error response. - * - * An error response generator must be provided as a constructor argument; - * if not provided, an instance of Zend\Stratigility\Middleware\ErrorResponseGenerator - * will be used. - * - * Listeners use the following signature: - * - * - * function ( - * Throwable $e, - * ServerRequestInterface $request, - * ResponseInterface $response - * ) : void - * - * - * Listeners are given the error, the request responsible, and the generated - * error response, and can then react to them. They are best suited for - * logging and monitoring purposes. - * - * Listeners are attached using the attachListener() method, and triggered - * in the order attached. + * @deprecated This class is being dropped in v4.0 in favor of the ErrorMiddleware. */ -class ErrorHandler implements MiddlewareInterface +class ErrorHandler extends ErrorHandlerMiddleware { - /** - * @var callable[] - */ - private $listeners = []; - - /** - * @var callable Routine that will generate the error response. - */ - private $responseGenerator; - - /** - * @var callable - */ - private $responseFactory; - - /** - * @param callable $responseFactory A factory capable of returning an - * empty ResponseInterface instance to update and return when returning - * an error response. - * @param null|callable $responseGenerator Callback that will generate the final - * error response; if none is provided, ErrorResponseGenerator is used. - */ - public function __construct(callable $responseFactory, callable $responseGenerator = null) - { - $this->responseFactory = function () use ($responseFactory) : ResponseInterface { - return $responseFactory(); - }; - $this->responseGenerator = $responseGenerator ?: new ErrorResponseGenerator(); - } - - /** - * Attach an error listener. - * - * Each listener receives the following three arguments: - * - * - Throwable $error - * - ServerRequestInterface $request - * - ResponseInterface $response - * - * These instances are all immutable, and the return values of - * listeners are ignored; use listeners for reporting purposes - * only. - */ - public function attachListener(callable $listener) : void - { - if (in_array($listener, $this->listeners, true)) { - return; - } - - $this->listeners[] = $listener; - } - - /** - * Middleware to handle errors and exceptions in layers it wraps. - * - * Adds an error handler that will convert PHP errors to ErrorException - * instances. - * - * Internally, wraps the call to $next() in a try/catch block, catching - * all PHP Throwables. - * - * When an exception is caught, an appropriate error response is created - * and returned instead; otherwise, the response returned by $next is - * used. - */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface - { - set_error_handler($this->createErrorHandler()); - - try { - $response = $handler->handle($request); - } catch (Throwable $e) { - $response = $this->handleThrowable($e, $request); - } - - restore_error_handler(); - - return $response; - } - - /** - * Handles all throwables, generating and returning a response. - * - * Passes the error, request, and response prototype to createErrorResponse(), - * triggers all listeners with the same arguments (but using the response - * returned from createErrorResponse()), and then returns the response. - */ - private function handleThrowable(Throwable $e, ServerRequestInterface $request) : ResponseInterface - { - $generator = $this->responseGenerator; - $response = $generator($e, $request, ($this->responseFactory)()); - $this->triggerListeners($e, $request, $response); - return $response; - } - - /** - * Creates and returns a callable error handler that raises exceptions. - * - * Only raises exceptions for errors that are within the error_reporting mask. - */ - private function createErrorHandler() : callable - { - /** - * @throws ErrorException if error is not within the error_reporting mask. - */ - return function (int $errno, string $errstr, string $errfile, int $errline) : void { - if (! (error_reporting() & $errno)) { - // error_reporting does not include this error - return; - } - - throw new ErrorException($errstr, 0, $errno, $errfile, $errline); - }; - } - - /** - * Trigger all error listeners. - */ - private function triggerListeners( - Throwable $error, - ServerRequestInterface $request, - ResponseInterface $response - ) : void { - foreach ($this->listeners as $listener) { - $listener($error, $request, $response); - } - } } diff --git a/src/Middleware/ErrorHandlerMiddleware.php b/src/Middleware/ErrorHandlerMiddleware.php new file mode 100644 index 0000000..0eb0f9b --- /dev/null +++ b/src/Middleware/ErrorHandlerMiddleware.php @@ -0,0 +1,202 @@ + + * function ( + * Throwable $e, + * ServerRequestInterface $request, + * ResponseInterface $response + * ) : ResponseInterface + * + * + * These are provided the error, and the request responsible; the response + * provided is the response prototype provided to the ErrorHandler instance + * itself, and can be used as the basis for returning an error response. + * + * An error response generator must be provided as a constructor argument; + * if not provided, an instance of Zend\Stratigility\Middleware\ErrorResponseGenerator + * will be used. + * + * Listeners use the following signature: + * + * + * function ( + * Throwable $e, + * ServerRequestInterface $request, + * ResponseInterface $response + * ) : void + * + * + * Listeners are given the error, the request responsible, and the generated + * error response, and can then react to them. They are best suited for + * logging and monitoring purposes. + * + * Listeners are attached using the attachListener() method, and triggered + * in the order attached. + * + * @todo Mark this error middleware as final in v4 + */ +/* final */ class ErrorHandlerMiddleware implements MiddlewareInterface +{ + /** + * @var callable[] + */ + private $listeners = []; + + /** + * @var callable Routine that will generate the error response. + */ + private $responseGenerator; + + /** + * @var callable + */ + private $responseFactory; + + /** + * @param callable $responseFactory A factory capable of returning an + * empty ResponseInterface instance to update and return when returning + * an error response. + * @param null|callable $responseGenerator Callback that will generate the final + * error response; if none is provided, ErrorResponseGenerator is used. + */ + public function __construct(callable $responseFactory, callable $responseGenerator = null) + { + $this->responseFactory = function () use ($responseFactory) : ResponseInterface { + return $responseFactory(); + }; + $this->responseGenerator = $responseGenerator ?: new ErrorResponseGenerator(); + } + + /** + * Attach an error listener. + * + * Each listener receives the following three arguments: + * + * - Throwable $error + * - ServerRequestInterface $request + * - ResponseInterface $response + * + * These instances are all immutable, and the return values of + * listeners are ignored; use listeners for reporting purposes + * only. + */ + public function attachListener(callable $listener) : void + { + if (in_array($listener, $this->listeners, true)) { + return; + } + + $this->listeners[] = $listener; + } + + /** + * Middleware to handle errors and exceptions in layers it wraps. + * + * Adds an error handler that will convert PHP errors to ErrorException + * instances. + * + * Internally, wraps the call to $next() in a try/catch block, catching + * all PHP Throwables. + * + * When an exception is caught, an appropriate error response is created + * and returned instead; otherwise, the response returned by $next is + * used. + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + set_error_handler($this->createErrorHandler()); + + try { + $response = $handler->handle($request); + } catch (Throwable $e) { + $response = $this->handleThrowable($e, $request); + } + + restore_error_handler(); + + return $response; + } + + /** + * Handles all throwables, generating and returning a response. + * + * Passes the error, request, and response prototype to createErrorResponse(), + * triggers all listeners with the same arguments (but using the response + * returned from createErrorResponse()), and then returns the response. + */ + private function handleThrowable(Throwable $e, ServerRequestInterface $request) : ResponseInterface + { + $generator = $this->responseGenerator; + $response = $generator($e, $request, ($this->responseFactory)()); + $this->triggerListeners($e, $request, $response); + return $response; + } + + /** + * Creates and returns a callable error handler that raises exceptions. + * + * Only raises exceptions for errors that are within the error_reporting mask. + */ + private function createErrorHandler() : callable + { + /** + * @throws ErrorException if error is not within the error_reporting mask. + */ + return function (int $errno, string $errstr, string $errfile, int $errline) : void { + if (! (error_reporting() & $errno)) { + // error_reporting does not include this error + return; + } + + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + }; + } + + /** + * Trigger all error listeners. + */ + private function triggerListeners( + Throwable $error, + ServerRequestInterface $request, + ResponseInterface $response + ) : void { + foreach ($this->listeners as $listener) { + $listener($error, $request, $response); + } + } +} diff --git a/test/Middleware/ErrorHandlerTest.php b/test/Middleware/ErrorHandlerMiddlewareTest.php similarity index 93% rename from test/Middleware/ErrorHandlerTest.php rename to test/Middleware/ErrorHandlerMiddlewareTest.php index 8900584..c689694 100644 --- a/test/Middleware/ErrorHandlerTest.php +++ b/test/Middleware/ErrorHandlerMiddlewareTest.php @@ -18,7 +18,7 @@ use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; use Zend\Escaper\Escaper; -use Zend\Stratigility\Middleware\ErrorHandler; +use Zend\Stratigility\Middleware\ErrorHandlerMiddleware; use Zend\Stratigility\Middleware\ErrorResponseGenerator; use function error_reporting; @@ -26,7 +26,7 @@ use const E_USER_DEPRECATED; -class ErrorHandlerTest extends TestCase +class ErrorHandlerMiddlewareTest extends TestCase { /** @var ResponseInterface|ObjectProphecy */ private $response; @@ -34,6 +34,26 @@ class ErrorHandlerTest extends TestCase /** @var callable */ private $responseFactory; + /** + * @var ObjectProphecy|ServerRequestInterface + */ + private $request; + + /** + * @var ObjectProphecy|StreamInterface + */ + private $body; + + /** + * @var ObjectProphecy|RequestHandlerInterface + */ + private $handler; + + /** + * @var int + */ + private $errorReporting; + public function setUp() { $this->response = $this->prophesize(ResponseInterface::class); @@ -54,7 +74,7 @@ public function tearDown() public function createMiddleware($isDevelopmentMode = false) { $generator = new ErrorResponseGenerator($isDevelopmentMode); - return new ErrorHandler($this->responseFactory, $generator); + return new ErrorHandlerMiddleware($this->responseFactory, $generator); } public function testReturnsResponseFromHandlerWhenNoProblemsOccur() @@ -219,7 +239,7 @@ public function testCanProvideAlternateErrorResponseGenerator() $this->response->getBody()->will([$this->body, 'reveal']); $this->body->write('The client messed up')->shouldBeCalled(); - $middleware = new ErrorHandler($this->responseFactory, $generator); + $middleware = new ErrorHandlerMiddleware($this->responseFactory, $generator); $result = $middleware->process($this->request->reveal(), $this->handler->reveal()); $this->assertSame($this->response->reveal(), $result);