Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ parameters:
- src/
- tests/

fileExtensions:
- php
- phpt

reportUnmatchedIgnoredErrors: false
3 changes: 2 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<testsuites>
<testsuite name="Framework X test suite">
<directory>./tests/</directory>
<exclude>./tests/integration/</exclude>
<directory suffix=".phpt">./tests/</directory>
<exclude>./tests/integration/vendor/</exclude>
</testsuite>
</testsuites>
<coverage>
Expand Down
3 changes: 2 additions & 1 deletion phpunit.xml.legacy
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
<testsuites>
<testsuite name="Framework X test suite">
<directory>./tests/</directory>
<exclude>./tests/install-as-dep/</exclude>
<directory suffix=".phpt">./tests/</directory>
<exclude>./tests/integration/vendor/</exclude>
</testsuite>
</testsuites>
<filter>
Expand Down
8 changes: 3 additions & 5 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,7 +20,7 @@ class App
/** @var RouteHandler */
private $router;

/** @var HttpServerRunner|SapiRunner|callable(callable(ServerRequestInterface):(ResponseInterface|PromiseInterface<ResponseInterface>)):void */
/** @var callable(callable(ServerRequestInterface):(ResponseInterface|PromiseInterface<ResponseInterface>)):void */
private $runner;

/**
Expand Down Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions src/Runner/NullRunner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace FrameworkX\Runner;

/**
* Application runner stub that does nothing when invoked.
*
* This experimental application runner can be used in environments where the
* application lifecycle is managed externally. It can be useful for integration
* testing or when embedding the application in other systems.
*
* Note that this runner is not intended for production use and is not loaded by
* default. You need to explicitly configure your application to use this runner
* through the `X_EXPERIMENTAL_RUNNER` environment variable:
*
* ```php
* $container = new Container([
* // 'X_EXPERIMENTAL_RUNNER' => 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
}
}
13 changes: 13 additions & 0 deletions tests/AppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions tests/Runner/NullRunnerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace FrameworkX\Tests\Runner;

use FrameworkX\Runner\NullRunner;
use PHPUnit\Framework\TestCase;

class NullRunnerTest extends TestCase
{
public function testInvokeReturnsImmediately(): void
{
$runner = new NullRunner();

$this->expectOutputString('');
$runner(function () {
throw new \BadFunctionCallException('Should not be called');
});
}
}
9 changes: 8 additions & 1 deletion tests/integration/public/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
25 changes: 25 additions & 0 deletions tests/integration/tests/AppInvokeIndexReturnsResponse.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php

require __DIR__ . '/../../../vendor/autoload.php';
require __DIR__ . '/../public/index.php';

/** @var FrameworkX\App $app */
assert($app instanceof FrameworkX\App);

$request = new React\Http\Message\ServerRequest('GET', '/');
$response = $app($request);
assert($response instanceof Psr\Http\Message\ResponseInterface);

echo $response->getBody();

?>
--EXPECT--
Hello world!
11 changes: 11 additions & 0 deletions tests/integration/tests/AppStopsWithoutOutputWithNullRunner.phpt
Original file line number Diff line number Diff line change
@@ -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--
^$