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

Commit 1760897

Browse files
committed
Merge branch 'develop'
Merge develop to master for 1.1.0 release.
2 parents 53d25cd + bfd0e19 commit 1760897

18 files changed

+905
-63
lines changed

CHANGELOG.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,46 @@
22

33
All notable changes to this project will be documented in this file, in reverse chronological order by release.
44

5+
## 1.1.0 - TBD
6+
7+
### Added
8+
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).
27+
28+
### Deprecated
29+
30+
- Nothing.
31+
32+
### Removed
33+
34+
- [#43](https://github.com/zendframework/zend-diactoros/pull/43) removed both
35+
`ServerRequestFactory::marshalUri()` and `ServerRequestFactory::marshalHostAndPort()`,
36+
which were deprecated prior to the 1.0 release.
37+
38+
### Fixed
39+
40+
- [#29](https://github.com/zendframework/zend-diactoros/pull/29) fixes request
41+
method validation to allow any valid token as defined by [RFC
42+
7230](http://tools.ietf.org/html/rfc7230#appendix-B). This allows usage of
43+
custom request methods, vs a static, hard-coded list.
44+
545
## 1.0.5 - 2015-06-24
646

747
### Added
@@ -124,7 +164,7 @@ immediately.
124164
### Removed
125165

126166
- Nothing.
127-
167+
-
128168
### Fixed
129169

130170
- [#41](https://github.com/zendframework/zend-diactoros/pull/41) fixes the

doc/book/api.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,39 @@ class Response
7373
Like the `Request` and `ServerRequest`, responses are immutable. Any methods that would change state
7474
-- those prefixed with `with` and `without` -- all return a new instance with the changes requested.
7575

76-
### ServerRequestFactory
76+
### StringResponse (factory)
77+
78+
- Added in 1.1.0
79+
80+
The most common use case in server-side applications for generating responses is to provide a string
81+
to use for the response, typically HTML or data to serialize as JSON. `Zend\Diactoros\Response\StringResponse`
82+
exists to facilitate these use cases:
83+
84+
```php
85+
$htmlResponse = StringResponse::html($html);
86+
87+
$jsonResponse = StringResponse::json($data);
88+
```
89+
90+
In the first example, you will receive a response with a stream containing the HTML; additionally,
91+
the `Content-Type` header will be set to `text/html`. In the second case, the stream will contain a
92+
stream containing the JSON-serialized `$data`, and have a `Content-Type` header set to
93+
`application/json`.
94+
95+
Both factory methods allow passing the HTTP status, as well as any headers you want to specify,
96+
including the `Content-Type` header:
97+
98+
```php
99+
$htmlResponse = StringResponse::html($html, 404, [
100+
'Content-Type' => [ 'application/xhtml+xml' ],
101+
]);
102+
103+
$jsonResponse = StringResponse::html($html, 422, [
104+
'Content-Type' => [ 'application/problem+json' ],
105+
]);
106+
```
107+
108+
## ServerRequestFactory
77109

78110
This static class can be used to marshal a `ServerRequest` instance from the PHP environment. The
79111
primary entry point is `Zend\Diactoros\ServerRequestFactory::fromGlobals(array $server, array

doc/book/custom-responses.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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+
- Returning a redirect response; in this case, you likely just want to specify the target for the
21+
`Location` header, and optionally the status code.
22+
23+
Starting with version 1.1, Diactoros offers several custom response types for simplifying these
24+
common tasks.
25+
26+
## HTML Responses
27+
28+
`Zend\Diactoros\Response\HtmlResponse` allows specifying HTML as a payload, and sets the
29+
`Content-Type` header to `text/html` by default:
30+
31+
```php
32+
$response = new HtmlResponse($htmlContent);
33+
```
34+
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+
```
42+
43+
Headers must be in the same format as you would provide to the
44+
[Response constructor][api.md#response-message].
45+
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`:
50+
51+
```php
52+
$response = new JsonResponse($data);
53+
```
54+
55+
If a null value is provide, an empty JSON object is used for the content. Scalar data is cast to an
56+
array before serialization. If providing an object, we recommend implementing
57+
[JsonSerializable](http://php.net/JsonSerializable) to ensure your object is correctly serialized.
58+
59+
Just like the `HtmlResponse`, the `JsonResponse` allows passing two additional arguments — a
60+
status code, and an array of headers — to allow you to further seed the initial state of the
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+
```
80+
81+
## Empty Responses
82+
83+
Many API actions allow returning empty responses:
84+
85+
- `201 Created` responses are often empty, and only include a `Link` or `Location` header pointing
86+
to the newly created resource.
87+
- `202 Accepted` responses are typically empty, indicating that the new entity has been received,
88+
but not yet processed.
89+
- `204 No Content` responses are, by definition, empty, and often used as a success response when
90+
deleting an entity.
91+
92+
`Zend\Diactoros\Response\EmptyResponse` is a `Zend\Diactoros\Response` extension that, by default,
93+
returns an empty response with a 204 status. Its constructor allows passing the status and headers
94+
only:
95+
96+
```php
97+
class EmptyResponse extends Response
98+
{
99+
public function __construct($status = 204, array $headers = []);
100+
}
101+
```
102+
103+
An empty, read-only body is injected at instantiation, ensuring no write operations are possible on
104+
the response. Usage is typically one of the following forms:
105+
106+
```php
107+
// Basic 204 response:
108+
$response = new EmptyResponse();
109+
110+
// 201 response with location header:
111+
$response = new EmptyResponse(201, [
112+
'Location' => [ $url ],
113+
]);
114+
115+
// Alternately, set the header after instantiation:
116+
$response = ( new EmptyResponse(201) )->withHeader('Location', $url);
117+
```
118+
119+
## Redirects
120+
121+
`Zend\Diactoros\Response\RedirectResponse` is a `Zend\Diactoros\Response` extension for producing
122+
redirect responses. The only required argument is a URI, which may be provided as either a string or
123+
`Psr\Http\Message\UriInterface` instance. By default, the status 302 is used, and no other headers
124+
are produced; you may alter these via the additional optional arguments:
125+
126+
```php
127+
class RedirectResponse extends Response
128+
{
129+
public function __construct($uri, $status = 302, array $headers = []);
130+
}
131+
```
132+
133+
Typical usage is:
134+
135+
```php
136+
// 302 redirect:
137+
$response = new RedirectResponse('/user/login');
138+
139+
// 301 redirect:
140+
$response = new RedirectResponse('/user/login', 301);
141+
142+
// using a URI instance (e.g., by altering the request URI instance)
143+
$uri = $request->getUri();
144+
$response = new RedirectResponse($uri->withPath('/login'));
145+
```
146+
147+
## Creating custom responses
148+
149+
PHP allows constructor overloading. What this means is that constructors of extending classes can
150+
define completely different argument sets without conflicting with the parent implementation.
151+
Considering that most custom response types do not need to change internal functionality, but
152+
instead focus on user experience (i.e., simplifying instantiation), this fact can be leveraged to
153+
create your custom types.
154+
155+
The general pattern will be something like this:
156+
157+
```php
158+
class MyCustomResponse extends Response
159+
{
160+
public function __construct($data, $status = 200, array $headers = [])
161+
{
162+
// - Do something with $data, and create a Stream for the body (if necessary).
163+
// - Maybe set some default headers.
164+
165+
parent::__construct($body, $status, $headers);
166+
}
167+
}
168+
```
169+
170+
Note the call to `parent::__construct()`. This is particularly relevant, as the implementation at
171+
the time of writing has all class properties marked as private, making them inaccessible to
172+
extensions; this is done to protect encapsulation and ensure consistency of operations between
173+
instances.
174+
175+
If you don't want to go the extension route (perhaps you don't want another `ResponseInterface`
176+
implementation within your object graph) you can instead create a factory. As an example:
177+
178+
```php
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!');
189+
```
190+
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.

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"

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/RequestTrait.php

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,6 @@ trait RequestTrait
4747
*/
4848
private $uri;
4949

50-
/**
51-
* Supported HTTP methods
52-
*
53-
* @var array
54-
*/
55-
private $validMethods = [
56-
'CONNECT',
57-
'DELETE',
58-
'GET',
59-
'HEAD',
60-
'OPTIONS',
61-
'PATCH',
62-
'POST',
63-
'PUT',
64-
'TRACE',
65-
];
66-
6750
/**
6851
* Initialize request state.
6952
*
@@ -290,9 +273,7 @@ private function validateMethod($method)
290273
));
291274
}
292275

293-
$method = strtoupper($method);
294-
295-
if (! in_array($method, $this->validMethods, true)) {
276+
if (! preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)) {
296277
throw new InvalidArgumentException(sprintf(
297278
'Unsupported HTTP method "%s" provided',
298279
$method

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+
}

0 commit comments

Comments
 (0)