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

Commit

Permalink
Merge branch 'feature/58' into develop
Browse files Browse the repository at this point in the history
Close #58
  • Loading branch information
weierophinney committed Jun 23, 2015
2 parents 3224057 + 4d20a58 commit 58fca27
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ All notable changes to this project will be documented in this file, in reverse
HTML or JSON responses. It contains the static methods:
- `html($html, $status = 200, array $headers = [])`
- `json($data, $status = 200, array $headers = [])`
- [#58](https://github.com/zendframework/zend-diactoros/pull/58) adds
`Zend\Diactoros\Response\EmptyResponse`, a `Zend\Diactoros\Response` extension
for quickly creating empty, read-only responses.

### Deprecated

Expand Down
136 changes: 136 additions & 0 deletions doc/book/custom-responses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Custom Responses

When developing server-side applications, the message type you're most likely to create manually is
the response. In such cases, the standard signature can be an obstacle to usability. Let's review:

```php
class Response implements ResponseInterface
{
public function __construct($body = 'php://temp', $status = 200, array $headers = []);
}
```

Some standard use cases, however, make this un-wieldy:

- Returning a response containing HTML; in this case, you likely want to provide the HTML to the
constructor, not a stream with the HTML injected.
- Returning a response containing JSON; in this case, you likely want to provide the data to
seriazlize to JSON, not a stream containing serialized JSON.
- Returning a response with no content; in this case, you don't want to bother with the body at all.

Starting with version 1.1, Diactoros offers several custom response types and factories for
simplifying these common tasks.

## String responses

`Zend\Diactoros\Response\StringResponse` provides factory methods for two standard string response
types: HTML and JSON.

### HTML

The `html()` factory will create a response with the provided HTML as a payload, setting the
`Content-Type` header to `text/html` by default:

```php
$response = StringResponse::html($htmlContent);
```

The factory allows passing two additional arguments: a status code, and an array of headers. These
allow you to further seed the initial state of the response.

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

### JSON
The `json()` factory accepts a data structure to convert to JSON, and returns a response with the
JSON content and the `Content-Type` header set to `application/json`:

```php
$response = StringResponse::json($data);
```

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

Just like the `html()` factory, the `json()` factory allows passing two additional arguments — a
status code, and an array of headers — to allow you to further seed the initial state of the
response.

## Empty Responses

Many API actions allow returning empty responses:

- `201 Created` responses are often empty, and only include a `Link` or `Location` header pointing
to the newly created resource.
- `202 Accepted` responses are typically empty, indicating that the new entity has been received,
but not yet processed.
- `204 No Content` responses are, by definition, empty, and often used as a success response when
deleting an entity.

`Zend\Diactoros\Response\EmptyResponse` is a `Zend\Diactoros\Response` extension that, by default,
returns an empty response with a 204 status. Its constructor allows passing the status and headers
only:

```php
class EmptyResponse extends Response
{
public function __construct($status = 204, array $headers = []);
}
```

An empty, read-only body is injected at instantiation, ensuring no write operations are possible on
the response. Usage is typically one of the following forms:

```php
// Basic 204 response:
$response = new EmptyResponse();

// 201 response with location header:
$response = new EmptyResponse(201, [
'Location' => [ $url ],
]);

// Alternately, set the header after instantiation:
$response = ( new EmptyResponse(201) )->withHeader('Location', $url);
```

## Creating custom responses

PHP allows constructor overloading. What this means is that constructors of extending classes can
define completely different argument sets without conflicting with the parent implementation.
Considering that most custom response types do not need to change internal functionality, but
instead focus on user experience (i.e., simplifying instantiation), this fact can be leveraged to
create your custom types.

The general pattern will be something like this:

```php
class MyCustomResponse extends Response
{
public function __construct($data, $status = 200, array $headers = [])
{
// - Do something with $data, and create a Stream for the body (if necessary).
// - Maybe set some default headers.

parent::__construct($body, $status, $headers);
}
}
```

Note the call to `parent::__construct()`. This is particularly relevant, as the implementation at
the time of writing has all class properties marked as private, making them inaccessible to
extensions; this is done to protect encapsulation and ensure consistency of operations between
instances.

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

```php
function ($dataOrMessage, $status = 200, array $headers = []);
```

These ensure consistency of factories, and allow consumers to provide the status and
instance-specific headers on creation. (Obviously, specify different defaults as necessary.)
1 change: 1 addition & 0 deletions doc/bookdown.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"book/overview.md",
"book/install.md",
"book/usage.md",
"book/custom-responses.md",
"book/emitting-responses.md",
"book/serialization.md",
"book/api.md"
Expand Down
42 changes: 42 additions & 0 deletions src/Response/EmptyResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Diactoros\Response;

use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;

/**
* A class representing empty HTTP responses.
*/
class EmptyResponse extends Response
{
/**
* Create an empty response with the given status code.
*
* @param int $status Status code for the response, if any.
* @param array $headers Headers for the response, if any.
*/
public function __construct($status = 204, array $headers = [])
{
$body = new Stream('php://temp', 'r');
parent::__construct($body, $status, $headers);
}

/**
* Create an empty response with the given headers.
*
* @param array $headers Headers for the response.
* @return EmptyResponse
*/
public static function withHeaders(array $headers)
{
return new static(204, $headers);
}
}
4 changes: 2 additions & 2 deletions src/Response/StringResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private function __construct()
* Create a Response from the provided information.
*
* Creates a Response using a php://temp stream, and writes the provided
* body to the stream; if non content-type header was provided, the given
* body to the stream; if no content-type header was provided, the given
* $contentType is injected for it.
*
* @param string $body
Expand All @@ -85,7 +85,7 @@ private static function createResponse($body, $status, array $headers, $contentT
$response = new Response('php://temp', $status, $headers);
$response->getBody()->write($body);

if ($response->hasHeader('content-type')) {
if (empty($contentType) || $response->hasHeader('content-type')) {
return $response;
}

Expand Down
33 changes: 33 additions & 0 deletions test/Response/EmptyResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/

namespace ZendTest\Diactoros\Response;

use PHPUnit_Framework_TestCase as TestCase;
use Zend\Diactoros\Response\EmptyResponse;

class EmptyResponseTest extends TestCase
{
public function testConstructor()
{
$response = new EmptyResponse(201);
$this->assertInstanceOf('Zend\Diactoros\Response', $response);
$this->assertEquals('', (string) $response->getBody());
$this->assertEquals(201, $response->getStatusCode());
}

public function testHeaderConstructor()
{
$response = EmptyResponse::withHeaders(['x-empty' => ['true']]);
$this->assertInstanceOf('Zend\Diactoros\Response', $response);
$this->assertEquals('', (string) $response->getBody());
$this->assertEquals(204, $response->getStatusCode());
$this->assertEquals('true', $response->getHeaderLine('x-empty'));
}
}

0 comments on commit 58fca27

Please sign in to comment.