-
Notifications
You must be signed in to change notification settings - Fork 57
Deprecate FinalHandler and ErrorMiddlewareInterface #66
Deprecate FinalHandler and ErrorMiddlewareInterface #66
Conversation
In doing so, discovered that if we make `$out` required in v2, there's no need to introduce the `setFinalHandler()` argument, as it can be passed directly, or handled via `Zend\Diactoros\Server::listen()`.
First step in the journey to remove the FinalHandler. - Bump minimum supported PHP version to 5.6 (allows calling callables without `call_user_func()`, and using `::class` notation). - `NotFoundHandler` returns a basic 404 response, always. - `ErrorHandler`: - creates a PHP error handler that will throw any errors not in the current error_handler mask as ErrorExceptions. - Does a try/catch around `$next()` - Raises a new exception, `MissingResponseException`, if `$next` did not return a response. - Casts all Throwable/Exception items to error responses: - If a boolean true flag is passed to the constructor, it will use the stack trace in the response. - Triggers listeners on the instance with the exception/throwable, request, and response, for reporting purposes. In order to return a response, each middleware type accepts a prototype `ResponseInterface` instance to the constructor; this is then used to generate the returned response in 404/error conditions.
We need to notify users that `$out` cannot be omitted starting with version 2.0.0.
f653477
to
2aa080d
Compare
- Space around operators - Space after !
Marked as a BC break, as it introduces deprecation notices. These are, however, documented, and the messages themselves point to online (not-yet-published, but included in this PR) documentation to assist users in making their code forwards-compatible. |
Pinging @zendframework/community-review-team / @ezimuel / @michaelmoussa for review! |
I think new ErrorHandler should have possibility to create response depending on content-type. It may be optional, eg. any callable param in constructor. Thoughts? |
@vaclavvanik I like the idea, but I think that belongs more properly in zend-expressive or its skeleton, and not in Stratigility. Stratigility is a foundation, and the expectation is that it provides only the minimum functionality; in fact, the docs I wrote even recommend replacing the ErrorHandler and NotFoundHandler in your own applications in order to provide your own features. |
- Created a protected method, `createErrorResponse()`, for creating and returning the error response to use. - Made `$isDevelopmentMode` protected, to allow extending classes to query its value.
@vaclavvanik I've updated the implementation to provide an extension point in
This will provide a better starting point with flexibility for end-users, while still being minimalist. |
@weierophinney Imo custom error response factory could be injected as optional callable into ErrorHandler constructor. This allow great flexibility when pulling ErrorHandler from container and
Ummm... thinking maybe whole error response factory could be moved to another class. So ErrorHandler constructor could look:
Thanks for your opinions. |
Per @vaclavvanik, this patch: - Moves the error generation aspects to a new class, `ErrorResponseGenerator`. - Updates the `ErrorHandler`: - to allow passing an optional, callable response generator via the constructor, instead of the `$isDevelopmentMode` flag. - to specify `ErrorResponseGenerator` in production mode if no generator is provided during instantiation. - to trigger the response generator from `handleThrowable()` in order to get an error response, passing the exception, request, and response prototype. - Marks the class final. This allows flexibility in generating error responses without requiring extension, by composing the generator in the handler itself.
* update and return when returning an error response. | ||
* @param bool $isDevelopmentMode | ||
*/ | ||
public function __construct(ResponseInterface $responsePrototype, $isDevelopmentMode = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weierophinney what are your thoughts on a more granular configuration $options
parameter with an is_development_mode
setting rather than a simple flag? Or would further configurability be something that should be accomplished by extending the handler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've actually just pushed changes based on the feedback from @vaclavvanik that shoves that stuff into the response generator. As such, the ErrorHandler
now has only the following responsibilities:
- compose a response prototype, a response generator, and listeners
- create and enable a PHP error handler that casts errors to exceptions
- detect when no inner middleware returns a response, and raise an exception
- catch all exceptions, and pass them, the request, and the prototype response to the response generator
- notifiy all listeners of the error, the request, and the final response
As such, no "optional" types of behavior are present, and those would be handled by the response generator and/or listeners, which fall out of scope of the class itself.
*/ | ||
private function handleThrowable($e, ServerRequestInterface $request) | ||
{ | ||
$response = $this->createErrorResponse($e, $request, $this->responsePrototype); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weierophinney I'm not clear on why the responsePrototype
is needed in the ErrorHandler
. Could we not use the $response
that's passed to __invoke
and treat that as the prototype?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using the response prototype in part to prepare for PSR-15, where the response is not passed to middleware.
use Zend\Stratigility\Exception\MissingResponseException; | ||
use Zend\Stratigility\Utils; | ||
|
||
class ErrorHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weierophinney shouldn't this implement MiddlewareInterface
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, but if MiddlewareInterface
is replaced in v2 with the PSR-15 versions, it'd be an extra change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated to make this implement MiddlewareInterface
now. We can change if/when PSR-15 is ratified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll look at the remainder of modifications later this afternoon.
In general, should we tend to approach solutions based on what works best for Expressive in the present? At least until PSR-15 is close to ratification or the decision to not include the Response in the signature is as close to a sure thing as possible?
I figure that no matter what happens, there will need to be changes, so perhaps sticking to the conventions we have now will make it easier for people to continue upgrading until it's time for a major migration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the decision to not include the Response in the signature is as close to a sure thing as possible
This is a sure thing at this point; all those working on http-interop have agreed it should not be present.
In terms of upgrading, in the case of these two middleware, it's an implementation detail unless they choose to write their own implementations. If they do, they can do so using whatever conventions make sense to them. We'd just be providing minimalist barebones implementations (though the ErrorHandler
can likely be used verbatim everywhere, due to the ability to inject a response generator).
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class NotFoundHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weierophinney I'm a little unclear on $responsePrototype
here as well, in addition to the different __invoke
signature. If we add the response param to __invoke
, then that could be used as the prototype, and if we add $next
, then the NoopFinalHandler
will complete the pipe and return the response back, while still giving the user the option to execute some additional middleware after the NotFoundHandler
without having to extend it (perhaps logging or metrics gathering of some sort following a 404). I think in that case the NotFoundHandler
would also be able to implement MiddlewareInterface
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see my response above regarding the ErrorHandler
.
Essentially, you should only manipulate the response when traversing outwards; i.e., the response returned by $next()
.
Since this particular middleware does not call on $next()
, I omitted that argument entirely. However, to be useful in other stacks, it would need to implement the full interface. I'll update both of these middleware to do so shortly.
Incorporated feedback from @vaclavvanik and @michaelmoussa . |
@vaclavvanik I've adopted the idea of injecting a response generator via the |
|
||
```php | ||
// setup error handling | ||
$app->pipe(new ErrorHandler(new Response(), new ErrorHandler($isDevelopmentMode)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should'nt be $app->pipe(new ErrorHandler(new Response(), new ErrorResponseGenerator($isDevelopmentMode));
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! fixed!
Spotted by @vaclavvanik
* Listeners are attached using the attachListener() method, and triggered | ||
* in the order attached. | ||
*/ | ||
final class ErrorHandler implements MiddlewareInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weierophinney should this be final
if we have the extra attachListener
public method w/no interface to cover it? (i'm sure you've seen Marco's blog post on the subject). Perhaps final class ErrorHandler implements ErrorHandlingMiddleware
instead, with this interface?
interface ErrorHandlerMiddleware extends MiddlewareInterface
{
public function attachListener(callable $listener);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH it actually might not matter in this case since nothing typehints specifically on ErrorHandler
, so it shouldn't affect anyone's ability to test or reuse this component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelmoussa Exactly. 😄
Essentially, if they need to extend it, it's small enough they can copy/paste. In terms of testing a system, they can provide a stub implementing MiddlewareInterface
against which to perform assertions.
This patch will provide the forwards compatibility features for the next minor version of Stratigility that implement the RFC on FinalHandler and ErrorMiddlewareInterface removal.
NotFoundHandler
returns a basic 404 response, always.ErrorHandler
:error_handler
mask asErrorExceptions
.$next()
MissingResponseException
, if$next
does not return a response.Throwable
/Exception
items to error responses:MiddlewarePipe
to issue a deprecation notice if$out
is omitted.Dispatch
as deprecated.Next
to raise deprecation notice when receiving a non-null$err
argument.FinalHandler
as deprecated.ErrorMiddlewareInterface
as deprecated.Notes
NotFoundHandler
,ErrorHandler
) accepts a prototypeResponseInterface
instance to the constructor; this is then used to generate the returned response in 404/error conditions. This is done to allow re-use with any PSR-7 implementation.