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

Commit bfd0e19

Browse files
committed
Merge branch 'feature/61' into develop
Close #61
2 parents 12117d8 + 57c9caa commit bfd0e19

File tree

10 files changed

+403
-162
lines changed

10 files changed

+403
-162
lines changed

CHANGELOG.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@ All notable changes to this project will be documented in this file, in reverse
66

77
### Added
88

9-
- [#52](https://github.com/zendframework/zend-diactoros/pull/52) adds
10-
`Zend\Diactoros\Response\StringResponse`, a factory class for generating
11-
HTML or JSON responses. It contains the static methods:
12-
- `html($html, $status = 200, array $headers = [])`
13-
- `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.
17-
- [#59](https://github.com/zendframework/zend-diactoros/pull/59) adds
18-
`Zend\Diactoros\Response\RedirectResponse`, a `Zend\Diactoros\Response` extension
19-
for quickly creating redirect responses.
9+
- [#52](https://github.com/zendframework/zend-diactoros/pull/52),
10+
[#58](https://github.com/zendframework/zend-diactoros/pull/58),
11+
[#59](https://github.com/zendframework/zend-diactoros/pull/59), and
12+
[#61](https://github.com/zendframework/zend-diactoros/pull/61) create several
13+
custom response types for simplifying response creation:
14+
15+
- `Zend\Diactoros\Response\HtmlResponse` accepts HTML content via its
16+
constructor, and sets the `Content-Type` to `text/html`.
17+
- `Zend\Diactoros\Response\JsonResponse` accepts data to serialize to JSON via
18+
its constructor, and sets the `Content-Type` to `application/json`.
19+
- `Zend\Diactoros\Response\EmptyResponse` allows creating empty, read-only
20+
responses, with a default status code of 204.
21+
- `Zend\Diactoros\Response\RedirectResponse` allows specifying a URI for the
22+
`Location` header in the constructor, with a default status code of 302.
23+
24+
Each also accepts an optional status code, and optional headers (which can
25+
also be used to provide an alternate `Content-Type` in the case of the HTML
26+
and JSON responses).
2027

2128
### Deprecated
2229

doc/book/custom-responses.md

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,63 @@ Some standard use cases, however, make this un-wieldy:
2020
- Returning a redirect response; in this case, you likely just want to specify the target for the
2121
`Location` header, and optionally the status code.
2222

23-
Starting with version 1.1, Diactoros offers several custom response types and factories for
24-
simplifying these common tasks.
23+
Starting with version 1.1, Diactoros offers several custom response types for simplifying these
24+
common tasks.
2525

26-
## String responses
26+
## HTML Responses
2727

28-
`Zend\Diactoros\Response\StringResponse` provides factory methods for two standard string response
29-
types: HTML and JSON.
30-
31-
### HTML
32-
33-
The `html()` factory will create a response with the provided HTML as a payload, setting the
28+
`Zend\Diactoros\Response\HtmlResponse` allows specifying HTML as a payload, and sets the
3429
`Content-Type` header to `text/html` by default:
3530

3631
```php
37-
$response = StringResponse::html($htmlContent);
32+
$response = new HtmlResponse($htmlContent);
3833
```
3934

40-
The factory allows passing two additional arguments: a status code, and an array of headers. These
41-
allow you to further seed the initial state of the response.
35+
The constructor allows passing two additional arguments: a status code, and an array of headers.
36+
These allow you to further seed the initial state of the response, as well as to override the
37+
`Content-Type` header if desired:
38+
39+
```php
40+
$response = new HtmlResponse($htmlContent, 200, [ 'Content-Type' => ['application/xhtml+xml']]);
41+
```
4242

4343
Headers must be in the same format as you would provide to the
4444
[Response constructor][api.md#response-message].
4545

46-
### JSON
47-
The `json()` factory accepts a data structure to convert to JSON, and returns a response with the
48-
JSON content and the `Content-Type` header set to `application/json`:
46+
## JSON Responses
47+
48+
`Zend\Diactoros\Response\JsonResponse` accepts a data structure to convert to JSON, and sets
49+
the `Content-Type` header to `application/json`:
4950

5051
```php
51-
$response = StringResponse::json($data);
52+
$response = new JsonResponse($data);
5253
```
5354

5455
If a null value is provide, an empty JSON object is used for the content. Scalar data is cast to an
5556
array before serialization. If providing an object, we recommend implementing
5657
[JsonSerializable](http://php.net/JsonSerializable) to ensure your object is correctly serialized.
5758

58-
Just like the `html()` factory, the `json()` factory allows passing two additional arguments — a
59+
Just like the `HtmlResponse`, the `JsonResponse` allows passing two additional arguments — a
5960
status code, and an array of headers — to allow you to further seed the initial state of the
60-
response.
61+
response:
62+
63+
```php
64+
$response = new JsonResponse($data, 200, [ 'Content-Type' => ['application/hal+json']]);
65+
```
66+
67+
Finally, `JsonResponse` allows a fourth optional argument, the flags to provide to `json_encode()`.
68+
By default, these are set to `JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT` (integer
69+
15), providing [RFC 4627](http://tools.ietf.org/html/rfc4627) compliant JSON capable of embedding in
70+
HTML. If you want to specify a different set of flags, use the fourth constructor argument:
71+
72+
```php
73+
$response = new JsonResponse(
74+
$data,
75+
200,
76+
[],
77+
JSON_PRETTY_PRINT | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
78+
);
79+
```
6180

6281
## Empty Responses
6382

@@ -154,13 +173,20 @@ extensions; this is done to protect encapsulation and ensure consistency of oper
154173
instances.
155174

156175
If you don't want to go the extension route (perhaps you don't want another `ResponseInterface`
157-
implementation within your object graph) you can instead create a factory.
158-
[StringResponse](https://github.com/zendframework/zend-diactoros/tree/master/src/Response/StringResponse.php)
159-
provides one such example. We recommend the following semantics:
176+
implementation within your object graph) you can instead create a factory. As an example:
160177

161178
```php
162-
function ($dataOrMessage, $status = 200, array $headers = []);
179+
$plainTextResponse = function ($text, $status = 200, array $headers = []) {
180+
$response = new Response('php://temp', $status, $headers);
181+
$response->getBody()->write($text);
182+
if (! $response->hasHeader('Content-Type')) {
183+
$response = $response->withHeader('Content-Type', 'text/plain');
184+
}
185+
return $response;
186+
};
187+
188+
$response = $plainTextResponse('Hello, world!');
163189
```
164190

165-
These ensure consistency of factories, and allow consumers to provide the status and
166-
instance-specific headers on creation. (Obviously, specify different defaults as necessary.)
191+
We recommend following the semantic of providing the status and headers as the final two arguments
192+
for any factory or custom response extensions.

phpunit.xml.dist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44
<directory>./test</directory>
55
</testsuite>
66
</testsuites>
7+
8+
<filter>
9+
<whitelist processUncoveredFilesFromWhitelist="true">
10+
<directory suffix=".php">src</directory>
11+
</whitelist>
12+
</filter>
713
</phpunit>

src/Response/HtmlResponse.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 InvalidArgumentException;
13+
use Psr\Http\Message\StreamInterface;
14+
use Zend\Diactoros\Response;
15+
use Zend\Diactoros\Stream;
16+
17+
/**
18+
* HTML response.
19+
*
20+
* Allows creating a response by passing an HTML string to the constructor;
21+
* by default, sets a status code of 200 and sets the Content-Type header to
22+
* text/html.
23+
*/
24+
class HtmlResponse extends Response
25+
{
26+
use InjectContentTypeTrait;
27+
28+
/**
29+
* Create an HTML response.
30+
*
31+
* Produces an HTML response with a Content-Type of text/html and a default
32+
* status of 200.
33+
*
34+
* @param string|StreamInterface $html HTML or stream for the message body.
35+
* @param int $status Integer status code for the response; 200 by default.
36+
* @param array $headers Array of headers to use at initialization.
37+
* @throws InvalidArgumentException if $html is neither a string or stream.
38+
*/
39+
public function __construct($html, $status = 200, array $headers = [])
40+
{
41+
parent::__construct(
42+
$this->createBody($html),
43+
$status,
44+
$this->injectContentType('text/html', $headers)
45+
);
46+
}
47+
48+
/**
49+
* Create the message body.
50+
*
51+
* @param string|StreamInterface $html
52+
* @return StreamInterface
53+
* @throws InvalidArgumentException if $html is neither a string or stream.
54+
*/
55+
private function createBody($html)
56+
{
57+
if ($html instanceof StreamInterface) {
58+
return $html;
59+
}
60+
61+
if (! is_string($html)) {
62+
throw new InvalidArgumentException(sprintf(
63+
'Invalid content (%s) provided to %s',
64+
(is_object($html) ? get_class($html) : gettype($html)),
65+
__CLASS__
66+
));
67+
}
68+
69+
$body = new Stream('php://temp', 'wb+');
70+
$body->write($html);
71+
return $body;
72+
}
73+
}
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 Zend\Diactoros\Response;
11+
12+
trait InjectContentTypeTrait
13+
{
14+
/**
15+
* Inject the provided Content-Type, if none is already present.
16+
*
17+
* @param string $contentType
18+
* @param array $headers
19+
* @return array Headers with injected Content-Type
20+
*/
21+
private function injectContentType($contentType, array $headers)
22+
{
23+
$hasContentType = array_reduce(array_keys($headers), function ($carry, $item) {
24+
return $carry ?: (strtolower($item) === 'content-type');
25+
}, false);
26+
27+
if (! $hasContentType) {
28+
$headers['content-type'] = [$contentType];
29+
}
30+
31+
return $headers;
32+
}
33+
}

src/Response/JsonResponse.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 ArrayObject;
13+
use InvalidArgumentException;
14+
use Zend\Diactoros\Response;
15+
use Zend\Diactoros\Stream;
16+
17+
/**
18+
* HTML response.
19+
*
20+
* Allows creating a response by passing an HTML string to the constructor;
21+
* by default, sets a status code of 200 and sets the Content-Type header to
22+
* text/html.
23+
*/
24+
class JsonResponse extends Response
25+
{
26+
use InjectContentTypeTrait;
27+
28+
/**
29+
* Create a JSON response with the given array of data.
30+
*
31+
* If the data provided is null, an empty ArrayObject is used; if the data
32+
* is scalar, it is cast to an array prior to serialization.
33+
*
34+
* Default JSON encoding is performed with the following options, which
35+
* produces RFC4627-compliant JSON, capable of embedding into HTML.
36+
*
37+
* - JSON_HEX_TAG
38+
* - JSON_HEX_APOS
39+
* - JSON_HEX_AMP
40+
* - JSON_HEX_QUOT
41+
*
42+
* @param string $data Data to convert to JSON.
43+
* @param int $status Integer status code for the response; 200 by default.
44+
* @param array $headers Array of headers to use at initialization.
45+
* @param int $encodingOptions JSON encoding options to use.
46+
* @throws InvalidArgumentException if unable to encode the $data to JSON.
47+
*/
48+
public function __construct($data, $status = 200, array $headers = [], $encodingOptions = 15)
49+
{
50+
$body = new Stream('php://temp', 'wb+');
51+
$body->write($this->jsonEncode($data, $encodingOptions));
52+
53+
$headers = $this->injectContentType('application/json', $headers);
54+
55+
parent::__construct($body, $status, $headers);
56+
}
57+
58+
/**
59+
* Encode the provided data to JSON.
60+
*
61+
* @param mixed $data
62+
* @param int $encodingOptions
63+
* @return string
64+
* @throws InvalidArgumentException if unable to encode the $data to JSON.
65+
*/
66+
private function jsonEncode($data, $encodingOptions)
67+
{
68+
if (is_resource($data)) {
69+
throw new InvalidArgumentException('Cannot JSON encode resources');
70+
}
71+
72+
if ($data === null) {
73+
// Use an ArrayObject to force an empty JSON object.
74+
$data = new ArrayObject();
75+
}
76+
77+
if (is_scalar($data)) {
78+
$data = (array) $data;
79+
}
80+
81+
// Clear json_last_error()
82+
json_encode(null);
83+
84+
$json = json_encode($data, $encodingOptions);
85+
86+
if (JSON_ERROR_NONE !== json_last_error()) {
87+
throw new InvalidArgumentException(sprintf(
88+
'Unable to encode data to JSON in %s: %s',
89+
__CLASS__,
90+
json_last_error_msg()
91+
));
92+
}
93+
94+
return $json;
95+
}
96+
}

src/Response/RedirectResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function __construct($uri, $status = 302, array $headers = [])
4141
));
4242
}
4343

44-
$headers['location'] = [$uri];
44+
$headers['location'] = [(string) $uri];
4545
parent::__construct('php://temp', $status, $headers);
4646
}
4747
}

0 commit comments

Comments
 (0)