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

Commit bd31030

Browse files
committed
Merging develop to master in preparation for 1.4.0 release.
2 parents 95c5af4 + 8a23a7d commit bd31030

15 files changed

+618
-43
lines changed

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,49 @@
22

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

5+
## 1.4.0 - TBD
6+
7+
### Added
8+
9+
- [#219](https://github.com/zendframework/zend-diactoros/pull/219) adds two new
10+
classes, `Zend\Diactoros\Request\ArraySerializer` and
11+
`Zend\Diactoros\Response\ArraySerializer`. Each exposes the static methods
12+
`toArray()` and `fromArray()`, allowing de/serialization of messages from and
13+
to arrays.
14+
15+
- [#236](https://github.com/zendframework/zend-diactoros/pull/236) adds two new
16+
constants to the `Response` class: `MIN_STATUS_CODE_VALUE` and
17+
`MAX_STATUS_CODE_VALUE`.
18+
19+
### Changes
20+
21+
- [#240](https://github.com/zendframework/zend-diactoros/pull/240) changes the
22+
behavior of `ServerRequestFactory::fromGlobals()` when no `$cookies` argument
23+
is present. Previously, it would use `$_COOKIES`; now, if a `Cookie` header is
24+
present, it will parse and use that to populate the instance instead.
25+
26+
This change allows utilizing cookies that contain period characters (`.`) in
27+
their names (PHP's built-in cookie handling renames these to replace `.` with
28+
`_`, which can lead to synchronization issues with clients).
29+
30+
- [#235](https://github.com/zendframework/zend-diactoros/pull/235) changes the
31+
behavior of `Uri::__toString()` to better follow proscribed behavior in PSR-7.
32+
In particular, prior to this release, if a scheme was missing but an authority
33+
was present, the class was incorrectly returning a value that did not include
34+
a `//` prefix. As of this release, it now does this correctly.
35+
36+
### Deprecated
37+
38+
- Nothing.
39+
40+
### Removed
41+
42+
- Nothing.
43+
44+
### Fixed
45+
46+
- Nothing.
47+
548
## 1.3.11 - 2017-04-06
649

750
### Added

doc/book/serialization.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Serialization
22

3+
## String
4+
35
At times, it's useful to either create a string representation of a message (serialization), or to
46
cast a string or stream message to an object (deserialization). This package provides features for
57
this in `Zend\Diactoros\Request\Serializer` and `Zend\Diactoros\Response\Serializer`; each provides
@@ -15,3 +17,43 @@ the following static methods:
1517
The deserialization methods (`from*()`) will raise exceptions if errors occur while parsing the
1618
message. The serialization methods (`toString()`) will raise exceptions if required data for
1719
serialization is not present in the message instance.
20+
21+
## Array
22+
23+
This package also provides features for array serialization using
24+
`Zend\Diactoros\Request\ArraySerializer` and `Zend\Diactoros\Response\ArraySerializer`; each provides
25+
the following static methods:
26+
27+
- `fromArray(array $message)` will create either a `Request` or `Response` instance (based on the
28+
serializer used) from the array message.
29+
- `toArray(Psr\Http\Message\RequestInterface|Psr\Http\Message\ResponseInterface $message)` will
30+
create an array from the provided message.
31+
32+
The deserialization methods (`fromArray()`) will raise exceptions if errors occur while parsing the
33+
message.
34+
35+
### Example usage
36+
37+
Array serialization can be usesful for log messages:
38+
39+
```php
40+
class LoggerMiddleware
41+
{
42+
/**
43+
* @var \Psr\Log\LoggerInterface
44+
*/
45+
protected $logger;
46+
47+
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
48+
{
49+
$response = $next($request, $response);
50+
51+
$this->logger->debug('Request/Response', [
52+
'request' => \Zend\Diactoros\Request\ArraySerializer::toArray($request),
53+
'response' => \Zend\Diactoros\Response\ArraySerializer::toArray($response),
54+
]);
55+
56+
return $response;
57+
}
58+
}
59+
```

doc/book/usage.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ $request = Zend\Diactoros\ServerRequestFactory::fromGlobals(
8787
);
8888
```
8989

90+
When no cookie array is supplied, `fromGlobals` will first try to parse the supplied `cookie` header
91+
before falling back to the `$_COOKIE` superglobal. This is done because PHP has some legacy handling
92+
for request parameters which were then registered as global variables. Due to this, cookies with a period
93+
in the name were renamed with underlines. By getting the cookies directly from the cookie header, you have
94+
access to the original cookies in the way you set them in your application and they are send by the user
95+
agent.
96+
9097
### Manipulating the response
9198

9299
Use the response object to add headers and provide content for the response. Writing to the body

src/Request/ArraySerializer.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
/**
3+
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
4+
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
namespace Zend\Diactoros\Request;
9+
10+
use Psr\Http\Message\RequestInterface;
11+
use UnexpectedValueException;
12+
use Zend\Diactoros\Request;
13+
use Zend\Diactoros\Stream;
14+
15+
/**
16+
* Serialize or deserialize request messages to/from arrays.
17+
*
18+
* This class provides functionality for serializing a RequestInterface instance
19+
* to an array, as well as the reverse operation of creating a Request instance
20+
* from an array representing a message.
21+
*/
22+
final class ArraySerializer
23+
{
24+
/**
25+
* Serialize a request message to an array.
26+
*
27+
* @param RequestInterface $request
28+
* @return array
29+
*/
30+
public static function toArray(RequestInterface $request)
31+
{
32+
return [
33+
'method' => $request->getMethod(),
34+
'request_target' => $request->getRequestTarget(),
35+
'uri' => (string) $request->getUri(),
36+
'protocol_version' => $request->getProtocolVersion(),
37+
'headers' => $request->getHeaders(),
38+
'body' => (string) $request->getBody(),
39+
];
40+
}
41+
42+
/**
43+
* Deserialize a request array to a request instance.
44+
*
45+
* @param array $serializedRequest
46+
* @return Request
47+
* @throws UnexpectedValueException when cannot deserialize response
48+
*/
49+
public static function fromArray(array $serializedRequest)
50+
{
51+
try {
52+
$uri = self::getValueFromKey($serializedRequest, 'uri');
53+
$method = self::getValueFromKey($serializedRequest, 'method');
54+
$body = new Stream('php://memory', 'wb+');
55+
$body->write(self::getValueFromKey($serializedRequest, 'body'));
56+
$headers = self::getValueFromKey($serializedRequest, 'headers');
57+
$requestTarget = self::getValueFromKey($serializedRequest, 'request_target');
58+
$protocolVersion = self::getValueFromKey($serializedRequest, 'protocol_version');
59+
60+
return (new Request($uri, $method, $body, $headers))
61+
->withRequestTarget($requestTarget)
62+
->withProtocolVersion($protocolVersion);
63+
} catch (\Exception $exception) {
64+
throw new UnexpectedValueException('Cannot deserialize request', null, $exception);
65+
}
66+
}
67+
68+
/**
69+
* @param array $data
70+
* @param string $key
71+
* @param string $message
72+
* @return mixed
73+
* @throws UnexpectedValueException
74+
*/
75+
private static function getValueFromKey(array $data, $key, $message = null)
76+
{
77+
if (isset($data[$key])) {
78+
return $data[$key];
79+
}
80+
if ($message === null) {
81+
$message = sprintf('Missing "%s" key in serialized request', $key);
82+
}
83+
throw new UnexpectedValueException($message);
84+
}
85+
}

src/Response.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class Response implements ResponseInterface
2424
{
2525
use MessageTrait;
2626

27+
const MIN_STATUS_CODE_VALUE = 100;
28+
const MAX_STATUS_CODE_VALUE = 599;
29+
2730
/**
2831
* Map of standard HTTP status code/reason phrases
2932
*
@@ -159,21 +162,23 @@ public function withStatus($code, $reasonPhrase = '')
159162
}
160163

161164
/**
162-
* Validate a status code.
165+
* Set a valid status code.
163166
*
164-
* @param int|string $code
167+
* @param int $code
165168
* @throws InvalidArgumentException on an invalid status code.
166169
*/
167170
private function setStatusCode($code)
168171
{
169172
if (! is_numeric($code)
170173
|| is_float($code)
171-
|| $code < 100
172-
|| $code >= 600
174+
|| $code < static::MIN_STATUS_CODE_VALUE
175+
|| $code > static::MAX_STATUS_CODE_VALUE
173176
) {
174177
throw new InvalidArgumentException(sprintf(
175-
'Invalid status code "%s"; must be an integer between 100 and 599, inclusive',
176-
(is_scalar($code) ? $code : gettype($code))
178+
'Invalid status code "%s"; must be an integer between %d and %d, inclusive',
179+
(is_scalar($code) ? $code : gettype($code)),
180+
static::MIN_STATUS_CODE_VALUE,
181+
static::MAX_STATUS_CODE_VALUE
177182
));
178183
}
179184
$this->statusCode = $code;

src/Response/ArraySerializer.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
/**
3+
* @see http://github.com/zendframework/zend-diactoros for the canonical source repository
4+
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
namespace Zend\Diactoros\Response;
9+
10+
use Psr\Http\Message\ResponseInterface;
11+
use UnexpectedValueException;
12+
use Zend\Diactoros\Response;
13+
use Zend\Diactoros\Stream;
14+
15+
/**
16+
* Serialize or deserialize response messages to/from arrays.
17+
*
18+
* This class provides functionality for serializing a ResponseInterface instance
19+
* to an array, as well as the reverse operation of creating a Response instance
20+
* from an array representing a message.
21+
*/
22+
final class ArraySerializer
23+
{
24+
/**
25+
* Serialize a response message to an array.
26+
*
27+
* @param ResponseInterface $response
28+
* @return array
29+
*/
30+
public static function toArray(ResponseInterface $response)
31+
{
32+
return [
33+
'status_code' => $response->getStatusCode(),
34+
'reason_phrase' => $response->getReasonPhrase(),
35+
'protocol_version' => $response->getProtocolVersion(),
36+
'headers' => $response->getHeaders(),
37+
'body' => (string) $response->getBody(),
38+
];
39+
}
40+
41+
/**
42+
* Deserialize a response array to a response instance.
43+
*
44+
* @param array $serializedResponse
45+
* @return Response
46+
* @throws UnexpectedValueException when cannot deserialize response
47+
*/
48+
public static function fromArray(array $serializedResponse)
49+
{
50+
try {
51+
$body = new Stream('php://memory', 'wb+');
52+
$body->write(self::getValueFromKey($serializedResponse, 'body'));
53+
54+
$statusCode = self::getValueFromKey($serializedResponse, 'status_code');
55+
$headers = self::getValueFromKey($serializedResponse, 'headers');
56+
$protocolVersion = self::getValueFromKey($serializedResponse, 'protocol_version');
57+
$reasonPhrase = self::getValueFromKey($serializedResponse, 'reason_phrase');
58+
59+
return (new Response($body, $statusCode, $headers))
60+
->withProtocolVersion($protocolVersion)
61+
->withStatus($statusCode, $reasonPhrase);
62+
} catch (\Exception $exception) {
63+
throw new UnexpectedValueException('Cannot deserialize response', null, $exception);
64+
}
65+
}
66+
67+
/**
68+
* @param array $data
69+
* @param string $key
70+
* @param string $message
71+
* @return mixed
72+
* @throws UnexpectedValueException
73+
*/
74+
private static function getValueFromKey(array $data, $key, $message = null)
75+
{
76+
if (isset($data[$key])) {
77+
return $data[$key];
78+
}
79+
if ($message === null) {
80+
$message = sprintf('Missing "%s" key in serialized request', $key);
81+
}
82+
throw new UnexpectedValueException($message);
83+
}
84+
}

src/Response/Serializer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static function fromString($message)
3737
* Parse a response from a stream.
3838
*
3939
* @param StreamInterface $stream
40-
* @return ResponseInterface
40+
* @return Response
4141
* @throws InvalidArgumentException when the stream is not readable.
4242
* @throws UnexpectedValueException when errors occur parsing the message.
4343
*/

src/ServerRequestFactory.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ public static function fromGlobals(
6060
$files = static::normalizeFiles($files ?: $_FILES);
6161
$headers = static::marshalHeaders($server);
6262

63+
if (null === $cookies && array_key_exists('cookie', $headers)) {
64+
$cookies = self::parseCookieHeader($headers['cookie']);
65+
}
66+
6367
return new ServerRequest(
6468
$server,
6569
$files,
@@ -485,4 +489,34 @@ private static function marshalProtocolVersion(array $server)
485489

486490
return $matches['version'];
487491
}
492+
493+
/**
494+
* Parse a cookie header according to RFC 6265.
495+
*
496+
* PHP will replace special characters in cookie names, which results in other cookies not being available due to
497+
* overwriting. Thus, the server request should take the cookies from the request header instead.
498+
*
499+
* @param $cookieHeader
500+
* @return array
501+
*/
502+
private static function parseCookieHeader($cookieHeader)
503+
{
504+
preg_match_all('(
505+
(?:^\\n?[ \t]*|;[ ])
506+
(?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
507+
=
508+
(?P<DQUOTE>"?)
509+
(?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
510+
(?P=DQUOTE)
511+
(?=\\n?[ \t]*$|;[ ])
512+
)x', $cookieHeader, $matches, PREG_SET_ORDER);
513+
514+
$cookies = [];
515+
516+
foreach ($matches as $match) {
517+
$cookies[$match['name']] = urldecode($match['value']);
518+
}
519+
520+
return $cookies;
521+
}
488522
}

src/Stream.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class Stream implements StreamInterface
2020
{
2121
/**
22-
* @var resource
22+
* @var resource|null
2323
*/
2424
protected $resource;
2525

0 commit comments

Comments
 (0)