From 6113356d8d419d474fc38f3757e998900fe39f26 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Wed, 30 May 2018 17:17:49 +0200 Subject: [PATCH 01/12] Support swoole_http_request --- src/ServerRequestFactory.php | 65 +++++++++++++++++++++++++++++++ test/ServerRequestFactoryTest.php | 31 +++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index 57cdc167..5924f68f 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -9,7 +9,9 @@ use InvalidArgumentException; use Psr\Http\Message\UploadedFileInterface; +use RuntimeException; use stdClass; +use swoole_http_request; use UnexpectedValueException; use function array_change_key_case; @@ -99,6 +101,69 @@ public static function fromGlobals( ); } + /** + * Create a request from a Swoole request + * + * @param swoole_http_request $request + * @return ServerRequest + * @throws RuntimeException if Swoole is not installed + */ + + public static function fromSwoole(swoole_http_request $request) + { + if (! extension_loaded('swoole')) { + throw new Exception\RuntimeException('Swoole extension is not installed!'); + } + $get = $request->get ?? []; + $post = $request->post ?? []; + $cookie = $request->cookie ?? []; + $files = $request->files ?? []; + + $server = [ + 'REQUEST_METHOD' => $request->server['request_method'], + 'REQUEST_URI' => $request->server['request_uri'], + 'PATH_INFO' => $request->server['path_info'], + 'REQUEST_TIME' => $request->server['request_time'], + 'GATEWAY_INTERFACE' => 'swoole/' . SWOOLE_VERSION, + // Server + 'SERVER_PROTOCOL' => $request->header['server_protocol'] ?? $request->server['server_protocol'], + 'REQUEST_SCHEMA' => $request->header['request_scheme'] ?? + explode('/', $request->server['server_protocol'])[0], + 'SERVER_NAME' => $request->header['server_name'] ?? $request->header['host'], + 'SERVER_ADDR' => $request->header['host'], + 'SERVER_PORT' => $request->header['server_port'] ?? $request->server['server_port'], + 'REMOTE_ADDR' => $request->server['remote_addr'] ?? $request->header['host'], + 'REMOTE_PORT' => $request->header['remote_port'] ?? $request->server['remote_port'], + 'QUERY_STRING' => $request->server['query_string'] ?? '', + // Headers + 'HTTP_HOST' => $request->header['host'], + 'HTTP_USER_AGENT' => $request->header['user-agent'] ?? '', + 'HTTP_ACCEPT' => $request->header['accept'] ?? '*/*', + 'HTTP_ACCEPT_LANGUAGE' => $request->header['accept-language'] ?? '', + 'HTTP_ACCEPT_ENCODING' => $request->header['accept-encoding'] ?? '', + 'HTTP_CONNECTION' => $request->header['connection'] ?? '', + 'HTTP_CACHE_CONTROL' => $request->header['cache-control'] ?? '', + ]; + + $headers = []; + foreach ($request->header as $name => $value) { + $headers[str_replace('-', '_', $name)] = $value; + } + + return new ServerRequest( + $server, + static::normalizeFiles($files), + static::marshalUriFromServer($server, $headers), + $server['REQUEST_METHOD'], + $request->rawContent(), + $headers, + $cookie, + $get, + $post, + static::marshalProtocolVersion($server) + ); + } + /** * Access a value in an array, returning a default value if not found * diff --git a/test/ServerRequestFactoryTest.php b/test/ServerRequestFactoryTest.php index 63f202e1..36b9f06b 100644 --- a/test/ServerRequestFactoryTest.php +++ b/test/ServerRequestFactoryTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use ReflectionMethod; use ReflectionProperty; +use swoole_http_request; use UnexpectedValueException; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\ServerRequestFactory; @@ -580,4 +581,34 @@ public function marshalProtocolVersionProvider() 'HTTP/2' => ['HTTP/2', '2'], ]; } + + public function testFromSwoole() + { + if (! extension_loaded('swoole')) { + $this->markTestSkipped('The Swoole extesion is not available'); + } + + $swooleRequest = $this->createMock(swoole_http_request::class); + $swooleRequest->header['host'] = 'localhost:9501'; + $swooleRequest->server['request_method'] = 'GET'; + $swooleRequest->server['request_uri'] = '/'; + $swooleRequest->server['path_info'] = '/'; + $swooleRequest->server['request_time'] = time(); + $swooleRequest->server['server_protocol'] = 'HTTP/1.1'; + $swooleRequest->server['server_port'] = 9501; + $swooleRequest->server['remote_port'] = 45314; + $swooleRequest->method('rawContent')->willReturn('php://input'); + + $request = ServerRequestFactory::fromSwoole($swooleRequest); + $this->assertInstanceOf(ServerRequest::class, $request); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/', $request->getRequestTarget()); + $this->assertEquals('/', $request->getUri()->getPath()); + $this->assertEquals('1.1', $request->getProtocolVersion()); + $this->assertEquals('', $request->getBody()); + $this->assertEquals([], $request->getCookieParams()); + $this->assertEquals([], $request->getQueryParams()); + $this->assertEquals([], $request->getUploadedFiles()); + $this->assertEquals(['host' => ['localhost:9501']], $request->getHeaders()); + } } From a51f767f97f51912acddec472ab98f9115c22020 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Wed, 30 May 2018 17:48:39 +0200 Subject: [PATCH 02/12] Remove ?? usage for PHP 5.6 support --- src/ServerRequestFactory.php | 47 +++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index 5924f68f..ec81fc9c 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -114,10 +114,10 @@ public static function fromSwoole(swoole_http_request $request) if (! extension_loaded('swoole')) { throw new Exception\RuntimeException('Swoole extension is not installed!'); } - $get = $request->get ?? []; - $post = $request->post ?? []; - $cookie = $request->cookie ?? []; - $files = $request->files ?? []; + $get = isset($request->get) ? $request->get : []; + $post = isset($request->post) ? $request->post : []; + $cookie = isset($request->cookie) ? $request->cookie : []; + $files = isset($request->files) ? $request->files : []; $server = [ 'REQUEST_METHOD' => $request->server['request_method'], @@ -126,23 +126,36 @@ public static function fromSwoole(swoole_http_request $request) 'REQUEST_TIME' => $request->server['request_time'], 'GATEWAY_INTERFACE' => 'swoole/' . SWOOLE_VERSION, // Server - 'SERVER_PROTOCOL' => $request->header['server_protocol'] ?? $request->server['server_protocol'], - 'REQUEST_SCHEMA' => $request->header['request_scheme'] ?? + 'SERVER_PROTOCOL' => isset($request->header['server_protocol']) ? + $request->header['server_protocol'] : $request->server['server_protocol'], + 'REQUEST_SCHEMA' => isset($request->header['request_scheme']) ? + $request->header['request_scheme'] : explode('/', $request->server['server_protocol'])[0], - 'SERVER_NAME' => $request->header['server_name'] ?? $request->header['host'], + 'SERVER_NAME' => isset($request->header['server_name']) ? + $request->header['server_name'] : $request->header['host'], 'SERVER_ADDR' => $request->header['host'], - 'SERVER_PORT' => $request->header['server_port'] ?? $request->server['server_port'], - 'REMOTE_ADDR' => $request->server['remote_addr'] ?? $request->header['host'], - 'REMOTE_PORT' => $request->header['remote_port'] ?? $request->server['remote_port'], - 'QUERY_STRING' => $request->server['query_string'] ?? '', + 'SERVER_PORT' => isset($request->header['server_port']) ? + $request->header['server_port'] : $request->server['server_port'], + 'REMOTE_ADDR' => isset($request->server['remote_addr']) ? + $request->server['remote_addr'] : $request->header['host'], + 'REMOTE_PORT' => isset($request->header['remote_port']) ? + $request->header['remote_port'] : $request->server['remote_port'], + 'QUERY_STRING' => isset($request->server['query_string']) ? + $request->server['query_string'] : '', // Headers 'HTTP_HOST' => $request->header['host'], - 'HTTP_USER_AGENT' => $request->header['user-agent'] ?? '', - 'HTTP_ACCEPT' => $request->header['accept'] ?? '*/*', - 'HTTP_ACCEPT_LANGUAGE' => $request->header['accept-language'] ?? '', - 'HTTP_ACCEPT_ENCODING' => $request->header['accept-encoding'] ?? '', - 'HTTP_CONNECTION' => $request->header['connection'] ?? '', - 'HTTP_CACHE_CONTROL' => $request->header['cache-control'] ?? '', + 'HTTP_USER_AGENT' => isset($request->header['user-agent']) ? + $request->header['user-agent'] : '', + 'HTTP_ACCEPT' => isset($request->header['accept']) ? + $request->header['accept'] : '*/*', + 'HTTP_ACCEPT_LANGUAGE' => isset($request->header['accept-language']) ? + $request->header['accept-language'] : '', + 'HTTP_ACCEPT_ENCODING' => isset($request->header['accept-encoding']) ? + $request->header['accept-encoding'] : '', + 'HTTP_CONNECTION' => isset($request->header['connection']) ? + $request->header['connection'] : '', + 'HTTP_CACHE_CONTROL' => isset($request->header['cache-control']) ? + $request->header['cache-control'] : '', ]; $headers = []; From 6ffa7f651075674b2e4495ebc47f9bda4b8851b0 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Thu, 31 May 2018 17:17:56 +0200 Subject: [PATCH 03/12] Reduced the params conversion from Swoole to PSR-7 --- src/ServerRequestFactory.php | 42 ++++-------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index ec81fc9c..ecabc070 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -119,44 +119,10 @@ public static function fromSwoole(swoole_http_request $request) $cookie = isset($request->cookie) ? $request->cookie : []; $files = isset($request->files) ? $request->files : []; - $server = [ - 'REQUEST_METHOD' => $request->server['request_method'], - 'REQUEST_URI' => $request->server['request_uri'], - 'PATH_INFO' => $request->server['path_info'], - 'REQUEST_TIME' => $request->server['request_time'], - 'GATEWAY_INTERFACE' => 'swoole/' . SWOOLE_VERSION, - // Server - 'SERVER_PROTOCOL' => isset($request->header['server_protocol']) ? - $request->header['server_protocol'] : $request->server['server_protocol'], - 'REQUEST_SCHEMA' => isset($request->header['request_scheme']) ? - $request->header['request_scheme'] : - explode('/', $request->server['server_protocol'])[0], - 'SERVER_NAME' => isset($request->header['server_name']) ? - $request->header['server_name'] : $request->header['host'], - 'SERVER_ADDR' => $request->header['host'], - 'SERVER_PORT' => isset($request->header['server_port']) ? - $request->header['server_port'] : $request->server['server_port'], - 'REMOTE_ADDR' => isset($request->server['remote_addr']) ? - $request->server['remote_addr'] : $request->header['host'], - 'REMOTE_PORT' => isset($request->header['remote_port']) ? - $request->header['remote_port'] : $request->server['remote_port'], - 'QUERY_STRING' => isset($request->server['query_string']) ? - $request->server['query_string'] : '', - // Headers - 'HTTP_HOST' => $request->header['host'], - 'HTTP_USER_AGENT' => isset($request->header['user-agent']) ? - $request->header['user-agent'] : '', - 'HTTP_ACCEPT' => isset($request->header['accept']) ? - $request->header['accept'] : '*/*', - 'HTTP_ACCEPT_LANGUAGE' => isset($request->header['accept-language']) ? - $request->header['accept-language'] : '', - 'HTTP_ACCEPT_ENCODING' => isset($request->header['accept-encoding']) ? - $request->header['accept-encoding'] : '', - 'HTTP_CONNECTION' => isset($request->header['connection']) ? - $request->header['connection'] : '', - 'HTTP_CACHE_CONTROL' => isset($request->header['cache-control']) ? - $request->header['cache-control'] : '', - ]; + $server = []; + foreach ($request->server as $key => $value) { + $server[strtoupper($key)] = $value; + } $headers = []; foreach ($request->header as $name => $value) { From 08a46aa9ac1ded059f26c86f7f479f8dc03be403 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Tue, 5 Jun 2018 17:35:32 +0200 Subject: [PATCH 04/12] Added Swoole emitter --- src/Response/SwooleEmitter.php | 84 ++++++++++++++++++++++++++++ test/Response/SwooleEmitterTest.php | 87 +++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 src/Response/SwooleEmitter.php create mode 100644 test/Response/SwooleEmitterTest.php diff --git a/src/Response/SwooleEmitter.php b/src/Response/SwooleEmitter.php new file mode 100644 index 00000000..431501e3 --- /dev/null +++ b/src/Response/SwooleEmitter.php @@ -0,0 +1,84 @@ +swooleResponse = $swooleResponse; + } + + /** + * Emits a response for the Swoole environment. + * + * @param ResponseInterface $response + */ + public function emit(ResponseInterface $response) + { + $this->emitStatusCode($response); + $this->emitHeaders($response); + $this->emitBody($response); + } + + /** + * Emit the message body. + * + * @param ResponseInterface $response + */ + private function emitBody(ResponseInterface $response) + { + $body = $response->getBody(); + $body->rewind(); + if ($body->getSize() > static::CHUNK_SIZE) { + while (! $body->eof()) { + $this->swooleResponse->write($body->read(static::CHUNK_SIZE)); + } + $this->swooleResponse->end(); + } else { + $this->swooleResponse->end($body->getContents()); + } + } + + /** + * Emit the headers + * + * @param ResponseInterface $response + */ + private function emitHeaders(ResponseInterface $response) + { + foreach ($response->getHeaders() as $name => $values) { + $name = $this->filterHeader($name); + $this->swooleResponse->header($name, implode(', ', $values)); + } + } + + /** + * Emit the status code + * + * @param ResponseInterface $response + */ + private function emitStatusCode(ResponseInterface $response) + { + $this->swooleResponse->status($response->getStatusCode()); + } +} diff --git a/test/Response/SwooleEmitterTest.php b/test/Response/SwooleEmitterTest.php new file mode 100644 index 00000000..7218540f --- /dev/null +++ b/test/Response/SwooleEmitterTest.php @@ -0,0 +1,87 @@ +swooleResponse = $this->prophesize(swoole_http_response::class); + $this->emitter = new SwooleEmitter($this->swooleResponse->reveal()); + } + + public function testEmit() + { + $response = (new Response()) + ->withStatus(200) + ->withAddedHeader('Content-Type', 'text/plain'); + $response->getBody()->write('Content!'); + + $this->emitter->emit($response); + + $this->swooleResponse->status(200)->shouldHaveBeenCalled(); + $this->swooleResponse->header('Content-Type', 'text/plain') + ->shouldHaveBeenCalled(); + $this->swooleResponse->end('Content!')->shouldHaveBeenCalled(); + } + + public function testMultipleHeaders() + { + $response = (new Response()) + ->withStatus(200) + ->withHeader('Content-Type', 'text/plain') + ->withHeader('Content-Length', '256'); + + $this->emitter->emit($response); + + $this->swooleResponse->status(200)->shouldHaveBeenCalled(); + $this->swooleResponse->header('Content-Type', 'text/plain') + ->shouldHaveBeenCalled(); + $this->swooleResponse->header('Content-Length', '256') + ->shouldHaveBeenCalled(); + } + + public function testMultipleSetCookieHeaders() + { + $response = (new Response()) + ->withStatus(200) + ->withAddedHeader('Set-Cookie', 'foo=bar') + ->withAddedHeader('Set-Cookie', 'bar=baz'); + + $this->emitter->emit($response); + + $this->swooleResponse->status(200)->shouldHaveBeenCalled(); + $this->swooleResponse->header('Set-Cookie', 'foo=bar, bar=baz') + ->shouldHaveBeenCalled(); + } + + public function testEmitWithBigContentBody() + { + $content = base64_encode(random_bytes(SwooleEmitter::CHUNK_SIZE)); // CHUNK_SIZE * 1.33333 + $response = (new Response()) + ->withStatus(200) + ->withAddedHeader('Content-Type', 'text/plain'); + $response->getBody()->write($content); + + $this->emitter->emit($response); + + $this->swooleResponse->status(200)->shouldHaveBeenCalled(); + $this->swooleResponse->header('Content-Type', 'text/plain') + ->shouldHaveBeenCalled(); + $this->swooleResponse->write(substr($content, 0, SwooleEmitter::CHUNK_SIZE)) + ->shouldHaveBeenCalled(); + $this->swooleResponse->write(substr($content, SwooleEmitter::CHUNK_SIZE)) + ->shouldHaveBeenCalled(); + $this->swooleResponse->end()->shouldHaveBeenCalled(); + } +} From 2cecc83bff0c12cdbc6c624ecbacea38d4173103 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Tue, 5 Jun 2018 18:07:42 +0200 Subject: [PATCH 05/12] Added the test for swoole extension in test --- test/Response/SwooleEmitterTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Response/SwooleEmitterTest.php b/test/Response/SwooleEmitterTest.php index 7218540f..f2f67f9d 100644 --- a/test/Response/SwooleEmitterTest.php +++ b/test/Response/SwooleEmitterTest.php @@ -16,6 +16,9 @@ class SwooleEmitterTest extends TestCase { public function setUp() { + if (! extension_loaded('swoole')) { + $this->markTestSkipped('The Swoole extesion is not available'); + } $this->swooleResponse = $this->prophesize(swoole_http_response::class); $this->emitter = new SwooleEmitter($this->swooleResponse->reveal()); } From 30db0ca088ca26b5356574781bb5a531661d2e14 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 5 Jun 2018 15:59:44 -0500 Subject: [PATCH 06/12] Consistency changes and expectation updates `ServerRequestFactory` was incorrectly passing the body to the new request constructor. The content being passed was string content, and needs to be either a stream URI, resource, or `StreamInterface` instance. Test was updated to demonstrate this, and the factory was updated to create the stream. A number of consistency updates were also made: - License annotation in headers of affected files. - Flow of mock expectations. --- src/Response/SwooleEmitter.php | 46 +++++++++++---------- src/ServerRequestFactory.php | 8 +++- test/Response/SwooleEmitterTest.php | 63 +++++++++++++++++++---------- test/ServerRequestFactoryTest.php | 8 ++-- 4 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/Response/SwooleEmitter.php b/src/Response/SwooleEmitter.php index 431501e3..903e9f08 100644 --- a/src/Response/SwooleEmitter.php +++ b/src/Response/SwooleEmitter.php @@ -1,7 +1,7 @@ getBody(); - $body->rewind(); - if ($body->getSize() > static::CHUNK_SIZE) { - while (! $body->eof()) { - $this->swooleResponse->write($body->read(static::CHUNK_SIZE)); - } - $this->swooleResponse->end(); - } else { - $this->swooleResponse->end($body->getContents()); - } + $this->swooleResponse->status($response->getStatusCode()); } /** * Emit the headers * - * @param ResponseInterface $response + * @return void */ private function emitHeaders(ResponseInterface $response) { @@ -73,12 +66,23 @@ private function emitHeaders(ResponseInterface $response) } /** - * Emit the status code + * Emit the message body. * - * @param ResponseInterface $response + * @return void */ - private function emitStatusCode(ResponseInterface $response) + private function emitBody(ResponseInterface $response) { - $this->swooleResponse->status($response->getStatusCode()); + $body = $response->getBody(); + $body->rewind(); + + if ($body->getSize() <= static::CHUNK_SIZE) { + $this->swooleResponse->end($body->getContents()); + return; + } + + while (! $body->eof()) { + $this->swooleResponse->write($body->read(static::CHUNK_SIZE)); + } + $this->swooleResponse->end(); } } diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index ecabc070..112c19c9 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -1,7 +1,7 @@ write($request->rawContent()); + $body->rewind(); + return new ServerRequest( $server, static::normalizeFiles($files), static::marshalUriFromServer($server, $headers), $server['REQUEST_METHOD'], - $request->rawContent(), + $body, $headers, $cookie, $get, diff --git a/test/Response/SwooleEmitterTest.php b/test/Response/SwooleEmitterTest.php index f2f67f9d..760a32b6 100644 --- a/test/Response/SwooleEmitterTest.php +++ b/test/Response/SwooleEmitterTest.php @@ -1,7 +1,7 @@ markTestSkipped('The Swoole extesion is not available'); + $this->markTestSkipped('The Swoole extension is not available'); } $this->swooleResponse = $this->prophesize(swoole_http_response::class); $this->emitter = new SwooleEmitter($this->swooleResponse->reveal()); @@ -32,10 +32,15 @@ public function testEmit() $this->emitter->emit($response); - $this->swooleResponse->status(200)->shouldHaveBeenCalled(); - $this->swooleResponse->header('Content-Type', 'text/plain') - ->shouldHaveBeenCalled(); - $this->swooleResponse->end('Content!')->shouldHaveBeenCalled(); + $this->swooleResponse + ->status(200) + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->header('Content-Type', 'text/plain') + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->end('Content!') + ->shouldHaveBeenCalled(); } public function testMultipleHeaders() @@ -47,11 +52,15 @@ public function testMultipleHeaders() $this->emitter->emit($response); - $this->swooleResponse->status(200)->shouldHaveBeenCalled(); - $this->swooleResponse->header('Content-Type', 'text/plain') - ->shouldHaveBeenCalled(); - $this->swooleResponse->header('Content-Length', '256') - ->shouldHaveBeenCalled(); + $this->swooleResponse + ->status(200) + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->header('Content-Type', 'text/plain') + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->header('Content-Length', '256') + ->shouldHaveBeenCalled(); } public function testMultipleSetCookieHeaders() @@ -63,9 +72,12 @@ public function testMultipleSetCookieHeaders() $this->emitter->emit($response); - $this->swooleResponse->status(200)->shouldHaveBeenCalled(); - $this->swooleResponse->header('Set-Cookie', 'foo=bar, bar=baz') - ->shouldHaveBeenCalled(); + $this->swooleResponse + ->status(200) + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->header('Set-Cookie', 'foo=bar, bar=baz') + ->shouldHaveBeenCalled(); } public function testEmitWithBigContentBody() @@ -78,13 +90,20 @@ public function testEmitWithBigContentBody() $this->emitter->emit($response); - $this->swooleResponse->status(200)->shouldHaveBeenCalled(); - $this->swooleResponse->header('Content-Type', 'text/plain') - ->shouldHaveBeenCalled(); - $this->swooleResponse->write(substr($content, 0, SwooleEmitter::CHUNK_SIZE)) - ->shouldHaveBeenCalled(); - $this->swooleResponse->write(substr($content, SwooleEmitter::CHUNK_SIZE)) - ->shouldHaveBeenCalled(); - $this->swooleResponse->end()->shouldHaveBeenCalled(); + $this->swooleResponse + ->status(200) + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->header('Content-Type', 'text/plain') + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->write(substr($content, 0, SwooleEmitter::CHUNK_SIZE)) + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->write(substr($content, SwooleEmitter::CHUNK_SIZE)) + ->shouldHaveBeenCalled(); + $this->swooleResponse + ->end() + ->shouldHaveBeenCalled(); } } diff --git a/test/ServerRequestFactoryTest.php b/test/ServerRequestFactoryTest.php index 36b9f06b..e690b83c 100644 --- a/test/ServerRequestFactoryTest.php +++ b/test/ServerRequestFactoryTest.php @@ -1,7 +1,7 @@ markTestSkipped('The Swoole extesion is not available'); + $this->markTestSkipped('The Swoole extension is not available'); } $swooleRequest = $this->createMock(swoole_http_request::class); @@ -597,7 +597,7 @@ public function testFromSwoole() $swooleRequest->server['server_protocol'] = 'HTTP/1.1'; $swooleRequest->server['server_port'] = 9501; $swooleRequest->server['remote_port'] = 45314; - $swooleRequest->method('rawContent')->willReturn('php://input'); + $swooleRequest->method('rawContent')->willReturn('some content'); $request = ServerRequestFactory::fromSwoole($swooleRequest); $this->assertInstanceOf(ServerRequest::class, $request); @@ -605,7 +605,7 @@ public function testFromSwoole() $this->assertEquals('/', $request->getRequestTarget()); $this->assertEquals('/', $request->getUri()->getPath()); $this->assertEquals('1.1', $request->getProtocolVersion()); - $this->assertEquals('', $request->getBody()); + $this->assertEquals('some content', $request->getBody()); $this->assertEquals([], $request->getCookieParams()); $this->assertEquals([], $request->getQueryParams()); $this->assertEquals([], $request->getUploadedFiles()); From 56ae7d26d65c9effbdbe2de70f0bcc24745a0845 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 5 Jun 2018 16:00:46 -0500 Subject: [PATCH 07/12] Install swoole extension in CI environment But only in PHP 7 environments. Since the package has required prompts, this uses a script that has values for each prompt. --- .travis.yml | 11 ++++++++++- .travis/install_swoole.sh | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100755 .travis/install_swoole.sh diff --git a/.travis.yml b/.travis.yml index d6e27d3f..dde9eb8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,36 +26,45 @@ matrix: - php: 7 env: - DEPS=lowest + - INSTALL_SWOOLE=true - php: 7 env: - DEPS=locked - CHECK_CS=true - TEST_COVERAGE=true + - INSTALL_SWOOLE=true - php: 7 env: - DEPS=latest + - INSTALL_SWOOLE=true - php: 7.1 env: - DEPS=lowest + - INSTALL_SWOOLE=true - php: 7.1 env: - DEPS=locked + - INSTALL_SWOOLE=true - php: 7.1 env: - DEPS=latest + - INSTALL_SWOOLE=true - php: 7.2 env: - DEPS=lowest + - INSTALL_SWOOLE=true - php: 7.2 env: - DEPS=locked + - INSTALL_SWOOLE=true - php: 7.2 env: - DEPS=latest + - INSTALL_SWOOLE=true before_install: - if [[ $TEST_COVERAGE != 'true' && "$(php --version | grep xdebug -ci)" -ge 1 ]]; then phpenv config-rm xdebug.ini || return 0 ; fi - - travis_retry composer self-update + - if [[ $INSTALL_SWOOLE == 'true' ]]; then ./.travis/install_swoole.sh ; fi install: - travis_retry composer install $COMPOSER_ARGS --ignore-platform-reqs diff --git a/.travis/install_swoole.sh b/.travis/install_swoole.sh new file mode 100755 index 00000000..6a08e99d --- /dev/null +++ b/.travis/install_swoole.sh @@ -0,0 +1,10 @@ +#!/bin/bash +pecl install swoole << EOF +`#enable debug/trace log support? [no] :` +`#enable sockets supports? [no] :`y +`#enable openssl support? [no] :` +`#enable http2 support? [no] :` +`#enable async-redis support? [no] :` +`#enable mysqlnd support? [no] :` +`#enable postgresql coroutine client support? [no] :` +EOF From c557ee66e2559aed8c91cc74521e442f17607fef Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Wed, 6 Jun 2018 15:12:04 +0200 Subject: [PATCH 08/12] Added the documentation for Swoole --- doc/book/swoole.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 55 insertions(+) create mode 100644 doc/book/swoole.md diff --git a/doc/book/swoole.md b/doc/book/swoole.md new file mode 100644 index 00000000..ae8beaf5 --- /dev/null +++ b/doc/book/swoole.md @@ -0,0 +1,54 @@ +# Swoole +use Zend\Diactoros\ServerRequestFactory; + +[Swoole](https://www.swoole.co.uk/) is an async programming Framework for PHP +that can be used to create high performance HTTP server applications, e.g. web +APIs. We provided the support of Swoole in `zend-diactoros` using two methods to +convert a [Swoole\Http\Request](http://php.net/manual/en/class.swoole-http-request.php) +in a [PSR-7 Request](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface) +and a [PSR-7 Response](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface) +in a [Swoole\Http\Response](http://php.net/manual/en/class.swoole-http-response.php). + +To convert a Swoole request in PSR-7 we provided the following static function: + +``` +Zend\Diactoros\ServerRequestFactory::fromSwoole(swoole_http_request $request) +``` + +Where `$request` is an instance of `swoole_http_request` (alias of +`Swoole\Http\Request`). + +To convert a PSR-7 response in a Swoole response we built a specific emitter, +`Zend\Diactoros\Response\SwooleEmitter`. If you can use this emitter instead of +`Zend\Diactoros\Response\SapiEmitter`. + +You need to instantiate the `SwooleEmitter` passing a `swoole_http_response` +object in the constructor. + +You can execute Swoole with [Expressive](https://getexpressive.org/) using the +following server implementation: + +```php +use Zend\Diactoros\ServerRequestFactory; +use Zend\Diactoros\Response\SwooleEmitter; + +$http = new swoole_http_server("127.0.0.1", 9501); + +$http->on("start", function ($server) { + echo "Swoole http server is started at http://127.0.0.1:9501\n"; +}); + +// Bootstrap of zend-expressive +$container = require 'config/container.php'; +$app = $container->get(\Zend\Expressive\Application::class); + +$http->on("request", function ($request, $response) use ($app) { + $psr7Request = ServerRequestFactory::fromSwoole($request); + $psr7Response = $app->handle($psr7Request); + + $emitter = new SwooleEmitter($response); + $emitter->emit($psr7Response); +}); + +$http->start(); +``` diff --git a/mkdocs.yml b/mkdocs.yml index a0866064..0cfc4c65 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,6 +10,7 @@ pages: - "Emitting Responses": emitting-responses.md - Serialization: serialization.md - API: api.md + - Swoole: swoole.md site_name: zend-diactoros site_description: 'zend-diactoros: PSR-7 HTTP message implementation' repo_url: 'https://github.com/zendframework/zend-diactoros' From cb21c21d33fd4b74f9045aaa67b0d8fbee2bba48 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Tue, 12 Jun 2018 16:15:48 +0200 Subject: [PATCH 09/12] Added a SwooleStream class and removed Swoole Emitter --- src/Response/SwooleEmitter.php | 88 ------------- src/ServerRequestFactory.php | 20 +-- src/SwooleStream.php | 189 ++++++++++++++++++++++++++++ test/Response/SwooleEmitterTest.php | 109 ---------------- test/SwooleStreamTest.php | 176 ++++++++++++++++++++++++++ 5 files changed, 375 insertions(+), 207 deletions(-) delete mode 100644 src/Response/SwooleEmitter.php create mode 100644 src/SwooleStream.php delete mode 100644 test/Response/SwooleEmitterTest.php create mode 100644 test/SwooleStreamTest.php diff --git a/src/Response/SwooleEmitter.php b/src/Response/SwooleEmitter.php deleted file mode 100644 index 903e9f08..00000000 --- a/src/Response/SwooleEmitter.php +++ /dev/null @@ -1,88 +0,0 @@ -swooleResponse = $swooleResponse; - } - - /** - * Emits a response for the Swoole environment. - * - * @return void - */ - public function emit(ResponseInterface $response) - { - $this->emitStatusCode($response); - $this->emitHeaders($response); - $this->emitBody($response); - } - - /** - * Emit the status code - * - * @return void - */ - private function emitStatusCode(ResponseInterface $response) - { - $this->swooleResponse->status($response->getStatusCode()); - } - - /** - * Emit the headers - * - * @return void - */ - private function emitHeaders(ResponseInterface $response) - { - foreach ($response->getHeaders() as $name => $values) { - $name = $this->filterHeader($name); - $this->swooleResponse->header($name, implode(', ', $values)); - } - } - - /** - * Emit the message body. - * - * @return void - */ - private function emitBody(ResponseInterface $response) - { - $body = $response->getBody(); - $body->rewind(); - - if ($body->getSize() <= static::CHUNK_SIZE) { - $this->swooleResponse->end($body->getContents()); - return; - } - - while (! $body->eof()) { - $this->swooleResponse->write($body->read(static::CHUNK_SIZE)); - } - $this->swooleResponse->end(); - } -} diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index 112c19c9..c5815ae6 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -120,25 +120,25 @@ public static function fromSwoole(swoole_http_request $request) $files = isset($request->files) ? $request->files : []; $server = []; - foreach ($request->server as $key => $value) { - $server[strtoupper($key)] = $value; + if (isset($request->server)) { + foreach ($request->server as $key => $value) { + $server[strtoupper($key)] = $value; + } } $headers = []; - foreach ($request->header as $name => $value) { - $headers[str_replace('-', '_', $name)] = $value; + if (isset($request->header)) { + foreach ($request->header as $name => $value) { + $headers[str_replace('-', '_', $name)] = $value; + } } - $body = new Stream('php://temp', 'wb+'); - $body->write($request->rawContent()); - $body->rewind(); - return new ServerRequest( $server, static::normalizeFiles($files), static::marshalUriFromServer($server, $headers), - $server['REQUEST_METHOD'], - $body, + $server['REQUEST_METHOD'] ?? 'GET', + new SwooleStream($request), $headers, $cookie, $get, diff --git a/src/SwooleStream.php b/src/SwooleStream.php new file mode 100644 index 00000000..a01c45bb --- /dev/null +++ b/src/SwooleStream.php @@ -0,0 +1,189 @@ +request = $request; + } + + /** + * {@inheritdoc} + */ + public function getContents() + { + if (! isset($this->body)) { + $this->body = $this->request->rawcontent(); + } + return $this->body; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->getContents(); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + if (! isset($this->bodySize)) { + $this->bodySize = strlen($this->getContents()); + } + return $this->bodySize; + } + + /** + * {@inheritdoc} + */ + public function tell() + { + return $this->index; + } + + /** + * {@inheritdoc} + */ + public function eof() + { + return $this->index >= $this->getSize() - 1; + } + + /** + * {@inheritdoc} + */ + public function isReadable() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($length) + { + $result = substr($this->getContents(), $this->index, $length); + $this->index += $length; + return $result; + } + + /** + * {@inheritdoc} + */ + public function isSeekable() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + + switch ($whence) { + case SEEK_SET: + if ($offset >= $this->getSize()) { + throw new RuntimeException( + 'Offset cannot be longer than content size' + ); + } + $this->index = $offset; + break; + case SEEK_CUR: + if ($offset + $this->index >= $this->getSize()) { + throw new RuntimeException( + 'Offset + current position cannot be longer than content size' + ); + } + $this->index += $offset; + break; + case SEEK_END: + if ($offset + $this->getSize() >= $this->getSize()) { + throw new RuntimeException( + 'Offset must be a negative number to be under the content size' + ); + } + $this->index = $this->getSize() - 1 + $offset; + break; + } + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->index = 0; + } + + /** + * {@inheritdoc} + */ + public function isWritable() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function write($string) + { + throw new RuntimeException('Stream is not writable'); + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = null) + { + return null; + } + + /** + * {@inheritdoc} + */ + public function detach() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return null; + } +} diff --git a/test/Response/SwooleEmitterTest.php b/test/Response/SwooleEmitterTest.php deleted file mode 100644 index 760a32b6..00000000 --- a/test/Response/SwooleEmitterTest.php +++ /dev/null @@ -1,109 +0,0 @@ -markTestSkipped('The Swoole extension is not available'); - } - $this->swooleResponse = $this->prophesize(swoole_http_response::class); - $this->emitter = new SwooleEmitter($this->swooleResponse->reveal()); - } - - public function testEmit() - { - $response = (new Response()) - ->withStatus(200) - ->withAddedHeader('Content-Type', 'text/plain'); - $response->getBody()->write('Content!'); - - $this->emitter->emit($response); - - $this->swooleResponse - ->status(200) - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->header('Content-Type', 'text/plain') - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->end('Content!') - ->shouldHaveBeenCalled(); - } - - public function testMultipleHeaders() - { - $response = (new Response()) - ->withStatus(200) - ->withHeader('Content-Type', 'text/plain') - ->withHeader('Content-Length', '256'); - - $this->emitter->emit($response); - - $this->swooleResponse - ->status(200) - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->header('Content-Type', 'text/plain') - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->header('Content-Length', '256') - ->shouldHaveBeenCalled(); - } - - public function testMultipleSetCookieHeaders() - { - $response = (new Response()) - ->withStatus(200) - ->withAddedHeader('Set-Cookie', 'foo=bar') - ->withAddedHeader('Set-Cookie', 'bar=baz'); - - $this->emitter->emit($response); - - $this->swooleResponse - ->status(200) - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->header('Set-Cookie', 'foo=bar, bar=baz') - ->shouldHaveBeenCalled(); - } - - public function testEmitWithBigContentBody() - { - $content = base64_encode(random_bytes(SwooleEmitter::CHUNK_SIZE)); // CHUNK_SIZE * 1.33333 - $response = (new Response()) - ->withStatus(200) - ->withAddedHeader('Content-Type', 'text/plain'); - $response->getBody()->write($content); - - $this->emitter->emit($response); - - $this->swooleResponse - ->status(200) - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->header('Content-Type', 'text/plain') - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->write(substr($content, 0, SwooleEmitter::CHUNK_SIZE)) - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->write(substr($content, SwooleEmitter::CHUNK_SIZE)) - ->shouldHaveBeenCalled(); - $this->swooleResponse - ->end() - ->shouldHaveBeenCalled(); - } -} diff --git a/test/SwooleStreamTest.php b/test/SwooleStreamTest.php new file mode 100644 index 00000000..22e3396e --- /dev/null +++ b/test/SwooleStreamTest.php @@ -0,0 +1,176 @@ +request = $this->prophesize(swoole_http_request::class); + $this->request + ->rawcontent() + ->willReturn(self::DEFAULT_CONTENT); + + $this->stream = new SwooleStream($this->request->reveal()); + } + + public function testConstructor() + { + $this->assertInstanceOf(StreamInterface::class, $this->stream); + } + + public function testGetContents() + { + $this->assertEquals(self::DEFAULT_CONTENT, $this->stream->getContents()); + } + + public function testGetContentsWithEmptyBody() + { + $this->request + ->rawcontent() + ->willReturn(''); + $this->stream = new SwooleStream($this->request->reveal()); + + $this->assertEquals('', $this->stream->getContents()); + } + + public function testToString() + { + $this->assertEquals(self::DEFAULT_CONTENT, (string) $this->stream); + } + + public function testGetSize() + { + $this->assertEquals( + strlen(self::DEFAULT_CONTENT), + $this->stream->getSize() + ); + } + + public function testGetSizeWithEmptyBody() + { + $this->request + ->rawcontent() + ->willReturn(''); + $this->stream = new SwooleStream($this->request->reveal()); + + $this->assertEquals(0, $this->stream->getSize()); + } + + public function testTell() + { + $tot = strlen(self::DEFAULT_CONTENT); + for ($i = 0; $i < strlen(self::DEFAULT_CONTENT); $i++) { + $this->stream->seek($i); + $this->assertEquals($i, $this->stream->tell()); + } + } + + public function testEof() + { + $this->assertFalse($this->stream->eof()); + $this->stream->seek($this->stream->getSize() - 1); + $this->assertTrue($this->stream->eof()); + } + + public function testIsReadable() + { + $this->assertTrue($this->stream->isReadable()); + } + + public function testRead() + { + $result = $this->stream->read(4); + $this->assertEquals(substr(self::DEFAULT_CONTENT, 0, 4), $result); + $this->assertEquals(4, $this->stream->tell()); + } + + public function testIsSeekable() + { + $this->assertTrue($this->stream->isSeekable()); + } + + public function testSeek() + { + $this->stream->seek(4); + $this->assertEquals(4, $this->stream->tell()); + $this->stream->seek(1, SEEK_CUR); + $this->assertEquals(5, $this->stream->tell()); + $this->stream->seek(-1, SEEK_END); + $this->assertEquals(strlen(self::DEFAULT_CONTENT) - 2, $this->stream->tell()); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Offset cannot be longer than content size + */ + public function testSeekSetOverflow() + { + $this->stream->seek(strlen(self::DEFAULT_CONTENT)); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Offset + current position cannot be longer than content size + */ + public function testSeekCurOverflow() + { + $this->stream->seek(strlen(self::DEFAULT_CONTENT), SEEK_CUR); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Offset must be a negative number to be under the content size + */ + public function testSeekEndOverflow() + { + $this->stream->seek(1, SEEK_END); + } + + public function testRewind() + { + $this->stream->rewind(); + $this->assertEquals(0, $this->stream->tell()); + } + + public function testIsWritable() + { + $this->assertFalse($this->stream->isWritable()); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Stream is not writable + */ + public function testWrite() + { + $this->stream->write('Hello!'); + } + + public function testGetMetadata() + { + $this->assertNull($this->stream->getMetadata()); + } + + public function testDetach() + { + $this->assertNull($this->stream->detach()); + } + + public function testClose() + { + $this->assertNull($this->stream->close()); + } +} From 9dda5f929e5981a539dd8b26f152691034128bcf Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Tue, 12 Jun 2018 16:21:44 +0200 Subject: [PATCH 10/12] Fixed the Swoole docs --- doc/book/swoole.md | 46 ++++------------------------------------------ 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/doc/book/swoole.md b/doc/book/swoole.md index ae8beaf5..42d592fc 100644 --- a/doc/book/swoole.md +++ b/doc/book/swoole.md @@ -1,15 +1,12 @@ # Swoole -use Zend\Diactoros\ServerRequestFactory; [Swoole](https://www.swoole.co.uk/) is an async programming Framework for PHP that can be used to create high performance HTTP server applications, e.g. web -APIs. We provided the support of Swoole in `zend-diactoros` using two methods to -convert a [Swoole\Http\Request](http://php.net/manual/en/class.swoole-http-request.php) -in a [PSR-7 Request](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface) -and a [PSR-7 Response](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface) -in a [Swoole\Http\Response](http://php.net/manual/en/class.swoole-http-response.php). +APIs. We provided the support of Swoole in `zend-diactoros` using a static +method to convert a [Swoole\Http\Request](http://php.net/manual/en/class.swoole-http-request.php) +in a [PSR-7 Request](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface). -To convert a Swoole request in PSR-7 we provided the following static function: +This method is as follows: ``` Zend\Diactoros\ServerRequestFactory::fromSwoole(swoole_http_request $request) @@ -17,38 +14,3 @@ Zend\Diactoros\ServerRequestFactory::fromSwoole(swoole_http_request $request) Where `$request` is an instance of `swoole_http_request` (alias of `Swoole\Http\Request`). - -To convert a PSR-7 response in a Swoole response we built a specific emitter, -`Zend\Diactoros\Response\SwooleEmitter`. If you can use this emitter instead of -`Zend\Diactoros\Response\SapiEmitter`. - -You need to instantiate the `SwooleEmitter` passing a `swoole_http_response` -object in the constructor. - -You can execute Swoole with [Expressive](https://getexpressive.org/) using the -following server implementation: - -```php -use Zend\Diactoros\ServerRequestFactory; -use Zend\Diactoros\Response\SwooleEmitter; - -$http = new swoole_http_server("127.0.0.1", 9501); - -$http->on("start", function ($server) { - echo "Swoole http server is started at http://127.0.0.1:9501\n"; -}); - -// Bootstrap of zend-expressive -$container = require 'config/container.php'; -$app = $container->get(\Zend\Expressive\Application::class); - -$http->on("request", function ($request, $response) use ($app) { - $psr7Request = ServerRequestFactory::fromSwoole($request); - $psr7Response = $app->handle($psr7Request); - - $emitter = new SwooleEmitter($response); - $emitter->emit($psr7Response); -}); - -$http->start(); -``` From 66c56a0531fe94f8874f1217e47481b111111dd3 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Tue, 12 Jun 2018 16:47:50 +0200 Subject: [PATCH 11/12] Fixed PHP 5.6 compatibility --- src/ServerRequestFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index c5815ae6..93ec1416 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -137,7 +137,7 @@ public static function fromSwoole(swoole_http_request $request) $server, static::normalizeFiles($files), static::marshalUriFromServer($server, $headers), - $server['REQUEST_METHOD'] ?? 'GET', + isset($server['REQUEST_METHOD']) ? $server['REQUEST_METHOD'] : 'GET', new SwooleStream($request), $headers, $cookie, From 92624044575258926d909616f312c892d21e10ef Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Tue, 12 Jun 2018 16:56:17 +0200 Subject: [PATCH 12/12] Added the Swoole skip test --- test/SwooleStreamTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/SwooleStreamTest.php b/test/SwooleStreamTest.php index 22e3396e..ade82cd7 100644 --- a/test/SwooleStreamTest.php +++ b/test/SwooleStreamTest.php @@ -18,6 +18,9 @@ class SwooleStreamTest extends TestCase public function setUp() { + if (! extension_loaded('swoole')) { + $this->markTestSkipped('The Swoole extension is not available'); + } $this->request = $this->prophesize(swoole_http_request::class); $this->request ->rawcontent()