diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 7e81cef..6e2581c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -5,4 +5,8 @@ parameters: - src/ - tests/ + fileExtensions: + - php + - phpt + reportUnmatchedIgnoredErrors: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fb514a6..acf5e2b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,8 @@ ./tests/ - ./tests/integration/ + ./tests/ + ./tests/integration/vendor/ diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index a35d140..8113110 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -9,7 +9,8 @@ ./tests/ - ./tests/install-as-dep/ + ./tests/ + ./tests/integration/vendor/ diff --git a/src/App.php b/src/App.php index 808e3d9..519e157 100644 --- a/src/App.php +++ b/src/App.php @@ -5,8 +5,6 @@ use FrameworkX\Io\MiddlewareHandler; use FrameworkX\Io\RedirectHandler; use FrameworkX\Io\RouteHandler; -use FrameworkX\Runner\HttpServerRunner; -use FrameworkX\Runner\SapiRunner; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use React\Http\Message\Response; @@ -22,7 +20,7 @@ class App /** @var RouteHandler */ private $router; - /** @var HttpServerRunner|SapiRunner|callable(callable(ServerRequestInterface):(ResponseInterface|PromiseInterface)):void */ + /** @var callable(callable(ServerRequestInterface):(ResponseInterface|PromiseInterface)):void */ private $runner; /** @@ -257,8 +255,8 @@ public function redirect(string $route, string $target, int $code = Response::ST * the `X_EXPERIMENTAL_RUNNER` environment variable to the desired runner * class name ({@see Container::getRunner()}). * - * @see HttpServerRunner::__invoke() - * @see SapiRunner::__invoke() + * @see \FrameworkX\Runner\HttpServerRunner::__invoke() + * @see \FrameworkX\Runner\SapiRunner::__invoke() * @see Container::getRunner() */ public function run(): void diff --git a/src/Runner/NullRunner.php b/src/Runner/NullRunner.php new file mode 100644 index 0000000..8fc5e17 --- /dev/null +++ b/src/Runner/NullRunner.php @@ -0,0 +1,41 @@ + fn(?string $X_EXPERIMENTAL_RUNNER = null): ?string => $X_EXPERIMENTAL_RUNNER, + * // 'X_EXPERIMENTAL_RUNNER' => fn(bool|string $ACME = false): ?string => $ACME ? NullRunner::class : null, + * 'X_EXPERIMENTAL_RUNNER' => NullRunner::class + * ]); + * + * $app = new App($container); + * ``` + * + * Likewise, you may pass this runner through an environment variable from your + * integration tests, see also included PHPT test files for examples. + * + * @see \FrameworkX\Container::getRunner() + */ +class NullRunner +{ + /** + * @param callable(\Psr\Http\Message\ServerRequestInterface):(\Psr\Http\Message\ResponseInterface|\React\Promise\PromiseInterface<\Psr\Http\Message\ResponseInterface>) $handler + * @return void + */ + public function __invoke(callable $handler): void + { + // NO-OP + } +} diff --git a/tests/AppTest.php b/tests/AppTest.php index 2b8eb52..fee95df 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -9,6 +9,7 @@ use FrameworkX\Io\MiddlewareHandler; use FrameworkX\Io\RouteHandler; use FrameworkX\Runner\HttpServerRunner; +use FrameworkX\Runner\NullRunner; use FrameworkX\Tests\Fixtures\InvalidAbstract; use FrameworkX\Tests\Fixtures\InvalidConstructorInt; use FrameworkX\Tests\Fixtures\InvalidConstructorIntersection; @@ -949,6 +950,18 @@ public function testRunWillInvokeCustomRunnerFromContainerEnvironmentVariable(): $app->run(); } + public function testRunReturnsImmediatelyWithNullRunnerFromContainerEnvironmentVariable(): void + { + $container = new Container([ + 'X_EXPERIMENTAL_RUNNER' => NullRunner::class + ]); + + $app = new App($container); + + $this->expectOutputString(''); + $app->run(); + } + public function testGetMethodAddsGetRouteOnRouter(): void { $router = $this->createMock(RouteHandler::class); diff --git a/tests/Runner/NullRunnerTest.php b/tests/Runner/NullRunnerTest.php new file mode 100644 index 0000000..eda32dd --- /dev/null +++ b/tests/Runner/NullRunnerTest.php @@ -0,0 +1,19 @@ +expectOutputString(''); + $runner(function () { + throw new \BadFunctionCallException('Should not be called'); + }); + } +} diff --git a/tests/integration/public/index.php b/tests/integration/public/index.php index 8bca0ce..1fceb3a 100644 --- a/tests/integration/public/index.php +++ b/tests/integration/public/index.php @@ -26,7 +26,14 @@ function asleep(float $s): PromiseInterface }); } -$app = new FrameworkX\App(); +$container = new FrameworkX\Container([ + FrameworkX\AccessLogHandler::class => function (?string $X_EXPERIMENTAL_RUNNER = null) { + // log to /dev/null when running in experimental runner mode to avoid cluttering output + return new FrameworkX\AccessLogHandler($X_EXPERIMENTAL_RUNNER !== null ? (DIRECTORY_SEPARATOR !== '\\' ? '/dev/null' : __DIR__ . '\\nul') : null); + } +]); + +$app = new FrameworkX\App($container); $app->get('/', function () { return React\Http\Message\Response::plaintext( diff --git a/tests/integration/tests/AppInvokeIndexReturnsResponse.phpt b/tests/integration/tests/AppInvokeIndexReturnsResponse.phpt new file mode 100644 index 0000000..4db264f --- /dev/null +++ b/tests/integration/tests/AppInvokeIndexReturnsResponse.phpt @@ -0,0 +1,25 @@ +--TEST-- +Loading index file with NullRunner allows invoking the app +--INI-- +# suppress legacy PHPUnit 7 warning for Xdebug 3 +xdebug.default_enable= +--ENV-- +X_EXPERIMENTAL_RUNNER=FrameworkX\Runner\NullRunner +--FILE-- +getBody(); + +?> +--EXPECT-- +Hello world! diff --git a/tests/integration/tests/AppStopsWithoutOutputWithNullRunner.phpt b/tests/integration/tests/AppStopsWithoutOutputWithNullRunner.phpt new file mode 100644 index 0000000..80bf4b7 --- /dev/null +++ b/tests/integration/tests/AppStopsWithoutOutputWithNullRunner.phpt @@ -0,0 +1,11 @@ +--TEST-- +Loading index file with NullRunner stops immediately without output +--INI-- +# suppress legacy PHPUnit 7 warning for Xdebug 3 +xdebug.default_enable= +--ENV-- +X_EXPERIMENTAL_RUNNER=FrameworkX\Runner\NullRunner +--FILE_EXTERNAL-- +../public/index.php +--EXPECTREGEX-- +^$