Skip to content

Commit

Permalink
Fix incompatibility in response->body(callable) & PSR7 stack
Browse files Browse the repository at this point in the history
The old response->body() interface doesn't involve returning a string,
so it isn't going to work with CallbackStream. Furthermore there is an
incompatibility with CallbackStream and SapiStreamEmitter. These changes
work around both of those issues by creating a more permissive
CallbackStream and only using SapiStreamEmitter on seekable streams.

Refs #9408
  • Loading branch information
markstory committed Sep 5, 2016
1 parent d8ff0b7 commit 38bc7ce
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 1 deletion.
46 changes: 46 additions & 0 deletions src/Http/CallbackStream.php
@@ -0,0 +1,46 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.3.4
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Http;

use Zend\Diactoros\CallbackStream as BaseCallbackStream;

/**
* Implementation of PSR HTTP streams.
*
* This differs from Zend\Diactoros\Callback stream in that
* it allows the use of `echo` inside the callback, and gracefully
* handles the callback not returning a string.
*
* Ideally we can amend/update diactoros, but we need to figure
* that out with the diactoros project. Until then we'll use this shim
* to provide backwards compatiblity with existing CakePHP apps.
*
* @internal
*/
class CallbackStream extends BaseCallbackStream
{
/**
* {@inheritdoc}
*/
public function getContents()
{
$callback = $this->detach();
$result = $callback ? $callback() : '';
if (!is_string($result)) {
return '';
}
return $result;
}
}
2 changes: 1 addition & 1 deletion src/Http/ResponseTransformer.php
Expand Up @@ -14,9 +14,9 @@
*/
namespace Cake\Http;

use Cake\Http\CallbackStream;
use Cake\Network\Response as CakeResponse;
use Psr\Http\Message\ResponseInterface as PsrResponse;
use Zend\Diactoros\CallbackStream;
use Zend\Diactoros\Response as DiactorosResponse;
use Zend\Diactoros\Stream;

Expand Down
5 changes: 5 additions & 0 deletions src/Http/Server.php
Expand Up @@ -20,6 +20,7 @@
use RuntimeException;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\EmitterInterface;
use Zend\Diactoros\Response\SapiEmitter;
use Zend\Diactoros\Response\SapiStreamEmitter;

/**
Expand Down Expand Up @@ -101,6 +102,10 @@ public function run(ServerRequestInterface $request = null, ResponseInterface $r
*/
public function emit(ResponseInterface $response, EmitterInterface $emitter = null)
{
$stream = $response->getBody();
if (!$emitter && !$stream->isSeekable()) {
$emitter = new SapiEmitter();
}
if (!$emitter) {
$emitter = new SapiStreamEmitter();
}
Expand Down
24 changes: 24 additions & 0 deletions tests/TestCase/Http/ServerTest.php
Expand Up @@ -14,13 +14,16 @@
*/
namespace Cake\Test\TestCase;

use Cake\Http\CallbackStream;
use Cake\Http\Server;
use Cake\TestSuite\TestCase;
use TestApp\Http\BadResponseApplication;
use TestApp\Http\InvalidMiddlewareApplication;
use TestApp\Http\MiddlewareApplication;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequestFactory;
require __DIR__ . '/server_mocks.php';


/**
* Server test case
Expand All @@ -37,6 +40,7 @@ public function setUp()
parent::setUp();
$this->server = $_SERVER;
$this->config = dirname(dirname(__DIR__));
$GLOBALS['mockedHeaders'] = [];
}

/**
Expand Down Expand Up @@ -172,6 +176,26 @@ public function testEmit()
$server->emit($server->run(null, $response), $emitter);
}

/**
* Test that emit invokes the appropriate methods on the emitter.
*
* @return void
*/
public function testEmitCallbackStream()
{
$response = new Response('php://memory', 200, ['x-testing' => 'source header']);
$response = $response->withBody(new CallbackStream(function () {
echo 'body content';
}));

$app = new MiddlewareApplication($this->config);
$server = new Server($app);
ob_start();
$server->emit($response);
$result = ob_get_clean();
$this->assertEquals('body content', $result);
}

/**
* Ensure that the Server.buildMiddleware event is fired.
*
Expand Down
12 changes: 12 additions & 0 deletions tests/TestCase/Http/server_mocks.php
@@ -0,0 +1,12 @@
<?php
namespace Zend\Diactoros\Response;

function headers_sent()
{
return false;
}

function header($header)
{
$GLOBALS['mockedHeaders'][] = $header;
}

0 comments on commit 38bc7ce

Please sign in to comment.