Made Engelsystem\Http\Request PSR-7 RequestInterface compatible

This commit is contained in:
Igor Scheller 2018-08-16 18:13:53 +02:00
parent 18fd73a899
commit 5427ee385d
7 changed files with 313 additions and 19 deletions

View File

@ -4,7 +4,6 @@ namespace Engelsystem\Http;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Zend\Diactoros\Stream; use Zend\Diactoros\Stream;
/** /**
@ -41,7 +40,12 @@ trait MessageTrait
public function withProtocolVersion($version) public function withProtocolVersion($version)
{ {
$new = clone $this; $new = clone $this;
$new->setProtocolVersion($version); if (method_exists($new, 'setProtocolVersion')) {
$new->setProtocolVersion($version);
} else {
$new->server->set('SERVER_PROTOCOL', $version);
}
return $new; return $new;
} }
@ -72,7 +76,11 @@ trait MessageTrait
*/ */
public function getHeaders() public function getHeaders()
{ {
return $this->headers->allPreserveCase(); if (method_exists($this->headers, 'allPreserveCase')) {
return $this->headers->allPreserveCase();
}
return $this->headers->all();
} }
/** /**
@ -228,7 +236,12 @@ trait MessageTrait
public function withBody(StreamInterface $body) public function withBody(StreamInterface $body)
{ {
$new = clone $this; $new = clone $this;
$new->setContent($body);
if (method_exists($new, 'setContent')) {
$new->setContent($body);
} else {
$new->content = $body;
}
return $new; return $new;
} }

View File

@ -2,10 +2,15 @@
namespace Engelsystem\Http; namespace Engelsystem\Http;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Zend\Diactoros\Uri;
class Request extends SymfonyRequest class Request extends SymfonyRequest implements RequestInterface
{ {
use MessageTrait;
/** /**
* Get POST input * Get POST input
* *
@ -64,4 +69,128 @@ class Request extends SymfonyRequest
{ {
return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/'); return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
} }
/**
* Retrieves the message's request target.
*
*
* Retrieves the message's request-target either as it will appear (for
* clients), as it appeared at request (for servers), or as it was
* specified for the instance (see withRequestTarget()).
*
* In most cases, this will be the origin-form of the composed URI,
* unless a value was provided to the concrete implementation (see
* withRequestTarget() below).
*
* If no URI is available, and no request-target has been specifically
* provided, this method MUST return the string "/".
*
* @return string
*/
public function getRequestTarget()
{
$query = $this->getQueryString();
return '/' . $this->path() . (!empty($query) ? '?' . $query : '');
}
/**
* Return an instance with the specific request-target.
*
* If the request needs a non-origin-form request-target e.g., for
* specifying an absolute-form, authority-form, or asterisk-form
* this method may be used to create an instance with the specified
* request-target, verbatim.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* changed request target.
*
* @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
* request-target forms allowed in request messages)
* @param mixed $requestTarget
* @return static
*/
public function withRequestTarget($requestTarget)
{
return $this->create($requestTarget);
}
/**
* Return an instance with the provided HTTP method.
*
* While HTTP method names are typically all uppercase characters, HTTP
* method names are case-sensitive and thus implementations SHOULD NOT
* modify the given string.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* changed request method.
*
* @param string $method Case-sensitive method.
* @return static
* @throws \InvalidArgumentException for invalid HTTP methods.
*/
public function withMethod($method)
{
$new = clone $this;
$new->setMethod($method);
return $new;
}
/**
* Returns an instance with the provided URI.
*
* This method MUST update the Host header of the returned request by
* default if the URI contains a host component. If the URI does not
* contain a host component, any pre-existing Host header MUST be carried
* over to the returned request.
*
* You can opt-in to preserving the original state of the Host header by
* setting `$preserveHost` to `true`. When `$preserveHost` is set to
* `true`, this method interacts with the Host header in the following ways:
*
* - If the Host header is missing or empty, and the new URI contains
* a host component, this method MUST update the Host header in the returned
* request.
* - If the Host header is missing or empty, and the new URI does not contain a
* host component, this method MUST NOT update the Host header in the returned
* request.
* - If a Host header is present and non-empty, this method MUST NOT update
* the Host header in the returned request.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @param UriInterface $uri New request URI to use.
* @param bool $preserveHost Preserve the original state of the Host header.
* @return static
*/
public function withUri(UriInterface $uri, $preserveHost = false)
{
$new = $this->create($uri);
if ($preserveHost) {
$new->headers->set('HOST', $this->getHost());
}
return $new;
}
/**
* Retrieves the URI instance.
*
* This method MUST return a UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @return string|UriInterface Returns a UriInterface instance
* representing the URI of the request.
*/
public function getUri()
{
$uri = parent::getUri();
return new Uri($uri);
}
} }

View File

@ -0,0 +1,50 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Test\Unit\Http\Stub\MessageTraitRequestImplementation;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\Stream;
class MessageTraitRequestTest extends TestCase
{
/**
* @covers \Engelsystem\Http\MessageTrait::withProtocolVersion
*/
public function testWithProtocolVersion()
{
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withProtocolVersion('0.1');
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('0.1', $newMessage->getProtocolVersion());
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaders
*/
public function testGetHeaders()
{
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withHeader('lorem', 'ipsum');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['lorem' => ['ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withBody
*/
public function testWithBody()
{
/** @var Stream $stream */
$stream = new Stream('php://memory', 'wb+');
$stream->write('Test content');
$stream->rewind();
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withBody($stream);
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('Test content', $newMessage->getContent());
}
}

View File

@ -2,21 +2,21 @@
namespace Engelsystem\Test\Unit\Http; namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Test\Unit\Http\Stub\MessageTraitImplementation; use Engelsystem\Test\Unit\Http\Stub\MessageTraitResponseImplementation;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Zend\Diactoros\Stream; use Zend\Diactoros\Stream;
class MessageTraitTest extends TestCase class MessageTraitResponseTest extends TestCase
{ {
/** /**
* @covers \Engelsystem\Http\MessageTrait * @covers \Engelsystem\Http\MessageTrait
*/ */
public function testCreate() public function testCreate()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$this->assertInstanceOf(MessageInterface::class, $message); $this->assertInstanceOf(MessageInterface::class, $message);
$this->assertInstanceOf(SymfonyResponse::class, $message); $this->assertInstanceOf(SymfonyResponse::class, $message);
} }
@ -27,7 +27,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testGetProtocolVersion() public function testGetProtocolVersion()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withProtocolVersion('0.1'); $newMessage = $message->withProtocolVersion('0.1');
$this->assertNotEquals($message, $newMessage); $this->assertNotEquals($message, $newMessage);
$this->assertEquals('0.1', $newMessage->getProtocolVersion()); $this->assertEquals('0.1', $newMessage->getProtocolVersion());
@ -38,7 +38,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testGetHeaders() public function testGetHeaders()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('Foo', 'bar'); $newMessage = $message->withHeader('Foo', 'bar');
$this->assertNotEquals($message, $newMessage); $this->assertNotEquals($message, $newMessage);
@ -53,7 +53,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testHasHeader() public function testHasHeader()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$this->assertFalse($message->hasHeader('test')); $this->assertFalse($message->hasHeader('test'));
$newMessage = $message->withHeader('test', '12345'); $newMessage = $message->withHeader('test', '12345');
@ -66,7 +66,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testGetHeader() public function testGetHeader()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar'); $newMessage = $message->withHeader('foo', 'bar');
$this->assertEquals(['bar'], $newMessage->getHeader('Foo')); $this->assertEquals(['bar'], $newMessage->getHeader('Foo'));
@ -78,7 +78,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testGetHeaderLine() public function testGetHeaderLine()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', ['bar', 'bla']); $newMessage = $message->withHeader('foo', ['bar', 'bla']);
$this->assertEquals('', $newMessage->getHeaderLine('Lorem-Ipsum')); $this->assertEquals('', $newMessage->getHeaderLine('Lorem-Ipsum'));
@ -90,7 +90,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testWithHeader() public function testWithHeader()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar'); $newMessage = $message->withHeader('foo', 'bar');
$this->assertNotEquals($message, $newMessage); $this->assertNotEquals($message, $newMessage);
@ -105,7 +105,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testWithAddedHeader() public function testWithAddedHeader()
{ {
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar'); $newMessage = $message->withHeader('foo', 'bar');
$this->assertNotEquals($message, $newMessage); $this->assertNotEquals($message, $newMessage);
@ -120,7 +120,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testWithoutHeader() public function testWithoutHeader()
{ {
$message = (new MessageTraitImplementation())->withHeader('foo', 'bar'); $message = (new MessageTraitResponseImplementation())->withHeader('foo', 'bar');
$this->assertTrue($message->hasHeader('foo')); $this->assertTrue($message->hasHeader('foo'));
$newMessage = $message->withoutHeader('Foo'); $newMessage = $message->withoutHeader('Foo');
@ -133,7 +133,7 @@ class MessageTraitTest extends TestCase
*/ */
public function testGetBody() public function testGetBody()
{ {
$message = (new MessageTraitImplementation())->setContent('Foo bar!'); $message = (new MessageTraitResponseImplementation())->setContent('Foo bar!');
$body = $message->getBody(); $body = $message->getBody();
$this->assertInstanceOf(StreamInterface::class, $body); $this->assertInstanceOf(StreamInterface::class, $body);
@ -150,7 +150,7 @@ class MessageTraitTest extends TestCase
$stream->write('Test content'); $stream->write('Test content');
$stream->rewind(); $stream->rewind();
$message = new MessageTraitImplementation(); $message = new MessageTraitResponseImplementation();
$newMessage = $message->withBody($stream); $newMessage = $message->withBody($stream);
$this->assertNotEquals($message, $newMessage); $this->assertNotEquals($message, $newMessage);

View File

@ -5,6 +5,8 @@ namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject; use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class RequestTest extends TestCase class RequestTest extends TestCase
@ -16,6 +18,7 @@ class RequestTest extends TestCase
{ {
$response = new Request(); $response = new Request();
$this->assertInstanceOf(SymfonyRequest::class, $response); $this->assertInstanceOf(SymfonyRequest::class, $response);
$this->assertInstanceOf(RequestInterface::class, $response);
} }
/** /**
@ -106,4 +109,91 @@ class RequestTest extends TestCase
$this->assertEquals('http://foo.bar/bla/foo', $request->url()); $this->assertEquals('http://foo.bar/bla/foo', $request->url());
$this->assertEquals('https://lorem.ipsum/dolor/sit', $request->url()); $this->assertEquals('https://lorem.ipsum/dolor/sit', $request->url());
} }
/**
* @covers \Engelsystem\Http\Request::getRequestTarget
*/
public function testGetRequestTarget()
{
/** @var Request|MockObject $request */
$request = $this
->getMockBuilder(Request::class)
->setMethods(['getQueryString', 'path'])
->getMock();
$request->expects($this->exactly(2))
->method('getQueryString')
->willReturnOnConsecutiveCalls(null, 'foo=bar&lorem=ipsum');
$request->expects($this->exactly(2))
->method('path')
->willReturn('foo/bar');
$this->assertEquals('/foo/bar', $request->getRequestTarget());
$this->assertEquals('/foo/bar?foo=bar&lorem=ipsum', $request->getRequestTarget());
}
/**
* @covers \Engelsystem\Http\Request::withRequestTarget
*/
public function testWithRequestTarget()
{
$request = new Request();
foreach (
[
'*',
'/foo/bar',
'https://lorem.ipsum/test?lor=em'
] as $target
) {
$new = $request->withRequestTarget($target);
$this->assertNotEquals($request, $new);
}
}
/**
* @covers \Engelsystem\Http\Request::withMethod
*/
public function testWithMethod()
{
$request = new Request();
$new = $request->withMethod('PUT');
$this->assertNotEquals($request, $new);
$this->assertEquals('PUT', $new->getMethod());
}
/**
* @covers \Engelsystem\Http\Request::withUri
*/
public function testWithUri()
{
/** @var UriInterface|MockObject $uri */
$uri = $this->getMockForAbstractClass(UriInterface::class);
$uri->expects($this->atLeastOnce())
->method('__toString')
->willReturn('http://foo.bar/bla?foo=bar');
$request = Request::create('http://lor.em/');
$new = $request->withUri($uri);
$this->assertNotEquals($request, $new);
$this->assertEquals('http://foo.bar/bla?foo=bar', (string)$new->getUri());
$new = $request->withUri($uri, true);
$this->assertEquals('http://lor.em/bla?foo=bar', (string)$new->getUri());
}
/**
* @covers \Engelsystem\Http\Request::getUri
*/
public function testGetUri()
{
$request = Request::create('http://lor.em/test?bla=foo');
$uri = $request->getUri();
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertEquals('http://lor.em/test?bla=foo', (string)$uri);
}
} }

View File

@ -0,0 +1,12 @@
<?php
namespace Engelsystem\Test\Unit\Http\Stub;
use Engelsystem\Http\MessageTrait;
use Psr\Http\Message\MessageInterface;
use Symfony\Component\HttpFoundation\Request;
class MessageTraitRequestImplementation extends Request implements MessageInterface
{
use MessageTrait;
}

View File

@ -6,7 +6,7 @@ use Engelsystem\Http\MessageTrait;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class MessageTraitImplementation extends Response implements MessageInterface class MessageTraitResponseImplementation extends Response implements MessageInterface
{ {
use MessageTrait; use MessageTrait;
} }