The request object based on PSR-7: Server Request. Full documentation: https://quillstack.org/server-request
A request built from what PHP hands the process — $_SERVER, $_GET, $_COOKIE, $_FILES
and the body — and answered for the way PSR-7 says it should be. Every part of it is a
parameter bag, so reading something which was
not sent is an answer rather than a notice.
A server request has two entirely different origins, and most implementations only take one
seriously. There is the one a web server hands over — $_SERVER, $_GET, $_FILES, a body on
php://input — and the one a test or a client makes up from a verb and an address. Building the
first from globals is fiddly and easy to get subtly wrong; building the second should be one
line.
This does both: ServerRequestFromGlobalsFactory for what arrived, and a PSR-17
ServerRequestFactory for everything else. A controller can also declare its own request
class — LoginRequest, CreateUserRequest — and be handed that instead, which is where
validation belongs and where a framework usually makes you go looking.
- PHP 8.1 or newer
composer require quillstack/server-requestuse Quillstack\ServerRequest\Factory\ServerRequestFactory;
$request = (new ServerRequestFactory())->createServerRequest('GET', 'https://api.example.org/users/42?page=2');$request->getMethod(); // 'GET'
$request->getRequestTarget(); // '/users/42?page=2'
$request->getHeaderLine('Host'); // 'api.example.org'
$request->getQueryParams(); // ['page' => '2']It is PSR-17, so anything expecting ServerRequestFactoryInterface can be handed one, and it
needs nothing to be built itself. The Host header is set from the address, with the port where
that port is not the usual one for the scheme.
$request = $factory->createServerRequest();
$request->getMethod(); // 'GET'
$request->getUri()->getPath(); // '/users/42'
$request->getHeaderLine('accept'); // 'application/json'
$request->getRequestTarget(); // '/users/42?page=2'
$request->getQueryParams(); // ['page' => '2']
$request->getCookieParams();
$request->getParsedBody();The query parameters come from $_GET rather than from the URI, which is what PSR-7 allows
and what everything else in PHP does — so a rewritten URL and the parameters PHP parsed cannot
disagree.
What the router matched arrives here, and anything else a middleware wants to hand along:
$request = $request->withAttribute('id', '42');
$request->getAttribute('id'); // '42'
$request->getAttribute('page', '1'); // '1' — nothing under that nameEvery change hands back a copy, so a middleware adding one does not change the request anybody else is holding.
A controller can be handed a request of its own type, so what an endpoint expects is a class rather than a convention:
$factory->setRequestClass(UserRequest::class);The class has to exist; UnknownServerRequestClassException says so when it does not.
foreach ($request->getUploadedFiles() as $name => $file) {
$file->getClientFilename(); // 'photo.jpg'
$file->getClientMediaType(); // 'image/jpeg'
$file->getSize(); // 20481
$file->getError(); // UPLOAD_ERR_OK
$file->moveTo('/var/www/uploads/photo.jpg');
}A file can only be moved once — moving it again throws UploadedFileAlreadyMovedException,
because the second call would otherwise fail in a way nobody expects. getStream() after a
move throws for the same reason.
| Class | What it is |
|---|---|
ServerRequest |
the request, implementing Psr\Http\Message\ServerRequestInterface |
Factory\ServerRequest\ServerRequestFactory |
builds one from given parameters |
Factory\ServerRequest\ServerRequestFromGlobalsFactory |
builds one from what PHP was given |
Factory\ServerRequest\GivenServerRequestFromGlobalsFactory |
the same, as a named class |
UploadedFiles\UploadedFile |
one uploaded file, implementing UploadedFileInterface |
UploadedFiles\UploadedFileFactory |
turns $_FILES into those |
| Exception | Thrown when |
|---|---|
RequiredParamFromGlobalsNotFoundException |
$_SERVER is missing something a request needs |
ServerRequestMethodNotKnownException |
the method is not one HTTP has |
UnknownServerRequestClassException |
the request class named does not exist |
ServerParamNotSetException |
a parameter the factory was told to use is not there |
UploadedFileNotUploadedException |
the file did not arrive as an upload |
UploadedFileAlreadyMovedException |
it has been moved once already |
UploadedFileNotMovedException |
moving it failed |
getRequestTarget() is the path and the query string, taken from the URI — the path is
already an absolute one, so nothing is added to the front of it.
Measured with quillstack/benchmark on one request built from a method and an address with a query string, a thousand times. All four produce the same method, target and Host header. Runs are interleaved, each figure is the median of five, and PHP is 8.5.7.
| Version | |
|---|---|
| quillstack/server-request | v0.8.0 |
| nyholm/psr7 | 1.8.2 |
| laminas/laminas-diactoros | 3.8.0 |
| guzzlehttp/psr7 | 2.13.0 |
| Per request | Relative | |
|---|---|---|
| nyholm/psr7 | 6.2 µs | 0.46× |
| quillstack/server-request | 13.4 µs | — |
| laminas/laminas-diactoros | 14.4 µs | 1.07× |
| guzzlehttp/psr7 | 15.0 µs | 1.12× |
nyholm/psr7 builds one in less than half the time, and it is worth saying plainly rather
than burying: it is the leanest PSR-7 there is, and if constructing requests in a loop is what
your application does, that is the one to use.
Where this one spends the difference is on the parts that make it a server request rather than a message: server parameters, query parameters, cookies and uploaded files each arrive in a parameter bag rather than a bare array, and the URI goes through a conformance-tested parser. At thirteen microseconds, a request that builds one of these spends thirteen microseconds on it.
composer test
composer test:coverage
composer stanThis is one component of Quillstack, a PHP framework which is as simple to use as it is strict about what it does.
- quillstack/router — what matches one to a controller
- quillstack/uri — what takes the address apart
- quillstack/parameter-bag — what holds the parameters
- quillstack/response — what goes back
MIT. See LICENSE.