Handlers and Middleware¶
WellRESTed allows you to define and use your handlers and middleware in a number of ways.
Defining Handlers and Middleware¶
PSR-15 Interfaces¶
The preferred method is to use the interfaces standardized by PSR-15. This standard includes two interfaces, Psr\Http\Server\RequestHandlerInterface
and Psr\Http\Server\MiddlewareInterface
.
Use RequestHandlerInterface
for individual components that generate and return responses.
class HelloHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// Check for a "name" attribute which may have been provided as a
// path variable. Use "world" as a default.
$name = $request->getAttribute("name", "world");
// Set the response body to the greeting and the status code to 200 OK.
$response = (new Response(200))
->withHeader("Content-type", "text/plain")
->withBody(new Stream("Hello, $name!"));
// Return the response.
return $response;
}
}
Use MiddlewareInterface
for classes that interact with other middleware and handlers. For example, you may have middleware that attempts to retrieve a cached response and delegates to other handlers on a cache miss.
class CacheMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// Inspect the request to see if there is a representation on hand.
$representation = $this->getCachedRepresentation($request);
if ($representation !== null) {
// There is already a cached representation.
// Return it without delegating to the next handler.
return (new Response())
->withStatus(200)
->withBody($representation);
}
// No representation exists. Delegate to the next handler.
$response = $handler->handle($request);
// Attempt to store the response to the cache.
$this->storeRepresentationToCache($response);
return $response
}
private function getCachedRepresentation(ServerRequestInterface $request)
{
// Look for a cached representation. Return null if not found.
// ...
}
private function storeRepresentationToCache(ResponseInterface $response)
{
// Ensure the response contains a success code, a valid body,
// headers that allow caching, etc. and store the representation.
// ...
}
}
Legacy Middleware Interface¶
Prior to PSR-15, WellRESTed’s recommended handler interface was WellRESTed\MiddlewareInterface
. This interface is still supported for backwards compatibility.
This interface serves for both handlers and middleware. It differs from the Psr\Http\Server\MiddlewareInterface
in that is expects an incoming $response
parameter which you may use to generate the returned response. It also expected a $next
parameter which is a callable
with this signature:
function next($request, $response): ResponseInterface
Call $next
and pass $request
and $response
to forward the request to the next handler. $next
will return the response from the handler. Here’s the cache example above as a WellRESTed\MiddlewareInterface
.
class CacheMiddleware implements WellRESTed\MiddlewareInterface
{
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
$next
) {
// Inspect the request to see if there is a representation on hand.
$representation = $this->getCachedRepresentation($request);
if ($representation !== null) {
// There is already a cached representation.
// Return it without delegating to the next handler.
return $response
->withStatus(200)
->withBody($representation);
}
// No representation exists. Delegate to the next handler.
$response = $next($request, $response);
// Attempt to store the response to the cache.
$this->storeRepresentationToCache($response);
return $response
}
private function getCachedRepresentation(ServerRequestInterface $request)
{
// Look for a cached representation. Return null if not found.
// ...
}
private function storeRepresentationToCache(ResponseInterface $response)
{
// Ensure the response contains a success code, a valid body,
// headers that allow caching, etc. and store the representation.
// ...
}
}
Callables¶
You may also use a callable
similar to the legacy WellRESTed\MiddlewareInterface
. The signature of the callable matches the signature of WellRESTed\MiddlewareInterface::__invoke
.
$handler = function ($request, $response, $next) {
// Delegate to the next handler.
$response = $next($request, $response);
return $response
->withHeader("Content-type", "text/plain")
->withBody(new Stream("Hello, $name!"));
}
Using Handlers and Middleware¶
Methods that accept handlers and middleware (e.g., Server::add
, Router::register
) allow you to provide them in a number of ways. For example, you can provide an instance, a callable
that returns an instance, or an array
of middleware to use in sequence. The following examples will demonstrate all of the ways you can register handlers and middleware.
Factory Functions¶
The best method is to use a function that returns an instance of your handler. The main benefit of this approach is that no handlers are instantiated until they are needed.
$router->register("GET,PUT,DELETE", "/widgets/{id}",
function () { return new App\WidgetHandler() }
);
If you’re using Pimple
, a popular dependency injection container for PHP, you may have code that looks like this:
// Create a DI container.
$c = new Container();
// Store a function to the container that will create and return the handler.
$c['widgetHandler'] = $c->protect(function () use ($c) {
return new App\WidgetHandler();
});
$router->register("GET,PUT,DELETE", "/widgets/{id}", $c['widgetHandler']);
Instance¶
WellRESTed also allows you to pass an instance of a handler directly. This may be useful for smaller handlers that don’t require many dependencies, although the factory function approach is better in most cases.
$widgetHandler = new App\WidgetHandler();
$router->register("GET,PUT,DELETE", "/widgets/{id}", $widgetHandler);
Warning
This is simple, but has a significant disadvantage over the other options because each middleware used this way will be loaded and instantiated, even if it’s not needed for a given request-response cycle. You may find this approach useful for testing, but avoid if for production code.
Fully Qualified Class Name (FQCN)¶
For handlers that do not require any arguments passed to the constructor, you may pass the fully qualified class name of your handler as a string
. You can do that like this:
$router->register('GET,PUT,DELETE', '/widgets/{id}', App\WidgetHandler::class);
// ... or ...
$router->register('GET,PUT,DELETE', '/widgets/{id}', 'App\\WidgetHandler');
The class is not loaded, and no instances are created, until the route is matched and dispatched. However, the drawback to this approach is the there is no way to pass any arguments to the constructor.
Array¶
The final approach is to provide a sequence of middleware and a handler as an array
.
For example, imagine if we had a Pimple container with these services:
$c['authMiddleware'] // Ensures the user is logged in
$c['cacheMiddleware'] // Provides a cached response if able
$c['widgetHandler'] // Provides a widget representation
We could provide these as a sequence by using an array
.
$router->register('GET', '/widgets/{id}', [
$c['authMiddleware'],
$c['cacheMiddleware'],
$c['widgetHandler']
]);