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
46 changes: 38 additions & 8 deletions src/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -668,20 +668,14 @@ protected static function mapHandler(

if (is_array($handler)) {
if (is_string($handler['middleware'] ?? null)) {
$parsedOptions['middleware'] = (class_exists($handler['middleware'])) ? function () use ($handler) {
(new $handler['middleware']())->call();
} : static::$namedMiddleware[$handler['middleware']] ?? null;
$parsedOptions['middleware'] = static::resolveMiddleware($handler['middleware']);
}

if (is_array($handler['middleware'] ?? null)) {
$parsedOptions['middleware'] = [];

foreach ($handler['middleware'] as $middleware) {
if (is_string($middleware)) {
$parsedOptions['middleware'][] = static::$namedMiddleware[$middleware] ?? null;
} else {
$parsedOptions['middleware'][] = $middleware;
}
$parsedOptions['middleware'][] = static::resolveMiddleware($middleware);
}
}

Expand Down Expand Up @@ -805,6 +799,42 @@ public static function use($middleware)
}
}

/**
* Turn a middleware reference into something invokable.
*
* Class names are wrapped so the class is instantiated and called
* at request time. Named middleware is resolved lazily, so it does
* not matter whether registerMiddleware() runs before or after the
* routes that use it. A name that is still unknown when the request
* hits the route throws instead of silently letting the route run.
*
* @param callable|string $middleware A callable, a class name or a registered middleware name
*
* @return callable
*/
protected static function resolveMiddleware($middleware): callable
{
if (!is_string($middleware)) {
return $middleware;
}

if (class_exists($middleware)) {
return function () use ($middleware) {
(new $middleware())->call();
};
}

return function (...$params) use ($middleware) {
if (!isset(static::$namedMiddleware[$middleware])) {
throw new \RuntimeException(
"Middleware named '$middleware' not found. Register it with registerMiddleware() before the request is handled."
);
}

return static::$namedMiddleware[$middleware](...$params);
};
}

/**
* Register a middleware in your Leaf application by name
*
Expand Down
53 changes: 53 additions & 0 deletions tests/middleware.test.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,56 @@ class StaticTestClassMid
expect($ran['global'])->toBeTrue()
->and($ran['group'])->toBeTrue();
});

test('named middleware registered after the routes still runs', function () {
\Leaf\Router::reset();
app()->setBasePath('/');
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/late/42';

$ran = ['string' => false, 'array' => false, 'group' => false];

app()->get('/late/{id}', ['middleware' => 'late.string', function () {}]);
app()->get('/late-array/{id}', ['middleware' => ['late.array'], function () {}]);
app()->group('/late-group', ['middleware' => 'late.group', function () {
app()->get('/{id}', function () {});
}]);

// registered AFTER the routes above were declared
app()->registerMiddleware('late.string', function () use (&$ran) {
$ran['string'] = true;
});
app()->registerMiddleware('late.array', function () use (&$ran) {
$ran['array'] = true;
});
app()->registerMiddleware('late.group', function () use (&$ran) {
$ran['group'] = true;
});

app()->run();
expect($ran['string'])->toBeTrue();

$_SERVER['REQUEST_URI'] = '/late-array/42';
app()->run();
expect($ran['array'])->toBeTrue();

$_SERVER['REQUEST_URI'] = '/late-group/42';
app()->run();
expect($ran['group'])->toBeTrue();
});

test('a route using an unregistered named middleware fails closed', function () {
\Leaf\Router::reset();
app()->setBasePath('/');
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/never';

$handlerRan = false;

app()->get('/never', ['middleware' => 'never.registered', function () use (&$handlerRan) {
$handlerRan = true;
}]);

expect(fn () => app()->run())->toThrow(RuntimeException::class, 'never.registered');
expect($handlerRan)->toBeFalse();
});
Loading