The Response class represents outbound information returned to whatever entity made the original request. HTTP headers play an important role in this context as we need to know that format is requested by the client, usually in the incoming Accept header. We then need to set the appropriate Content-Type header in the Response class to match that format. Otherwise, the actual body of the response will be HTML, JSON, or whatever else has been requested (and delivered).
Response class is actually much easier to implement than the Request class as we are only concerned with returning the response from the server to the client. Additionally, it extends our Application\MiddleWare\Message class where most of the work has been done. So, all that remains to be done is to define an Application\MiddleWare\Response class. As you will note, the only unique property is $statusCode:namespace Application\MiddleWare;
use Psr\Http\Message\ { Constants, ResponseInterface, StreamInterface };
class Response extends Message implements ResponseInterface
{
protected $statusCode;Response instance with all parts intact. We use methods from Message and constants from the Constants class to verify the arguments:public function __construct($statusCode = NULL,
StreamInterface $body = NULL,
$headers = NULL,
$version = NULL)
{
$this->body = $body;
$this->status['code'] = $statusCode ?? Constants::DEFAULT_STATUS_CODE;
$this->status['reason'] = Constants::STATUS_CODES[$statusCode] ?? '';
$this->httpHeaders = $headers;
$this->version = $this->onlyVersion($version);
if ($statusCode) $this->setStatusCode();
}http_response_code(), available from PHP 5.4 onwards. As this work is on PHP 7, we are safe in the knowledge that this method exists:public function setStatusCode()
{
http_response_code($this->getStatusCode());
}public function getStatusCode()
{
return $this->status['code'];
}with method that sets the status code and returns the current instance. Note the use of STATUS_CODES to confirm its existence:public function withStatus($statusCode, $reasonPhrase = '')
{
if (!isset(Constants::STATUS_CODES[$statusCode])) {
throw new InvalidArgumentException(Constants::ERROR_INVALID_STATUS);
}
$this->status['code'] = $statusCode;
$this->status['reason'] = ($reasonPhrase) ? Constants::STATUS_CODES[$statusCode] : NULL;
$this->setStatusCode();
return $this;
}?? that returns the first non-null item out of three possible choices:public function getReasonPhrase()
{
return $this->status['reason']
?? Constants::STATUS_CODES[$this->status['code']]
?? '';
}
}First of all, be sure to define the classes discussed in the previous two recipes. After that, you can create another simple server program, chap_09_middleware_server_with_response.php, which sets up autoloading and uses the appropriate classes:
<?php
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\MiddleWare\ { Constants, ServerRequest, Response, Stream };You can then define an array with key/value pairs, where the value points to a text file in the current directory to be used as content:
$data = [ 1 => 'churchill.txt', 2 => 'gettysburg.txt', 3 => 'star_trek.txt' ];
Next, inside a try...catch block, you can initialize some variables, initialize the server request, and set up a temporary filename:
try {
$body['text'] = 'Initial State';
$request = new ServerRequest();
$request->initialize();
$tempFile = bin2hex(random_bytes(8)) . '.txt';
$code = 200;After that, check to see whether the method is GET or POST. If it's GET, check to see whether an id parameter was passed. If so, return the body of the matching text file. Otherwise, return a list of text files:
if ($request->getMethod() == Constants::METHOD_GET) {
$id = $request->getQueryParams()['id'] ?? NULL;
$id = (int) $id;
if ($id && $id <= count($data)) {
$body['text'] = file_get_contents(
__DIR__ . '/' . $data[$id]);
} else {
$body['text'] = $data;
}Otherwise, return a response indicating a success code 204 and the size of the request body received:
} elseif ($request->getMethod() == Constants::METHOD_POST) {
$size = $request->getBody()->getSize();
$body['text'] = $size . ' bytes of data received';
if ($size) {
$code = 201;
} else {
$code = 204;
}
}You can then catch any exceptions and report them with a status code of 500:
} catch (Exception $e) {
$code = 500;
$body['text'] = 'ERROR: ' . $e->getMessage();
}The response needs to be wrapped in a stream, so you can write the body out to the temp file and create it as Stream. You can also set the Content-Type header to application/json and run getHeaders(), which outputs the current set of headers. After that, echo the body of the response. For this illustration, you could also dump the Response instance to confirm it was constructed correctly:
try {
file_put_contents($tempFile, json_encode($body));
$body = new Stream($tempFile);
$header[Constants::HEADER_CONTENT_TYPE] = 'application/json';
$response = new Response($code, $body, $header);
$response->getHeaders();
echo $response->getBody()->getContents() . PHP_EOL;
var_dump($response);To wrap things up, catch any errors or exceptions using Throwable, and don't forget to delete the temp file:
} catch (Throwable $e) {
echo $e->getMessage();
} finally {
unlink($tempFile);
}To test, it's just a matter of opening a terminal window, changing to the /path/to/source/for/this/chapter directory, and running the following command:
php -S localhost:8080
From a browser, you can then call this program, adding an id parameter. You might consider opening the developer tools to monitor the response header. Here is an example of the expected output. Note the content type of application/json:

|
Framework |
Website |
Notes |
|---|---|---|
|
Slim |
http://www.slimframework.com/docs/concepts/value-objects.html | |
|
Laravel/Lumen | ||
|
Zend Framework 3/Expressive |
https://framework.zend.com/blog/2016-06-28-zend-framework-3.html or https://zendframework.github.io/zend-expressive/ respectively |
High PSR-7 compliance Also Diactoros, and Straigility |
|
Zend Framework 2 | ||
|
Symfony | ||
|
Joomla | ||
|
Cake PHP |
PSR-7 support is in the roadmap and will use the bridge approach |
|
Middleware |
Website |
Notes |
|---|---|---|
|
Guzzle | ||
|
Relay |
Dispatcher | |
|
Radar |
Action/domain/responder skeleton | |
|
NegotiationMiddleware | ||
|
psr7-csrf-middleware |
https://packagist.org/packages/schnittstabil/psr7-csrf-middleware | |
|
oauth2-server |
http://alexbilbie.com/2016/04/league-oauth2-server-version-5-is-out | |
|
zend-diactoros |