Browse Source

Fix incompatibility in response->body(callable) & PSR7 stack

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
Mark Story 9 years ago
parent
commit
38bc7ce958

+ 46 - 0
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;
+    }
+}

+ 1 - 1
src/Http/ResponseTransformer.php

@@ -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;
 

+ 5 - 0
src/Http/Server.php

@@ -20,6 +20,7 @@ use Psr\Http\Message\ServerRequestInterface;
 use RuntimeException;
 use Zend\Diactoros\Response;
 use Zend\Diactoros\Response\EmitterInterface;
+use Zend\Diactoros\Response\SapiEmitter;
 use Zend\Diactoros\Response\SapiStreamEmitter;
 
 /**
@@ -101,6 +102,10 @@ class Server
      */
     public function emit(ResponseInterface $response, EmitterInterface $emitter = null)
     {
+        $stream = $response->getBody();
+        if (!$emitter && !$stream->isSeekable()) {
+            $emitter = new SapiEmitter();
+        }
         if (!$emitter) {
             $emitter = new SapiStreamEmitter();
         }

+ 24 - 0
tests/TestCase/Http/ServerTest.php

@@ -14,6 +14,7 @@
  */
 namespace Cake\Test\TestCase;
 
+use Cake\Http\CallbackStream;
 use Cake\Http\Server;
 use Cake\TestSuite\TestCase;
 use TestApp\Http\BadResponseApplication;
@@ -21,6 +22,8 @@ use TestApp\Http\InvalidMiddlewareApplication;
 use TestApp\Http\MiddlewareApplication;
 use Zend\Diactoros\Response;
 use Zend\Diactoros\ServerRequestFactory;
+require __DIR__ . '/server_mocks.php';
+
 
 /**
  * Server test case
@@ -37,6 +40,7 @@ class ServerTest extends TestCase
         parent::setUp();
         $this->server = $_SERVER;
         $this->config = dirname(dirname(__DIR__));
+        $GLOBALS['mockedHeaders'] = [];
     }
 
     /**
@@ -173,6 +177,26 @@ class ServerTest extends TestCase
     }
 
     /**
+     * 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.
      *
      * @return void

+ 12 - 0
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;
+}