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

Commit 635b40b

Browse files
committed
Merging develop to master in preparation for 1.3.0
2 parents 3f2f9e3 + 3dff33f commit 635b40b

File tree

5 files changed

+329
-79
lines changed

5 files changed

+329
-79
lines changed

CHANGELOG.md

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

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

5+
## 1.3.0 - TBD
6+
7+
### Added
8+
9+
- [#110](https://github.com/zendframework/zend-diactoros/pull/110) adds
10+
`Zend\Diactoros\Response\SapiEmitterTrait`, which provides the following
11+
private method definitions:
12+
- `injectContentLength()`
13+
- `emitStatusLine()`
14+
- `emitHeaders()`
15+
- `flush()`
16+
- `filterHeader()`
17+
The `SapiEmitter` implementation has been updated to remove those methods and
18+
instead compose the trait.
19+
- [#111](https://github.com/zendframework/zend-diactoros/pull/111) adds
20+
a new emitter implementation, `SapiStreamEmitter`; this emitter type will
21+
loop through the stream instead of emitting it in one go, and supports content
22+
ranges.
23+
24+
### Deprecated
25+
26+
- Nothing.
27+
28+
### Removed
29+
30+
- Nothing.
31+
32+
### Fixed
33+
34+
- Nothing.
35+
536
## 1.2.1 - 2015-12-15
637

738
### Added

src/Response/SapiEmitter.php

Lines changed: 6 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
class SapiEmitter implements EmitterInterface
1616
{
17+
use SapiEmitterTrait;
18+
1719
/**
1820
* Emits a response for a PHP SAPI environment.
1921
*
@@ -29,96 +31,21 @@ public function emit(ResponseInterface $response, $maxBufferLevel = null)
2931
throw new RuntimeException('Unable to emit response; headers already sent');
3032
}
3133

32-
if (! $response->hasHeader('Content-Length')) {
33-
// PSR-7 indicates int OR null for the stream size; for null values,
34-
// we will not auto-inject the Content-Length.
35-
if (null !== $response->getBody()->getSize()) {
36-
$response = $response->withHeader('Content-Length', (string) $response->getBody()->getSize());
37-
}
38-
}
34+
$response = $this->injectContentLength($response);
3935

4036
$this->emitStatusLine($response);
4137
$this->emitHeaders($response);
42-
$this->emitBody($response, $maxBufferLevel);
43-
}
44-
45-
/**
46-
* Emit the status line.
47-
*
48-
* Emits the status line using the protocol version and status code from
49-
* the response; if a reason phrase is availble, it, too, is emitted.
50-
*
51-
* @param ResponseInterface $response
52-
*/
53-
private function emitStatusLine(ResponseInterface $response)
54-
{
55-
$reasonPhrase = $response->getReasonPhrase();
56-
header(sprintf(
57-
'HTTP/%s %d%s',
58-
$response->getProtocolVersion(),
59-
$response->getStatusCode(),
60-
($reasonPhrase ? ' ' . $reasonPhrase : '')
61-
));
62-
}
63-
64-
/**
65-
* Emit response headers.
66-
*
67-
* Loops through each header, emitting each; if the header value
68-
* is an array with multiple values, ensures that each is sent
69-
* in such a way as to create aggregate headers (instead of replace
70-
* the previous).
71-
*
72-
* @param ResponseInterface $response
73-
*/
74-
private function emitHeaders(ResponseInterface $response)
75-
{
76-
foreach ($response->getHeaders() as $header => $values) {
77-
$name = $this->filterHeader($header);
78-
$first = true;
79-
foreach ($values as $value) {
80-
header(sprintf(
81-
'%s: %s',
82-
$name,
83-
$value
84-
), $first);
85-
$first = false;
86-
}
87-
}
38+
$this->flush($maxBufferLevel);
39+
$this->emitBody($response);
8840
}
8941

9042
/**
9143
* Emit the message body.
9244
*
93-
* Loops through the output buffer, flushing each, before emitting
94-
* the response body using `echo()`.
95-
*
9645
* @param ResponseInterface $response
97-
* @param int $maxBufferLevel Flush up to this buffer level.
9846
*/
99-
private function emitBody(ResponseInterface $response, $maxBufferLevel)
47+
private function emitBody(ResponseInterface $response)
10048
{
101-
if (null === $maxBufferLevel) {
102-
$maxBufferLevel = ob_get_level();
103-
}
104-
105-
while (ob_get_level() > $maxBufferLevel) {
106-
ob_end_flush();
107-
}
108-
10949
echo $response->getBody();
11050
}
111-
112-
/**
113-
* Filter a header name to wordcase
114-
*
115-
* @param string $header
116-
* @return string
117-
*/
118-
private function filterHeader($header)
119-
{
120-
$filtered = str_replace('-', ' ', $header);
121-
$filtered = ucwords($filtered);
122-
return str_replace(' ', '-', $filtered);
123-
}
12451
}

src/Response/SapiEmitterTrait.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 Psr\Http\Message\ResponseInterface;
13+
use RuntimeException;
14+
15+
trait SapiEmitterTrait
16+
{
17+
/**
18+
* Inject the Content-Length header if is not already present.
19+
*
20+
* @param ResponseInterface $response
21+
* @return ResponseInterface
22+
*/
23+
private function injectContentLength(ResponseInterface $response)
24+
{
25+
if (! $response->hasHeader('Content-Length')) {
26+
// PSR-7 indicates int OR null for the stream size; for null values,
27+
// we will not auto-inject the Content-Length.
28+
if (null !== $response->getBody()->getSize()) {
29+
return $response->withHeader('Content-Length', (string) $response->getBody()->getSize());
30+
}
31+
}
32+
33+
return $response;
34+
}
35+
36+
/**
37+
* Emit the status line.
38+
*
39+
* Emits the status line using the protocol version and status code from
40+
* the response; if a reason phrase is availble, it, too, is emitted.
41+
*
42+
* @param ResponseInterface $response
43+
*/
44+
private function emitStatusLine(ResponseInterface $response)
45+
{
46+
$reasonPhrase = $response->getReasonPhrase();
47+
header(sprintf(
48+
'HTTP/%s %d%s',
49+
$response->getProtocolVersion(),
50+
$response->getStatusCode(),
51+
($reasonPhrase ? ' ' . $reasonPhrase : '')
52+
));
53+
}
54+
55+
/**
56+
* Emit response headers.
57+
*
58+
* Loops through each header, emitting each; if the header value
59+
* is an array with multiple values, ensures that each is sent
60+
* in such a way as to create aggregate headers (instead of replace
61+
* the previous).
62+
*
63+
* @param ResponseInterface $response
64+
*/
65+
private function emitHeaders(ResponseInterface $response)
66+
{
67+
foreach ($response->getHeaders() as $header => $values) {
68+
$name = $this->filterHeader($header);
69+
$first = true;
70+
foreach ($values as $value) {
71+
header(sprintf(
72+
'%s: %s',
73+
$name,
74+
$value
75+
), $first);
76+
$first = false;
77+
}
78+
}
79+
}
80+
81+
/**
82+
* Loops through the output buffer, flushing each, before emitting
83+
* the response.
84+
*
85+
* @param int|null $maxBufferLevel Flush up to this buffer level.
86+
*/
87+
private function flush($maxBufferLevel = null)
88+
{
89+
if (null === $maxBufferLevel) {
90+
$maxBufferLevel = ob_get_level();
91+
}
92+
93+
while (ob_get_level() > $maxBufferLevel) {
94+
ob_end_flush();
95+
}
96+
}
97+
98+
/**
99+
* Filter a header name to wordcase
100+
*
101+
* @param string $header
102+
* @return string
103+
*/
104+
private function filterHeader($header)
105+
{
106+
$filtered = str_replace('-', ' ', $header);
107+
$filtered = ucwords($filtered);
108+
return str_replace(' ', '-', $filtered);
109+
}
110+
}

src/Response/SapiStreamEmitter.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 Oscar Otero (http://oscarotero.com) / 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 Psr\Http\Message\ResponseInterface;
13+
use RuntimeException;
14+
15+
class SapiStreamEmitter implements EmitterInterface
16+
{
17+
use SapiEmitterTrait;
18+
19+
/**
20+
* Emits a response for a PHP SAPI environment.
21+
*
22+
* Emits the status line and headers via the header() function, and the
23+
* body content via the output buffer.
24+
*
25+
* @param ResponseInterface $response
26+
* @param int $maxBufferLength Maximum output buffering size for each iteration
27+
*/
28+
public function emit(ResponseInterface $response, $maxBufferLength = 8192)
29+
{
30+
if (headers_sent()) {
31+
throw new RuntimeException('Unable to emit response; headers already sent');
32+
}
33+
34+
$response = $this->injectContentLength($response);
35+
36+
$this->emitStatusLine($response);
37+
$this->emitHeaders($response);
38+
$this->flush();
39+
40+
$range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
41+
42+
if (is_array($range)) {
43+
$this->emitBodyRange($range, $response, $maxBufferLength);
44+
return;
45+
}
46+
47+
$this->emitBody($response, $maxBufferLength);
48+
}
49+
50+
/**
51+
* Emit the message body.
52+
*
53+
* @param ResponseInterface $response
54+
* @param int $maxBufferLength
55+
*/
56+
private function emitBody(ResponseInterface $response, $maxBufferLength)
57+
{
58+
$body = $response->getBody();
59+
$body->rewind();
60+
61+
while (! $body->eof()) {
62+
echo $body->read($maxBufferLength);
63+
}
64+
}
65+
66+
/**
67+
* Emit a range of the message body.
68+
*
69+
* @param array $range
70+
* @param ResponseInterface $response
71+
* @param int $maxBufferLength
72+
*/
73+
private function emitBodyRange(array $range, ResponseInterface $response, $maxBufferLength)
74+
{
75+
list($unit, $first, $last, $lenght) = $range;
76+
77+
++$last; //zero-based position
78+
$body = $response->getBody();
79+
$body->seek($first);
80+
$pos = $first;
81+
82+
while (! $body->eof() && $pos < $last) {
83+
if (($pos + $maxBufferLength) > $last) {
84+
echo $body->read($last - $pos);
85+
break;
86+
}
87+
88+
echo $body->read($maxBufferLength);
89+
$pos = $body->tell();
90+
}
91+
}
92+
93+
/**
94+
* Parse content-range header
95+
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
96+
*
97+
* @param string $header
98+
* @return false|array [unit, first, last, length]; returns false if no
99+
* content range or an invalid content range is provided
100+
*/
101+
private function parseContentRange($header)
102+
{
103+
if (preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
104+
return [
105+
$matches['unit'],
106+
(int) $matches['first'],
107+
(int) $matches['last'],
108+
$matches['length'] === '*' ? '*' : (int) $matches['length'],
109+
];
110+
}
111+
return false;
112+
}
113+
}

0 commit comments

Comments
 (0)