From 168a53bf59c56ae4cfe2c4155e655a3cde58ee9d Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Wed, 17 Aug 2016 23:31:10 +0200 Subject: [PATCH 01/20] remove faker | remove urlgenerator --- README.md | 2 +- composer.json | 1 - src/Viserio/Routing/UrlGenerator.php | 28 ---------------------------- 3 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 src/Viserio/Routing/UrlGenerator.php diff --git a/README.md b/README.md index 664ad4a71..844ca8d2f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Author](http://img.shields.io/badge/author-@anolilab-blue.svg?style=flat-square)](https://twitter.com/anolilab) [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark) [![Latest Version](https://img.shields.io/packagist/v/narrowspark/framework.svg?style=flat-square)](https://github.com/narrowspark/framework/releases) -[![Minimum PHP Version](https://img.shields.io/badge/php-%5E7.0-8892BF.svg?style=flat-square)](https://php.net/) +[![Minimum PHP Version](https://img.shields.io/badge/php-%5E7.0.7-8892BF.svg?style=flat-square)](https://php.net/) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) ## Chat Room on Gitter diff --git a/composer.json b/composer.json index a0ae30d11..5cdcf1910 100644 --- a/composer.json +++ b/composer.json @@ -107,7 +107,6 @@ "cache/filesystem-adapter" : "^0.3", "cache/session-handler" : "^0.2", "cache/void-adapter" : "^0.3", - "fzaninotto/faker" : "^1.6", "guzzlehttp/guzzle" : "^6.0", "league/flysystem-aws-s3-v3" : "^1.0", "league/flysystem-cached-adapter" : "^1.0", diff --git a/src/Viserio/Routing/UrlGenerator.php b/src/Viserio/Routing/UrlGenerator.php deleted file mode 100644 index ab604a0c2..000000000 --- a/src/Viserio/Routing/UrlGenerator.php +++ /dev/null @@ -1,28 +0,0 @@ - '/', - '%40' => '@', - '%3A' => ':', - '%3B' => ';', - '%2C' => ',', - '%3D' => '=', - '%2B' => '+', - '%21' => '!', - '%2A' => '*', - '%7C' => '|', - '%3F' => '?', - '%26' => '&', - '%23' => '#', - '%25' => '%', - ]; -} From f51deaa42a7831def5444b74035e79f736efdec5 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sat, 20 Aug 2016 14:28:35 +0200 Subject: [PATCH 02/20] update --- .travis.yml | 6 +- src/Viserio/Contracts/Parsers/Dumper.php | 17 ++ src/Viserio/Contracts/Parsers/Format.php | 11 - src/Viserio/Http/AbstractMessage.php | 31 ++- src/Viserio/Http/Request.php | 25 +- .../Http/Response/RedirectResponse.php | 4 +- src/Viserio/Http/ServerRequest.php | 17 -- src/Viserio/Http/Stream/LazyOpenStream.php | 3 +- src/Viserio/Http/StreamFactory.php | 29 ++- src/Viserio/Http/Tests/RequestTest.php | 188 +++++++++------ src/Viserio/Http/Tests/ResponseTest.php | 221 +++++++++--------- .../Http/Tests/ServerRequestFactoryTest.php | 1 + src/Viserio/Http/Tests/ServerRequestTest.php | 3 + .../Http/Tests/Stream/BufferStreamTest.php | 67 +++--- .../Tests/Stream/ByteCountingStreamTest.php | 14 +- .../Http/Tests/Stream/FnStreamTest.php | 70 +++--- .../Http/Tests/Stream/LazyOpenStreamTest.php | 45 ++-- .../Http/Tests/Stream/LimitStreamTest.php | 53 +++-- .../Http/Tests/Stream/NoSeekStreamTest.php | 7 +- .../Http/Tests/Stream/PhpInputStreamTest.php | 3 + .../Http/Tests/Stream/PumpStreamTest.php | 58 ++--- src/Viserio/Http/Tests/StreamFactoryTest.php | 89 +++++++ src/Viserio/Http/Tests/StreamTest.php | 4 +- src/Viserio/Http/Tests/UploadedFileTest.php | 18 +- .../Http/Tests/Uri/Filter/PathTest.php | 1 + src/Viserio/Http/Tests/UriTest.php | 53 +++-- src/Viserio/Http/Tests/UtilTest.php | 120 ++-------- src/Viserio/Http/Uri.php | 2 +- src/Viserio/Http/Util.php | 64 +---- src/Viserio/Parsers/Formats/BSON.php | 3 +- src/Viserio/Parsers/Formats/INI.php | 11 +- src/Viserio/Parsers/Formats/JSON.php | 3 +- src/Viserio/Parsers/Formats/MSGPack.php | 3 +- src/Viserio/Parsers/Formats/PHP.php | 3 +- src/Viserio/Parsers/Formats/Po.php | 8 - src/Viserio/Parsers/Formats/QueryStr.php | 3 +- src/Viserio/Parsers/Formats/Serialize.php | 3 +- src/Viserio/Parsers/Formats/TOML.php | 8 - src/Viserio/Parsers/Formats/XML.php | 3 +- src/Viserio/Parsers/Formats/YAML.php | 3 +- .../Parsers/Tests/Formats/TOMLTest.php | 5 - src/Viserio/Parsers/composer.json | 3 +- src/Viserio/Support/Env.php | 68 ++++++ src/Viserio/Support/Proxies/Env.php | 13 ++ src/Viserio/Support/Tests/EnvTest.php | 56 +++++ 45 files changed, 804 insertions(+), 616 deletions(-) create mode 100644 src/Viserio/Contracts/Parsers/Dumper.php create mode 100644 src/Viserio/Http/Tests/StreamFactoryTest.php create mode 100644 src/Viserio/Support/Env.php create mode 100644 src/Viserio/Support/Proxies/Env.php create mode 100644 src/Viserio/Support/Tests/EnvTest.php diff --git a/.travis.yml b/.travis.yml index 7f91207e8..16eda9e86 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,15 +23,15 @@ matrix: env: SETUP=lowest - php: 7.0 env: SETUP=stable - - php: 7.1 + - php: 7.1.0beta2 env: SETUP=lowest - - php: 7.1 + - php: 7.1.0beta2 env: SETUP=stable allow_failures: - env: SETUP=lowest php: 7.0 - env: SETUP=lowest - php: 7.1 + php: 7.1.0beta2 fast_finish: true cache: diff --git a/src/Viserio/Contracts/Parsers/Dumper.php b/src/Viserio/Contracts/Parsers/Dumper.php new file mode 100644 index 000000000..a2a1065e9 --- /dev/null +++ b/src/Viserio/Contracts/Parsers/Dumper.php @@ -0,0 +1,17 @@ +stream) { - $this->stream = Util::getStream(''); + $this->stream = (new StreamFactory())->createStreamFromString(''); } return $this->stream; @@ -236,6 +236,33 @@ protected function setHeaders(array $headers) } } + /** + * Create a new stream based on the input type. + * + * @param string|null|resource|\Psr\Http\Message\StreamInterface $body + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + * + * @return \Psr\Http\Message\StreamInterface + */ + protected function createStream($body): StreamInterface + { + $stream = new StreamFactory(); + $type = gettype($body); + + if ($body instanceof StreamInterface) { + return $body; + } elseif (is_string($body)) { + return $stream->createStreamFromString($body); + } elseif ($type === 'NULL') { + return new Stream(fopen('php://temp', 'r+')); + } elseif ($type === 'resource') { + return $stream->createStreamFromResource($body); + } + + throw new InvalidArgumentException('Invalid resource type: ' . gettype($body)); + } + /** * Validate the HTTP protocol version. * diff --git a/src/Viserio/Http/Request.php b/src/Viserio/Http/Request.php index 113455d93..8c54488fb 100644 --- a/src/Viserio/Http/Request.php +++ b/src/Viserio/Http/Request.php @@ -4,7 +4,6 @@ use InvalidArgumentException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; class Request extends AbstractMessage implements RequestInterface @@ -57,11 +56,11 @@ class Request extends AbstractMessage implements RequestInterface /** * Create a new request instance. * - * @param null|string|UriInterface $uri URI for the request. - * @param string|null $method HTTP method for the request. - * @param array $headers Headers for the message. - * @param string|null|resource|StreamInterface $body Message body. - * @param string $version HTTP protocol version. + * @param null|string|UriInterface $uri URI for the request. + * @param string|null $method HTTP method for the request. + * @param array $headers Headers for the message. + * @param string|null|resource|\Psr\Http\Message\StreamInterface $body Message body. + * @param string $version HTTP protocol version. */ public function __construct( $uri, @@ -80,7 +79,7 @@ public function __construct( } if ($body !== '' && $body !== null) { - $this->stream = Util::getStream($body); + $this->stream = $this->createStream($body); } } @@ -215,17 +214,9 @@ private function filterMethod($method): string $method = strtoupper($method); - if (! is_string($method)) { - throw new InvalidArgumentException( - 'The HTTP method must be a string' - ); - } - - $method = strtoupper($method); - if (! isset(static::$validMethods[$method])) { throw new InvalidArgumentException(sprintf( - 'Unsupported HTTP method "%s"', + 'Unsupported HTTP method "%s".', $method )); } @@ -266,7 +257,7 @@ private function createUri($uri) } throw new InvalidArgumentException( - 'Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance' + 'Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance.' ); } } diff --git a/src/Viserio/Http/Response/RedirectResponse.php b/src/Viserio/Http/Response/RedirectResponse.php index 5e4b86610..a46d8f594 100644 --- a/src/Viserio/Http/Response/RedirectResponse.php +++ b/src/Viserio/Http/Response/RedirectResponse.php @@ -5,7 +5,7 @@ use InvalidArgumentException; use Psr\Http\Message\UriInterface; use Viserio\Http\Response; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class RedirectResponse extends Response { @@ -33,6 +33,6 @@ public function __construct($uri, int $status = 302, array $headers = []) $headers['location'] = [(string) $uri]; - parent::__construct($status, $headers, Util::getStream()); + parent::__construct($status, $headers, (new StreamFactory())->createStream()); } } diff --git a/src/Viserio/Http/ServerRequest.php b/src/Viserio/Http/ServerRequest.php index f0547c2f0..09cee5b6e 100644 --- a/src/Viserio/Http/ServerRequest.php +++ b/src/Viserio/Http/ServerRequest.php @@ -120,23 +120,6 @@ public function getAttributes() return $this->attributes; } - /** - * Proxy to receive the request method. - * - * This overrides the parent functionality to ensure the method is never - * empty; if no method is present, it returns 'GET'. - * - * @return string - */ - public function getMethod() - { - if ($this->method === null) { - return 'GET'; - } - - return $this->method; - } - /** * {@inheritdoc} */ diff --git a/src/Viserio/Http/Stream/LazyOpenStream.php b/src/Viserio/Http/Stream/LazyOpenStream.php index 7e0edccd9..1a17fb7fd 100644 --- a/src/Viserio/Http/Stream/LazyOpenStream.php +++ b/src/Viserio/Http/Stream/LazyOpenStream.php @@ -5,6 +5,7 @@ use Psr\Http\Message\StreamInterface; use Throwable; use UnexpectedValueException; +use Viserio\Http\StreamFactory; use Viserio\Http\Util; class LazyOpenStream implements StreamInterface @@ -187,6 +188,6 @@ public function write($string) */ protected function createStream(): StreamInterface { - return Util::getStream(Util::tryFopen($this->filename, $this->mode)); + return (new StreamFactory())->createStreamFromResource(Util::tryFopen($this->filename, $this->mode)); } } diff --git a/src/Viserio/Http/StreamFactory.php b/src/Viserio/Http/StreamFactory.php index c35febc52..a58d0f5c8 100644 --- a/src/Viserio/Http/StreamFactory.php +++ b/src/Viserio/Http/StreamFactory.php @@ -2,48 +2,53 @@ declare(strict_types=1); namespace Viserio\Http; +use InvalidArgumentException; use Psr\Http\Message\StreamInterface; use Viserio\Contracts\Http\StreamFactory as StreamFactoryContract; +use Viserio\Http\Stream\PumpStream; final class StreamFactory implements StreamFactoryContract { /** * {@inheritdoc} - * - * @codeCoverageIgnore */ public function createStream(): StreamInterface { - return Util::getStream(); + return new Stream(fopen('php://temp', 'r+')); } /** * {@inheritdoc} - * - * @codeCoverageIgnore */ public function createStreamFromCallback(callable $callback): StreamInterface { - return Util::getStream($callback); + return new PumpStream($callback); } /** * {@inheritdoc} - * - * @codeCoverageIgnore */ public function createStreamFromResource($body): StreamInterface { - return Util::getStream($body); + if (gettype($body) === 'resource') { + return new Stream($body); + } + + throw new InvalidArgumentException(sprintf('Invalid resource type: %s.', gettype($body))); } /** * {@inheritdoc} - * - * @codeCoverageIgnore */ public function createStreamFromString(string $body): StreamInterface { - return Util::getStream($body); + $stream = fopen('php://temp', 'r+'); + + if ($body !== '') { + fwrite($stream, $body); + fseek($stream, 0); + } + + return new Stream($stream); } } diff --git a/src/Viserio/Http/Tests/RequestTest.php b/src/Viserio/Http/Tests/RequestTest.php index fe1610cb5..83c936337 100644 --- a/src/Viserio/Http/Tests/RequestTest.php +++ b/src/Viserio/Http/Tests/RequestTest.php @@ -6,10 +6,11 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; +use StdClass; use Viserio\Http\Request; use Viserio\Http\Stream\FnStream; +use Viserio\Http\StreamFactory; use Viserio\Http\Uri; -use Viserio\Http\Util; class RequestTest extends AbstractMessageTest { @@ -39,6 +40,7 @@ public function testValidDefaultRequestTarget() { $message = $this->classToTest; $target = $message->getRequestTarget(); + $this->assertInternalType('string', $target, 'getRequestTarget must return a string'); $this->assertEquals( '/', @@ -51,6 +53,7 @@ public function testValidDefaultMethod() { $message = $this->classToTest; $target = $message->getMethod(); + $this->assertInternalType('string', $target, 'getMethod must return a string'); } @@ -58,6 +61,7 @@ public function testValidDefaultUri() { $message = $this->classToTest; $body = $message->getUri(); + $this->assertInstanceOf( UriInterface::class, $body, @@ -75,6 +79,7 @@ public function testValidWithRequestTarget($expectedRequestTarget) $request = $this->classToTest; $requestClone = clone $request; $newRequest = $request->withRequestTarget($expectedRequestTarget); + $this->assertImmutable($requestClone, $request, $newRequest); $this->assertEquals( $expectedRequestTarget, @@ -101,6 +106,7 @@ public function testValidWithMethod($expectedMethod) $request = $this->classToTest; $requestClone = clone $request; $newRequest = $request->withMethod($expectedMethod); + $this->assertImmutable($requestClone, $request, $newRequest); $this->assertEquals( $expectedMethod, @@ -132,6 +138,7 @@ public function testValidWithUri() ->andReturn('') ->getMock(); $newRequest = $request->withUri($uri); + $this->assertImmutable($requestClone, $request, $newRequest); $this->assertEquals( $uri, @@ -144,7 +151,7 @@ public function testConstructorDoesNotReadStreamBody() { $streamIsRead = false; - $body = FnStream::decorate(Util::getStream(''), [ + $body = FnStream::decorate((new StreamFactory())->createStreamFromString(''), [ '__toString' => function () use (&$streamIsRead) { $streamIsRead = true; @@ -152,9 +159,10 @@ public function testConstructorDoesNotReadStreamBody() }, ]); - $r = new Request('/', 'GET', [], $body); + $request = new Request('/', 'GET', [], $body); + $this->assertFalse($streamIsRead); - $this->assertSame($body, $r->getBody()); + $this->assertSame($body, $request->getBody()); } /** @@ -172,6 +180,7 @@ public function testHostHeaderPreservationWhenUriIsSet( $expectedHostHeaderLine ) { $requestAfterUri = $request->withUri($uri, $preserveHost); + $this->assertEquals($expectedHostHeaderLine, $requestAfterUri->getHeaderLine('Host')); } @@ -265,6 +274,7 @@ public function hostHeaderPreservationWhenUriIsSetProvider() public function testHostHeaderSetFromUriOnCreationIfNoHostHeaderSpecified() { $request = new Request('http://www.example.com'); + $this->assertTrue($request->hasHeader('Host')); $this->assertEquals('www.example.com', $request->getHeaderLine('host')); } @@ -272,189 +282,231 @@ public function testHostHeaderSetFromUriOnCreationIfNoHostHeaderSpecified() public function testHostHeaderNotSetFromUriOnCreationIfHostHeaderSpecified() { $request = new Request('http://www.example.com', null, ['Host' => 'www.test.com'], 'php://memory'); + $this->assertEquals('www.test.com', $request->getHeaderLine('host')); } public function testRequestUriMayBeString() { - $r = new Request('/', 'GET'); - $this->assertEquals('/', (string) $r->getUri()); + $request = new Request('/', 'GET'); + + $this->assertEquals('/', (string) $request->getUri()); } public function testRequestUriMayBeUri() { $uri = new Uri('/'); - $r = new Request($uri, 'GET'); - $this->assertSame($uri, $r->getUri()); + $request = new Request($uri, 'GET'); + + $this->assertSame($uri, $request->getUri()); } /** * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to parse URI: ///. */ public function testValidateRequestUri() { new Request('///', 'GET'); } + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unsupported HTTP method "FOO". + */ + public function testWithNotValidMethodRequest() + { + new Request('/', 'foo'); + } + public function testCanConstructWithBody() { - $r = new Request('/', 'GET', [], 'baz'); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertEquals('baz', (string) $r->getBody()); + $request = new Request('/', 'GET', [], 'baz'); + + $this->assertInstanceOf(StreamInterface::class, $request->getBody()); + $this->assertEquals('baz', (string) $request->getBody()); } public function testNullBody() { - $r = new Request('/', 'GET', [], null); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('', (string) $r->getBody()); + $request = new Request('/', 'GET', [], null); + + $this->assertInstanceOf(StreamInterface::class, $request->getBody()); + $this->assertSame('', (string) $request->getBody()); } public function testFalseyBody() { - $r = new Request('/', 'GET', [], '0'); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('0', (string) $r->getBody()); + $request = new Request('/', 'GET', [], '0'); + + $this->assertInstanceOf(StreamInterface::class, $request->getBody()); + $this->assertSame('0', (string) $request->getBody()); } public function testCapitalizesMethod() { - $r = new Request('/', 'get'); - $this->assertEquals('GET', $r->getMethod()); + $request = new Request('/', 'get'); + + $this->assertEquals('GET', $request->getMethod()); } public function testCapitalizesWithMethod() { - $r = new Request('/', 'GET'); - $this->assertEquals('PUT', $r->withMethod('put')->getMethod()); + $request = new Request('/', 'GET'); + + $this->assertEquals('PUT', $request->withMethod('put')->getMethod()); } public function testWithUri() { - $r1 = new Request('/', 'GET'); - $u1 = $r1->getUri(); + $request1 = new Request('/', 'GET'); + $uri1 = $request1->getUri(); - $u2 = new Uri('http://www.example.com'); - $r2 = $r1->withUri($u2); + $uri2 = new Uri('http://www.example.com'); + $request2 = $request1->withUri($uri2); - $this->assertNotSame($r1, $r2); - $this->assertSame($u2, $r2->getUri()); - $this->assertSame($u1, $r1->getUri()); + $this->assertNotSame($request1, $request2); + $this->assertSame($uri2, $request2->getUri()); + $this->assertSame($uri1, $request1->getUri()); } public function testSameInstanceWhenSameUri() { - $r1 = new Request('http://foo.com', 'GET'); - $r2 = $r1->withUri($r1->getUri()); - $this->assertSame($r1, $r2); + $request1 = new Request('http://foo.com', 'GET'); + $request2 = $request1->withUri($request1->getUri()); + + $this->assertSame($request1, $request2); } public function testWithRequestTarget() { - $r1 = new Request('/', 'GET'); - $r2 = $r1->withRequestTarget('*'); + $request1 = new Request('/', 'GET'); + $request2 = $request1->withRequestTarget('*'); + + $this->assertEquals('*', $request2->getRequestTarget()); + $this->assertEquals('/', $request1->getRequestTarget()); + } + + public function testWithRequestNullUri() + { + $request = new Request(null, 'GET'); + + $this->assertEquals('/', $request->getRequestTarget()); + } - $this->assertEquals('*', $r2->getRequestTarget()); - $this->assertEquals('/', $r1->getRequestTarget()); + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance. + */ + public function testRequestToThrowException() + { + new Request(new StdClass(), 'GET'); } /** * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid request target provided; cannot contain whitespace */ public function testRequestTargetDoesNotAllowSpaces() { - $r1 = new Request('/', 'GET'); - $r1->withRequestTarget('/foo bar'); + $request1 = new Request('/', 'GET'); + $request1->withRequestTarget('/foo bar'); } public function testRequestTargetDefaultsToSlash() { - $r1 = new Request('', 'GET'); - $this->assertEquals('/', $r1->getRequestTarget()); + $request1 = new Request('', 'GET'); + + $this->assertEquals('/', $request1->getRequestTarget()); - $r2 = new Request('*', 'GET'); - $this->assertEquals('*', $r2->getRequestTarget()); + $request2 = new Request('*', 'GET'); - $r3 = new Request('http://foo.com/bar baz/', 'GET'); - $this->assertEquals('/bar%20baz/', $r3->getRequestTarget()); + $this->assertEquals('*', $request2->getRequestTarget()); + + $request3 = new Request('http://foo.com/bar baz/', 'GET'); + + $this->assertEquals('/bar%20baz/', $request3->getRequestTarget()); } public function testBuildsRequestTarget() { - $r1 = new Request('http://foo.com/baz?bar=bam', 'GET'); - $this->assertEquals('/baz?bar=bam', $r1->getRequestTarget()); + $request1 = new Request('http://foo.com/baz?bar=bam', 'GET'); + + $this->assertEquals('/baz?bar=bam', $request1->getRequestTarget()); } public function testBuildsRequestTargetWithFalseyQuery() { - $r1 = new Request('http://foo.com/baz?0', 'GET'); - $this->assertEquals('/baz?0', $r1->getRequestTarget()); + $request1 = new Request('http://foo.com/baz?0', 'GET'); + + $this->assertEquals('/baz?0', $request1->getRequestTarget()); } public function testHostIsAddedFirst() { - $r = new Request('http://foo.com/baz?bar=bam', 'GET', ['Foo' => 'Bar']); + $request = new Request('http://foo.com/baz?bar=bam', 'GET', ['Foo' => 'Bar']); + $this->assertEquals([ 'Host' => ['foo.com'], 'Foo' => ['Bar'], - ], $r->getHeaders()); + ], $request->getHeaders()); } public function testCanGetHeaderAsCsv() { - $r = new Request('http://foo.com/baz?bar=bam', 'GET', [ + $request = new Request('http://foo.com/baz?bar=bam', 'GET', [ 'Foo' => ['a', 'b', 'c'], ]); - $this->assertEquals('a,b,c', $r->getHeaderLine('Foo')); - $this->assertEquals('', $r->getHeaderLine('Bar')); + $this->assertEquals('a,b,c', $request->getHeaderLine('Foo')); + $this->assertEquals('', $request->getHeaderLine('Bar')); } public function testHostIsNotOverwrittenWhenPreservingHost() { - $r = new Request('http://foo.com/baz?bar=bam', 'GET', ['Host' => 'a.com']); + $request = new Request('http://foo.com/baz?bar=bam', 'GET', ['Host' => 'a.com']); - $this->assertEquals(['Host' => ['a.com']], $r->getHeaders()); + $this->assertEquals(['Host' => ['a.com']], $request->getHeaders()); - $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true); + $request2 = $request->withUri(new Uri('http://www.foo.com/bar'), true); - $this->assertEquals('a.com', $r2->getHeaderLine('Host')); + $this->assertEquals('a.com', $request2->getHeaderLine('Host')); } public function testOverridesHostWithUri() { - $r = new Request('http://foo.com/baz?bar=bam', 'GET'); + $request = new Request('http://foo.com/baz?bar=bam', 'GET'); - $this->assertEquals(['Host' => ['foo.com']], $r->getHeaders()); + $this->assertEquals(['Host' => ['foo.com']], $request->getHeaders()); - $r2 = $r->withUri(new Uri('http://www.baz.com/bar')); + $request2 = $request->withUri(new Uri('http://www.baz.com/bar')); - $this->assertEquals('www.baz.com', $r2->getHeaderLine('Host')); + $this->assertEquals('www.baz.com', $request2->getHeaderLine('Host')); } public function testAggregatesHeaders() { - $r = new Request('', 'GET', [ + $request = new Request('', 'GET', [ 'ZOO' => 'zoobar', 'zoo' => ['foobar', 'zoobar'], ]); - $this->assertEquals(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders()); - $this->assertEquals('zoobar,foobar,zoobar', $r->getHeaderLine('zoo')); + $this->assertEquals(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $request->getHeaders()); + $this->assertEquals('zoobar,foobar,zoobar', $request->getHeaderLine('zoo')); } public function testAddsPortToHeader() { - $r = new Request('http://foo.com:8124/bar', 'GET'); + $request = new Request('http://foo.com:8124/bar', 'GET'); - $this->assertEquals('foo.com:8124', $r->getHeaderLine('host')); + $this->assertEquals('foo.com:8124', $request->getHeaderLine('host')); } public function testAddsPortToHeaderAndReplacePreviousPort() { - $r = new Request('http://foo.com:8124/bar', 'GET'); - $r = $r->withUri(new Uri('http://foo.com:8125/bar')); + $request = new Request('http://foo.com:8124/bar', 'GET'); + $request = $request->withUri(new Uri('http://foo.com:8125/bar')); - $this->assertEquals('foo.com:8125', $r->getHeaderLine('host')); + $this->assertEquals('foo.com:8125', $request->getHeaderLine('host')); } } diff --git a/src/Viserio/Http/Tests/ResponseTest.php b/src/Viserio/Http/Tests/ResponseTest.php index cc07ca7ff..4b8527716 100644 --- a/src/Viserio/Http/Tests/ResponseTest.php +++ b/src/Viserio/Http/Tests/ResponseTest.php @@ -6,7 +6,7 @@ use Psr\Http\Message\StreamInterface; use Viserio\Http\Response; use Viserio\Http\Stream\FnStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class ResponseTest extends AbstractMessageTest { @@ -24,6 +24,7 @@ public function testValidDefaultStatusCode() { $message = $this->classToTest; $statusCode = $message->getStatusCode(); + $this->assertInternalType('integer', $statusCode, 'getStatusCode must return an integer'); } @@ -31,6 +32,7 @@ public function testValidDefaultReasonPhrase() { $message = $this->classToTest; $reasonPhrase = $message->getReasonPhrase(); + $this->assertInternalType('string', $reasonPhrase, 'getReasonPhrase must return a string'); } @@ -41,6 +43,7 @@ public function testValidWithStatusDefaultReasonPhrase() $messageClone = clone $message; $statusCode = 100; $newMessage = $message->withStatus($statusCode); + $this->assertImmutable($messageClone, $message, $newMessage); $this->assertEquals( $statusCode, @@ -56,6 +59,7 @@ public function testValidWithStatusCustomReasonPhrase() $statusCode = 100; $reasonPhrase = 'example'; $newMessage = $message->withStatus($statusCode, $reasonPhrase); + $this->assertImmutable($messageClone, $message, $newMessage); $this->assertEquals( $statusCode, @@ -71,28 +75,28 @@ public function testValidWithStatusCustomReasonPhrase() public function testDefaultConstructor() { - $r = $this->classToTest; - - $this->assertSame(200, $r->getStatusCode()); - $this->assertSame('1.1', $r->getProtocolVersion()); - $this->assertSame('OK', $r->getReasonPhrase()); - $this->assertSame([], $r->getHeaders()); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('', (string) $r->getBody()); + $response = $this->classToTest; + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('1.1', $response->getProtocolVersion()); + $this->assertSame('OK', $response->getReasonPhrase()); + $this->assertSame([], $response->getHeaders()); + $this->assertInstanceOf(StreamInterface::class, $response->getBody()); + $this->assertSame('', (string) $response->getBody()); } public function testCanConstructWithStatusCode() { - $r = new Response(404); + $response = new Response(404); - $this->assertSame(404, $r->getStatusCode()); - $this->assertSame('Not Found', $r->getReasonPhrase()); + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame('Not Found', $response->getReasonPhrase()); } public function testConstructorDoesNotReadStreamBody() { $streamIsRead = false; - $body = FnStream::decorate(Util::getStream(''), [ + $body = FnStream::decorate((new StreamFactory())->createStreamFromString(''), [ '__toString' => function () use (&$streamIsRead) { $streamIsRead = true; @@ -100,74 +104,75 @@ public function testConstructorDoesNotReadStreamBody() }, ]); - $r = new Response(200, [], $body); + $response = new Response(200, [], $body); + $this->assertFalse($streamIsRead); - $this->assertSame($body, $r->getBody()); + $this->assertSame($body, $response->getBody()); } public function testCanConstructWithHeaders() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame('Bar', $r->getHeaderLine('Foo')); - $this->assertSame(['Bar'], $r->getHeader('Foo')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame('Bar', $response->getHeaderLine('Foo')); + $this->assertSame(['Bar'], $response->getHeader('Foo')); } public function testCanConstructWithHeadersAsArray() { - $r = new Response(200, [ + $response = new Response(200, [ 'Foo' => ['baz', 'bar'], ]); - $this->assertSame(['Foo' => ['baz', 'bar']], $r->getHeaders()); - $this->assertSame('baz,bar', $r->getHeaderLine('Foo')); - $this->assertSame(['baz', 'bar'], $r->getHeader('Foo')); + $this->assertSame(['Foo' => ['baz', 'bar']], $response->getHeaders()); + $this->assertSame('baz,bar', $response->getHeaderLine('Foo')); + $this->assertSame(['baz', 'bar'], $response->getHeader('Foo')); } public function testCanConstructWithBody() { - $r = new Response(200, [], 'baz'); + $response = new Response(200, [], 'baz'); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('baz', (string) $r->getBody()); + $this->assertInstanceOf(StreamInterface::class, $response->getBody()); + $this->assertSame('baz', (string) $response->getBody()); } public function testNullBody() { - $r = new Response(200, [], null); + $response = new Response(200, [], null); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('', (string) $r->getBody()); + $this->assertInstanceOf(StreamInterface::class, $response->getBody()); + $this->assertSame('', (string) $response->getBody()); } public function testFalseyBody() { - $r = new Response(200, [], '0'); + $response = new Response(200, [], '0'); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('0', (string) $r->getBody()); + $this->assertInstanceOf(StreamInterface::class, $response->getBody()); + $this->assertSame('0', (string) $response->getBody()); } public function testWithStatusCodeAndNoReason() { - $r = (new Response())->withStatus(201); + $response = (new Response())->withStatus(201); - $this->assertSame(201, $r->getStatusCode()); - $this->assertSame('Created', $r->getReasonPhrase()); + $this->assertSame(201, $response->getStatusCode()); + $this->assertSame('Created', $response->getReasonPhrase()); } public function testWithStatusCodeAndReason() { - $r = (new Response())->withStatus(201, 'Foo'); + $response = (new Response())->withStatus(201, 'Foo'); - $this->assertSame(201, $r->getStatusCode()); - $this->assertSame('Foo', $r->getReasonPhrase()); + $this->assertSame(201, $response->getStatusCode()); + $this->assertSame('Foo', $response->getReasonPhrase()); - $r = (new Response())->withStatus(201, '0'); + $response = (new Response())->withStatus(201, '0'); - $this->assertSame(201, $r->getStatusCode()); - $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works'); + $this->assertSame(201, $response->getStatusCode()); + $this->assertSame('0', $response->getReasonPhrase(), 'Falsey reason works'); } /** @@ -176,137 +181,137 @@ public function testWithStatusCodeAndReason() */ public function testWithProtocolVersion() { - $r = (new Response())->withProtocolVersion('1000'); + $response = (new Response())->withProtocolVersion('1000'); } public function testSameInstanceWhenSameProtocol() { - $r = new Response(); + $response = new Response(); - $this->assertSame($r, $r->withProtocolVersion('1.1')); + $this->assertSame($response, $response->withProtocolVersion('1.1')); } public function testWithBody() { - $b = Util::getStream('0'); - $r = (new Response())->withBody($b); + $body = (new StreamFactory())->createStreamFromString('0'); + $response = (new Response())->withBody($body); - $this->assertInstanceOf(StreamInterface::class, $r->getBody()); - $this->assertSame('0', (string) $r->getBody()); + $this->assertInstanceOf(StreamInterface::class, $response->getBody()); + $this->assertSame('0', (string) $response->getBody()); } public function testSameInstanceWhenSameBody() { - $r = new Response(); + $response = new Response(); + $body = $response->getBody(); - $b = $r->getBody(); - $this->assertSame($r, $r->withBody($b)); + $this->assertSame($response, $response->withBody($body)); } public function testWithHeader() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); + $response2 = $response->withHeader('baZ', 'Bam'); - $r2 = $r->withHeader('baZ', 'Bam'); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $r2->getHeaders()); - $this->assertSame('Bam', $r2->getHeaderLine('baz')); - $this->assertSame(['Bam'], $r2->getHeader('baz')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $response2->getHeaders()); + $this->assertSame('Bam', $response2->getHeaderLine('baz')); + $this->assertSame(['Bam'], $response2->getHeader('baz')); } public function testWithHeaderAsArray() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); + $response2 = $response->withHeader('baZ', ['Bam', 'Bar']); - $r2 = $r->withHeader('baZ', ['Bam', 'Bar']); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $r2->getHeaders()); - $this->assertSame('Bam,Bar', $r2->getHeaderLine('baz')); - $this->assertSame(['Bam', 'Bar'], $r2->getHeader('baz')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $response2->getHeaders()); + $this->assertSame('Bam,Bar', $response2->getHeaderLine('baz')); + $this->assertSame(['Bam', 'Bar'], $response2->getHeader('baz')); } public function testWithHeaderReplacesDifferentCase() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); + $response2 = $response->withHeader('foO', 'Bam'); - $r2 = $r->withHeader('foO', 'Bam'); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame(['foO' => ['Bam']], $r2->getHeaders()); - $this->assertSame('Bam', $r2->getHeaderLine('foo')); - $this->assertSame(['Bam'], $r2->getHeader('foo')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame(['foO' => ['Bam']], $response2->getHeaders()); + $this->assertSame('Bam', $response2->getHeaderLine('foo')); + $this->assertSame(['Bam'], $response2->getHeader('foo')); } public function testWithAddedHeader() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); + $response2 = $response->withAddedHeader('foO', 'Baz'); - $r2 = $r->withAddedHeader('foO', 'Baz'); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame(['Foo' => ['Bar', 'Baz']], $r2->getHeaders()); - $this->assertSame('Bar,Baz', $r2->getHeaderLine('foo')); - $this->assertSame(['Bar', 'Baz'], $r2->getHeader('foo')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame(['Foo' => ['Bar', 'Baz']], $response2->getHeaders()); + $this->assertSame('Bar,Baz', $response2->getHeaderLine('foo')); + $this->assertSame(['Bar', 'Baz'], $response2->getHeader('foo')); } public function testWithAddedHeaderAsArray() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); + $response2 = $response->withAddedHeader('foO', ['Baz', 'Bam']); - $r2 = $r->withAddedHeader('foO', ['Baz', 'Bam']); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $r2->getHeaders()); - $this->assertSame('Bar,Baz,Bam', $r2->getHeaderLine('foo')); - $this->assertSame(['Bar', 'Baz', 'Bam'], $r2->getHeader('foo')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $response2->getHeaders()); + $this->assertSame('Bar,Baz,Bam', $response2->getHeaderLine('foo')); + $this->assertSame(['Bar', 'Baz', 'Bam'], $response2->getHeader('foo')); } public function testWithAddedHeaderThatDoesNotExist() { - $r = new Response(200, ['Foo' => 'Bar']); + $response = new Response(200, ['Foo' => 'Bar']); + $response2 = $response->withAddedHeader('nEw', 'Baz'); - $r2 = $r->withAddedHeader('nEw', 'Baz'); - $this->assertSame(['Foo' => ['Bar']], $r->getHeaders()); - $this->assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $r2->getHeaders()); - $this->assertSame('Baz', $r2->getHeaderLine('new')); - $this->assertSame(['Baz'], $r2->getHeader('new')); + $this->assertSame(['Foo' => ['Bar']], $response->getHeaders()); + $this->assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $response2->getHeaders()); + $this->assertSame('Baz', $response2->getHeaderLine('new')); + $this->assertSame(['Baz'], $response2->getHeader('new')); } public function testWithoutHeaderThatExists() { - $r = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']); + $response = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']); + $response2 = $response->withoutHeader('foO'); - $r2 = $r->withoutHeader('foO'); - $this->assertTrue($r->hasHeader('foo')); - $this->assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $r->getHeaders()); - $this->assertFalse($r2->hasHeader('foo')); - $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders()); + $this->assertTrue($response->hasHeader('foo')); + $this->assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $response->getHeaders()); + $this->assertFalse($response2->hasHeader('foo')); + $this->assertSame(['Baz' => ['Bam']], $response2->getHeaders()); } public function testWithoutHeaderThatDoesNotExist() { - $r = new Response(200, ['Baz' => 'Bam']); + $response = new Response(200, ['Baz' => 'Bam']); + $response2 = $response->withoutHeader('foO'); - $r2 = $r->withoutHeader('foO'); - $this->assertSame($r, $r2); - $this->assertFalse($r2->hasHeader('foo')); - $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders()); + $this->assertSame($response, $response2); + $this->assertFalse($response2->hasHeader('foo')); + $this->assertSame(['Baz' => ['Bam']], $response2->getHeaders()); } public function testSameInstanceWhenRemovingMissingHeader() { - $r = new Response(); + $response = new Response(); - $this->assertSame($r, $r->withoutHeader('foo')); + $this->assertSame($response, $response->withoutHeader('foo')); } public function testHeaderValuesAreTrimmed() { - $r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]); - $r2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t "); - $r3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t "); - - foreach ([$r1, $r2, $r3] as $r) { - $this->assertSame(['OWS' => ['Foo']], $r->getHeaders()); - $this->assertSame('Foo', $r->getHeaderLine('OWS')); - $this->assertSame(['Foo'], $r->getHeader('OWS')); + $response1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]); + $response2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t "); + $response3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t "); + + foreach ([$response1, $response2, $response3] as $response) { + $this->assertSame(['OWS' => ['Foo']], $response->getHeaders()); + $this->assertSame('Foo', $response->getHeaderLine('OWS')); + $this->assertSame(['Foo'], $response->getHeader('OWS')); } } } diff --git a/src/Viserio/Http/Tests/ServerRequestFactoryTest.php b/src/Viserio/Http/Tests/ServerRequestFactoryTest.php index ce0007e86..96160eb79 100644 --- a/src/Viserio/Http/Tests/ServerRequestFactoryTest.php +++ b/src/Viserio/Http/Tests/ServerRequestFactoryTest.php @@ -76,6 +76,7 @@ public function testGetUriFromGlobals($expected, $serverParams) { $_SERVER = $serverParams; $serverRequest = (new ServerRequestFactory())->createServerRequestFromGlobals(); + $this->assertEquals(new Uri($expected), $serverRequest->getUri()); } diff --git a/src/Viserio/Http/Tests/ServerRequestTest.php b/src/Viserio/Http/Tests/ServerRequestTest.php index 3dae15077..09b7a2583 100644 --- a/src/Viserio/Http/Tests/ServerRequestTest.php +++ b/src/Viserio/Http/Tests/ServerRequestTest.php @@ -12,6 +12,9 @@ public function testUploadedFiles() $request1 = new ServerRequest('', 'GET'); $files = [ 'file' => new UploadedFile('test', 123, UPLOAD_ERR_OK), + 'file2' => [ + new UploadedFile('test', 123, UPLOAD_ERR_OK), + ], ]; $request2 = $request1->withUploadedFiles($files); diff --git a/src/Viserio/Http/Tests/Stream/BufferStreamTest.php b/src/Viserio/Http/Tests/Stream/BufferStreamTest.php index e809ab2da..a9ac439b4 100644 --- a/src/Viserio/Http/Tests/Stream/BufferStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/BufferStreamTest.php @@ -8,26 +8,26 @@ class BufferStreamTest extends \PHPUnit_Framework_TestCase { public function testHasMetadata() { - $b = new BufferStream(10); + $buffer = new BufferStream(10); - $this->assertTrue($b->isReadable()); - $this->assertTrue($b->isWritable()); - $this->assertFalse($b->isSeekable()); - $this->assertEquals(null, $b->getMetadata('foo')); - $this->assertEquals(10, $b->getMetadata('hwm')); - $this->assertEquals([], $b->getMetadata()); + $this->assertTrue($buffer->isReadable()); + $this->assertTrue($buffer->isWritable()); + $this->assertFalse($buffer->isSeekable()); + $this->assertEquals(null, $buffer->getMetadata('foo')); + $this->assertEquals(10, $buffer->getMetadata('hwm')); + $this->assertEquals([], $buffer->getMetadata()); } public function testRemovesReadDataFromBuffer() { - $b = new BufferStream(); + $buffer = new BufferStream(); - $this->assertEquals(3, $b->write('foo')); - $this->assertEquals(3, $b->getSize()); - $this->assertFalse($b->eof()); - $this->assertEquals('foo', $b->read(10)); - $this->assertTrue($b->eof()); - $this->assertEquals('', $b->read(10)); + $this->assertEquals(3, $buffer->write('foo')); + $this->assertEquals(3, $buffer->getSize()); + $this->assertFalse($buffer->eof()); + $this->assertEquals('foo', $buffer->read(10)); + $this->assertTrue($buffer->eof()); + $this->assertEquals('', $buffer->read(10)); } /** @@ -36,33 +36,36 @@ public function testRemovesReadDataFromBuffer() */ public function testCanCastToStringOrGetContents() { - $b = new BufferStream(); - $b->write('foo'); - $b->write('baz'); - $this->assertEquals('foo', $b->read(3)); - $b->write('bar'); - $this->assertEquals('bazbar', (string) $b); - $b->tell(); + $buffer = new BufferStream(); + $buffer->write('foo'); + $buffer->write('baz'); + + $this->assertEquals('foo', $buffer->read(3)); + + $buffer->write('bar'); + + $this->assertEquals('bazbar', (string) $buffer); + $buffer->tell(); } public function testDetachClearsBuffer() { - $b = new BufferStream(); - $b->write('foo'); - $b->detach(); + $buffer = new BufferStream(); + $buffer->write('foo'); + $buffer->detach(); - $this->assertTrue($b->eof()); - $this->assertEquals(3, $b->write('abc')); - $this->assertEquals('abc', $b->read(10)); + $this->assertTrue($buffer->eof()); + $this->assertEquals(3, $buffer->write('abc')); + $this->assertEquals('abc', $buffer->read(10)); } public function testExceedingHighwaterMarkReturnsFalseButStillBuffers() { - $b = new BufferStream(5); + $buffer = new BufferStream(5); - $this->assertEquals(3, $b->write('hi ')); - $this->assertFalse($b->write('hello')); - $this->assertEquals('hi hello', (string) $b); - $this->assertEquals(4, $b->write('test')); + $this->assertEquals(3, $buffer->write('hi ')); + $this->assertFalse($buffer->write('hello')); + $this->assertEquals('hi hello', (string) $buffer); + $this->assertEquals(4, $buffer->write('test')); } } diff --git a/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php b/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php index 5d0258f8e..9e96c6c57 100644 --- a/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php @@ -3,7 +3,7 @@ namespace Viserio\Http\Tests\Stream; use Viserio\Http\Stream\ByteCountingStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class ByteCountingStreamTest extends \PHPUnit_Framework_TestCase { @@ -13,7 +13,7 @@ class ByteCountingStreamTest extends \PHPUnit_Framework_TestCase */ public function testEnsureNonNegativeByteCount() { - new ByteCountingStream(Util::getStream('testing'), -2); + new ByteCountingStream((new StreamFactory())->createStreamFromString('testing'), -2); } /** @@ -22,19 +22,19 @@ public function testEnsureNonNegativeByteCount() */ public function testEnsureValidByteCountNumber() { - new ByteCountingStream(Util::getStream('testing'), 10); + new ByteCountingStream((new StreamFactory())->createStreamFromString('testing'), 10); } public function testByteCountingReadWhenAvailable() { - $testStream = new ByteCountingStream(Util::getStream('foo bar test'), 8); + $testStream = new ByteCountingStream((new StreamFactory())->createStreamFromString('foo bar test'), 8); $this->assertEquals('foo ', $testStream->read(4)); $this->assertEquals('bar ', $testStream->read(4)); $this->assertEquals('', $testStream->read(4)); $testStream->close(); - $testStream = new ByteCountingStream(Util::getStream('testing'), 5); + $testStream = new ByteCountingStream((new StreamFactory())->createStreamFromString('testing'), 5); $testStream->seek(4); $this->assertEquals('ing', $testStream->read(5)); @@ -48,7 +48,7 @@ public function testByteCountingReadWhenAvailable() */ public function testEnsureStopReadWhenHitEof() { - $testStream = new ByteCountingStream(Util::getStream('abc'), 3); + $testStream = new ByteCountingStream((new StreamFactory())->createStreamFromString('abc'), 3); $testStream->seek(3); $testStream->read(3); } @@ -59,7 +59,7 @@ public function testEnsureStopReadWhenHitEof() */ public function testEnsureReadUnclosedStream() { - $body = Util::getStream('closed'); + $body = (new StreamFactory())->createStreamFromString('closed'); $closedStream = new ByteCountingStream($body, 5); $body->close(); $closedStream->read(3); diff --git a/src/Viserio/Http/Tests/Stream/FnStreamTest.php b/src/Viserio/Http/Tests/Stream/FnStreamTest.php index 9dba6a678..0acbdcc4f 100644 --- a/src/Viserio/Http/Tests/Stream/FnStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/FnStreamTest.php @@ -3,7 +3,7 @@ namespace Viserio\Http\Tests\Stream; use Viserio\Http\Stream\FnStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class FnStreamTest extends \PHPUnit_Framework_TestCase { @@ -18,7 +18,7 @@ public function testThrowsWhenNotImplemented() public function testProxiesToFunction() { - $s = new FnStream([ + $stream = new FnStream([ 'read' => function ($len) { $this->assertEquals(3, $len); @@ -26,70 +26,70 @@ public function testProxiesToFunction() }, ]); - $this->assertEquals('foo', $s->read(3)); + $this->assertEquals('foo', $stream->read(3)); } public function testCanCloseOnDestruct() { $called = false; - $s = new FnStream([ + $stream = new FnStream([ 'close' => function () use (&$called) { $called = true; }, ]); - unset($s); + unset($stream); $this->assertTrue($called); } - public function testDoesNotRequireClose() + public function doesNotRequireClose() { - $s = new FnStream([]); - unset($s); + $stream = new FnStream([]); + unset($stream); } public function testDecoratesStream() { - $a = Util::getStream('foo'); - $b = FnStream::decorate($a, []); + $stream1 = (new StreamFactory())->createStreamFromString('foo'); + $stream2 = FnStream::decorate($stream1, []); - $this->assertEquals(3, $b->getSize()); - $this->assertEquals($b->isWritable(), true); - $this->assertEquals($b->isReadable(), true); - $this->assertEquals($b->isSeekable(), true); - $this->assertEquals($b->read(3), 'foo'); - $this->assertEquals($b->tell(), 3); - $this->assertEquals($a->tell(), 3); - $this->assertSame('', $a->read(1)); - $this->assertEquals($b->eof(), true); - $this->assertEquals($a->eof(), true); - $b->seek(0); - $this->assertEquals('foo', (string) $b); - $b->seek(0); - $this->assertEquals('foo', $b->getContents()); - $this->assertEquals($a->getMetadata(), $b->getMetadata()); - $b->seek(0, SEEK_END); - $b->write('bar'); - $this->assertEquals('foobar', (string) $b); - $this->assertInternalType('resource', $b->detach()); - $b->close(); + $this->assertEquals(3, $stream2->getSize()); + $this->assertEquals($stream2->isWritable(), true); + $this->assertEquals($stream2->isReadable(), true); + $this->assertEquals($stream2->isSeekable(), true); + $this->assertEquals($stream2->read(3), 'foo'); + $this->assertEquals($stream2->tell(), 3); + $this->assertEquals($stream1->tell(), 3); + $this->assertSame('', $stream1->read(1)); + $this->assertEquals($stream2->eof(), true); + $this->assertEquals($stream1->eof(), true); + $stream2->seek(0); + $this->assertEquals('foo', (string) $stream2); + $stream2->seek(0); + $this->assertEquals('foo', $stream2->getContents()); + $this->assertEquals($stream1->getMetadata(), $stream2->getMetadata()); + $stream2->seek(0, SEEK_END); + $stream2->write('bar'); + $this->assertEquals('foobar', (string) $stream2); + $this->assertInternalType('resource', $stream2->detach()); + $stream2->close(); } public function testDecoratesWithCustomizations() { $called = false; - $a = Util::getStream('foo'); - $b = FnStream::decorate($a, [ - 'read' => function ($len) use (&$called, $a) { + $stream1 = (new StreamFactory())->createStreamFromString('foo'); + $stream2 = FnStream::decorate($stream1, [ + 'read' => function ($len) use (&$called, $stream1) { $called = true; - return $a->read($len); + return $stream1->read($len); }, ]); - $this->assertEquals('foo', $b->read(3)); + $this->assertEquals('foo', $stream2->read(3)); $this->assertTrue($called); } } diff --git a/src/Viserio/Http/Tests/Stream/LazyOpenStreamTest.php b/src/Viserio/Http/Tests/Stream/LazyOpenStreamTest.php index 1cccfe626..06dabd22d 100644 --- a/src/Viserio/Http/Tests/Stream/LazyOpenStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/LazyOpenStreamTest.php @@ -26,41 +26,42 @@ public function tearDown() public function testOpensLazily() { - $l = new LazyOpenStream($this->fname, 'w+'); - $l->write('foo'); + $lazy = new LazyOpenStream($this->fname, 'w+'); + $lazy->write('foo'); - $this->assertInternalType('array', $l->getMetadata()); + $this->assertInternalType('array', $lazy->getMetadata()); $this->assertFileExists($this->fname); $this->assertEquals('foo', file_get_contents($this->fname)); - $this->assertEquals('foo', (string) $l); + $this->assertEquals('foo', (string) $lazy); } public function testProxiesToFile() { file_put_contents($this->fname, 'foo'); - $l = new LazyOpenStream($this->fname, 'r'); - - $this->assertEquals('foo', $l->read(4)); - $this->assertTrue($l->eof()); - $this->assertEquals(3, $l->tell()); - $this->assertTrue($l->isReadable()); - $this->assertTrue($l->isSeekable()); - $this->assertFalse($l->isWritable()); - - $l->seek(1); - $this->assertEquals('oo', $l->getContents()); - $this->assertEquals('foo', (string) $l); - $this->assertEquals(3, $l->getSize()); - $this->assertInternalType('array', $l->getMetadata()); - - $l->close(); + $lazy = new LazyOpenStream($this->fname, 'r'); + + $this->assertEquals('foo', $lazy->read(4)); + $this->assertTrue($lazy->eof()); + $this->assertEquals(3, $lazy->tell()); + $this->assertTrue($lazy->isReadable()); + $this->assertTrue($lazy->isSeekable()); + $this->assertFalse($lazy->isWritable()); + + $lazy->seek(1); + + $this->assertEquals('oo', $lazy->getContents()); + $this->assertEquals('foo', (string) $lazy); + $this->assertEquals(3, $lazy->getSize()); + $this->assertInternalType('array', $lazy->getMetadata()); + + $lazy->close(); } public function testDetachesUnderlyingStream() { file_put_contents($this->fname, 'foo'); - $l = new LazyOpenStream($this->fname, 'r'); - $r = $l->detach(); + $lazy = new LazyOpenStream($this->fname, 'r'); + $r = $lazy->detach(); $this->assertInternalType('resource', $r); fseek($r, 0); diff --git a/src/Viserio/Http/Tests/Stream/LimitStreamTest.php b/src/Viserio/Http/Tests/Stream/LimitStreamTest.php index 220e2bf15..84f25582b 100644 --- a/src/Viserio/Http/Tests/Stream/LimitStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/LimitStreamTest.php @@ -7,7 +7,7 @@ use Viserio\Http\Stream\FnStream; use Viserio\Http\Stream\LimitStream; use Viserio\Http\Stream\NoSeekStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class LimitStreamTest extends \PHPUnit_Framework_TestCase { @@ -19,17 +19,19 @@ class LimitStreamTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->decorated = Util::getStream(fopen(__FILE__, 'r')); + $this->decorated = (new StreamFactory())->createStreamFromResource(fopen(__FILE__, 'r')); $this->body = new LimitStream($this->decorated, 10, 3); } public function testReturnsSubset() { - $body = new LimitStream(Util::getStream('foo'), -1, 1); + $body = new LimitStream((new StreamFactory())->createStreamFromString('foo'), -1, 1); $this->assertEquals('oo', (string) $body); $this->assertTrue($body->eof()); + $body->seek(0); + $this->assertFalse($body->eof()); $this->assertEquals('oo', $body->read(100)); $this->assertSame('', $body->read(1)); @@ -38,7 +40,7 @@ public function testReturnsSubset() public function testReturnsSubsetWhenCastToString() { - $body = Util::getStream('foo_baz_bar'); + $body = (new StreamFactory())->createStreamFromString('foo_baz_bar'); $limited = new LimitStream($body, 3, 4); $this->assertEquals('baz', (string) $limited); @@ -50,12 +52,12 @@ public function testReturnsSubsetWhenCastToString() */ public function testEnsuresPositionCanBeekSeekedTo() { - new LimitStream(Util::getStream(''), 0, 10); + new LimitStream((new StreamFactory())->createStreamFromString(''), 0, 10); } public function testReturnsSubsetOfEmptyBodyWhenCastToString() { - $body = Util::getStream('01234567891234'); + $body = (new StreamFactory())->createStreamFromString('01234567891234'); $limited = new LimitStream($body, 0, 10); $this->assertEquals('', (string) $limited); @@ -63,7 +65,7 @@ public function testReturnsSubsetOfEmptyBodyWhenCastToString() public function testReturnsSpecificSubsetOBodyWhenCastToString() { - $body = Util::getStream('0123456789abcdef'); + $body = (new StreamFactory())->createStreamFromString('0123456789abcdef'); $limited = new LimitStream($body, 3, 10); $this->assertEquals('abc', (string) $limited); @@ -83,16 +85,19 @@ public function testAllowsBoundedSeek() $this->body->seek(0); $this->assertEquals(0, $this->body->tell()); $this->assertEquals(3, $this->decorated->tell()); + try { $this->body->seek(-10); $this->fail(); } catch (RuntimeException $e) { } + $this->assertEquals(0, $this->body->tell()); $this->assertEquals(3, $this->decorated->tell()); $this->body->seek(5); $this->assertEquals(5, $this->body->tell()); $this->assertEquals(8, $this->decorated->tell()); + // Fail try { $this->body->seek(1000, SEEK_END); @@ -104,10 +109,13 @@ public function testAllowsBoundedSeek() public function testReadsOnlySubsetOfData() { $data = $this->body->read(100); + $this->assertEquals(10, strlen($data)); $this->assertSame('', $this->body->read(1000)); $this->body->setOffset(10); + $newData = $this->body->read(100); + $this->assertEquals(10, strlen($newData)); $this->assertNotSame($data, $newData); } @@ -118,26 +126,29 @@ public function testReadsOnlySubsetOfData() */ public function testThrowsWhenCurrentGreaterThanOffsetSeek() { - $a = Util::getStream('foo_bar'); - $b = new NoSeekStream($a); - $c = new LimitStream($b); - $a->getContents(); - $c->setOffset(2); + $stream1 = (new StreamFactory())->createStreamFromString('foo_bar'); + $stream2 = new NoSeekStream($stream1); + $stream3 = new LimitStream($stream2); + + $stream1->getContents(); + $stream3->setOffset(2); } public function testCanGetContentsWithoutSeeking() { - $a = Util::getStream('foo_bar'); - $b = new NoSeekStream($a); - $c = new LimitStream($b); + $stream1 = (new StreamFactory())->createStreamFromString('foo_bar'); + $stream2 = new NoSeekStream($stream1); + $stream3 = new LimitStream($stream2); - $this->assertEquals('foo_bar', $c->getContents()); + $this->assertEquals('foo_bar', $stream3->getContents()); } public function testClaimsConsumedWhenReadLimitIsReached() { $this->assertFalse($this->body->eof()); + $this->body->read(1000); + $this->assertTrue($this->body->eof()); } @@ -148,28 +159,28 @@ public function testContentLengthIsBounded() public function testGetContentsIsBasedOnSubset() { - $body = new LimitStream(Util::getStream('foobazbar'), 3, 3); + $body = new LimitStream((new StreamFactory())->createStreamFromString('foobazbar'), 3, 3); $this->assertEquals('baz', $body->getContents()); } public function testReturnsNullIfSizeCannotBeDetermined() { - $a = new FnStream([ + $stream = new FnStream([ 'getSize' => function () { }, 'tell' => function () { return 0; }, ]); - $b = new LimitStream($a); + $stream2 = new LimitStream($stream); - $this->assertNull($b->getSize()); + $this->assertNull($stream2->getSize()); } public function testLengthLessOffsetWhenNoLimitSize() { - $a = Util::getStream('foo_bar'); + $a = (new StreamFactory())->createStreamFromString('foo_bar'); $b = new LimitStream($a, -1, 4); $this->assertEquals(3, $b->getSize()); diff --git a/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php b/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php index b1dff0ce4..55b756983 100644 --- a/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php @@ -2,8 +2,9 @@ declare(strict_types=1); namespace Viserio\Http\Tests\Stream; +use Psr\Http\Message\StreamInterface; use Viserio\Http\Stream\NoSeekStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class NoSeekStreamTest extends \PHPUnit_Framework_TestCase { @@ -13,7 +14,7 @@ class NoSeekStreamTest extends \PHPUnit_Framework_TestCase */ public function testCannotSeek() { - $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + $s = $this->getMockBuilder(StreamInterface::class) ->setMethods(['isSeekable', 'seek']) ->getMockForAbstractClass(); @@ -31,7 +32,7 @@ public function testCannotSeek() */ public function testHandlesClose() { - $s = Util::getStream('foo'); + $s = (new StreamFactory())->createStreamFromString('foo'); $wrapped = new NoSeekStream($s); $wrapped->close(); $wrapped->write('foo'); diff --git a/src/Viserio/Http/Tests/Stream/PhpInputStreamTest.php b/src/Viserio/Http/Tests/Stream/PhpInputStreamTest.php index 47d845f47..09b4d7da7 100644 --- a/src/Viserio/Http/Tests/Stream/PhpInputStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/PhpInputStreamTest.php @@ -30,6 +30,7 @@ public function getFileContents() public function assertStreamContents($test, $message = null) { $content = $this->getFileContents(); + $this->assertEquals($content, $test, $message); } @@ -54,12 +55,14 @@ public function testGetContentsReturnsRemainingContentsOfStream() $start = $this->stream->read(128); $remainder = $this->stream->getContents(); $contents = $this->getFileContents(); + $this->assertEquals(substr($contents, 128), $remainder); } public function testCastingToStringReturnsFullContentsRegardlesOfPriorReads() { $start = $this->stream->read(128); + $this->assertStreamContents($this->stream->__toString()); } diff --git a/src/Viserio/Http/Tests/Stream/PumpStreamTest.php b/src/Viserio/Http/Tests/Stream/PumpStreamTest.php index c8c3c1c38..ee0810e05 100644 --- a/src/Viserio/Http/Tests/Stream/PumpStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/PumpStreamTest.php @@ -5,79 +5,81 @@ use RuntimeException; use Viserio\Http\Stream\LimitStream; use Viserio\Http\Stream\PumpStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class PumpStreamTest extends \PHPUnit_Framework_TestCase { public function testHasMetadataAndSize() { - $p = new PumpStream(function () { + $pump = new PumpStream(function () { }, [ 'metadata' => ['foo' => 'bar'], 'size' => 100, ]); - $this->assertEquals('bar', $p->getMetadata('foo')); - $this->assertEquals(['foo' => 'bar'], $p->getMetadata()); - $this->assertEquals(100, $p->getSize()); + $this->assertEquals('bar', $pump->getMetadata('foo')); + $this->assertEquals(['foo' => 'bar'], $pump->getMetadata()); + $this->assertEquals(100, $pump->getSize()); } public function testCanReadFromCallable() { - $p = Util::getStream(function ($size) { + $pump = (new StreamFactory())->createStreamFromCallback(function ($size) { return 'a'; }); - $this->assertEquals('a', $p->read(1)); - $this->assertEquals(1, $p->tell()); - $this->assertEquals('aaaaa', $p->read(5)); - $this->assertEquals(6, $p->tell()); + $this->assertEquals('a', $pump->read(1)); + $this->assertEquals(1, $pump->tell()); + $this->assertEquals('aaaaa', $pump->read(5)); + $this->assertEquals(6, $pump->tell()); } public function testStoresExcessDataInBuffer() { $called = []; - $p = Util::getStream(function ($size) use (&$called) { + $pump = (new StreamFactory())->createStreamFromCallback(function ($size) use (&$called) { $called[] = $size; return 'abcdef'; }); - $this->assertEquals('a', $p->read(1)); - $this->assertEquals('b', $p->read(1)); - $this->assertEquals('cdef', $p->read(4)); - $this->assertEquals('abcdefabc', $p->read(9)); + $this->assertEquals('a', $pump->read(1)); + $this->assertEquals('b', $pump->read(1)); + $this->assertEquals('cdef', $pump->read(4)); + $this->assertEquals('abcdefabc', $pump->read(9)); $this->assertEquals([1, 9, 3], $called); } public function testInifiniteStreamWrappedInLimitStream() { - $p = Util::getStream(function () { + $pump = (new StreamFactory())->createStreamFromCallback(function () { return 'a'; }); - $s = new LimitStream($p, 5); + $s = new LimitStream($pump, 5); $this->assertEquals('aaaaa', (string) $s); } public function testDescribesCapabilities() { - $p = Util::getStream(function () { + $pump = (new StreamFactory())->createStreamFromCallback(function () { }); - $this->assertTrue($p->isReadable()); - $this->assertFalse($p->isSeekable()); - $this->assertFalse($p->isWritable()); - $this->assertNull($p->getSize()); - $this->assertEquals('', $p->getContents()); - $this->assertEquals('', (string) $p); - $p->close(); - $this->assertEquals('', $p->read(10)); - $this->assertTrue($p->eof()); + $this->assertTrue($pump->isReadable()); + $this->assertFalse($pump->isSeekable()); + $this->assertFalse($pump->isWritable()); + $this->assertNull($pump->getSize()); + $this->assertEquals('', $pump->getContents()); + $this->assertEquals('', (string) $pump); + + $pump->close(); + + $this->assertEquals('', $pump->read(10)); + $this->assertTrue($pump->eof()); try { - $this->assertFalse($p->write('aa')); + $this->assertFalse($pump->write('aa')); $this->fail(); } catch (RuntimeException $e) { } diff --git a/src/Viserio/Http/Tests/StreamFactoryTest.php b/src/Viserio/Http/Tests/StreamFactoryTest.php new file mode 100644 index 000000000..c14e1b875 --- /dev/null +++ b/src/Viserio/Http/Tests/StreamFactoryTest.php @@ -0,0 +1,89 @@ +createStreamFromResource($resource); + + $this->assertEquals(10, $stream->tell()); + + $stream->close(); + } + + public function testCreatesWithFactory() + { + $streamFactory = new StreamFactory(); + $stream = $streamFactory->createStreamFromString('foo'); + + $this->assertInstanceOf(Stream::class, $stream); + $this->assertEquals('foo', $stream->getContents()); + + $stream->close(); + } + + public function testFactoryCreatesFromEmptyString() + { + $streamFactory = new StreamFactory(); + $this->assertInstanceOf(Stream::class, $streamFactory->createStream()); + } + + public function testFactoryCreatesFromResource() + { + $resource = fopen(__FILE__, 'r'); + $streamFactory = new StreamFactory(); + $stream = $streamFactory->createStreamFromResource($resource); + + $this->assertInstanceOf(Stream::class, $stream); + $this->assertSame(file_get_contents(__FILE__), (string) $stream); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid resource type: string. + */ + public function testFactoryCreatesFromResourceToThorwException() + { + $streamFactory = new StreamFactory(); + $stream = $streamFactory->createStreamFromResource('foo'); + } + + public function testCanCreateCallableBasedStream() + { + $resource = new ArrayIterator(['foo', 'bar', '123']); + + $streamFactory = new StreamFactory(); + $stream = $streamFactory->createStreamFromCallback(function () use ($resource) { + if (! $resource->valid()) { + return false; + } + + $result = $resource->current(); + $resource->next(); + + return $result; + }); + + $this->assertInstanceOf(PumpStream::class, $stream); + $this->assertEquals('foo', $stream->read(3)); + $this->assertFalse($stream->eof()); + $this->assertEquals('b', $stream->read(1)); + $this->assertEquals('a', $stream->read(1)); + $this->assertEquals('r12', $stream->read(3)); + $this->assertFalse($stream->eof()); + $this->assertEquals('3', $stream->getContents()); + $this->assertTrue($stream->eof()); + $this->assertEquals(9, $stream->tell()); + } +} diff --git a/src/Viserio/Http/Tests/StreamTest.php b/src/Viserio/Http/Tests/StreamTest.php index 6ff9ea3c2..51b3c0d1c 100644 --- a/src/Viserio/Http/Tests/StreamTest.php +++ b/src/Viserio/Http/Tests/StreamTest.php @@ -5,7 +5,7 @@ use Exception; use Viserio\Http\Stream; use Viserio\Http\Stream\NoSeekStream; -use Viserio\Http\Util; +use Viserio\Http\StreamFactory; class StreamTest extends \PHPUnit_Framework_TestCase { @@ -179,7 +179,7 @@ public function testCloseClearProperties() public function testDoesNotThrowInToString() { - $s = Util::getStream('foo'); + $s = (new StreamFactory())->createStreamFromString('foo'); $s = new NoSeekStream($s); $this->assertEquals('foo', (string) $s); diff --git a/src/Viserio/Http/Tests/UploadedFileTest.php b/src/Viserio/Http/Tests/UploadedFileTest.php index 791904ba6..a05ea9e42 100644 --- a/src/Viserio/Http/Tests/UploadedFileTest.php +++ b/src/Viserio/Http/Tests/UploadedFileTest.php @@ -4,8 +4,8 @@ use ReflectionProperty; use Viserio\Http\Stream; +use Viserio\Http\StreamFactory; use Viserio\Http\UploadedFile; -use Viserio\Http\Util; class UploadedFileTest extends \PHPUnit_Framework_TestCase { @@ -42,7 +42,7 @@ public function invalidStreams() /** * @dataProvider invalidStreams * - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException */ public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile) { @@ -107,7 +107,7 @@ public function invalidFilenamesAndMediaTypes() /** * @dataProvider invalidFilenamesAndMediaTypes * - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException * @expectedExceptionMessage filename */ public function testRaisesExceptionOnInvalidClientFilename($filename) @@ -118,7 +118,7 @@ public function testRaisesExceptionOnInvalidClientFilename($filename) /** * @dataProvider invalidFilenamesAndMediaTypes * - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException * @expectedExceptionMessage media type */ public function testRaisesExceptionOnInvalidClientMediaType($mediaType) @@ -158,7 +158,7 @@ public function testGetStreamReturnsStreamForFile() public function testSuccessful() { - $stream = Util::getStream('Foo bar!'); + $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain'); $this->assertEquals($stream->getSize(), $upload->getSize()); @@ -189,12 +189,12 @@ public function invalidMovePaths() /** * @dataProvider invalidMovePaths * - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException * @expectedExceptionMessage path */ public function testMoveRaisesExceptionForInvalidPath($path) { - $stream = Util::getStream('Foo bar!'); + $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); $this->cleanup[] = $path; @@ -208,7 +208,7 @@ public function testMoveRaisesExceptionForInvalidPath($path) */ public function testMoveCannotBeCalledMoreThanOnce() { - $stream = Util::getStream('Foo bar!'); + $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); @@ -226,7 +226,7 @@ public function testMoveCannotBeCalledMoreThanOnce() */ public function testCannotRetrieveStreamAfterMove() { - $stream = Util::getStream('Foo bar!'); + $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); diff --git a/src/Viserio/Http/Tests/Uri/Filter/PathTest.php b/src/Viserio/Http/Tests/Uri/Filter/PathTest.php index 1360afee4..8bb2f69db 100644 --- a/src/Viserio/Http/Tests/Uri/Filter/PathTest.php +++ b/src/Viserio/Http/Tests/Uri/Filter/PathTest.php @@ -15,6 +15,7 @@ class PathTest extends \PHPUnit_Framework_TestCase public function testFilter($raw, $parsed) { $path = new Path(); + $this->assertSame($parsed, $path->filter($raw)); } diff --git a/src/Viserio/Http/Tests/UriTest.php b/src/Viserio/Http/Tests/UriTest.php index 13d7dd762..598fcd62e 100644 --- a/src/Viserio/Http/Tests/UriTest.php +++ b/src/Viserio/Http/Tests/UriTest.php @@ -17,12 +17,12 @@ public function createDefaultUri() public function testUriImplementsInterface() { - $this->assertInstanceOf('Psr\Http\Message\UriInterface', new Uri()); + $this->assertInstanceOf(UriInterface::class, new Uri()); } /** - * @dataProvider testInvalidURI - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException + * @dataProvider invalidURIProvider * * @param string $uri */ @@ -31,7 +31,7 @@ public function testParseFailed($uri) new Uri($uri); } - public function testInvalidURI() + public function invalidURIProvider() { return [ 'invalid uri' => ['///'], @@ -66,6 +66,7 @@ public function testCanTransformAndRetrievePartsIndividually() ->withPath('/path/123') ->withQuery('q=abc') ->withFragment('test'); + $this->assertSame('https', $uri->getScheme()); $this->assertSame('user:pass@example.com:8080', $uri->getAuthority()); $this->assertSame('user:pass', $uri->getUserInfo()); @@ -166,10 +167,12 @@ public function testFragmentMustHaveCorrectType() public function testSchemeIsNormalizedToLowercase() { $uri = new Uri('HTTP://example.com'); + $this->assertSame('http', $uri->getScheme()); $this->assertSame('http://example.com', (string) $uri); $uri = (new Uri('//example.com'))->withScheme('HTTP'); + $this->assertSame('http', $uri->getScheme()); $this->assertSame('http://example.com', (string) $uri); } @@ -178,19 +181,23 @@ public function testPortIsNullIfStandardPortForScheme() { // HTTPS standard port $uri = new Uri('https://example.com:443'); + $this->assertNull($uri->getPort()); $this->assertSame('example.com', $uri->getAuthority()); $uri = (new Uri('https://example.com'))->withPort(443); + $this->assertNull($uri->getPort()); $this->assertSame('example.com', $uri->getAuthority()); // HTTP standard port $uri = new Uri('http://example.com:80'); + $this->assertNull($uri->getPort()); $this->assertSame('example.com', $uri->getAuthority()); $uri = (new Uri('http://example.com'))->withPort(80); + $this->assertNull($uri->getPort()); $this->assertSame('example.com', $uri->getAuthority()); } @@ -198,6 +205,7 @@ public function testPortIsNullIfStandardPortForScheme() public function testPortIsReturnedIfSchemeUnknown() { $uri = (new Uri('//example.com'))->withPort(80); + $this->assertSame(80, $uri->getPort()); $this->assertSame('example.com:80', $uri->getAuthority()); } @@ -205,15 +213,19 @@ public function testPortIsReturnedIfSchemeUnknown() public function testStandardPortIsNullIfSchemeChanges() { $uri = new Uri('http://example.com:443'); + $this->assertSame('http', $uri->getScheme()); $this->assertSame(443, $uri->getPort()); + $uri = $uri->withScheme('https'); + $this->assertNull($uri->getPort()); } public function testPortPassedAsStringIsCastedToInt() { $uri = (new Uri('//example.com'))->withPort('8080'); + $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer'); $this->assertSame('example.com:8080', $uri->getAuthority()); } @@ -221,6 +233,7 @@ public function testPortPassedAsStringIsCastedToInt() public function testPortCanBeRemoved() { $uri = (new Uri('http://example.com:8080'))->withPort(null); + $this->assertNull($uri->getPort()); $this->assertSame('http://example.com', (string) $uri); } @@ -232,15 +245,18 @@ public function testPortCanBeRemoved() public function testAuthorityWithUserInfoOrPortButWithoutHost() { $uri = $this->createDefaultUri()->withUserInfo('user', 'pass'); + $this->assertSame('user:pass', $uri->getUserInfo()); $this->assertSame('user:pass@', $uri->getAuthority()); $uri = $uri->withPort(8080); + $this->assertSame(8080, $uri->getPort()); $this->assertSame('user:pass@:8080', $uri->getAuthority()); $this->assertSame('//user:pass@:8080', (string) $uri); $uri = $uri->withUserInfo(''); + $this->assertSame(':8080', $uri->getAuthority()); } @@ -312,6 +328,7 @@ public function uriComponentsEncodingProvider() public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output) { $uri = new Uri($input); + $this->assertSame($path, $uri->getPath()); $this->assertSame($query, $uri->getQuery()); $this->assertSame($fragment, $uri->getFragment()); @@ -321,6 +338,7 @@ public function testUriComponentsGetEncodedProperly($input, $path, $query, $frag public function testWithPathEncodesProperly() { $uri = $this->createDefaultUri()->withPath('/baz?#€/b%61r^bar'); + // Query and fragment delimiters and multibyte chars are encoded. $this->assertSame('/baz%3F%23%E2%82%AC/b%61r%5Ebar', $uri->getPath()); $this->assertSame('/baz%3F%23%E2%82%AC/b%61r%5Ebar', (string) $uri); @@ -329,6 +347,7 @@ public function testWithPathEncodesProperly() public function testWithQueryEncodesProperly() { $uri = $this->createDefaultUri()->withQuery('?=#&€=/&b%61r'); + // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to // construct such an URI. Also the "?" and "/" does not need to be encoded in the query. $this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery()); @@ -338,6 +357,7 @@ public function testWithQueryEncodesProperly() public function testWithFragmentEncodesProperly() { $uri = $this->createDefaultUri()->withFragment('#€?/b%61r'); + // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment. $this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment()); @@ -347,6 +367,7 @@ public function testWithFragmentEncodesProperly() public function testAllowsForRelativeUri() { $uri = $this->createDefaultUri()->withPath('foo'); + $this->assertSame('foo', $uri->getPath()); $this->assertSame('foo', (string) $uri); } @@ -357,6 +378,7 @@ public function testAllowsForRelativeUri() public function testPathStartingWithTwoSlashes() { $uri = new Uri('http://example.org//path-not-host.com'); + $this->assertSame('//path-not-host.com', $uri->getPath()); $uri = $uri->withScheme(''); @@ -401,6 +423,7 @@ public function testRelativeUriWithPathBeginngWithColonSegmentIsInvalid() public function testRelativeUriWithPathHavingColonSegment() { $uri = (new Uri('urn:/mailto:foo'))->withScheme(''); + $this->assertSame('/mailto:foo', $uri->getPath()); (new Uri('urn:mailto:foo'))->withScheme(''); @@ -409,6 +432,7 @@ public function testRelativeUriWithPathHavingColonSegment() public function testDefaultReturnValuesOfGetters() { $uri = new Uri(); + $this->assertSame('', $uri->getScheme()); $this->assertSame('', $uri->getAuthority()); $this->assertSame('', $uri->getUserInfo()); @@ -422,6 +446,7 @@ public function testDefaultReturnValuesOfGetters() public function testImmutability() { $uri = new Uri(); + $this->assertNotSame($uri, $uri->withScheme('https')); $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass')); $this->assertNotSame($uri, $uri->withHost('example.com')); @@ -437,7 +462,7 @@ public function testExtendingClassesInstantiates() // should not use late static binding to access private static members. // If they do, this will fatal. $this->assertInstanceOf( - '\Viserio\Http\Tests\Fixture\ExtendedUriTest', + ExtendedUriTest::class, new ExtendedUriTest('http://h:9/') ); } @@ -447,8 +472,8 @@ public function testExtendingClassesInstantiates() */ public function testHostnameMustBeLowerCasedAsPerPsr7Interface() { - $url = 'http://WwW.ExAmPlE.CoM'; - $uri = new Uri($url); + $uri = new Uri('http://WwW.ExAmPlE.CoM'); + $this->assertEquals('www.example.com', $uri->getHost()); } @@ -457,8 +482,8 @@ public function testHostnameMustBeLowerCasedAsPerPsr7Interface() */ public function testSchemeMustBeLowerCasedAsPerPsr7Interface() { - $url = 'hTtp://www.example.com'; - $uri = new Uri($url); + $uri = new Uri('hTtp://www.example.com'); + $this->assertEquals('http', $uri->getScheme()); } @@ -492,6 +517,7 @@ public function pathProvider() public function testGetQuery($query, $expected) { $uri = (new Uri())->withQuery($query); + $this->assertEquals($expected, $uri->getQuery(), 'Query must be normalized according to RFC3986'); } @@ -512,8 +538,8 @@ public function queryProvider() */ public function testUrlStandardNormalization() { - $url = 'hTtp://WwW.ExAmPlE.CoM/%a1/%7Epsr7/rocks'; - $uri = new Uri($url); + $uri = new Uri('hTtp://WwW.ExAmPlE.CoM/%a1/%7Epsr7/rocks'); + $this->assertEquals('http://www.example.com/%A1/~psr7/rocks', (string) $uri); } @@ -545,8 +571,8 @@ public function authorityProvider() */ public function testWithPortWithNullValue() { - $url = 'http://www.example.com:81'; - $uri = new Uri($url); + $uri = new Uri('http://www.example.com:81'); + $this->assertNull($uri->withPort(null)->getPort()); } @@ -665,6 +691,7 @@ public function invalidStringProvider() public function testGetHost($host, $expected) { $uri = $this->createDefaultUri()->withHost($host); + $this->assertEquals($expected, $uri->getHost(), 'Host must be normalized according to RFC3986'); } diff --git a/src/Viserio/Http/Tests/UtilTest.php b/src/Viserio/Http/Tests/UtilTest.php index 0e5f45dd9..127ff390f 100644 --- a/src/Viserio/Http/Tests/UtilTest.php +++ b/src/Viserio/Http/Tests/UtilTest.php @@ -2,11 +2,8 @@ declare(strict_types=1); namespace Viserio\Http\Tests; -use ArrayIterator; -use Viserio\Http\Stream; use Viserio\Http\Stream\FnStream; -use Viserio\Http\Stream\PumpStream; -use Viserio\Http\Tests\Fixture\HasToString; +use Viserio\Http\StreamFactory; use Viserio\Http\UploadedFile; use Viserio\Http\Util; @@ -14,7 +11,7 @@ class UtilTest extends \PHPUnit_Framework_TestCase { public function testCopiesToString() { - $s = Util::getStream('foobaz'); + $s = (new StreamFactory())->createStreamFromString('foobaz'); $this->assertEquals('foobaz', Util::copyToString($s)); $s->seek(0); @@ -25,7 +22,7 @@ public function testCopiesToString() public function testCopiesToStringStopsWhenReadFails() { - $s1 = Util::getStream('foobaz'); + $s1 = (new StreamFactory())->createStreamFromString('foobaz'); $s1 = FnStream::decorate($s1, [ 'read' => function () { return ''; @@ -38,12 +35,12 @@ public function testCopiesToStringStopsWhenReadFails() public function testCopiesToStream() { - $s1 = Util::getStream('foobaz'); - $s2 = Util::getStream(''); + $s1 = (new StreamFactory())->createStreamFromString('foobaz'); + $s2 = (new StreamFactory())->createStreamFromString(''); Util::copyToStream($s1, $s2); $this->assertEquals('foobaz', (string) $s2); - $s2 = Util::getStream(''); + $s2 = (new StreamFactory())->createStreamFromString(''); $s1->seek(0); Util::copyToStream($s1, $s2, 3); @@ -68,7 +65,7 @@ public function testCopyToStreamReadsInChunksInsteadOfAllInMemory() }, ]); - $s2 = Util::getStream(''); + $s2 = (new StreamFactory())->createStreamFromString(''); Util::copyToStream($s1, $s2, 16394); $s2->seek(0); @@ -81,8 +78,8 @@ public function testCopyToStreamReadsInChunksInsteadOfAllInMemory() public function testStopsCopyToStreamWhenWriteFails() { - $s1 = Util::getStream('foobaz'); - $s2 = Util::getStream(''); + $s1 = (new StreamFactory())->createStreamFromString('foobaz'); + $s2 = (new StreamFactory())->createStreamFromString(''); $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); @@ -93,8 +90,8 @@ public function testStopsCopyToStreamWhenWriteFails() public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() { - $s1 = Util::getStream('foobaz'); - $s2 = Util::getStream(''); + $s1 = (new StreamFactory())->createStreamFromString('foobaz'); + $s2 = (new StreamFactory())->createStreamFromString(''); $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); @@ -105,11 +102,11 @@ public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() public function testStopsCopyToSteamWhenReadFailsWithMaxLen() { - $s1 = Util::getStream('foobaz'); + $s1 = (new StreamFactory())->createStreamFromString('foobaz'); $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]); - $s2 = Util::getStream(''); + $s2 = (new StreamFactory())->createStreamFromString(''); Util::copyToStream($s1, $s2, 10); $this->assertEquals('', (string) $s2); @@ -131,97 +128,6 @@ public function testThrowsExceptionNotWarning() Util::tryFopen('/path/to/does/not/exist', 'r'); } - public function testKeepsPositionOfResource() - { - $h = fopen(__FILE__, 'r'); - fseek($h, 10); - $stream = Util::getStream($h); - $this->assertEquals(10, $stream->tell()); - $stream->close(); - } - - public function testCreatesWithFactory() - { - $stream = Util::getStream('foo'); - $this->assertInstanceOf(Stream::class, $stream); - $this->assertEquals('foo', $stream->getContents()); - $stream->close(); - } - - public function testFactoryCreatesFromEmptyString() - { - $s = Util::getStream(); - $this->assertInstanceOf(Stream::class, $s); - } - - public function testFactoryCreatesFromNull() - { - $s = Util::getStream(null); - $this->assertInstanceOf(Stream::class, $s); - } - - public function testFactoryCreatesFromResource() - { - $r = fopen(__FILE__, 'r'); - $s = Util::getStream($r); - $this->assertInstanceOf(Stream::class, $s); - $this->assertSame(file_get_contents(__FILE__), (string) $s); - } - - public function testFactoryCreatesFromObjectWithToString() - { - $r = new HasToString(); - $s = Util::getStream($r); - $this->assertInstanceOf(Stream::class, $s); - $this->assertEquals('foo', (string) $s); - } - - public function testCreatePassesThrough() - { - $s = Util::getStream('foo'); - $this->assertSame($s, Util::getStream($s)); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testThrowsExceptionForUnknown() - { - Util::getStream(new \stdClass()); - } - - public function testReturnsCustomMetadata() - { - $s = Util::getStream('foo', ['metadata' => ['hwm' => 3]]); - - $this->assertEquals(3, $s->getMetadata('hwm')); - $this->assertArrayHasKey('hwm', $s->getMetadata()); - } - - public function testCanSetSize() - { - $s = Util::getStream('', ['size' => 10]); - - $this->assertEquals(10, $s->getSize()); - } - - public function testCanCreateIteratorBasedStream() - { - $a = new ArrayIterator(['foo', 'bar', '123']); - $p = Util::getStream($a); - - $this->assertInstanceOf(PumpStream::class, $p); - $this->assertEquals('foo', $p->read(3)); - $this->assertFalse($p->eof()); - $this->assertEquals('b', $p->read(1)); - $this->assertEquals('a', $p->read(1)); - $this->assertEquals('r12', $p->read(3)); - $this->assertFalse($p->eof()); - $this->assertEquals('3', $p->getContents()); - $this->assertTrue($p->eof()); - $this->assertEquals(9, $p->tell()); - } - public function dataNormalizeFiles() { return [ diff --git a/src/Viserio/Http/Uri.php b/src/Viserio/Http/Uri.php index 04f6444fc..1f53046dd 100644 --- a/src/Viserio/Http/Uri.php +++ b/src/Viserio/Http/Uri.php @@ -520,7 +520,7 @@ function ($matches) { $components = parse_url($encodeUrl); if (! $components) { - throw new InvalidArgumentException("Unable to parse URI: $url"); + throw new InvalidArgumentException(sprintf('Unable to parse URI: %s.', $url)); } foreach ($components as $key => $value) { diff --git a/src/Viserio/Http/Util.php b/src/Viserio/Http/Util.php index a3acb10bd..ccd1b0028 100644 --- a/src/Viserio/Http/Util.php +++ b/src/Viserio/Http/Util.php @@ -3,11 +3,9 @@ namespace Viserio\Http; use InvalidArgumentException; -use Iterator; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; use RuntimeException; -use Viserio\Http\Stream\PumpStream; class Util { @@ -20,66 +18,6 @@ private function __construct() { } - /** - * Create a new stream based on the input type. - * - * Options is an associative array that can contain the following keys: - * - metadata: Array of custom metadata. - * - size: Size of the stream. - * - * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data - * @param array $options Additional options - * - * @throws \InvalidArgumentException if the $resource arg is not valid. - * - * @return Stream - */ - public static function getStream($resource = '', array $options = []): StreamInterface - { - if (is_scalar($resource)) { - $stream = fopen('php://temp', 'r+'); - - if ($resource !== '') { - fwrite($stream, $resource); - fseek($stream, 0); - } - - return new Stream($stream, $options); - } - - switch (gettype($resource)) { - case 'resource': - return new Stream($resource, $options); - case 'object': - if ($resource instanceof StreamInterface) { - return $resource; - } elseif ($resource instanceof Iterator) { - return new PumpStream(function () use ($resource) { - if (! $resource->valid()) { - return false; - } - - $result = $resource->current(); - $resource->next(); - - return $result; - }, $options); - } elseif (method_exists($resource, '__toString')) { - return self::getStream((string) $resource, $options); - } - - break; - case 'NULL': - return new Stream(fopen('php://temp', 'r+'), $options); - } - - if (is_callable($resource)) { - return new PumpStream($resource, $options); - } - - throw new InvalidArgumentException('Invalid resource type: ' . gettype($resource)); - } - /** * Safely opens a PHP stream resource using a filename. * @@ -218,7 +156,7 @@ public static function copyToStream( * * @param array $files A array which respect $_FILES structure * - * @throws InvalidArgumentException for unrecognized values + * @throws \InvalidArgumentException for unrecognized values * * @return array */ diff --git a/src/Viserio/Parsers/Formats/BSON.php b/src/Viserio/Parsers/Formats/BSON.php index 242e2aa44..2f2539091 100644 --- a/src/Viserio/Parsers/Formats/BSON.php +++ b/src/Viserio/Parsers/Formats/BSON.php @@ -3,11 +3,12 @@ namespace Viserio\Parsers\Formats; use RuntimeException; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\DumpException; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class BSON implements FormatContract +class BSON implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/INI.php b/src/Viserio/Parsers/Formats/INI.php index 715c2ccb4..c86293162 100644 --- a/src/Viserio/Parsers/Formats/INI.php +++ b/src/Viserio/Parsers/Formats/INI.php @@ -2,10 +2,11 @@ declare(strict_types=1); namespace Viserio\Parsers\Formats; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class INI implements FormatContract +class INI implements FormatContract, DumperContract { /** * {@inheritdoc} @@ -37,7 +38,13 @@ public function dump(array $data): string return $output; } - protected function writeSection($section, $array) + /** + * @param string $section + * @param array $array + * + * @return string + */ + protected function writeSection(string $section, array $array): string { $subsections = []; $output = '[' . $section . ']' . PHP_EOL; diff --git a/src/Viserio/Parsers/Formats/JSON.php b/src/Viserio/Parsers/Formats/JSON.php index 8a2cc73c9..b9ed1fea2 100644 --- a/src/Viserio/Parsers/Formats/JSON.php +++ b/src/Viserio/Parsers/Formats/JSON.php @@ -2,11 +2,12 @@ declare(strict_types=1); namespace Viserio\Parsers\Formats; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\DumpException; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class JSON implements FormatContract +class JSON implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/MSGPack.php b/src/Viserio/Parsers/Formats/MSGPack.php index 12bddbeb4..3fdbb8459 100644 --- a/src/Viserio/Parsers/Formats/MSGPack.php +++ b/src/Viserio/Parsers/Formats/MSGPack.php @@ -3,11 +3,12 @@ namespace Viserio\Parsers\Formats; use RuntimeException; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\DumpException; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class MSGPack implements FormatContract +class MSGPack implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/PHP.php b/src/Viserio/Parsers/Formats/PHP.php index c8db363aa..1d6a06f2e 100644 --- a/src/Viserio/Parsers/Formats/PHP.php +++ b/src/Viserio/Parsers/Formats/PHP.php @@ -2,10 +2,11 @@ declare(strict_types=1); namespace Viserio\Parsers\Formats; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class PHP implements FormatContract +class PHP implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/Po.php b/src/Viserio/Parsers/Formats/Po.php index 5e54a2965..16ab32760 100644 --- a/src/Viserio/Parsers/Formats/Po.php +++ b/src/Viserio/Parsers/Formats/Po.php @@ -42,12 +42,4 @@ public function parse(string $payload): array ]); } } - - /** - * {@inheritdoc} - */ - public function dump(array $data): string - { - // - } } diff --git a/src/Viserio/Parsers/Formats/QueryStr.php b/src/Viserio/Parsers/Formats/QueryStr.php index 947e0a6fd..3613c397c 100644 --- a/src/Viserio/Parsers/Formats/QueryStr.php +++ b/src/Viserio/Parsers/Formats/QueryStr.php @@ -2,9 +2,10 @@ declare(strict_types=1); namespace Viserio\Parsers\Formats; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Format as FormatContract; -class QueryStr implements FormatContract +class QueryStr implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/Serialize.php b/src/Viserio/Parsers/Formats/Serialize.php index af9248c26..9a14cd5b9 100644 --- a/src/Viserio/Parsers/Formats/Serialize.php +++ b/src/Viserio/Parsers/Formats/Serialize.php @@ -3,11 +3,12 @@ namespace Viserio\Parsers\Formats; use Exception; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\DumpException; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class Serialize implements FormatContract +class Serialize implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/TOML.php b/src/Viserio/Parsers/Formats/TOML.php index e1f8d2618..0b554e5fd 100644 --- a/src/Viserio/Parsers/Formats/TOML.php +++ b/src/Viserio/Parsers/Formats/TOML.php @@ -36,12 +36,4 @@ public function parse(string $payload): array ]); } } - - /** - * {@inheritdoc} - */ - public function dump(array $data): string - { - return 'Not supported.'; - } } diff --git a/src/Viserio/Parsers/Formats/XML.php b/src/Viserio/Parsers/Formats/XML.php index 372cfce84..6c0bde46e 100644 --- a/src/Viserio/Parsers/Formats/XML.php +++ b/src/Viserio/Parsers/Formats/XML.php @@ -6,11 +6,12 @@ use Exception; use RuntimeException; use Spatie\ArrayToXml\ArrayToXml; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\DumpException; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class XML implements FormatContract +class XML implements FormatContract, DumperContract { /** * {@inheritdoc} diff --git a/src/Viserio/Parsers/Formats/YAML.php b/src/Viserio/Parsers/Formats/YAML.php index afb706820..12ad979ee 100644 --- a/src/Viserio/Parsers/Formats/YAML.php +++ b/src/Viserio/Parsers/Formats/YAML.php @@ -5,10 +5,11 @@ use RuntimeException; use Symfony\Component\Yaml\Exception\ParseException as YamlParseException; use Symfony\Component\Yaml\Parser; +use Viserio\Contracts\Parsers\Dumper as DumperContract; use Viserio\Contracts\Parsers\Exception\ParseException; use Viserio\Contracts\Parsers\Format as FormatContract; -class YAML implements FormatContract +class YAML implements FormatContract, DumperContract { /** * The filesystem instance. diff --git a/src/Viserio/Parsers/Tests/Formats/TOMLTest.php b/src/Viserio/Parsers/Tests/Formats/TOMLTest.php index ba80438df..f270842db 100644 --- a/src/Viserio/Parsers/Tests/Formats/TOMLTest.php +++ b/src/Viserio/Parsers/Tests/Formats/TOMLTest.php @@ -51,9 +51,4 @@ public function testParseToThrowException() { $this->parser->parse('nonexistfile'); } - - public function testDump() - { - $this->assertSame('Not supported.', $this->parser->dump([])); - } } diff --git a/src/Viserio/Parsers/composer.json b/src/Viserio/Parsers/composer.json index dfbb18e08..7c011f4a7 100644 --- a/src/Viserio/Parsers/composer.json +++ b/src/Viserio/Parsers/composer.json @@ -50,7 +50,8 @@ "sepia/po-parser" : "Required to use the PO parser (^4.2).", "spatie/array-to-xml" : "Required to use the XML dump (^2.1.1).", "viserio/statical-proxy" : "Required to use the proxies (self.version).", - "yosymfony/toml" : "Required to use the TOML parser (^0.3)." + "yosymfony/toml" : "Required to use the TOML parser (^0.3).", + "vlucas/phpdotenv" : "Required to use the .env parser (^2.3)." }, "autoload": { "psr-4": { diff --git a/src/Viserio/Support/Env.php b/src/Viserio/Support/Env.php new file mode 100644 index 000000000..1296f95bf --- /dev/null +++ b/src/Viserio/Support/Env.php @@ -0,0 +1,68 @@ + 1 && + mb_substr($value, 0, strlen('"')) === '"' && + mb_substr($value, -strlen('"')) === '"' + ) { + return mb_substr($value, 1, -1); + } + + return $value; + } +} diff --git a/src/Viserio/Support/Proxies/Env.php b/src/Viserio/Support/Proxies/Env.php new file mode 100644 index 000000000..7e641a7b4 --- /dev/null +++ b/src/Viserio/Support/Proxies/Env.php @@ -0,0 +1,13 @@ +assertTrue(Env::get('TEST_TRUE')); + $this->assertFalse(Env::get('NOT_SET', false)); + $this->assertSame('test', Env::get('NOT_SET2', function () { + return 'test'; + })); + $this->assertFalse(Env::get('TEST_FALSE')); + $this->assertFalse(Env::get('TEST_FALSE_2')); + $this->assertSame(null, Env::get('TEST_NULL')); + $this->assertSame(25, Env::get('TEST_NUM')); + $this->assertSame('', Env::get('TEST_EMPTY')); + $this->assertSame('teststring', Env::get('TEST_NORMAL')); + $this->assertSame('teststring', Env::get('TEST_QUOTES')); + } +} From bdac32d63e84bd3d7319df7dde2c364d26f9ac95 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 01:41:53 +0200 Subject: [PATCH 03/20] Added routetree builder --- .../Providers/ConfigServiceProvider.php | 1 - src/Viserio/Config/composer.json | 4 +- .../Contracts/Routing/NodeContents.php | 7 + src/Viserio/Contracts/Routing/Route.php | 7 + src/Viserio/Routing/Dispatcher.php | 3 +- .../Generator/ChildrenNodeCollection.php | 74 ++++++ .../Routing/Generator/MatchedRouteDataMap.php | 97 +++++++ .../Routing/Generator/RouteTreeBuilder.php | 103 ++++++++ .../Routing/Generator/RouteTreeNode.php | 111 ++++++++ .../Routing/Generator/RouteTreeOptimizer.php | 248 ++++++++++++++++++ .../Routing/Matchers/ParameterMatcher.php | 28 -- src/Viserio/Routing/Matchers/RegexMatcher.php | 14 +- src/Viserio/Routing/Route.php | 47 +++- src/Viserio/Routing/RouteCollection.php | 113 -------- src/Viserio/Routing/RouteParser.php | 4 +- src/Viserio/Routing/Router.php | 94 ++++++- .../Routing/Segments/ParameterSegment.php | 52 ++++ src/Viserio/Routing/Tests/DispatcherTest.php | 9 +- .../Tests/Generator/RouteTreeBuilderTest.php | 163 ++++++++++++ .../Tests/Generator/RouteTreeNodeTest.php | 128 +++++++++ .../Generator/RouteTreeOptimizerTest.php | 7 + .../Routing/Tests/RouteCollectionTest.php | 7 - src/Viserio/Routing/Tests/RouteParserTest.php | 32 +-- src/Viserio/Routing/Tests/RouteTest.php | 24 +- .../Tests/Segments/ParameterSegmentTest.php | 23 ++ src/Viserio/Routing/composer.json | 1 + 26 files changed, 1188 insertions(+), 213 deletions(-) create mode 100644 src/Viserio/Contracts/Routing/NodeContents.php create mode 100644 src/Viserio/Routing/Generator/ChildrenNodeCollection.php create mode 100644 src/Viserio/Routing/Generator/MatchedRouteDataMap.php create mode 100644 src/Viserio/Routing/Generator/RouteTreeNode.php create mode 100644 src/Viserio/Routing/Generator/RouteTreeOptimizer.php delete mode 100644 src/Viserio/Routing/Matchers/ParameterMatcher.php delete mode 100644 src/Viserio/Routing/RouteCollection.php create mode 100644 src/Viserio/Routing/Segments/ParameterSegment.php create mode 100644 src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php create mode 100644 src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php create mode 100644 src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php delete mode 100644 src/Viserio/Routing/Tests/RouteCollectionTest.php create mode 100644 src/Viserio/Routing/Tests/Segments/ParameterSegmentTest.php diff --git a/src/Viserio/Config/Providers/ConfigServiceProvider.php b/src/Viserio/Config/Providers/ConfigServiceProvider.php index efbec8417..48a19446d 100644 --- a/src/Viserio/Config/Providers/ConfigServiceProvider.php +++ b/src/Viserio/Config/Providers/ConfigServiceProvider.php @@ -5,7 +5,6 @@ use Viserio\Application\ServiceProvider; use Viserio\Config\Manager as ConfigManager; use Viserio\Config\Repository; -use Viserio\Filesystem\FileLoader; class ConfigServiceProvider extends ServiceProvider { diff --git a/src/Viserio/Config/composer.json b/src/Viserio/Config/composer.json index fa2ae9b32..8b0ebffb4 100644 --- a/src/Viserio/Config/composer.json +++ b/src/Viserio/Config/composer.json @@ -26,8 +26,7 @@ "phpunit/phpunit" : "^5.1", "mikey179/vfsStream" : "^1.6", "narrowspark/testing-helper" : "^1.5", - "viserio/parsers" : "self.version", - "viserio/filesystem" : "self.version" + "viserio/parsers" : "self.version" }, "autoload": { "psr-4": { @@ -46,7 +45,6 @@ } }, "suggest": { - "viserio/filesystem" : "Required to use bind or cascadePackage function (0.10)", "viserio/parsers" : "Required to use FileLoader class (self.version)." }, "minimum-stability" : "dev", diff --git a/src/Viserio/Contracts/Routing/NodeContents.php b/src/Viserio/Contracts/Routing/NodeContents.php new file mode 100644 index 000000000..dafb0ccc4 --- /dev/null +++ b/src/Viserio/Contracts/Routing/NodeContents.php @@ -0,0 +1,7 @@ +routes = new RouteCollection(); } /** diff --git a/src/Viserio/Routing/Generator/ChildrenNodeCollection.php b/src/Viserio/Routing/Generator/ChildrenNodeCollection.php new file mode 100644 index 000000000..490f52260 --- /dev/null +++ b/src/Viserio/Routing/Generator/ChildrenNodeCollection.php @@ -0,0 +1,74 @@ +children = $children; + } + + /** + * @return \Viserio\Contracts\Routing\RouteTreeNode[] + */ + public function getChildren(): array + { + return $this->children; + } + + /** + * @param \Viserio\Contracts\Routing\RouteTreeNode $node + * + * @return bool + */ + public function hasChild(RouteTreeNode $node): bool + { + return $this->hasChildFor($node->getFirstMatcher()); + } + + /** + * @param \Viserio\Contracts\Routing\SegmentMatcher $matcher + * + * @return bool + */ + public function hasChildFor(SegmentMatcherContract $matcher): bool + { + $hash = $matcher->getHash(); + + return isset($this->children[$hash]); + } + + /** + * @param \Viserio\Contracts\Routing\SegmentMatcher $matcher + * + * @return \Viserio\Contracts\Routing\RouteTreeNode|null + */ + public function getChild(SegmentMatcherContract $matcher) + { + return $this->children[$matcher->getHash()] ?? null; + } + + /** + * @param \Viserio\Contracts\Routing\RouteTreeNode $node + */ + public function addChild(RouteTreeNode $node) + { + $hash = $node->getFirstMatcher()->getHash(); + + $this->children[$hash] = $node; + } +} diff --git a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php new file mode 100644 index 000000000..1eba1f639 --- /dev/null +++ b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php @@ -0,0 +1,97 @@ +httpMethodRouteDataMap = $httpMethodRouteDataMap; + $this->defaultRouteData = $defaultRouteData; + } + + /** + * @return array + */ + public function getHttpMethodRouteDataMap() + { + return $this->httpMethodRouteDataMap; + } + + /** + * @return array|null + */ + public function getAllowedHttpMethods() + { + if ($this->hasDefaultRouteData()) { + return 'GET'; + } + + $allowedHttpMethods = []; + + foreach ($this->httpMethodRouteDataMap as $item) { + foreach ($item[0] as $method) { + $allowedHttpMethods[$method] = true; + } + } + + return array_keys($allowedHttpMethods); + } + + /** + * @return MatchedRouteData|null + */ + public function getDefaultRouteData() + { + return $this->defaultRouteData; + } + + /** + * @return bool + */ + public function hasDefaultRouteData() + { + return $this->defaultRouteData !== null; + } + + /** + * @return bool + */ + public function isEmpty() + { + return $this->defaultRouteData === null && empty($this->httpMethodRouteDataMap); + } + + /** + * Adds the supplied route to the matched route data map + * + * @param \Viserio\Contracts\Routing\Route $route + * @param array $parameterIndexNameMap + */ + public function addRoute(RouteContract $route, array $parameterIndexNameMap) + { + if (count($route->getMethods()) === 1 && in_array('ANY', $route->getMethods())) { + $this->defaultRouteData = [$parameterIndexNameMap, $route->getParameters()]; + } else { + $this->httpMethodRouteDataMap[] = [$route->getMethods(), [$parameterIndexNameMap, $route->getParameters()]]; + } + } +} diff --git a/src/Viserio/Routing/Generator/RouteTreeBuilder.php b/src/Viserio/Routing/Generator/RouteTreeBuilder.php index a9028cf1e..92adae91b 100644 --- a/src/Viserio/Routing/Generator/RouteTreeBuilder.php +++ b/src/Viserio/Routing/Generator/RouteTreeBuilder.php @@ -2,6 +2,109 @@ declare(strict_types=1); namespace Viserio\Routing\Generator; +use Viserio\Contracts\Routing\Route as RouteContract; +use Viserio\Contracts\Routing\SegmentMatcher as SegmentMatcherContract; + class RouteTreeBuilder { + /** + * Creates a route tree from the supplied routes. + * + * @param \Viserio\Contracts\Routing\Route[] $routes + * + * @return array + */ + public function build(array $routes): array + { + $rootRouteData = null; + $nodes = []; + $groupedRoutes = []; + + foreach ($routes as $route) { + $groupedRoutes[count($route->getSegments())][] = $route; + } + + if (isset($groupedRoutes[0])) { + $rootRouteData = new MatchedRouteDataMap(); + $rootRouteData->addRoute($groupedRoutes[0][0], []); + + unset($groupedRoutes[0]); + } + + foreach ($groupedRoutes as $segmentDepth => $group) { + $groupNodes = []; + + foreach ($group as $route) { + $parameterIndexNameMap = []; + $segments = $route->getSegments(); + $segmentMatcher = $this->getMatcher(array_shift($segments), $parameterIndexNameMap); + $firstSegmentHash = $segmentMatcher->getHash(); + + if (! isset($groupNodes[$firstSegmentHash])) { + $groupNodes[$firstSegmentHash] = new RouteTreeNode( + [0 => $segmentMatcher], + $segmentDepth === 1 ? new MatchedRouteDataMap() : new ChildrenNodeCollection() + ); + } + + $this->addRouteToNode($groupNodes[$firstSegmentHash], $route, $segments, 1, $parameterIndexNameMap); + } + + $nodes[$segmentDepth] = new ChildrenNodeCollection($groupNodes); + } + + return [$rootRouteData, $nodes]; + } + + /** + * Adds a route to the node tree. + * + * @param \Viserio\\Routing\Generator\RouteTreeNode $node + * @param \Viserio\Contracts\Routing\Route $route + * @param array $segments + * @param int $segmentDepth + * @param array $parameterIndexNameMap + */ + protected function addRouteToNode( + RouteTreeNode $node, + RouteContract $route, + array $segments, + int $segmentDepth, + array $parameterIndexNameMap + ) { + if (empty($segments)) { + $node->getContents()->addRoute($route, $parameterIndexNameMap); + + return; + } + + $childSegmentMatcher = $this->getMatcher(array_shift($segments), $parameterIndexNameMap); + + if ($node->getContents()->hasChildFor($childSegmentMatcher)) { + $child = $node->getContents()->getChild($childSegmentMatcher); + } else { + $child = new RouteTreeNode( + [ + $segmentDepth => $childSegmentMatcher, + ], + empty($segments) ? new MatchedRouteDataMap() : new ChildrenNodeCollection() + ); + $node->getContents()->addChild($child); + } + + $this->addRouteToNode($child, $route, $segments, $segmentDepth + 1, $parameterIndexNameMap); + } + + /** + * Get the right Matcher. + * + * @param object $firstSegment + * @param array &$parameterIndexNameMap + * + * @return \Viserio\Contracts\Routing\SegmentMatcher + */ + private function getMatcher($firstSegment, array &$parameterIndexNameMap): SegmentMatcherContract + { + return method_exists($firstSegment, 'getMatcher') ? $firstSegment->getMatcher($parameterIndexNameMap) : $firstSegment; + } } diff --git a/src/Viserio/Routing/Generator/RouteTreeNode.php b/src/Viserio/Routing/Generator/RouteTreeNode.php new file mode 100644 index 000000000..9c1ab1db6 --- /dev/null +++ b/src/Viserio/Routing/Generator/RouteTreeNode.php @@ -0,0 +1,111 @@ +matchers = $matchers; + $this->contents = $contents; + } + + /** + * Get all matchers from array. + * + * @return \Viserio\Contracts\Routing\SegmentMatcher[] + */ + public function getMatchers(): array + { + return $this->matchers; + } + + /** + * Get the first matcher from array. + * + * @return \Viserio\Contracts\Routing\SegmentMatcher + */ + public function getFirstMatcher(): SegmentMatcherContract + { + return $this->matchers[min(array_keys($this->matchers))]; + } + + /** + * Check if content is a leaf node. + * + * @return bool + */ + public function isLeafNode(): bool + { + return $this->contents instanceof MatchedRouteDataMap; + } + + /** + * Check if content is a parent node. + * + * @return bool + */ + public function isParentNode(): bool + { + return $this->contents instanceof ChildrenNodeCollection; + } + + /** + * Get actual route content. + * + * @return \Viserio\Contracts\Routing\NodeContents + */ + public function getContents(): NodeContentsContract + { + return $this->contents; + } + + /** + * Update RouteTreeNode class. + * + * @param array $matchers + * @param NodeContentsBase $contents + * + * @return \Viserio\Routing\Generator\RouteTreeNode + */ + public function update(array $matchers, NodeContentsBase $contents): RouteTreeNode + { + if ($this->matchers === $matchers && $this->contents === $contents) { + return $this; + } + + $clone = clone $this; + $clone->matchers = $matchers; + $clone->contents = $contents; + + return $clone; + } +} diff --git a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php new file mode 100644 index 000000000..b26570a62 --- /dev/null +++ b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php @@ -0,0 +1,248 @@ + $nodes) { + $segmentDepthNodeMap[$segmentDepth] = $this->optimizeNodes($nodes); + } + + return [$routeTree[0], $segmentDepthNodeMap]; + } + + /** + * @param \Viserio\Routing\Generator\ChildrenNodeCollection $nodes + * + * @return \Viserio\Routing\Generator\ChildrenNodeCollection + */ + protected function optimizeNodes(ChildrenNodeCollection $nodes): ChildrenNodeCollection + { + $optimizedNodes = []; + + foreach ($nodes->getChildren() as $key => $node) { + $optimizedNodes[$key] = $this->optimizeNode($node); + } + + $optimizedNodes = new ChildrenNodeCollection($optimizedNodes); + $optimizedNodes = $this->moveCommonMatchersToParentNode($optimizedNodes); + + return $optimizedNodes; + } + + /** + * [optimizeNode description] + * + * @param RouteTreeNode $node + * + * @return RouteTreeNode + */ + protected function optimizeNode(RouteTreeNode $node): RouteTreeNode + { + $matchers = $node->getMatchers(); + $contents = $node->getContents(); + + if ($node->isParentNode()) { + $contents = $this->optimizeNodes($node->getContents()); + $children = $contents->getChildren(); + + if (count($children) === 1) { + $childNode = reset($children); + $matchers = $this->mergeMatchers($node->getMatchers(), $childNode->getMatchers()); + $contents = $childNode->getContents(); + } + } + + $matchers = $this->optimizeMatchers($matchers); + + return $node->update($matchers, $contents); + } + + /** + * [mergeMatchers description] + * + * @param array $parentMatchers + * @param array $childMatchers + * + * @return array + */ + protected function mergeMatchers(array $parentMatchers, array $childMatchers): array + { + $mergedMatchers = $parentMatchers; + + foreach ($childMatchers as $segment => $childMatcher) { + if (isset($mergedMatchers[$segment])) { + $mergedMatchers[$segment] = new CompoundMatcher([$mergedMatchers[$segment], $childMatcher]); + } else { + $mergedMatchers[$segment] = $childMatcher; + } + } + + return $mergedMatchers; + } + + /** + * @param \Viserio\Contracts\Routing\SegmentMatcher $matcher + * + * @return \Viserio\Contracts\Routing\SegmentMatcher + */ + protected function optimizeMatcher(SegmentMatcherContract $matcher): SegmentMatcherContract + { + if ($matcher instanceof RegexMatcher && $matcher->getGroupCount() === 1) { + $parameterKeys = $matcher->getParameterKeys(); + + switch ($matcher->getRegex()) { + case Pattern::ANY: + return new AnyMatcher($parameterKeys); + case Pattern::DIGITS: + return new ExpressionMatcher('ctype_digit({segment})', $parameterKeys); + case Pattern::ALPHA: + return new ExpressionMatcher('ctype_alpha({segment})', $parameterKeys); + case Pattern::ALPHA_LOWER: + return new ExpressionMatcher('ctype_lower({segment})', $parameterKeys); + case Pattern::ALPHA_UPPER: + return new ExpressionMatcher('ctype_upper({segment})', $parameterKeys); + case Pattern::ALPHA_NUM: + return new ExpressionMatcher('ctype_alnum({segment})', $parameterKeys); + case Pattern::ALPHA_NUM_DASH: + return new ExpressionMatcher('ctype_alnum(str_replace(\'-\', \'\', {segment}))', $parameterKeys); + } + } + + return $matcher; + } + + /** + * [optimizeMatcherOrder description] + * + * @param array $matchers + * + * @return array + */ + protected function optimizeMatcherOrder(array $matchers): array + { + $computationalCostOrder = [ + AnyMatcher::class, + StaticMatcher::class, + ExpressionMatcher::class, + RegexMatcher::class, + // Unknown types last + SegmentMatcherContract::class, + ]; + + $groups = []; + + foreach ($computationalCostOrder as $type) { + foreach ($matchers as $index => $matcher) { + if ($matcher instanceof $type) { + unset($matchers[$index]); + $groups[$type][$index] = $matcher; + } + } + } + + $matchers = []; + + foreach ($groups as $group) { + foreach ($group as $index => $matcher) { + $matchers[$index] = $matcher; + } + } + + return $matchers; + } + + /** + * + * @param \Viserio\Routing\Generator\ChildrenNodeCollection $nodeCollection + * + * @return \Viserio\Routing\Generator\ChildrenNodeCollection + */ + protected function moveCommonMatchersToParentNode(ChildrenNodeCollection $nodeCollection): ChildrenNodeCollection + { + $nodes = $nodeCollection->getChildren(); + + if (count($nodes) <= 1) { + return $nodeCollection; + } + + $children = []; + $previous = array_shift($nodes); + + foreach ($nodes as $node) { + $parent = $this->extractCommonParentNode($previous, $node); + + if ($parent) { + $previous = $parent; + } else { + $children[] = $previous; + $previous = $node; + } + } + + $children[] = $previous; + + return new ChildrenNodeCollection($children); + } + + /** + * + * @param \Viserio\Routing\Generator\RouteTreeNode $node1 + * @param \Viserio\Routing\Generator\RouteTreeNode $node2 + * + * @return \Viserio\Routing\Generator\RouteTreeNode|null + */ + protected function extractCommonParentNode(RouteTreeNode $node1, RouteTreeNode $node2) + { + $matcherCompare = function (SegmentMatcherContract $matcher, SegmentMatcherContract $matcher2) { + return strcmp($matcher->getHash(), $matcher2->getHash()); + }; + + $commonMatchers = array_uintersect_assoc($node1->getMatchers(), $node2->getMatchers(), $matcherCompare); + + if (empty($commonMatchers)) { + return; + } + + $children = []; + $nodes = [$node1, $node2]; + + foreach ($nodes as $node) { + $specificMatchers = array_udiff_assoc($node->getMatchers(), $commonMatchers, $matcherCompare); + $duplicateMatchers = array_uintersect_assoc($node->getMatchers(), $commonMatchers, $matcherCompare); + + foreach ($duplicateMatchers as $segmentDepth => $matcher) { + $commonMatchers[$segmentDepth]->mergeParameterKeys($matcher); + } + + if (empty($specificMatchers) && $node->isParentNode()) { + foreach ($node->getContents()->getChildren() as $childNode) { + $children[] = $childNode; + } + } else { + $children[] = $node->update($specificMatchers, $node->getContents()); + } + } + + return new RouteTreeNode($commonMatchers, new ChildrenNodeCollection($children)); + } +} diff --git a/src/Viserio/Routing/Matchers/ParameterMatcher.php b/src/Viserio/Routing/Matchers/ParameterMatcher.php deleted file mode 100644 index 4bb4084ca..000000000 --- a/src/Viserio/Routing/Matchers/ParameterMatcher.php +++ /dev/null @@ -1,28 +0,0 @@ -names = is_array($names) ? $names : [$names]; - $this->regex = $regex; - } -} diff --git a/src/Viserio/Routing/Matchers/RegexMatcher.php b/src/Viserio/Routing/Matchers/RegexMatcher.php index a844ba169..3c420b549 100644 --- a/src/Viserio/Routing/Matchers/RegexMatcher.php +++ b/src/Viserio/Routing/Matchers/RegexMatcher.php @@ -24,14 +24,18 @@ class RegexMatcher extends AbstractMatcher /** * Create a new regex segment matcher instance. * - * @param string $regex - * @param int $parameterKeyGroupMap + * @param string $regex + * @param array|int $parameterKeyGroupMap */ - public function __construct(string $regex, int $parameterKeyGroupMap) + public function __construct(string $regex, $parameterKeyGroupMap) { - $this->regex = $regex; + if (strpos($regex, '/^(') !== false && strpos($regex, ')$/') !== false) { + $this->regex = $regex; + } else { + $this->regex = '/^(' . $regex . ')$/'; + } - $map = [$parameterKeyGroupMap => 0]; + $map = is_array($parameterKeyGroupMap) ? $parameterKeyGroupMap : [$parameterKeyGroupMap => 0]; $this->parameterKeyGroupMap = $map; $this->parameterKeys = array_keys($map); diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index 034c668b9..07f9cb99c 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -8,7 +8,7 @@ use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\Router as RouterContract; -use Viserio\Routing\Matchers\ParameterMatcher; +use Viserio\Routing\Segments\ParameterSegment; use Viserio\Support\Invoker; class Route implements RouteContract @@ -64,14 +64,29 @@ class Route implements RouteContract */ protected $invoker; + /** + * Optional route parameters. + * + * @var \Viserio\Contracts\Routing\RouteMatcher[] + */ + protected $segments; + + /** + * Global route parameters. + * + * @var array + */ + protected $globalParameters; + /** * Create a new Route instance. * * @param array|string $methods * @param string $uri * @param \Closure|array|null $action + * @param array $globalParameters */ - public function __construct($methods, $uri, $action) + public function __construct($methods, $uri, $action, $globalParameters = []) { $this->uri = $uri; // According to RFC methods are defined in uppercase (See RFC 7231) @@ -85,6 +100,10 @@ public function __construct($methods, $uri, $action) if (isset($this->action['prefix'])) { $this->addPrefix($this->action['prefix']); } + + $this->globalParameters = $globalParameters; + + $this->parseSegment($uri); } /** @@ -122,6 +141,8 @@ public function setUri(string $uri): RouteContract { $this->uri = $uri; + $this->parseSegment($uri); + return $this; } @@ -277,7 +298,7 @@ public function isStatic(): bool $this->getParameters(); foreach ($this->parameters as $parameter) { - if ($parameter instanceof ParameterMatcher) { + if ($parameter instanceof ParameterSegment) { return false; } } @@ -285,6 +306,16 @@ public function isStatic(): bool return true; } + /** + * Get optional route parameters. + * + * @return \Viserio\Contracts\Routing\RouteMatcher[] + */ + public function getSegments(): array + { + return $this->segments; + } + /** * {@inheritdoc} */ @@ -372,4 +403,14 @@ protected function parseAction($action): array return $action; } + + /** + * Get the optional parameters for the route. + * + * @param string $uri + */ + private function parseSegment(string $uri) + { + $this->segments = (new RouteParser())->parse($uri, $this->globalParameters); + } } diff --git a/src/Viserio/Routing/RouteCollection.php b/src/Viserio/Routing/RouteCollection.php deleted file mode 100644 index 58d208542..000000000 --- a/src/Viserio/Routing/RouteCollection.php +++ /dev/null @@ -1,113 +0,0 @@ -addToCollections($route); - - return $route; - } - - /** - * Get all of the routes in the collection. - * - * @return array - */ - public function getRoutes(): array - { - return array_values($this->allRoutes); - } - - /** - * Add the given route to the arrays of routes. - * - * @param \Viserio\Contracts\Routing\Route $route - */ - protected function addToCollections(RouteContract $route) - { - $domainAndUri = $route->getDomain() . $route->getUri(); - - foreach ($route->getMethods() as $method) { - $this->routes[$method][$domainAndUri] = $route; - } - - $this->allRoutes[$method . $domainAndUri] = $route; - } - - /** - * @param string $pattern [description] - * - * @return array - */ - protected function parseRoutingPattern(string $pattern): array - { - if (is_string($pattern)) { - return [$pattern, []]; - } - - if (is_array($pattern)) { - if (! isset($pattern[0]) || ! is_string($pattern[0])) { - throw new InvalidRoutePatternException(sprintf( - 'Cannot add route: route pattern array must have the first element containing the pattern string, %s given', - isset($pattern[0]) ? gettype($pattern[0]) : 'none' - )); - } - - $patternString = $pattern[0]; - $parameterConditions = $pattern; - - unset($parameterConditions[0]); - - return [$patternString, $parameterConditions]; - } - - throw new InvalidRoutePatternException(sprintf( - 'Cannot add route: route pattern must be a pattern string, %s given', - gettype($pattern) - )); - } -} diff --git a/src/Viserio/Routing/RouteParser.php b/src/Viserio/Routing/RouteParser.php index a3587463e..66ab1dd71 100644 --- a/src/Viserio/Routing/RouteParser.php +++ b/src/Viserio/Routing/RouteParser.php @@ -5,8 +5,8 @@ use Viserio\Contracts\Routing\Exceptions\InvalidRoutePatternException; use Viserio\Contracts\Routing\Pattern; use Viserio\Contracts\Routing\RouteParser as RouteParserContract; -use Viserio\Routing\Matchers\ParameterMatcher; use Viserio\Routing\Matchers\StaticMatcher; +use Viserio\Routing\Segments\ParameterSegment; class RouteParser implements RouteParserContract { @@ -31,7 +31,7 @@ public function parse(string $route, array $conditions): array foreach ($patternSegments as $key => $patternSegment) { if ($this->matchRouteParameters($route, $patternSegment, $conditions, $matches, $names)) { - $segments[] = new ParameterMatcher( + $segments[] = new ParameterSegment( $names, $this->generateRegex($matches, $conditions) ); diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php index f242b6506..dbe097dab 100644 --- a/src/Viserio/Routing/Router.php +++ b/src/Viserio/Routing/Router.php @@ -9,7 +9,6 @@ use Viserio\Contracts\Events\Traits\EventsAwareTrait; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteGroup as RouteGroupContract; -use Viserio\Contracts\Routing\RouteParser as RouteParserContract; use Viserio\Contracts\Routing\Router as RouterContract; class Router implements RouterContract @@ -24,16 +23,33 @@ class Router implements RouterContract */ protected $routes = []; + /** + * An flattened array of all of the routes. + * + * @var array + */ + protected $allRoutes = []; + + /** + * @var \Viserio\Routing\RouteGroup[] + */ + protected $groups = []; + + /** + * Used global parameters in all routes. + * + * @var string[] + */ + protected $globalParameterConditions = []; + /** * Create a new Router instance. * - * @param \Interop\Container\ContainerInterface $container - * @param \Viserio\Contracts\Routing\RouteParser $parser + * @param \Interop\Container\ContainerInterface $container */ - public function __construct(ContainerInterface $container, RouteParserContract $parser) + public function __construct(ContainerInterface $container) { $this->container = $container; - $this->parser = $parser; } /** @@ -120,6 +136,55 @@ public function group(array $attributes, Closure $callback): RouterContract return $this; } + /** + * Defines the supplied parameter name to be globally associated with the pattern + * + * @param string $parameterName + * @param string $pattern + * + * @return $this + */ + public function setGlobalParameter(string $parameterName, string $pattern) + { + $this->globalParameterConditions[$parameterName] = $pattern; + + return $this; + } + + /** + * Defines the supplied parameter name to be globally associated with the pattern + * + * @param string[] $parameterPatternMap + * + * @return $this + */ + public function addGlobalParameters(array $parameterPatternMap) + { + $this->globalParameterConditions += $parameterPatternMap; + + return $this; + } + + /** + * Removes the global pattern associated with the supplied parameter name + * + * @param string $name + */ + public function removeGlobalParameter(string $name) + { + unset($this->globalParameterConditions[$name]); + } + + /** + * Get all global parameters for all routes. + * + * @return array + */ + public function getGlobalParameters(): array + { + return $this->globalParameterConditions; + } + /** * Dispatch router for HTTP request. * @@ -142,7 +207,15 @@ public function dispatch(ServerRequestInterface $request) */ protected function addRoute($methods, string $uri, $action): RouteContract { - return $this->routes[] = $this->createRoute($methods, $uri, $action); + $route = $this->createRoute($methods, $uri, $action); + + $domainAndUri = $route->getDomain() . $route->getUri(); + + foreach ($route->getMethods() as $method) { + $this->routes[$method][$domainAndUri] = $route; + } + + return $this->allRoutes[$method . $domainAndUri] = $route; } /** @@ -156,18 +229,13 @@ protected function addRoute($methods, string $uri, $action): RouteContract */ protected function createRoute($methods, string $uri, $action): RouteContract { - $pattern = $this->parser->parse($uri, ['TODO']); - $route = $this->newRoute( $methods, $this->prefix($uri), - $action + $action, + $this->getGlobalParameters() ); - foreach ($pattern as $key => $value) { - $route->setParameter($key, $value); - } - return $route; } diff --git a/src/Viserio/Routing/Segments/ParameterSegment.php b/src/Viserio/Routing/Segments/ParameterSegment.php new file mode 100644 index 000000000..9136f1247 --- /dev/null +++ b/src/Viserio/Routing/Segments/ParameterSegment.php @@ -0,0 +1,52 @@ +names = is_array($names) ? $names : [$names]; + $this->regex = $regex; + } + + /** + * [getMatcher description] + * + * @param array &$parameterIndexNameMap + * + * @return \Viserio\Routing\Matchers\RegexMatcher + */ + public function getMatcher(array &$parameterIndexNameMap): RegexMatcher + { + $parameterKey = empty($parameterIndexNameMap) ? 0 : max(array_keys($parameterIndexNameMap)) + 1; + $parameterKeyGroupMap = []; + $group = 0; + + foreach ($this->names as $name) { + $parameterIndexNameMap[$parameterKey] = $name; + $parameterKeyGroupMap[$parameterKey] = $group++; + ++$parameterKey; + } + + return new RegexMatcher($this->regex, $parameterKeyGroupMap); + } +} diff --git a/src/Viserio/Routing/Tests/DispatcherTest.php b/src/Viserio/Routing/Tests/DispatcherTest.php index e0d7111e4..68503717c 100644 --- a/src/Viserio/Routing/Tests/DispatcherTest.php +++ b/src/Viserio/Routing/Tests/DispatcherTest.php @@ -2,19 +2,12 @@ declare(strict_types=1); namespace Viserio\Routing\Tests; -use Viserio\Routing\Dispatcher; use Viserio\Routing\Route; -use Viserio\Routing\RouteCollection; class DispatcherTest extends \PHPUnit_Framework_TestCase { public function testMatch() { - $route = new Route('GET', 'test', null); - - $collection = new RouteCollection(); - $collection->addRoute($route); - - $dispatcher = new Dispatcher($collection); + $route = new Route('GET', '/test', null); } } diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php new file mode 100644 index 000000000..42a0b461e --- /dev/null +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php @@ -0,0 +1,163 @@ +setParameter('route', '')], + new MatchedRouteDataMap([], [[], ['route' => '']]), + [], + ], + [ + [(new Route('ANY', '/', null))->setParameter('route', '')], + null, + [ + 1 => new ChildrenNodeCollection([ + (new StaticMatcher(''))->getHash() => new RouteTreeNode( + [0 => new StaticMatcher('')], + new MatchedRouteDataMap([], [[], ['route' => '']]) + ), + ]), + ], + ], + [ + [(new Route(['GET'], '/{param}', null, [new ParameterSegment('param', Pattern::ANY)]))->setParameter('root-route', '')], + null, + [ + 1 => new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( + [0 => new RegexMatcher(Pattern::ANY, 0)], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[0 => 'param'], ['root-route' => '']]], + ]) + ), + ]), + ], + ], + [ + [ + (new Route(['GET'], '/first/{param1}', null))->setParameter('static-first', ''), + (new Route(['GET'], '/{param1}/{param2}', null, [new ParameterSegment('param1', Pattern::ANY), new ParameterSegment('param2', Pattern::ANY)]))->setParameter('dynamic', ''), + ], + null, + [ + 2 => new ChildrenNodeCollection([ + (new StaticMatcher('first'))->getHash() => new RouteTreeNode( + [0 => new StaticMatcher('first')], + new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( + [1 => new RegexMatcher(Pattern::ANY, 0)], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[0 => 'param1'], ['static-first' => '']]], + ]) + ), + ]) + ), + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( + [0 => new RegexMatcher(Pattern::ANY, 0)], + new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 1))->getHash() => new RouteTreeNode( + [1 => new RegexMatcher(Pattern::ANY, 1)], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[0 => 'param1', 1 => 'param2'], ['dynamic' => '']]], + ]) + ), + ]) + ), + ]), + ], + ], + [ + [ + (new Route('ANY', '', null))->setParameter('home', ''), + (new Route('ANY', '/main', null))->setParameter('main.root', ''), + (new Route(['GET'], '/main/place', null))->setParameter('main.place-get', ''), + (new Route(['POST'], '/main/place', null))->setParameter('main.place-post', ''), + (new Route('ANY', '/main/thing', null))->setParameter('main.thing', ''), + (new Route('ANY', '/main/thing/abc', null))->setParameter('main.thing.abc', ''), + (new Route('ANY', '/user/{name}', null, [new StaticMatcher('user'), new ParameterSegment('name', Pattern::ANY)]))->setParameter('user.show', ''), + (new Route('ANY', '/user/{name}/edit', null, [new ParameterSegment('name', Pattern::ANY)]))->setParameter('user.edit', ''), + (new Route('ANY', '/user/create', null))->setParameter('user.create', ''), + ], + new MatchedRouteDataMap([], [[], ['home' => '']]), + [ + 1 => new ChildrenNodeCollection([ + (new StaticMatcher('main'))->getHash() => new RouteTreeNode( + [0 => new StaticMatcher('main')], + new MatchedRouteDataMap([], [[], ['main.root' => '']]) + ), + ]), + 2 => new ChildrenNodeCollection([ + (new StaticMatcher('main'))->getHash() => new RouteTreeNode([0 => new StaticMatcher('main')], new ChildrenNodeCollection([ + (new StaticMatcher('place'))->getHash() => new RouteTreeNode( + [1 => new StaticMatcher('place')], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[], ['main.place-get' => '']]], + [['POST'], [[], ['main.place-post' => '']]], + ]) + ), + (new StaticMatcher('thing'))->getHash() => new RouteTreeNode( + [1 => new StaticMatcher('thing')], + new MatchedRouteDataMap([], [[], ['main.thing' => '']]) + ), + ])), + (new StaticMatcher('user'))->getHash() => new RouteTreeNode([0 => new StaticMatcher('user')], new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( + [1 => new RegexMatcher(Pattern::ANY, 0)], + new MatchedRouteDataMap([], [[0 => 'name'], ['user.show' => '']]) + ), + (new StaticMatcher('create'))->getHash() => new RouteTreeNode( + [1 => new StaticMatcher('create')], + new MatchedRouteDataMap([], [[], ['user.create' => '']]) + ), + ])), + ]), + 3 => new ChildrenNodeCollection([ + (new StaticMatcher('main'))->getHash() => new RouteTreeNode([0 => new StaticMatcher('main')], new ChildrenNodeCollection([ + (new StaticMatcher('thing'))->getHash() => new RouteTreeNode([1 => new StaticMatcher('thing')], new ChildrenNodeCollection([ + (new StaticMatcher('abc'))->getHash() => new RouteTreeNode( + [2 => new StaticMatcher('abc')], + new MatchedRouteDataMap([], [[], ['main.thing.abc' => '']]) + ), + ])), + ])), + (new StaticMatcher('user'))->getHash() => new RouteTreeNode([0 => new StaticMatcher('user')], new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode([1 => new RegexMatcher(Pattern::ANY, 0)], new ChildrenNodeCollection([ + (new StaticMatcher('edit'))->getHash() => new RouteTreeNode( + [2 => new StaticMatcher('edit')], + new MatchedRouteDataMap([], [[0 => 'name'], ['user.edit' => '']]) + ), + ])), + ])), + ]), + ], + ], + ]; + } + + /** + * @dataProvider routeTreeBuilderCases + */ + public function testRouteTreeBuilder($routes, $rootRoute, $segmentDepthNodesMap) + { + list($rootRouteData, $segmentDepthNodeMap) = (new RouteTreeBuilder())->build($routes); + + $this->assertSame($rootRoute !== null, $rootRouteData !== null && ! $rootRouteData->isEmpty()); + $this->assertEquals($rootRoute, $rootRouteData); + $this->assertEquals($segmentDepthNodesMap, $segmentDepthNodeMap); + } +} diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php new file mode 100644 index 000000000..064ac6745 --- /dev/null +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php @@ -0,0 +1,128 @@ +mock(AbstractMatcher::class); + $matcher2 = $this->mock(AbstractMatcher::class); + + $node = new RouteTreeNode([1 => $matcher2, 0 => $matcher1], new ChildrenNodeCollection()); + + $this->assertSame([1 => $matcher2, 0 => $matcher1], $node->getMatchers()); + $this->assertSame($matcher1, $node->getFirstMatcher()); + } + + public function testParentRouteTreeNode() + { + $matcher = $this->mock(AbstractMatcher::class); + $contents = new ChildrenNodeCollection(); + $node = new RouteTreeNode([$matcher], $contents); + + $this->assertSame([$matcher], $node->getMatchers()); + $this->assertSame($matcher, $node->getFirstMatcher()); + $this->assertSame($contents, $node->getContents()); + $this->assertTrue($node->isParentNode()); + $this->assertFalse($node->isLeafNode()); + } + + public function testLeafRouteTreeNode() + { + $matcher = $this->mock(AbstractMatcher::class); + $contents = new MatchedRouteDataMap(); + $node = new RouteTreeNode([$matcher], $contents); + + $this->assertSame([$matcher], $node->getMatchers()); + $this->assertSame($matcher, $node->getFirstMatcher()); + $this->assertSame($contents, $node->getContents()); + $this->assertTrue($node->isLeafNode()); + $this->assertFalse($node->isParentNode()); + } + + public function testChildrenCollectionOperations() + { + $matcher1 = $this->mock(AbstractMatcher::class); + $matcher2 = $this->mock(AbstractMatcher::class); + $matcher2->shouldReceive('getHash') + ->times(7) + ->andReturn('some-hash'); + $matcher3 = $this->mock(AbstractMatcher::class); + $matcher3->shouldReceive('getHash') + ->once() + ->andReturn('some-other-hash'); + + $node = new RouteTreeNode([$matcher1], new ChildrenNodeCollection()); + $child = new RouteTreeNode([$matcher2], new ChildrenNodeCollection()); + $node->getContents()->addChild($child); + + $this->assertSame([$child->getFirstMatcher()->getHash() => $child], $node->getContents()->getChildren()); + $this->assertTrue($node->getContents()->hasChild($child)); + $this->assertTrue($node->getContents()->hasChildFor($child->getFirstMatcher())); + $this->assertTrue($node->getContents()->hasChild(clone $child)); + $this->assertTrue($node->getContents()->hasChildFor(clone $child->getFirstMatcher())); + $this->assertFalse($node->getContents()->hasChildFor($matcher3)); + $this->assertSame($child, $node->getContents()->getChild($child->getFirstMatcher())); + } + + public function testMatchedRouteDataMapOperations() + { + $node = new RouteTreeNode([$this->mock(AbstractMatcher::class)], new MatchedRouteDataMap()); + $node->getContents()->addRoute((new Route(['GET', 'POST'], '', null))->setParameter('first_route', ''), []); + + $this->assertSame(['GET', 'POST', 'HEAD'], $node->getContents()->getAllowedHttpMethods()); + $this->assertEquals( + [ + [['GET', 'POST', 'HEAD'], [[], ['first_route' => '']]], + ], + $node->getContents()->getHttpMethodRouteDataMap() + ); + $this->assertNull($node->getContents()->getDefaultRouteData()); + $this->assertFalse($node->getContents()->hasDefaultRouteData()); + + $node->getContents()->addRoute((new Route('PATCH', '', null))->setParameter('second_route', ''), [0 => 'param']); + + $this->assertSame(['GET', 'POST', 'HEAD', 'PATCH'], $node->getContents()->getAllowedHttpMethods()); + $this->assertEquals( + [ + [['GET', 'POST', 'HEAD'], [[], ['first_route' => '']]], + [['PATCH'], [[0 => 'param'], ['second_route' => '']]], + ], + $node->getContents()->getHttpMethodRouteDataMap() + ); + $this->assertNull($node->getContents()->getDefaultRouteData()); + $this->assertFalse($node->getContents()->hasDefaultRouteData()); + + $node->getContents()->addRoute((new Route(['ANY'], '', null))->setParameter('third_route', ''), []); + + $this->assertSame('GET', $node->getContents()->getAllowedHttpMethods()); + $this->assertEquals( + [ + [['GET', 'POST', 'HEAD'], [[], ['first_route' => '']]], + [['PATCH'], [[0 => 'param'], ['second_route' => '']]], + ], + $node->getContents()->getHttpMethodRouteDataMap() + ); + $this->assertEquals([[], ['third_route' => '']], $node->getContents()->getDefaultRouteData()); + $this->assertTrue($node->getContents()->hasDefaultRouteData()); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Cannot construct Viserio\Routing\Generator\RouteTreeNode: matchers must not be empty. + */ + public function testThrowsExceptionForEmptyMatchers() + { + new RouteTreeNode([], new ChildrenNodeCollection([])); + } +} diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php new file mode 100644 index 000000000..4ec2a2d6b --- /dev/null +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php @@ -0,0 +1,7 @@ + Pattern::ALPHA_NUM], - [new ParameterMatcher('param', '/^(' . Pattern::ALPHA_NUM . ')$/')], + [new ParameterSegment('param', '/^(' . Pattern::ALPHA_NUM . ')$/')], ], [ '/user/{id}/profile/{type}', ['id' => Pattern::DIGITS, 'type' => Pattern::ALPHA_LOWER], [ new StaticMatcher('user'), - new ParameterMatcher('id', '/^(' . Pattern::DIGITS . ')$/'), + new ParameterSegment('id', '/^(' . Pattern::DIGITS . ')$/'), new StaticMatcher('profile'), - new ParameterMatcher('type', '/^(' . Pattern::ALPHA_LOWER . ')$/'), + new ParameterSegment('type', '/^(' . Pattern::ALPHA_LOWER . ')$/'), ], ], [ '/prefix{param}', ['param' => Pattern::ALPHA_NUM], - [new ParameterMatcher(['param'], '/^prefix(' . Pattern::ALPHA_NUM . ')$/')], + [new ParameterSegment(['param'], '/^prefix(' . Pattern::ALPHA_NUM . ')$/')], ], [ '/{param}suffix', ['param' => Pattern::ALPHA_NUM], - [new ParameterMatcher(['param'], '/^(' . Pattern::ALPHA_NUM . ')suffix$/')], + [new ParameterSegment(['param'], '/^(' . Pattern::ALPHA_NUM . ')suffix$/')], ], [ '/abc{param1}:{param2}', ['param1' => Pattern::ANY, 'param2' => Pattern::ALPHA], - [new ParameterMatcher(['param1', 'param2'], '/^abc(' . Pattern::ANY . ')\:(' . Pattern::ALPHA . ')$/')], + [new ParameterSegment(['param1', 'param2'], '/^abc(' . Pattern::ANY . ')\:(' . Pattern::ALPHA . ')$/')], ], [ '/shop/{category}:{product}/buy/quantity:{quantity}', ['category' => Pattern::ALPHA, 'product' => Pattern::ALPHA, 'quantity' => Pattern::DIGITS], [ new StaticMatcher('shop'), - new ParameterMatcher(['category', 'product'], '/^(' . Pattern::ALPHA . ')\:(' . Pattern::ALPHA . ')$/'), + new ParameterSegment(['category', 'product'], '/^(' . Pattern::ALPHA . ')\:(' . Pattern::ALPHA . ')$/'), new StaticMatcher('buy'), - new ParameterMatcher(['quantity'], '/^quantity\:(' . Pattern::DIGITS . ')$/'), + new ParameterSegment(['quantity'], '/^quantity\:(' . Pattern::DIGITS . ')$/'), ], ], [ '/{param:[0-9]+}', [], - [new ParameterMatcher(['param'], '/^([0-9]+)$/')], + [new ParameterSegment(['param'], '/^([0-9]+)$/')], ], [ '/{param:[\:]+}', [], - [new ParameterMatcher(['param'], '/^([\:]+)$/')], + [new ParameterSegment(['param'], '/^([\:]+)$/')], ], [ // Inline regexps take precedence '/{param:[a-z]+}', ['param' => Pattern::ALPHA_UPPER], - [new ParameterMatcher(['param'], '/^([a-z]+)$/')], + [new ParameterSegment(['param'], '/^([a-z]+)$/')], ], [ '/abc{param1:.+}:{param2:.+}', [], - [new ParameterMatcher(['param1', 'param2'], '/^abc(.+)\:(.+)$/')], + [new ParameterSegment(['param1', 'param2'], '/^abc(.+)\:(.+)$/')], ], [ '/shop/{category:[\w]+}:{product:[\w]+}/buy/quantity:{quantity:[0-9]+}', [], [ new StaticMatcher('shop'), - new ParameterMatcher(['category', 'product'], '/^([\w]+)\:([\w]+)$/'), + new ParameterSegment(['category', 'product'], '/^([\w]+)\:([\w]+)$/'), new StaticMatcher('buy'), - new ParameterMatcher(['quantity'], '/^quantity\:([0-9]+)$/'), + new ParameterSegment(['quantity'], '/^quantity\:([0-9]+)$/'), ], ], ]; diff --git a/src/Viserio/Routing/Tests/RouteTest.php b/src/Viserio/Routing/Tests/RouteTest.php index 757bf6a1f..d5c1aecd1 100644 --- a/src/Viserio/Routing/Tests/RouteTest.php +++ b/src/Viserio/Routing/Tests/RouteTest.php @@ -26,31 +26,31 @@ public function testBasicDispatchingOfRoutes() public function testGetMethods() { - $route = new Route('GET', 'test', ['uses' => Controller::class . '::string']); + $route = new Route('GET', '/test', ['uses' => Controller::class . '::string']); $this->assertSame(['GET', 'HEAD'], $route->getMethods()); - $route = new Route('PUT', 'test', ['uses' => Controller::class . '::string']); + $route = new Route('PUT', '/test', ['uses' => Controller::class . '::string']); $this->assertSame(['PUT'], $route->getMethods()); - $route = new Route(['GET', 'POST'], 'test', ['uses' => Controller::class . '::string']); + $route = new Route(['GET', 'POST'], '/test', ['uses' => Controller::class . '::string']); $this->assertSame(['GET', 'POST', 'HEAD'], $route->getMethods()); } public function testGetDomain() { - $route = new Route('GET', 'test', ['domain' => 'test.com']); + $route = new Route('GET', '/test', ['domain' => 'test.com']); $this->assertSame('test.com', $route->getDomain()); } public function testGetAndSetUri() { - $route = new Route('GET', 'test', ['domain' => 'test.com']); + $route = new Route('GET', '/test', ['domain' => 'test.com']); - $this->assertSame('test', $route->getUri()); + $this->assertSame('/test', $route->getUri()); $route->setUri('/foo/bar'); @@ -59,7 +59,7 @@ public function testGetAndSetUri() public function testGetAndSetName() { - $route = new Route('GET', 'test', ['as' => 'test']); + $route = new Route('GET', '/test', ['as' => 'test']); $this->assertSame('test', $route->getName()); @@ -67,7 +67,7 @@ public function testGetAndSetName() $this->assertSame('testfoo', $route->getName()); - $route = new Route('GET', 'test', null); + $route = new Route('GET', '/test', null); $route->setName('test'); $this->assertSame('test', $route->getName()); @@ -75,23 +75,23 @@ public function testGetAndSetName() public function testHttpAndHttps() { - $route = new Route('GET', 'test', ['http']); + $route = new Route('GET', '/test', ['http']); $this->assertTrue($route->isHttpOnly()); - $route = new Route('GET', 'test', ['https']); + $route = new Route('GET', '/test', ['https']); $this->assertTrue($route->isHttpsOnly()); } public function testSetAndGetPrefix() { - $route = new Route('GET', 'test', ['prefix' => 'test']); + $route = new Route('GET', '/test', ['prefix' => 'test']); $this->assertSame('test', $route->getPrefix()); $this->assertSame('test/test', $route->getUri()); - $route = new Route('GET', 'test', null); + $route = new Route('GET', '/test', null); $route->addPrefix('foo'); $this->assertSame('foo/test', $route->getUri()); diff --git a/src/Viserio/Routing/Tests/Segments/ParameterSegmentTest.php b/src/Viserio/Routing/Tests/Segments/ParameterSegmentTest.php new file mode 100644 index 000000000..2c2cdaa24 --- /dev/null +++ b/src/Viserio/Routing/Tests/Segments/ParameterSegmentTest.php @@ -0,0 +1,23 @@ +assertInstanceOf(RegexMatcher::class, $segment->getMatcher($parameters)); + $this->assertSame([0 => 'param'], $parameters); + } + } +} diff --git a/src/Viserio/Routing/composer.json b/src/Viserio/Routing/composer.json index dde3d0e68..1b23d1e1f 100644 --- a/src/Viserio/Routing/composer.json +++ b/src/Viserio/Routing/composer.json @@ -29,6 +29,7 @@ }, "require-dev": { "narrowspark/testing-helper" : "^1.5", + "mockery/mockery" : "^0.9.5", "phpunit/phpunit" : "^5.1", "viserio/http" : "self.version" }, From e356b1b73910c95f5797e799c840a9871739f2a0 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sat, 20 Aug 2016 23:42:29 +0000 Subject: [PATCH 04/20] Applied fixes from StyleCI --- src/Viserio/Routing/Generator/RouteTreeOptimizer.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php index b26570a62..df9cdf4e7 100644 --- a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php +++ b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php @@ -5,10 +5,10 @@ use Viserio\Contracts\Routing\Pattern; use Viserio\Contracts\Routing\SegmentMatcher as SegmentMatcherContract; use Viserio\Routing\Matchers\AnyMatcher; -use Viserio\Routing\Matchers\StaticMatcher; use Viserio\Routing\Matchers\CompoundMatcher; use Viserio\Routing\Matchers\ExpressionMatcher; use Viserio\Routing\Matchers\RegexMatcher; +use Viserio\Routing\Matchers\StaticMatcher; class RouteTreeOptimizer { @@ -172,7 +172,6 @@ protected function optimizeMatcherOrder(array $matchers): array } /** - * * @param \Viserio\Routing\Generator\ChildrenNodeCollection $nodeCollection * * @return \Viserio\Routing\Generator\ChildrenNodeCollection @@ -205,7 +204,6 @@ protected function moveCommonMatchersToParentNode(ChildrenNodeCollection $nodeCo } /** - * * @param \Viserio\Routing\Generator\RouteTreeNode $node1 * @param \Viserio\Routing\Generator\RouteTreeNode $node2 * From 4523af351f6457e69f42e934ee9f13e39e0ea7a1 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 02:24:07 +0200 Subject: [PATCH 05/20] cs fixes --- .../Generator/ChildrenNodeCollection.php | 8 +++-- .../Routing/Generator/MatchedRouteDataMap.php | 29 ++++++++++++------- .../Routing/Generator/RouteTreeBuilder.php | 10 +++---- .../Routing/Generator/RouteTreeNode.php | 8 ++--- .../Routing/Generator/RouteTreeOptimizer.php | 4 +-- .../Routing/Matchers/AbstractMatcher.php | 2 +- src/Viserio/Routing/Route.php | 2 +- 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/Viserio/Routing/Generator/ChildrenNodeCollection.php b/src/Viserio/Routing/Generator/ChildrenNodeCollection.php index 490f52260..df052f2c8 100644 --- a/src/Viserio/Routing/Generator/ChildrenNodeCollection.php +++ b/src/Viserio/Routing/Generator/ChildrenNodeCollection.php @@ -8,7 +8,9 @@ final class ChildrenNodeCollection implements NodeContentsContract { /** - * @var \Viserio\Contracts\Routing\RouteTreeNode[] + * All added children routes. + * + * @var \Viserio\Routing\RouteTreeNode[] */ protected $children = []; @@ -23,7 +25,7 @@ public function __construct(array $children = []) } /** - * @return \Viserio\Contracts\Routing\RouteTreeNode[] + * @return \Viserio\Routing\RouteTreeNode[] */ public function getChildren(): array { @@ -31,7 +33,7 @@ public function getChildren(): array } /** - * @param \Viserio\Contracts\Routing\RouteTreeNode $node + * @param \Viserio\Routing\RouteTreeNode $node * * @return bool */ diff --git a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php index 1eba1f639..65c6e8f95 100644 --- a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php +++ b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php @@ -10,7 +10,7 @@ class MatchedRouteDataMap implements NodeContentsContract /** * @var array */ - protected $httpMethodRouteDataMap = []; + protected $httpMethodRouteMap = []; /** * @var array|null @@ -20,23 +20,26 @@ class MatchedRouteDataMap implements NodeContentsContract /** * Create a new child node collection instance. * - * @param array $children + * @param array $httpMethodRouteMap + * @param array|null $defaultRouteData */ - public function __construct(array $httpMethodRouteDataMap = [], array $defaultRouteData = null) + public function __construct(array $httpMethodRouteMap = [], array $defaultRouteData = null) { - $this->httpMethodRouteDataMap = $httpMethodRouteDataMap; + $this->httpMethodRouteMap = $httpMethodRouteMap; $this->defaultRouteData = $defaultRouteData; } /** * @return array */ - public function getHttpMethodRouteDataMap() + public function getHttpMethodRouteDataMap(): array { - return $this->httpMethodRouteDataMap; + return $this->httpMethodRouteMap; } /** + * Get all allowed http methods. + * * @return array|null */ public function getAllowedHttpMethods() @@ -47,7 +50,7 @@ public function getAllowedHttpMethods() $allowedHttpMethods = []; - foreach ($this->httpMethodRouteDataMap as $item) { + foreach ($this->httpMethodRouteMap as $item) { foreach ($item[0] as $method) { $allowedHttpMethods[$method] = true; } @@ -57,7 +60,9 @@ public function getAllowedHttpMethods() } /** - * @return MatchedRouteData|null + * Get the default data. + * + * @return array|null */ public function getDefaultRouteData() { @@ -65,17 +70,21 @@ public function getDefaultRouteData() } /** + * Check if route has default data. + * * @return bool */ - public function hasDefaultRouteData() + public function hasDefaultRouteData(): bool { return $this->defaultRouteData !== null; } /** + * Check if route is empty. + * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return $this->defaultRouteData === null && empty($this->httpMethodRouteDataMap); } diff --git a/src/Viserio/Routing/Generator/RouteTreeBuilder.php b/src/Viserio/Routing/Generator/RouteTreeBuilder.php index 92adae91b..1af91c630 100644 --- a/src/Viserio/Routing/Generator/RouteTreeBuilder.php +++ b/src/Viserio/Routing/Generator/RouteTreeBuilder.php @@ -59,11 +59,11 @@ public function build(array $routes): array /** * Adds a route to the node tree. * - * @param \Viserio\\Routing\Generator\RouteTreeNode $node - * @param \Viserio\Contracts\Routing\Route $route - * @param array $segments - * @param int $segmentDepth - * @param array $parameterIndexNameMap + * @param \Viserio\Routing\Generator\RouteTreeNode $node + * @param \Viserio\Contracts\Routing\Route $route + * @param array $segments + * @param int $segmentDepth + * @param array $parameterIndexNameMap */ protected function addRouteToNode( RouteTreeNode $node, diff --git a/src/Viserio/Routing/Generator/RouteTreeNode.php b/src/Viserio/Routing/Generator/RouteTreeNode.php index 9c1ab1db6..502721dc9 100644 --- a/src/Viserio/Routing/Generator/RouteTreeNode.php +++ b/src/Viserio/Routing/Generator/RouteTreeNode.php @@ -25,8 +25,8 @@ class RouteTreeNode /** * Create a new child node collection instance. * - * @param array $matchers - * @param \Viserio\Contracts\Routing\NodeContents $contents + * @param \Viserio\Contracts\Routing\SegmentMatcher[] $matchers + * @param \Viserio\Contracts\Routing\NodeContents $contents */ public function __construct(array $matchers, NodeContentsContract $contents) { @@ -81,9 +81,9 @@ public function isParentNode(): bool /** * Get actual route content. * - * @return \Viserio\Contracts\Routing\NodeContents + * @return \Viserio\Routing\Generator\ChildrenNodeCollection|\Viserio\Routing\Generator\MatchedRouteDataMap */ - public function getContents(): NodeContentsContract + public function getContents() { return $this->contents; } diff --git a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php index df9cdf4e7..a0c9ec055 100644 --- a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php +++ b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php @@ -80,8 +80,8 @@ protected function optimizeNode(RouteTreeNode $node): RouteTreeNode /** * [mergeMatchers description] * - * @param array $parentMatchers - * @param array $childMatchers + * @param \Viserio\Contracts\Routing\SegmentMatcher[] $parentMatchers + * @param array $childMatchers * * @return array */ diff --git a/src/Viserio/Routing/Matchers/AbstractMatcher.php b/src/Viserio/Routing/Matchers/AbstractMatcher.php index 0925ec14f..402f04d7a 100644 --- a/src/Viserio/Routing/Matchers/AbstractMatcher.php +++ b/src/Viserio/Routing/Matchers/AbstractMatcher.php @@ -10,7 +10,7 @@ abstract class AbstractMatcher implements SegmentMatcherContract /** * Stores all parameters keys. * - * @var int[] + * @var array */ protected $parameterKeys = []; diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index 07f9cb99c..c558ea3ec 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -86,7 +86,7 @@ class Route implements RouteContract * @param \Closure|array|null $action * @param array $globalParameters */ - public function __construct($methods, $uri, $action, $globalParameters = []) + public function __construct($methods, $uri, $action, array $globalParameters = []) { $this->uri = $uri; // According to RFC methods are defined in uppercase (See RFC 7231) From 8050dc26c2424ddcae1fa5275f50dc1033b59d22 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 03:16:41 +0200 Subject: [PATCH 06/20] Added more tests | cs fixes --- .../Routing/Generator/MatchedRouteDataMap.php | 4 +- .../Generator/Optimizer/MatcherOptimizer.php | 135 ++++++++ .../Routing/Generator/RouteTreeNode.php | 6 +- .../Routing/Generator/RouteTreeOptimizer.php | 107 +----- src/Viserio/Routing/Router.php | 11 +- .../Generator/RouteTreeOptimizerTest.php | 321 ++++++++++++++++++ src/Viserio/Routing/TreeRouteCompiler.php | 47 +++ src/Viserio/Routing/VarExporter.php | 3 +- 8 files changed, 523 insertions(+), 111 deletions(-) create mode 100644 src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php create mode 100644 src/Viserio/Routing/TreeRouteCompiler.php diff --git a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php index 65c6e8f95..42232f30a 100644 --- a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php +++ b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php @@ -86,7 +86,7 @@ public function hasDefaultRouteData(): bool */ public function isEmpty(): bool { - return $this->defaultRouteData === null && empty($this->httpMethodRouteDataMap); + return $this->defaultRouteData === null && empty($this->httpMethodRouteMap); } /** @@ -100,7 +100,7 @@ public function addRoute(RouteContract $route, array $parameterIndexNameMap) if (count($route->getMethods()) === 1 && in_array('ANY', $route->getMethods())) { $this->defaultRouteData = [$parameterIndexNameMap, $route->getParameters()]; } else { - $this->httpMethodRouteDataMap[] = [$route->getMethods(), [$parameterIndexNameMap, $route->getParameters()]]; + $this->httpMethodRouteMap[] = [$route->getMethods(), [$parameterIndexNameMap, $route->getParameters()]]; } } } diff --git a/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php b/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php new file mode 100644 index 000000000..617df512e --- /dev/null +++ b/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php @@ -0,0 +1,135 @@ + $childMatcher) { + if (isset($mergedMatchers[$segment])) { + $mergedMatchers[$segment] = new CompoundMatcher([$mergedMatchers[$segment], $childMatcher]); + } else { + $mergedMatchers[$segment] = $childMatcher; + } + } + + return $mergedMatchers; + } + + /** + * Optimize matcher and matcher order. + * + * @param \Viserio\Contracts\Routing\SegmentMatcher[] $matchers + * + * @return \Viserio\Contracts\Routing\SegmentMatcher[] + */ + public static function optimizeMatchers(array $matchers): array + { + foreach ($matchers as $key => $matcher) { + $matchers[$key] = self::optimizeMatcher($matcher); + } + + return self::optimizeMatcherOrder($matchers); + } + + /** + * Change matcher for a faster one, if available. + * + * @param \Viserio\Contracts\Routing\SegmentMatcher $matcher + * + * @return \Viserio\Contracts\Routing\SegmentMatcher + */ + protected static function optimizeMatcher(SegmentMatcherContract $matcher): SegmentMatcherContract + { + if ($matcher instanceof RegexMatcher && $matcher->getGroupCount() === 1) { + $parameterKeys = $matcher->getParameterKeys(); + + switch ($matcher->getRegex()) { + case '/^(' . Pattern::ANY . ')$/': + return new AnyMatcher($parameterKeys); + case '/^(' . Pattern::DIGITS . ')$/': + return new ExpressionMatcher('ctype_digit({segment})', $parameterKeys); + case '/^(' . Pattern::ALPHA . ')$/': + return new ExpressionMatcher('ctype_alpha({segment})', $parameterKeys); + case '/^(' . Pattern::ALPHA_LOWER . ')$/': + return new ExpressionMatcher('ctype_lower({segment})', $parameterKeys); + case '/^(' . Pattern::ALPHA_UPPER . ')$/': + return new ExpressionMatcher('ctype_upper({segment})', $parameterKeys); + case '/^(' . Pattern::ALPHA_NUM . ')$/': + return new ExpressionMatcher('ctype_alnum({segment})', $parameterKeys); + case '/^(' . Pattern::ALPHA_NUM_DASH . ')$/': + return new ExpressionMatcher('ctype_alnum(str_replace(\'-\', \'\', {segment}))', $parameterKeys); + } + } + + return $matcher; + } + + /** + * Optimizing the matcher order, unknown types are added last. + * + * @param array $matchers + * + * @return array + */ + protected static function optimizeMatcherOrder(array $matchers): array + { + $computationalCostOrder = [ + AnyMatcher::class, + StaticMatcher::class, + ExpressionMatcher::class, + RegexMatcher::class, + // Unknown types last + SegmentMatcherContract::class, + ]; + + $groups = []; + + foreach ($computationalCostOrder as $type) { + foreach ($matchers as $index => $matcher) { + if ($matcher instanceof $type) { + unset($matchers[$index]); + $groups[$type][$index] = $matcher; + } + } + } + + $matchers = []; + + foreach ($groups as $group) { + foreach ($group as $index => $matcher) { + $matchers[$index] = $matcher; + } + } + + return $matchers; + } +} diff --git a/src/Viserio/Routing/Generator/RouteTreeNode.php b/src/Viserio/Routing/Generator/RouteTreeNode.php index 502721dc9..9713a4911 100644 --- a/src/Viserio/Routing/Generator/RouteTreeNode.php +++ b/src/Viserio/Routing/Generator/RouteTreeNode.php @@ -91,12 +91,12 @@ public function getContents() /** * Update RouteTreeNode class. * - * @param array $matchers - * @param NodeContentsBase $contents + * @param array $matchers + * @param \Viserio\Contracts\Routing\NodeContents $contents * * @return \Viserio\Routing\Generator\RouteTreeNode */ - public function update(array $matchers, NodeContentsBase $contents): RouteTreeNode + public function update(array $matchers, NodeContentsContract $contents): RouteTreeNode { if ($this->matchers === $matchers && $this->contents === $contents) { return $this; diff --git a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php index a0c9ec055..db424da20 100644 --- a/src/Viserio/Routing/Generator/RouteTreeOptimizer.php +++ b/src/Viserio/Routing/Generator/RouteTreeOptimizer.php @@ -2,18 +2,13 @@ declare(strict_types=1); namespace Viserio\Routing\Generator; -use Viserio\Contracts\Routing\Pattern; use Viserio\Contracts\Routing\SegmentMatcher as SegmentMatcherContract; -use Viserio\Routing\Matchers\AnyMatcher; -use Viserio\Routing\Matchers\CompoundMatcher; -use Viserio\Routing\Matchers\ExpressionMatcher; -use Viserio\Routing\Matchers\RegexMatcher; -use Viserio\Routing\Matchers\StaticMatcher; +use Viserio\Routing\Generator\Optimizer\MatcherOptimizer; class RouteTreeOptimizer { /** - * Optimizes the supplied route tree + * Optimizes the supplied route tree. * * @param array $routeTree * @@ -67,110 +62,16 @@ protected function optimizeNode(RouteTreeNode $node): RouteTreeNode if (count($children) === 1) { $childNode = reset($children); - $matchers = $this->mergeMatchers($node->getMatchers(), $childNode->getMatchers()); + $matchers = MatcherOptimizer::mergeMatchers($node->getMatchers(), $childNode->getMatchers()); $contents = $childNode->getContents(); } } - $matchers = $this->optimizeMatchers($matchers); + $matchers = MatcherOptimizer::optimizeMatchers($matchers); return $node->update($matchers, $contents); } - /** - * [mergeMatchers description] - * - * @param \Viserio\Contracts\Routing\SegmentMatcher[] $parentMatchers - * @param array $childMatchers - * - * @return array - */ - protected function mergeMatchers(array $parentMatchers, array $childMatchers): array - { - $mergedMatchers = $parentMatchers; - - foreach ($childMatchers as $segment => $childMatcher) { - if (isset($mergedMatchers[$segment])) { - $mergedMatchers[$segment] = new CompoundMatcher([$mergedMatchers[$segment], $childMatcher]); - } else { - $mergedMatchers[$segment] = $childMatcher; - } - } - - return $mergedMatchers; - } - - /** - * @param \Viserio\Contracts\Routing\SegmentMatcher $matcher - * - * @return \Viserio\Contracts\Routing\SegmentMatcher - */ - protected function optimizeMatcher(SegmentMatcherContract $matcher): SegmentMatcherContract - { - if ($matcher instanceof RegexMatcher && $matcher->getGroupCount() === 1) { - $parameterKeys = $matcher->getParameterKeys(); - - switch ($matcher->getRegex()) { - case Pattern::ANY: - return new AnyMatcher($parameterKeys); - case Pattern::DIGITS: - return new ExpressionMatcher('ctype_digit({segment})', $parameterKeys); - case Pattern::ALPHA: - return new ExpressionMatcher('ctype_alpha({segment})', $parameterKeys); - case Pattern::ALPHA_LOWER: - return new ExpressionMatcher('ctype_lower({segment})', $parameterKeys); - case Pattern::ALPHA_UPPER: - return new ExpressionMatcher('ctype_upper({segment})', $parameterKeys); - case Pattern::ALPHA_NUM: - return new ExpressionMatcher('ctype_alnum({segment})', $parameterKeys); - case Pattern::ALPHA_NUM_DASH: - return new ExpressionMatcher('ctype_alnum(str_replace(\'-\', \'\', {segment}))', $parameterKeys); - } - } - - return $matcher; - } - - /** - * [optimizeMatcherOrder description] - * - * @param array $matchers - * - * @return array - */ - protected function optimizeMatcherOrder(array $matchers): array - { - $computationalCostOrder = [ - AnyMatcher::class, - StaticMatcher::class, - ExpressionMatcher::class, - RegexMatcher::class, - // Unknown types last - SegmentMatcherContract::class, - ]; - - $groups = []; - - foreach ($computationalCostOrder as $type) { - foreach ($matchers as $index => $matcher) { - if ($matcher instanceof $type) { - unset($matchers[$index]); - $groups[$type][$index] = $matcher; - } - } - } - - $matchers = []; - - foreach ($groups as $group) { - foreach ($group as $index => $matcher) { - $matchers[$index] = $matcher; - } - } - - return $matchers; - } - /** * @param \Viserio\Routing\Generator\ChildrenNodeCollection $nodeCollection * diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php index dbe097dab..8231e679e 100644 --- a/src/Viserio/Routing/Router.php +++ b/src/Viserio/Routing/Router.php @@ -7,11 +7,12 @@ use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Events\Traits\EventsAwareTrait; +use Viserio\Contracts\Support\Arrayable as ArrayableContract; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteGroup as RouteGroupContract; use Viserio\Contracts\Routing\Router as RouterContract; -class Router implements RouterContract +class Router implements RouterContract, ArrayableContract { use ContainerAwareTrait; use EventsAwareTrait; @@ -196,6 +197,14 @@ public function dispatch(ServerRequestInterface $request) { } + /** + * {@inheritdoc} + */ + public function toArray(): array + { + return $this->allRoutes; + } + /** * Add a route to the underlying route collection. * diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php index 4ec2a2d6b..75113c1dc 100644 --- a/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php @@ -2,6 +2,327 @@ declare(strict_types=1); namespace Viserio\Routing\Tests\Generator; +use Narrowspark\TestingHelper\Traits\MockeryTrait; +use Viserio\Contracts\Routing\Pattern; +use Viserio\Contracts\Routing\SegmentMatcher as SegmentMatcherContract; +use Viserio\Routing\Generator\ChildrenNodeCollection; +use Viserio\Routing\Generator\MatchedRouteDataMap; +use Viserio\Routing\Generator\RouteTreeNode; +use Viserio\Routing\Generator\RouteTreeOptimizer; +use Viserio\Routing\Matchers\AnyMatcher; +use Viserio\Routing\Matchers\CompoundMatcher; +use Viserio\Routing\Matchers\ExpressionMatcher; +use Viserio\Routing\Matchers\RegexMatcher; +use Viserio\Routing\Matchers\StaticMatcher; + class RouteTreeOptimizerTest extends \PHPUnit_Framework_TestCase { + use MockeryTrait; + + public function optimizationCasesProvider() + { + return [ + [ + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc')], new ChildrenNodeCollection([])), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc')], new ChildrenNodeCollection([])), + ]), + ]], + ], + [ + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc')], new ChildrenNodeCollection([ + new RouteTreeNode([1 => new StaticMatcher('deg')], new ChildrenNodeCollection([ + ])), + ])), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc'), 1 => new StaticMatcher('deg')], new ChildrenNodeCollection([ + ])), + ]), + ]], + ], + [ + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc')], new ChildrenNodeCollection([ + new RouteTreeNode([1 => new StaticMatcher('def')], new ChildrenNodeCollection([ + new RouteTreeNode([2 => new StaticMatcher('ghi')], new ChildrenNodeCollection([ + ])), + ])), + ])), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc'), 1 => new StaticMatcher('def'), 2 => new StaticMatcher('ghi')], new ChildrenNodeCollection([ + ])), + ]), + ]], + ], + [ + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc')], new ChildrenNodeCollection([ + new RouteTreeNode([1 => new StaticMatcher('def'), 2 => new StaticMatcher('ghi')], new ChildrenNodeCollection([ + ])), + ])), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc'), 1 => new StaticMatcher('def'), 2 => new StaticMatcher('ghi')], new ChildrenNodeCollection([ + ])), + ]), + ]], + ], + [ + // Should optimize common regex patterns to more efficient PHP equivalents + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + new RegexMatcher(Pattern::ANY, 0), + new RegexMatcher(Pattern::DIGITS, 1), + new RegexMatcher(Pattern::ALPHA, 2), + new RegexMatcher(Pattern::ALPHA_LOWER, 3), + new RegexMatcher(Pattern::ALPHA_UPPER, 4), + new RegexMatcher(Pattern::ALPHA_NUM, 5), + new RegexMatcher(Pattern::ALPHA_NUM_DASH, 6), + new RegexMatcher('some\-custom\-pattern!{1,100}', 7), + ], + new ChildrenNodeCollection([]) + ), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + new AnyMatcher([0]), + new ExpressionMatcher('ctype_digit({segment})', [1]), + new ExpressionMatcher('ctype_alpha({segment})', [2]), + new ExpressionMatcher('ctype_lower({segment})', [3]), + new ExpressionMatcher('ctype_upper({segment})', [4]), + new ExpressionMatcher('ctype_alnum({segment})', [5]), + new ExpressionMatcher('ctype_alnum(str_replace(\'-\', \'\', {segment}))', [6]), + new RegexMatcher('some\-custom\-pattern!{1,100}', 7), + ], + new ChildrenNodeCollection([]) + ), + ]), + ]], + ], + [ + // Should order checks from least expensive to most expensive + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + 0 => $customSegmentMatcher = $this->mock(SegmentMatcherContract::class), + 1 => new ExpressionMatcher('some_expression({segment})', [0]), + 2 => new StaticMatcher('fdsf'), + 3 => new AnyMatcher([2]), + 4 => new RegexMatcher('[1-5a-g]+', 3), + 5 => new ExpressionMatcher('some_other_expression({segment})', [4]), + 6 => new AnyMatcher([5]), + 7 => new StaticMatcher('aqw'), + ], + new ChildrenNodeCollection([]) + ), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + 3 => new AnyMatcher([2]), + 6 => new AnyMatcher([5]), + 2 => new StaticMatcher('fdsf'), + 7 => new StaticMatcher('aqw'), + 1 => new ExpressionMatcher('some_expression({segment})', [0]), + 5 => new ExpressionMatcher('some_other_expression({segment})', [4]), + 4 => new RegexMatcher('[1-5a-g]+', 3), + 0 => $customSegmentMatcher, + ], + new ChildrenNodeCollection([]) + ), + ]), + ]], + ], + [ + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode([0 => new StaticMatcher('abc')], new ChildrenNodeCollection([ + new RouteTreeNode([1 => new RegexMatcher('[abc]+', 0)], new ChildrenNodeCollection([ + new RouteTreeNode([2 => new StaticMatcher('def')], new ChildrenNodeCollection([ + ])), + ])), + ])), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + 0 => new StaticMatcher('abc'), + 2 => new StaticMatcher('def'), + 1 => new RegexMatcher('[abc]+', 0), + ], + new ChildrenNodeCollection([]) + ), + ]), + ]], + ], + [ + // Should factor out common matchers into a parent node + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + 0 => new StaticMatcher('abc'), + 1 => new AnyMatcher([0]), + 2 => new StaticMatcher('1'), + 3 => new StaticMatcher('def'), + ], + new ChildrenNodeCollection([]) + ), + new RouteTreeNode( + [ + 0 => new StaticMatcher('abc'), + 1 => new AnyMatcher([0]), + 2 => new StaticMatcher('1'), + 3 => new StaticMatcher('def'), + ], + new ChildrenNodeCollection([]) + ), + new RouteTreeNode( + [ + 0 => new StaticMatcher('abc'), + 1 => new AnyMatcher([0]), + 2 => new StaticMatcher('3'), + 3 => new StaticMatcher('def'), + ], + new ChildrenNodeCollection([]) + ), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [ + 1 => new AnyMatcher([0]), + 0 => new StaticMatcher('abc'), + 3 => new StaticMatcher('def'), + ], + new ChildrenNodeCollection([ + new RouteTreeNode( + [ + 2 => new StaticMatcher('1'), + ], + new ChildrenNodeCollection([])), + new RouteTreeNode( + [ + 2 => new StaticMatcher('3'), + ], + new ChildrenNodeCollection([]) + ), + ]) + ), + ]), + ]], + ], + [ + 'original' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [0 => new StaticMatcher('abc')], + new ChildrenNodeCollection([ + new RouteTreeNode( + [0 => new StaticMatcher('def')], + new ChildrenNodeCollection([]) + ), + ]) + ), + ]), + ]], + 'optimized' => [null, [ + 1 => new ChildrenNodeCollection([ + new RouteTreeNode( + [0 => new CompoundMatcher([new StaticMatcher('abc'), new StaticMatcher('def')])], + new ChildrenNodeCollection([]) + ), + ]), + ]], + ], + [ + 'original' => [null, [ + 2 => new ChildrenNodeCollection([ + (new StaticMatcher('first'))->getHash() => new RouteTreeNode( + [0 => new StaticMatcher('first')], + new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( + [1 => new RegexMatcher(Pattern::ANY, 0)], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[0 => 'param1'], ['static-first']]], + ]) + ), + ]) + ), + (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( + [0 => new RegexMatcher(Pattern::ANY, 0)], + new ChildrenNodeCollection([ + (new RegexMatcher(Pattern::ANY, 1))->getHash() => new RouteTreeNode( + [1 => new RegexMatcher(Pattern::ANY, 1)], + new MatchedRouteDataMap([ + [ + ['GET', 'HEAD'], + [[0 => 'param1', 1 => 'param2'], ['dynamic']], + ], + ]) + ), + ]) + ), + ]), + ]], + 'optimized' => [null, [ + 2 => new ChildrenNodeCollection([ + new RouteTreeNode( + [1 => new AnyMatcher([0, 1])], + new ChildrenNodeCollection([ + new RouteTreeNode( + [0 => new StaticMatcher('first')], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[0 => 'param1'], ['static-first']]], + ]) + ), + new RouteTreeNode( + [0 => new AnyMatcher([0])], + new MatchedRouteDataMap([ + [['GET', 'HEAD'], [[0 => 'param1', 1 => 'param2'], ['dynamic']]], + ]) + ), + ]) + ), + ]), + ]], + ], + ]; + } + + /** + * @dataProvider optimizationCasesProvider + */ + public function testRouteTreeOptimizer(array $original, array $expected) + { + $this->assertEquals($expected, (new RouteTreeOptimizer())->optimize($original)); + } } diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php new file mode 100644 index 000000000..5fddd5008 --- /dev/null +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -0,0 +1,47 @@ +treeBuilder = $treeBuilder; + $this->treeOptimizer = $treeOptimizer; + } + + /** + * Complie all added routes to a router handler. + * + * @param array $routes + * + * @return string + */ + public function compile(array $routes): string + { + + } +} diff --git a/src/Viserio/Routing/VarExporter.php b/src/Viserio/Routing/VarExporter.php index 87c6dae48..4804b85ad 100644 --- a/src/Viserio/Routing/VarExporter.php +++ b/src/Viserio/Routing/VarExporter.php @@ -7,13 +7,12 @@ class VarExporter { /** - * Don't instantiate this class. + * Private constructor; non-instantiable. * * @codeCoverageIgnore */ private function __construct() { - // } /** From 53f01f4fa620f299864f03e72731b03de4039aad Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 01:16:55 +0000 Subject: [PATCH 07/20] Applied fixes from StyleCI --- src/Viserio/Routing/Router.php | 4 ++-- src/Viserio/Routing/TreeRouteCompiler.php | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php index 8231e679e..dca17aaad 100644 --- a/src/Viserio/Routing/Router.php +++ b/src/Viserio/Routing/Router.php @@ -7,10 +7,10 @@ use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Events\Traits\EventsAwareTrait; -use Viserio\Contracts\Support\Arrayable as ArrayableContract; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteGroup as RouteGroupContract; use Viserio\Contracts\Routing\Router as RouterContract; +use Viserio\Contracts\Support\Arrayable as ArrayableContract; class Router implements RouterContract, ArrayableContract { @@ -202,7 +202,7 @@ public function dispatch(ServerRequestInterface $request) */ public function toArray(): array { - return $this->allRoutes; + return $this->allRoutes; } /** diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php index 5fddd5008..3730c54bb 100644 --- a/src/Viserio/Routing/TreeRouteCompiler.php +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -42,6 +42,5 @@ public function __construct(RouteTreeBuilder $treeBuilder, RouteTreeOptimizer $t */ public function compile(array $routes): string { - } } From d07995da7f3cae7c171ed89f196247cb5262d176 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 19:30:38 +0200 Subject: [PATCH 08/20] Added more tests | Added TreeRouteCompiler --- src/Viserio/Contracts/Routing/Dispatcher.php | 21 +- src/Viserio/Contracts/Routing/Route.php | 27 +- .../{Router.php => RouteCollection.php} | 24 +- .../Contracts/Routing/RouteSegment.php | 15 + .../Contracts/Routing/SegmentMatcher.php | 12 +- src/Viserio/Routing/Dispatcher.php | 63 ++- .../Routing/Generator/RouteTreeBuilder.php | 7 +- .../Routing/Matchers/AbstractMatcher.php | 2 +- src/Viserio/Routing/Matchers/AnyMatcher.php | 2 +- .../Routing/Matchers/CompoundMatcher.php | 8 +- .../Routing/Matchers/ExpressionMatcher.php | 2 +- src/Viserio/Routing/Matchers/RegexMatcher.php | 4 +- .../Routing/Matchers/StaticMatcher.php | 4 +- src/Viserio/Routing/Route.php | 88 ++-- src/Viserio/Routing/RouteCollection.php | 494 ++++++++++++++++++ src/Viserio/Routing/RouteGroup.php | 67 --- src/Viserio/Routing/Router.php | 278 ---------- .../Routing/Segments/ParameterSegment.php | 12 +- src/Viserio/Routing/Tests/Cache/.gitignore | 1 + src/Viserio/Routing/Tests/DispatcherTest.php | 13 - .../Tests/Generator/RouteTreeBuilderTest.php | 9 +- .../Generator/RouteTreeOptimizerTest.php | 3 +- .../Tests/Matchers/CompoundMatcherTest.php | 12 +- .../Routing/Tests/RouteCollectionTest.php | 31 ++ src/Viserio/Routing/Tests/RouteTest.php | 24 +- src/Viserio/Routing/TreeRouteCompiler.php | 309 ++++++++++- 26 files changed, 1013 insertions(+), 519 deletions(-) rename src/Viserio/Contracts/Routing/{Router.php => RouteCollection.php} (85%) create mode 100644 src/Viserio/Contracts/Routing/RouteSegment.php create mode 100644 src/Viserio/Routing/RouteCollection.php delete mode 100644 src/Viserio/Routing/RouteGroup.php delete mode 100644 src/Viserio/Routing/Router.php create mode 100644 src/Viserio/Routing/Tests/Cache/.gitignore delete mode 100644 src/Viserio/Routing/Tests/DispatcherTest.php create mode 100644 src/Viserio/Routing/Tests/RouteCollectionTest.php diff --git a/src/Viserio/Contracts/Routing/Dispatcher.php b/src/Viserio/Contracts/Routing/Dispatcher.php index 561803c44..3737df1de 100644 --- a/src/Viserio/Contracts/Routing/Dispatcher.php +++ b/src/Viserio/Contracts/Routing/Dispatcher.php @@ -10,7 +10,7 @@ interface Dispatcher const FOUND = 1; - const METHOD_NOT_ALLOWED = 2; + const HTTP_METHOD_NOT_ALLOWED = 2; /** * Match and dispatch a route matching the given http method and @@ -21,23 +21,4 @@ interface Dispatcher * @return mixed */ public function handle(ServerRequestInterface $request); - - /** - * Constructs a match results object from the supplied array. - * The expected format is one of: - * - * [0 => MatchResult::FOUND, 1 => , 2 => ] - * - * [0 => MatchResult::HTTP_METHOD_NOT_ALLOWED, 1 => ] - * - * [0 => MatchResult::NOT_FOUND] - * - * @param string $httpMethod - * @param string $uri - * - * @throws \RuntimeException - * - * @return array - */ - public function dispatch(string $httpMethod, string $uri): array; } diff --git a/src/Viserio/Contracts/Routing/Route.php b/src/Viserio/Contracts/Routing/Route.php index cfc9c541d..3dd95f873 100644 --- a/src/Viserio/Contracts/Routing/Route.php +++ b/src/Viserio/Contracts/Routing/Route.php @@ -18,15 +18,6 @@ public function getDomain(); */ public function getUri(): string; - /** - * Set the URI that the route responds to. - * - * @param string $uri - * - * @return $this - */ - public function setUri(string $uri): Route; - /** * Get the name of the route instance. * @@ -50,6 +41,16 @@ public function setName(string $name): Route; */ public function getMethods(): array; + /** + * Set a regular expression requirement on the route. + * + * @param array|string $name + * @param string $expression + * + * @return $this + */ + public function where($name, string $expression = null): Route; + /** * Determine if the route only responds to HTTP requests. * @@ -163,9 +164,9 @@ public function forgetParameter(string $name); public function isStatic(): bool; /** - * Get optional route parameters. + * The regular expression requirements. * - * @return \Viserio\Contracts\Routing\RouteMatcher[] + * @return \\Viserio\Contracts\Routing\RouteMatcher[] */ public function getSegments(): array; @@ -179,9 +180,9 @@ public function run(); /** * Set the router instance on the route. * - * @param \Viserio\Contracts\Routing\Router $router + * @param \Viserio\Contracts\Routing\RouteCollection $router * * @return $this */ - public function setRouter(Router $router): Route; + public function setRouter(RouteCollection $router): Route; } diff --git a/src/Viserio/Contracts/Routing/Router.php b/src/Viserio/Contracts/Routing/RouteCollection.php similarity index 85% rename from src/Viserio/Contracts/Routing/Router.php rename to src/Viserio/Contracts/Routing/RouteCollection.php index 02ef644c2..496dff74e 100644 --- a/src/Viserio/Contracts/Routing/Router.php +++ b/src/Viserio/Contracts/Routing/RouteCollection.php @@ -3,8 +3,9 @@ namespace Viserio\Contracts\Routing; use Closure; +use Viserio\Contracts\Support\Arrayable as ArrayableContract; -interface Router +interface RouteCollection extends ArrayableContract { /** * Register a new GET route with the router. @@ -88,19 +89,24 @@ public function any(string $uri, $action = null): Route; public function match($methods, $uri, $action = null): Route; /** - * Get the parent group. + * Create a route group with shared attributes. * - * @return \Viserio\Contracts\Routing\RouteGroup + * @param array $attributes + * @param \Closure $callback */ - public function getGroup(): RouteGroup; + public function group(array $attributes, Closure $callback); /** - * Create a route group with shared attributes. + * Determine if the router currently has a group stack. * - * @param array $attributes - * @param \Closure $callback + * @return bool + */ + public function hasGroupStack(): bool; + + /** + * Get the current group stack for the router. * - * @return \Viserio\Contracts\Routing\Router + * @return array */ - public function group(array $attributes, Closure $callback): Router; + public function getGroupStack(): array; } diff --git a/src/Viserio/Contracts/Routing/RouteSegment.php b/src/Viserio/Contracts/Routing/RouteSegment.php new file mode 100644 index 000000000..2ae024d4a --- /dev/null +++ b/src/Viserio/Contracts/Routing/RouteSegment.php @@ -0,0 +1,15 @@ +router = $router; + $this->container = $container; } /** @@ -33,32 +43,57 @@ public function __construct($routes) */ public function handle(ServerRequestInterface $request) { - $match = $this->dispatch( + $router = $this->router; + $match = $router( $request->getMethod(), $request->getUri()->getPath() - ); + );var_dump($match); switch ($match[0]) { case DispatcherContract::NOT_FOUND: - // 404 Not Found... + return $this->handleNotFound(); break; case DispatcherContract::HTTP_METHOD_NOT_ALLOWED: - // 405 Method Not Allowed... + $allowed = (array) $match[1]; + return $this->handleNotAllowed($allowed); break; case DispatcherContract::FOUND: - // Matched route, dispatch to associated handler... + + return $this->handleFound($match[1], (array) $match[2]); break; } } /** - * {@inheritdoc} + * Handle dispatching of a found route. + * + * @param callable $route + * @param array $vars + * + * @return \League\Route\Middleware\ExecutionChain */ - public function dispatch(string $httpMethod, string $uri): array + protected function handleFound(callable $route, array $vars) { + } - protected function generate() + /** + * Handle a not found route. + * + * @return \League\Route\Middleware\ExecutionChain + */ + protected function handleNotFound() + { + } + + /** + * Handles a not allowed route. + * + * @param array $allowed + * + * @return \League\Route\Middleware\ExecutionChain + */ + protected function handleNotAllowed(array $allowed) { } } diff --git a/src/Viserio/Routing/Generator/RouteTreeBuilder.php b/src/Viserio/Routing/Generator/RouteTreeBuilder.php index 1af91c630..beae4e559 100644 --- a/src/Viserio/Routing/Generator/RouteTreeBuilder.php +++ b/src/Viserio/Routing/Generator/RouteTreeBuilder.php @@ -3,6 +3,7 @@ namespace Viserio\Routing\Generator; use Viserio\Contracts\Routing\Route as RouteContract; +use Viserio\Contracts\Routing\RouteSegment as RouteSegmentContract; use Viserio\Contracts\Routing\SegmentMatcher as SegmentMatcherContract; class RouteTreeBuilder @@ -105,6 +106,10 @@ protected function addRouteToNode( */ private function getMatcher($firstSegment, array &$parameterIndexNameMap): SegmentMatcherContract { - return method_exists($firstSegment, 'getMatcher') ? $firstSegment->getMatcher($parameterIndexNameMap) : $firstSegment; + if ($firstSegment instanceof RouteSegmentContract) { + return $firstSegment->getMatcher($parameterIndexNameMap); + } + + return $firstSegment; } } diff --git a/src/Viserio/Routing/Matchers/AbstractMatcher.php b/src/Viserio/Routing/Matchers/AbstractMatcher.php index 402f04d7a..da091cd59 100644 --- a/src/Viserio/Routing/Matchers/AbstractMatcher.php +++ b/src/Viserio/Routing/Matchers/AbstractMatcher.php @@ -25,7 +25,7 @@ public function getParameterKeys(): array /** * {@inheritdoc} */ - public function getMatchedParameterExpressions(string $segmentVariable, string $uniqueKey = null): array + public function getMatchedParameterExpressions(string $segmentVariable, int $uniqueKey = null): array { return array_fill_keys($this->parameterKeys, $segmentVariable); } diff --git a/src/Viserio/Routing/Matchers/AnyMatcher.php b/src/Viserio/Routing/Matchers/AnyMatcher.php index 47678cec2..b0a40e2f6 100644 --- a/src/Viserio/Routing/Matchers/AnyMatcher.php +++ b/src/Viserio/Routing/Matchers/AnyMatcher.php @@ -17,7 +17,7 @@ public function __construct(array $parameterKeys) /** * {@inheritdoc} */ - public function getConditionExpression(string $segmentVariable, string $uniqueKey = null): string + public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string { return $segmentVariable . ' !== \'\''; } diff --git a/src/Viserio/Routing/Matchers/CompoundMatcher.php b/src/Viserio/Routing/Matchers/CompoundMatcher.php index 7aa127e87..c6a418566 100644 --- a/src/Viserio/Routing/Matchers/CompoundMatcher.php +++ b/src/Viserio/Routing/Matchers/CompoundMatcher.php @@ -31,12 +31,12 @@ public function __construct(array $matchers) /** * {@inheritdoc} */ - public function getConditionExpression(string $segmentVariable, string $uniqueKey = null): string + public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string { $conditions = []; foreach ($this->matchers as $key => $matcher) { - $conditions[] = $matcher->getConditionExpression($segmentVariable, $uniqueKey . '_' . $key); + $conditions[] = $matcher->getConditionExpression($segmentVariable, $uniqueKey + $key); } return implode(' && ', $conditions); @@ -45,14 +45,14 @@ public function getConditionExpression(string $segmentVariable, string $uniqueKe /** * {@inheritdoc} */ - public function getMatchedParameterExpressions(string $segmentVariable, string $uniqueKey = null): array + public function getMatchedParameterExpressions(string $segmentVariable, int $uniqueKey = null): array { $expressions = []; foreach ($this->matchers as $key => $matcher) { $matchedParameterExpressions = $matcher->getMatchedParameterExpressions( $segmentVariable, - $uniqueKey . '_' . $key + $uniqueKey + $key ); foreach ($matchedParameterExpressions as $parameter => $expression) { diff --git a/src/Viserio/Routing/Matchers/ExpressionMatcher.php b/src/Viserio/Routing/Matchers/ExpressionMatcher.php index 2788f1a4b..4858af492 100644 --- a/src/Viserio/Routing/Matchers/ExpressionMatcher.php +++ b/src/Viserio/Routing/Matchers/ExpressionMatcher.php @@ -36,7 +36,7 @@ public function getExpression(): string /** * {@inheritdoc} */ - public function getConditionExpression(string $segmentVariable, string $uniqueKey = null): string + public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string { return str_replace(self::SEGMENT_PLACEHOLDER, $segmentVariable, $this->expression); } diff --git a/src/Viserio/Routing/Matchers/RegexMatcher.php b/src/Viserio/Routing/Matchers/RegexMatcher.php index 3c420b549..238c6269d 100644 --- a/src/Viserio/Routing/Matchers/RegexMatcher.php +++ b/src/Viserio/Routing/Matchers/RegexMatcher.php @@ -74,7 +74,7 @@ public function getRegex(): string /** * {@inheritdoc} */ - public function getConditionExpression(string $segmentVariable, string $uniqueKey = null): string + public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string { return 'preg_match(' . VarExporter::export($this->regex) @@ -88,7 +88,7 @@ public function getConditionExpression(string $segmentVariable, string $uniqueKe /** * {@inheritdoc} */ - public function getMatchedParameterExpressions(string $segmentVariable, string $uniqueKey = null): array + public function getMatchedParameterExpressions(string $segmentVariable, int $uniqueKey = null): array { $matches = []; diff --git a/src/Viserio/Routing/Matchers/StaticMatcher.php b/src/Viserio/Routing/Matchers/StaticMatcher.php index 0e2369ef5..02b2fe507 100644 --- a/src/Viserio/Routing/Matchers/StaticMatcher.php +++ b/src/Viserio/Routing/Matchers/StaticMatcher.php @@ -35,7 +35,7 @@ public function __construct(string $segment, array $parameterKeys = null) /** * {@inheritdoc} */ - public function getConditionExpression(string $segmentVariable, string $uniqueKey = null): string + public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string { return $segmentVariable . ' === ' . VarExporter::export($this->segment); } @@ -43,7 +43,7 @@ public function getConditionExpression(string $segmentVariable, string $uniqueKe /** * {@inheritdoc} */ - public function getMatchedParameterExpressions(string $segmentVariable, string $uniqueKey = null): array + public function getMatchedParameterExpressions(string $segmentVariable, int $uniqueKey = null): array { $keys = $this->parameterKeys; diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index c558ea3ec..ca345f0ce 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -7,7 +7,8 @@ use UnexpectedValueException; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Routing\Route as RouteContract; -use Viserio\Contracts\Routing\Router as RouterContract; +use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; +use Viserio\Contracts\Routing\RouteSegment as RouteSegmentContract; use Viserio\Routing\Segments\ParameterSegment; use Viserio\Support\Invoker; @@ -46,9 +47,16 @@ class Route implements RouteContract /** * The array of matched parameters. * - * @var array|null + * @var array + */ + protected $parameters = []; + + /** + * The regular expression requirements. + * + * @var array */ - protected $parameters; + protected $wheres = []; /** * The router instance used by the route. @@ -64,29 +72,14 @@ class Route implements RouteContract */ protected $invoker; - /** - * Optional route parameters. - * - * @var \Viserio\Contracts\Routing\RouteMatcher[] - */ - protected $segments; - - /** - * Global route parameters. - * - * @var array - */ - protected $globalParameters; - /** * Create a new Route instance. * * @param array|string $methods * @param string $uri * @param \Closure|array|null $action - * @param array $globalParameters */ - public function __construct($methods, $uri, $action, array $globalParameters = []) + public function __construct($methods, string $uri, $action) { $this->uri = $uri; // According to RFC methods are defined in uppercase (See RFC 7231) @@ -100,10 +93,6 @@ public function __construct($methods, $uri, $action, array $globalParameters = [ if (isset($this->action['prefix'])) { $this->addPrefix($this->action['prefix']); } - - $this->globalParameters = $globalParameters; - - $this->parseSegment($uri); } /** @@ -134,18 +123,6 @@ public function getUri(): string return $this->uri; } - /** - * {@inheritdoc} - */ - public function setUri(string $uri): RouteContract - { - $this->uri = $uri; - - $this->parseSegment($uri); - - return $this; - } - /** * {@inheritdoc} */ @@ -172,6 +149,18 @@ public function getMethods(): array return $this->httpMethods; } + /** + * {@inheritdoc} + */ + public function where($name, string $expression = null): RouteContract + { + foreach ($this->parseWhere($name, $expression) as $name => $expression) { + $this->wheres[] = new ParameterSegment($name, $expression); + } + + return $this; + } + /** * {@inheritdoc} */ @@ -307,13 +296,11 @@ public function isStatic(): bool } /** - * Get optional route parameters. - * - * @return \Viserio\Contracts\Routing\RouteMatcher[] + * {@inheritdoc} */ public function getSegments(): array { - return $this->segments; + return (new RouteParser())->parse($this->uri, $this->wheres); } /** @@ -332,7 +319,7 @@ public function run() /** * {@inheritdoc} */ - public function setRouter(RouterContract $router): RouteContract + public function setRouter(RouteCollectionContract $router): RouteContract { $this->router = $router; @@ -405,12 +392,25 @@ protected function parseAction($action): array } /** - * Get the optional parameters for the route. + * Parse arguments to the where method into an array. + * + * @param array|string $name + * @param string $expression * - * @param string $uri + * @return array */ - private function parseSegment(string $uri) + protected function parseWhere($name, string $expression): array { - $this->segments = (new RouteParser())->parse($uri, $this->globalParameters); + if (is_string($name)) { + return [$name => $expression]; + } + + $arr = []; + + foreach ($name as $paramName) { + $arr[$paramName] = $expression; + } + + return $arr; } } diff --git a/src/Viserio/Routing/RouteCollection.php b/src/Viserio/Routing/RouteCollection.php new file mode 100644 index 000000000..603dfa974 --- /dev/null +++ b/src/Viserio/Routing/RouteCollection.php @@ -0,0 +1,494 @@ +path = $path; + $this->container = $container; + } + + /** + * Route collection is in develop mode. + * + * @param bool $isDev + */ + public function isDevelopMode(bool $isDev) + { + $this->isDevelopMode = $isDev; + } + + /** + * {@inheritdoc} + */ + public function get(string $uri, $action = null): RouteContract + { + return $this->addRoute(['GET', 'HEAD'], $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function post(string $uri, $action = null): RouteContract + { + return $this->addRoute('POST', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function put(string $uri, $action = null): RouteContract + { + return $this->addRoute('PUT', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function patch(string $uri, $action = null): RouteContract + { + return $this->addRoute('PATCH', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function delete(string $uri, $action = null): RouteContract + { + return $this->addRoute('DELETE', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function options(string $uri, $action = null): RouteContract + { + return $this->addRoute('OPTIONS', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function any(string $uri, $action = null): RouteContract + { + $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; + + return $this->addRoute($verbs, $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function match($methods, $uri, $action = null): RouteContract + { + return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function group(array $attributes, Closure $callback) + { + if (! empty($this->groupStack)) { + $attributes = $this->mergeGroup($attributes, end($this->groupStack)); + } + + $this->groupStack[] = $attributes; + + // Once we have updated the group stack, we will execute the user Closure and + // merge in the groups attributes when the route is created. After we have + // run the callback, we will pop the attributes off of this group stack. + call_user_func($callback, $this); + + array_pop($this->groupStack); + } + + /** + * Merge the given array with the last group stack. + * + * @param array $new + * + * @return array + */ + public function mergeWithLastGroup($new) + { + return $this->mergeGroup($new, end($this->groupStack)); + } + + /** + * {@inheritdoc} + */ + public function hasGroupStack(): bool + { + return ! empty($this->groupStack); + } + + /** + * {@inheritdoc} + */ + public function getGroupStack(): array + { + return $this->groupStack; + } + + /** + * Defines the supplied parameter name to be globally associated with the pattern + * + * @param string $parameterName + * @param string $pattern + * + * @return $this + */ + public function setParameter(string $parameterName, string $pattern) + { + $this->globalParameterConditions[$parameterName] = $pattern; + + return $this; + } + + /** + * Defines the supplied parameter name to be globally associated with the pattern + * + * @param string[] $parameterPatternMap + * + * @return $this + */ + public function addParameters(array $parameterPatternMap) + { + $this->globalParameterConditions += $parameterPatternMap; + + return $this; + } + + /** + * Removes the global pattern associated with the supplied parameter name + * + * @param string $name + */ + public function removeParameter(string $name) + { + unset($this->globalParameterConditions[$name]); + } + + /** + * Get all global parameters for all routes. + * + * @return array + */ + public function getParameters(): array + { + return $this->globalParameterConditions; + } + + /** + * Dispatch router for HTTP request. + * + * @param \Psr\Http\Message\ServerRequestInterface $request The current HTTP request object + * + * @return array + */ + public function dispatch(ServerRequestInterface $request) + { + $router = $this->generateRouterFile(); + + $dispatcher = new Dispatcher($router, $this->getContainer()); + + return $dispatcher->handle($request); + } + + /** + * {@inheritdoc} + */ + public function toArray(): array + { + return $this->allRoutes; + } + + /** + * Add a route to the underlying route collection. + * + * @param array|string $methods + * @param string $uri + * @param \Closure|array|string|null $action + * + * @return \Viserio\Contracts\Routing\Route + */ + protected function addRoute($methods, string $uri, $action): RouteContract + { + $route = $this->createRoute($methods, $uri, $action); + + $domainAndUri = $route->getDomain() . $route->getUri(); + + foreach ($route->getMethods() as $method) { + $this->routes[$method][$domainAndUri] = $route; + } + + return $this->allRoutes[$method . $domainAndUri] = $route; + } + + /** + * Create a new route instance. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * + * @return \Viserio\Contracts\Routing\Route + */ + protected function createRoute($methods, string $uri, $action): RouteContract + { + // If the route is routing to a controller we will parse the route action into + // an acceptable array format before registering it and creating this route + // instance itself. We need to build the Closure that will call this out. + if ($this->actionReferencesController($action)) { + $action = $this->convertToControllerAction($action); + } + + $route = (new Route($methods, $this->prefix($uri), $action)) + ->setRouter($this) + ->setContainer($this->container); + + // If we have groups that need to be merged, we will merge them now after this + // route has already been created and is ready to go. After we're done with + // the merge we will be ready to return the route back out to the caller. + if ($this->hasGroupStack()) { + $action = $this->mergeWithLastGroup($route->getAction()); + + $route->setAction($action); + } + + return $route; + } + + /** + * Determine if the action is routing to a controller. + * + * @param string|array|\Closure $action + * + * @return bool + */ + protected function actionReferencesController($action): bool + { + if ($action instanceof Closure) { + return false; + } + + return is_string($action) || (isset($action['uses']) && is_string($action['uses'])); + } + + /** + * Add a controller based route action to the action array. + * + * @param array|string $action + * + * @return array + */ + protected function convertToControllerAction($action): array + { + if (is_string($action)) { + $action = ['uses' => $action]; + } + + // Here we'll merge any group "uses" statement if necessary so that the action + // has the proper clause for this property. Then we can simply set the name + // of the controller on the action and return the action array for usage. + if (! empty($this->groupStack)) { + $action['uses'] = $this->prependGroupUses($action['uses']); + } + + // Here we will set this controller name on the action array just so we always + // have a copy of it for reference if we need it. This can be used while we + // search for a controller name or do some other type of fetch operation. + $action['controller'] = $action['uses']; + + return $action; + } + + /** + * Merge the given group attributes. + * + * @param array $new + * @param array $old + * + * @return array + */ + protected function mergeGroup(array $new, array $old): array + { + $new['namespace'] = $this->formatUsesPrefix($new, $old); + $new['prefix'] = $this->formatGroupPrefix($new, $old); + + if (isset($new['domain'])) { + unset($old['domain']); + } + + $new['where'] = array_merge($old['where'] ?? [], $new['where'] ?? []); + + if (isset($old['as'])) { + $new['as'] = $old['as'] . ($new['as'] ?? ''); + } + + return array_merge_recursive(Arr::except($old, ['namespace', 'prefix', 'where', 'as']), $new); + } + + /** + * Format the uses prefix for the new group attributes. + * + * @param array $new + * @param array $old + * + * @return string|null + */ + protected function formatUsesPrefix(array $new, array $old) + { + if (isset($new['namespace'])) { + return isset($old['namespace']) + ? trim($old['namespace'], '\\') . '\\' . trim($new['namespace'], '\\') + : trim($new['namespace'], '\\'); + } + + return $old['namespace'] ?? null; + } + + /** + * Format the prefix for the new group attributes. + * + * @param array $new + * @param array $old + * + * @return string|null + */ + protected function formatGroupPrefix(array $new, array $old) + { + $oldPrefix = $old['prefix'] ?? null; + + if (isset($new['prefix'])) { + return trim($oldPrefix, '/') . '/' . trim($new['prefix'], '/'); + } + + return $oldPrefix; + } + + /** + * Prefix the given URI with the last prefix. + * + * @param string $uri + * + * @return string + */ + protected function prefix($uri) + { + return '/' . trim(trim($this->getLastGroupPrefix(), '/') . '/' . trim($uri, '/'), '/'); + } + + /** + * Get the prefix from the last group on the stack. + * + * @return string + */ + protected function getLastGroupPrefix(): string + { + if (! empty($this->groupStack)) { + $last = end($this->groupStack); + + return isset($last['prefix']) ? $last['prefix'] : ''; + } + + return ''; + } + + /** + * Prepend the last group uses onto the use clause. + * + * @param string $uses + * + * @return string + */ + protected function prependGroupUses(string $uses): string + { + $group = end($this->groupStack); + + return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'].'\\'.$uses : $uses; + } + + /** + * Generates a router file with all routes. + * + * @return object + */ + protected function generateRouterFile() + { + if ($this->isDevelopMode && file_exists($this->path)) { + @unlink($this->path); + } + + if (!file_exists($this->path)) { + $routerCompiler = new TreeRouteCompiler(new RouteTreeBuilder(), new RouteTreeOptimizer()); + + file_put_contents($this->path, $routerCompiler->compile($this)); + } + + return require $this->path; + } +} diff --git a/src/Viserio/Routing/RouteGroup.php b/src/Viserio/Routing/RouteGroup.php deleted file mode 100644 index 2b6b84fbd..000000000 --- a/src/Viserio/Routing/RouteGroup.php +++ /dev/null @@ -1,67 +0,0 @@ -callback = $callback; - $this->collection = $collection; - $this->prefix = sprintf('/%s', ltrim($prefix, '/')); - } - - /** - * Process the group and ensure routes are added to the collection. - */ - public function __invoke() - { - call_user_func_array($this->callback, [$this]); - } - - /** - * {@inheritdoc} - */ - public function map($method, $path, $handler) - { - $path = ($path === '/') ? $this->prefix : $this->prefix . sprintf('/%s', ltrim($path, '/')); - $route = $this->collection->map($method, $path, $handler); - $route->setParentGroup($this); - - if ($host = $this->getHost()) { - $route->setHost($host); - } - - if ($scheme = $this->getScheme()) { - $route->setScheme($scheme); - } - - foreach ($this->getMiddlewareStack() as $middleware) { - $route->middleware($middleware); - } - - return $route; - } -} diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php deleted file mode 100644 index dca17aaad..000000000 --- a/src/Viserio/Routing/Router.php +++ /dev/null @@ -1,278 +0,0 @@ -container = $container; - } - - /** - * {@inheritdoc} - */ - public function get(string $uri, $action = null): RouteContract - { - return $this->addRoute(['GET', 'HEAD'], $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function post(string $uri, $action = null): RouteContract - { - return $this->addRoute('POST', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function put(string $uri, $action = null): RouteContract - { - return $this->addRoute('PUT', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function patch(string $uri, $action = null): RouteContract - { - return $this->addRoute('PATCH', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function delete(string $uri, $action = null): RouteContract - { - return $this->addRoute('DELETE', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function options(string $uri, $action = null): RouteContract - { - return $this->addRoute('OPTIONS', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function any(string $uri, $action = null): RouteContract - { - $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; - - return $this->addRoute($verbs, $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function match($methods, $uri, $action = null): RouteContract - { - return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function getGroup(): RouteGroupContract - { - return $this->group; - } - - /** - * {@inheritdoc} - */ - public function group(array $attributes, Closure $callback): RouterContract - { - $this->group = $group; - - return $this; - } - - /** - * Defines the supplied parameter name to be globally associated with the pattern - * - * @param string $parameterName - * @param string $pattern - * - * @return $this - */ - public function setGlobalParameter(string $parameterName, string $pattern) - { - $this->globalParameterConditions[$parameterName] = $pattern; - - return $this; - } - - /** - * Defines the supplied parameter name to be globally associated with the pattern - * - * @param string[] $parameterPatternMap - * - * @return $this - */ - public function addGlobalParameters(array $parameterPatternMap) - { - $this->globalParameterConditions += $parameterPatternMap; - - return $this; - } - - /** - * Removes the global pattern associated with the supplied parameter name - * - * @param string $name - */ - public function removeGlobalParameter(string $name) - { - unset($this->globalParameterConditions[$name]); - } - - /** - * Get all global parameters for all routes. - * - * @return array - */ - public function getGlobalParameters(): array - { - return $this->globalParameterConditions; - } - - /** - * Dispatch router for HTTP request. - * - * @param \Psr\Http\Message\ServerRequestInterface $request The current HTTP request object - * - * @return array - */ - public function dispatch(ServerRequestInterface $request) - { - } - - /** - * {@inheritdoc} - */ - public function toArray(): array - { - return $this->allRoutes; - } - - /** - * Add a route to the underlying route collection. - * - * @param array|string $methods - * @param string $uri - * @param \Closure|array|string|null $action - * - * @return \Viserio\Contracts\Routing\Route - */ - protected function addRoute($methods, string $uri, $action): RouteContract - { - $route = $this->createRoute($methods, $uri, $action); - - $domainAndUri = $route->getDomain() . $route->getUri(); - - foreach ($route->getMethods() as $method) { - $this->routes[$method][$domainAndUri] = $route; - } - - return $this->allRoutes[$method . $domainAndUri] = $route; - } - - /** - * Create a new route instance. - * - * @param array|string $methods - * @param string $uri - * @param mixed $action - * - * @return \Viserio\Contracts\Routing\Route - */ - protected function createRoute($methods, string $uri, $action): RouteContract - { - $route = $this->newRoute( - $methods, - $this->prefix($uri), - $action, - $this->getGlobalParameters() - ); - - return $route; - } - - /** - * Create a new Route object. - * - * @param array|string $methods - * @param string $uri - * @param mixed $action - * - * @return \Viserio\Contracts\Routing\Route - */ - protected function newRoute($methods, string $uri, $action): Route - { - return (new Route($methods, $uri, $action)) - ->setRouter($this) - ->setContainer($this->container); - } - - /** - * Prefix the given URI with the last prefix. - * - * @param string $uri - * - * @return string - */ - protected function prefix($uri) - { - return trim('/' . trim($uri, '/'), '/') ?: '/'; - } -} diff --git a/src/Viserio/Routing/Segments/ParameterSegment.php b/src/Viserio/Routing/Segments/ParameterSegment.php index 9136f1247..a173808ab 100644 --- a/src/Viserio/Routing/Segments/ParameterSegment.php +++ b/src/Viserio/Routing/Segments/ParameterSegment.php @@ -2,9 +2,11 @@ declare(strict_types=1); namespace Viserio\Routing\Segments; +use Viserio\Contracts\Routing\RouteSegment as RouteSegmentContract; +use Viserio\Contracts\Routing\SegmentMatcher as SegmentMatcherContract; use Viserio\Routing\Matchers\RegexMatcher; -class ParameterSegment +class ParameterSegment implements RouteSegmentContract { /** * @var string[] @@ -29,13 +31,9 @@ public function __construct($names, string $regex) } /** - * [getMatcher description] - * - * @param array &$parameterIndexNameMap - * - * @return \Viserio\Routing\Matchers\RegexMatcher + * {@inheritdoc} */ - public function getMatcher(array &$parameterIndexNameMap): RegexMatcher + public function getMatcher(array &$parameterIndexNameMap): SegmentMatcherContract { $parameterKey = empty($parameterIndexNameMap) ? 0 : max(array_keys($parameterIndexNameMap)) + 1; $parameterKeyGroupMap = []; diff --git a/src/Viserio/Routing/Tests/Cache/.gitignore b/src/Viserio/Routing/Tests/Cache/.gitignore new file mode 100644 index 000000000..021099315 --- /dev/null +++ b/src/Viserio/Routing/Tests/Cache/.gitignore @@ -0,0 +1 @@ +router.cache diff --git a/src/Viserio/Routing/Tests/DispatcherTest.php b/src/Viserio/Routing/Tests/DispatcherTest.php deleted file mode 100644 index 68503717c..000000000 --- a/src/Viserio/Routing/Tests/DispatcherTest.php +++ /dev/null @@ -1,13 +0,0 @@ -setParameter('root-route', '')], + [(new Route(['GET'], '/{param}', null))->where('param', Pattern::ANY)->setParameter('root-route', '')], null, [ 1 => new ChildrenNodeCollection([ @@ -51,7 +50,7 @@ public function routeTreeBuilderCases() [ [ (new Route(['GET'], '/first/{param1}', null))->setParameter('static-first', ''), - (new Route(['GET'], '/{param1}/{param2}', null, [new ParameterSegment('param1', Pattern::ANY), new ParameterSegment('param2', Pattern::ANY)]))->setParameter('dynamic', ''), + (new Route(['GET'], '/{param1}/{param2}', null))->where(['param1', 'param2'], Pattern::ANY)->setParameter('dynamic', ''), ], null, [ @@ -89,8 +88,8 @@ public function routeTreeBuilderCases() (new Route(['POST'], '/main/place', null))->setParameter('main.place-post', ''), (new Route('ANY', '/main/thing', null))->setParameter('main.thing', ''), (new Route('ANY', '/main/thing/abc', null))->setParameter('main.thing.abc', ''), - (new Route('ANY', '/user/{name}', null, [new StaticMatcher('user'), new ParameterSegment('name', Pattern::ANY)]))->setParameter('user.show', ''), - (new Route('ANY', '/user/{name}/edit', null, [new ParameterSegment('name', Pattern::ANY)]))->setParameter('user.edit', ''), + (new Route('ANY', '/user/{name}', null))->where('name', Pattern::ANY)->setParameter('user.show', ''), + (new Route('ANY', '/user/{name}/edit', null))->where('name', Pattern::ANY)->setParameter('user.edit', ''), (new Route('ANY', '/user/create', null))->setParameter('user.create', ''), ], new MatchedRouteDataMap([], [[], ['home' => '']]), diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php index 75113c1dc..fe4db28be 100644 --- a/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeOptimizerTest.php @@ -228,7 +228,8 @@ public function optimizationCasesProvider() [ 2 => new StaticMatcher('1'), ], - new ChildrenNodeCollection([])), + new ChildrenNodeCollection([]) + ), new RouteTreeNode( [ 2 => new StaticMatcher('3'), diff --git a/src/Viserio/Routing/Tests/Matchers/CompoundMatcherTest.php b/src/Viserio/Routing/Tests/Matchers/CompoundMatcherTest.php index 0c8f63e37..32ce14246 100644 --- a/src/Viserio/Routing/Tests/Matchers/CompoundMatcherTest.php +++ b/src/Viserio/Routing/Tests/Matchers/CompoundMatcherTest.php @@ -15,7 +15,7 @@ public function testGetConditionExpression() new AnyMatcher([0]), ]); - $this->assertSame('test === \'test\' && test !== \'\'', $matcher->getConditionExpression('test', '2')); + $this->assertSame('test === \'test\' && test !== \'\'', $matcher->getConditionExpression('test', 2)); } public function testGetMatchedParameterExpressions() @@ -25,7 +25,7 @@ public function testGetMatchedParameterExpressions() new AnyMatcher([0]), ]); - $this->assertSame([1 => 'test', 0 => 'test'], $matcher->getMatchedParameterExpressions('test', '2')); + $this->assertSame([1 => 'test', 0 => 'test'], $matcher->getMatchedParameterExpressions('test', 2)); } public function testGetHash() @@ -45,9 +45,9 @@ public function testCompoundSegmentMatcher() $this->assertSame([0], $matcher1->getParameterKeys()); $this->assertNotEquals($matcher2->getHash(), $matcher1->getHash()); - $this->assertSame('$segment === \'a\' && $segment === \'b\'', $matcher1->getConditionExpression('$segment', '0')); - $this->assertSame([0 => '$segment'], $matcher1->getMatchedParameterExpressions('$segment', '0')); - $this->assertSame('$segment === \'a\' && $segment === \'c\'', $matcher2->getConditionExpression('$segment', '0')); - $this->assertSame([0 => '$segment', 1 => '$segment'], $matcher2->getMatchedParameterExpressions('$segment', '0')); + $this->assertSame('$segment === \'a\' && $segment === \'b\'', $matcher1->getConditionExpression('$segment', 0)); + $this->assertSame([0 => '$segment'], $matcher1->getMatchedParameterExpressions('$segment', 0)); + $this->assertSame('$segment === \'a\' && $segment === \'c\'', $matcher2->getConditionExpression('$segment', 0)); + $this->assertSame([0 => '$segment', 1 => '$segment'], $matcher2->getMatchedParameterExpressions('$segment', 0)); } } diff --git a/src/Viserio/Routing/Tests/RouteCollectionTest.php b/src/Viserio/Routing/Tests/RouteCollectionTest.php new file mode 100644 index 000000000..555a6d972 --- /dev/null +++ b/src/Viserio/Routing/Tests/RouteCollectionTest.php @@ -0,0 +1,31 @@ +mock(ContainerInterface::class)); + $router->isDevelopMode(true); + + $router->get('/', function () { + return 'hello'; + }); + + // $this->assertEquals('hello', $router->dispatch((new ServerRequestFactory())->createServerRequest('GET', '/'))); + + $router->get('/news/page/{slug}', function ($slug) { + return $slug; + }); + + // $this->assertEquals('hello', $router->dispatch((new ServerRequestFactory())->createServerRequest('GET', '/news/123/hello'))); + } +} diff --git a/src/Viserio/Routing/Tests/RouteTest.php b/src/Viserio/Routing/Tests/RouteTest.php index d5c1aecd1..39b9a7f9d 100644 --- a/src/Viserio/Routing/Tests/RouteTest.php +++ b/src/Viserio/Routing/Tests/RouteTest.php @@ -5,25 +5,14 @@ use Interop\Container\ContainerInterface; use Narrowspark\TestingHelper\Traits\MockeryTrait; use Viserio\Routing\Route; -use Viserio\Routing\RouteParser; -use Viserio\Routing\Router; +use Viserio\Routing\Dispatcher; use Viserio\Routing\Tests\Fixture\Controller; +use Viserio\Http\ServerRequestFactory; class RouteTest extends \PHPUnit_Framework_TestCase { use MockeryTrait; - public function testBasicDispatchingOfRoutes() - { - $router = $this->getRouter(); - // $router->get('/hello/{name}', function (Request $request, Response $response) { - // $name = $request->getAttribute('name'); - // $response->getBody()->write("Hello, $name"); - - // return $response; - // }); - } - public function testGetMethods() { $route = new Route('GET', '/test', ['uses' => Controller::class . '::string']); @@ -51,10 +40,6 @@ public function testGetAndSetUri() $route = new Route('GET', '/test', ['domain' => 'test.com']); $this->assertSame('/test', $route->getUri()); - - $route->setUri('/foo/bar'); - - $this->assertSame('/foo/bar', $route->getUri()); } public function testGetAndSetName() @@ -100,9 +85,4 @@ public function testSetAndGetPrefix() $this->assertSame('test/foo/test', $route->getUri()); } - - protected function getRouter() - { - return new Router($this->mock(ContainerInterface::class), new RouteParser()); - } } diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php index 3730c54bb..1c1d52561 100644 --- a/src/Viserio/Routing/TreeRouteCompiler.php +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -2,8 +2,12 @@ declare(strict_types=1); namespace Viserio\Routing; +use Viserio\Contracts\Routing\Dispatcher; +use Viserio\Routing\Generator\MatchedRouteDataMap; +use Viserio\Routing\Generator\ChildrenNodeCollection; use Viserio\Routing\Generator\RouteTreeBuilder; use Viserio\Routing\Generator\RouteTreeOptimizer; +use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; class TreeRouteCompiler { @@ -36,11 +40,312 @@ public function __construct(RouteTreeBuilder $treeBuilder, RouteTreeOptimizer $t /** * Complie all added routes to a router handler. * - * @param array $routes + * @param \Viserio\Contracts\Routing\RouteCollection $routes * * @return string */ - public function compile(array $routes): string + public function compile(RouteCollectionContract $routes): string { + $routeTree = $this->treeOptimizer->optimize( + $this->treeBuilder->build($routes->toArray()) + ); + + $code = $this->phpBuilder(); + $code->indent = 1; + + $this->compileRouteTree($code, $routeTree); + + $rootRouteCode = $this->phpBuilder(); + $rootRouteCode->indent = 2; + + if ($routeTree[0] !== null && ! $routeTree[0]->isEmpty()) { + $this->compiledRouteHttpMethodMatch($rootRouteCode, $routeTree[0], []); + } else { + $this->compileNotFound($rootRouteCode); + } + + return $this->createRouterClassTemplate(substr($rootRouteCode->code, 0, -strlen(PHP_EOL)), $code->code); + } + + /** + * Creating a template for the router class. + * + * @param string $rootRoute + * @param string $body + * + * @return string + */ + protected function createRouterClassTemplate(string $rootRoute, string $body): string + { + $template = <<<'PHP' + $rootRoute, '{body}' => $body]); + } + + /** + * [compileRouteTree description] + * + * @param object $code + * @param array $routeTree + */ + protected function compileRouteTree($code, array $routeTree) + { + $code->appendLine('switch (count($segments)) {'); + + ++$code->indent; + + foreach ($routeTree[1] as $segmentDepth => $nodes) { + $code->appendLine('case ' . VarExporter::export($segmentDepth) . ':'); + + ++$code->indent; + + $segmentVariables = []; + + for ($i = 0; $i < $segmentDepth; ++$i) { + $segmentVariables[$i] = '$s' . $i; + } + + $code->appendLine('list(' . implode(', ', $segmentVariables) . ') = $segments;'); + + $this->compileSegmentNodes($code, $nodes, $segmentVariables); + $this->compileDisallowedHttpMethodOrNotFound($code); + + $code->appendLine('break;'); + + --$code->indent; + + $code->appendLine(); + } + + $code->appendLine('default:'); + + ++$code->indent; + + $this->compileNotFound($code); + + --$code->indent; + --$code->indent; + + $code->append('}'); + } + + /** + * [compileSegmentNodes description] + * + * @param object $code + * @param ChildrenNodeCollection $nodes + * @param array $segmentVariables + * @param array $parameters + */ + protected function compileSegmentNodes($code, ChildrenNodeCollection $nodes, array $segmentVariables, array $parameters = []) + { + $originalParameters = $parameters; + + foreach ($nodes->getChildren() as $node) { + $parameters = $originalParameters; + $segmentMatchers = $node->getMatchers(); + $conditions = []; + $currentParameter = empty($parameters) ? 0 : max(array_keys($parameters)) + 1; + $count = $currentParameter; + + foreach ($segmentMatchers as $segmentDepth => $matcher) { + $conditions[] = $matcher->getConditionExpression($segmentVariables[$segmentDepth], $count++); + } + + $code->appendLine('if (' . implode(' && ', $conditions) . ') {'); + + ++$code->indent; + + $count = $currentParameter; + + foreach ($segmentMatchers as $segmentDepth => $matcher) { + $matchedParameters = $matcher->getMatchedParameterExpressions($segmentVariables[$segmentDepth], $count++); + + foreach ($matchedParameters as $parameterKey => $matchedParameter) { + $parameters[$parameterKey] = $matchedParameter; + } + } + + $contents = $node->getContents(); + + if ($contents instanceof MatchedRouteDataMap) { + $this->compiledRouteHttpMethodMatch($code, $contents, $parameters); + } else { + $this->compileSegmentNodes($code, $contents, $segmentVariables, $parameters); + } + + --$code->indent; + + $code->appendLine('}'); + } + } + + protected function compiledRouteHttpMethodMatch($code, MatchedRouteDataMap $routeDataMap, array $parameters) + { + $code->appendLine('switch ($method) {'); + + ++$code->indent; + + foreach ($routeDataMap->getHttpMethodRouteDataMap() as $item) { + list($httpMethods, $routeData) = $item; + + foreach ($httpMethods as $httpMethod) { + $code->appendLine('case ' . VarExporter::export($httpMethod) . ':'); + } + + ++$code->indent; + + $this->compileFoundRoute($code, $routeData, $parameters); + + --$code->indent; + } + + $code->appendLine('default:'); + + ++$code->indent; + + if ($routeDataMap->hasDefaultRouteData()) { + $this->compileFoundRoute($code, $routeDataMap->getDefaultRouteData(), $parameters); + } else { + foreach ($routeDataMap->getAllowedHttpMethods() as $method) { + $code->appendLine('$allowedHttpMethods[] = ' . VarExporter::export($method) . ';'); + } + + $code->appendLine('break;'); + } + + --$code->indent; + --$code->indent; + + $code->appendLine('}'); + } + + /** + * [compileNotFound description] + * + * @param object $code + */ + protected function compileNotFound($code) + { + $code->appendLine('return [' . VarExporter::export(Dispatcher::NOT_FOUND) . '];'); + } + + /** + * [compileDisallowedHttpMethod + * + * @param object $code + * @param array $allowedMethod + */ + protected function compileDisallowedHttpMethod($code, array $allowedMethod) + { + $code->appendLine('return [' . VarExporter::export(Dispatcher::HTTP_METHOD_NOT_ALLOWED) . ', ' . VarExporter::export($allowedMethod) . '];'); + } + + /** + * [compileDisallowedHttpMethodOrNotFound + * + * @param object $code + */ + protected function compileDisallowedHttpMethodOrNotFound($code) + { + $code->appendLine('return ' . + 'isset($allowedHttpMethods) ' + . '? ' + . '[' + . VarExporter::export(Dispatcher::HTTP_METHOD_NOT_ALLOWED) + . ', $allowedHttpMethods] ' + . ': ' + . '[' + . VarExporter::export(Dispatcher::NOT_FOUND) + . '];'); + } + + /** + * [compileFoundRoute description] + * + * @param object $code + * @param array $foundRoute + * @param array $parameterExpressions + */ + protected function compileFoundRoute($code, array $foundRoute, array $parameterExpressions) + { + $parameters = '['; + + foreach ($foundRoute[0] as $index => $parameterName) { + $parameters .= VarExporter::export($parameterName) . ' => ' . $parameterExpressions[$index] . ', '; + } + + if (strlen($parameters) > 2) { + $parameters = substr($parameters, 0, -2); + } + + $parameters .= ']'; + + $code->appendLine('return [' + . VarExporter::export(Dispatcher::FOUND) + . ', ' + . VarExporter::export($foundRoute[1]) + . ', ' + . $parameters + . '];' + ); + } + + /** + * The php code builder class. + * + * @return object + */ + private function phpBuilder() + { + return new class() { + /** + * The php code. + * + * @var string + */ + public $code = ''; + + /** + * The current indentation level of the code + * + * @var int + */ + public $indent = ''; + + /** + * Appends the supplied code to the builder. + * + * @param string $code + */ + public function append(string $code) + { + $indent = str_repeat(' ', 4 * $this->indent); + + $this->code .= $indent . str_replace(PHP_EOL, PHP_EOL . $indent, $code); + } + + /** + * Appends the supplied code and a new line to the builder. + * + * @param string $code + */ + public function appendLine(string $code = '') + { + $this->append($code); + $this->code .= PHP_EOL; + } + }; } } From 4fabd544ee158eafeda6e7936989f98f88b0586f Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 17:31:03 +0000 Subject: [PATCH 09/20] Applied fixes from StyleCI --- src/Viserio/Routing/Dispatcher.php | 5 +++-- src/Viserio/Routing/RouteCollection.php | 4 +--- src/Viserio/Routing/Tests/RouteCollectionTest.php | 4 ++-- src/Viserio/Routing/Tests/RouteTest.php | 4 ++-- src/Viserio/Routing/TreeRouteCompiler.php | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Viserio/Routing/Dispatcher.php b/src/Viserio/Routing/Dispatcher.php index dde23e790..20e233b62 100644 --- a/src/Viserio/Routing/Dispatcher.php +++ b/src/Viserio/Routing/Dispatcher.php @@ -47,7 +47,8 @@ public function handle(ServerRequestInterface $request) $match = $router( $request->getMethod(), $request->getUri()->getPath() - );var_dump($match); + ); + var_dump($match); switch ($match[0]) { case DispatcherContract::NOT_FOUND: @@ -55,6 +56,7 @@ public function handle(ServerRequestInterface $request) break; case DispatcherContract::HTTP_METHOD_NOT_ALLOWED: $allowed = (array) $match[1]; + return $this->handleNotAllowed($allowed); break; case DispatcherContract::FOUND: @@ -74,7 +76,6 @@ public function handle(ServerRequestInterface $request) */ protected function handleFound(callable $route, array $vars) { - } /** diff --git a/src/Viserio/Routing/RouteCollection.php b/src/Viserio/Routing/RouteCollection.php index 603dfa974..17566718e 100644 --- a/src/Viserio/Routing/RouteCollection.php +++ b/src/Viserio/Routing/RouteCollection.php @@ -45,8 +45,6 @@ class RouteCollection implements RouteCollectionContract protected $globalParameterConditions = []; /** - * - * * @var bool */ protected $isDevelopMode = true; @@ -469,7 +467,7 @@ protected function prependGroupUses(string $uses): string { $group = end($this->groupStack); - return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'].'\\'.$uses : $uses; + return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'] . '\\' . $uses : $uses; } /** diff --git a/src/Viserio/Routing/Tests/RouteCollectionTest.php b/src/Viserio/Routing/Tests/RouteCollectionTest.php index 555a6d972..a1fb0b830 100644 --- a/src/Viserio/Routing/Tests/RouteCollectionTest.php +++ b/src/Viserio/Routing/Tests/RouteCollectionTest.php @@ -4,8 +4,8 @@ use Interop\Container\ContainerInterface; use Narrowspark\TestingHelper\Traits\MockeryTrait; -use Viserio\Routing\RouteCollection; use Viserio\Http\ServerRequestFactory; +use Viserio\Routing\RouteCollection; class RouteCollectionTest extends \PHPUnit_Framework_TestCase { @@ -13,7 +13,7 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase public function testMatch() { - $router = new RouteCollection(__DIR__.'/Cache/router.cache', $this->mock(ContainerInterface::class)); + $router = new RouteCollection(__DIR__ . '/Cache/router.cache', $this->mock(ContainerInterface::class)); $router->isDevelopMode(true); $router->get('/', function () { diff --git a/src/Viserio/Routing/Tests/RouteTest.php b/src/Viserio/Routing/Tests/RouteTest.php index 39b9a7f9d..6e1945372 100644 --- a/src/Viserio/Routing/Tests/RouteTest.php +++ b/src/Viserio/Routing/Tests/RouteTest.php @@ -4,10 +4,10 @@ use Interop\Container\ContainerInterface; use Narrowspark\TestingHelper\Traits\MockeryTrait; -use Viserio\Routing\Route; +use Viserio\Http\ServerRequestFactory; use Viserio\Routing\Dispatcher; +use Viserio\Routing\Route; use Viserio\Routing\Tests\Fixture\Controller; -use Viserio\Http\ServerRequestFactory; class RouteTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php index 1c1d52561..4e0e87f56 100644 --- a/src/Viserio/Routing/TreeRouteCompiler.php +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -3,11 +3,11 @@ namespace Viserio\Routing; use Viserio\Contracts\Routing\Dispatcher; -use Viserio\Routing\Generator\MatchedRouteDataMap; +use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; use Viserio\Routing\Generator\ChildrenNodeCollection; +use Viserio\Routing\Generator\MatchedRouteDataMap; use Viserio\Routing\Generator\RouteTreeBuilder; use Viserio\Routing\Generator\RouteTreeOptimizer; -use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; class TreeRouteCompiler { From 57b18d12b358140daa208f370d2c9740842f94fc Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 19:50:07 +0200 Subject: [PATCH 10/20] fixed test --- src/Viserio/Routing/Route.php | 1 - src/Viserio/Routing/RouteCollection.php | 3 +- .../Routing/Tests/RouteCollectionBaseTest.php | 10 ++++++ .../Routing/Tests/RouteCollectionTest.php | 31 ------------------- src/Viserio/Routing/Tests/RouteTest.php | 3 -- src/Viserio/Routing/TreeRouteCompiler.php | 1 - 6 files changed, 11 insertions(+), 38 deletions(-) create mode 100644 src/Viserio/Routing/Tests/RouteCollectionBaseTest.php delete mode 100644 src/Viserio/Routing/Tests/RouteCollectionTest.php diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index ca345f0ce..4e0fe553b 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -8,7 +8,6 @@ use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; -use Viserio\Contracts\Routing\RouteSegment as RouteSegmentContract; use Viserio\Routing\Segments\ParameterSegment; use Viserio\Support\Invoker; diff --git a/src/Viserio/Routing/RouteCollection.php b/src/Viserio/Routing/RouteCollection.php index 17566718e..5849f2665 100644 --- a/src/Viserio/Routing/RouteCollection.php +++ b/src/Viserio/Routing/RouteCollection.php @@ -6,7 +6,6 @@ use Interop\Container\ContainerInterface; use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; -use Viserio\Contracts\Events\Traits\EventsAwareTrait; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; use Viserio\Routing\Generator\RouteTreeBuilder; @@ -481,7 +480,7 @@ protected function generateRouterFile() @unlink($this->path); } - if (!file_exists($this->path)) { + if (! file_exists($this->path)) { $routerCompiler = new TreeRouteCompiler(new RouteTreeBuilder(), new RouteTreeOptimizer()); file_put_contents($this->path, $routerCompiler->compile($this)); diff --git a/src/Viserio/Routing/Tests/RouteCollectionBaseTest.php b/src/Viserio/Routing/Tests/RouteCollectionBaseTest.php new file mode 100644 index 000000000..eb658e54a --- /dev/null +++ b/src/Viserio/Routing/Tests/RouteCollectionBaseTest.php @@ -0,0 +1,10 @@ +mock(ContainerInterface::class)); - $router->isDevelopMode(true); - - $router->get('/', function () { - return 'hello'; - }); - - // $this->assertEquals('hello', $router->dispatch((new ServerRequestFactory())->createServerRequest('GET', '/'))); - - $router->get('/news/page/{slug}', function ($slug) { - return $slug; - }); - - // $this->assertEquals('hello', $router->dispatch((new ServerRequestFactory())->createServerRequest('GET', '/news/123/hello'))); - } -} diff --git a/src/Viserio/Routing/Tests/RouteTest.php b/src/Viserio/Routing/Tests/RouteTest.php index 6e1945372..035eb11fe 100644 --- a/src/Viserio/Routing/Tests/RouteTest.php +++ b/src/Viserio/Routing/Tests/RouteTest.php @@ -2,10 +2,7 @@ declare(strict_types=1); namespace Viserio\Routing\Tests; -use Interop\Container\ContainerInterface; use Narrowspark\TestingHelper\Traits\MockeryTrait; -use Viserio\Http\ServerRequestFactory; -use Viserio\Routing\Dispatcher; use Viserio\Routing\Route; use Viserio\Routing\Tests\Fixture\Controller; diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php index 4e0e87f56..540e6daee 100644 --- a/src/Viserio/Routing/TreeRouteCompiler.php +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -2,7 +2,6 @@ declare(strict_types=1); namespace Viserio\Routing; -use Viserio\Contracts\Routing\Dispatcher; use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; use Viserio\Routing\Generator\ChildrenNodeCollection; use Viserio\Routing\Generator\MatchedRouteDataMap; From a029ec6070ea06053edaf326bd4ae54cf44281c9 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Mon, 22 Aug 2016 00:30:56 +0200 Subject: [PATCH 11/20] Updated middleware | added more functions to Route and RouteCollection --- .../Contracts/Middleware/ClientMiddleware.php | 8 +- .../Middleware/{Frame.php => Delegate.php} | 6 +- .../Contracts/Middleware/ServerMiddleware.php | 8 +- src/Viserio/Contracts/Routing/Route.php | 39 +++++-- .../Contracts/Routing/RouteCollection.php | 15 +++ .../Middleware/AddQueuedCookiesToResponse.php | 4 +- .../Cookie/Middleware/EncryptedCookie.php | 4 +- src/Viserio/Middleware/Dispatcher.php | 4 +- .../Tests/Fixture/FakeContainerMiddleware.php | 4 +- .../Tests/Fixture/FakeMiddleware.php | 4 +- .../Tests/Fixture/FakeMiddleware2.php | 4 +- src/Viserio/Routing/Dispatcher.php | 26 +---- src/Viserio/Routing/Route.php | 61 +++++++--- src/Viserio/Routing/RouteCollection.php | 107 +++++++++++++----- src/Viserio/Routing/Tests/Cache/.gitignore | 2 +- .../RouteCollection/RootRoutesRouterTest.php | 34 ++++++ .../RouteCollectionBaseTest.php | 45 ++++++++ .../Routing/Tests/RouteCollectionBaseTest.php | 10 -- .../Session/Middleware/SessionMiddleware.php | 4 +- .../Middleware/VerifyCsrfTokenMiddleware.php | 4 +- .../Middleware/ShareErrorsFromSession.php | 4 +- 21 files changed, 288 insertions(+), 109 deletions(-) rename src/Viserio/Contracts/Middleware/{Frame.php => Delegate.php} (71%) create mode 100644 src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php create mode 100644 src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php delete mode 100644 src/Viserio/Routing/Tests/RouteCollectionBaseTest.php diff --git a/src/Viserio/Contracts/Middleware/ClientMiddleware.php b/src/Viserio/Contracts/Middleware/ClientMiddleware.php index bda798b9b..d2215c41f 100644 --- a/src/Viserio/Contracts/Middleware/ClientMiddleware.php +++ b/src/Viserio/Contracts/Middleware/ClientMiddleware.php @@ -13,13 +13,13 @@ interface ClientMiddleware extends Middleware * Takes the incoming request and optionally modifies it before delegating * to the next frame to get a response. * - * @param RequestInterface $request - * @param Frame $next + * @param \Psr\Http\Message\RequestInterface $request + * @param \Viserio\Contracts\Middleware\Delegate $next * - * @return ResponseInterface + * @return \Psr\Http\Message\ResponseInterface */ public function process( RequestInterface $request, - Frame $next + Delegate $next ): ResponseInterface; } diff --git a/src/Viserio/Contracts/Middleware/Frame.php b/src/Viserio/Contracts/Middleware/Delegate.php similarity index 71% rename from src/Viserio/Contracts/Middleware/Frame.php rename to src/Viserio/Contracts/Middleware/Delegate.php index bd976b698..9bdb98b3b 100644 --- a/src/Viserio/Contracts/Middleware/Frame.php +++ b/src/Viserio/Contracts/Middleware/Delegate.php @@ -5,14 +5,14 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -interface Frame +interface Delegate { /** * Dispatch the next available middleware and return the response. * - * @param RequestInterface $request + * @param \Psr\Http\Message\RequestInterface $request * - * @return ResponseInterface + * @return \Psr\Http\Message\ResponseInterface */ public function next(RequestInterface $request): ResponseInterface; } diff --git a/src/Viserio/Contracts/Middleware/ServerMiddleware.php b/src/Viserio/Contracts/Middleware/ServerMiddleware.php index d42113f1c..1992dd577 100644 --- a/src/Viserio/Contracts/Middleware/ServerMiddleware.php +++ b/src/Viserio/Contracts/Middleware/ServerMiddleware.php @@ -13,13 +13,13 @@ interface ServerMiddleware extends Middleware * Takes the incoming request and optionally modifies it before delegating * to the next frame to get a response. * - * @param ServerRequestInterface $request - * @param Frame $frame + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Viserio\Contracts\Middleware\Delegate $frame * - * @return ResponseInterface + * @return \Psr\Http\Message\ResponseInterface */ public function process( ServerRequestInterface $request, - Frame $frame + Delegate $frame ): ResponseInterface; } diff --git a/src/Viserio/Contracts/Routing/Route.php b/src/Viserio/Contracts/Routing/Route.php index 3dd95f873..ab8dc7b9c 100644 --- a/src/Viserio/Contracts/Routing/Route.php +++ b/src/Viserio/Contracts/Routing/Route.php @@ -2,6 +2,8 @@ declare(strict_types=1); namespace Viserio\Contracts\Routing; +use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; + interface Route { /** @@ -51,6 +53,34 @@ public function getMethods(): array; */ public function where($name, string $expression = null): Route; + /** + * Add a middleware to route. + * + * @return $this + */ + public function withMiddleware(MiddlewareContract $middleware): Route; + + /** + * Get all added withmiddlewares. + * + * @return array + */ + public function getWithMiddlewares(): array; + + /** + * Remove a middleware from route. + * + * @return $this + */ + public function withoutMiddleware(MiddlewareContract $middleware): Route; + + /** + * Get all middlewares that need to be removed. + * + * @return array + */ + public function getWithoutMiddlewares(): array; + /** * Determine if the route only responds to HTTP requests. * @@ -176,13 +206,4 @@ public function getSegments(): array; * @return mixed */ public function run(); - - /** - * Set the router instance on the route. - * - * @param \Viserio\Contracts\Routing\RouteCollection $router - * - * @return $this - */ - public function setRouter(RouteCollection $router): Route; } diff --git a/src/Viserio/Contracts/Routing/RouteCollection.php b/src/Viserio/Contracts/Routing/RouteCollection.php index 496dff74e..af8b627e6 100644 --- a/src/Viserio/Contracts/Routing/RouteCollection.php +++ b/src/Viserio/Contracts/Routing/RouteCollection.php @@ -4,6 +4,7 @@ use Closure; use Viserio\Contracts\Support\Arrayable as ArrayableContract; +use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; interface RouteCollection extends ArrayableContract { @@ -109,4 +110,18 @@ public function hasGroupStack(): bool; * @return array */ public function getGroupStack(): array; + + /** + * Add a middleware to all routes. + * + * @return $this + */ + public function withMiddleware(MiddlewareContract $middleware): RouteCollection; + + /** + * Remove a middleware from all routes. + * + * @return $this + */ + public function withoutMiddleware(MiddlewareContract $middleware): RouteCollection; } diff --git a/src/Viserio/Cookie/Middleware/AddQueuedCookiesToResponse.php b/src/Viserio/Cookie/Middleware/AddQueuedCookiesToResponse.php index 4595a5e71..bd805fe6e 100644 --- a/src/Viserio/Cookie/Middleware/AddQueuedCookiesToResponse.php +++ b/src/Viserio/Cookie/Middleware/AddQueuedCookiesToResponse.php @@ -5,7 +5,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Cookie\QueueingFactory as CookieJar; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; class AddQueuedCookiesToResponse implements MiddlewareContract @@ -32,7 +32,7 @@ public function __construct(CookieJar $cookies) */ public function handle( ServerRequestInterface $request, - FrameContract $frame + DelegateContract $frame ): ResponseInterface { $response = $frame->next($request); diff --git a/src/Viserio/Cookie/Middleware/EncryptedCookie.php b/src/Viserio/Cookie/Middleware/EncryptedCookie.php index 971af64ef..16a5e661f 100644 --- a/src/Viserio/Cookie/Middleware/EncryptedCookie.php +++ b/src/Viserio/Cookie/Middleware/EncryptedCookie.php @@ -8,7 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Cookie\Cookie as CookieContract; use Viserio\Contracts\Encryption\Encrypter as EncrypterContract; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Cookie\Cookie; @@ -43,7 +43,7 @@ public function __construct(EncrypterContract $encrypter) */ public function handle( ServerRequestInterface $request, - FrameContract $frame + DelegateContract $frame ): ResponseInterface { return $this->encrypt($frame->next($this->decrypt($request))); } diff --git a/src/Viserio/Middleware/Dispatcher.php b/src/Viserio/Middleware/Dispatcher.php index 4e1661053..ff42f3d63 100644 --- a/src/Viserio/Middleware/Dispatcher.php +++ b/src/Viserio/Middleware/Dispatcher.php @@ -7,7 +7,7 @@ use SplDoublyLinkedList; use SplStack; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Contracts\Middleware\Stack as StackContract; @@ -73,7 +73,7 @@ public function withoutMiddleware(MiddlewareContract $middleware): StackContract */ public function process(RequestInterface $request): ResponseInterface { - return (new class($this->stack, $this->response) implements FrameContract { + return (new class($this->stack, $this->response) implements DelegateContract { private $middlewares; private $response; diff --git a/src/Viserio/Middleware/Tests/Fixture/FakeContainerMiddleware.php b/src/Viserio/Middleware/Tests/Fixture/FakeContainerMiddleware.php index 08d17210e..eb7b96f55 100644 --- a/src/Viserio/Middleware/Tests/Fixture/FakeContainerMiddleware.php +++ b/src/Viserio/Middleware/Tests/Fixture/FakeContainerMiddleware.php @@ -5,7 +5,7 @@ use Interop\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; class FakeContainerMiddleware implements ServerMiddlewareContract @@ -43,7 +43,7 @@ public function getContainer(): ContainerInterface public function process( ServerRequestInterface $request, - FrameContract $frame + DelegateContract $frame ): ResponseInterface { $response = $frame->next($request); $response = $response->withAddedHeader('X-Foo', $this->getcontainer()->get('doo')); diff --git a/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware.php b/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware.php index 532a5a9a0..ee0989400 100644 --- a/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware.php +++ b/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware.php @@ -4,14 +4,14 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; class FakeMiddleware implements ServerMiddlewareContract { public function process( ServerRequestInterface $request, - FrameContract $frame + DelegateContract $frame ): ResponseInterface { $response = $frame->next($request); diff --git a/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware2.php b/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware2.php index 9b86e4d21..67925711b 100644 --- a/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware2.php +++ b/src/Viserio/Middleware/Tests/Fixture/FakeMiddleware2.php @@ -4,14 +4,14 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; class FakeMiddleware2 implements ServerMiddlewareContract { public function process( ServerRequestInterface $request, - FrameContract $frame + DelegateContract $frame ): ResponseInterface { $response = $frame->next($request); diff --git a/src/Viserio/Routing/Dispatcher.php b/src/Viserio/Routing/Dispatcher.php index 20e233b62..3d1b6f11f 100644 --- a/src/Viserio/Routing/Dispatcher.php +++ b/src/Viserio/Routing/Dispatcher.php @@ -2,35 +2,31 @@ declare(strict_types=1); namespace Viserio\Routing; -use Interop\Container\ContainerInterface; +use Closure; use Psr\Http\Message\ServerRequestInterface; -use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Events\Traits\EventsAwareTrait; use Viserio\Contracts\Routing\Dispatcher as DispatcherContract; class Dispatcher implements DispatcherContract { - use ContainerAwareTrait; use EventsAwareTrait; /** * The router instance. * - * @var object + * @var \Closure */ protected $router; /** * Create a new Router instance. * - * @param object $path - * @param \Interop\Container\ContainerInterface $container - * @param array $options + * @param \Closure $path + * @param \Viserio\Contracts\Middleware\Stack $middlewareDispatcher */ - public function __construct($router, ContainerInterface $container) + public function __construct(Closure $router) { $this->router = $router; - $this->container = $container; } /** @@ -48,19 +44,15 @@ public function handle(ServerRequestInterface $request) $request->getMethod(), $request->getUri()->getPath() ); - var_dump($match); switch ($match[0]) { case DispatcherContract::NOT_FOUND: return $this->handleNotFound(); break; case DispatcherContract::HTTP_METHOD_NOT_ALLOWED: - $allowed = (array) $match[1]; - - return $this->handleNotAllowed($allowed); + return $this->handleNotAllowed($match[1]); break; case DispatcherContract::FOUND: - return $this->handleFound($match[1], (array) $match[2]); break; } @@ -71,8 +63,6 @@ public function handle(ServerRequestInterface $request) * * @param callable $route * @param array $vars - * - * @return \League\Route\Middleware\ExecutionChain */ protected function handleFound(callable $route, array $vars) { @@ -80,8 +70,6 @@ protected function handleFound(callable $route, array $vars) /** * Handle a not found route. - * - * @return \League\Route\Middleware\ExecutionChain */ protected function handleNotFound() { @@ -91,8 +79,6 @@ protected function handleNotFound() * Handles a not allowed route. * * @param array $allowed - * - * @return \League\Route\Middleware\ExecutionChain */ protected function handleNotAllowed(array $allowed) { diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index 4e0fe553b..ee392e9ca 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -6,8 +6,8 @@ use Narrowspark\Arr\StaticArr as Arr; use UnexpectedValueException; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; +use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Contracts\Routing\Route as RouteContract; -use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; use Viserio\Routing\Segments\ParameterSegment; use Viserio\Support\Invoker; @@ -58,11 +58,18 @@ class Route implements RouteContract protected $wheres = []; /** - * The router instance used by the route. + * All of the middlewares. * - * @var \Viserio\Contracts\Routing\Router + * @var array + */ + protected $withMiddlewares = []; + + /** + * All to remove middlewares. + * + * @var array */ - protected $router; + protected $withoutMiddlewares = []; /** * Invoker instance. @@ -160,6 +167,42 @@ public function where($name, string $expression = null): RouteContract return $this; } + /** + * {@inheritdoc} + */ + public function withMiddleware(MiddlewareContract $middleware): RouteContract + { + $this->withMiddlewares[] = $middleware; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getWithMiddlewares(): array + { + return $this->withMiddlewares; + } + + /** + * {@inheritdoc} + */ + public function withoutMiddleware(MiddlewareContract $middleware): RouteContract + { + $this->withoutMiddlewares[] = $middleware; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getWithoutMiddlewares(): array + { + return $this->withoutMiddlewares; + } + /** * {@inheritdoc} */ @@ -315,16 +358,6 @@ public function run() ); } - /** - * {@inheritdoc} - */ - public function setRouter(RouteCollectionContract $router): RouteContract - { - $this->router = $router; - - return $this; - } - /** * Set configured invoker. * diff --git a/src/Viserio/Routing/RouteCollection.php b/src/Viserio/Routing/RouteCollection.php index 5849f2665..b80b08fad 100644 --- a/src/Viserio/Routing/RouteCollection.php +++ b/src/Viserio/Routing/RouteCollection.php @@ -4,10 +4,13 @@ use Closure; use Interop\Container\ContainerInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; +use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; +use Viserio\Middleware\Dispatcher as MiddlewareDispatcher; use Viserio\Routing\Generator\RouteTreeBuilder; use Viserio\Routing\Generator\RouteTreeOptimizer; @@ -16,25 +19,32 @@ class RouteCollection implements RouteCollectionContract use ContainerAwareTrait; /** - * The route collection instance. + * An flattened array of all of the routes. * * @var array */ - protected $routes = []; + protected $allRoutes = []; /** - * An flattened array of all of the routes. + * The route group attribute stack. * * @var array */ - protected $allRoutes = []; + protected $groupStack = []; /** - * The route group attribute stack. + * All of the middlewares. * * @var array */ - protected $groupStack = []; + protected $withMiddlewares = []; + + /** + * All to remove middlewares. + * + * @var array + */ + protected $withoutMiddlewares = []; /** * The globally available parameter patterns. @@ -191,22 +201,22 @@ public function getGroupStack(): array } /** - * Defines the supplied parameter name to be globally associated with the pattern + * Defines the supplied parameter name to be globally associated with the expression. * * @param string $parameterName - * @param string $pattern + * @param string $expression * * @return $this */ - public function setParameter(string $parameterName, string $pattern) + public function setParameter(string $parameterName, string $expression) { - $this->globalParameterConditions[$parameterName] = $pattern; + $this->globalParameterConditions[$parameterName] = $expression; return $this; } /** - * Defines the supplied parameter name to be globally associated with the pattern + * Defines the supplied parameter name to be globally associated with the expression. * * @param string[] $parameterPatternMap * @@ -220,7 +230,7 @@ public function addParameters(array $parameterPatternMap) } /** - * Removes the global pattern associated with the supplied parameter name + * Removes the global expression associated with the supplied parameter name. * * @param string $name */ @@ -239,20 +249,49 @@ public function getParameters(): array return $this->globalParameterConditions; } + /** + * {@inheritdoc} + */ + public function withMiddleware(MiddlewareContract $middleware): RouteCollectionContract + { + $this->withMiddlewares[] = $middleware; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function withoutMiddleware(MiddlewareContract $middleware): RouteCollectionContract + { + $this->withoutMiddlewares[] = $middleware; + + return $this; + } + /** * Dispatch router for HTTP request. * - * @param \Psr\Http\Message\ServerRequestInterface $request The current HTTP request object + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response * - * @return array + * @return \Psr\Http\Message\ResponseInterface */ - public function dispatch(ServerRequestInterface $request) + public function dispatch(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { - $router = $this->generateRouterFile(); + $middlewareDispatcher = new MiddlewareDispatcher($response); + $dispatcher = new Dispatcher($this->generateRouterFile()); + $route = $dispatcher->handle($request); - $dispatcher = new Dispatcher($router, $this->getContainer()); + foreach (array_merge($this->withMiddlewares, $route->getWithMiddlewares()) as $withMiddleware) { + $middlewareDispatcher->withMiddleware($withMiddleware); + } + + foreach (array_merge($this->withoutMiddlewares, $route->getWithoutMiddlewares()) as $withoutMiddleware) { + $middlewareDispatcher->withoutMiddleware($withoutMiddleware); + } - return $dispatcher->handle($request); + return $middlewareDispatcher->process($route->run()); } /** @@ -278,11 +317,7 @@ protected function addRoute($methods, string $uri, $action): RouteContract $domainAndUri = $route->getDomain() . $route->getUri(); - foreach ($route->getMethods() as $method) { - $this->routes[$method][$domainAndUri] = $route; - } - - return $this->allRoutes[$method . $domainAndUri] = $route; + return $this->allRoutes[implode('', $methods) . $domainAndUri] = $route; } /** @@ -303,9 +338,8 @@ protected function createRoute($methods, string $uri, $action): RouteContract $action = $this->convertToControllerAction($action); } - $route = (new Route($methods, $this->prefix($uri), $action)) - ->setRouter($this) - ->setContainer($this->container); + $route = new Route($methods, $this->prefix($uri), $action); + $route->setContainer($this->container); // If we have groups that need to be merged, we will merge them now after this // route has already been created and is ready to go. After we're done with @@ -316,6 +350,27 @@ protected function createRoute($methods, string $uri, $action): RouteContract $route->setAction($action); } + $this->addWhereClausesToRoute($route); + + return $route; + } + + /** + * Add the necessary where clauses to the route based on its initial registration. + * + * @param \Viserio\Contracts\Routing\Route $route + * + * @return \Viserio\Contracts\Routing\Route + */ + protected function addWhereClausesToRoute(RouteContract $route): RouteContract + { + $where = $route->getAction()['where'] ?? []; + $patern = array_merge($this->globalParameterConditions, $where); + + foreach ($patern as $name => $value) { + $route->where($name, $value); + } + return $route; } diff --git a/src/Viserio/Routing/Tests/Cache/.gitignore b/src/Viserio/Routing/Tests/Cache/.gitignore index 021099315..72e8ffc0d 100644 --- a/src/Viserio/Routing/Tests/Cache/.gitignore +++ b/src/Viserio/Routing/Tests/Cache/.gitignore @@ -1 +1 @@ -router.cache +* diff --git a/src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php b/src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php new file mode 100644 index 000000000..4f0564318 --- /dev/null +++ b/src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php @@ -0,0 +1,34 @@ + '1']) + * ] + * + * @return array[] + */ + public function routerMatchingProvider() + { + return [ + ['GET', '', ['name' => 'root'], []], + ['GET', '/', ['name' => 'root-slash'], []], + ['GET', '/a', []], + ]; + } + + protected function definitions($router) + { + $router->get('')->setParameter('name', 'root'); + $router->get('/')->setParameter('name', 'root-slash'); + } +} diff --git a/src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php b/src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php new file mode 100644 index 000000000..77619fddb --- /dev/null +++ b/src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php @@ -0,0 +1,45 @@ +getShortName(); + $router = new RouteCollection(__DIR__ . '/../Cache/' . $name . '.cache', $this->mock(ContainerInterface::class)); + $router->isDevelopMode(true); + + $this->definitions($router); + + $this->router = $router; + } + + /** + * @dataProvider routerMatchingProvider + */ + public function testRouter($httpMethod, $uri, $expectedResult) + { + // $actualResult = $this->router->dispatch( + // (new ServerRequestFactory())->createServerRequest($httpMethod, $uri), + // (new ResponseFactory())->createResponse() + // ); + + // $this->assertEquals($expectedResult, $actualResult); + } + + abstract protected function definitions($routes); +} diff --git a/src/Viserio/Routing/Tests/RouteCollectionBaseTest.php b/src/Viserio/Routing/Tests/RouteCollectionBaseTest.php deleted file mode 100644 index eb658e54a..000000000 --- a/src/Viserio/Routing/Tests/RouteCollectionBaseTest.php +++ /dev/null @@ -1,10 +0,0 @@ -generateNewToken($request); diff --git a/src/Viserio/View/Middleware/ShareErrorsFromSession.php b/src/Viserio/View/Middleware/ShareErrorsFromSession.php index 433763db8..c9150d028 100644 --- a/src/Viserio/View/Middleware/ShareErrorsFromSession.php +++ b/src/Viserio/View/Middleware/ShareErrorsFromSession.php @@ -4,7 +4,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Viserio\Contracts\Middleware\Frame as FrameContract; +use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Contracts\View\Factory as ViewFactory; @@ -29,7 +29,7 @@ public function __construct(ViewFactory $view) public function handle( ServerRequestInterface $request, - FrameContract $frame + DelegateContract $frame ): ResponseInterface { // If the current session has an "errors" variable bound to it, we will share // its value with all view instances so the views can easily access errors From e9a3dfafff0ac4ce96c4d8bb1d0b375a8618e98d Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 21 Aug 2016 22:31:17 +0000 Subject: [PATCH 12/20] Applied fixes from StyleCI --- src/Viserio/Contracts/Routing/RouteCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Viserio/Contracts/Routing/RouteCollection.php b/src/Viserio/Contracts/Routing/RouteCollection.php index af8b627e6..45d0e5099 100644 --- a/src/Viserio/Contracts/Routing/RouteCollection.php +++ b/src/Viserio/Contracts/Routing/RouteCollection.php @@ -3,8 +3,8 @@ namespace Viserio\Contracts\Routing; use Closure; -use Viserio\Contracts\Support\Arrayable as ArrayableContract; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; +use Viserio\Contracts\Support\Arrayable as ArrayableContract; interface RouteCollection extends ArrayableContract { From ea5952d616a6ed94065f29d17a153f8c6ece61b0 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Wed, 24 Aug 2016 00:20:57 +0200 Subject: [PATCH 13/20] Changed the way a route is cached | Added a RouteCollection | added new tests | added middlewares --- src/Viserio/Contracts/Routing/Route.php | 7 - .../Contracts/Routing/RouteCollection.php | 114 +--- src/Viserio/Contracts/Routing/Router.php | 126 +++++ src/Viserio/Routing/Dispatcher.php | 106 +++- .../Routing/Generator/MatchedRouteDataMap.php | 4 +- .../Middlewares/NotAllowedMiddleware.php | 20 + .../Middlewares/NotFoundMiddleware.php | 20 + src/Viserio/Routing/Route.php | 33 +- src/Viserio/Routing/RouteCollection.php | 499 ++-------------- src/Viserio/Routing/Router.php | 531 ++++++++++++++++++ .../Tests/Generator/RouteTreeBuilderTest.php | 54 +- .../Tests/Generator/RouteTreeNodeTest.php | 18 +- .../Routing/Tests/RouteCollectionTest.php | 66 +++ .../RootRoutesRouterTest.php | 6 +- .../RouteRouterBaseTest.php} | 8 +- src/Viserio/Routing/TreeRouteCompiler.php | 44 +- 16 files changed, 1023 insertions(+), 633 deletions(-) create mode 100644 src/Viserio/Contracts/Routing/Router.php create mode 100644 src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php create mode 100644 src/Viserio/Routing/Middlewares/NotFoundMiddleware.php create mode 100644 src/Viserio/Routing/Router.php create mode 100644 src/Viserio/Routing/Tests/RouteCollectionTest.php rename src/Viserio/Routing/Tests/{RouteCollection => Router}/RootRoutesRouterTest.php (80%) rename src/Viserio/Routing/Tests/{RouteCollection/RouteCollectionBaseTest.php => Router/RouteRouterBaseTest.php} (77%) diff --git a/src/Viserio/Contracts/Routing/Route.php b/src/Viserio/Contracts/Routing/Route.php index ab8dc7b9c..fa0cf5fcc 100644 --- a/src/Viserio/Contracts/Routing/Route.php +++ b/src/Viserio/Contracts/Routing/Route.php @@ -186,13 +186,6 @@ public function hasParameters(): bool; */ public function forgetParameter(string $name); - /** - * Check if route is a static route. - * - * @return bool - */ - public function isStatic(): bool; - /** * The regular expression requirements. * diff --git a/src/Viserio/Contracts/Routing/RouteCollection.php b/src/Viserio/Contracts/Routing/RouteCollection.php index 45d0e5099..165097fc7 100644 --- a/src/Viserio/Contracts/Routing/RouteCollection.php +++ b/src/Viserio/Contracts/Routing/RouteCollection.php @@ -2,126 +2,66 @@ declare(strict_types=1); namespace Viserio\Contracts\Routing; -use Closure; -use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; -use Viserio\Contracts\Support\Arrayable as ArrayableContract; - -interface RouteCollection extends ArrayableContract +interface RouteCollection { /** - * Register a new GET route with the router. - * - * @param string $uri - * @param \Closure|array|string|null $action - * - * @return \Viserio\Contracts\Routing\Route - */ - public function get(string $uri, $action = null): Route; - - /** - * Register a new POST route with the router. - * - * @param string $uri - * @param \Closure|array|string|null $action - * - * @return \Viserio\Contracts\Routing\Route - */ - public function post(string $uri, $action = null): Route; - - /** - * Register a new PUT route with the router. - * - * @param string $uri - * @param \Closure|array|string|null $action - * - * @return \Viserio\Contracts\Routing\Route - */ - public function put(string $uri, $action = null): Route; - - /** - * Register a new PATCH route with the router. + * Add a Route instance to the collection. * - * @param string $uri - * @param \Closure|array|string|null $action + * @param \Viserio\Contracts\Routing\Route $route * - * @return \Viserio\Contracts\Routing\Route + * @return Viserio\Contracts\Routing\Route */ - public function patch(string $uri, $action = null): Route; + public function add(Route $route): Route; /** - * Register a new DELETE route with the router. - * - * @param string $uri - * @param \Closure|array|string|null $action + * Find the first route matching a given identifier. * - * @return \Viserio\Contracts\Routing\Route - */ - public function delete(string $uri, $action = null): Route; - - /** - * Register a new OPTIONS route with the router. + * @param string $identifier * - * @param string $uri - * @param \Closure|array|string|null $action + * @throws \RuntimeException * - * @return \Viserio\Contracts\Routing\Route + * @return Viserio\Contracts\Routing\Route */ - public function options(string $uri, $action = null): Route; + public function match(string $identifier): Route; /** - * Register a new route responding to all verbs. + * Determine if the route collection contains a given named route. * - * @param string $uri - * @param \Closure|array|string|null $action + * @param string $name * - * @return \Viserio\Contracts\Routing\Route + * @return bool */ - public function any(string $uri, $action = null): Route; + public function hasNamedRoute(string $name): bool; /** - * Register a new route with the given verbs. + * Get a route instance by its name. * - * @param array|string $methods - * @param string $uri - * @param \Closure|array|string|null $action + * @param string $name * - * @return \Viserio\Contracts\Routing\Route + * @return Viserio\Contracts\Routing\Route|null */ - public function match($methods, $uri, $action = null): Route; + public function getByName(string $name); /** - * Create a route group with shared attributes. + * Get a route instance by its controller action. * - * @param array $attributes - * @param \Closure $callback - */ - public function group(array $attributes, Closure $callback); - - /** - * Determine if the router currently has a group stack. + * @param string $action * - * @return bool + * @return Viserio\Contracts\Routing\Route|null */ - public function hasGroupStack(): bool; + public function getByAction(string $action); /** - * Get the current group stack for the router. + * Get all of the routes in the collection. * * @return array */ - public function getGroupStack(): array; + public function getRoutes(): array; /** - * Add a middleware to all routes. + * Get all of the routes keyed by their HTTP verb / method. * - * @return $this - */ - public function withMiddleware(MiddlewareContract $middleware): RouteCollection; - - /** - * Remove a middleware from all routes. - * - * @return $this + * @return array */ - public function withoutMiddleware(MiddlewareContract $middleware): RouteCollection; + public function getRoutesByMethod(): array; } diff --git a/src/Viserio/Contracts/Routing/Router.php b/src/Viserio/Contracts/Routing/Router.php new file mode 100644 index 000000000..8670fb7af --- /dev/null +++ b/src/Viserio/Contracts/Routing/Router.php @@ -0,0 +1,126 @@ +router = $router; + public function __construct( + string $path, + RouteCollectionContract $routes, + MiddlewareDispatcher $middlewareDispatcher, + bool $isDevelopMode + ) { + $this->path = $path; + $this->routes = $routes; + $this->middlewareDispatcher = $middlewareDispatcher; + $this->isDevelopMode = $isDevelopMode; } /** @@ -35,14 +70,14 @@ public function __construct(Closure $router) * * @param \Psr\Http\Message\ServerRequestInterface $request * - * @return mixed + * @return \Viserio\Middleware\Dispatcher */ - public function handle(ServerRequestInterface $request) + public function handle(ServerRequestInterface $request): MiddlewareDispatcher { - $router = $this->router; + $router = $this->generateRouterFile(); $match = $router( $request->getMethod(), - $request->getUri()->getPath() + '/' . ltrim($request->getUri()->getPath(), '/') ); switch ($match[0]) { @@ -53,7 +88,7 @@ public function handle(ServerRequestInterface $request) return $this->handleNotAllowed($match[1]); break; case DispatcherContract::FOUND: - return $this->handleFound($match[1], (array) $match[2]); + return $this->handleFound($match[1], $match[2]); break; } } @@ -61,26 +96,61 @@ public function handle(ServerRequestInterface $request) /** * Handle dispatching of a found route. * - * @param callable $route - * @param array $vars + * @param string $identifier + * @param array $segments + * + * @return \Viserio\Middleware\Dispatcher */ - protected function handleFound(callable $route, array $vars) + protected function handleFound(string $identifier, array $segments): MiddlewareDispatcher { + $route = $this->routes->match($identifier); + + foreach ($segments as $key => $value) { + $route->setParameter($key, $value); + } + + return $this->middlewareDispatcher; } /** * Handle a not found route. + * + * @return \Viserio\Middleware\Dispatcher */ - protected function handleNotFound() + protected function handleNotFound(): MiddlewareDispatcher { + return $this->middlewareDispatcher->withMiddleware(new NotFoundMiddleware()); } /** * Handles a not allowed route. * * @param array $allowed + * + * @return \Viserio\Middleware\Dispatcher */ - protected function handleNotAllowed(array $allowed) + protected function handleNotAllowed(array $allowed): MiddlewareDispatcher { + return $this->middlewareDispatcher->withMiddleware(new NotFoundMiddleware($allowed)); + } + + /** + * Generates a router file with all routes. + * + * @return \Closure + */ + protected function generateRouterFile(): Closure + { + if ($this->isDevelopMode && file_exists($this->path)) { + @unlink($this->path); + } + + if (! file_exists($this->path)) { + $routerCompiler = new TreeRouteCompiler(new RouteTreeBuilder(), new RouteTreeOptimizer()); + + file_put_contents($this->path, $routerCompiler->compile($this->routes->getRoutes())); + } + + return require $this->path; } } diff --git a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php index 42232f30a..1e611095e 100644 --- a/src/Viserio/Routing/Generator/MatchedRouteDataMap.php +++ b/src/Viserio/Routing/Generator/MatchedRouteDataMap.php @@ -98,9 +98,9 @@ public function isEmpty(): bool public function addRoute(RouteContract $route, array $parameterIndexNameMap) { if (count($route->getMethods()) === 1 && in_array('ANY', $route->getMethods())) { - $this->defaultRouteData = [$parameterIndexNameMap, $route->getParameters()]; + $this->defaultRouteData = [$parameterIndexNameMap, $route->getIdentifier()]; } else { - $this->httpMethodRouteMap[] = [$route->getMethods(), [$parameterIndexNameMap, $route->getParameters()]]; + $this->httpMethodRouteMap[] = [$route->getMethods(), [$parameterIndexNameMap, $route->getIdentifier()]]; } } } diff --git a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php new file mode 100644 index 000000000..23ca129af --- /dev/null +++ b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php @@ -0,0 +1,20 @@ +next($request); + + return $response; + } +} diff --git a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php new file mode 100644 index 000000000..584f51d75 --- /dev/null +++ b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php @@ -0,0 +1,20 @@ +next($request); + + return $response; + } +} diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index ee392e9ca..7e163bd3d 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -78,6 +78,13 @@ class Route implements RouteContract */ protected $invoker; + /** + * Route identifier. + * + * @var string + */ + protected $identifier; + /** * Create a new Route instance. * @@ -113,6 +120,16 @@ public function __get($key) return $this->getParameter($key); } + /** + * Get route identifier + * + * @return string + */ + public function getIdentifier(): string + { + return implode($this->httpMethods, '|') . $this->getDomain() . $this->uri; + } + /** * {@inheritdoc} */ @@ -321,22 +338,6 @@ public function forgetParameter(string $name) unset($this->parameters[$name]); } - /** - * {@inheritdoc} - */ - public function isStatic(): bool - { - $this->getParameters(); - - foreach ($this->parameters as $parameter) { - if ($parameter instanceof ParameterSegment) { - return false; - } - } - - return true; - } - /** * {@inheritdoc} */ diff --git a/src/Viserio/Routing/RouteCollection.php b/src/Viserio/Routing/RouteCollection.php index b80b08fad..b9aa752db 100644 --- a/src/Viserio/Routing/RouteCollection.php +++ b/src/Viserio/Routing/RouteCollection.php @@ -2,545 +2,144 @@ declare(strict_types=1); namespace Viserio\Routing; -use Closure; -use Interop\Container\ContainerInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Viserio\Contracts\Container\Traits\ContainerAwareTrait; -use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; +use RuntimeException; use Viserio\Contracts\Routing\Route as RouteContract; use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; -use Viserio\Middleware\Dispatcher as MiddlewareDispatcher; -use Viserio\Routing\Generator\RouteTreeBuilder; -use Viserio\Routing\Generator\RouteTreeOptimizer; class RouteCollection implements RouteCollectionContract { - use ContainerAwareTrait; - /** - * An flattened array of all of the routes. + * An array of the routes keyed by method. * * @var array */ - protected $allRoutes = []; + protected $routes = []; /** - * The route group attribute stack. + * An flattened array of all of the routes. * * @var array */ - protected $groupStack = []; + protected $allRoutes = []; /** - * All of the middlewares. + * A look-up table of routes by their names. * * @var array */ - protected $withMiddlewares = []; + protected $nameList = []; /** - * All to remove middlewares. + * A look-up table of routes by controller action. * * @var array */ - protected $withoutMiddlewares = []; - - /** - * The globally available parameter patterns. - * - * @var string[] - */ - protected $globalParameterConditions = []; - - /** - * @var bool - */ - protected $isDevelopMode = true; - - /** - * Path to the cached router file. - * - * @var string - */ - protected $path; - - /** - * Create a new Router instance. - * - * @param string $path - * @param \Interop\Container\ContainerInterface $container - */ - public function __construct(string $path, ContainerInterface $container) - { - $this->path = $path; - $this->container = $container; - } - - /** - * Route collection is in develop mode. - * - * @param bool $isDev - */ - public function isDevelopMode(bool $isDev) - { - $this->isDevelopMode = $isDev; - } - - /** - * {@inheritdoc} - */ - public function get(string $uri, $action = null): RouteContract - { - return $this->addRoute(['GET', 'HEAD'], $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function post(string $uri, $action = null): RouteContract - { - return $this->addRoute('POST', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function put(string $uri, $action = null): RouteContract - { - return $this->addRoute('PUT', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function patch(string $uri, $action = null): RouteContract - { - return $this->addRoute('PATCH', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function delete(string $uri, $action = null): RouteContract - { - return $this->addRoute('DELETE', $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function options(string $uri, $action = null): RouteContract - { - return $this->addRoute('OPTIONS', $uri, $action); - } + protected $actionList = []; /** * {@inheritdoc} */ - public function any(string $uri, $action = null): RouteContract + public function add(RouteContract $route): RouteContract { - $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; + $this->addToCollections($route); + $this->addLookups($route); - return $this->addRoute($verbs, $uri, $action); - } - - /** - * {@inheritdoc} - */ - public function match($methods, $uri, $action = null): RouteContract - { - return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + return $route; } /** * {@inheritdoc} */ - public function group(array $attributes, Closure $callback) + public function match(string $identifier): RouteContract { - if (! empty($this->groupStack)) { - $attributes = $this->mergeGroup($attributes, end($this->groupStack)); + if (isset($this->allRoutes[$identifier])) { + return $this->allRoutes[$identifier]; } - $this->groupStack[] = $attributes; - - // Once we have updated the group stack, we will execute the user Closure and - // merge in the groups attributes when the route is created. After we have - // run the callback, we will pop the attributes off of this group stack. - call_user_func($callback, $this); - - array_pop($this->groupStack); - } - - /** - * Merge the given array with the last group stack. - * - * @param array $new - * - * @return array - */ - public function mergeWithLastGroup($new) - { - return $this->mergeGroup($new, end($this->groupStack)); + throw new RuntimeException('Route not found, looks like your route cache is stale.'); } /** * {@inheritdoc} */ - public function hasGroupStack(): bool + public function hasNamedRoute(string $name): bool { - return ! empty($this->groupStack); + return ! is_null($this->getByName($name)); } /** * {@inheritdoc} */ - public function getGroupStack(): array + public function getByName(string $name) { - return $this->groupStack; - } - - /** - * Defines the supplied parameter name to be globally associated with the expression. - * - * @param string $parameterName - * @param string $expression - * - * @return $this - */ - public function setParameter(string $parameterName, string $expression) - { - $this->globalParameterConditions[$parameterName] = $expression; - - return $this; - } - - /** - * Defines the supplied parameter name to be globally associated with the expression. - * - * @param string[] $parameterPatternMap - * - * @return $this - */ - public function addParameters(array $parameterPatternMap) - { - $this->globalParameterConditions += $parameterPatternMap; - - return $this; - } - - /** - * Removes the global expression associated with the supplied parameter name. - * - * @param string $name - */ - public function removeParameter(string $name) - { - unset($this->globalParameterConditions[$name]); - } - - /** - * Get all global parameters for all routes. - * - * @return array - */ - public function getParameters(): array - { - return $this->globalParameterConditions; + return isset($this->nameList[$name]) ? $this->nameList[$name] : null; } /** * {@inheritdoc} */ - public function withMiddleware(MiddlewareContract $middleware): RouteCollectionContract + public function getByAction(string $action) { - $this->withMiddlewares[] = $middleware; - - return $this; + return isset($this->actionList[$action]) ? $this->actionList[$action] : null; } /** * {@inheritdoc} - */ - public function withoutMiddleware(MiddlewareContract $middleware): RouteCollectionContract - { - $this->withoutMiddlewares[] = $middleware; - - return $this; - } - - /** - * Dispatch router for HTTP request. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response * - * @return \Psr\Http\Message\ResponseInterface + * @codeCoverageIgnore */ - public function dispatch(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + public function getRoutes(): array { - $middlewareDispatcher = new MiddlewareDispatcher($response); - $dispatcher = new Dispatcher($this->generateRouterFile()); - $route = $dispatcher->handle($request); - - foreach (array_merge($this->withMiddlewares, $route->getWithMiddlewares()) as $withMiddleware) { - $middlewareDispatcher->withMiddleware($withMiddleware); - } - - foreach (array_merge($this->withoutMiddlewares, $route->getWithoutMiddlewares()) as $withoutMiddleware) { - $middlewareDispatcher->withoutMiddleware($withoutMiddleware); - } - - return $middlewareDispatcher->process($route->run()); + return array_values($this->allRoutes); } /** * {@inheritdoc} - */ - public function toArray(): array - { - return $this->allRoutes; - } - - /** - * Add a route to the underlying route collection. * - * @param array|string $methods - * @param string $uri - * @param \Closure|array|string|null $action - * - * @return \Viserio\Contracts\Routing\Route + * @codeCoverageIgnore */ - protected function addRoute($methods, string $uri, $action): RouteContract + public function getRoutesByMethod(): array { - $route = $this->createRoute($methods, $uri, $action); - - $domainAndUri = $route->getDomain() . $route->getUri(); - - return $this->allRoutes[implode('', $methods) . $domainAndUri] = $route; + return $this->routes; } /** - * Create a new route instance. - * - * @param array|string $methods - * @param string $uri - * @param mixed $action - * - * @return \Viserio\Contracts\Routing\Route - */ - protected function createRoute($methods, string $uri, $action): RouteContract - { - // If the route is routing to a controller we will parse the route action into - // an acceptable array format before registering it and creating this route - // instance itself. We need to build the Closure that will call this out. - if ($this->actionReferencesController($action)) { - $action = $this->convertToControllerAction($action); - } - - $route = new Route($methods, $this->prefix($uri), $action); - $route->setContainer($this->container); - - // If we have groups that need to be merged, we will merge them now after this - // route has already been created and is ready to go. After we're done with - // the merge we will be ready to return the route back out to the caller. - if ($this->hasGroupStack()) { - $action = $this->mergeWithLastGroup($route->getAction()); - - $route->setAction($action); - } - - $this->addWhereClausesToRoute($route); - - return $route; - } - - /** - * Add the necessary where clauses to the route based on its initial registration. + * Add the given route to the arrays of routes. * * @param \Viserio\Contracts\Routing\Route $route - * - * @return \Viserio\Contracts\Routing\Route - */ - protected function addWhereClausesToRoute(RouteContract $route): RouteContract - { - $where = $route->getAction()['where'] ?? []; - $patern = array_merge($this->globalParameterConditions, $where); - - foreach ($patern as $name => $value) { - $route->where($name, $value); - } - - return $route; - } - - /** - * Determine if the action is routing to a controller. - * - * @param string|array|\Closure $action - * - * @return bool */ - protected function actionReferencesController($action): bool + protected function addToCollections(RouteContract $route) { - if ($action instanceof Closure) { - return false; - } - - return is_string($action) || (isset($action['uses']) && is_string($action['uses'])); - } - - /** - * Add a controller based route action to the action array. - * - * @param array|string $action - * - * @return array - */ - protected function convertToControllerAction($action): array - { - if (is_string($action)) { - $action = ['uses' => $action]; - } - - // Here we'll merge any group "uses" statement if necessary so that the action - // has the proper clause for this property. Then we can simply set the name - // of the controller on the action and return the action array for usage. - if (! empty($this->groupStack)) { - $action['uses'] = $this->prependGroupUses($action['uses']); - } - - // Here we will set this controller name on the action array just so we always - // have a copy of it for reference if we need it. This can be used while we - // search for a controller name or do some other type of fetch operation. - $action['controller'] = $action['uses']; - - return $action; - } - - /** - * Merge the given group attributes. - * - * @param array $new - * @param array $old - * - * @return array - */ - protected function mergeGroup(array $new, array $old): array - { - $new['namespace'] = $this->formatUsesPrefix($new, $old); - $new['prefix'] = $this->formatGroupPrefix($new, $old); - - if (isset($new['domain'])) { - unset($old['domain']); - } - - $new['where'] = array_merge($old['where'] ?? [], $new['where'] ?? []); - - if (isset($old['as'])) { - $new['as'] = $old['as'] . ($new['as'] ?? ''); - } - - return array_merge_recursive(Arr::except($old, ['namespace', 'prefix', 'where', 'as']), $new); - } - - /** - * Format the uses prefix for the new group attributes. - * - * @param array $new - * @param array $old - * - * @return string|null - */ - protected function formatUsesPrefix(array $new, array $old) - { - if (isset($new['namespace'])) { - return isset($old['namespace']) - ? trim($old['namespace'], '\\') . '\\' . trim($new['namespace'], '\\') - : trim($new['namespace'], '\\'); - } - - return $old['namespace'] ?? null; - } - - /** - * Format the prefix for the new group attributes. - * - * @param array $new - * @param array $old - * - * @return string|null - */ - protected function formatGroupPrefix(array $new, array $old) - { - $oldPrefix = $old['prefix'] ?? null; - - if (isset($new['prefix'])) { - return trim($oldPrefix, '/') . '/' . trim($new['prefix'], '/'); - } - - return $oldPrefix; - } - - /** - * Prefix the given URI with the last prefix. - * - * @param string $uri - * - * @return string - */ - protected function prefix($uri) - { - return '/' . trim(trim($this->getLastGroupPrefix(), '/') . '/' . trim($uri, '/'), '/'); - } - - /** - * Get the prefix from the last group on the stack. - * - * @return string - */ - protected function getLastGroupPrefix(): string - { - if (! empty($this->groupStack)) { - $last = end($this->groupStack); + $domainAndUri = $route->getDomain() . $route->getUri(); - return isset($last['prefix']) ? $last['prefix'] : ''; + foreach ($route->getMethods() as $method) { + $this->routes[$method][$domainAndUri] = $route; } - return ''; + $this->allRoutes[implode($route->getMethods(), '|') . $domainAndUri] = $route; } /** - * Prepend the last group uses onto the use clause. + * Add the route to any look-up tables if necessary. * - * @param string $uses - * - * @return string + * @param \Viserio\Contracts\Routing\Route $route */ - protected function prependGroupUses(string $uses): string + protected function addLookups(RouteContract $route) { - $group = end($this->groupStack); - - return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'] . '\\' . $uses : $uses; - } + // If the route has a name, we will add it to the name look-up table so that we + // will quickly be able to find any route associate with a name and not have + // to iterate through every route every time we need to perform a look-up. + $action = $route->getAction(); - /** - * Generates a router file with all routes. - * - * @return object - */ - protected function generateRouterFile() - { - if ($this->isDevelopMode && file_exists($this->path)) { - @unlink($this->path); + if (isset($action['as'])) { + $this->nameList[$action['as']] = $route; } - if (! file_exists($this->path)) { - $routerCompiler = new TreeRouteCompiler(new RouteTreeBuilder(), new RouteTreeOptimizer()); - - file_put_contents($this->path, $routerCompiler->compile($this)); + // When the route is routing to a controller we will also store the action that + // is used by the route. This will let us reverse route to controllers while + // processing a request and easily generate URLs to the given controllers. + if (isset($action['controller'])) { + $this->actionList[trim($action['controller'], '\\')] = $route; } - - return require $this->path; } } diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php new file mode 100644 index 000000000..bff2a9294 --- /dev/null +++ b/src/Viserio/Routing/Router.php @@ -0,0 +1,531 @@ +path = $path; + $this->container = $container; + $this->routes = new RouteCollection(); + } + + /** + * Route collection is in develop mode. + * + * @param bool $isDev + */ + public function isDevelopMode(bool $isDev) + { + $this->isDevelopMode = $isDev; + } + + /** + * {@inheritdoc} + */ + public function get(string $uri, $action = null): RouteContract + { + return $this->addRoute(['GET', 'HEAD'], $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function post(string $uri, $action = null): RouteContract + { + return $this->addRoute('POST', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function put(string $uri, $action = null): RouteContract + { + return $this->addRoute('PUT', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function patch(string $uri, $action = null): RouteContract + { + return $this->addRoute('PATCH', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function delete(string $uri, $action = null): RouteContract + { + return $this->addRoute('DELETE', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function options(string $uri, $action = null): RouteContract + { + return $this->addRoute('OPTIONS', $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function any(string $uri, $action = null): RouteContract + { + $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; + + return $this->addRoute($verbs, $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function match($methods, $uri, $action = null): RouteContract + { + return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + } + + /** + * {@inheritdoc} + */ + public function group(array $attributes, Closure $callback) + { + if (! empty($this->groupStack)) { + $attributes = $this->mergeGroup($attributes, end($this->groupStack)); + } + + $this->groupStack[] = $attributes; + + // Once we have updated the group stack, we will execute the user Closure and + // merge in the groups attributes when the route is created. After we have + // run the callback, we will pop the attributes off of this group stack. + call_user_func($callback, $this); + + array_pop($this->groupStack); + } + + /** + * Merge the given array with the last group stack. + * + * @param array $new + * + * @return array + */ + public function mergeWithLastGroup($new) + { + return $this->mergeGroup($new, end($this->groupStack)); + } + + /** + * {@inheritdoc} + */ + public function hasGroupStack(): bool + { + return ! empty($this->groupStack); + } + + /** + * {@inheritdoc} + */ + public function getGroupStack(): array + { + return $this->groupStack; + } + + /** + * Defines the supplied parameter name to be globally associated with the expression. + * + * @param string $parameterName + * @param string $expression + * + * @return $this + */ + public function setParameter(string $parameterName, string $expression) + { + $this->globalParameterConditions[$parameterName] = $expression; + + return $this; + } + + /** + * Defines the supplied parameter name to be globally associated with the expression. + * + * @param string[] $parameterPatternMap + * + * @return $this + */ + public function addParameters(array $parameterPatternMap) + { + $this->globalParameterConditions += $parameterPatternMap; + + return $this; + } + + /** + * Removes the global expression associated with the supplied parameter name. + * + * @param string $name + */ + public function removeParameter(string $name) + { + unset($this->globalParameterConditions[$name]); + } + + /** + * Get all global parameters for all routes. + * + * @return array + */ + public function getParameters(): array + { + return $this->globalParameterConditions; + } + + /** + * {@inheritdoc} + */ + public function withMiddleware(MiddlewareContract $middleware): RouterContract + { + $this->withMiddlewares[] = $middleware; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function withoutMiddleware(MiddlewareContract $middleware): RouterContract + { + $this->withoutMiddlewares[] = $middleware; + + return $this; + } + + /** + * Dispatch router for HTTP request. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function dispatch(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + $middlewareDispatcher = new MiddlewareDispatcher($response); + $dispatcher = new Dispatcher( + $this->path, + $this->routes, + $middlewareDispatcher, + $this->isDevelopMode + ); + $route = $dispatcher->handle($request); + + foreach ($this->withMiddlewares as $withMiddleware) { + $middlewareDispatcher->withMiddleware($withMiddleware); + } + + foreach ($this->withoutMiddlewares as $withoutMiddleware) { + $middlewareDispatcher->withoutMiddleware($withoutMiddleware); + } + + return $middlewareDispatcher->process($route->run()); + } + + /** + * Get the underlying route collection. + * + * @return \Viserio\Routing\RouteCollection + */ + public function getRoutes() + { + return $this->routes; + } + + /** + * Add a route to the underlying route collection. + * + * @param array|string $methods + * @param string $uri + * @param \Closure|array|string|null $action + * + * @return \Viserio\Contracts\Routing\Route + */ + protected function addRoute($methods, string $uri, $action): RouteContract + { + return $this->routes->add($this->createRoute($methods, $uri, $action)); + } + + /** + * Create a new route instance. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * + * @return \Viserio\Contracts\Routing\Route + */ + protected function createRoute($methods, string $uri, $action): RouteContract + { + // If the route is routing to a controller we will parse the route action into + // an acceptable array format before registering it and creating this route + // instance itself. We need to build the Closure that will call this out. + if ($this->actionReferencesController($action)) { + $action = $this->convertToControllerAction($action); + } + + $route = new Route($methods, $this->prefix($uri), $action); + $route->setContainer($this->container); + + // If we have groups that need to be merged, we will merge them now after this + // route has already been created and is ready to go. After we're done with + // the merge we will be ready to return the route back out to the caller. + if ($this->hasGroupStack()) { + $action = $this->mergeWithLastGroup($route->getAction()); + + $route->setAction($action); + } + + $this->addWhereClausesToRoute($route); + + return $route; + } + + /** + * Add the necessary where clauses to the route based on its initial registration. + * + * @param \Viserio\Contracts\Routing\Route $route + * + * @return \Viserio\Contracts\Routing\Route + */ + protected function addWhereClausesToRoute(RouteContract $route): RouteContract + { + $where = $route->getAction()['where'] ?? []; + $patern = array_merge($this->globalParameterConditions, $where); + + foreach ($patern as $name => $value) { + $route->where($name, $value); + } + + return $route; + } + + /** + * Determine if the action is routing to a controller. + * + * @param string|array|\Closure $action + * + * @return bool + */ + protected function actionReferencesController($action): bool + { + if ($action instanceof Closure) { + return false; + } + + return is_string($action) || (isset($action['uses']) && is_string($action['uses'])); + } + + /** + * Add a controller based route action to the action array. + * + * @param array|string $action + * + * @return array + */ + protected function convertToControllerAction($action): array + { + if (is_string($action)) { + $action = ['uses' => $action]; + } + + // Here we'll merge any group "uses" statement if necessary so that the action + // has the proper clause for this property. Then we can simply set the name + // of the controller on the action and return the action array for usage. + if (! empty($this->groupStack)) { + $action['uses'] = $this->prependGroupUses($action['uses']); + } + + // Here we will set this controller name on the action array just so we always + // have a copy of it for reference if we need it. This can be used while we + // search for a controller name or do some other type of fetch operation. + $action['controller'] = $action['uses']; + + return $action; + } + + /** + * Merge the given group attributes. + * + * @param array $new + * @param array $old + * + * @return array + */ + protected function mergeGroup(array $new, array $old): array + { + $new['namespace'] = $this->formatUsesPrefix($new, $old); + $new['prefix'] = $this->formatGroupPrefix($new, $old); + + if (isset($new['domain'])) { + unset($old['domain']); + } + + $new['where'] = array_merge($old['where'] ?? [], $new['where'] ?? []); + + if (isset($old['as'])) { + $new['as'] = $old['as'] . ($new['as'] ?? ''); + } + + return array_merge_recursive(Arr::except($old, ['namespace', 'prefix', 'where', 'as']), $new); + } + + /** + * Format the uses prefix for the new group attributes. + * + * @param array $new + * @param array $old + * + * @return string|null + */ + protected function formatUsesPrefix(array $new, array $old) + { + if (isset($new['namespace'])) { + return isset($old['namespace']) + ? trim($old['namespace'], '\\') . '\\' . trim($new['namespace'], '\\') + : trim($new['namespace'], '\\'); + } + + return $old['namespace'] ?? null; + } + + /** + * Format the prefix for the new group attributes. + * + * @param array $new + * @param array $old + * + * @return string|null + */ + protected function formatGroupPrefix(array $new, array $old) + { + $oldPrefix = $old['prefix'] ?? null; + + if (isset($new['prefix'])) { + return trim($oldPrefix, '/') . '/' . trim($new['prefix'], '/'); + } + + return $oldPrefix; + } + + /** + * Prefix the given URI with the last prefix. + * + * @param string $uri + * + * @return string + */ + protected function prefix($uri) + { + return '/' . trim(trim($this->getLastGroupPrefix(), '/') . '/' . trim($uri, '/'), '/'); + } + + /** + * Get the prefix from the last group on the stack. + * + * @return string + */ + protected function getLastGroupPrefix(): string + { + if (! empty($this->groupStack)) { + $last = end($this->groupStack); + + return isset($last['prefix']) ? $last['prefix'] : ''; + } + + return ''; + } + + /** + * Prepend the last group uses onto the use clause. + * + * @param string $uses + * + * @return string + */ + protected function prependGroupUses(string $uses): string + { + $group = end($this->groupStack); + + return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'] . '\\' . $uses : $uses; + } +} diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php index c82a8d3aa..612a368e9 100644 --- a/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php @@ -17,31 +17,31 @@ public function routeTreeBuilderCases() { return [ [ - [(new Route('ANY', '', null))->setParameter('route', '')], - new MatchedRouteDataMap([], [[], ['route' => '']]), + [(new Route('ANY', '', null))], + new MatchedRouteDataMap([], [[], 'ANY']), [], ], [ - [(new Route('ANY', '/', null))->setParameter('route', '')], + [(new Route('ANY', '/', null))], null, [ 1 => new ChildrenNodeCollection([ (new StaticMatcher(''))->getHash() => new RouteTreeNode( [0 => new StaticMatcher('')], - new MatchedRouteDataMap([], [[], ['route' => '']]) + new MatchedRouteDataMap([], [[], 'ANY/']) ), ]), ], ], [ - [(new Route(['GET'], '/{param}', null))->where('param', Pattern::ANY)->setParameter('root-route', '')], + [(new Route(['GET'], '/{param}', null))->where('param', Pattern::ANY)], null, [ 1 => new ChildrenNodeCollection([ (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( [0 => new RegexMatcher(Pattern::ANY, 0)], new MatchedRouteDataMap([ - [['GET', 'HEAD'], [[0 => 'param'], ['root-route' => '']]], + [['GET', 'HEAD'], [[0 => 'param'], 'GET|HEAD/{param}']], ]) ), ]), @@ -49,8 +49,8 @@ public function routeTreeBuilderCases() ], [ [ - (new Route(['GET'], '/first/{param1}', null))->setParameter('static-first', ''), - (new Route(['GET'], '/{param1}/{param2}', null))->where(['param1', 'param2'], Pattern::ANY)->setParameter('dynamic', ''), + (new Route(['GET'], '/first/{param1}', null)), + (new Route(['GET'], '/{param1}/{param2}', null))->where(['param1', 'param2'], Pattern::ANY), ], null, [ @@ -61,7 +61,7 @@ public function routeTreeBuilderCases() (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( [1 => new RegexMatcher(Pattern::ANY, 0)], new MatchedRouteDataMap([ - [['GET', 'HEAD'], [[0 => 'param1'], ['static-first' => '']]], + [['GET', 'HEAD'], [[0 => 'param1'], 'GET|HEAD/first/{param1}']], ]) ), ]) @@ -72,7 +72,7 @@ public function routeTreeBuilderCases() (new RegexMatcher(Pattern::ANY, 1))->getHash() => new RouteTreeNode( [1 => new RegexMatcher(Pattern::ANY, 1)], new MatchedRouteDataMap([ - [['GET', 'HEAD'], [[0 => 'param1', 1 => 'param2'], ['dynamic' => '']]], + [['GET', 'HEAD'], [[0 => 'param1', 1 => 'param2'], 'GET|HEAD/{param1}/{param2}']], ]) ), ]) @@ -82,22 +82,22 @@ public function routeTreeBuilderCases() ], [ [ - (new Route('ANY', '', null))->setParameter('home', ''), - (new Route('ANY', '/main', null))->setParameter('main.root', ''), - (new Route(['GET'], '/main/place', null))->setParameter('main.place-get', ''), - (new Route(['POST'], '/main/place', null))->setParameter('main.place-post', ''), - (new Route('ANY', '/main/thing', null))->setParameter('main.thing', ''), - (new Route('ANY', '/main/thing/abc', null))->setParameter('main.thing.abc', ''), - (new Route('ANY', '/user/{name}', null))->where('name', Pattern::ANY)->setParameter('user.show', ''), - (new Route('ANY', '/user/{name}/edit', null))->where('name', Pattern::ANY)->setParameter('user.edit', ''), + (new Route('ANY', '', null)), + (new Route('ANY', '/main', null)), + (new Route(['GET'], '/main/place', null)), + (new Route(['POST'], '/main/place', null)), + (new Route('ANY', '/main/thing', null)), + (new Route('ANY', '/main/thing/abc', null)), + (new Route('ANY', '/user/{name}', null))->where('name', Pattern::ANY), + (new Route('ANY', '/user/{name}/edit', null))->where('name', Pattern::ANY), (new Route('ANY', '/user/create', null))->setParameter('user.create', ''), ], - new MatchedRouteDataMap([], [[], ['home' => '']]), + new MatchedRouteDataMap([], [[], 'ANY']), [ 1 => new ChildrenNodeCollection([ (new StaticMatcher('main'))->getHash() => new RouteTreeNode( [0 => new StaticMatcher('main')], - new MatchedRouteDataMap([], [[], ['main.root' => '']]) + new MatchedRouteDataMap([], [[], 'ANY/main']) ), ]), 2 => new ChildrenNodeCollection([ @@ -105,23 +105,23 @@ public function routeTreeBuilderCases() (new StaticMatcher('place'))->getHash() => new RouteTreeNode( [1 => new StaticMatcher('place')], new MatchedRouteDataMap([ - [['GET', 'HEAD'], [[], ['main.place-get' => '']]], - [['POST'], [[], ['main.place-post' => '']]], + [['GET', 'HEAD'], [[], 'GET|HEAD/main/place']], + [['POST'], [[], 'POST/main/place']], ]) ), (new StaticMatcher('thing'))->getHash() => new RouteTreeNode( [1 => new StaticMatcher('thing')], - new MatchedRouteDataMap([], [[], ['main.thing' => '']]) + new MatchedRouteDataMap([], [[], 'ANY/main/thing']) ), ])), (new StaticMatcher('user'))->getHash() => new RouteTreeNode([0 => new StaticMatcher('user')], new ChildrenNodeCollection([ (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( [1 => new RegexMatcher(Pattern::ANY, 0)], - new MatchedRouteDataMap([], [[0 => 'name'], ['user.show' => '']]) + new MatchedRouteDataMap([], [[0 => 'name'], 'ANY/user/{name}']) ), (new StaticMatcher('create'))->getHash() => new RouteTreeNode( [1 => new StaticMatcher('create')], - new MatchedRouteDataMap([], [[], ['user.create' => '']]) + new MatchedRouteDataMap([], [[], 'ANY/user/create']) ), ])), ]), @@ -130,7 +130,7 @@ public function routeTreeBuilderCases() (new StaticMatcher('thing'))->getHash() => new RouteTreeNode([1 => new StaticMatcher('thing')], new ChildrenNodeCollection([ (new StaticMatcher('abc'))->getHash() => new RouteTreeNode( [2 => new StaticMatcher('abc')], - new MatchedRouteDataMap([], [[], ['main.thing.abc' => '']]) + new MatchedRouteDataMap([], [[], 'ANY/main/thing/abc']) ), ])), ])), @@ -138,7 +138,7 @@ public function routeTreeBuilderCases() (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode([1 => new RegexMatcher(Pattern::ANY, 0)], new ChildrenNodeCollection([ (new StaticMatcher('edit'))->getHash() => new RouteTreeNode( [2 => new StaticMatcher('edit')], - new MatchedRouteDataMap([], [[0 => 'name'], ['user.edit' => '']]) + new MatchedRouteDataMap([], [[0 => 'name'], 'ANY/user/{name}/edit']) ), ])), ])), diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php index 064ac6745..d71616fcc 100644 --- a/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeNodeTest.php @@ -78,42 +78,42 @@ public function testChildrenCollectionOperations() public function testMatchedRouteDataMapOperations() { $node = new RouteTreeNode([$this->mock(AbstractMatcher::class)], new MatchedRouteDataMap()); - $node->getContents()->addRoute((new Route(['GET', 'POST'], '', null))->setParameter('first_route', ''), []); + $node->getContents()->addRoute((new Route(['GET', 'POST'], '', null)), []); $this->assertSame(['GET', 'POST', 'HEAD'], $node->getContents()->getAllowedHttpMethods()); $this->assertEquals( [ - [['GET', 'POST', 'HEAD'], [[], ['first_route' => '']]], + [['GET', 'POST', 'HEAD'], [[], 'GET|POST|HEAD']], ], $node->getContents()->getHttpMethodRouteDataMap() ); $this->assertNull($node->getContents()->getDefaultRouteData()); $this->assertFalse($node->getContents()->hasDefaultRouteData()); - $node->getContents()->addRoute((new Route('PATCH', '', null))->setParameter('second_route', ''), [0 => 'param']); + $node->getContents()->addRoute((new Route('PATCH', '', null)), [0 => 'param']); $this->assertSame(['GET', 'POST', 'HEAD', 'PATCH'], $node->getContents()->getAllowedHttpMethods()); $this->assertEquals( [ - [['GET', 'POST', 'HEAD'], [[], ['first_route' => '']]], - [['PATCH'], [[0 => 'param'], ['second_route' => '']]], + [['GET', 'POST', 'HEAD'], [[], 'GET|POST|HEAD']], + [['PATCH'], [[0 => 'param'], 'PATCH']], ], $node->getContents()->getHttpMethodRouteDataMap() ); $this->assertNull($node->getContents()->getDefaultRouteData()); $this->assertFalse($node->getContents()->hasDefaultRouteData()); - $node->getContents()->addRoute((new Route(['ANY'], '', null))->setParameter('third_route', ''), []); + $node->getContents()->addRoute((new Route(['ANY'], '', null)), []); $this->assertSame('GET', $node->getContents()->getAllowedHttpMethods()); $this->assertEquals( [ - [['GET', 'POST', 'HEAD'], [[], ['first_route' => '']]], - [['PATCH'], [[0 => 'param'], ['second_route' => '']]], + [['GET', 'POST', 'HEAD'], [[], 'GET|POST|HEAD']], + [['PATCH'], [[0 => 'param'], 'PATCH']], ], $node->getContents()->getHttpMethodRouteDataMap() ); - $this->assertEquals([[], ['third_route' => '']], $node->getContents()->getDefaultRouteData()); + $this->assertEquals([[], 'ANY'], $node->getContents()->getDefaultRouteData()); $this->assertTrue($node->getContents()->hasDefaultRouteData()); } diff --git a/src/Viserio/Routing/Tests/RouteCollectionTest.php b/src/Viserio/Routing/Tests/RouteCollectionTest.php new file mode 100644 index 000000000..99e21975f --- /dev/null +++ b/src/Viserio/Routing/Tests/RouteCollectionTest.php @@ -0,0 +1,66 @@ + 'test.com']); + + $this->assertInstanceOf(Route::class, $collection->add($route)); + } + + public function testMatch() + { + $collection = new RouteCollection(); + $collection->add($route1 = new Route('GET', '/test', null)); + $collection->add($route2 = new Route('PATCH', '/test2', null)); + $collection->add($route3 = new Route(['GET', 'POST'], '/test', null)); + + $this->assertSame($route2, $collection->match('PATCH/test2')); + $this->assertSame($route1, $collection->match('GET|HEAD/test')); + $this->assertSame($route3, $collection->match('GET|POST|HEAD/test')); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Route not found, looks like your route cache is stale. + */ + public function testMatchToThrowException() + { + $collection = new RouteCollection(); + $collection->match('PATCH/test2'); + } + + public function testHasNamedRoute() + { + $collection = new RouteCollection(); + $collection->add(new Route('GET', '/test', ['as' => 'narrowspark'])); + + $this->assertTrue($collection->hasNamedRoute('narrowspark')); + $this->assertFalse($collection->hasNamedRoute('PATCH/test2')); + } + + public function testGetByName() + { + $collection = new RouteCollection(); + $collection->add($route = new Route('GET', '/test', ['as' => 'narrowspark'])); + + $this->assertSame($route, $collection->getByName('narrowspark')); + $this->assertNull($collection->getByName('PATCH/test2')); + } + + public function testGetByAction() + { + $collection = new RouteCollection(); + $collection->add($route = new Route('GET', '/test', ['controller' => 'narrowspark'])); + + $this->assertSame($route, $collection->getByAction('narrowspark')); + $this->assertNull($collection->getByAction('PATCH/test2')); + } +} diff --git a/src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php similarity index 80% rename from src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php rename to src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php index 4f0564318..305dccd2f 100644 --- a/src/Viserio/Routing/Tests/RouteCollection/RootRoutesRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php @@ -1,10 +1,10 @@ 'root'], []], ['GET', '/', ['name' => 'root-slash'], []], ['GET', '/a', []], + ['GET', 'test/123', []], ]; } @@ -30,5 +31,6 @@ protected function definitions($router) { $router->get('')->setParameter('name', 'root'); $router->get('/')->setParameter('name', 'root-slash'); + $router->get('/test/{param}'); } } diff --git a/src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php similarity index 77% rename from src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php rename to src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php index 77619fddb..cf5f174b2 100644 --- a/src/Viserio/Routing/Tests/RouteCollection/RouteCollectionBaseTest.php +++ b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php @@ -1,15 +1,15 @@ getShortName(); - $router = new RouteCollection(__DIR__ . '/../Cache/' . $name . '.cache', $this->mock(ContainerInterface::class)); + $router = new Router(__DIR__ . '/../Cache/' . $name . '.cache', $this->mock(ContainerInterface::class)); $router->isDevelopMode(true); $this->definitions($router); diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php index 540e6daee..d6081bd14 100644 --- a/src/Viserio/Routing/TreeRouteCompiler.php +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -2,7 +2,6 @@ declare(strict_types=1); namespace Viserio\Routing; -use Viserio\Contracts\Routing\RouteCollection as RouteCollectionContract; use Viserio\Routing\Generator\ChildrenNodeCollection; use Viserio\Routing\Generator\MatchedRouteDataMap; use Viserio\Routing\Generator\RouteTreeBuilder; @@ -39,14 +38,14 @@ public function __construct(RouteTreeBuilder $treeBuilder, RouteTreeOptimizer $t /** * Complie all added routes to a router handler. * - * @param \Viserio\Contracts\Routing\RouteCollection $routes + * @param array $routes * * @return string */ - public function compile(RouteCollectionContract $routes): string + public function compile(array $routes): string { $routeTree = $this->treeOptimizer->optimize( - $this->treeBuilder->build($routes->toArray()) + $this->treeBuilder->build($routes) ); $code = $this->phpBuilder(); @@ -147,8 +146,12 @@ protected function compileRouteTree($code, array $routeTree) * @param array $segmentVariables * @param array $parameters */ - protected function compileSegmentNodes($code, ChildrenNodeCollection $nodes, array $segmentVariables, array $parameters = []) - { + protected function compileSegmentNodes( + $code, + ChildrenNodeCollection $nodes, + array $segmentVariables, + array $parameters = [] + ) { $originalParameters = $parameters; foreach ($nodes->getChildren() as $node) { @@ -169,7 +172,10 @@ protected function compileSegmentNodes($code, ChildrenNodeCollection $nodes, arr $count = $currentParameter; foreach ($segmentMatchers as $segmentDepth => $matcher) { - $matchedParameters = $matcher->getMatchedParameterExpressions($segmentVariables[$segmentDepth], $count++); + $matchedParameters = $matcher->getMatchedParameterExpressions( + $segmentVariables[$segmentDepth], + $count++ + ); foreach ($matchedParameters as $parameterKey => $matchedParameter) { $parameters[$parameterKey] = $matchedParameter; @@ -190,6 +196,13 @@ protected function compileSegmentNodes($code, ChildrenNodeCollection $nodes, arr } } + /** + * [compiledRouteHttpMethodMatch description] + * + * @param object $code + * @param MatchedRouteDataMap $routeDataMap + * @param array $parameters + */ protected function compiledRouteHttpMethodMatch($code, MatchedRouteDataMap $routeDataMap, array $parameters) { $code->appendLine('switch ($method) {'); @@ -248,7 +261,13 @@ protected function compileNotFound($code) */ protected function compileDisallowedHttpMethod($code, array $allowedMethod) { - $code->appendLine('return [' . VarExporter::export(Dispatcher::HTTP_METHOD_NOT_ALLOWED) . ', ' . VarExporter::export($allowedMethod) . '];'); + $code->appendLine( + 'return [' . + VarExporter::export(Dispatcher::HTTP_METHOD_NOT_ALLOWED) . + ', ' . + VarExporter::export($allowedMethod) . + '];' + ); } /** @@ -258,7 +277,8 @@ protected function compileDisallowedHttpMethod($code, array $allowedMethod) */ protected function compileDisallowedHttpMethodOrNotFound($code) { - $code->appendLine('return ' . + $code->appendLine( + 'return ' . 'isset($allowedHttpMethods) ' . '? ' . '[' @@ -267,7 +287,8 @@ protected function compileDisallowedHttpMethodOrNotFound($code) . ': ' . '[' . VarExporter::export(Dispatcher::NOT_FOUND) - . '];'); + . '];' + ); } /** @@ -291,7 +312,8 @@ protected function compileFoundRoute($code, array $foundRoute, array $parameterE $parameters .= ']'; - $code->appendLine('return [' + $code->appendLine( + 'return [' . VarExporter::export(Dispatcher::FOUND) . ', ' . VarExporter::export($foundRoute[1]) From 16e0a223de64ee27948b184eade5f8584495f7b7 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Wed, 24 Aug 2016 20:51:20 +0200 Subject: [PATCH 14/20] added first basic tests | finished all needed middlewares | cs fixes --- src/Viserio/Contracts/Routing/Route.php | 13 ++++-- .../Contracts/Routing/RouteCollection.php | 8 ++-- src/Viserio/Routing/Dispatcher.php | 28 +++++++++--- .../Generator/ChildrenNodeCollection.php | 14 +++--- .../Generator/Optimizer/MatcherOptimizer.php | 8 ++-- .../Routing/Middlewares/FoundMiddleware.php | 44 +++++++++++++++++++ .../InternalServerErrorMiddleware.php | 23 ++++++++++ .../Middlewares/NotAllowedMiddleware.php | 22 +++++++++- .../Middlewares/NotFoundMiddleware.php | 5 ++- src/Viserio/Routing/Route.php | 6 ++- src/Viserio/Routing/Router.php | 7 ++- .../Tests/Router/RootRoutesRouterTest.php | 27 ++++++++---- .../Tests/Router/RouteRouterBaseTest.php | 10 ++--- src/Viserio/Routing/TreeRouteCompiler.php | 14 +++--- 14 files changed, 173 insertions(+), 56 deletions(-) create mode 100644 src/Viserio/Routing/Middlewares/FoundMiddleware.php create mode 100644 src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php diff --git a/src/Viserio/Contracts/Routing/Route.php b/src/Viserio/Contracts/Routing/Route.php index fa0cf5fcc..9b9bfdcd0 100644 --- a/src/Viserio/Contracts/Routing/Route.php +++ b/src/Viserio/Contracts/Routing/Route.php @@ -3,6 +3,8 @@ namespace Viserio\Contracts\Routing; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; interface Route { @@ -47,7 +49,7 @@ public function getMethods(): array; * Set a regular expression requirement on the route. * * @param array|string $name - * @param string $expression + * @param string|null $expression * * @return $this */ @@ -189,14 +191,17 @@ public function forgetParameter(string $name); /** * The regular expression requirements. * - * @return \\Viserio\Contracts\Routing\RouteMatcher[] + * @return \Viserio\Contracts\Routing\RouteMatcher[] */ public function getSegments(): array; /** * Run the route action and return the response. * - * @return mixed + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + * + * @return \Psr\Http\Message\ResponseInterface */ - public function run(); + public function run(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface; } diff --git a/src/Viserio/Contracts/Routing/RouteCollection.php b/src/Viserio/Contracts/Routing/RouteCollection.php index 165097fc7..f19ce0616 100644 --- a/src/Viserio/Contracts/Routing/RouteCollection.php +++ b/src/Viserio/Contracts/Routing/RouteCollection.php @@ -9,7 +9,7 @@ interface RouteCollection * * @param \Viserio\Contracts\Routing\Route $route * - * @return Viserio\Contracts\Routing\Route + * @return \Viserio\Contracts\Routing\Route */ public function add(Route $route): Route; @@ -20,7 +20,7 @@ public function add(Route $route): Route; * * @throws \RuntimeException * - * @return Viserio\Contracts\Routing\Route + * @return \Viserio\Contracts\Routing\Route */ public function match(string $identifier): Route; @@ -38,7 +38,7 @@ public function hasNamedRoute(string $name): bool; * * @param string $name * - * @return Viserio\Contracts\Routing\Route|null + * @return \Viserio\Contracts\Routing\Route|null */ public function getByName(string $name); @@ -47,7 +47,7 @@ public function getByName(string $name); * * @param string $action * - * @return Viserio\Contracts\Routing\Route|null + * @return \Viserio\Contracts\Routing\Route|null */ public function getByAction(string $action); diff --git a/src/Viserio/Routing/Dispatcher.php b/src/Viserio/Routing/Dispatcher.php index 62b84220a..f60421224 100644 --- a/src/Viserio/Routing/Dispatcher.php +++ b/src/Viserio/Routing/Dispatcher.php @@ -10,7 +10,10 @@ use Viserio\Middleware\Dispatcher as MiddlewareDispatcher; use Viserio\Routing\Generator\RouteTreeBuilder; use Viserio\Routing\Generator\RouteTreeOptimizer; +use Viserio\Routing\Middlewares\FoundMiddleware; use Viserio\Routing\Middlewares\NotFoundMiddleware; +use Viserio\Routing\Middlewares\NotAllowedMiddleware; +use Viserio\Routing\Middlewares\InternalServerErrorMiddleware; class Dispatcher implements DispatcherContract { @@ -26,7 +29,7 @@ class Dispatcher implements DispatcherContract /** * The route collection instance. * - * @var \Viserio\Routing\RouteCollection + * @var \Viserio\Contracts\Routing\RouteCollection */ protected $routes; @@ -83,13 +86,12 @@ public function handle(ServerRequestInterface $request): MiddlewareDispatcher switch ($match[0]) { case DispatcherContract::NOT_FOUND: return $this->handleNotFound(); - break; case DispatcherContract::HTTP_METHOD_NOT_ALLOWED: return $this->handleNotAllowed($match[1]); - break; case DispatcherContract::FOUND: return $this->handleFound($match[1], $match[2]); - break; + default: + return $this->handleInternalServerError(); } } @@ -106,10 +108,10 @@ protected function handleFound(string $identifier, array $segments): MiddlewareD $route = $this->routes->match($identifier); foreach ($segments as $key => $value) { - $route->setParameter($key, $value); + $route->setParameter($key, urldecode($value)); } - return $this->middlewareDispatcher; + return $this->middlewareDispatcher->withMiddleware(new FoundMiddleware($route)); } /** @@ -131,7 +133,19 @@ protected function handleNotFound(): MiddlewareDispatcher */ protected function handleNotAllowed(array $allowed): MiddlewareDispatcher { - return $this->middlewareDispatcher->withMiddleware(new NotFoundMiddleware($allowed)); + return $this->middlewareDispatcher->withMiddleware(new NotAllowedMiddleware($allowed)); + } + + /** + * Handles a internal server error. + * + * @param array $allowed + * + * @return \Viserio\Middleware\Dispatcher + */ + public function handleInternalServerError(): MiddlewareDispatcher + { + return $this->middlewareDispatcher->withMiddleware(new InternalServerErrorMiddleware()); } /** diff --git a/src/Viserio/Routing/Generator/ChildrenNodeCollection.php b/src/Viserio/Routing/Generator/ChildrenNodeCollection.php index df052f2c8..9e2cb745a 100644 --- a/src/Viserio/Routing/Generator/ChildrenNodeCollection.php +++ b/src/Viserio/Routing/Generator/ChildrenNodeCollection.php @@ -10,7 +10,7 @@ final class ChildrenNodeCollection implements NodeContentsContract /** * All added children routes. * - * @var \Viserio\Routing\RouteTreeNode[] + * @var \Viserio\Routing\Generator\RouteTreeNode[] */ protected $children = []; @@ -25,7 +25,7 @@ public function __construct(array $children = []) } /** - * @return \Viserio\Routing\RouteTreeNode[] + * @return \Viserio\Routing\Generator\RouteTreeNode[] */ public function getChildren(): array { @@ -33,7 +33,7 @@ public function getChildren(): array } /** - * @param \Viserio\Routing\RouteTreeNode $node + * @param \Viserio\Routing\Generator\RouteTreeNode $node * * @return bool */ @@ -49,15 +49,13 @@ public function hasChild(RouteTreeNode $node): bool */ public function hasChildFor(SegmentMatcherContract $matcher): bool { - $hash = $matcher->getHash(); - - return isset($this->children[$hash]); + return isset($this->children[$matcher->getHash()]); } /** * @param \Viserio\Contracts\Routing\SegmentMatcher $matcher * - * @return \Viserio\Contracts\Routing\RouteTreeNode|null + * @return \Viserio\Routing\Generator\RouteTreeNode|null */ public function getChild(SegmentMatcherContract $matcher) { @@ -65,7 +63,7 @@ public function getChild(SegmentMatcherContract $matcher) } /** - * @param \Viserio\Contracts\Routing\RouteTreeNode $node + * @param \Viserio\Routing\Generator\RouteTreeNode $node */ public function addChild(RouteTreeNode $node) { diff --git a/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php b/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php index 617df512e..fc3e9f3c7 100644 --- a/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php +++ b/src/Viserio/Routing/Generator/Optimizer/MatcherOptimizer.php @@ -24,8 +24,8 @@ private function __construct() /** * Merge same matcher together. * - * @param \Viserio\Contracts\Routing\SegmentMatcher[] $parentMatchers - * @param array $childMatchers + * @param array $parentMatchers + * @param array $childMatchers * * @return array */ @@ -47,9 +47,9 @@ public static function mergeMatchers(array $parentMatchers, array $childMatchers /** * Optimize matcher and matcher order. * - * @param \Viserio\Contracts\Routing\SegmentMatcher[] $matchers + * @param array $matchers * - * @return \Viserio\Contracts\Routing\SegmentMatcher[] + * @return array */ public static function optimizeMatchers(array $matchers): array { diff --git a/src/Viserio/Routing/Middlewares/FoundMiddleware.php b/src/Viserio/Routing/Middlewares/FoundMiddleware.php new file mode 100644 index 000000000..ddc449bc9 --- /dev/null +++ b/src/Viserio/Routing/Middlewares/FoundMiddleware.php @@ -0,0 +1,44 @@ +route = $route; + } + + /** + * {@inheritdoc} + */ + public function process( + ServerRequestInterface $request, + DelegateContract $frame + ): ResponseInterface { + // add route to the request's attributes in case a middleware or handler needs access to the route + $request = $request->withAttribute('route', $this->route); + + $response = $frame->next($request); + + return $this->route->run($request, $response); + } +} diff --git a/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php b/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php new file mode 100644 index 000000000..10dcc7afe --- /dev/null +++ b/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php @@ -0,0 +1,23 @@ +next($request); + + return $response->withStatus(500); + } +} diff --git a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php index 23ca129af..32803a584 100644 --- a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php @@ -9,12 +9,32 @@ class NotAllowedMiddleware implements ServerMiddlewareContract { + /** + * All not allowed http methods. + * + * @var array + */ + protected $allowed; + + /** + * Create a found middleware instance. + * + * @param array $allowed + */ + public function __construct(array $allowed) + { + $this->allowed = $allowed; + } + + /** + * {@inheritdoc} + */ public function process( ServerRequestInterface $request, DelegateContract $frame ): ResponseInterface { $response = $frame->next($request); - return $response; + return $response->withStatus(405); } } diff --git a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php index 584f51d75..5d280ac48 100644 --- a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php @@ -9,12 +9,15 @@ class NotFoundMiddleware implements ServerMiddlewareContract { + /** + * {@inheritdoc} + */ public function process( ServerRequestInterface $request, DelegateContract $frame ): ResponseInterface { $response = $frame->next($request); - return $response; + return $response->withStatus(404); } } diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index 7e163bd3d..314e4b6d4 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -5,6 +5,8 @@ use LogicException; use Narrowspark\Arr\StaticArr as Arr; use UnexpectedValueException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Contracts\Routing\Route as RouteContract; @@ -349,13 +351,13 @@ public function getSegments(): array /** * {@inheritdoc} */ - public function run() + public function run(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $this->initInvoker(); return $this->invoker->call( $this->action['uses'], - array_values($this->getParameters()) + [$request, $response, $this->getParameters()] ); } diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php index bff2a9294..dea236510 100644 --- a/src/Viserio/Routing/Router.php +++ b/src/Viserio/Routing/Router.php @@ -281,14 +281,13 @@ public function withoutMiddleware(MiddlewareContract $middleware): RouterContrac */ public function dispatch(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { - $middlewareDispatcher = new MiddlewareDispatcher($response); $dispatcher = new Dispatcher( $this->path, $this->routes, - $middlewareDispatcher, + new MiddlewareDispatcher($response), $this->isDevelopMode ); - $route = $dispatcher->handle($request); + $middlewareDispatcher = $dispatcher->handle($request); foreach ($this->withMiddlewares as $withMiddleware) { $middlewareDispatcher->withMiddleware($withMiddleware); @@ -298,7 +297,7 @@ public function dispatch(ServerRequestInterface $request, ResponseInterface $res $middlewareDispatcher->withoutMiddleware($withoutMiddleware); } - return $middlewareDispatcher->process($route->run()); + return $middlewareDispatcher->process($request); } /** diff --git a/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php index 305dccd2f..9d3c5e77f 100644 --- a/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php @@ -3,6 +3,7 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Dispatcher; +use Viserio\Http\StreamFactory; class RootRoutesRouterTest extends RouteRouterBaseTest { @@ -12,25 +13,33 @@ class RootRoutesRouterTest extends RouteRouterBaseTest * [ * 'GET', * '/user/1', - * Dispatcher::found(['route_data'], ['id' => '1']) + * body string * ] * * @return array[] */ - public function routerMatchingProvider() + public function routerMatchingProvider(): array { return [ - ['GET', '', ['name' => 'root'], []], - ['GET', '/', ['name' => 'root-slash'], []], - ['GET', '/a', []], - ['GET', 'test/123', []], + ['GET', '', 'Hello'], + ['GET', '/', 'Hello'], + ['GET', '/a', ''], + ['GET', 'test/123', 'Hello, 123'], ]; } protected function definitions($router) { - $router->get('')->setParameter('name', 'root'); - $router->get('/')->setParameter('name', 'root-slash'); - $router->get('/test/{param}'); + $router->get('', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString('Hello')); + })->setParameter('name', 'root'); + + $router->get('/', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString('Hello')); + })->setParameter('name', 'root-slash'); + + $router->get('/test/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString('Hello, ' . $args['param'])); + }); } } diff --git a/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php index cf5f174b2..99fa7cb99 100644 --- a/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php +++ b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php @@ -33,12 +33,12 @@ public function setUp() */ public function testRouter($httpMethod, $uri, $expectedResult) { - // $actualResult = $this->router->dispatch( - // (new ServerRequestFactory())->createServerRequest($httpMethod, $uri), - // (new ResponseFactory())->createResponse() - // ); + $actualResult = $this->router->dispatch( + (new ServerRequestFactory())->createServerRequest($httpMethod, $uri), + (new ResponseFactory())->createResponse() + ); - // $this->assertEquals($expectedResult, $actualResult); + $this->assertEquals($expectedResult, (string) $actualResult->getBody()); } abstract protected function definitions($routes); diff --git a/src/Viserio/Routing/TreeRouteCompiler.php b/src/Viserio/Routing/TreeRouteCompiler.php index d6081bd14..ca7e98770 100644 --- a/src/Viserio/Routing/TreeRouteCompiler.php +++ b/src/Viserio/Routing/TreeRouteCompiler.php @@ -92,7 +92,7 @@ protected function createRouterClassTemplate(string $rootRoute, string $body): s } /** - * [compileRouteTree description] + * Compile the counter for the segments check. * * @param object $code * @param array $routeTree @@ -139,7 +139,7 @@ protected function compileRouteTree($code, array $routeTree) } /** - * [compileSegmentNodes description] + * Comple the segemtns nodes to if statements. * * @param object $code * @param ChildrenNodeCollection $nodes @@ -197,7 +197,7 @@ protected function compileSegmentNodes( } /** - * [compiledRouteHttpMethodMatch description] + * Compile the route http method match switch. * * @param object $code * @param MatchedRouteDataMap $routeDataMap @@ -244,7 +244,7 @@ protected function compiledRouteHttpMethodMatch($code, MatchedRouteDataMap $rout } /** - * [compileNotFound description] + * Compile the return data. * * @param object $code */ @@ -254,7 +254,7 @@ protected function compileNotFound($code) } /** - * [compileDisallowedHttpMethod + * Compile disallowed http method data. * * @param object $code * @param array $allowedMethod @@ -271,7 +271,7 @@ protected function compileDisallowedHttpMethod($code, array $allowedMethod) } /** - * [compileDisallowedHttpMethodOrNotFound + * Compile disallowed http method or not found data check. * * @param object $code */ @@ -292,7 +292,7 @@ protected function compileDisallowedHttpMethodOrNotFound($code) } /** - * [compileFoundRoute description] + * Compile the found route data. * * @param object $code * @param array $foundRoute From db6139cfd311a3843e33c96abb9bad55649d85e8 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Wed, 24 Aug 2016 18:51:40 +0000 Subject: [PATCH 15/20] Applied fixes from StyleCI --- src/Viserio/Contracts/Routing/Route.php | 2 +- src/Viserio/Routing/Dispatcher.php | 4 ++-- src/Viserio/Routing/Route.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Viserio/Contracts/Routing/Route.php b/src/Viserio/Contracts/Routing/Route.php index 9b9bfdcd0..ce8abb4d6 100644 --- a/src/Viserio/Contracts/Routing/Route.php +++ b/src/Viserio/Contracts/Routing/Route.php @@ -2,9 +2,9 @@ declare(strict_types=1); namespace Viserio\Contracts\Routing; -use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; interface Route { diff --git a/src/Viserio/Routing/Dispatcher.php b/src/Viserio/Routing/Dispatcher.php index f60421224..d2f200282 100644 --- a/src/Viserio/Routing/Dispatcher.php +++ b/src/Viserio/Routing/Dispatcher.php @@ -11,9 +11,9 @@ use Viserio\Routing\Generator\RouteTreeBuilder; use Viserio\Routing\Generator\RouteTreeOptimizer; use Viserio\Routing\Middlewares\FoundMiddleware; -use Viserio\Routing\Middlewares\NotFoundMiddleware; -use Viserio\Routing\Middlewares\NotAllowedMiddleware; use Viserio\Routing\Middlewares\InternalServerErrorMiddleware; +use Viserio\Routing\Middlewares\NotAllowedMiddleware; +use Viserio\Routing\Middlewares\NotFoundMiddleware; class Dispatcher implements DispatcherContract { diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index 314e4b6d4..e52a7fd54 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -4,9 +4,9 @@ use LogicException; use Narrowspark\Arr\StaticArr as Arr; -use UnexpectedValueException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use UnexpectedValueException; use Viserio\Contracts\Container\Traits\ContainerAwareTrait; use Viserio\Contracts\Middleware\Middleware as MiddlewareContract; use Viserio\Contracts\Routing\Route as RouteContract; From 85de386251d64ac7bd868508fbab8115055f6840 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Thu, 25 Aug 2016 00:22:45 +0200 Subject: [PATCH 16/20] added more tests --- .../Contracts/Routing/SegmentMatcher.php | 2 - src/Viserio/Routing/Dispatcher.php | 4 +- .../Routing/Matchers/ExpressionMatcher.php | 2 +- src/Viserio/Routing/Matchers/RegexMatcher.php | 4 +- .../InternalServerErrorMiddleware.php | 3 +- .../Middlewares/NotAllowedMiddleware.php | 3 +- .../Middlewares/NotFoundMiddleware.php | 3 +- src/Viserio/Routing/Route.php | 2 +- src/Viserio/Routing/RouteParser.php | 4 +- src/Viserio/Routing/Router.php | 42 +++++- .../Tests/Generator/RouteTreeBuilderTest.php | 10 +- .../BasicParameterPatternsRouterTest.php | 141 ++++++++++++++++++ .../Tests/Router/BasicRestfulRouterTest.php | 84 +++++++++++ .../Router/CommonRouteSegmentRouterTest.php | 54 +++++++ .../ComplexParameterPatternsRouterTest.php | 100 +++++++++++++ .../Tests/Router/ComplexShopRouterTest.php | 26 ++++ .../Tests/Router/RootRoutesRouterTest.php | 24 +-- .../Tests/Router/RouteRouterBaseTest.php | 27 +++- src/Viserio/Routing/composer.json | 1 + 19 files changed, 500 insertions(+), 36 deletions(-) create mode 100644 src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php create mode 100644 src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php create mode 100644 src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php create mode 100644 src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php create mode 100644 src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php diff --git a/src/Viserio/Contracts/Routing/SegmentMatcher.php b/src/Viserio/Contracts/Routing/SegmentMatcher.php index ea042b7fb..9d0b3c7c4 100644 --- a/src/Viserio/Contracts/Routing/SegmentMatcher.php +++ b/src/Viserio/Contracts/Routing/SegmentMatcher.php @@ -4,8 +4,6 @@ interface SegmentMatcher { - const SEGMENT_PLACEHOLDER = '{segment}'; - /** * Return all set parameters keys. * diff --git a/src/Viserio/Routing/Dispatcher.php b/src/Viserio/Routing/Dispatcher.php index d2f200282..0f99b2292 100644 --- a/src/Viserio/Routing/Dispatcher.php +++ b/src/Viserio/Routing/Dispatcher.php @@ -87,7 +87,7 @@ public function handle(ServerRequestInterface $request): MiddlewareDispatcher case DispatcherContract::NOT_FOUND: return $this->handleNotFound(); case DispatcherContract::HTTP_METHOD_NOT_ALLOWED: - return $this->handleNotAllowed($match[1]); + return $this->handleMethodNotAllowed($match[1]); case DispatcherContract::FOUND: return $this->handleFound($match[1], $match[2]); default: @@ -131,7 +131,7 @@ protected function handleNotFound(): MiddlewareDispatcher * * @return \Viserio\Middleware\Dispatcher */ - protected function handleNotAllowed(array $allowed): MiddlewareDispatcher + protected function handleMethodNotAllowed(array $allowed): MiddlewareDispatcher { return $this->middlewareDispatcher->withMiddleware(new NotAllowedMiddleware($allowed)); } diff --git a/src/Viserio/Routing/Matchers/ExpressionMatcher.php b/src/Viserio/Routing/Matchers/ExpressionMatcher.php index 4858af492..a9be67aa6 100644 --- a/src/Viserio/Routing/Matchers/ExpressionMatcher.php +++ b/src/Viserio/Routing/Matchers/ExpressionMatcher.php @@ -38,7 +38,7 @@ public function getExpression(): string */ public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string { - return str_replace(self::SEGMENT_PLACEHOLDER, $segmentVariable, $this->expression); + return str_replace('{segment}', $segmentVariable, $this->expression); } /** diff --git a/src/Viserio/Routing/Matchers/RegexMatcher.php b/src/Viserio/Routing/Matchers/RegexMatcher.php index 238c6269d..16833c29d 100644 --- a/src/Viserio/Routing/Matchers/RegexMatcher.php +++ b/src/Viserio/Routing/Matchers/RegexMatcher.php @@ -29,7 +29,9 @@ class RegexMatcher extends AbstractMatcher */ public function __construct(string $regex, $parameterKeyGroupMap) { - if (strpos($regex, '/^(') !== false && strpos($regex, ')$/') !== false) { + if ((strpos($regex, '/^(') !== false && strpos($regex, ')$/') !== false) || + (strpos($regex, '/^') !== false && strpos($regex, '$/') !== false) + ) { $this->regex = $regex; } else { $this->regex = '/^(' . $regex . ')$/'; diff --git a/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php b/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php index 10dcc7afe..079091564 100644 --- a/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php +++ b/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php @@ -4,6 +4,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Narrowspark\HttpStatus\Exception\InternalServerErrorException; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; @@ -18,6 +19,6 @@ public function process( ): ResponseInterface { $response = $frame->next($request); - return $response->withStatus(500); + throw new InternalServerErrorException(); } } diff --git a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php index 32803a584..a1d45a4eb 100644 --- a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php @@ -4,6 +4,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Narrowspark\HttpStatus\Exception\MethodNotAllowedException; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; @@ -35,6 +36,6 @@ public function process( ): ResponseInterface { $response = $frame->next($request); - return $response->withStatus(405); + throw new MethodNotAllowedException(); } } diff --git a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php index 5d280ac48..8f4bf4830 100644 --- a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php @@ -4,6 +4,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Narrowspark\HttpStatus\Exception\NotFoundException; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; @@ -18,6 +19,6 @@ public function process( ): ResponseInterface { $response = $frame->next($request); - return $response->withStatus(404); + throw new NotFoundException(); } } diff --git a/src/Viserio/Routing/Route.php b/src/Viserio/Routing/Route.php index e52a7fd54..88217aad1 100644 --- a/src/Viserio/Routing/Route.php +++ b/src/Viserio/Routing/Route.php @@ -180,7 +180,7 @@ public function getMethods(): array public function where($name, string $expression = null): RouteContract { foreach ($this->parseWhere($name, $expression) as $name => $expression) { - $this->wheres[] = new ParameterSegment($name, $expression); + $this->wheres[$name] = $expression; } return $this; diff --git a/src/Viserio/Routing/RouteParser.php b/src/Viserio/Routing/RouteParser.php index 66ab1dd71..dbd5bd08a 100644 --- a/src/Viserio/Routing/RouteParser.php +++ b/src/Viserio/Routing/RouteParser.php @@ -130,7 +130,6 @@ protected function matchRouteParameters( protected function generateRegex(array $matches, array $parameterPatterns): string { $regex = '/^'; - foreach ($matches as $match) { list($type, $part) = $match; @@ -138,7 +137,8 @@ protected function generateRegex(array $matches, array $parameterPatterns): stri $regex .= preg_quote($part, '/'); } else { // Parameter, $part is the parameter name - $regex .= '(' . ($parameterPatterns[$part] ?? Pattern::ANY) . ')'; + $pattern = $parameterPatterns[$part] ?? Pattern::ANY; + $regex .= '(' . $pattern . ')'; } } diff --git a/src/Viserio/Routing/Router.php b/src/Viserio/Routing/Router.php index dea236510..d803263cc 100644 --- a/src/Viserio/Routing/Router.php +++ b/src/Viserio/Routing/Router.php @@ -52,6 +52,13 @@ class Router implements RouterContract */ protected $globalParameterConditions = []; + /** + * The globally available parameter patterns. + * + * @var array + */ + protected $patterns = []; + /** * Flag for development mode. * @@ -202,6 +209,39 @@ public function getGroupStack(): array return $this->groupStack; } + /** + * Set a global where pattern on all routes. + * + * @param string $key + * @param string $pattern + */ + public function pattern(string $key, string $pattern) + { + $this->patterns[$key] = $pattern; + } + + /** + * Set a group of global where patterns on all routes. + * + * @param array $patterns + */ + public function patterns(array $patterns) + { + foreach ($patterns as $key => $pattern) { + $this->pattern($key, $pattern); + } + } + + /** + * Get the global "where" patterns. + * + * @return array + */ + public function getPatterns(): array + { + return $this->patterns; + } + /** * Defines the supplied parameter name to be globally associated with the expression. * @@ -369,7 +409,7 @@ protected function createRoute($methods, string $uri, $action): RouteContract protected function addWhereClausesToRoute(RouteContract $route): RouteContract { $where = $route->getAction()['where'] ?? []; - $patern = array_merge($this->globalParameterConditions, $where); + $patern = array_merge($this->patterns, $where); foreach ($patern as $name => $value) { $route->where($name, $value); diff --git a/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php b/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php index 612a368e9..85fb1441b 100644 --- a/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php +++ b/src/Viserio/Routing/Tests/Generator/RouteTreeBuilderTest.php @@ -50,7 +50,7 @@ public function routeTreeBuilderCases() [ [ (new Route(['GET'], '/first/{param1}', null)), - (new Route(['GET'], '/{param1}/{param2}', null))->where(['param1', 'param2'], Pattern::ANY), + (new Route(['GET'], '/{param1}/{param2}', null))->where(['param1', 'param2'], Pattern::ALPHA), ], null, [ @@ -66,11 +66,11 @@ public function routeTreeBuilderCases() ), ]) ), - (new RegexMatcher(Pattern::ANY, 0))->getHash() => new RouteTreeNode( - [0 => new RegexMatcher(Pattern::ANY, 0)], + (new RegexMatcher(Pattern::ALPHA, 0))->getHash() => new RouteTreeNode( + [0 => new RegexMatcher(Pattern::ALPHA, 0)], new ChildrenNodeCollection([ - (new RegexMatcher(Pattern::ANY, 1))->getHash() => new RouteTreeNode( - [1 => new RegexMatcher(Pattern::ANY, 1)], + (new RegexMatcher(Pattern::ALPHA, 1))->getHash() => new RouteTreeNode( + [1 => new RegexMatcher(Pattern::ALPHA, 1)], new MatchedRouteDataMap([ [['GET', 'HEAD'], [[0 => 'param1', 1 => 'param2'], 'GET|HEAD/{param1}/{param2}']], ]) diff --git a/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php b/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php new file mode 100644 index 000000000..f9a5d4f3e --- /dev/null +++ b/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php @@ -0,0 +1,141 @@ +get('/digits/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::DIGITS)->setParameter('name', 'digits'); + + $router->get('/alpha/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::ALPHA)->setParameter('name', 'alpha'); + + $router->get('/alpha_low/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::ALPHA_LOWER)->setParameter('name', 'alpha_low'); + + $router->get('/alpha_up/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::ALPHA_UPPER)->setParameter('name', 'alpha_up'); + + $router->get('/alpha_num/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::ALPHA_NUM)->setParameter('name', 'alpha_num'); + + $router->get('/alpha_num_dash/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::ALPHA_NUM_DASH)->setParameter('name', 'alpha_num_dash'); + + $router->get('/any/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', Pattern::ANY)->setParameter('name', 'any'); + + $router->get('/custom/{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['param'] . ' | ' . $args['name'])); + })->where('param', '[\!]{3,5}')->setParameter('name', 'custom'); + } +} diff --git a/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php b/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php new file mode 100644 index 000000000..e917b7a72 --- /dev/null +++ b/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php @@ -0,0 +1,84 @@ +pattern('id', Pattern::DIGITS); + $router->get('/user', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.index'); + $router->get('/user/create', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.create'); + $router->post('/user', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.save'); + $router->get('/user/{id}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.show'); + $router->get('/user/{id}/edit', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.edit'); + $router->put('/user/{id}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.update'); + $router->delete('/user/{id}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); + })->setParameter('name','user.delete'); + } +} diff --git a/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php b/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php new file mode 100644 index 000000000..549673236 --- /dev/null +++ b/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php @@ -0,0 +1,54 @@ +pattern('p2', Pattern::ALPHA); + + $router->get('/route1/{p1}/{p2}/{p3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3'])); + })->setParameter('name', 'route1'); + $router->get('/route2/{p1}/{p2}/{p3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3'])); + })->setParameter('name', 'route2'); + $router->get('/route3/{p1}/{p2}/{p3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3'])); + })->setParameter('name', 'route3'); + $router->get('/route4/{p1}/{p2}/{p3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3'])); + })->setParameter('name', 'route4'); + $router->get('/route5/{p_1}/{p_2}/{p_3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | p_1 = ' . $args['p_1'] . ' | p_2 = ' . $args['p_2'] . ' | p_3 = ' . $args['p_3'])); + })->setParameter('name', 'route5'); + $router->get('/route6/{p_1}/{p2}/{p_3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | p_1 = ' . $args['p_1'] . ' | p2 = ' . $args['p2'] . ' | p_3 = ' . $args['p_3'])); + })->setParameter('name', 'route6'); + } +} diff --git a/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php b/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php new file mode 100644 index 000000000..b54b931bb --- /dev/null +++ b/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php @@ -0,0 +1,100 @@ +get('/a/prefix:{param}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | param = ' . $args['param'])); + })->setParameter('name', 'prefix'); + $router->get('/b/{param}:suffix', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | param = ' . $args['param'])); + })->setParameter('name', 'suffix'); + $router->get('/c/prefix:{param}:suffix', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | param = ' . $args['param'])); + })->setParameter('name', 'prefix-and-suffix'); + $router->get('/d/{param1}-{param2}:{param3}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | param1 = ' . $args['param1'] . ' | param2 = ' . $args['param2'] . ' | param3 = ' . $args['param3'])); + })->setParameter('name', 'multi-param'); + $router->get('/e/{digits}-{alpha}:{exclaim}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['routename'] . ' | digits = ' . $args['digits'] . ' | alpha = ' . $args['alpha'] . ' | exclaim = ' . $args['exclaim'])); + }) + ->where('digits', Pattern::DIGITS) + ->where('alpha', Pattern::ALPHA) + ->where('exclaim', '!{3,5}') + ->setParameter('routename', 'filtered-multi-param'); + $router->get('/f/{name}-is-awesome-at-{thing}', function ($request, $response, $args) { + return $response->withBody((new StreamFactory())->createStreamFromString($args['routename'] . ' | name = ' . $args['name'] . ' | thing = ' . $args['thing'])); + })->where('name', '[A-Z]?[a-z]+') + ->where('thing', Pattern::ALPHA_LOWER) + ->setParameter('routename', 'sentence-multi-param'); + } +} diff --git a/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php b/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php new file mode 100644 index 000000000..367c7df27 --- /dev/null +++ b/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php @@ -0,0 +1,26 @@ +get('/', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString('Hello')); })->setParameter('name', 'root-slash'); - - $router->get('/test/{param}', function ($request, $response, $args) { - return $response->withBody((new StreamFactory())->createStreamFromString('Hello, ' . $args['param'])); - }); } } diff --git a/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php index 99fa7cb99..642a933fc 100644 --- a/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php +++ b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php @@ -31,7 +31,7 @@ public function setUp() /** * @dataProvider routerMatchingProvider */ - public function testRouter($httpMethod, $uri, $expectedResult) + public function testRouter($httpMethod, $uri, $expectedResult, $status = 200) { $actualResult = $this->router->dispatch( (new ServerRequestFactory())->createServerRequest($httpMethod, $uri), @@ -39,6 +39,31 @@ public function testRouter($httpMethod, $uri, $expectedResult) ); $this->assertEquals($expectedResult, (string) $actualResult->getBody()); + $this->assertSame($status, $actualResult->getStatusCode()); + } + + /** + * @dataProvider routerMatching404Provider + * @expectedException \Narrowspark\HttpStatus\Exception\NotFoundException + */ + public function testRouter404($httpMethod, $uri) + { + $this->router->dispatch( + (new ServerRequestFactory())->createServerRequest($httpMethod, $uri), + (new ResponseFactory())->createResponse() + ); + } + + /** + * @dataProvider routerMatching405Provider + * @expectedException \Narrowspark\HttpStatus\Exception\MethodNotAllowedException + */ + public function testRouter405($httpMethod, $uri) + { + $this->router->dispatch( + (new ServerRequestFactory())->createServerRequest($httpMethod, $uri), + (new ResponseFactory())->createResponse() + ); } abstract protected function definitions($routes); diff --git a/src/Viserio/Routing/composer.json b/src/Viserio/Routing/composer.json index 1b23d1e1f..849b7ef25 100644 --- a/src/Viserio/Routing/composer.json +++ b/src/Viserio/Routing/composer.json @@ -21,6 +21,7 @@ "php" : "7.0.0 - 7.0.5 || ^7.0.7", "container-interop/container-interop" : "^1.0", "narrowspark/arr" : "^1.1", + "narrowspark/http-status" : "^2.0", "php-di/invoker" : "^1.3", "psr/http-message" : "^1.0", "viserio/contracts" : "self.version", From 122ca1f45b8b6b9e30ad54276ac285b5fce869bb Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Wed, 24 Aug 2016 22:23:01 +0000 Subject: [PATCH 17/20] Applied fixes from StyleCI --- .../InternalServerErrorMiddleware.php | 2 +- .../Routing/Middlewares/NotAllowedMiddleware.php | 2 +- .../Routing/Middlewares/NotFoundMiddleware.php | 2 +- .../Tests/Router/BasicRestfulRouterTest.php | 16 ++++++++-------- .../Tests/Router/ComplexShopRouterTest.php | 1 - .../Tests/Router/RootRoutesRouterTest.php | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php b/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php index 079091564..75988f8af 100644 --- a/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php +++ b/src/Viserio/Routing/Middlewares/InternalServerErrorMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); namespace Viserio\Routing\Middlewares; +use Narrowspark\HttpStatus\Exception\InternalServerErrorException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Narrowspark\HttpStatus\Exception\InternalServerErrorException; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; diff --git a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php index a1d45a4eb..6391e09ef 100644 --- a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); namespace Viserio\Routing\Middlewares; +use Narrowspark\HttpStatus\Exception\MethodNotAllowedException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Narrowspark\HttpStatus\Exception\MethodNotAllowedException; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; diff --git a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php index 8f4bf4830..72e60ac95 100644 --- a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php @@ -2,9 +2,9 @@ declare(strict_types=1); namespace Viserio\Routing\Middlewares; +use Narrowspark\HttpStatus\Exception\NotFoundException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Narrowspark\HttpStatus\Exception\NotFoundException; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; diff --git a/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php b/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php index e917b7a72..487ae6eac 100644 --- a/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Dispatcher; -use Viserio\Http\StreamFactory; use Viserio\Contracts\Routing\Pattern; +use Viserio\Http\StreamFactory; class BasicRestfulRouterTest extends RouteRouterBaseTest { @@ -61,24 +61,24 @@ protected function definitions($router) $router->pattern('id', Pattern::DIGITS); $router->get('/user', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.index'); + })->setParameter('name', 'user.index'); $router->get('/user/create', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.create'); + })->setParameter('name', 'user.create'); $router->post('/user', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.save'); + })->setParameter('name', 'user.save'); $router->get('/user/{id}', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.show'); + })->setParameter('name', 'user.show'); $router->get('/user/{id}/edit', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.edit'); + })->setParameter('name', 'user.edit'); $router->put('/user/{id}', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.update'); + })->setParameter('name', 'user.update'); $router->delete('/user/{id}', function ($request, $response, $args) { return $response->withBody((new StreamFactory())->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? ''))); - })->setParameter('name','user.delete'); + })->setParameter('name', 'user.delete'); } } diff --git a/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php b/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php index 367c7df27..c21bacdef 100644 --- a/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php @@ -21,6 +21,5 @@ public function routerMatching404Provider() protected function definitions($router) { - } } diff --git a/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php index 223392a3d..123ee9a52 100644 --- a/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php @@ -18,7 +18,7 @@ public function routerMatchingProvider(): array public function routerMatching404Provider() { return [ - ['GET', '/a'] + ['GET', '/a'], ]; } From c02182f6a738a138614a72c31f2d35c2cbd2cd21 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Thu, 25 Aug 2016 19:41:01 +0200 Subject: [PATCH 18/20] adding psr-17 --- composer.json | 1 + phpunit.xml.dist | 3 + src/Viserio/Http/RequestFactory.php | 24 ------ src/Viserio/Http/ResponseFactory.php | 22 ------ src/Viserio/Http/StreamFactory.php | 54 ------------- src/Viserio/Http/UploadedFileFactory.php | 24 ------ src/Viserio/Http/UriFactory.php | 19 ----- src/Viserio/Http/composer.json | 3 +- src/Viserio/HttpFactory/LICENSE | 21 +++++ src/Viserio/HttpFactory/README.md | 33 ++++++++ src/Viserio/HttpFactory/RequestFactory.php | 20 +++++ src/Viserio/HttpFactory/ResponseFactory.php | 19 +++++ .../ServerRequestFactory.php | 12 ++- src/Viserio/HttpFactory/StreamFactory.php | 19 +++++ .../HttpFactory/Tests/RequestFactoryTest.php | 57 +++++++++++++ .../HttpFactory/Tests/ResponseFactoryTest.php | 43 ++++++++++ .../Tests/ServerRequestFactoryTest.php | 72 +++++++++++++++++ .../HttpFactory/Tests/StreamFactoryTest.php | 29 +++++++ .../Tests/UploadedFileFactoryTest.php | 79 +++++++++++++++++++ .../HttpFactory/UploadedFileFactory.php | 51 ++++++++++++ src/Viserio/HttpFactory/UriFactory.php | 17 ++++ src/Viserio/HttpFactory/composer.json | 52 ++++++++++++ src/Viserio/HttpFactory/phpunit.xml.dist | 40 ++++++++++ .../Middlewares/NotAllowedMiddleware.php | 2 - .../Middlewares/NotFoundMiddleware.php | 2 - 25 files changed, 563 insertions(+), 155 deletions(-) delete mode 100644 src/Viserio/Http/RequestFactory.php delete mode 100644 src/Viserio/Http/ResponseFactory.php delete mode 100644 src/Viserio/Http/StreamFactory.php delete mode 100644 src/Viserio/Http/UploadedFileFactory.php delete mode 100644 src/Viserio/Http/UriFactory.php create mode 100644 src/Viserio/HttpFactory/LICENSE create mode 100644 src/Viserio/HttpFactory/README.md create mode 100644 src/Viserio/HttpFactory/RequestFactory.php create mode 100644 src/Viserio/HttpFactory/ResponseFactory.php rename src/Viserio/{Http => HttpFactory}/ServerRequestFactory.php (84%) create mode 100644 src/Viserio/HttpFactory/StreamFactory.php create mode 100644 src/Viserio/HttpFactory/Tests/RequestFactoryTest.php create mode 100644 src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php create mode 100644 src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php create mode 100644 src/Viserio/HttpFactory/Tests/StreamFactoryTest.php create mode 100644 src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php create mode 100644 src/Viserio/HttpFactory/UploadedFileFactory.php create mode 100644 src/Viserio/HttpFactory/UriFactory.php create mode 100644 src/Viserio/HttpFactory/composer.json create mode 100644 src/Viserio/HttpFactory/phpunit.xml.dist diff --git a/composer.json b/composer.json index 34ea12780..6b6728650 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "danielstjules/stringy" : "^2.3", "defuse/php-encryption" : "^2.0", "filp/whoops" : "^2.0", + "http-interop/http-factory" : "^0.1", "league/flysystem" : "^1.0", "jeremeamia/SuperClosure" : "^2.2", "monolog/monolog" : "^1.17", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4279064dc..65149ba0b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -72,6 +72,9 @@ ./src/Viserio/Http/Tests + + ./src/Viserio/HttpFactory/Tests + ./src/Viserio/Log/Tests diff --git a/src/Viserio/Http/RequestFactory.php b/src/Viserio/Http/RequestFactory.php deleted file mode 100644 index d7398497c..000000000 --- a/src/Viserio/Http/RequestFactory.php +++ /dev/null @@ -1,24 +0,0 @@ - **Note:** If you want to build an application using Narrowspark, visit the main [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark). + +## Contributing + +Issues for this package shall be posted on [Narrowspark framework issues](http://github.com/narrowspark/framework/issues). +Thank you for considering contributing to the Narrowspark framework! The contribution guide can be found in the [Narrowspark documentation](http://narrowspark.de/docs/contributions). + +## Installation + +Use [Composer](https://getcomposer.org/) to install this package: + +```sh +composer require viserio/http +``` + +## Official Documentation + +Documentation for the framework can be found on the [Narrowspark website](http://narrowspark.de/docs). + +### License + +The Narrowspark framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/src/Viserio/HttpFactory/RequestFactory.php b/src/Viserio/HttpFactory/RequestFactory.php new file mode 100644 index 000000000..ad68d0a9d --- /dev/null +++ b/src/Viserio/HttpFactory/RequestFactory.php @@ -0,0 +1,20 @@ +factory = new RequestFactory(); + } + + public function dataMethods() + { + return [ + ['GET'], + ['POST'], + ['PUT'], + ['DELETE'], + ['OPTIONS'], + ['HEAD'], + ]; + } + + /** + * @dataProvider dataMethods + */ + public function testCreateRequest($method) + { + $uri = 'http://example.com/'; + $request = $this->factory->createRequest($method, $uri); + + $this->assertRequest($request, $method, $uri); + } + + public function testCreateRequestWithUri() + { + $uriFactory = new UriFactory(); + $method = 'GET'; + $uri = 'http://example.com/'; + $request = $this->factory->createRequest($method, $uriFactory->createUri($uri)); + + $this->assertRequest($request, $method, $uri); + } + + private function assertRequest($request, $method, $uri) + { + $this->assertInstanceOf(RequestInterface::class, $request); + $this->assertSame($method, $request->getMethod()); + $this->assertSame($uri, (string) $request->getUri()); + } +} diff --git a/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php b/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php new file mode 100644 index 000000000..ecc946cf4 --- /dev/null +++ b/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php @@ -0,0 +1,43 @@ +factory = new ResponseFactory(); + } + + public function dataCodes() + { + return [ + [200], + [301], + [404], + [500], + ]; + } + + /** + * @dataProvider dataCodes + */ + public function testCreateResponse($code) + { + $response = $this->factory->createResponse($code); + + $this->assertResponse($response, $code); + } + + private function assertResponse($response, $code) + { + $this->assertInstanceOf(ResponseInterface::class, $response); + + $this->assertSame($code, $response->getStatusCode()); + } +} diff --git a/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php b/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php new file mode 100644 index 000000000..8201cf24b --- /dev/null +++ b/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php @@ -0,0 +1,72 @@ +factory = new ServerRequestFactory(); + } + + public function dataMethods() + { + return [ + ['GET'], + ['POST'], + ['PUT'], + ['DELETE'], + ['OPTIONS'], + ['HEAD'], + ]; + } + + /** + * @dataProvider dataMethods + */ + public function testCreateServerRequest($method) + { + $uri = 'http://example.com/'; + $request = $this->factory->createServerRequest($method, $uri); + + $this->assertServerRequest($request, $method, $uri); + } + + public function testCreateServerRequestWithUri() + { + $uriFactory = new UriFactory(); + $method = 'GET'; + $uri = 'http://example.com/'; + $request = $this->factory->createServerRequest($method, $uriFactory->createUri($uri)); + + $this->assertServerRequest($request, $method, $uri); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testCreateServerRequestFromGlobals() + { + $_SERVER['REQUEST_METHOD'] = $method = 'GET'; + $_SERVER['QUERY_STRING'] = $qs = 'foo=1&bar=true'; + $_SERVER['HTTP_HOST'] = $host = 'example.org'; + $uri = "http://{$host}/?$qs"; + $request = $this->factory->createServerRequestFromGlobals(); + + $this->assertServerRequest($request, $method, $uri); + } + + private function assertServerRequest($request, $method, $uri) + { + $this->assertInstanceOf(ServerRequestInterface::class, $request); + $this->assertSame($method, $request->getMethod()); + $this->assertSame($uri, (string) $request->getUri()); + } +} diff --git a/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php b/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php new file mode 100644 index 000000000..adf441260 --- /dev/null +++ b/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php @@ -0,0 +1,29 @@ +factory = new StreamFactory(); + } + + public function testCreateStream() + { + $resource = tmpfile(); + $stream = $this->factory->createStream($resource); + $this->assertStream($stream, ''); + } + + private function assertStream($stream, $content) + { + $this->assertInstanceOf(StreamInterface::class, $stream); + $this->assertSame($content, (string) $stream); + } +} diff --git a/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php b/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php new file mode 100644 index 000000000..50771fb3d --- /dev/null +++ b/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php @@ -0,0 +1,79 @@ +factory = new UploadedFileFactory(); + } + + public function testCreateUploadedFileWithString() + { + $filename = tempnam(sys_get_temp_dir(), 'http-factory-test'); + $content = 'i made this!'; + $size = strlen($content); + + file_put_contents($filename, $content); + + $file = $this->factory->createUploadedFile($filename); + + $this->assertUploadedFile($file, $content, $size); + + unlink($filename); + } + + public function testCreateUploadedFileWithClientFilenameAndMediaType() + { + $tmpfname = tempnam('/tmp', 'foo'); + $upload = fopen($tmpfname, 'w+'); + $content = 'this is your capitan speaking'; + $error = \UPLOAD_ERR_OK; + $clientFilename = 'test.txt'; + $clientMediaType = 'text/plain'; + fwrite($upload, $content); + $file = $this->factory->createUploadedFile( + $tmpfname, + strlen($content), + $error, + $clientFilename, + $clientMediaType + ); + + $this->assertUploadedFile($file, $content, null, $error, $clientFilename, $clientMediaType); + } + + public function testCreateUploadedFileWithError() + { + $upload = tmpfile(); + $error = \UPLOAD_ERR_NO_FILE; + $file = $this->factory->createUploadedFile($upload, null, $error); + + // Cannot use assertUploadedFile() here because the error prevents + // fetching the content stream. + $this->assertInstanceOf(UploadedFileInterface::class, $file); + $this->assertSame($error, $file->getError()); + } + + private function assertUploadedFile( + $file, + $content, + $size = null, + $error = null, + $clientFilename = null, + $clientMediaType = null + ) { + $this->assertInstanceOf(UploadedFileInterface::class, $file); + $this->assertSame($content, (string) $file->getStream()); + $this->assertSame($size ?: strlen($content), $file->getSize()); + $this->assertSame($error ?: UPLOAD_ERR_OK, $file->getError()); + $this->assertSame($clientFilename, $file->getClientFilename()); + $this->assertSame($clientMediaType, $file->getClientMediaType()); + } +} diff --git a/src/Viserio/HttpFactory/UploadedFileFactory.php b/src/Viserio/HttpFactory/UploadedFileFactory.php new file mode 100644 index 000000000..26624b883 --- /dev/null +++ b/src/Viserio/HttpFactory/UploadedFileFactory.php @@ -0,0 +1,51 @@ +getSize($file, $size), + $error, + $clientFilename, + $clientMediaType + ); + } + + /** + * Detect the Uploaded file size + * + * @param mixed $file + * @param int|null $size + * + * @return int + */ + protected function getSize($file, $size): int + { + if (null !== $size) { + return $size; + } + + if (is_string($file)) { + return filesize($file); + } + + return fstat($file)['size']; + } +} diff --git a/src/Viserio/HttpFactory/UriFactory.php b/src/Viserio/HttpFactory/UriFactory.php new file mode 100644 index 000000000..79bacd035 --- /dev/null +++ b/src/Viserio/HttpFactory/UriFactory.php @@ -0,0 +1,17 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + + diff --git a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php index 6391e09ef..66179986c 100644 --- a/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotAllowedMiddleware.php @@ -34,8 +34,6 @@ public function process( ServerRequestInterface $request, DelegateContract $frame ): ResponseInterface { - $response = $frame->next($request); - throw new MethodNotAllowedException(); } } diff --git a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php index 72e60ac95..db238c574 100644 --- a/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php +++ b/src/Viserio/Routing/Middlewares/NotFoundMiddleware.php @@ -17,8 +17,6 @@ public function process( ServerRequestInterface $request, DelegateContract $frame ): ResponseInterface { - $response = $frame->next($request); - throw new NotFoundException(); } } From a6a79cb71749f307344e197f54f191518459dbd5 Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 28 Aug 2016 22:27:18 +0200 Subject: [PATCH 19/20] added PSR-17 --- build/subsplitt/viserio-split-full.sh | 1 + build/subsplitt/viserio-split.sh | 1 + composer.json | 3 +- src/Viserio/Http/AbstractMessage.php | 14 +- .../Http/Response/RedirectResponse.php | 4 +- src/Viserio/Http/Stream/LazyOpenStream.php | 4 +- src/Viserio/Http/Tests/RequestTest.php | 4 +- src/Viserio/Http/Tests/ResponseTest.php | 13 +- .../Http/Tests/ServerRequestFactoryTest.php | 165 ---------------- .../Tests/Stream/ByteCountingStreamTest.php | 50 ++++- .../Http/Tests/Stream/FnStreamTest.php | 17 +- .../Http/Tests/Stream/LimitStreamTest.php | 72 +++++-- .../Http/Tests/Stream/NoSeekStreamTest.php | 10 +- .../Http/Tests/Stream/PumpStreamTest.php | 37 +++- src/Viserio/Http/Tests/StreamFactoryTest.php | 89 --------- src/Viserio/Http/Tests/StreamTest.php | 13 +- src/Viserio/Http/Tests/UploadedFileTest.php | 33 +++- src/Viserio/Http/Tests/UtilTest.php | 62 ++++-- src/Viserio/HttpFactory/README.md | 4 +- src/Viserio/HttpFactory/RequestFactory.php | 1 + src/Viserio/HttpFactory/ResponseFactory.php | 1 + .../HttpFactory/ServerRequestFactory.php | 7 +- src/Viserio/HttpFactory/StreamFactory.php | 18 +- .../Tests/ServerRequestFactoryTest.php | 178 ++++++++++++++---- .../HttpFactory/Tests/StreamFactoryTest.php | 38 ++++ .../HttpFactory/UploadedFileFactory.php | 3 +- src/Viserio/HttpFactory/UriFactory.php | 2 + .../Fixture/ControllerClosureMiddleware.php | 4 +- .../Routing/Tests/Fixture/FakeMiddleware.php | 4 +- .../Routing/Tests/Fixture/FooMiddleware.php | 2 +- .../RouteTestClosureMiddlewareController.php | 6 +- .../BasicParameterPatternsRouterTest.php | 20 +- .../Tests/Router/BasicRestfulRouterTest.php | 22 +-- .../Router/CommonRouteSegmentRouterTest.php | 16 +- .../ComplexParameterPatternsRouterTest.php | 16 +- .../Tests/Router/ComplexShopRouterTest.php | 84 ++++----- .../Tests/Router/EdgeCasesRouterTest.php | 28 +-- .../Tests/Router/HttpMethodRouterTest.php | 12 +- .../Router/InlineParameterRouterTest.php | 14 +- .../Tests/Router/RootRoutesRouterTest.php | 16 +- .../Tests/Router/RouteRouterBaseTest.php | 4 +- src/Viserio/Routing/composer.json | 2 +- 42 files changed, 599 insertions(+), 495 deletions(-) delete mode 100644 src/Viserio/Http/Tests/ServerRequestFactoryTest.php delete mode 100644 src/Viserio/Http/Tests/StreamFactoryTest.php diff --git a/build/subsplitt/viserio-split-full.sh b/build/subsplitt/viserio-split-full.sh index 39f0056fa..bbd8b12a9 100644 --- a/build/subsplitt/viserio-split-full.sh +++ b/build/subsplitt/viserio-split-full.sh @@ -13,6 +13,7 @@ git subsplit publish src/Viserio/Exception:git@github.com:viserio/exception.git git subsplit publish src/Viserio/Filesystem:git@github.com:viserio/filesystem.git git subsplit publish src/Viserio/Hashing:git@github.com:viserio/hashing.git git subsplit publish src/Viserio/Http:git@github.com:viserio/http.git +git subsplit publish src/Viserio/HttpFactory:git@github.com:viserio/http-factory.git git subsplit publish src/Viserio/Log:git@github.com:viserio/log.git git subsplit publish src/Viserio/Mail:git@github.com:viserio/mail.git git subsplit publish src/Viserio/Middleware:git@github.com:viserio/middleware.git diff --git a/build/subsplitt/viserio-split.sh b/build/subsplitt/viserio-split.sh index f0d925cf7..04e9cd45d 100644 --- a/build/subsplitt/viserio-split.sh +++ b/build/subsplitt/viserio-split.sh @@ -13,6 +13,7 @@ git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Exception git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Filesystem:git@github.com:viserio/filesystem.git git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Hashing:git@github.com:viserio/hashing.git git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Http:git@github.com:viserio/http.git +git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/HttpFactory:git@github.com:viserio/http-factory.git git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Log:git@github.com:viserio/log.git git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Mail:git@github.com:viserio/mail.git git subsplit publish --heads="master 0.10.0-dev" --no-tags src/Viserio/Middleware:git@github.com:viserio/middleware.git diff --git a/composer.json b/composer.json index 6b6728650..bbe400589 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "danielstjules/stringy" : "^2.3", "defuse/php-encryption" : "^2.0", "filp/whoops" : "^2.0", - "http-interop/http-factory" : "^0.1", + "http-interop/http-factory" : "dev-master", "league/flysystem" : "^1.0", "jeremeamia/SuperClosure" : "^2.2", "monolog/monolog" : "^1.17", @@ -83,6 +83,7 @@ "viserio/filessystem" : "self.version", "viserio/hashing" : "self.version", "viserio/http" : "self.version", + "viserio/http-factory" : "self.version", "viserio/log" : "self.version", "viserio/mail" : "self.version", "viserio/middleware" : "self.version", diff --git a/src/Viserio/Http/AbstractMessage.php b/src/Viserio/Http/AbstractMessage.php index 7f3650b74..ad54b3a66 100644 --- a/src/Viserio/Http/AbstractMessage.php +++ b/src/Viserio/Http/AbstractMessage.php @@ -186,7 +186,7 @@ public function withoutHeader($header) public function getBody() { if (! $this->stream) { - $this->stream = (new StreamFactory())->createStreamFromString(''); + $this->stream = new Stream(fopen('php://temp', 'r+')); } return $this->stream; @@ -247,17 +247,23 @@ protected function setHeaders(array $headers) */ protected function createStream($body): StreamInterface { - $stream = new StreamFactory(); $type = gettype($body); if ($body instanceof StreamInterface) { return $body; } elseif (is_string($body)) { - return $stream->createStreamFromString($body); + $stream = fopen('php://temp', 'r+'); + + if ($body !== '') { + fwrite($stream, $body); + fseek($stream, 0); + } + + return new Stream($stream); } elseif ($type === 'NULL') { return new Stream(fopen('php://temp', 'r+')); } elseif ($type === 'resource') { - return $stream->createStreamFromResource($body); + return new Stream($body); } throw new InvalidArgumentException('Invalid resource type: ' . gettype($body)); diff --git a/src/Viserio/Http/Response/RedirectResponse.php b/src/Viserio/Http/Response/RedirectResponse.php index a46d8f594..cdcb034e6 100644 --- a/src/Viserio/Http/Response/RedirectResponse.php +++ b/src/Viserio/Http/Response/RedirectResponse.php @@ -4,8 +4,8 @@ use InvalidArgumentException; use Psr\Http\Message\UriInterface; +use Viserio\Http\Stream; use Viserio\Http\Response; -use Viserio\Http\StreamFactory; class RedirectResponse extends Response { @@ -33,6 +33,6 @@ public function __construct($uri, int $status = 302, array $headers = []) $headers['location'] = [(string) $uri]; - parent::__construct($status, $headers, (new StreamFactory())->createStream()); + parent::__construct($status, $headers, new Stream(fopen('php://temp', 'r+'))); } } diff --git a/src/Viserio/Http/Stream/LazyOpenStream.php b/src/Viserio/Http/Stream/LazyOpenStream.php index 1a17fb7fd..a410994f2 100644 --- a/src/Viserio/Http/Stream/LazyOpenStream.php +++ b/src/Viserio/Http/Stream/LazyOpenStream.php @@ -5,8 +5,8 @@ use Psr\Http\Message\StreamInterface; use Throwable; use UnexpectedValueException; -use Viserio\Http\StreamFactory; use Viserio\Http\Util; +use Viserio\Http\Stream; class LazyOpenStream implements StreamInterface { @@ -188,6 +188,6 @@ public function write($string) */ protected function createStream(): StreamInterface { - return (new StreamFactory())->createStreamFromResource(Util::tryFopen($this->filename, $this->mode)); + return new Stream(Util::tryFopen($this->filename, $this->mode)); } } diff --git a/src/Viserio/Http/Tests/RequestTest.php b/src/Viserio/Http/Tests/RequestTest.php index 83c936337..a34898ef5 100644 --- a/src/Viserio/Http/Tests/RequestTest.php +++ b/src/Viserio/Http/Tests/RequestTest.php @@ -9,7 +9,7 @@ use StdClass; use Viserio\Http\Request; use Viserio\Http\Stream\FnStream; -use Viserio\Http\StreamFactory; +use Viserio\Http\Stream; use Viserio\Http\Uri; class RequestTest extends AbstractMessageTest @@ -151,7 +151,7 @@ public function testConstructorDoesNotReadStreamBody() { $streamIsRead = false; - $body = FnStream::decorate((new StreamFactory())->createStreamFromString(''), [ + $body = FnStream::decorate(new Stream(fopen('php://temp', 'r+')), [ '__toString' => function () use (&$streamIsRead) { $streamIsRead = true; diff --git a/src/Viserio/Http/Tests/ResponseTest.php b/src/Viserio/Http/Tests/ResponseTest.php index 4b8527716..b27ad1ca4 100644 --- a/src/Viserio/Http/Tests/ResponseTest.php +++ b/src/Viserio/Http/Tests/ResponseTest.php @@ -5,8 +5,8 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; use Viserio\Http\Response; +use Viserio\Http\Stream; use Viserio\Http\Stream\FnStream; -use Viserio\Http\StreamFactory; class ResponseTest extends AbstractMessageTest { @@ -96,7 +96,7 @@ public function testCanConstructWithStatusCode() public function testConstructorDoesNotReadStreamBody() { $streamIsRead = false; - $body = FnStream::decorate((new StreamFactory())->createStreamFromString(''), [ + $body = FnStream::decorate(new Stream(fopen('php://temp', 'r+')), [ '__toString' => function () use (&$streamIsRead) { $streamIsRead = true; @@ -193,8 +193,13 @@ public function testSameInstanceWhenSameProtocol() public function testWithBody() { - $body = (new StreamFactory())->createStreamFromString('0'); - $response = (new Response())->withBody($body); + $body = '0'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $response = (new Response())->withBody(new Stream($stream)); $this->assertInstanceOf(StreamInterface::class, $response->getBody()); $this->assertSame('0', (string) $response->getBody()); diff --git a/src/Viserio/Http/Tests/ServerRequestFactoryTest.php b/src/Viserio/Http/Tests/ServerRequestFactoryTest.php deleted file mode 100644 index 2b52cde03..000000000 --- a/src/Viserio/Http/Tests/ServerRequestFactoryTest.php +++ /dev/null @@ -1,165 +0,0 @@ - '/blog/article.php', - 'GATEWAY_INTERFACE' => 'CGI/1.1', - 'SERVER_ADDR' => 'Server IP: 127.0.0.1', - 'SERVER_NAME' => 'www.narrowspark.com', - 'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/7.0.7', - 'SERVER_PROTOCOL' => 'HTTP/1.0', - 'REQUEST_METHOD' => 'POST', - 'REQUEST_TIME' => 'Request start time: 1280149029', - 'QUERY_STRING' => 'id=10&user=foo', - 'DOCUMENT_ROOT' => '/path/to/your/server/root/', - 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'HTTP_ACCEPT_ENCODING' => 'gzip,deflate', - 'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5', - 'HTTP_CONNECTION' => 'keep-alive', - 'HTTP_HOST' => 'www.narrowspark.com', - 'HTTP_REFERER' => 'http://previous.url.com', - 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)', - 'REMOTE_ADDR' => '193.60.168.69', - 'REMOTE_HOST' => 'Client server\'s host name', - 'REMOTE_PORT' => '5390', - 'SCRIPT_FILENAME' => '/path/to/this/script.php', - 'SERVER_ADMIN' => 'webmaster@narrowspark.com', - 'SERVER_PORT' => '80', - 'SERVER_SIGNATURE' => 'Version signature: 5.123', - 'SCRIPT_NAME' => '/blog/article.php', - 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', - ]; - - return [ - 'Normal request' => [ - 'http://www.narrowspark.com/blog/article.php?id=10&user=foo', - $server, - ], - 'Secure request' => [ - 'https://www.narrowspark.com/blog/article.php?id=10&user=foo', - array_merge($server, ['HTTPS' => 'on', 'SERVER_PORT' => '443']), - ], - 'No HTTPS param' => [ - 'http://www.narrowspark.com/blog/article.php?id=10&user=foo', - $server, - ], - 'HTTP_HOST missing' => [ - 'http://www.narrowspark.com/blog/article.php?id=10&user=foo', - array_merge($server, ['HTTP_HOST' => null]), - ], - 'No query String' => [ - 'http://www.narrowspark.com/blog/article.php', - array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']), - ], - 'Different port' => [ - 'http://www.narrowspark.com:8324/blog/article.php?id=10&user=foo', - array_merge($server, ['SERVER_PORT' => '8324']), - ], - 'Empty server variable' => [ - '', - [], - ], - ]; - } - - /** - * @dataProvider dataGetUriFromGlobals - */ - public function testGetUriFromGlobals($expected, $serverParams) - { - $_SERVER = $serverParams; - $serverRequest = (new ServerRequestFactory())->createServerRequestFromGlobals(); - - $this->assertEquals(new Uri($expected), $serverRequest->getUri()); - } - - public function testFromGlobals() - { - $_SERVER = [ - 'PHP_SELF' => '/blog/article.php', - 'GATEWAY_INTERFACE' => 'CGI/1.1', - 'SERVER_ADDR' => 'Server IP: 127.0.0.1', - 'SERVER_NAME' => 'www.narrowspark.com', - 'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/7.0.7', - 'SERVER_PROTOCOL' => 'HTTP/1.0', - 'REQUEST_METHOD' => 'POST', - 'REQUEST_TIME' => 'Request start time: 1280149029', - 'QUERY_STRING' => 'id=10&user=foo', - 'DOCUMENT_ROOT' => '/path/to/your/server/root/', - 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'HTTP_ACCEPT_ENCODING' => 'gzip,deflate', - 'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5', - 'HTTP_CONNECTION' => 'keep-alive', - 'HTTP_HOST' => 'www.narrowspark.com', - 'HTTP_REFERER' => 'http://previous.url.com', - 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)', - 'HTTPS' => '1', - 'REMOTE_ADDR' => '193.60.168.69', - 'REMOTE_HOST' => 'Client server\'s host name', - 'REMOTE_PORT' => '5390', - 'SCRIPT_FILENAME' => '/path/to/this/script.php', - 'SERVER_ADMIN' => 'webmaster@narrowspark.com', - 'SERVER_PORT' => '80', - 'SERVER_SIGNATURE' => 'Version signature: 5.123', - 'SCRIPT_NAME' => '/blog/article.php', - 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', - ]; - $_COOKIE = [ - 'logged-in' => 'yes!', - ]; - $_POST = [ - 'name' => 'Pesho', - 'email' => 'pesho@example.com', - ]; - $_GET = [ - 'id' => 10, - 'user' => 'foo', - ]; - $_FILES = [ - 'file' => [ - 'name' => 'MyFile.txt', - 'type' => 'text/plain', - 'tmp_name' => '/tmp/php/php1h4j1o', - 'error' => UPLOAD_ERR_OK, - 'size' => 123, - ], - ]; - - $server = (new ServerRequestFactory())->createServerRequestFromGlobals(); - - $this->assertEquals('POST', $server->getMethod()); - $this->assertEquals(['Host' => ['www.narrowspark.com']], $server->getHeaders()); - $this->assertEquals('', (string) $server->getBody()); - $this->assertEquals('1.0', $server->getProtocolVersion()); - $this->assertEquals($_COOKIE, $server->getCookieParams()); - $this->assertEquals($_POST, $server->getParsedBody()); - $this->assertEquals($_GET, $server->getQueryParams()); - $this->assertEquals( - new Uri('http://www.narrowspark.com/blog/article.php?id=10&user=foo'), - $server->getUri() - ); - - $expectedFiles = [ - 'file' => new UploadedFile( - '/tmp/php/php1h4j1o', - 123, - UPLOAD_ERR_OK, - 'MyFile.txt', - 'text/plain' - ), - ]; - - $this->assertEquals($expectedFiles, $server->getUploadedFiles()); - } -} diff --git a/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php b/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php index 9e96c6c57..82eceed40 100644 --- a/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php @@ -3,7 +3,7 @@ namespace Viserio\Http\Tests\Stream; use Viserio\Http\Stream\ByteCountingStream; -use Viserio\Http\StreamFactory; +use Viserio\Http\Stream; class ByteCountingStreamTest extends \PHPUnit_Framework_TestCase { @@ -13,7 +13,13 @@ class ByteCountingStreamTest extends \PHPUnit_Framework_TestCase */ public function testEnsureNonNegativeByteCount() { - new ByteCountingStream((new StreamFactory())->createStreamFromString('testing'), -2); + $body = 'testing'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + new ByteCountingStream(new Stream($stream), -2); } /** @@ -22,19 +28,37 @@ public function testEnsureNonNegativeByteCount() */ public function testEnsureValidByteCountNumber() { - new ByteCountingStream((new StreamFactory())->createStreamFromString('testing'), 10); + $body = 'testing'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + new ByteCountingStream(new Stream($stream), 10); } public function testByteCountingReadWhenAvailable() { - $testStream = new ByteCountingStream((new StreamFactory())->createStreamFromString('foo bar test'), 8); + $body = 'foo bar test'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $testStream = new ByteCountingStream(new Stream($stream), 8); $this->assertEquals('foo ', $testStream->read(4)); $this->assertEquals('bar ', $testStream->read(4)); $this->assertEquals('', $testStream->read(4)); + $body = 'testing'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + $testStream->close(); - $testStream = new ByteCountingStream((new StreamFactory())->createStreamFromString('testing'), 5); + $testStream = new ByteCountingStream(new Stream($stream), 5); $testStream->seek(4); $this->assertEquals('ing', $testStream->read(5)); @@ -48,7 +72,13 @@ public function testByteCountingReadWhenAvailable() */ public function testEnsureStopReadWhenHitEof() { - $testStream = new ByteCountingStream((new StreamFactory())->createStreamFromString('abc'), 3); + $body = 'abc'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $testStream = new ByteCountingStream(new Stream($stream), 3); $testStream->seek(3); $testStream->read(3); } @@ -59,7 +89,13 @@ public function testEnsureStopReadWhenHitEof() */ public function testEnsureReadUnclosedStream() { - $body = (new StreamFactory())->createStreamFromString('closed'); + $body = 'closed'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $body = new Stream($stream); $closedStream = new ByteCountingStream($body, 5); $body->close(); $closedStream->read(3); diff --git a/src/Viserio/Http/Tests/Stream/FnStreamTest.php b/src/Viserio/Http/Tests/Stream/FnStreamTest.php index 0acbdcc4f..175265a85 100644 --- a/src/Viserio/Http/Tests/Stream/FnStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/FnStreamTest.php @@ -3,7 +3,7 @@ namespace Viserio\Http\Tests\Stream; use Viserio\Http\Stream\FnStream; -use Viserio\Http\StreamFactory; +use Viserio\Http\Stream; class FnStreamTest extends \PHPUnit_Framework_TestCase { @@ -51,7 +51,12 @@ public function doesNotRequireClose() public function testDecoratesStream() { - $stream1 = (new StreamFactory())->createStreamFromString('foo'); + $body = 'foo'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + $stream1 = new Stream($stream); $stream2 = FnStream::decorate($stream1, []); $this->assertEquals(3, $stream2->getSize()); @@ -80,7 +85,13 @@ public function testDecoratesWithCustomizations() { $called = false; - $stream1 = (new StreamFactory())->createStreamFromString('foo'); + $body = 'foo'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream1 = new Stream($stream); $stream2 = FnStream::decorate($stream1, [ 'read' => function ($len) use (&$called, $stream1) { $called = true; diff --git a/src/Viserio/Http/Tests/Stream/LimitStreamTest.php b/src/Viserio/Http/Tests/Stream/LimitStreamTest.php index 84f25582b..30de6954d 100644 --- a/src/Viserio/Http/Tests/Stream/LimitStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/LimitStreamTest.php @@ -7,7 +7,6 @@ use Viserio\Http\Stream\FnStream; use Viserio\Http\Stream\LimitStream; use Viserio\Http\Stream\NoSeekStream; -use Viserio\Http\StreamFactory; class LimitStreamTest extends \PHPUnit_Framework_TestCase { @@ -19,13 +18,19 @@ class LimitStreamTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->decorated = (new StreamFactory())->createStreamFromResource(fopen(__FILE__, 'r')); + $this->decorated = new Stream(fopen(__FILE__, 'r')); $this->body = new LimitStream($this->decorated, 10, 3); } public function testReturnsSubset() { - $body = new LimitStream((new StreamFactory())->createStreamFromString('foo'), -1, 1); + $body = 'foo'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $body = new LimitStream(new Stream($stream), -1, 1); $this->assertEquals('oo', (string) $body); $this->assertTrue($body->eof()); @@ -40,8 +45,13 @@ public function testReturnsSubset() public function testReturnsSubsetWhenCastToString() { - $body = (new StreamFactory())->createStreamFromString('foo_baz_bar'); - $limited = new LimitStream($body, 3, 4); + $body = 'foo_baz_bar'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $limited = new LimitStream(new Stream($stream), 3, 4); $this->assertEquals('baz', (string) $limited); } @@ -52,21 +62,31 @@ public function testReturnsSubsetWhenCastToString() */ public function testEnsuresPositionCanBeekSeekedTo() { - new LimitStream((new StreamFactory())->createStreamFromString(''), 0, 10); + new LimitStream(new Stream(fopen('php://temp', 'r+')), 0, 10); } public function testReturnsSubsetOfEmptyBodyWhenCastToString() { - $body = (new StreamFactory())->createStreamFromString('01234567891234'); - $limited = new LimitStream($body, 0, 10); + $body = '01234567891234'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $limited = new LimitStream(new Stream($stream), 0, 10); $this->assertEquals('', (string) $limited); } public function testReturnsSpecificSubsetOBodyWhenCastToString() { - $body = (new StreamFactory())->createStreamFromString('0123456789abcdef'); - $limited = new LimitStream($body, 3, 10); + $body = '0123456789abcdef'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $limited = new LimitStream(new Stream($stream), 3, 10); $this->assertEquals('abc', (string) $limited); } @@ -126,7 +146,13 @@ public function testReadsOnlySubsetOfData() */ public function testThrowsWhenCurrentGreaterThanOffsetSeek() { - $stream1 = (new StreamFactory())->createStreamFromString('foo_bar'); + $body = 'foo_bar'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream1 = new Stream($stream); $stream2 = new NoSeekStream($stream1); $stream3 = new LimitStream($stream2); @@ -136,7 +162,13 @@ public function testThrowsWhenCurrentGreaterThanOffsetSeek() public function testCanGetContentsWithoutSeeking() { - $stream1 = (new StreamFactory())->createStreamFromString('foo_bar'); + $body = 'foo_bar'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream1 = new Stream($stream); $stream2 = new NoSeekStream($stream1); $stream3 = new LimitStream($stream2); @@ -159,7 +191,13 @@ public function testContentLengthIsBounded() public function testGetContentsIsBasedOnSubset() { - $body = new LimitStream((new StreamFactory())->createStreamFromString('foobazbar'), 3, 3); + $body = 'foobazbar'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $body = new LimitStream(new Stream($stream), 3, 3); $this->assertEquals('baz', $body->getContents()); } @@ -180,7 +218,13 @@ public function testReturnsNullIfSizeCannotBeDetermined() public function testLengthLessOffsetWhenNoLimitSize() { - $a = (new StreamFactory())->createStreamFromString('foo_bar'); + $body = 'foo_bar'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $a = new Stream($stream); $b = new LimitStream($a, -1, 4); $this->assertEquals(3, $b->getSize()); diff --git a/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php b/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php index 55b756983..cd791cc7c 100644 --- a/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php @@ -4,7 +4,7 @@ use Psr\Http\Message\StreamInterface; use Viserio\Http\Stream\NoSeekStream; -use Viserio\Http\StreamFactory; +use Viserio\Http\Stream; class NoSeekStreamTest extends \PHPUnit_Framework_TestCase { @@ -32,7 +32,13 @@ public function testCannotSeek() */ public function testHandlesClose() { - $s = (new StreamFactory())->createStreamFromString('foo'); + $body = 'foo'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s = new Stream($stream); $wrapped = new NoSeekStream($s); $wrapped->close(); $wrapped->write('foo'); diff --git a/src/Viserio/Http/Tests/Stream/PumpStreamTest.php b/src/Viserio/Http/Tests/Stream/PumpStreamTest.php index ee0810e05..57d12ee02 100644 --- a/src/Viserio/Http/Tests/Stream/PumpStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/PumpStreamTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); namespace Viserio\Http\Tests\Stream; +use ArrayIterator; use RuntimeException; use Viserio\Http\Stream\LimitStream; use Viserio\Http\Stream\PumpStream; -use Viserio\Http\StreamFactory; class PumpStreamTest extends \PHPUnit_Framework_TestCase { @@ -24,7 +24,7 @@ public function testHasMetadataAndSize() public function testCanReadFromCallable() { - $pump = (new StreamFactory())->createStreamFromCallback(function ($size) { + $pump = new PumpStream(function ($size) { return 'a'; }); @@ -38,7 +38,7 @@ public function testStoresExcessDataInBuffer() { $called = []; - $pump = (new StreamFactory())->createStreamFromCallback(function ($size) use (&$called) { + $pump = new PumpStream(function ($size) use (&$called) { $called[] = $size; return 'abcdef'; @@ -53,7 +53,7 @@ public function testStoresExcessDataInBuffer() public function testInifiniteStreamWrappedInLimitStream() { - $pump = (new StreamFactory())->createStreamFromCallback(function () { + $pump = new PumpStream(function () { return 'a'; }); $s = new LimitStream($pump, 5); @@ -63,7 +63,7 @@ public function testInifiniteStreamWrappedInLimitStream() public function testDescribesCapabilities() { - $pump = (new StreamFactory())->createStreamFromCallback(function () { + $pump = new PumpStream(function () { }); $this->assertTrue($pump->isReadable()); @@ -84,4 +84,31 @@ public function testDescribesCapabilities() } catch (RuntimeException $e) { } } + + public function testCanCreateCallableBasedStream() + { + $resource = new ArrayIterator(['foo', 'bar', '123']); + + $stream = new PumpStream(function () use ($resource) { + if (! $resource->valid()) { + return false; + } + + $result = $resource->current(); + $resource->next(); + + return $result; + }); + + $this->assertInstanceOf(PumpStream::class, $stream); + $this->assertEquals('foo', $stream->read(3)); + $this->assertFalse($stream->eof()); + $this->assertEquals('b', $stream->read(1)); + $this->assertEquals('a', $stream->read(1)); + $this->assertEquals('r12', $stream->read(3)); + $this->assertFalse($stream->eof()); + $this->assertEquals('3', $stream->getContents()); + $this->assertTrue($stream->eof()); + $this->assertEquals(9, $stream->tell()); + } } diff --git a/src/Viserio/Http/Tests/StreamFactoryTest.php b/src/Viserio/Http/Tests/StreamFactoryTest.php deleted file mode 100644 index c14e1b875..000000000 --- a/src/Viserio/Http/Tests/StreamFactoryTest.php +++ /dev/null @@ -1,89 +0,0 @@ -createStreamFromResource($resource); - - $this->assertEquals(10, $stream->tell()); - - $stream->close(); - } - - public function testCreatesWithFactory() - { - $streamFactory = new StreamFactory(); - $stream = $streamFactory->createStreamFromString('foo'); - - $this->assertInstanceOf(Stream::class, $stream); - $this->assertEquals('foo', $stream->getContents()); - - $stream->close(); - } - - public function testFactoryCreatesFromEmptyString() - { - $streamFactory = new StreamFactory(); - $this->assertInstanceOf(Stream::class, $streamFactory->createStream()); - } - - public function testFactoryCreatesFromResource() - { - $resource = fopen(__FILE__, 'r'); - $streamFactory = new StreamFactory(); - $stream = $streamFactory->createStreamFromResource($resource); - - $this->assertInstanceOf(Stream::class, $stream); - $this->assertSame(file_get_contents(__FILE__), (string) $stream); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid resource type: string. - */ - public function testFactoryCreatesFromResourceToThorwException() - { - $streamFactory = new StreamFactory(); - $stream = $streamFactory->createStreamFromResource('foo'); - } - - public function testCanCreateCallableBasedStream() - { - $resource = new ArrayIterator(['foo', 'bar', '123']); - - $streamFactory = new StreamFactory(); - $stream = $streamFactory->createStreamFromCallback(function () use ($resource) { - if (! $resource->valid()) { - return false; - } - - $result = $resource->current(); - $resource->next(); - - return $result; - }); - - $this->assertInstanceOf(PumpStream::class, $stream); - $this->assertEquals('foo', $stream->read(3)); - $this->assertFalse($stream->eof()); - $this->assertEquals('b', $stream->read(1)); - $this->assertEquals('a', $stream->read(1)); - $this->assertEquals('r12', $stream->read(3)); - $this->assertFalse($stream->eof()); - $this->assertEquals('3', $stream->getContents()); - $this->assertTrue($stream->eof()); - $this->assertEquals(9, $stream->tell()); - } -} diff --git a/src/Viserio/Http/Tests/StreamTest.php b/src/Viserio/Http/Tests/StreamTest.php index 51b3c0d1c..8d0426410 100644 --- a/src/Viserio/Http/Tests/StreamTest.php +++ b/src/Viserio/Http/Tests/StreamTest.php @@ -5,7 +5,6 @@ use Exception; use Viserio\Http\Stream; use Viserio\Http\Stream\NoSeekStream; -use Viserio\Http\StreamFactory; class StreamTest extends \PHPUnit_Framework_TestCase { @@ -179,9 +178,15 @@ public function testCloseClearProperties() public function testDoesNotThrowInToString() { - $s = (new StreamFactory())->createStreamFromString('foo'); - $s = new NoSeekStream($s); + $body = 'foo'; + $stream = fopen('php://temp', 'r+'); - $this->assertEquals('foo', (string) $s); + fwrite($stream, $body); + fseek($stream, 0); + + $stream = new Stream($stream); + $stream = new NoSeekStream($stream); + + $this->assertEquals('foo', (string) $stream); } } diff --git a/src/Viserio/Http/Tests/UploadedFileTest.php b/src/Viserio/Http/Tests/UploadedFileTest.php index a05ea9e42..2f00f9653 100644 --- a/src/Viserio/Http/Tests/UploadedFileTest.php +++ b/src/Viserio/Http/Tests/UploadedFileTest.php @@ -4,7 +4,6 @@ use ReflectionProperty; use Viserio\Http\Stream; -use Viserio\Http\StreamFactory; use Viserio\Http\UploadedFile; class UploadedFileTest extends \PHPUnit_Framework_TestCase @@ -158,7 +157,13 @@ public function testGetStreamReturnsStreamForFile() public function testSuccessful() { - $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); + $body = 'Foo bar!'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream = new Stream($stream); $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain'); $this->assertEquals($stream->getSize(), $upload->getSize()); @@ -194,7 +199,13 @@ public function invalidMovePaths() */ public function testMoveRaisesExceptionForInvalidPath($path) { - $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); + $body = 'Foo bar!'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream = new Stream($stream); $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); $this->cleanup[] = $path; @@ -208,7 +219,13 @@ public function testMoveRaisesExceptionForInvalidPath($path) */ public function testMoveCannotBeCalledMoreThanOnce() { - $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); + $body = 'Foo bar!'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream = new Stream($stream); $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); @@ -226,7 +243,13 @@ public function testMoveCannotBeCalledMoreThanOnce() */ public function testCannotRetrieveStreamAfterMove() { - $stream = (new StreamFactory())->createStreamFromString('Foo bar!'); + $body = 'Foo bar!'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $stream = new Stream($stream); $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); diff --git a/src/Viserio/Http/Tests/UtilTest.php b/src/Viserio/Http/Tests/UtilTest.php index 127ff390f..87c493a4c 100644 --- a/src/Viserio/Http/Tests/UtilTest.php +++ b/src/Viserio/Http/Tests/UtilTest.php @@ -3,7 +3,7 @@ namespace Viserio\Http\Tests; use Viserio\Http\Stream\FnStream; -use Viserio\Http\StreamFactory; +use Viserio\Http\Stream; use Viserio\Http\UploadedFile; use Viserio\Http\Util; @@ -11,7 +11,13 @@ class UtilTest extends \PHPUnit_Framework_TestCase { public function testCopiesToString() { - $s = (new StreamFactory())->createStreamFromString('foobaz'); + $body = 'foobaz'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s = new Stream($stream); $this->assertEquals('foobaz', Util::copyToString($s)); $s->seek(0); @@ -22,7 +28,13 @@ public function testCopiesToString() public function testCopiesToStringStopsWhenReadFails() { - $s1 = (new StreamFactory())->createStreamFromString('foobaz'); + $body = 'foobaz'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s1 = new Stream($stream); $s1 = FnStream::decorate($s1, [ 'read' => function () { return ''; @@ -35,12 +47,18 @@ public function testCopiesToStringStopsWhenReadFails() public function testCopiesToStream() { - $s1 = (new StreamFactory())->createStreamFromString('foobaz'); - $s2 = (new StreamFactory())->createStreamFromString(''); + $body = 'foobaz'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s1 = new Stream($stream); + $s2 = new Stream(fopen('php://temp', 'r+')); Util::copyToStream($s1, $s2); $this->assertEquals('foobaz', (string) $s2); - $s2 = (new StreamFactory())->createStreamFromString(''); + $s2 = new Stream(fopen('php://temp', 'r+')); $s1->seek(0); Util::copyToStream($s1, $s2, 3); @@ -65,7 +83,7 @@ public function testCopyToStreamReadsInChunksInsteadOfAllInMemory() }, ]); - $s2 = (new StreamFactory())->createStreamFromString(''); + $s2 = new Stream(fopen('php://temp', 'r+')); Util::copyToStream($s1, $s2, 16394); $s2->seek(0); @@ -78,8 +96,14 @@ public function testCopyToStreamReadsInChunksInsteadOfAllInMemory() public function testStopsCopyToStreamWhenWriteFails() { - $s1 = (new StreamFactory())->createStreamFromString('foobaz'); - $s2 = (new StreamFactory())->createStreamFromString(''); + $body = 'foobaz'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s1 = new Stream($stream); + $s2 = new Stream(fopen('php://temp', 'r+')); $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); @@ -90,8 +114,14 @@ public function testStopsCopyToStreamWhenWriteFails() public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() { - $s1 = (new StreamFactory())->createStreamFromString('foobaz'); - $s2 = (new StreamFactory())->createStreamFromString(''); + $body = 'foobaz'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s1 = new Stream($stream); + $s2 = new Stream(fopen('php://temp', 'r+')); $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); @@ -102,11 +132,17 @@ public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() public function testStopsCopyToSteamWhenReadFailsWithMaxLen() { - $s1 = (new StreamFactory())->createStreamFromString('foobaz'); + $body = 'foobaz'; + $stream = fopen('php://temp', 'r+'); + + fwrite($stream, $body); + fseek($stream, 0); + + $s1 = new Stream($stream); $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]); - $s2 = (new StreamFactory())->createStreamFromString(''); + $s2 = new Stream(fopen('php://temp', 'r+')); Util::copyToStream($s1, $s2, 10); $this->assertEquals('', (string) $s2); diff --git a/src/Viserio/HttpFactory/README.md b/src/Viserio/HttpFactory/README.md index 6e7569ceb..fe000074d 100644 --- a/src/Viserio/HttpFactory/README.md +++ b/src/Viserio/HttpFactory/README.md @@ -1,4 +1,4 @@ -# Viserio Http package +# Viserio Http Factory package This package is part of the [Narrowspark framework](http://github.com/narrowspark/framework). @@ -21,7 +21,7 @@ Thank you for considering contributing to the Narrowspark framework! The contrib Use [Composer](https://getcomposer.org/) to install this package: ```sh -composer require viserio/http +composer require viserio/http-factory ``` ## Official Documentation diff --git a/src/Viserio/HttpFactory/RequestFactory.php b/src/Viserio/HttpFactory/RequestFactory.php index ad68d0a9d..f7ee0014f 100644 --- a/src/Viserio/HttpFactory/RequestFactory.php +++ b/src/Viserio/HttpFactory/RequestFactory.php @@ -4,6 +4,7 @@ use Psr\Http\Message\RequestInterface; use Interop\Http\Factory\RequestFactoryInterface; +use Viserio\Http\Request; final class RequestFactory implements RequestFactoryInterface { diff --git a/src/Viserio/HttpFactory/ResponseFactory.php b/src/Viserio/HttpFactory/ResponseFactory.php index 5608d0284..3d88d6b25 100644 --- a/src/Viserio/HttpFactory/ResponseFactory.php +++ b/src/Viserio/HttpFactory/ResponseFactory.php @@ -4,6 +4,7 @@ use Psr\Http\Message\ResponseInterface; use Interop\Http\Factory\ResponseFactoryInterface; +use Viserio\Http\Response; final class ResponseFactory implements ResponseFactoryInterface { diff --git a/src/Viserio/HttpFactory/ServerRequestFactory.php b/src/Viserio/HttpFactory/ServerRequestFactory.php index e87bca36e..65f135b91 100644 --- a/src/Viserio/HttpFactory/ServerRequestFactory.php +++ b/src/Viserio/HttpFactory/ServerRequestFactory.php @@ -5,9 +5,13 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; use Interop\Http\Factory\ServerRequestFactoryInterface; +use Interop\Http\Factory\ServerRequestFromGlobalsFactoryInterface; use Viserio\Http\Stream\LazyOpenStream; +use Viserio\Http\ServerRequest; +use Viserio\Http\Uri; +use Viserio\Http\Util; -class ServerRequestFactory implements ServerRequestFactoryInterface +class ServerRequestFactory implements ServerRequestFactoryInterface, ServerRequestFromGlobalsFactoryInterface { /** * {@inheritdoc} @@ -32,6 +36,7 @@ public function createServerRequestFromGlobals() /** * {@inheritdoc} + * @codeCoverageIgnore */ public function createServerRequest($method, $uri) { diff --git a/src/Viserio/HttpFactory/StreamFactory.php b/src/Viserio/HttpFactory/StreamFactory.php index b3ae4c10e..98e0ad669 100644 --- a/src/Viserio/HttpFactory/StreamFactory.php +++ b/src/Viserio/HttpFactory/StreamFactory.php @@ -2,18 +2,26 @@ declare(strict_types=1); namespace Viserio\HttpFactory; -use InvalidArgumentException; -use Psr\Http\Message\StreamInterface; use Interop\Http\Factory\StreamFactoryInterface; -use Viserio\Http\Stream\PumpStream; +use Viserio\Http\Stream; final class StreamFactory implements StreamFactoryInterface { /** * {@inheritdoc} */ - public function createStream($stream) + public function createStream($resource) { - return new Stream(fopen('php://temp', 'r+')); + if (gettype($resource) === 'resource') { + return new Stream($resource); + } + + $stream = fopen('php://temp', 'r+'); + + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream); } } diff --git a/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php b/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php index 8201cf24b..f8566cb23 100644 --- a/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php @@ -2,9 +2,11 @@ declare(strict_types=1); namespace Viserio\HttpFactory\Tests; +use Psr\Http\Message\ServerRequestInterface; use Viserio\HttpFactory\ServerRequestFactory; use Viserio\HttpFactory\UriFactory; -use Psr\Http\Message\ServerRequestInterface; +use Viserio\Http\UploadedFile; +use Viserio\Http\Uri; class ServerRequestFactoryTest extends \PHPUnit_Framework_TestCase { @@ -15,58 +17,158 @@ public function setUp() $this->factory = new ServerRequestFactory(); } - public function dataMethods() + public function dataGetUriFromGlobals() { + $server = [ + 'PHP_SELF' => '/blog/article.php', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SERVER_ADDR' => 'Server IP: 127.0.0.1', + 'SERVER_NAME' => 'www.narrowspark.com', + 'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/7.0.7', + 'SERVER_PROTOCOL' => 'HTTP/1.0', + 'REQUEST_METHOD' => 'POST', + 'REQUEST_TIME' => 'Request start time: 1280149029', + 'QUERY_STRING' => 'id=10&user=foo', + 'DOCUMENT_ROOT' => '/path/to/your/server/root/', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'HTTP_ACCEPT_ENCODING' => 'gzip,deflate', + 'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5', + 'HTTP_CONNECTION' => 'keep-alive', + 'HTTP_HOST' => 'www.narrowspark.com', + 'HTTP_REFERER' => 'http://previous.url.com', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)', + 'REMOTE_ADDR' => '193.60.168.69', + 'REMOTE_HOST' => 'Client server\'s host name', + 'REMOTE_PORT' => '5390', + 'SCRIPT_FILENAME' => '/path/to/this/script.php', + 'SERVER_ADMIN' => 'webmaster@narrowspark.com', + 'SERVER_PORT' => '80', + 'SERVER_SIGNATURE' => 'Version signature: 5.123', + 'SCRIPT_NAME' => '/blog/article.php', + 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', + ]; + return [ - ['GET'], - ['POST'], - ['PUT'], - ['DELETE'], - ['OPTIONS'], - ['HEAD'], + 'Normal request' => [ + 'http://www.narrowspark.com/blog/article.php?id=10&user=foo', + $server, + ], + 'Secure request' => [ + 'https://www.narrowspark.com/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTPS' => 'on', 'SERVER_PORT' => '443']), + ], + 'No HTTPS param' => [ + 'http://www.narrowspark.com/blog/article.php?id=10&user=foo', + $server, + ], + 'HTTP_HOST missing' => [ + 'http://www.narrowspark.com/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => null]), + ], + 'No query String' => [ + 'http://www.narrowspark.com/blog/article.php', + array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']), + ], + 'Different port' => [ + 'http://www.narrowspark.com:8324/blog/article.php?id=10&user=foo', + array_merge($server, ['SERVER_PORT' => '8324']), + ], + 'Empty server variable' => [ + '', + [], + ], ]; } /** - * @dataProvider dataMethods + * @dataProvider dataGetUriFromGlobals */ - public function testCreateServerRequest($method) + public function testGetUriFromGlobals($expected, $serverParams) { - $uri = 'http://example.com/'; - $request = $this->factory->createServerRequest($method, $uri); + $_SERVER = $serverParams; + $serverRequest = $this->factory->createServerRequestFromGlobals(); - $this->assertServerRequest($request, $method, $uri); + $this->assertEquals(new Uri($expected), $serverRequest->getUri()); } - public function testCreateServerRequestWithUri() + public function testFromGlobals() { - $uriFactory = new UriFactory(); - $method = 'GET'; - $uri = 'http://example.com/'; - $request = $this->factory->createServerRequest($method, $uriFactory->createUri($uri)); + $_SERVER = [ + 'PHP_SELF' => '/blog/article.php', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SERVER_ADDR' => 'Server IP: 127.0.0.1', + 'SERVER_NAME' => 'www.narrowspark.com', + 'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/7.0.7', + 'SERVER_PROTOCOL' => 'HTTP/1.0', + 'REQUEST_METHOD' => 'POST', + 'REQUEST_TIME' => 'Request start time: 1280149029', + 'QUERY_STRING' => 'id=10&user=foo', + 'DOCUMENT_ROOT' => '/path/to/your/server/root/', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'HTTP_ACCEPT_ENCODING' => 'gzip,deflate', + 'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5', + 'HTTP_CONNECTION' => 'keep-alive', + 'HTTP_HOST' => 'www.narrowspark.com', + 'HTTP_REFERER' => 'http://previous.url.com', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)', + 'HTTPS' => '1', + 'REMOTE_ADDR' => '193.60.168.69', + 'REMOTE_HOST' => 'Client server\'s host name', + 'REMOTE_PORT' => '5390', + 'SCRIPT_FILENAME' => '/path/to/this/script.php', + 'SERVER_ADMIN' => 'webmaster@narrowspark.com', + 'SERVER_PORT' => '80', + 'SERVER_SIGNATURE' => 'Version signature: 5.123', + 'SCRIPT_NAME' => '/blog/article.php', + 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', + ]; + $_COOKIE = [ + 'logged-in' => 'yes!', + ]; + $_POST = [ + 'name' => 'Pesho', + 'email' => 'pesho@example.com', + ]; + $_GET = [ + 'id' => 10, + 'user' => 'foo', + ]; + $_FILES = [ + 'file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => UPLOAD_ERR_OK, + 'size' => 123, + ], + ]; - $this->assertServerRequest($request, $method, $uri); - } + $server = $this->factory->createServerRequestFromGlobals(); - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testCreateServerRequestFromGlobals() - { - $_SERVER['REQUEST_METHOD'] = $method = 'GET'; - $_SERVER['QUERY_STRING'] = $qs = 'foo=1&bar=true'; - $_SERVER['HTTP_HOST'] = $host = 'example.org'; - $uri = "http://{$host}/?$qs"; - $request = $this->factory->createServerRequestFromGlobals(); + $this->assertEquals('POST', $server->getMethod()); + $this->assertEquals(['Host' => ['www.narrowspark.com']], $server->getHeaders()); + $this->assertEquals('', (string) $server->getBody()); + $this->assertEquals('1.0', $server->getProtocolVersion()); + $this->assertEquals($_COOKIE, $server->getCookieParams()); + $this->assertEquals($_POST, $server->getParsedBody()); + $this->assertEquals($_GET, $server->getQueryParams()); + $this->assertEquals( + new Uri('http://www.narrowspark.com/blog/article.php?id=10&user=foo'), + $server->getUri() + ); - $this->assertServerRequest($request, $method, $uri); - } + $expectedFiles = [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + ]; - private function assertServerRequest($request, $method, $uri) - { - $this->assertInstanceOf(ServerRequestInterface::class, $request); - $this->assertSame($method, $request->getMethod()); - $this->assertSame($uri, (string) $request->getUri()); + $this->assertEquals($expectedFiles, $server->getUploadedFiles()); } } diff --git a/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php b/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php index adf441260..097fe665b 100644 --- a/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php @@ -2,6 +2,8 @@ declare(strict_types=1); namespace Viserio\HttpFactory\Tests; +use Viserio\Http\Stream; +use ArrayIterator; use Viserio\HttpFactory\StreamFactory; use Psr\Http\Message\StreamInterface; @@ -26,4 +28,40 @@ private function assertStream($stream, $content) $this->assertInstanceOf(StreamInterface::class, $stream); $this->assertSame($content, (string) $stream); } + + public function testKeepsPositionOfResource() + { + $resource = fopen(__FILE__, 'r'); + fseek($resource, 10); + + $stream = $this->factory->createStream($resource); + + $this->assertEquals(10, $stream->tell()); + + $stream->close(); + } + + public function testCreatesWithFactory() + { + $stream = $this->factory->createStream('foo'); + + $this->assertInstanceOf(Stream::class, $stream); + $this->assertEquals('foo', $stream->getContents()); + + $stream->close(); + } + + public function testFactoryCreatesFromEmptyString() + { + $this->assertInstanceOf(Stream::class, $this->factory->createStream('')); + } + + public function testFactoryCreatesFromResource() + { + $resource = fopen(__FILE__, 'r'); + $stream = $this->factory->createStream($resource); + + $this->assertInstanceOf(Stream::class, $stream); + $this->assertSame(file_get_contents(__FILE__), (string) $stream); + } } diff --git a/src/Viserio/HttpFactory/UploadedFileFactory.php b/src/Viserio/HttpFactory/UploadedFileFactory.php index 26624b883..b228b400d 100644 --- a/src/Viserio/HttpFactory/UploadedFileFactory.php +++ b/src/Viserio/HttpFactory/UploadedFileFactory.php @@ -4,13 +4,12 @@ use Psr\Http\Message\UploadedFileInterface; use Interop\Http\Factory\UploadedFileFactoryInterface; +use Viserio\Http\UploadedFile; final class UploadedFileFactory implements UploadedFileFactoryInterface { /** * {@inheritdoc} - * - * @codeCoverageIgnore */ public function createUploadedFile( $file, diff --git a/src/Viserio/HttpFactory/UriFactory.php b/src/Viserio/HttpFactory/UriFactory.php index 79bacd035..cc9f09c0b 100644 --- a/src/Viserio/HttpFactory/UriFactory.php +++ b/src/Viserio/HttpFactory/UriFactory.php @@ -4,11 +4,13 @@ use Psr\Http\Message\UriInterface; use Interop\Http\Factory\UriFactoryInterface; +use Viserio\Http\Uri; final class UriFactory implements UriFactoryInterface { /** * {@inheritdoc} + * @codeCoverageIgnore */ public function createUri($uri = ''): UriInterface { diff --git a/src/Viserio/Routing/Tests/Fixture/ControllerClosureMiddleware.php b/src/Viserio/Routing/Tests/Fixture/ControllerClosureMiddleware.php index ae7ffa7ed..c1366213d 100644 --- a/src/Viserio/Routing/Tests/Fixture/ControllerClosureMiddleware.php +++ b/src/Viserio/Routing/Tests/Fixture/ControllerClosureMiddleware.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\StreamFactory; class ControllerClosureMiddleware implements ServerMiddlewareContract { @@ -16,7 +16,7 @@ public function process( ): ResponseInterface { $response = $frame->next($request); - $response = $response->withBody((new StreamFactory())->createStreamFromString( + $response = $response->withBody((new StreamFactory())->createStream( $response->getBody() . '-' . $request->getAttribute('foo-middleware') . '-controller-closure' )); diff --git a/src/Viserio/Routing/Tests/Fixture/FakeMiddleware.php b/src/Viserio/Routing/Tests/Fixture/FakeMiddleware.php index 2718d83ec..76d2196f2 100644 --- a/src/Viserio/Routing/Tests/Fixture/FakeMiddleware.php +++ b/src/Viserio/Routing/Tests/Fixture/FakeMiddleware.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\StreamFactory; class FakeMiddleware implements ServerMiddlewareContract { @@ -18,7 +18,7 @@ public function process( return $response->withBody( (new StreamFactory()) - ->createStreamFromString('caught') + ->createStream('caught') ); } } diff --git a/src/Viserio/Routing/Tests/Fixture/FooMiddleware.php b/src/Viserio/Routing/Tests/Fixture/FooMiddleware.php index 8b052e6ac..1268976e4 100644 --- a/src/Viserio/Routing/Tests/Fixture/FooMiddleware.php +++ b/src/Viserio/Routing/Tests/Fixture/FooMiddleware.php @@ -6,7 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use Viserio\Contracts\Middleware\Delegate as DelegateContract; use Viserio\Contracts\Middleware\ServerMiddleware as ServerMiddlewareContract; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\StreamFactory; class FooMiddleware implements ServerMiddlewareContract { diff --git a/src/Viserio/Routing/Tests/Fixture/RouteTestClosureMiddlewareController.php b/src/Viserio/Routing/Tests/Fixture/RouteTestClosureMiddlewareController.php index d7dd99cbd..352f98aeb 100644 --- a/src/Viserio/Routing/Tests/Fixture/RouteTestClosureMiddlewareController.php +++ b/src/Viserio/Routing/Tests/Fixture/RouteTestClosureMiddlewareController.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Routing\Tests\Fixture; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; use Viserio\Routing\AbstractController; class RouteTestClosureMiddlewareController extends AbstractController @@ -19,7 +19,7 @@ public function index() ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('index') + ->createStream('index') ); } } diff --git a/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php b/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php index d41732fe4..45b13e2c2 100644 --- a/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/BasicParameterPatternsRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Pattern; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class BasicParameterPatternsRouterTest extends RouteRouterBaseTest { @@ -111,7 +111,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::DIGITS)->setParameter('name', 'digits'); @@ -120,7 +120,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::ALPHA)->setParameter('name', 'alpha'); @@ -129,7 +129,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::ALPHA_LOWER)->setParameter('name', 'alpha_low'); @@ -138,7 +138,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::ALPHA_UPPER)->setParameter('name', 'alpha_up'); @@ -147,7 +147,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::ALPHA_NUM)->setParameter('name', 'alpha_num'); @@ -156,7 +156,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::ALPHA_NUM_DASH)->setParameter('name', 'alpha_num_dash'); @@ -165,7 +165,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', Pattern::ANY)->setParameter('name', 'any'); @@ -174,7 +174,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['param'] . ' | ' . $args['name']) + ->createStream($args['param'] . ' | ' . $args['name']) ); })->where('param', '[\!]{3,5}')->setParameter('name', 'custom'); } diff --git a/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php b/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php index b153daadb..1cd550918 100644 --- a/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/BasicRestfulRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Pattern; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class BasicRestfulRouterTest extends RouteRouterBaseTest { @@ -68,7 +68,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.index'); $router->get('/user/create', function ($request, $args) { @@ -76,7 +76,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.create'); $router->post('/user', function ($request, $args) { @@ -84,7 +84,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.save'); $router->get('/user/{id}', function ($request, $args) { @@ -92,7 +92,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.show'); $router->get('/user/{id}/edit', function ($request, $args) { @@ -100,7 +100,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.edit'); $router->put('/user/{id}', function ($request, $args) { @@ -108,7 +108,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.update'); $router->delete('/user/{id}', function ($request, $args) { @@ -116,7 +116,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'user.delete'); $router->patch('/admin/{id}', function ($request, $args) { @@ -124,7 +124,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . ($args['id'] ?? '')) + ->createStream($args['name'] . ' | ' . ($args['id'] ?? '')) ); })->setParameter('name', 'admin.patch'); $router->options('/options', function ($request, $args) { @@ -132,7 +132,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | ' . $args['digits']) + ->createStream($args['name'] . ' | ' . $args['digits']) ); })->setParameter('name', 'options'); } diff --git a/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php b/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php index 730e025b5..b56075e73 100644 --- a/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/CommonRouteSegmentRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Pattern; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class CommonrouteregmentRouterTest extends RouteRouterBaseTest { @@ -37,7 +37,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) + ->createStream($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) ); })->setParameter('name', 'route1'); $router->get('/route2/{p1}/{p2}/{p3}', function ($request, $args) { @@ -45,7 +45,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) + ->createStream($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) ); })->setParameter('name', 'route2'); $router->get('/route3/{p1}/{p2}/{p3}', function ($request, $args) { @@ -53,7 +53,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) + ->createStream($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) ); })->setParameter('name', 'route3'); $router->get('/route4/{p1}/{p2}/{p3}', function ($request, $args) { @@ -61,7 +61,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) + ->createStream($args['name'] . ' | p1 = ' . $args['p1'] . ' | p2 = ' . $args['p2'] . ' | p3 = ' . $args['p3']) ); })->setParameter('name', 'route4'); $router->get('/route5/{p_1}/{p_2}/{p_3}', function ($request, $args) { @@ -69,7 +69,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | p_1 = ' . $args['p_1'] . ' | p_2 = ' . $args['p_2'] . ' | p_3 = ' . $args['p_3']) + ->createStream($args['name'] . ' | p_1 = ' . $args['p_1'] . ' | p_2 = ' . $args['p_2'] . ' | p_3 = ' . $args['p_3']) ); })->setParameter('name', 'route5'); $router->get('/route6/{p_1}/{p2}/{p_3}', function ($request, $args) { @@ -77,7 +77,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | p_1 = ' . $args['p_1'] . ' | p2 = ' . $args['p2'] . ' | p_3 = ' . $args['p_3']) + ->createStream($args['name'] . ' | p_1 = ' . $args['p_1'] . ' | p2 = ' . $args['p2'] . ' | p_3 = ' . $args['p_3']) ); })->setParameter('name', 'route6'); } diff --git a/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php b/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php index dca8ff28c..d3195d434 100644 --- a/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/ComplexParameterPatternsRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Pattern; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class ComplexParameterPatternsRouterTest extends RouteRouterBaseTest { @@ -77,7 +77,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | param = ' . $args['param']) + ->createStream($args['name'] . ' | param = ' . $args['param']) ); })->setParameter('name', 'prefix'); $router->get('/b/{param}:suffix', function ($request, $args) { @@ -85,7 +85,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | param = ' . $args['param']) + ->createStream($args['name'] . ' | param = ' . $args['param']) ); })->setParameter('name', 'suffix'); $router->get('/c/prefix:{param}:suffix', function ($request, $args) { @@ -93,7 +93,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | param = ' . $args['param']) + ->createStream($args['name'] . ' | param = ' . $args['param']) ); })->setParameter('name', 'prefix-and-suffix'); $router->get('/d/{param1}-{param2}:{param3}', function ($request, $args) { @@ -101,7 +101,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['name'] . ' | param1 = ' . $args['param1'] . ' | param2 = ' . $args['param2'] . ' | param3 = ' . $args['param3']) + ->createStream($args['name'] . ' | param1 = ' . $args['param1'] . ' | param2 = ' . $args['param2'] . ' | param3 = ' . $args['param3']) ); })->setParameter('name', 'multi-param'); $router->get('/e/{digits}-{alpha}:{exclaim}', function ($request, $args) { @@ -109,7 +109,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['routename'] . ' | digits = ' . $args['digits'] . ' | alpha = ' . $args['alpha'] . ' | exclaim = ' . $args['exclaim']) + ->createStream($args['routename'] . ' | digits = ' . $args['digits'] . ' | alpha = ' . $args['alpha'] . ' | exclaim = ' . $args['exclaim']) ); }) ->where('digits', Pattern::DIGITS) @@ -121,7 +121,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString($args['routename'] . ' | name = ' . $args['name'] . ' | thing = ' . $args['thing']) + ->createStream($args['routename'] . ' | name = ' . $args['name'] . ' | thing = ' . $args['thing']) ); })->where('name', '[A-Z]?[a-z]+') ->where('thing', Pattern::ALPHA_LOWER) diff --git a/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php b/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php index 8aaddc4de..69526da02 100644 --- a/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/ComplexShopRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Pattern; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class ComplexShopRouterTest extends RouteRouterBaseTest { @@ -122,7 +122,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'home'); $router->get('/about-us', function ($request, $args) { @@ -130,7 +130,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'about-us'); $router->get('/contact-us', function ($request, $args) { @@ -138,7 +138,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'contact-us'); $router->post('/contact-us', function ($request, $args) { @@ -146,7 +146,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'contact-us.submit'); @@ -155,7 +155,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'blog.index'); $router->get('/blog/recent', function ($request, $args) { @@ -163,7 +163,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'blog.recent'); $router->get('/blog/post/{post_slug}', function ($request, $args) { @@ -171,7 +171,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) + ->createStream('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) ); })->setParameter('name', 'blog.post.show'); $router->post('/blog/post/{post_slug}/comment', function ($request, $args) { @@ -179,7 +179,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) + ->createStream('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) ); })->setParameter('name', 'blog.post.comment'); @@ -188,7 +188,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.index'); @@ -197,7 +197,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.category.index'); $router->get('/shop/category/search/{filter_by}:{filter_value}', function ($request, $args) { @@ -205,7 +205,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | filter_by = ' . $args['filter_by'] . ' | filter_value = ' . $args['filter_value']) + ->createStream('name = ' . $args['name'] . ' | filter_by = ' . $args['filter_by'] . ' | filter_value = ' . $args['filter_value']) ); })->setParameter('name', 'shop.category.search'); $router->get('/shop/category/{category_id}', function ($request, $args) { @@ -213,7 +213,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) ); })->setParameter('name', 'shop.category.show'); $router->get('/shop/category/{category_id}/product', function ($request, $args) { @@ -221,7 +221,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) ); })->setParameter('name', 'shop.category.product.index'); $router->get('/shop/category/{category_id}/product/search/{filter_by}:{filter_value}', function ($request, $args) { @@ -229,7 +229,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id'] . ' | filter_by = ' . $args['filter_by'] . ' | filter_value = ' . $args['filter_value']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id'] . ' | filter_by = ' . $args['filter_by'] . ' | filter_value = ' . $args['filter_value']) ); })->setParameter('name', 'shop.category.product.search'); @@ -238,7 +238,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.product.index'); $router->get('/shop/product/search/{filter_by}:{filter_value}', function ($request, $args) { @@ -246,7 +246,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | filter_by = ' . $args['filter_by'] . ' | filter_value = ' . $args['filter_value']) + ->createStream('name = ' . $args['name'] . ' | filter_by = ' . $args['filter_by'] . ' | filter_value = ' . $args['filter_value']) ); })->setParameter('name', 'shop.product.search'); $router->get('/shop/product/{product_id}', function ($request, $args) { @@ -254,7 +254,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) + ->createStream('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) ); })->setParameter('name', 'shop.product.show'); @@ -263,7 +263,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.cart.show'); $router->put('/shop/cart', function ($request, $args) { @@ -271,7 +271,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.cart.add'); $router->delete('/shop/cart', function ($request, $args) { @@ -279,7 +279,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.cart.empty'); $router->get('/shop/cart/checkout', function ($request, $args) { @@ -287,7 +287,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.cart.checkout.show'); $router->post('/shop/cart/checkout', function ($request, $args) { @@ -295,7 +295,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'shop.cart.checkout.process'); @@ -304,7 +304,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.login'); $router->post('/admin/login', function ($request, $args) { @@ -312,7 +312,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.login.submit'); $router->get('/admin/logout', function ($request, $args) { @@ -320,7 +320,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.logout'); $router->get('/admin', function ($request, $args) { @@ -328,7 +328,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.index'); @@ -337,7 +337,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.product.index'); $router->get('/admin/product/create', function ($request, $args) { @@ -345,7 +345,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.product.create'); $router->post('/admin/product', function ($request, $args) { @@ -353,7 +353,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.product.store'); $router->get('/admin/product/{product_id}', function ($request, $args) { @@ -361,7 +361,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) + ->createStream('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) ); })->setParameter('name', 'admin.product.show'); $router->get('/admin/product/{product_id}/edit', function ($request, $args) { @@ -369,7 +369,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) + ->createStream('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) ); })->setParameter('name', 'admin.product.edit'); $router->match(['PUT', 'PATCH'], '/admin/product/{product_id}', function ($request, $args) { @@ -377,7 +377,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) + ->createStream('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) ); })->setParameter('name', 'admin.product.update'); $router->delete('/admin/product/{product_id}', function ($request, $args) { @@ -385,7 +385,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) + ->createStream('name = ' . $args['name'] . ' | product_id = ' . $args['product_id']) ); })->setParameter('name', 'admin.product.destroy'); @@ -394,7 +394,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.category.index'); $router->get('/admin/category/create', function ($request, $args) { @@ -402,7 +402,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.category.create'); $router->post('/admin/category', function ($request, $args) { @@ -410,7 +410,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'admin.category.store'); $router->get('/admin/category/{category_id}', function ($request, $args) { @@ -418,7 +418,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) ); })->setParameter('name', 'admin.category.show'); $router->get('/admin/category/{category_id}/edit', function ($request, $args) { @@ -426,7 +426,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) ); })->setParameter('name', 'admin.category.edit'); $router->match(['PUT', 'PATCH'], '/admin/category/{category_id}', function ($request, $args) { @@ -434,7 +434,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) ); })->setParameter('name', 'admin.category.update'); $router->delete('/admin/category/{category_id}', function ($request, $args) { @@ -442,7 +442,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) + ->createStream('name = ' . $args['name'] . ' | category_id = ' . $args['category_id']) ); })->setParameter('name', 'admin.category.destroy'); } diff --git a/src/Viserio/Routing/Tests/Router/EdgeCasesRouterTest.php b/src/Viserio/Routing/Tests/Router/EdgeCasesRouterTest.php index 537be3d06..27169e0ba 100644 --- a/src/Viserio/Routing/Tests/Router/EdgeCasesRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/EdgeCasesRouterTest.php @@ -3,8 +3,8 @@ namespace Viserio\Routing\Tests\Router; use Viserio\Contracts\Routing\Pattern; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class EdgeCasesRouterTest extends RouteRouterBaseTest { @@ -52,7 +52,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | param = ' . $args['param']) + ->createStream('name = ' . $args['name'] . ' | param = ' . $args['param']) ); })->setParameter('name', 'middle-param'); $router->get('/123/{param}/bar', function ($request, $args) { @@ -60,7 +60,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | param = ' . $args['param']) + ->createStream('name = ' . $args['name'] . ' | param = ' . $args['param']) ); })->where('param', '.*')->setParameter('name', 'all-middle-param'); $router->get('/string', function ($request, $args) { @@ -68,7 +68,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('some-string')); + ->createStream('some-string')); }); // Order of precedence: @@ -81,7 +81,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'http-method-fallback.static'); $router->any('/http/method/fallback', function ($request, $args) { @@ -89,7 +89,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'http-method-fallback.static.fallback'); $router->post('/http/method/{parameter}', function ($request, $args) { @@ -97,7 +97,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | parameter = ' . $args['parameter']) + ->createStream('name = ' . $args['name'] . ' | parameter = ' . $args['parameter']) ); })->setParameter('name', 'http-method-fallback.dynamic'); $router->any('/http/method/{parameter}', function ($request, $args) { @@ -105,7 +105,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | parameter = ' . $args['parameter']) + ->createStream('name = ' . $args['name'] . ' | parameter = ' . $args['parameter']) ); })->setParameter('name', 'http-method-fallback.dynamic.fallback'); @@ -115,7 +115,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'allowed-methods.static'); $router->post('/allowed-methods/{parameter}', function ($request, $args) { @@ -123,7 +123,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | parameter = ' . $args['parameter']) + ->createStream('name = ' . $args['name'] . ' | parameter = ' . $args['parameter']) ); })->setParameter('name', 'allowed-methods.dynamic'); $router->get('/complex-methods/{param}/foo/bar', function ($request, $args) { @@ -131,7 +131,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | param = ' . $args['param']) + ->createStream('name = ' . $args['name'] . ' | param = ' . $args['param']) ); })->where('param', Pattern::DIGITS)->setParameter('name', 'complex-methods.first'); $router->post('/complex-methods/{param}/foo/{param2}', function ($request, $args) { @@ -139,7 +139,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | param = ' . $args['param'] . ' | param2 = ' . $args['param2']) + ->createStream('name = ' . $args['name'] . ' | param = ' . $args['param'] . ' | param2 = ' . $args['param2']) ); })->where('param', Pattern::ALPHA_NUM)->setParameter('name', 'complex-methods.second'); $router->post('/complex-methods/{param}/{param2}', function ($request, $args) { @@ -147,7 +147,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | param = ' . $args['param'] . ' | param2 = ' . $args['param2']) + ->createStream('name = ' . $args['name'] . ' | param = ' . $args['param'] . ' | param2 = ' . $args['param2']) ); })->where('param', Pattern::ALPHA_NUM)->setParameter('name', 'complex-methods.second'); } diff --git a/src/Viserio/Routing/Tests/Router/HttpMethodRouterTest.php b/src/Viserio/Routing/Tests/Router/HttpMethodRouterTest.php index 6206f0569..c591f4381 100644 --- a/src/Viserio/Routing/Tests/Router/HttpMethodRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/HttpMethodRouterTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Routing\Tests\Router; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class HttpMethodRouterTest extends RouteRouterBaseTest { @@ -28,7 +28,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'home.get'); @@ -37,7 +37,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'home.post-or-patch'); @@ -46,7 +46,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'home.delete'); @@ -55,7 +55,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'home.fallback'); } diff --git a/src/Viserio/Routing/Tests/Router/InlineParameterRouterTest.php b/src/Viserio/Routing/Tests/Router/InlineParameterRouterTest.php index fd33e5924..6d3c2769d 100644 --- a/src/Viserio/Routing/Tests/Router/InlineParameterRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/InlineParameterRouterTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Routing\Tests\Router; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; class InlineParameterRouterTest extends RouteRouterBaseTest { @@ -49,7 +49,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'home'); @@ -58,7 +58,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name']) + ->createStream('name = ' . $args['name']) ); })->setParameter('name', 'blog.index'); $router->get('/blog/post/{post_slug:[a-z0-9\-]+}', function ($request, $args) { @@ -66,7 +66,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) + ->createStream('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) ); })->setParameter('name', 'blog.post.show'); $router->post('/blog/post/{post_slug:[a-z0-9\-]+}/comment', function ($request, $args) { @@ -74,7 +74,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) + ->createStream('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug']) ); })->setParameter('name', 'blog.post.comment'); $router->get('/blog/post/{post_slug:[a-z0-9\-]+}/comment/{comment_id:[0-9]+}', function ($request, $args) { @@ -82,7 +82,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug'] . ' | comment_id = ' . $args['comment_id']) + ->createStream('name = ' . $args['name'] . ' | post_slug = ' . $args['post_slug'] . ' | comment_id = ' . $args['comment_id']) ); })->setParameter('name', 'blog.post.comment.show'); } diff --git a/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php index 40930f109..719ac466f 100644 --- a/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php +++ b/src/Viserio/Routing/Tests/Router/RootRoutesRouterTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Routing\Tests\Router; -use Viserio\Http\ResponseFactory; -use Viserio\Http\StreamFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\StreamFactory; use Viserio\Routing\Tests\Fixture\FakeMiddleware; use Viserio\Routing\Tests\Fixture\FooMiddleware; use Viserio\Routing\Tests\Fixture\RouteTestClosureMiddlewareController; @@ -38,7 +38,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('Hello') + ->createStream('Hello') ); })->setParameter('name', 'root'); @@ -47,7 +47,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('Hello') + ->createStream('Hello') ); })->setParameter('name', 'root'); @@ -56,7 +56,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('Hello') + ->createStream('Hello') ); })->setParameter('name', 'root-slash'); @@ -65,7 +65,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('Hello') + ->createStream('Hello') ); })->setParameter('name', 'root-slash'); @@ -75,7 +75,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('Middleware') + ->createStream('Middleware') ); }])->setParameter('name', 'middleware'); $router->get('/middleware2', ['middleware.with' => new FakeMiddleware(), 'uses' => function ($request, $args) { @@ -83,7 +83,7 @@ protected function definitions($router) ->createResponse() ->withBody( (new StreamFactory()) - ->createStreamFromString('Middleware') + ->createStream('Middleware') ); }])->setParameter('name', 'middleware2'); diff --git a/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php index 70666e23c..d79a78b5b 100644 --- a/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php +++ b/src/Viserio/Routing/Tests/Router/RouteRouterBaseTest.php @@ -5,8 +5,8 @@ use Interop\Container\ContainerInterface; use Narrowspark\TestingHelper\Traits\MockeryTrait; use ReflectionClass; -use Viserio\Http\ResponseFactory; -use Viserio\Http\ServerRequestFactory; +use Viserio\HttpFactory\ResponseFactory; +use Viserio\HttpFactory\ServerRequestFactory; use Viserio\Routing\Router; abstract class RouteRouterBaseTest extends \PHPUnit_Framework_TestCase diff --git a/src/Viserio/Routing/composer.json b/src/Viserio/Routing/composer.json index ec591ba95..628007527 100644 --- a/src/Viserio/Routing/composer.json +++ b/src/Viserio/Routing/composer.json @@ -32,7 +32,7 @@ "narrowspark/testing-helper" : "^1.5", "mockery/mockery" : "^0.9.5", "phpunit/phpunit" : "^5.1", - "viserio/http" : "self.version" + "viserio/http-factory" : "self.version" }, "autoload": { "psr-4": { From 87cabe51e0dc72527f6608f8259242804fd8930e Mon Sep 17 00:00:00 2001 From: Daniel Bannert Date: Sun, 28 Aug 2016 20:28:38 +0000 Subject: [PATCH 20/20] Applied fixes from StyleCI --- src/Viserio/Http/Response/RedirectResponse.php | 2 +- src/Viserio/Http/Stream/LazyOpenStream.php | 2 +- src/Viserio/Http/Tests/RequestTest.php | 2 +- src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php | 2 +- src/Viserio/Http/Tests/Stream/FnStreamTest.php | 2 +- src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php | 2 +- src/Viserio/Http/Tests/UtilTest.php | 2 +- src/Viserio/HttpFactory/RequestFactory.php | 2 +- src/Viserio/HttpFactory/ResponseFactory.php | 2 +- src/Viserio/HttpFactory/ServerRequestFactory.php | 7 ++++--- src/Viserio/HttpFactory/StreamFactory.php | 1 + src/Viserio/HttpFactory/Tests/RequestFactoryTest.php | 2 +- src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php | 2 +- src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php | 4 ++-- src/Viserio/HttpFactory/Tests/StreamFactoryTest.php | 4 ++-- src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php | 2 +- src/Viserio/HttpFactory/UploadedFileFactory.php | 2 +- src/Viserio/HttpFactory/UriFactory.php | 3 ++- 18 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Viserio/Http/Response/RedirectResponse.php b/src/Viserio/Http/Response/RedirectResponse.php index cdcb034e6..ed3d9924e 100644 --- a/src/Viserio/Http/Response/RedirectResponse.php +++ b/src/Viserio/Http/Response/RedirectResponse.php @@ -4,8 +4,8 @@ use InvalidArgumentException; use Psr\Http\Message\UriInterface; -use Viserio\Http\Stream; use Viserio\Http\Response; +use Viserio\Http\Stream; class RedirectResponse extends Response { diff --git a/src/Viserio/Http/Stream/LazyOpenStream.php b/src/Viserio/Http/Stream/LazyOpenStream.php index a410994f2..28c04e978 100644 --- a/src/Viserio/Http/Stream/LazyOpenStream.php +++ b/src/Viserio/Http/Stream/LazyOpenStream.php @@ -5,8 +5,8 @@ use Psr\Http\Message\StreamInterface; use Throwable; use UnexpectedValueException; -use Viserio\Http\Util; use Viserio\Http\Stream; +use Viserio\Http\Util; class LazyOpenStream implements StreamInterface { diff --git a/src/Viserio/Http/Tests/RequestTest.php b/src/Viserio/Http/Tests/RequestTest.php index a34898ef5..51a7476f6 100644 --- a/src/Viserio/Http/Tests/RequestTest.php +++ b/src/Viserio/Http/Tests/RequestTest.php @@ -8,8 +8,8 @@ use Psr\Http\Message\UriInterface; use StdClass; use Viserio\Http\Request; -use Viserio\Http\Stream\FnStream; use Viserio\Http\Stream; +use Viserio\Http\Stream\FnStream; use Viserio\Http\Uri; class RequestTest extends AbstractMessageTest diff --git a/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php b/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php index 82eceed40..5fa480b4e 100644 --- a/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/ByteCountingStreamTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Http\Tests\Stream; -use Viserio\Http\Stream\ByteCountingStream; use Viserio\Http\Stream; +use Viserio\Http\Stream\ByteCountingStream; class ByteCountingStreamTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/Http/Tests/Stream/FnStreamTest.php b/src/Viserio/Http/Tests/Stream/FnStreamTest.php index 175265a85..594751893 100644 --- a/src/Viserio/Http/Tests/Stream/FnStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/FnStreamTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Http\Tests\Stream; -use Viserio\Http\Stream\FnStream; use Viserio\Http\Stream; +use Viserio\Http\Stream\FnStream; class FnStreamTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php b/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php index cd791cc7c..0a5cfd208 100644 --- a/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php +++ b/src/Viserio/Http/Tests/Stream/NoSeekStreamTest.php @@ -3,8 +3,8 @@ namespace Viserio\Http\Tests\Stream; use Psr\Http\Message\StreamInterface; -use Viserio\Http\Stream\NoSeekStream; use Viserio\Http\Stream; +use Viserio\Http\Stream\NoSeekStream; class NoSeekStreamTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/Http/Tests/UtilTest.php b/src/Viserio/Http/Tests/UtilTest.php index 87c493a4c..79e88d843 100644 --- a/src/Viserio/Http/Tests/UtilTest.php +++ b/src/Viserio/Http/Tests/UtilTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\Http\Tests; -use Viserio\Http\Stream\FnStream; use Viserio\Http\Stream; +use Viserio\Http\Stream\FnStream; use Viserio\Http\UploadedFile; use Viserio\Http\Util; diff --git a/src/Viserio/HttpFactory/RequestFactory.php b/src/Viserio/HttpFactory/RequestFactory.php index f7ee0014f..1b52d0859 100644 --- a/src/Viserio/HttpFactory/RequestFactory.php +++ b/src/Viserio/HttpFactory/RequestFactory.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\HttpFactory; -use Psr\Http\Message\RequestInterface; use Interop\Http\Factory\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; use Viserio\Http\Request; final class RequestFactory implements RequestFactoryInterface diff --git a/src/Viserio/HttpFactory/ResponseFactory.php b/src/Viserio/HttpFactory/ResponseFactory.php index 3d88d6b25..34328b615 100644 --- a/src/Viserio/HttpFactory/ResponseFactory.php +++ b/src/Viserio/HttpFactory/ResponseFactory.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\HttpFactory; -use Psr\Http\Message\ResponseInterface; use Interop\Http\Factory\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; use Viserio\Http\Response; final class ResponseFactory implements ResponseFactoryInterface diff --git a/src/Viserio/HttpFactory/ServerRequestFactory.php b/src/Viserio/HttpFactory/ServerRequestFactory.php index 65f135b91..9bbefb971 100644 --- a/src/Viserio/HttpFactory/ServerRequestFactory.php +++ b/src/Viserio/HttpFactory/ServerRequestFactory.php @@ -2,12 +2,12 @@ declare(strict_types=1); namespace Viserio\HttpFactory; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\UriInterface; use Interop\Http\Factory\ServerRequestFactoryInterface; use Interop\Http\Factory\ServerRequestFromGlobalsFactoryInterface; -use Viserio\Http\Stream\LazyOpenStream; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; use Viserio\Http\ServerRequest; +use Viserio\Http\Stream\LazyOpenStream; use Viserio\Http\Uri; use Viserio\Http\Util; @@ -36,6 +36,7 @@ public function createServerRequestFromGlobals() /** * {@inheritdoc} + * * @codeCoverageIgnore */ public function createServerRequest($method, $uri) diff --git a/src/Viserio/HttpFactory/StreamFactory.php b/src/Viserio/HttpFactory/StreamFactory.php index 98e0ad669..5667ff98a 100644 --- a/src/Viserio/HttpFactory/StreamFactory.php +++ b/src/Viserio/HttpFactory/StreamFactory.php @@ -22,6 +22,7 @@ public function createStream($resource) fwrite($stream, $resource); fseek($stream, 0); } + return new Stream($stream); } } diff --git a/src/Viserio/HttpFactory/Tests/RequestFactoryTest.php b/src/Viserio/HttpFactory/Tests/RequestFactoryTest.php index 11c61b014..def498b94 100644 --- a/src/Viserio/HttpFactory/Tests/RequestFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/RequestFactoryTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); namespace Viserio\HttpFactory\Tests; +use Psr\Http\Message\RequestInterface; use Viserio\HttpFactory\RequestFactory; use Viserio\HttpFactory\UriFactory; -use Psr\Http\Message\RequestInterface; class RequestFactoryTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php b/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php index ecc946cf4..18bdde84a 100644 --- a/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/ResponseFactoryTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\HttpFactory\Tests; -use Viserio\HttpFactory\ResponseFactory; use Psr\Http\Message\ResponseInterface; +use Viserio\HttpFactory\ResponseFactory; class ResponseFactoryTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php b/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php index f8566cb23..2103fc1a0 100644 --- a/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/ServerRequestFactoryTest.php @@ -3,10 +3,10 @@ namespace Viserio\HttpFactory\Tests; use Psr\Http\Message\ServerRequestInterface; -use Viserio\HttpFactory\ServerRequestFactory; -use Viserio\HttpFactory\UriFactory; use Viserio\Http\UploadedFile; use Viserio\Http\Uri; +use Viserio\HttpFactory\ServerRequestFactory; +use Viserio\HttpFactory\UriFactory; class ServerRequestFactoryTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php b/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php index 097fe665b..5c5beb698 100644 --- a/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/StreamFactoryTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); namespace Viserio\HttpFactory\Tests; -use Viserio\Http\Stream; use ArrayIterator; -use Viserio\HttpFactory\StreamFactory; use Psr\Http\Message\StreamInterface; +use Viserio\Http\Stream; +use Viserio\HttpFactory\StreamFactory; class StreamFactoryTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php b/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php index 50771fb3d..e6a696d9b 100644 --- a/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php +++ b/src/Viserio/HttpFactory/Tests/UploadedFileFactoryTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\HttpFactory\Tests; -use Viserio\HttpFactory\UploadedFileFactory; use Psr\Http\Message\UploadedFileInterface; +use Viserio\HttpFactory\UploadedFileFactory; class UploadedFileFactoryTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Viserio/HttpFactory/UploadedFileFactory.php b/src/Viserio/HttpFactory/UploadedFileFactory.php index b228b400d..2b1d3913a 100644 --- a/src/Viserio/HttpFactory/UploadedFileFactory.php +++ b/src/Viserio/HttpFactory/UploadedFileFactory.php @@ -2,8 +2,8 @@ declare(strict_types=1); namespace Viserio\HttpFactory; -use Psr\Http\Message\UploadedFileInterface; use Interop\Http\Factory\UploadedFileFactoryInterface; +use Psr\Http\Message\UploadedFileInterface; use Viserio\Http\UploadedFile; final class UploadedFileFactory implements UploadedFileFactoryInterface diff --git a/src/Viserio/HttpFactory/UriFactory.php b/src/Viserio/HttpFactory/UriFactory.php index cc9f09c0b..dab67d9a0 100644 --- a/src/Viserio/HttpFactory/UriFactory.php +++ b/src/Viserio/HttpFactory/UriFactory.php @@ -2,14 +2,15 @@ declare(strict_types=1); namespace Viserio\HttpFactory; -use Psr\Http\Message\UriInterface; use Interop\Http\Factory\UriFactoryInterface; +use Psr\Http\Message\UriInterface; use Viserio\Http\Uri; final class UriFactory implements UriFactoryInterface { /** * {@inheritdoc} + * * @codeCoverageIgnore */ public function createUri($uri = ''): UriInterface