Skip to content
Permalink
Browse files

Add the ResponseTransformer

This class handles bi-directional conversion between CakePHP and PSR7
responses. Being able to convert both ways is necessary to allow
converting from the PSR7 middleware into a Cake response that is passed
into the controller. After the controller is complete, the response
needs to be converted back into a PSR7 response to be emitted by the
server.
  • Loading branch information...
markstory committed Apr 10, 2016
1 parent a6f3ee8 commit ac7a91266e5587ee0d12c3d23ad44de033153846
Showing with 367 additions and 0 deletions.
  1. +128 −0 src/Http/ResponseTransformer.php
  2. +239 −0 tests/TestCase/Http/ResponseTransformerTest.php
@@ -0,0 +1,128 @@
<?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.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Http;
use Cake\Network\Response as CakeResponse;
use Psr\Http\Message\ResponseInterface as PsrResponse;
use Zend\Diactoros\Response as DiactorosResponse;
use Zend\Diactoros\CallbackStream;
use Zend\Diactoros\Stream;
/**
* This class converts PSR7 responses into CakePHP ones and back again.
*
* By bridging the CakePHP and PSR7 responses together, applications
* can be embedded as PSR7 middleware in a fully compatible way.
*
* @internal
*/
class ResponseTransformer
{
/**
* Convert a PSR7 Response into a CakePHP one.
*
* @param PsrResponse $response The response to convert.
* @return CakeResponse The equivalent CakePHP response
*/
public static function toCake(PsrResponse $response)
{
$data = [
'status' => $response->getStatusCode(),
'body' => static::getBody($response),
];
$cake = new CakeResponse($data);
$cake->header(static::collapseHeaders($response));
return $cake;
}
/**
* Get the response body from a PSR7 Response.
*
* @param PsrResponse $response The response to convert.
* @return string The response body.
*/
protected static function getBody(PsrResponse $response)
{
$stream = $response->getBody();
if ($stream->getSize() === 0) {
return '';
}
$stream->rewind();
return $stream->getContents();
}
/**
* Convert a PSR7 Response headers into a flat array
*
* @param PsrResponse $response The response to convert.
* @return CakeResponse The equivalent CakePHP response
*/
protected static function collapseHeaders(PsrResponse $response)
{
$out = [];
foreach ($response->getHeaders() as $name => $value) {
if (count($value) === 1) {
$out[$name] = $value[0];
} else {
$out[$name] = $value;
}
}
return $out;
}
/**
* Convert a CakePHP response into a PSR7 one.
*
* @param CakeResponse $response The CakePHP response to convert
* @return PsrResponse $response The equivalent PSR7 response.
*/
public static function toPsr(CakeResponse $response)
{
$status = $response->statusCode();
$headers = $response->header();
if (!isset($headers['Content-Type'])) {
$headers['Content-Type'] = $response->type();
}
$stream = static::getStream($response);
return new DiactorosResponse($stream, $status, $headers);
}
/**
* Get the stream for the new response.
*
* @param \Cake\Network\Response $response The cake response to extract the body from.
* @return Psr\Http\Message\StreamInterface The stream.
*/
protected static function getStream($response)
{
$stream = 'php://memory';
$body = $response->body();
if (is_string($body)) {
$stream = new Stream('php://memory', 'wb');
$stream->write($body);
return $stream;
}
if (is_callable($body)) {
$stream = new CallbackStream($body);
return $stream;
}
$file = $response->getFile();
if ($file) {
$stream = new Stream($file->path, 'rb');
return $stream;
}
return $stream;
}
}
@@ -0,0 +1,239 @@
<?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.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Test\TestCase\Http;
use Cake\Http\ResponseTransformer;
use Cake\TestSuite\TestCase;
use Zend\Diactoros\Response as PsrResponse;
use Cake\Network\Response as CakeResponse;
/**
* Test case for the response transformer.
*/
class ResponseTransformerTest extends TestCase
{
/**
* server used in testing
*
* @var array
*/
protected $server;
/**
* setup
*
* @return void
*/
public function setUp()
{
parent::setUp();
$this->server = $_SERVER;
}
/**
* teardown
*
* @return void
*/
public function tearDown()
{
parent::tearDown();
$_SERVER = $this->server;
}
/**
* Test conversion getting the right class type.
*
* @return void
*/
public function testToCakeCorrectType()
{
$psr = new PsrResponse('php://memory', 401, []);
$result = ResponseTransformer::toCake($psr);
$this->assertInstanceOf('Cake\Network\Response', $result);
}
/**
* Test conversion getting the status code
*
* @return void
*/
public function testToCakeStatusCode()
{
$psr = new PsrResponse('php://memory', 401, []);
$result = ResponseTransformer::toCake($psr);
$this->assertSame(401, $result->statusCode());
$psr = new PsrResponse('php://memory', 200, []);
$result = ResponseTransformer::toCake($psr);
$this->assertSame(200, $result->statusCode());
}
/**
* Test conversion getting headers.
*
* @return void
*/
public function testToCakeHeaders()
{
$psr = new PsrResponse('php://memory', 200, ['X-testing' => 'value']);
$result = ResponseTransformer::toCake($psr);
$this->assertSame(['X-testing' => 'value'], $result->header());
}
/**
* Test conversion getting headers.
*
* @return void
*/
public function testToCakeHeaderMultiple()
{
$psr = new PsrResponse('php://memory', 200, ['X-testing' => ['value', 'value2']]);
$result = ResponseTransformer::toCake($psr);
$this->assertSame(['X-testing' => ['value', 'value2']], $result->header());
}
/**
* Test conversion getting the body.
*
* @return void
*/
public function testToCakeBody()
{
$psr = new PsrResponse('php://memory', 200, ['X-testing' => ['value', 'value2']]);
$psr->getBody()->write('A message for you');
$result = ResponseTransformer::toCake($psr);
$this->assertSame('A message for you', $result->body());
}
/**
* Test conversion setting the status code.
*
* @return void
*/
public function testToPsrStatusCode()
{
$cake = new CakeResponse(['status' => 403]);
$result = ResponseTransformer::toPsr($cake);
$this->assertSame(403, $result->getStatusCode());
}
/**
* Test conversion setting the content-type.
*
* @return void
*/
public function testToPsrContentType()
{
$cake = new CakeResponse();
$cake->type('js');
$result = ResponseTransformer::toPsr($cake);
$this->assertSame('application/javascript', $result->getHeaderLine('Content-Type'));
}
/**
* Test conversion setting headers.
*
* @return void
*/
public function testToPsrHeaders()
{
$cake = new CakeResponse(['status' => 403]);
$cake->header([
'X-testing' => ['one', 'two'],
'Location' => 'http://example.com/testing'
]);
$result = ResponseTransformer::toPsr($cake);
$expected = [
'X-testing' => ['one', 'two'],
'Location' => ['http://example.com/testing'],
'Content-Type' => ['text/html'],
];
$this->assertSame($expected, $result->getHeaders());
}
/**
* Test conversion setting a string body.
*
* @return void
*/
public function testToPsrBodyString()
{
$cake = new CakeResponse(['status' => 403, 'body' => 'A response for you']);
$result = ResponseTransformer::toPsr($cake);
$this->assertSame($cake->body(), '' . $result->getBody());
}
/**
* Test conversion setting a callable body.
*
* @return void
*/
public function testToPsrBodyCallable()
{
$cake = new CakeResponse(['status' => 200]);
$cake->body(function () {
return 'callback response';
});
$result = ResponseTransformer::toPsr($cake);
$this->assertSame('callback response', '' . $result->getBody());
}
/**
* Test conversion setting a file body.
*
* @return void
*/
public function testToPsrBodyFileResponse()
{
$cake = $this->getMock('Cake\Network\Response', ['_clearBuffer']);
$cake->file(__FILE__, ['name' => 'some-file.php', 'download' => true]);
$result = ResponseTransformer::toPsr($cake);
$this->assertEquals(
'attachment; filename="some-file.php"',
$result->getHeaderLine('Content-Disposition')
);
$this->assertEquals(
'binary',
$result->getHeaderLine('Content-Transfer-Encoding')
);
$this->assertEquals(
'bytes',
$result->getHeaderLine('Accept-Ranges')
);
$this->assertContains('<?php', '' . $result->getBody());
}
/**
* Test conversion setting a file body with range headers
*
* @return void
*/
public function testToPsrBodyFileResponseFileRange()
{
$_SERVER['HTTP_RANGE'] = 'bytes=10-20';
$cake = $this->getMock('Cake\Network\Response', ['_clearBuffer']);
$path = TEST_APP . 'webroot/css/cake.generic.css';
$cake->file($path, ['name' => 'test-asset.css', 'download' => true]);
$result = ResponseTransformer::toPsr($cake);
$this->assertEquals(
'bytes 10-20/15640',
$result->getHeaderLine('Content-Range'),
'Content-Range header missing'
);
}
}

0 comments on commit ac7a912

Please sign in to comment.
You can’t perform that action at this time.