Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 58fca27

Browse files
committed
Merge branch 'feature/58' into develop
Close #58
2 parents 3224057 + 4d20a58 commit 58fca27

File tree

6 files changed

+217
-2
lines changed

6 files changed

+217
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ All notable changes to this project will be documented in this file, in reverse
1111
HTML or JSON responses. It contains the static methods:
1212
- `html($html, $status = 200, array $headers = [])`
1313
- `json($data, $status = 200, array $headers = [])`
14+
- [#58](https://github.com/zendframework/zend-diactoros/pull/58) adds
15+
`Zend\Diactoros\Response\EmptyResponse`, a `Zend\Diactoros\Response` extension
16+
for quickly creating empty, read-only responses.
1417

1518
### Deprecated
1619

doc/book/custom-responses.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Custom Responses
2+
3+
When developing server-side applications, the message type you're most likely to create manually is
4+
the response. In such cases, the standard signature can be an obstacle to usability. Let's review:
5+
6+
```php
7+
class Response implements ResponseInterface
8+
{
9+
public function __construct($body = 'php://temp', $status = 200, array $headers = []);
10+
}
11+
```
12+
13+
Some standard use cases, however, make this un-wieldy:
14+
15+
- Returning a response containing HTML; in this case, you likely want to provide the HTML to the
16+
constructor, not a stream with the HTML injected.
17+
- Returning a response containing JSON; in this case, you likely want to provide the data to
18+
seriazlize to JSON, not a stream containing serialized JSON.
19+
- Returning a response with no content; in this case, you don't want to bother with the body at all.
20+
21+
Starting with version 1.1, Diactoros offers several custom response types and factories for
22+
simplifying these common tasks.
23+
24+
## String responses
25+
26+
`Zend\Diactoros\Response\StringResponse` provides factory methods for two standard string response
27+
types: HTML and JSON.
28+
29+
### HTML
30+
31+
The `html()` factory will create a response with the provided HTML as a payload, setting the
32+
`Content-Type` header to `text/html` by default:
33+
34+
```php
35+
$response = StringResponse::html($htmlContent);
36+
```
37+
38+
The factory allows passing two additional arguments: a status code, and an array of headers. These
39+
allow you to further seed the initial state of the response.
40+
41+
Headers must be in the same format as you would provide to the
42+
[Response constructor][api.md#response-message].
43+
44+
### JSON
45+
The `json()` factory accepts a data structure to convert to JSON, and returns a response with the
46+
JSON content and the `Content-Type` header set to `application/json`:
47+
48+
```php
49+
$response = StringResponse::json($data);
50+
```
51+
52+
If a null value is provide, an empty JSON object is used for the content. Scalar data is cast to an
53+
array before serialization. If providing an object, we recommend implementing
54+
[JsonSerializable](http://php.net/JsonSerializable) to ensure your object is correctly serialized.
55+
56+
Just like the `html()` factory, the `json()` factory allows passing two additional arguments — a
57+
status code, and an array of headers — to allow you to further seed the initial state of the
58+
response.
59+
60+
## Empty Responses
61+
62+
Many API actions allow returning empty responses:
63+
64+
- `201 Created` responses are often empty, and only include a `Link` or `Location` header pointing
65+
to the newly created resource.
66+
- `202 Accepted` responses are typically empty, indicating that the new entity has been received,
67+
but not yet processed.
68+
- `204 No Content` responses are, by definition, empty, and often used as a success response when
69+
deleting an entity.
70+
71+
`Zend\Diactoros\Response\EmptyResponse` is a `Zend\Diactoros\Response` extension that, by default,
72+
returns an empty response with a 204 status. Its constructor allows passing the status and headers
73+
only:
74+
75+
```php
76+
class EmptyResponse extends Response
77+
{
78+
public function __construct($status = 204, array $headers = []);
79+
}
80+
```
81+
82+
An empty, read-only body is injected at instantiation, ensuring no write operations are possible on
83+
the response. Usage is typically one of the following forms:
84+
85+
```php
86+
// Basic 204 response:
87+
$response = new EmptyResponse();
88+
89+
// 201 response with location header:
90+
$response = new EmptyResponse(201, [
91+
'Location' => [ $url ],
92+
]);
93+
94+
// Alternately, set the header after instantiation:
95+
$response = ( new EmptyResponse(201) )->withHeader('Location', $url);
96+
```
97+
98+
## Creating custom responses
99+
100+
PHP allows constructor overloading. What this means is that constructors of extending classes can
101+
define completely different argument sets without conflicting with the parent implementation.
102+
Considering that most custom response types do not need to change internal functionality, but
103+
instead focus on user experience (i.e., simplifying instantiation), this fact can be leveraged to
104+
create your custom types.
105+
106+
The general pattern will be something like this:
107+
108+
```php
109+
class MyCustomResponse extends Response
110+
{
111+
public function __construct($data, $status = 200, array $headers = [])
112+
{
113+
// - Do something with $data, and create a Stream for the body (if necessary).
114+
// - Maybe set some default headers.
115+
116+
parent::__construct($body, $status, $headers);
117+
}
118+
}
119+
```
120+
121+
Note the call to `parent::__construct()`. This is particularly relevant, as the implementation at
122+
the time of writing has all class properties marked as private, making them inaccessible to
123+
extensions; this is done to protect encapsulation and ensure consistency of operations between
124+
instances.
125+
126+
If you don't want to go the extension route (perhaps you don't want another `ResponseInterface`
127+
implementation within your object graph) you can instead create a factory.
128+
[StringResponse](https://github.com/zendframework/zend-diactoros/tree/master/src/Response/StringResponse.php)
129+
provides one such example. We recommend the following semantics:
130+
131+
```php
132+
function ($dataOrMessage, $status = 200, array $headers = []);
133+
```
134+
135+
These ensure consistency of factories, and allow consumers to provide the status and
136+
instance-specific headers on creation. (Obviously, specify different defaults as necessary.)

doc/bookdown.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"book/overview.md",
55
"book/install.md",
66
"book/usage.md",
7+
"book/custom-responses.md",
78
"book/emitting-responses.md",
89
"book/serialization.md",
910
"book/api.md"

src/Response/EmptyResponse.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
6+
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
8+
*/
9+
10+
namespace Zend\Diactoros\Response;
11+
12+
use Zend\Diactoros\Response;
13+
use Zend\Diactoros\Stream;
14+
15+
/**
16+
* A class representing empty HTTP responses.
17+
*/
18+
class EmptyResponse extends Response
19+
{
20+
/**
21+
* Create an empty response with the given status code.
22+
*
23+
* @param int $status Status code for the response, if any.
24+
* @param array $headers Headers for the response, if any.
25+
*/
26+
public function __construct($status = 204, array $headers = [])
27+
{
28+
$body = new Stream('php://temp', 'r');
29+
parent::__construct($body, $status, $headers);
30+
}
31+
32+
/**
33+
* Create an empty response with the given headers.
34+
*
35+
* @param array $headers Headers for the response.
36+
* @return EmptyResponse
37+
*/
38+
public static function withHeaders(array $headers)
39+
{
40+
return new static(204, $headers);
41+
}
42+
}

src/Response/StringResponse.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private function __construct()
7171
* Create a Response from the provided information.
7272
*
7373
* Creates a Response using a php://temp stream, and writes the provided
74-
* body to the stream; if non content-type header was provided, the given
74+
* body to the stream; if no content-type header was provided, the given
7575
* $contentType is injected for it.
7676
*
7777
* @param string $body
@@ -85,7 +85,7 @@ private static function createResponse($body, $status, array $headers, $contentT
8585
$response = new Response('php://temp', $status, $headers);
8686
$response->getBody()->write($body);
8787

88-
if ($response->hasHeader('content-type')) {
88+
if (empty($contentType) || $response->hasHeader('content-type')) {
8989
return $response;
9090
}
9191

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
6+
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
8+
*/
9+
10+
namespace ZendTest\Diactoros\Response;
11+
12+
use PHPUnit_Framework_TestCase as TestCase;
13+
use Zend\Diactoros\Response\EmptyResponse;
14+
15+
class EmptyResponseTest extends TestCase
16+
{
17+
public function testConstructor()
18+
{
19+
$response = new EmptyResponse(201);
20+
$this->assertInstanceOf('Zend\Diactoros\Response', $response);
21+
$this->assertEquals('', (string) $response->getBody());
22+
$this->assertEquals(201, $response->getStatusCode());
23+
}
24+
25+
public function testHeaderConstructor()
26+
{
27+
$response = EmptyResponse::withHeaders(['x-empty' => ['true']]);
28+
$this->assertInstanceOf('Zend\Diactoros\Response', $response);
29+
$this->assertEquals('', (string) $response->getBody());
30+
$this->assertEquals(204, $response->getStatusCode());
31+
$this->assertEquals('true', $response->getHeaderLine('x-empty'));
32+
}
33+
}

0 commit comments

Comments
 (0)