coroq / request-handler
A base class for PSR-15 request handlers that reduces the boilerplate
Requires
- php: ^8.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0
Requires (Dev)
- nyholm/psr7: ^1.5
- phpunit/phpunit: ^9.5 || ^10.0
README
A base class for PSR-15 request handlers that reduces the boilerplate.
The same handler, first in plain PSR-15, then with this library:
Plain PSR-15
class ContactController implements RequestHandlerInterface { public function __construct( private ResponseFactoryInterface $responseFactory, ) { } public function handle(ServerRequestInterface $request): ResponseInterface { return match ($request->getMethod()) { 'GET' => $this->showForm($request), 'POST' => $this->submit($request), default => $this->responseFactory->createResponse(405), }; } private function showForm(ServerRequestInterface $request): ResponseInterface { $error = $request->getQueryParams()['error'] ?? null; // ... render the form ... } private function submit(ServerRequestInterface $request): ResponseInterface { $body = $request->getParsedBody(); $message = is_array($body) ? ($body['message'] ?? '') : ''; // ... store it, redirect ... } }
With coroq/request-handler
use Coroq\RequestHandler\RequestHandler; class ContactController extends RequestHandler { public function __construct( private ResponseFactoryInterface $responseFactory, ) { } public function handleGet(): ResponseInterface { $error = $this->get['error'] ?? null; // ... render the form ... } public function handlePost(): ResponseInterface { $message = $this->post['message'] ?? ''; // ... store it, redirect ... } }
Installation
composer require coroq/request-handler
Requires PHP ^8.0. Depends only on psr/http-message and
psr/http-server-handler.
How it works
RequestHandler implements PSR-15 RequestHandlerInterface.
handle() does two things:
- loads the request into properties (
$request,$get,$post, etc.), - dispatches by HTTP method to
handleGet(),handlePost(), etc.
Handler methods
| Method | HTTP method |
|---|---|
handleGet() |
GET |
handlePost() |
POST |
handlePut() |
PUT |
handleDelete() |
DELETE |
handlePatch() |
PATCH |
handleQuery() |
QUERY (the draft "safe method with a body") |
handleHead() |
HEAD |
handleOptions() |
OPTIONS |
handleOthers() |
any other method |
Override the ones your handler supports. Every default implementation
throws MethodNotAllowedException — except handleHead(), which falls
back to handleGet() (see below). handle() itself is deliberately
overridable — for method-agnostic handlers, or to wrap the dispatch with
parent::handle().
Request properties
The request and its contents are easily accessible from every handler method:
| Property | Content |
|---|---|
$this->request |
the ServerRequestInterface itself |
$this->get |
query parameters (like $_GET) |
$this->post |
body parameters (like $_POST) |
$this->cookie |
cookies (like $_COOKIE) |
$this->attributes |
request attributes (path parameters etc.) |
Two notes:
$this->postis filled only when the parsed body is an array (HTML forms). For JSON, parse the body into an array in an earlier middleware, or read$this->request->getBody()yourself.- Request handlers hold the request, so use one instance per request. Don't share instances via a caching container.
Handling HEAD requests
handleHead() falls back to handleGet() by default, so a handler that
supports GET answers HEAD automatically. A HEAD response is the GET
response without its body — stripping the body is the job of your emitter
or web server. Override handleHead() when you want to answer HEAD
yourself.
Handling unsupported methods and OPTIONS
The base never builds a response; when a method is not supported it
throws. MethodNotAllowedException carries allowedMethods — the methods
the handler supports, detected from which handleXxx() are overridden.
Catch it once, in a middleware near the front of your queue:
use Coroq\RequestHandler\MethodNotAllowedException; try { return $handler->handle($request); } catch (MethodNotAllowedException $exception) { // include OPTIONS because this middleware answers it $allow = join(', ', array_unique([...$exception->allowedMethods, 'OPTIONS'])); if ($request->getMethod() === 'OPTIONS') { return $this->responseFactory->createResponse(204)->withHeader('Allow', $allow); } return $this->responseFactory->createResponse(405)->withHeader('Allow', $allow); }
This one catch gives every handler an RFC 9110 compliant 405 and a generic OPTIONS answer (CORS preflights included).
RequestLoadingTrait on its own
The request loading is available standalone:
use Coroq\RequestHandler\RequestLoadingTrait; abstract class MyBase implements RequestHandlerInterface { use RequestLoadingTrait; public function handle(ServerRequestInterface $request): ResponseInterface { $this->loadRequest($request); // $this->get, $this->post, ... are now ready } }
License
MIT