From 3c088292050982505726f5136ff4d0f1a918b879 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Thu, 18 Jan 2018 19:01:34 +0100 Subject: Added Http\Response and Psr7{Request,Response} --- tests/Unit/Http/RequestTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'tests/Unit/Http/RequestTest.php') diff --git a/tests/Unit/Http/RequestTest.php b/tests/Unit/Http/RequestTest.php index a68f8b8f..f8444b84 100644 --- a/tests/Unit/Http/RequestTest.php +++ b/tests/Unit/Http/RequestTest.php @@ -5,9 +5,19 @@ namespace Engelsystem\Test\Unit\Http; use Engelsystem\Http\Request; use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; class RequestTest extends TestCase { + /** + * @covers \Engelsystem\Http\Request + */ + public function testCreate() + { + $response = new Request(); + $this->assertInstanceOf(SymfonyRequest::class, $response); + } + /** * @covers \Engelsystem\Http\Request::postData */ -- cgit v1.2.3-54-g00ecf From 5427ee385dc2ff117a1130eb606f78e708949c8f Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Thu, 16 Aug 2018 18:13:53 +0200 Subject: Made Engelsystem\Http\Request PSR-7 RequestInterface compatible --- src/Http/MessageTrait.php | 21 ++- src/Http/Request.php | 131 ++++++++++++++++- tests/Unit/Http/MessageTraitRequestTest.php | 50 +++++++ tests/Unit/Http/MessageTraitResponseTest.php | 159 +++++++++++++++++++++ tests/Unit/Http/MessageTraitTest.php | 159 --------------------- tests/Unit/Http/RequestTest.php | 90 ++++++++++++ .../Unit/Http/Stub/MessageTraitImplementation.php | 12 -- .../Stub/MessageTraitRequestImplementation.php | 12 ++ .../Stub/MessageTraitResponseImplementation.php | 12 ++ 9 files changed, 470 insertions(+), 176 deletions(-) create mode 100644 tests/Unit/Http/MessageTraitRequestTest.php create mode 100644 tests/Unit/Http/MessageTraitResponseTest.php delete mode 100644 tests/Unit/Http/MessageTraitTest.php delete mode 100644 tests/Unit/Http/Stub/MessageTraitImplementation.php create mode 100644 tests/Unit/Http/Stub/MessageTraitRequestImplementation.php create mode 100644 tests/Unit/Http/Stub/MessageTraitResponseImplementation.php (limited to 'tests/Unit/Http/RequestTest.php') diff --git a/src/Http/MessageTrait.php b/src/Http/MessageTrait.php index fa3a1459..e46d291e 100644 --- a/src/Http/MessageTrait.php +++ b/src/Http/MessageTrait.php @@ -4,7 +4,6 @@ namespace Engelsystem\Http; use Psr\Http\Message\StreamInterface; -use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Zend\Diactoros\Stream; /** @@ -41,7 +40,12 @@ trait MessageTrait public function withProtocolVersion($version) { $new = clone $this; - $new->setProtocolVersion($version); + if (method_exists($new, 'setProtocolVersion')) { + $new->setProtocolVersion($version); + } else { + $new->server->set('SERVER_PROTOCOL', $version); + } + return $new; } @@ -72,7 +76,11 @@ trait MessageTrait */ 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) { $new = clone $this; - $new->setContent($body); + + if (method_exists($new, 'setContent')) { + $new->setContent($body); + } else { + $new->content = $body; + } return $new; } diff --git a/src/Http/Request.php b/src/Http/Request.php index c6a9e5ad..fd3bff42 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -2,10 +2,15 @@ namespace Engelsystem\Http; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UriInterface; 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 * @@ -64,4 +69,128 @@ class Request extends SymfonyRequest { 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); + } } diff --git a/tests/Unit/Http/MessageTraitRequestTest.php b/tests/Unit/Http/MessageTraitRequestTest.php new file mode 100644 index 00000000..7430b5d7 --- /dev/null +++ b/tests/Unit/Http/MessageTraitRequestTest.php @@ -0,0 +1,50 @@ +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()); + } +} diff --git a/tests/Unit/Http/MessageTraitResponseTest.php b/tests/Unit/Http/MessageTraitResponseTest.php new file mode 100644 index 00000000..f60360a3 --- /dev/null +++ b/tests/Unit/Http/MessageTraitResponseTest.php @@ -0,0 +1,159 @@ +assertInstanceOf(MessageInterface::class, $message); + $this->assertInstanceOf(SymfonyResponse::class, $message); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::getProtocolVersion + * @covers \Engelsystem\Http\MessageTrait::withProtocolVersion + */ + public function testGetProtocolVersion() + { + $message = new MessageTraitResponseImplementation(); + $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 MessageTraitResponseImplementation(); + $newMessage = $message->withHeader('Foo', 'bar'); + + $this->assertNotEquals($message, $newMessage); + $this->assertArraySubset(['Foo' => ['bar']], $newMessage->getHeaders()); + + $newMessage = $message->withHeader('lorem', ['ipsum', 'dolor']); + $this->assertArraySubset(['lorem' => ['ipsum', 'dolor']], $newMessage->getHeaders()); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::hasHeader + */ + public function testHasHeader() + { + $message = new MessageTraitResponseImplementation(); + $this->assertFalse($message->hasHeader('test')); + + $newMessage = $message->withHeader('test', '12345'); + $this->assertTrue($newMessage->hasHeader('Test')); + $this->assertTrue($newMessage->hasHeader('test')); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::getHeader + */ + public function testGetHeader() + { + $message = new MessageTraitResponseImplementation(); + $newMessage = $message->withHeader('foo', 'bar'); + + $this->assertEquals(['bar'], $newMessage->getHeader('Foo')); + $this->assertEquals([], $newMessage->getHeader('LoremIpsum')); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::getHeaderLine + */ + public function testGetHeaderLine() + { + $message = new MessageTraitResponseImplementation(); + $newMessage = $message->withHeader('foo', ['bar', 'bla']); + + $this->assertEquals('', $newMessage->getHeaderLine('Lorem-Ipsum')); + $this->assertEquals('bar,bla', $newMessage->getHeaderLine('Foo')); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::withHeader + */ + public function testWithHeader() + { + $message = new MessageTraitResponseImplementation(); + $newMessage = $message->withHeader('foo', 'bar'); + + $this->assertNotEquals($message, $newMessage); + $this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders()); + + $newMessage = $newMessage->withHeader('Foo', ['lorem', 'ipsum']); + $this->assertArraySubset(['Foo' => ['lorem', 'ipsum']], $newMessage->getHeaders()); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::withAddedHeader + */ + public function testWithAddedHeader() + { + $message = new MessageTraitResponseImplementation(); + $newMessage = $message->withHeader('foo', 'bar'); + + $this->assertNotEquals($message, $newMessage); + $this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders()); + + $newMessage = $newMessage->withAddedHeader('Foo', ['lorem', 'ipsum']); + $this->assertArraySubset(['Foo' => ['bar', 'lorem', 'ipsum']], $newMessage->getHeaders()); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::withoutHeader + */ + public function testWithoutHeader() + { + $message = (new MessageTraitResponseImplementation())->withHeader('foo', 'bar'); + $this->assertTrue($message->hasHeader('foo')); + + $newMessage = $message->withoutHeader('Foo'); + $this->assertNotEquals($message, $newMessage); + $this->assertFalse($newMessage->hasHeader('foo')); + } + + /** + * @covers \Engelsystem\Http\MessageTrait::getBody + */ + public function testGetBody() + { + $message = (new MessageTraitResponseImplementation())->setContent('Foo bar!'); + $body = $message->getBody(); + + $this->assertInstanceOf(StreamInterface::class, $body); + $this->assertEquals('Foo bar!', $body->getContents()); + } + + /** + * @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 MessageTraitResponseImplementation(); + $newMessage = $message->withBody($stream); + + $this->assertNotEquals($message, $newMessage); + $this->assertEquals('Test content', $newMessage->getContent()); + } +} diff --git a/tests/Unit/Http/MessageTraitTest.php b/tests/Unit/Http/MessageTraitTest.php deleted file mode 100644 index 46076a67..00000000 --- a/tests/Unit/Http/MessageTraitTest.php +++ /dev/null @@ -1,159 +0,0 @@ -assertInstanceOf(MessageInterface::class, $message); - $this->assertInstanceOf(SymfonyResponse::class, $message); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::getProtocolVersion - * @covers \Engelsystem\Http\MessageTrait::withProtocolVersion - */ - public function testGetProtocolVersion() - { - $message = new MessageTraitImplementation(); - $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 MessageTraitImplementation(); - $newMessage = $message->withHeader('Foo', 'bar'); - - $this->assertNotEquals($message, $newMessage); - $this->assertArraySubset(['Foo' => ['bar']], $newMessage->getHeaders()); - - $newMessage = $message->withHeader('lorem', ['ipsum', 'dolor']); - $this->assertArraySubset(['lorem' => ['ipsum', 'dolor']], $newMessage->getHeaders()); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::hasHeader - */ - public function testHasHeader() - { - $message = new MessageTraitImplementation(); - $this->assertFalse($message->hasHeader('test')); - - $newMessage = $message->withHeader('test', '12345'); - $this->assertTrue($newMessage->hasHeader('Test')); - $this->assertTrue($newMessage->hasHeader('test')); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::getHeader - */ - public function testGetHeader() - { - $message = new MessageTraitImplementation(); - $newMessage = $message->withHeader('foo', 'bar'); - - $this->assertEquals(['bar'], $newMessage->getHeader('Foo')); - $this->assertEquals([], $newMessage->getHeader('LoremIpsum')); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::getHeaderLine - */ - public function testGetHeaderLine() - { - $message = new MessageTraitImplementation(); - $newMessage = $message->withHeader('foo', ['bar', 'bla']); - - $this->assertEquals('', $newMessage->getHeaderLine('Lorem-Ipsum')); - $this->assertEquals('bar,bla', $newMessage->getHeaderLine('Foo')); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::withHeader - */ - public function testWithHeader() - { - $message = new MessageTraitImplementation(); - $newMessage = $message->withHeader('foo', 'bar'); - - $this->assertNotEquals($message, $newMessage); - $this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders()); - - $newMessage = $newMessage->withHeader('Foo', ['lorem', 'ipsum']); - $this->assertArraySubset(['Foo' => ['lorem', 'ipsum']], $newMessage->getHeaders()); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::withAddedHeader - */ - public function testWithAddedHeader() - { - $message = new MessageTraitImplementation(); - $newMessage = $message->withHeader('foo', 'bar'); - - $this->assertNotEquals($message, $newMessage); - $this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders()); - - $newMessage = $newMessage->withAddedHeader('Foo', ['lorem', 'ipsum']); - $this->assertArraySubset(['Foo' => ['bar', 'lorem', 'ipsum']], $newMessage->getHeaders()); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::withoutHeader - */ - public function testWithoutHeader() - { - $message = (new MessageTraitImplementation())->withHeader('foo', 'bar'); - $this->assertTrue($message->hasHeader('foo')); - - $newMessage = $message->withoutHeader('Foo'); - $this->assertNotEquals($message, $newMessage); - $this->assertFalse($newMessage->hasHeader('foo')); - } - - /** - * @covers \Engelsystem\Http\MessageTrait::getBody - */ - public function testGetBody() - { - $message = (new MessageTraitImplementation())->setContent('Foo bar!'); - $body = $message->getBody(); - - $this->assertInstanceOf(StreamInterface::class, $body); - $this->assertEquals('Foo bar!', $body->getContents()); - } - - /** - * @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 MessageTraitImplementation(); - $newMessage = $message->withBody($stream); - - $this->assertNotEquals($message, $newMessage); - $this->assertEquals('Test content', $newMessage->getContent()); - } -} diff --git a/tests/Unit/Http/RequestTest.php b/tests/Unit/Http/RequestTest.php index f8444b84..f7d69aff 100644 --- a/tests/Unit/Http/RequestTest.php +++ b/tests/Unit/Http/RequestTest.php @@ -5,6 +5,8 @@ namespace Engelsystem\Test\Unit\Http; use Engelsystem\Http\Request; use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UriInterface; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; class RequestTest extends TestCase @@ -16,6 +18,7 @@ class RequestTest extends TestCase { $response = new Request(); $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('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); + } } diff --git a/tests/Unit/Http/Stub/MessageTraitImplementation.php b/tests/Unit/Http/Stub/MessageTraitImplementation.php deleted file mode 100644 index d78fd0b2..00000000 --- a/tests/Unit/Http/Stub/MessageTraitImplementation.php +++ /dev/null @@ -1,12 +0,0 @@ - Date: Mon, 20 Aug 2018 17:35:07 +0200 Subject: Made Engelsystem\Http\Request PSR-7 ServerRequestInterface compatible --- src/Http/Psr7ServiceProvider.php | 3 +- src/Http/Request.php | 320 +++++++++++++++++++++++++++- tests/Unit/Http/Psr7ServiceProviderTest.php | 5 - tests/Unit/Http/RequestTest.php | 183 ++++++++++++++++ 4 files changed, 502 insertions(+), 9 deletions(-) (limited to 'tests/Unit/Http/RequestTest.php') diff --git a/src/Http/Psr7ServiceProvider.php b/src/Http/Psr7ServiceProvider.php index 4a3c6583..72fdef8e 100644 --- a/src/Http/Psr7ServiceProvider.php +++ b/src/Http/Psr7ServiceProvider.php @@ -18,8 +18,7 @@ class Psr7ServiceProvider extends ServiceProvider /** @var Request $request */ $request = $this->app->get('request'); - $psr7request = $psr7Factory->createRequest($request); - $this->app->instance('psr7.request', $psr7request); + $this->app->instance('psr7.request', $request); $this->app->bind(ServerRequestInterface::class, 'psr7.request'); /** @var Response $response */ diff --git a/src/Http/Request.php b/src/Http/Request.php index fd3bff42..4729606f 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -2,12 +2,15 @@ namespace Engelsystem\Http; -use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; +use Zend\Diactoros\UploadedFile; use Zend\Diactoros\Uri; -class Request extends SymfonyRequest implements RequestInterface +class Request extends SymfonyRequest implements ServerRequestInterface { use MessageTrait; @@ -193,4 +196,317 @@ class Request extends SymfonyRequest implements RequestInterface return new Uri($uri); } + + /** + * Retrieve server parameters. + * + * Retrieves data related to the incoming request environment, + * typically derived from PHP's $_SERVER superglobal. The data IS NOT + * REQUIRED to originate from $_SERVER. + * + * @return array + */ + public function getServerParams() + { + return $this->server->all(); + } + + /** + * Retrieve cookies. + * + * Retrieves cookies sent by the client to the server. + * + * The data MUST be compatible with the structure of the $_COOKIE + * superglobal. + * + * @return array + */ + public function getCookieParams() + { + return $this->cookies->all(); + } + + /** + * Return an instance with the specified cookies. + * + * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST + * be compatible with the structure of $_COOKIE. Typically, this data will + * be injected at instantiation. + * + * This method MUST NOT update the related Cookie header of the request + * instance, nor related values in the server params. + * + * 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 + * updated cookie values. + * + * @param array $cookies Array of key/value pairs representing cookies. + * @return static + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookies = clone $this->cookies; + $new->cookies->replace($cookies); + + return $new; + } + + /** + * Retrieve query string arguments. + * + * Retrieves the deserialized query string arguments, if any. + * + * Note: the query params might not be in sync with the URI or server + * params. If you need to ensure you are only getting the original + * values, you may need to parse the query string from `getUri()->getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams() + { + return $this->query->all(); + } + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * 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 + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->query = clone $this->query; + $new->query->replace($query); + + return $new; + } + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles() + { + $files = []; + foreach ($this->files as $file) { + /** @var SymfonyFile $file */ + + $files[] = new UploadedFile( + $file->getPath(), + $file->getSize(), + $file->getError(), + $file->getClientOriginalName(), + $file->getClientMimeType() + ); + } + + return $files; + } + + /** + * Create a new instance with the specified uploaded files. + * + * 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 + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->files = clone $this->files; + + $files = []; + foreach ($uploadedFiles as $file) { + /** @var UploadedFileInterface $file */ + $filename = tempnam(sys_get_temp_dir(), 'upload'); + $handle = fopen($filename, "w"); + fwrite($handle, $file->getStream()->getContents()); + fclose($handle); + + $files[] = new SymfonyFile( + $filename, + $file->getClientFilename(), + $file->getClientMediaType(), + $file->getSize(), + $file->getError() + ); + } + $new->files->add($files); + + return $new; + } + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody() + { + return $this->request->all(); + } + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * 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 + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->request = clone $this->request; + + $new->request->replace($data); + + return $new; + } + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes() + { + return $this->attributes->all(); + } + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return $this->attributes->get($name, $default); + } + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * 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 + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value) + { + $new = clone $this; + $new->attributes = clone $this->attributes; + + $new->attributes->set($name, $value); + + return $new; + } + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name) + { + $new = clone $this; + $new->attributes = clone $this->attributes; + + $new->attributes->remove($name); + + return $new; + } } diff --git a/tests/Unit/Http/Psr7ServiceProviderTest.php b/tests/Unit/Http/Psr7ServiceProviderTest.php index a09e9572..e14daf2a 100644 --- a/tests/Unit/Http/Psr7ServiceProviderTest.php +++ b/tests/Unit/Http/Psr7ServiceProviderTest.php @@ -50,11 +50,6 @@ class Psr7ServiceProviderTest extends ServiceProviderTest [ResponseInterface::class, 'psr7.response'] ); - $psr7Factory->expects($this->once()) - ->method('createRequest') - ->with($request) - ->willReturn($psr7request); - $serviceProvider = new Psr7ServiceProvider($app); $serviceProvider->register(); } diff --git a/tests/Unit/Http/RequestTest.php b/tests/Unit/Http/RequestTest.php index f7d69aff..916aac35 100644 --- a/tests/Unit/Http/RequestTest.php +++ b/tests/Unit/Http/RequestTest.php @@ -6,7 +6,9 @@ use Engelsystem\Http\Request; use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; class RequestTest extends TestCase @@ -196,4 +198,185 @@ class RequestTest extends TestCase $this->assertInstanceOf(UriInterface::class, $uri); $this->assertEquals('http://lor.em/test?bla=foo', (string)$uri); } + + /** + * @covers \Engelsystem\Http\Request::getServerParams + */ + public function testGetServerParams() + { + $server = ['foo' => 'bar']; + $request = new Request([], [], [], [], [], $server); + + $this->assertEquals($server, $request->getServerParams()); + } + + /** + * @covers \Engelsystem\Http\Request::getCookieParams + */ + public function testGetCookieParams() + { + $cookies = ['session' => 'LoremIpsumDolorSit']; + $request = new Request([], [], [], $cookies); + + $this->assertEquals($cookies, $request->getCookieParams()); + } + + /** + * @covers \Engelsystem\Http\Request::withCookieParams + */ + public function testWithCookieParams() + { + $cookies = ['lor' => 'em']; + $request = new Request(); + + $new = $request->withCookieParams($cookies); + + $this->assertNotEquals($request, $new); + $this->assertEquals($cookies, $new->getCookieParams()); + } + + /** + * @covers \Engelsystem\Http\Request::getQueryParams + */ + public function testGetQueryParams() + { + $params = ['foo' => 'baz']; + $request = new Request($params); + + $this->assertEquals($params, $request->getQueryParams()); + } + + /** + * @covers \Engelsystem\Http\Request::withQueryParams + */ + public function testWithQueryParams() + { + $params = ['test' => 'ing']; + $request = new Request(); + + $new = $request->withQueryParams($params); + + $this->assertNotEquals($request, $new); + $this->assertEquals($params, $new->getQueryParams()); + } + + /** + * @covers \Engelsystem\Http\Request::getUploadedFiles + */ + public function testGetUploadedFiles() + { + $filename = tempnam(sys_get_temp_dir(), 'test'); + file_put_contents($filename, 'LoremIpsum!'); + $files = [new SymfonyFile($filename, 'foo.html', 'text/html', 11)]; + $request = new Request([], [], [], [], $files); + + $uploadedFiles = $request->getUploadedFiles(); + $this->assertNotEmpty($uploadedFiles); + + /** @var UploadedFileInterface $file */ + $file = $uploadedFiles[0]; + $this->assertInstanceOf(UploadedFileInterface::class, $file); + $this->assertEquals('foo.html', $file->getClientFilename()); + $this->assertEquals('text/html', $file->getClientMediaType()); + $this->assertEquals(11, $file->getSize()); + } + + /** + * @covers \Engelsystem\Http\Request::withUploadedFiles + */ + public function testWithUploadedFiles() + { + $filename = tempnam(sys_get_temp_dir(), 'test'); + file_put_contents($filename, 'LoremIpsum!'); + $file = new \Zend\Diactoros\UploadedFile($filename, 11, UPLOAD_ERR_OK, 'test.txt', 'text/plain'); + + $request = new Request(); + $new = $request->withUploadedFiles([$file]); + $uploadedFiles = $new->getUploadedFiles(); + $this->assertNotEquals($request, $new); + $this->assertNotEmpty($uploadedFiles); + + /** @var UploadedFileInterface $file */ + $file = $uploadedFiles[0]; + $this->assertEquals('test.txt', $file->getClientFilename()); + $this->assertEquals('text/plain', $file->getClientMediaType()); + $this->assertEquals(11, $file->getSize()); + } + + /** + * @covers \Engelsystem\Http\Request::getParsedBody + */ + public function testGetParsedBody() + { + $body = ['foo' => 'lorem']; + $request = new Request(); + $request->request->add($body); + + $this->assertEquals($body, $request->getParsedBody()); + } + + /** + * @covers \Engelsystem\Http\Request::withParsedBody + */ + public function testWithParsedBody() + { + $data = ['test' => 'er']; + $request = new Request(); + + $new = $request->withParsedBody($data); + + $this->assertNotEquals($request, $new); + $this->assertEquals($data, $new->getParsedBody()); + } + + /** + * @covers \Engelsystem\Http\Request::getAttributes + */ + public function testGetAttributes() + { + $attributes = ['foo' => 'lorem', 'ipsum' => 'dolor']; + $request = new Request([], [], $attributes); + + $this->assertEquals($attributes, $request->getAttributes()); + } + + /** + * @covers \Engelsystem\Http\Request::getAttribute + */ + public function testGetAttribute() + { + $attributes = ['foo' => 'lorem', 'ipsum' => 'dolor']; + $request = new Request([], [], $attributes); + + $this->assertEquals($attributes['ipsum'], $request->getAttribute('ipsum')); + $this->assertEquals(null, $request->getAttribute('dolor')); + $this->assertEquals(1234, $request->getAttribute('test', 1234)); + } + + /** + * @covers \Engelsystem\Http\Request::withAttribute + */ + public function testWithAttribute() + { + $request = new Request(); + + $new = $request->withAttribute('lorem', 'ipsum'); + + $this->assertNotEquals($request, $new); + $this->assertEquals('ipsum', $new->getAttribute('lorem')); + } + + /** + * @covers \Engelsystem\Http\Request::withoutAttribute + */ + public function testWithoutAttribute() + { + $attributes = ['foo' => 'lorem', 'ipsum' => 'dolor']; + $request = new Request([], [], $attributes); + + $new = $request->withoutAttribute('ipsum'); + + $this->assertNotEquals($request, $new); + $this->assertEquals(['foo' => 'lorem'], $new->getAttributes()); + } } -- cgit v1.2.3-54-g00ecf