Skip to content

Commit c3f12c3

Browse files
authored
Merge pull request #175 from clue-labs/container-handlers
Support loading `AccessLogHandler` and `ErrorHandler` from `Container`
2 parents 357f1fd + 8acba4e commit c3f12c3

6 files changed

Lines changed: 542 additions & 44 deletions

File tree

docs/api/app.md

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -194,25 +194,60 @@ the [`ErrorHandler`](middleware.md#errorhandler) to the list of middleware used.
194194
You may also explicitly pass an [`ErrorHandler`](middleware.md#errorhandler)
195195
middleware to the `App` like this:
196196

197+
=== "Using middleware instances"
198+
199+
```php title="public/index.php"
200+
<?php
201+
202+
require __DIR__ . '/../vendor/autoload.php';
203+
204+
$app = new FrameworkX\App(
205+
new FrameworkX\ErrorHandler()
206+
);
207+
208+
// Register routes here, see routing…
209+
210+
$app->run();
211+
```
212+
213+
=== "Using middleware names"
214+
215+
```php title="public/index.php"
216+
<?php
217+
218+
require __DIR__ . '/../vendor/autoload.php';
219+
220+
$app = new FrameworkX\App(
221+
FrameworkX\ErrorHandler::class
222+
);
223+
224+
// Register routes here, see routing…
225+
226+
$app->run();
227+
```
228+
229+
If you do not explicitly pass an [`ErrorHandler`](middleware.md#errorhandler) or
230+
if you pass another middleware before an [`ErrorHandler`](middleware.md#errorhandler)
231+
to the `App`, a default error handler will be added as a first handler automatically.
232+
You may use the [DI container configuration](../best-practices/controllers.md#container-configuration)
233+
to configure the default error handler like this:
234+
197235
```php title="public/index.php"
198236
<?php
199237

200238
require __DIR__ . '/../vendor/autoload.php';
201239

202-
$app = new FrameworkX\App(
203-
new FrameworkX\ErrorHandler()
204-
);
240+
$container = new FrameworkX\Container([
241+
FrameworkX\ErrorHandler::class => fn () => new FrameworkX\ErrorHandler()
242+
]);
243+
244+
$app = new FrameworkX\App($container);
205245

206246
// Register routes here, see routing…
207247

208248
$app->run();
209249
```
210250

211-
> ⚠️ **Feature preview**
212-
>
213-
> Note that the [`ErrorHandler`](middleware.md#errorhandler) may currently only
214-
> be passed as a middleware instance and not as a middleware name to the `App`.
215-
216251
By default, this error message contains only few details to the client to avoid
217252
leaking too much internal information.
218253
If you want to implement custom error handling, you're recommended to either
@@ -245,28 +280,68 @@ adding the [`AccessLogHandler`](middleware.md#accessloghandler) to the list of
245280
middleware used. You may also explicitly pass an [`AccessLogHandler`](middleware.md#accessloghandler)
246281
middleware to the `App` like this:
247282

248-
```php title="public/index.php"
249-
<?php
283+
=== "Using middleware instances"
250284

251-
require __DIR__ . '/../vendor/autoload.php';
285+
```php title="public/index.php"
286+
<?php
252287

253-
$app = new FrameworkX\App(
254-
new FrameworkX\AccessLogHandler(),
255-
new FrameworkX\ErrorHandler()
256-
);
288+
require __DIR__ . '/../vendor/autoload.php';
257289

258-
// Register routes here, see routing…
290+
$app = new FrameworkX\App(
291+
new FrameworkX\AccessLogHandler(),
292+
new FrameworkX\ErrorHandler()
293+
);
259294

260-
$app->run();
261-
```
295+
// Register routes here, see routing…
296+
297+
$app->run();
298+
```
299+
300+
=== "Using middleware names"
301+
302+
```php title="public/index.php"
303+
<?php
304+
305+
require __DIR__ . '/../vendor/autoload.php';
306+
307+
$app = new FrameworkX\App(
308+
FrameworkX\AccessLogHandler::class,
309+
FrameworkX\ErrorHandler::class
310+
);
311+
312+
// Register routes here, see routing…
313+
314+
$app->run();
315+
```
262316

263317
> ⚠️ **Feature preview**
264318
>
265319
> Note that the [`AccessLogHandler`](middleware.md#accessloghandler) may
266-
> currently only be passed as a global middleware instance and not as a global
267-
> middleware name to the `App` and may not be used for individual routes.
320+
> currently only be passed as a global middleware to the `App` and may not be
321+
> used for individual routes.
268322
269323
If you pass an [`AccessLogHandler`](middleware.md#accessloghandler) to the `App`,
270324
it must be followed by an [`ErrorHandler`](middleware.md#errorhandler) like in
271325
the previous example. See also [error handling](#error-handling) for more
272326
details.
327+
328+
If you do not explicitly pass an [`AccessLogHandler`](middleware.md#accessloghandler)
329+
to the `App`, a default access log handler will be added as a first handler automatically.
330+
You may use the [DI container configuration](../best-practices/controllers.md#container-configuration)
331+
to configure the default access log handler like this:
332+
333+
```php title="public/index.php"
334+
<?php
335+
336+
require __DIR__ . '/../vendor/autoload.php';
337+
338+
$container = new FrameworkX\Container([
339+
FrameworkX\AccessLogHandler::class => fn () => new FrameworkX\AccessLogHandler()
340+
]);
341+
342+
$app = new FrameworkX\App($container);
343+
344+
// Register routes here, see routing…
345+
346+
$app->run();
347+
```

src/App.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,45 @@ public function __construct(...$middleware)
4141
// new MiddlewareHandler([$fiberHandler, $accessLogHandler, $errorHandler, ...$middleware, $routeHandler])
4242
$handlers = [];
4343

44+
$container = $needsErrorHandler = new Container();
45+
4446
// only log for built-in webserver and PHP development webserver by default, others have their own access log
45-
$needsAccessLog = (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server');
47+
$needsAccessLog = (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') ? $container : null;
4648

47-
$container = new Container();
4849
if ($middleware) {
4950
$needsErrorHandlerNext = false;
5051
foreach ($middleware as $handler) {
52+
// load AccessLogHandler and ErrorHandler instance from last Container
53+
if ($handler === AccessLogHandler::class) {
54+
$handler = $container->getAccessLogHandler();
55+
} elseif ($handler === ErrorHandler::class) {
56+
$handler = $container->getErrorHandler();
57+
}
58+
59+
// ensure AccessLogHandler is always followed by ErrorHandler
5160
if ($needsErrorHandlerNext && !$handler instanceof ErrorHandler) {
5261
break;
5362
}
5463
$needsErrorHandlerNext = false;
5564

5665
if ($handler instanceof Container) {
66+
// remember last Container to load any following class names
5767
$container = $handler;
58-
} elseif ($handler === ErrorHandler::class || $handler === AccessLogHandler::class) {
59-
throw new \TypeError($handler . ' may currently only be passed as a middleware instance');
68+
69+
// add default ErrorHandler from last Container before adding any other handlers, may be followed by other Container instances (unlikely)
70+
if (!$handlers) {
71+
$needsErrorHandler = $needsAccessLog = $container;
72+
}
6073
} elseif (!\is_callable($handler)) {
6174
$handlers[] = $container->callable($handler);
6275
} else {
76+
// don't need a default ErrorHandler if we're adding one as first handler or AccessLogHandler as first followed by one
77+
if ($needsErrorHandler && ($handler instanceof ErrorHandler || $handler instanceof AccessLogHandler) && !$handlers) {
78+
$needsErrorHandler = null;
79+
}
6380
$handlers[] = $handler;
6481
if ($handler instanceof AccessLogHandler) {
65-
$needsAccessLog = false;
82+
$needsAccessLog = null;
6683
$needsErrorHandlerNext = true;
6784
}
6885
}
@@ -73,13 +90,13 @@ public function __construct(...$middleware)
7390
}
7491

7592
// add default ErrorHandler as first handler unless it is already added explicitly
76-
if (!($handlers[0] ?? null) instanceof ErrorHandler && !($handlers[0] ?? null) instanceof AccessLogHandler) {
77-
\array_unshift($handlers, new ErrorHandler());
93+
if ($needsErrorHandler instanceof Container) {
94+
\array_unshift($handlers, $needsErrorHandler->getErrorHandler());
7895
}
7996

8097
// only log for built-in webserver and PHP development webserver by default, others have their own access log
81-
if ($needsAccessLog) {
82-
\array_unshift($handlers, new AccessLogHandler());
98+
if ($needsAccessLog instanceof Container) {
99+
\array_unshift($handlers, $needsAccessLog->getAccessLogHandler());
83100
}
84101

85102
// automatically start new fiber for each request on PHP 8.1+

src/Container.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,32 @@ public function callable(string $class): callable
9292
};
9393
}
9494

95+
/** @internal */
96+
public function getAccessLogHandler(): AccessLogHandler
97+
{
98+
if ($this->container instanceof ContainerInterface) {
99+
if ($this->container->has(AccessLogHandler::class)) {
100+
return $this->container->get(AccessLogHandler::class);
101+
} else {
102+
return new AccessLogHandler();
103+
}
104+
}
105+
return $this->load(AccessLogHandler::class);
106+
}
107+
108+
/** @internal */
109+
public function getErrorHandler(): ErrorHandler
110+
{
111+
if ($this->container instanceof ContainerInterface) {
112+
if ($this->container->has(ErrorHandler::class)) {
113+
return $this->container->get(ErrorHandler::class);
114+
} else {
115+
return new ErrorHandler();
116+
}
117+
}
118+
return $this->load(ErrorHandler::class);
119+
}
120+
95121
/**
96122
* @param class-string $name
97123
* @return object

src/RouteHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function map(array $methods, string $route, $handler, ...$handlers): void
5656
$container = $handler;
5757
unset($handlers[$i]);
5858
} elseif ($handler instanceof AccessLogHandler || $handler === AccessLogHandler::class) {
59-
throw new \TypeError('AccessLogHandler may currently only be passed as a global middleware instance');
59+
throw new \TypeError('AccessLogHandler may currently only be passed as a global middleware');
6060
} elseif (!\is_callable($handler)) {
6161
$handlers[$i] = $container->callable($handler);
6262
}

0 commit comments

Comments
 (0)