Skip to content

Commit e52bde8

Browse files
authored
Merge pull request #291 from clue-labs/router-inject
Support explicitly injecting internal `RouteHandler` into `App` or load from `Container`
2 parents 0f2a5a5 + d5e2fea commit e52bde8

File tree

7 files changed

+387
-198
lines changed

7 files changed

+387
-198
lines changed

src/App.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,19 @@ public function __construct(...$middleware)
4949
// only log for built-in webserver and PHP development webserver by default, others have their own access log
5050
$needsAccessLog = (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') ? $container : null;
5151

52+
// remember if RouteHandler is added explicitly
53+
$router = null;
54+
5255
if ($middleware) {
5356
$needsErrorHandlerNext = false;
5457
foreach ($middleware as $handler) {
58+
// if explicit RouteHandler is given, it must be last in the chain
59+
if ($router !== null) {
60+
throw new \TypeError('RouteHandler must not be followed by other handlers');
61+
}
62+
5563
// load required internal classes from last Container
56-
if (\in_array($handler, [AccessLogHandler::class, ErrorHandler::class, Container::class], true)) {
64+
if (\in_array($handler, [AccessLogHandler::class, ErrorHandler::class, Container::class, RouteHandler::class], true)) {
5765
$handler = $container->getObject($handler);
5866
}
5967

@@ -88,6 +96,9 @@ public function __construct(...$middleware)
8896
$needsAccessLog = null;
8997
$needsErrorHandlerNext = true;
9098
}
99+
if ($handler instanceof RouteHandler) {
100+
$router = $handler;
101+
}
91102
}
92103
}
93104
if ($needsErrorHandlerNext) {
@@ -108,8 +119,12 @@ public function __construct(...$middleware)
108119
}
109120
}
110121

111-
$this->router = new RouteHandler($container);
112-
$handlers[] = $this->router;
122+
// add default RouteHandler as last handler in middleware chain
123+
if ($router === null) {
124+
$handlers[] = $router = $container->getObject(RouteHandler::class);
125+
}
126+
127+
$this->router = $router;
113128
$this->handler = new MiddlewareHandler($handlers);
114129
$this->sapi = \PHP_SAPI === 'cli' ? new ReactiveHandler($container->getEnv('X_LISTEN')) : new SapiHandler();
115130
}

src/Container.php

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

33
namespace FrameworkX;
44

5+
use FrameworkX\Io\RouteHandler;
56
use Psr\Container\ContainerInterface;
67
use Psr\Http\Message\ServerRequestInterface;
78

@@ -163,6 +164,8 @@ public function getObject(string $class) /*: object (PHP 7.2+) */
163164
// fallback for missing required internal classes from PSR-11 adapter
164165
if ($class === Container::class) {
165166
return $this; // @phpstan-ignore-line returns instanceof `T`
167+
} elseif ($class === RouteHandler::class) {
168+
return new RouteHandler($this); // @phpstan-ignore-line returns instanceof `T`
166169
}
167170
return new $class();
168171
}

src/Io/RouteHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ class RouteHandler
3030
/** @var Container */
3131
private $container;
3232

33-
public function __construct(?Container $container = null)
33+
public function __construct(Container $container)
3434
{
3535
$this->routeCollector = new RouteCollector(new RouteParser(), new RouteGenerator());
3636
$this->errorHandler = new ErrorHandler();
37-
$this->container = $container ?? new Container();
37+
$this->container = $container;
3838
}
3939

4040
/**

tests/AppMiddlewareTest.php

Lines changed: 18 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,171 +18,126 @@ class AppMiddlewareTest extends TestCase
1818
{
1919
public function testGetMethodWithMiddlewareAddsGetRouteOnRouter(): void
2020
{
21-
$app = $this->createAppWithoutLogger();
22-
2321
$middleware = function () {};
2422
$controller = function () { };
2523

2624
$router = $this->createMock(RouteHandler::class);
2725
$router->expects($this->once())->method('map')->with(['GET'], '/', $middleware, $controller);
26+
assert($router instanceof RouteHandler);
2827

29-
$ref = new \ReflectionProperty($app, 'router');
30-
if (PHP_VERSION_ID < 80100) {
31-
$ref->setAccessible(true);
32-
}
33-
$ref->setValue($app, $router);
28+
$app = new App($router);
3429

3530
$app->get('/', $middleware, $controller);
3631
}
3732

3833
public function testHeadMethodWithMiddlewareAddsHeadRouteOnRouter(): void
3934
{
40-
$app = $this->createAppWithoutLogger();
41-
4235
$middleware = function () {};
4336
$controller = function () { };
4437

4538
$router = $this->createMock(RouteHandler::class);
4639
$router->expects($this->once())->method('map')->with(['HEAD'], '/', $middleware, $controller);
40+
assert($router instanceof RouteHandler);
4741

48-
$ref = new \ReflectionProperty($app, 'router');
49-
if (PHP_VERSION_ID < 80100) {
50-
$ref->setAccessible(true);
51-
}
52-
$ref->setValue($app, $router);
42+
$app = new App($router);
5343

5444
$app->head('/', $middleware, $controller);
5545
}
5646

5747
public function testPostMethodWithMiddlewareAddsPostRouteOnRouter(): void
5848
{
59-
$app = $this->createAppWithoutLogger();
60-
6149
$middleware = function () {};
6250
$controller = function () { };
6351

6452
$router = $this->createMock(RouteHandler::class);
6553
$router->expects($this->once())->method('map')->with(['POST'], '/', $middleware, $controller);
54+
assert($router instanceof RouteHandler);
6655

67-
$ref = new \ReflectionProperty($app, 'router');
68-
if (PHP_VERSION_ID < 80100) {
69-
$ref->setAccessible(true);
70-
}
71-
$ref->setValue($app, $router);
56+
$app = new App($router);
7257

7358
$app->post('/', $middleware, $controller);
7459
}
7560

7661
public function testPutMethodWithMiddlewareAddsPutRouteOnRouter(): void
7762
{
78-
$app = $this->createAppWithoutLogger();
79-
8063
$middleware = function () {};
8164
$controller = function () { };
8265

8366
$router = $this->createMock(RouteHandler::class);
8467
$router->expects($this->once())->method('map')->with(['PUT'], '/', $middleware, $controller);
68+
assert($router instanceof RouteHandler);
8569

86-
$ref = new \ReflectionProperty($app, 'router');
87-
if (PHP_VERSION_ID < 80100) {
88-
$ref->setAccessible(true);
89-
}
90-
$ref->setValue($app, $router);
70+
$app = new App($router);
9171

9272
$app->put('/', $middleware, $controller);
9373
}
9474

9575
public function testPatchMethodWithMiddlewareAddsPatchRouteOnRouter(): void
9676
{
97-
$app = $this->createAppWithoutLogger();
98-
9977
$middleware = function () {};
10078
$controller = function () { };
10179

10280
$router = $this->createMock(RouteHandler::class);
10381
$router->expects($this->once())->method('map')->with(['PATCH'], '/', $middleware, $controller);
82+
assert($router instanceof RouteHandler);
10483

105-
$ref = new \ReflectionProperty($app, 'router');
106-
if (PHP_VERSION_ID < 80100) {
107-
$ref->setAccessible(true);
108-
}
109-
$ref->setValue($app, $router);
84+
$app = new App($router);
11085

11186
$app->patch('/', $middleware, $controller);
11287
}
11388

11489
public function testDeleteMethodWithMiddlewareAddsDeleteRouteOnRouter(): void
11590
{
116-
$app = $this->createAppWithoutLogger();
117-
11891
$middleware = function () {};
11992
$controller = function () { };
12093

12194
$router = $this->createMock(RouteHandler::class);
12295
$router->expects($this->once())->method('map')->with(['DELETE'], '/', $middleware, $controller);
96+
assert($router instanceof RouteHandler);
12397

124-
$ref = new \ReflectionProperty($app, 'router');
125-
if (PHP_VERSION_ID < 80100) {
126-
$ref->setAccessible(true);
127-
}
128-
$ref->setValue($app, $router);
98+
$app = new App($router);
12999

130100
$app->delete('/', $middleware, $controller);
131101
}
132102

133103
public function testOptionsMethodWithMiddlewareAddsOptionsRouteOnRouter(): void
134104
{
135-
$app = $this->createAppWithoutLogger();
136-
137105
$middleware = function () {};
138106
$controller = function () { };
139107

140108
$router = $this->createMock(RouteHandler::class);
141109
$router->expects($this->once())->method('map')->with(['OPTIONS'], '/', $middleware, $controller);
110+
assert($router instanceof RouteHandler);
142111

143-
$ref = new \ReflectionProperty($app, 'router');
144-
if (PHP_VERSION_ID < 80100) {
145-
$ref->setAccessible(true);
146-
}
147-
$ref->setValue($app, $router);
112+
$app = new App($router);
148113

149114
$app->options('/', $middleware, $controller);
150115
}
151116

152117
public function testAnyMethodWithMiddlewareAddsAllHttpMethodsOnRouter(): void
153118
{
154-
$app = $this->createAppWithoutLogger();
155-
156119
$middleware = function () {};
157120
$controller = function () { };
158121

159122
$router = $this->createMock(RouteHandler::class);
160123
$router->expects($this->once())->method('map')->with(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], '/', $middleware, $controller);
124+
assert($router instanceof RouteHandler);
161125

162-
$ref = new \ReflectionProperty($app, 'router');
163-
if (PHP_VERSION_ID < 80100) {
164-
$ref->setAccessible(true);
165-
}
166-
$ref->setValue($app, $router);
126+
$app = new App($router);
167127

168128
$app->any('/', $middleware, $controller);
169129
}
170130

171131
public function testMapMethodWithMiddlewareAddsGivenMethodsOnRouter(): void
172132
{
173-
$app = $this->createAppWithoutLogger();
174-
175133
$middleware = function () {};
176134
$controller = function () { };
177135

178136
$router = $this->createMock(RouteHandler::class);
179137
$router->expects($this->once())->method('map')->with(['GET', 'POST'], '/', $middleware, $controller);
138+
assert($router instanceof RouteHandler);
180139

181-
$ref = new \ReflectionProperty($app, 'router');
182-
if (PHP_VERSION_ID < 80100) {
183-
$ref->setAccessible(true);
184-
}
185-
$ref->setValue($app, $router);
140+
$app = new App($router);
186141

187142
$app->map(['GET', 'POST'], '/', $middleware, $controller);
188143
}

0 commit comments

Comments
 (0)