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

Commit ba5b16e

Browse files
committed
Removes output buffer awareness from Server, emitters
By removing a call to `ob_start()` within `Server::listen()`, we can solve the issue of detecting when we have both a response and content in the output buffer; in most cases, we will have already sent headers, which will cause an exception to be raised; we can also check the _current_ output buffer and, if non-empty, raise an exception. This means we can: - Remove the `SapiEmitterTrait::flush()` implementation, and all calls to it. - Remove the `$maxBufferLevel` argument to each emitter. - Remove tests regarding interactions of emitters with the output buffer. This is a backwards-incompatible change. However, it fixes a rather tricky problem that occurs currently when mixing buffered output and response instances.
1 parent 5f0c491 commit ba5b16e

File tree

8 files changed

+30
-117
lines changed

8 files changed

+30
-117
lines changed

src/Response/SapiEmitter.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,15 @@ class SapiEmitter implements EmitterInterface
2323
* body content via the output buffer.
2424
*
2525
* @param ResponseInterface $response
26-
* @param null|int $maxBufferLevel Maximum output buffering level to unwrap.
2726
*/
28-
public function emit(ResponseInterface $response, $maxBufferLevel = null)
27+
public function emit(ResponseInterface $response)
2928
{
30-
if (headers_sent()) {
31-
throw new RuntimeException('Unable to emit response; headers already sent');
32-
}
29+
$this->checkForPreviousOutput();
3330

3431
$response = $this->injectContentLength($response);
3532

3633
$this->emitStatusLine($response);
3734
$this->emitHeaders($response);
38-
$this->flush($maxBufferLevel);
3935
$this->emitBody($response);
4036
}
4137

src/Response/SapiEmitterTrait.php

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,30 @@
1010
namespace Zend\Diactoros\Response;
1111

1212
use Psr\Http\Message\ResponseInterface;
13+
use RuntimeException;
1314

1415
trait SapiEmitterTrait
1516
{
17+
/**
18+
* Checks to see if content has previously been sent.
19+
*
20+
* If either headers have been sent, or the current output buffer contains
21+
* content, raises an exception.
22+
*
23+
* @throws RuntimeException if headers have already been sent.
24+
* @throws RuntimeException if the current output buffer is not empty.
25+
*/
26+
private function checkForPreviousOutput()
27+
{
28+
if (headers_sent()) {
29+
throw new RuntimeException('Unable to emit response; headers already sent');
30+
}
31+
$bufferContents = ob_get_contents();
32+
if (! empty($bufferContents)) {
33+
throw new RuntimeException('Output has been emitted previously; cannot emit response: ' . $bufferContents);
34+
}
35+
}
36+
1637
/**
1738
* Inject the Content-Length header if is not already present.
1839
*
@@ -77,23 +98,6 @@ private function emitHeaders(ResponseInterface $response)
7798
}
7899
}
79100

80-
/**
81-
* Loops through the output buffer, flushing each, before emitting
82-
* the response.
83-
*
84-
* @param int|null $maxBufferLevel Flush up to this buffer level.
85-
*/
86-
private function flush($maxBufferLevel = null)
87-
{
88-
if (null === $maxBufferLevel) {
89-
$maxBufferLevel = ob_get_level();
90-
}
91-
92-
while (ob_get_level() > $maxBufferLevel) {
93-
ob_end_flush();
94-
}
95-
}
96-
97101
/**
98102
* Filter a header name to wordcase
99103
*

src/Response/SapiStreamEmitter.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,16 @@ class SapiStreamEmitter implements EmitterInterface
2424
* body content via the output buffer.
2525
*
2626
* @param ResponseInterface $response
27-
* @param null|int $maxBufferLevel Maximum output buffering level to unwrap.
2827
* @param int $maxBufferLength Maximum output buffering size for each iteration
2928
*/
30-
public function emit(ResponseInterface $response, $maxBufferLevel = null, $maxBufferLength = 8192)
29+
public function emit(ResponseInterface $response, $maxBufferLength = 8192)
3130
{
32-
if (headers_sent()) {
33-
throw new RuntimeException('Unable to emit response; headers already sent');
34-
}
31+
$this->checkForPreviousOutput();
3532

3633
$response = $this->injectContentLength($response);
3734

3835
$this->emitStatusLine($response);
3936
$this->emitHeaders($response);
40-
$this->flush($maxBufferLevel);
4137

4238
$range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
4339

src/Server.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,24 +150,18 @@ public static function createServerFromRequest(
150150
* If provided a $finalHandler, that callable will be used for
151151
* incomplete requests.
152152
*
153-
* Output buffering is enabled prior to invoking the attached
154-
* callback; any output buffered will be sent prior to any
155-
* response body content.
156-
*
157153
* @param null|callable $finalHandler
158154
*/
159155
public function listen(callable $finalHandler = null)
160156
{
161157
$callback = $this->callback;
162158

163-
ob_start();
164-
$bufferLevel = ob_get_level();
165-
166159
$response = $callback($this->request, $this->response, $finalHandler);
167160
if (! $response instanceof ResponseInterface) {
168161
$response = $this->response;
169162
}
170-
$this->getEmitter()->emit($response, $bufferLevel);
163+
164+
$this->getEmitter()->emit($response);
171165
}
172166

173167
/**

test/Response/SapiEmitterTest.php

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,4 @@
1313

1414
class SapiEmitterTest extends AbstractEmitterTest
1515
{
16-
public function testEmitsBufferLevel()
17-
{
18-
ob_start();
19-
echo "level" . ob_get_level() . " "; // 2
20-
ob_start();
21-
echo "level" . ob_get_level() . " "; // 3
22-
ob_start();
23-
echo "level" . ob_get_level() . " "; // 4
24-
$response = (new Response())
25-
->withStatus(200)
26-
->withAddedHeader('Content-Type', 'text/plain');
27-
$response->getBody()->write('Content!');
28-
ob_start();
29-
$this->emitter->emit($response);
30-
$this->assertEquals('Content!', ob_get_contents());
31-
ob_end_clean();
32-
$this->assertEquals('level4 ', ob_get_contents(), 'current buffer level string must remains after emit');
33-
ob_end_clean();
34-
$this->emitter->emit($response, 2);
35-
$this->assertEquals('level2 level3 Content!', ob_get_contents(), 'must buffer until specified level');
36-
ob_end_clean();
37-
}
3816
}

test/Response/SapiStreamEmitterTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function ($bufferLength) use (& $peakBufferLength) {
210210
->withBody($stream->reveal());
211211

212212
ob_start();
213-
$this->emitter->emit($response, null, $maxBufferLength);
213+
$this->emitter->emit($response, $maxBufferLength);
214214
$emittedContents = ob_get_clean();
215215

216216
if ($seekable) {
@@ -351,7 +351,7 @@ function ($bufferLength) use (& $peakBufferLength) {
351351
->withBody($stream->reveal());
352352

353353
ob_start();
354-
$this->emitter->emit($response, null, $maxBufferLength);
354+
$this->emitter->emit($response, $maxBufferLength);
355355
$emittedContents = ob_get_clean();
356356

357357
$stream->rewind()->shouldNotBeCalled();
@@ -497,7 +497,7 @@ function () use (& $closureTrackMemoryUsage) {
497497

498498
gc_disable();
499499

500-
$this->emitter->emit($response, null, $maxBufferLength);
500+
$this->emitter->emit($response, $maxBufferLength);
501501

502502
ob_end_flush();
503503

test/ServerIntegrationTest.php

Lines changed: 0 additions & 49 deletions
This file was deleted.

test/ServerTest.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ public function testEmitterSetter()
109109

110110
$this->expectOutputString('');
111111
$server->listen();
112-
ob_end_flush();
113112
}
114113

115114
public function testCreateServerWillCreateDefaultInstancesForRequestAndResponse()
@@ -153,7 +152,6 @@ public function testListenInvokesCallbackAndSendsResponse()
153152

154153
$this->expectOutputString('FOOBAR');
155154
$server->listen();
156-
ob_end_flush();
157155

158156
$this->assertContains('HTTP/1.1 200 OK', HeaderStack::stack());
159157
$this->assertContains('Content-Type: text/plain', HeaderStack::stack());
@@ -179,7 +177,6 @@ public function testListenEmitsStatusHeaderWithoutReasonPhraseIfNoReasonPhrase()
179177

180178
$this->expectOutputString('FOOBAR');
181179
$server->listen();
182-
ob_end_flush();
183180

184181
$this->assertContains('HTTP/1.1 299', HeaderStack::stack());
185182
$this->assertContains('Content-Type: text/plain', HeaderStack::stack());
@@ -204,7 +201,6 @@ public function testEnsurePercentCharactersDoNotResultInOutputError()
204201

205202
$this->expectOutputString('100%');
206203
$server->listen();
207-
ob_end_flush();
208204

209205
$this->assertContains('HTTP/1.1 200 OK', HeaderStack::stack());
210206
$this->assertContains('Content-Type: text/plain', HeaderStack::stack());
@@ -233,7 +229,6 @@ public function testEmitsHeadersWithMultipleValuesMultipleTimes()
233229
$server = Server::createServer($callback, $server, [], [], [], []);
234230

235231
$server->listen();
236-
ob_end_flush();
237232

238233
$this->assertContains('HTTP/1.1 200 OK', HeaderStack::stack());
239234
$this->assertContains('Content-Type: text/plain', HeaderStack::stack());
@@ -311,7 +306,6 @@ public function testListenPassesCallableArgumentToCallback()
311306
$this->response
312307
);
313308
$server->listen($final);
314-
ob_end_flush();
315309
$this->assertTrue($invoked);
316310
}
317311
}

0 commit comments

Comments
 (0)