Skip to content
Open
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
21 changes: 10 additions & 11 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

namespace FrameworkX;

use FrameworkX\Io\LogStreamHandler;
use FrameworkX\Io\MiddlewareHandler;
use FrameworkX\Io\ReactiveHandler;
use FrameworkX\Io\RedirectHandler;
use FrameworkX\Io\RouteHandler;
use FrameworkX\Io\SapiHandler;
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 @@ -23,8 +22,8 @@ class App
/** @var RouteHandler */
private $router;

/** @var ReactiveHandler|SapiHandler */
private $sapi;
/** @var HttpServerRunner|SapiRunner */
private $runner;

/**
* Instantiate new X application
Expand Down Expand Up @@ -127,7 +126,7 @@ public function __construct(...$middleware)

$this->router = $router;
$this->handler = new MiddlewareHandler($handlers);
$this->sapi = $container->getSapi();
$this->runner = $container->getRunner();
}

/**
Expand Down Expand Up @@ -241,7 +240,7 @@ public function redirect(string $route, string $target, int $code = Response::ST
* Runs the app to handle HTTP requests according to any registered routes and middleware.
*
* This is where the magic happens: When executed on the command line (CLI),
* this will run the powerful reactive request handler built on top of
* this will run the powerful reactive application runner built on top of
* ReactPHP. This works by running the efficient built-in HTTP web server to
* handle incoming HTTP requests through ReactPHP's HTTP and socket server.
* This async execution mode is usually recommended as it can efficiently
Expand All @@ -252,14 +251,14 @@ public function redirect(string $route, string $target, int $code = Response::ST
* When executed behind traditional PHP SAPIs (PHP-FPM, FastCGI, Apache, etc.),
* this will handle a single request and run until a single response is sent.
* This is particularly useful because it allows you to run the exact same
* app in any environment.
* application code in any environment.
*
* @see ReactiveHandler::run()
* @see SapiHandler::run()
* @see HttpServerRunner::__invoke()
* @see SapiRunner::__invoke()
*/
public function run(): void
{
$this->sapi->run(\Closure::fromCallable([$this, 'handleRequest']));
($this->runner)(\Closure::fromCallable([$this, 'handleRequest']));
}

/**
Expand Down
24 changes: 12 additions & 12 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
namespace FrameworkX;

use FrameworkX\Io\LogStreamHandler;
use FrameworkX\Io\ReactiveHandler;
use FrameworkX\Io\RouteHandler;
use FrameworkX\Io\SapiHandler;
use FrameworkX\Runner\HttpServerRunner;
use FrameworkX\Runner\SapiRunner;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;

Expand Down Expand Up @@ -169,8 +169,8 @@ public function getObject(string $class) /*: object (PHP 7.2+) */
return $this; // @phpstan-ignore-line returns instanceof `T`
} elseif ($class === RouteHandler::class) {
return new RouteHandler($this); // @phpstan-ignore-line returns instanceof `T`
} elseif ($class === ReactiveHandler::class) {
return new ReactiveHandler(new LogStreamHandler('php://output'), $this->getEnv('X_LISTEN')); // @phpstan-ignore-line returns instanceof `T`
} elseif ($class === HttpServerRunner::class) {
return new HttpServerRunner(new LogStreamHandler('php://output'), $this->getEnv('X_LISTEN')); // @phpstan-ignore-line returns instanceof `T`
}
return new $class();
}
Expand All @@ -179,16 +179,16 @@ public function getObject(string $class) /*: object (PHP 7.2+) */
}

/**
* [Internal] Get SAPI handler from container
* [Internal] Get the app runner appropriate for this environment from container
*
* @return ReactiveHandler|SapiHandler
* @return HttpServerRunner|SapiRunner
* @throws \TypeError if container config or factory returns an unexpected type
* @throws \Throwable if container factory function throws unexpected exception
* @internal
*/
public function getSapi() /*: ReactiveHandler|SapiHandler (PHP 8.0+) */
public function getRunner() /*: HttpServerRunner|SapiRunner (PHP 8.0+) */
{
return $this->getObject(\PHP_SAPI === 'cli' ? ReactiveHandler::class : SapiHandler::class);
return $this->getObject(\PHP_SAPI === 'cli' ? HttpServerRunner::class : SapiRunner::class);
}

/**
Expand All @@ -204,10 +204,10 @@ private function loadObject(string $name, int $depth = 64) /*: object (PHP 7.2+)
{
\assert(\is_array($this->container));

if ($name === ReactiveHandler::class && !\array_key_exists(ReactiveHandler::class, $this->container)) {
// special case: create ReactiveHandler with X_LISTEN environment variable
$this->container[ReactiveHandler::class] = static function (?string $X_LISTEN = null): ReactiveHandler {
return new ReactiveHandler(new LogStreamHandler('php://output'), $X_LISTEN);
if ($name === HttpServerRunner::class && !\array_key_exists(HttpServerRunner::class, $this->container)) {
// special case: create HttpServerRunner with X_LISTEN environment variable
$this->container[HttpServerRunner::class] = static function (?string $X_LISTEN = null): HttpServerRunner {
return new HttpServerRunner(new LogStreamHandler('php://output'), $X_LISTEN);
};
}

Expand Down
16 changes: 11 additions & 5 deletions src/Io/ReactiveHandler.php → src/Runner/HttpServerRunner.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<?php

namespace FrameworkX\Io;
namespace FrameworkX\Runner;

use FrameworkX\Io\FiberHandler;
use FrameworkX\Io\LogStreamHandler;
use React\EventLoop\Loop;
use React\Http\HttpServer;
use React\Socket\SocketServer;

/**
* [Internal] Powerful reactive request handler built on top of ReactPHP.
* [Internal] Powerful reactive application runner built on top of ReactPHP.
*
* This is where the magic happens: The main `App` uses this class to run
* ReactPHP's efficient HTTP server to handle incoming HTTP requests when
Expand All @@ -17,11 +19,11 @@
* continue to run until it is interrupted by a signal.
*
* Note that this is an internal class only and nothing you should usually have
* to care about. See also the `App` and `SapiHandler` for more details.
* to care about. See also the `App` and `SapiRunner` for more details.
*
* @internal
*/
class ReactiveHandler
class HttpServerRunner
{
/** @var LogStreamHandler */
private $logger;
Expand All @@ -36,7 +38,11 @@ public function __construct(LogStreamHandler $logger, ?string $listenAddress)
$this->listenAddress = $listenAddress ?? '127.0.0.1:8080';
}

public function run(callable $handler): void
/**
* @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
{
$socket = new SocketServer($this->listenAddress);

Expand Down
18 changes: 11 additions & 7 deletions src/Io/SapiHandler.php → src/Runner/SapiRunner.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace FrameworkX\Io;
namespace FrameworkX\Runner;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand All @@ -11,21 +11,25 @@
use React\Stream\ReadableStreamInterface;

/**
* [Internal] Request handler for traditional PHP SAPIs.
* [Internal] Application runner for traditional PHP SAPIs.
*
* This request handler will be used when executed behind traditional PHP SAPIs
* This application runner will be used when executed behind traditional PHP SAPIs
* (PHP-FPM, FastCGI, Apache, etc.). It will handle a single request and run
* until a single response is sent. This is particularly useful because it
* allows you to run the exact same app in any environment.
* allows you to run the exact same application code in any environment.
*
* Note that this is an internal class only and nothing you should usually have
* to care about. See also the `App` and `ReactiveHandler` for more details.
* to care about. See also the `App` and `HttpServerRunner` for more details.
*
* @internal
*/
class SapiHandler
class SapiRunner
{
public function run(callable $handler): void
/**
* @param callable(ServerRequestInterface):(ResponseInterface|PromiseInterface<ResponseInterface>) $handler
* @return void
*/
public function __invoke(callable $handler): void
{
$request = $this->requestFromGlobals();

Expand Down
38 changes: 19 additions & 19 deletions tests/AppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
use FrameworkX\Container;
use FrameworkX\ErrorHandler;
use FrameworkX\Io\MiddlewareHandler;
use FrameworkX\Io\ReactiveHandler;
use FrameworkX\Io\RouteHandler;
use FrameworkX\Runner\HttpServerRunner;
use FrameworkX\Tests\Fixtures\InvalidAbstract;
use FrameworkX\Tests\Fixtures\InvalidConstructorInt;
use FrameworkX\Tests\Fixtures\InvalidConstructorIntersection;
Expand Down Expand Up @@ -92,26 +92,26 @@ public function testConstructWithContainerMockAssignsDefaultHandlersFromContaine
$errorHandler = new ErrorHandler();
$routeHandler = $this->createMock(RouteHandler::class);

$sapi = $this->createMock(ReactiveHandler::class);
$runner = $this->createMock(HttpServerRunner::class);

$container = $this->createMock(Container::class);
$container->expects($this->exactly(3))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
[RouteHandler::class, $routeHandler],
]);
$container->expects($this->once())->method('getSapi')->willReturn($sapi);
$container->expects($this->once())->method('getRunner')->willReturn($runner);
assert($container instanceof Container);

$app = new App($container);

$ref = new \ReflectionProperty($app, 'sapi');
$ref = new \ReflectionProperty($app, 'runner');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ret = $ref->getValue($app);

$this->assertSame($sapi, $ret);
$this->assertSame($runner, $ret);

$ref = new ReflectionProperty($app, 'handler');
if (PHP_VERSION_ID < 80100) {
Expand All @@ -138,13 +138,13 @@ public function testConstructWithContainerInstanceAssignsDefaultHandlersAndConta
$container = new Container([]);
$app = new App($container);

$ref = new \ReflectionProperty($app, 'sapi');
$ref = new \ReflectionProperty($app, 'runner');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ret = $ref->getValue($app);

$this->assertSame($container->getSapi(), $ret);
$this->assertSame($container->getRunner(), $ret);

$ref = new ReflectionProperty($app, 'handler');
if (PHP_VERSION_ID < 80100) {
Expand Down Expand Up @@ -893,39 +893,39 @@ public function testConstructWithRouteHandlerClassNameFollowedByMiddlewareThrows
new App(RouteHandler::class, $middleware);
}

public function testConstructWithContainerWithListenAddressWillPassListenAddressToReactiveHandler(): void
public function testConstructWithContainerWithListenAddressWillPassListenAddressToHttpServerRunner(): void
{
$container = new Container([
'X_LISTEN' => '0.0.0.0:8081'
]);

$app = new App($container);

// $sapi = $app->sapi;
$ref = new \ReflectionProperty($app, 'sapi');
// $runner = $app->runner;
$ref = new \ReflectionProperty($app, 'runner');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$sapi = $ref->getValue($app);
assert($sapi instanceof ReactiveHandler);
$runner = $ref->getValue($app);
assert($runner instanceof HttpServerRunner);

// $listenAddress = $sapi->listenAddress;
$ref = new \ReflectionProperty($sapi, 'listenAddress');
// $listenAddress = $runner->listenAddress;
$ref = new \ReflectionProperty($runner, 'listenAddress');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$listenAddress = $ref->getValue($sapi);
$listenAddress = $ref->getValue($runner);

$this->assertEquals('0.0.0.0:8081', $listenAddress);
}

public function testRunWillExecuteRunOnSapiHandlerFromContainer(): void
public function testRunWillInvokeRunnerFromContainer(): void
{
$sapi = $this->createMock(ReactiveHandler::class);
$sapi->expects($this->once())->method('run');
$runner = $this->createMock(HttpServerRunner::class);
$runner->expects($this->once())->method('__invoke');

$container = new Container([
ReactiveHandler::class => $sapi
HttpServerRunner::class => $runner
]);

$app = new App($container);
Expand Down
Loading
Loading