Skip to content

Commit dcdcfb0

Browse files
authored
Merge pull request #292 from clue-labs/container-getsapi
Refactor and simplify loading `ReactiveHandler` with new `Container::getSapi()` helper
2 parents e52bde8 + f70c567 commit dcdcfb0

6 files changed

Lines changed: 211 additions & 91 deletions

File tree

src/App.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace FrameworkX;
44

5+
use FrameworkX\Io\LogStreamHandler;
56
use FrameworkX\Io\MiddlewareHandler;
67
use FrameworkX\Io\ReactiveHandler;
78
use FrameworkX\Io\RedirectHandler;
@@ -126,7 +127,7 @@ public function __construct(...$middleware)
126127

127128
$this->router = $router;
128129
$this->handler = new MiddlewareHandler($handlers);
129-
$this->sapi = \PHP_SAPI === 'cli' ? new ReactiveHandler($container->getEnv('X_LISTEN')) : new SapiHandler();
130+
$this->sapi = $container->getSapi();
130131
}
131132

132133
/**

src/Container.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace FrameworkX;
44

5+
use FrameworkX\Io\LogStreamHandler;
6+
use FrameworkX\Io\ReactiveHandler;
57
use FrameworkX\Io\RouteHandler;
8+
use FrameworkX\Io\SapiHandler;
69
use Psr\Container\ContainerInterface;
710
use Psr\Http\Message\ServerRequestInterface;
811

@@ -166,13 +169,28 @@ public function getObject(string $class) /*: object (PHP 7.2+) */
166169
return $this; // @phpstan-ignore-line returns instanceof `T`
167170
} elseif ($class === RouteHandler::class) {
168171
return new RouteHandler($this); // @phpstan-ignore-line returns instanceof `T`
172+
} elseif ($class === ReactiveHandler::class) {
173+
return new ReactiveHandler(new LogStreamHandler('php://output'), $this->getEnv('X_LISTEN')); // @phpstan-ignore-line returns instanceof `T`
169174
}
170175
return new $class();
171176
}
172177

173178
return $this->loadObject($class);
174179
}
175180

181+
/**
182+
* [Internal] Get SAPI handler from container
183+
*
184+
* @return ReactiveHandler|SapiHandler
185+
* @throws \TypeError if container config or factory returns an unexpected type
186+
* @throws \Throwable if container factory function throws unexpected exception
187+
* @internal
188+
*/
189+
public function getSapi() /*: ReactiveHandler|SapiHandler (PHP 8.0+) */
190+
{
191+
return $this->getObject(\PHP_SAPI === 'cli' ? ReactiveHandler::class : SapiHandler::class);
192+
}
193+
176194
/**
177195
* @template T of object
178196
* @param class-string<T> $name
@@ -186,6 +204,13 @@ private function loadObject(string $name, int $depth = 64) /*: object (PHP 7.2+)
186204
{
187205
\assert(\is_array($this->container));
188206

207+
if ($name === ReactiveHandler::class && !\array_key_exists(ReactiveHandler::class, $this->container)) {
208+
// special case: create ReactiveHandler with X_LISTEN environment variable
209+
$this->container[ReactiveHandler::class] = static function (?string $X_LISTEN = null): ReactiveHandler {
210+
return new ReactiveHandler(new LogStreamHandler('php://output'), $X_LISTEN);
211+
};
212+
}
213+
189214
if (\array_key_exists($name, $this->container)) {
190215
if (\is_string($this->container[$name])) {
191216
if ($depth < 1) {

src/Io/ReactiveHandler.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ class ReactiveHandler
2929
/** @var string */
3030
private $listenAddress;
3131

32-
public function __construct(?string $listenAddress)
32+
/** @throws void */
33+
public function __construct(LogStreamHandler $logger, ?string $listenAddress)
3334
{
34-
/** @throws void */
35-
$this->logger = new LogStreamHandler('php://output');
35+
$this->logger = $logger;
3636
$this->listenAddress = $listenAddress ?? '127.0.0.1:8080';
3737
}
3838

tests/AppTest.php

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,27 @@ public function testConstructWithContainerMockAssignsDefaultHandlersFromContaine
9292
$errorHandler = new ErrorHandler();
9393
$routeHandler = $this->createMock(RouteHandler::class);
9494

95+
$sapi = $this->createMock(ReactiveHandler::class);
96+
9597
$container = $this->createMock(Container::class);
9698
$container->expects($this->exactly(3))->method('getObject')->willReturnMap([
9799
[AccessLogHandler::class, $accessLogHandler],
98100
[ErrorHandler::class, $errorHandler],
99101
[RouteHandler::class, $routeHandler],
100102
]);
101-
103+
$container->expects($this->once())->method('getSapi')->willReturn($sapi);
102104
assert($container instanceof Container);
105+
103106
$app = new App($container);
104107

108+
$ref = new \ReflectionProperty($app, 'sapi');
109+
if (PHP_VERSION_ID < 80100) {
110+
$ref->setAccessible(true);
111+
}
112+
$ret = $ref->getValue($app);
113+
114+
$this->assertSame($sapi, $ret);
115+
105116
$ref = new ReflectionProperty($app, 'handler');
106117
if (PHP_VERSION_ID < 80100) {
107118
$ref->setAccessible(true);
@@ -127,6 +138,14 @@ public function testConstructWithContainerInstanceAssignsDefaultHandlersAndConta
127138
$container = new Container([]);
128139
$app = new App($container);
129140

141+
$ref = new \ReflectionProperty($app, 'sapi');
142+
if (PHP_VERSION_ID < 80100) {
143+
$ref->setAccessible(true);
144+
}
145+
$ret = $ref->getValue($app);
146+
147+
$this->assertSame($container->getSapi(), $ret);
148+
130149
$ref = new ReflectionProperty($app, 'handler');
131150
if (PHP_VERSION_ID < 80100) {
132151
$ref->setAccessible(true);
@@ -900,19 +919,16 @@ public function testConstructWithContainerWithListenAddressWillPassListenAddress
900919
$this->assertEquals('0.0.0.0:8081', $listenAddress);
901920
}
902921

903-
public function testRunWillExecuteRunOnSapiHandler(): void
922+
public function testRunWillExecuteRunOnSapiHandlerFromContainer(): void
904923
{
905-
$app = new App();
906-
907924
$sapi = $this->createMock(ReactiveHandler::class);
908925
$sapi->expects($this->once())->method('run');
909926

910-
// $app->sapi = $sapi;
911-
$ref = new \ReflectionProperty($app, 'sapi');
912-
if (PHP_VERSION_ID < 80100) {
913-
$ref->setAccessible(true);
914-
}
915-
$ref->setValue($app, $sapi);
927+
$container = new Container([
928+
ReactiveHandler::class => $sapi
929+
]);
930+
931+
$app = new App($container);
916932

917933
$app->run();
918934
}

tests/ContainerTest.php

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use FrameworkX\AccessLogHandler;
66
use FrameworkX\Container;
77
use FrameworkX\ErrorHandler;
8+
use FrameworkX\Io\LogStreamHandler;
9+
use FrameworkX\Io\ReactiveHandler;
810
use FrameworkX\Io\RouteHandler;
911
use PHPUnit\Framework\TestCase;
1012
use Psr\Container\ContainerInterface;
@@ -2636,7 +2638,7 @@ public function testGetObjectReturnsDefaultRouteHandlerInstance(): void
26362638
$this->assertInstanceOf(RouteHandler::class, $router);
26372639

26382640
$ref = new \ReflectionProperty($router, 'container');
2639-
if (\PHP_VERSION_ID < 801000) {
2641+
if (PHP_VERSION_ID < 80100) {
26402642
$ref->setAccessible(true);
26412643
}
26422644
$ret = $ref->getValue($router);
@@ -2702,7 +2704,7 @@ public function testGetObjectReturnsDefaultRouteHandlerInstanceIfPsrContainerHas
27022704
$this->assertInstanceOf(RouteHandler::class, $router);
27032705

27042706
$ref = new \ReflectionProperty($router, 'container');
2705-
if (\PHP_VERSION_ID < 801000) {
2707+
if (PHP_VERSION_ID < 80100) {
27062708
$ref->setAccessible(true);
27072709
}
27082710
$ret = $ref->getValue($router);
@@ -2823,6 +2825,130 @@ public function testGetObjectThrowsIfPsrContainerReturnsWrongType(): void
28232825
$container->getObject(AccessLogHandler::class);
28242826
}
28252827

2828+
public function testGetSapiReturnsDefaultReactiveHandlerInstance(): void
2829+
{
2830+
$container = new Container([]);
2831+
2832+
$sapi = $container->getSapi();
2833+
2834+
$this->assertInstanceOf(ReactiveHandler::class, $sapi);
2835+
2836+
$ref = new \ReflectionProperty($sapi, 'listenAddress');
2837+
if (PHP_VERSION_ID < 80100) {
2838+
$ref->setAccessible(true);
2839+
}
2840+
$listenAddress = $ref->getValue($sapi);
2841+
2842+
$this->assertEquals('127.0.0.1:8080', $listenAddress);
2843+
}
2844+
2845+
public function testGetSapiReturnsDefaultReactiveHandlerInstanceWithCustomListenAddress(): void
2846+
{
2847+
$container = new Container([
2848+
'X_LISTEN' => '127.0.0.1:8081'
2849+
]);
2850+
2851+
$sapi = $container->getSapi();
2852+
2853+
$this->assertInstanceOf(ReactiveHandler::class, $sapi);
2854+
2855+
$ref = new \ReflectionProperty($sapi, 'listenAddress');
2856+
if (PHP_VERSION_ID < 80100) {
2857+
$ref->setAccessible(true);
2858+
}
2859+
$listenAddress = $ref->getValue($sapi);
2860+
2861+
$this->assertEquals('127.0.0.1:8081', $listenAddress);
2862+
}
2863+
2864+
public function testGetSapiTwiceReturnsSameReactiveHandlerInstance(): void
2865+
{
2866+
$container = new Container([]);
2867+
2868+
$sapi = $container->getSapi();
2869+
2870+
$this->assertSame($sapi, $container->getSapi());
2871+
}
2872+
2873+
public function testGetSapiReturnsReactiveHandlerInstanceFromConfig(): void
2874+
{
2875+
$sapi = new ReactiveHandler(new LogStreamHandler('php://output'), null);
2876+
2877+
$container = new Container([
2878+
ReactiveHandler::class => $sapi
2879+
]);
2880+
2881+
$ret = $container->getSapi();
2882+
2883+
$this->assertSame($sapi, $ret);
2884+
}
2885+
2886+
public function testGetSapiReturnsReactiveHandlerInstanceFromPsrContainer(): void
2887+
{
2888+
$sapi = new ReactiveHandler(new LogStreamHandler('php://output'), null);
2889+
2890+
$psr = $this->createMock(ContainerInterface::class);
2891+
$psr->expects($this->once())->method('has')->with(ReactiveHandler::class)->willReturn(true);
2892+
$psr->expects($this->once())->method('get')->with(ReactiveHandler::class)->willReturn($sapi);
2893+
2894+
assert($psr instanceof ContainerInterface);
2895+
$container = new Container($psr);
2896+
2897+
$ret = $container->getSapi();
2898+
2899+
$this->assertSame($sapi, $ret);
2900+
}
2901+
2902+
public function testGetSapiReturnsDefaultReactiveHandlerInstanceWithDefaultListenAddressIfPsrContainerHasNoEntry(): void
2903+
{
2904+
$psr = $this->createMock(ContainerInterface::class);
2905+
$psr->expects($this->exactly(2))->method('has')->willReturnMap([
2906+
[ReactiveHandler::class, false],
2907+
['X_LISTEN', false],
2908+
]);
2909+
$psr->expects($this->never())->method('get');
2910+
2911+
assert($psr instanceof ContainerInterface);
2912+
$container = new Container($psr);
2913+
2914+
$sapi = $container->getSapi();
2915+
2916+
$this->assertInstanceOf(ReactiveHandler::class, $sapi);
2917+
2918+
$ref = new \ReflectionProperty($sapi, 'listenAddress');
2919+
if (PHP_VERSION_ID < 80100) {
2920+
$ref->setAccessible(true);
2921+
}
2922+
$listenAddress = $ref->getValue($sapi);
2923+
2924+
$this->assertEquals('127.0.0.1:8080', $listenAddress);
2925+
}
2926+
2927+
public function testGetSapiReturnsDefaultReactiveHandlerInstanceWithCustomListenAddressIfPsrContainerHasNoEntryButCustomListenAddress(): void
2928+
{
2929+
$psr = $this->createMock(ContainerInterface::class);
2930+
$psr->expects($this->exactly(2))->method('has')->willReturnMap([
2931+
[ReactiveHandler::class, false],
2932+
['X_LISTEN', true],
2933+
]);
2934+
$psr->expects($this->once())->method('get')->with('X_LISTEN')->willReturn('127.0.0.1:8081');
2935+
2936+
assert($psr instanceof ContainerInterface);
2937+
$container = new Container($psr);
2938+
2939+
$sapi = $container->getSapi();
2940+
2941+
$this->assertInstanceOf(ReactiveHandler::class, $sapi);
2942+
2943+
$ref = new \ReflectionProperty($sapi, 'listenAddress');
2944+
if (PHP_VERSION_ID < 80100) {
2945+
$ref->setAccessible(true);
2946+
}
2947+
$listenAddress = $ref->getValue($sapi);
2948+
2949+
$this->assertEquals('127.0.0.1:8081', $listenAddress);
2950+
}
2951+
28262952
public function testInvokeContainerAsMiddlewareReturnsFromNextRequestHandler(): void
28272953
{
28282954
$request = new ServerRequest('GET', 'http://example.com/');

0 commit comments

Comments
 (0)