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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 4.0.3 under development

- Enh #276: Explicitly import classes, functions, and constants in the "use" section (@rustamwin)
- Enh #277: Remove restrictions from `prependMiddleware()` and `middleware()` methods (@klsoft-web)

## 4.0.2 December 13, 2025

Expand Down
13 changes: 0 additions & 13 deletions src/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Yiisoft\Router;

use InvalidArgumentException;
use RuntimeException;
use Yiisoft\Router\Internal\MiddlewareFilter;

use function in_array;
Expand All @@ -28,8 +27,6 @@ final class Group
*/
private array $hosts = [];
private ?string $namePrefix = null;
private bool $routesAdded = false;
private bool $middlewareAdded = false;
private array $disabledMiddlewares = [];

/**
Expand Down Expand Up @@ -58,13 +55,8 @@ public static function create(?string $prefix = null): self

public function routes(self|Route ...$routes): self
{
if ($this->middlewareAdded) {
throw new RuntimeException('routes() can not be used after prependMiddleware().');
}

$new = clone $this;
$new->routes = $routes;
$new->routesAdded = true;

return $new;
}
Expand All @@ -89,10 +81,6 @@ public function withCors(array|callable|string|null $middlewareDefinition): self
*/
public function middleware(array|callable|string ...$definition): self
{
if ($this->routesAdded) {
throw new RuntimeException('middleware() can not be used after routes().');
}

$new = clone $this;
array_push(
$new->middlewares,
Expand All @@ -116,7 +104,6 @@ public function prependMiddleware(array|callable|string ...$definition): self
...array_values($definition),
);

$new->middlewareAdded = true;
$new->enabledMiddlewaresCache = null;

return $new;
Expand Down
27 changes: 15 additions & 12 deletions src/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
namespace Yiisoft\Router;

use InvalidArgumentException;
use RuntimeException;
use Stringable;
use Yiisoft\Http\Method;
use Yiisoft\Router\Internal\MiddlewareFilter;

use function array_slice;
use function count;
use function in_array;

/**
Expand Down Expand Up @@ -194,19 +195,25 @@ public function defaults(array $defaults): self
/**
* Appends a handler middleware definition that should be invoked for a matched route.
* First added handler will be executed first.
* If no actions have been added, the middleware is added to the end of the list. Otherwise, it is added before the action.
*/
public function middleware(array|callable|string ...$definition): self
{
$route = clone $this;
if ($this->actionAdded) {
throw new RuntimeException('middleware() can not be used after action().');
$lastIndex = count($route->middlewares) - 1;
$route->middlewares = array_merge(
array_slice($route->middlewares, 0, $lastIndex),
array_values($definition),
array_slice($route->middlewares, $lastIndex),
);
} else {
array_push(
$route->middlewares,
...array_values($definition),
);
}

$route = clone $this;
array_push(
$route->middlewares,
...array_values($definition),
);

$route->enabledMiddlewaresCache = null;

return $route;
Expand All @@ -227,10 +234,6 @@ public function middleware(array|callable|string ...$definition): self
*/
public function prependMiddleware(array|callable|string ...$definition): self
{
if (!$this->actionAdded) {
throw new RuntimeException('prependMiddleware() can not be used before action().');
}

$route = clone $this;
array_unshift(
$route->middlewares,
Expand Down
36 changes: 22 additions & 14 deletions tests/GroupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RuntimeException;
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
use Yiisoft\Router\Group;
Expand All @@ -23,6 +22,7 @@
use Yiisoft\Router\Tests\Support\TestMiddleware1;
use Yiisoft\Router\Tests\Support\TestMiddleware2;
use Yiisoft\Router\Tests\Support\TestMiddleware3;
use Yiisoft\Router\Tests\Support\TestController;

final class GroupTest extends TestCase
{
Expand Down Expand Up @@ -124,16 +124,11 @@ public function testNamedArgumentsInMiddlewareMethods(): void

public function testRoutesAfterMiddleware(): void
{
$group = Group::create();

$middleware1 = static fn() => new Response();

$group = $group->prependMiddleware($middleware1);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('routes() can not be used after prependMiddleware().');
$group = Group::create()
->prependMiddleware(TestMiddleware1::class)
->routes(Route::get('/'));

$group->routes(Route::get('/'));
$this->assertSame([TestMiddleware1::class], $group->getData('enabledMiddlewares'));
}

public function testAddNestedMiddleware(): void
Expand Down Expand Up @@ -438,11 +433,24 @@ public function testWithCorsWithNestedGroups2(): void

public function testMiddlewareAfterRoutes(): void
{
$group = Group::create()->routes(Route::get('/info')->action(static fn() => 'info'));
$group = Group::create()
->routes(Route::get('/info')
->middleware(TestMiddleware3::class)
->action([TestController::class, 'index']))
->middleware(TestMiddleware1::class, TestMiddleware2::class);

$collector = new RouteCollector();
$collector->addRoute($group);
$routeCollection = new RouteCollection($collector);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('middleware() can not be used after routes().');
$group->middleware(static fn() => new Response());
$this->assertSame(
[TestMiddleware1::class, TestMiddleware2::class],
$group->getData('enabledMiddlewares'),
);
$this->assertSame(
[TestMiddleware1::class, TestMiddleware2::class, TestMiddleware3::class, [TestController::class, 'index']],
$routeCollection->getRoute('GET /info')->getData('enabledMiddlewares'),
);
}

public function testDuplicateHosts(): void
Expand Down
27 changes: 17 additions & 10 deletions tests/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RuntimeException;
use Yiisoft\Http\Method;
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
Expand Down Expand Up @@ -230,22 +229,30 @@ public function testDispatcherInjecting(): void
$this->assertSame(200, $response->getStatusCode());
}

public function testMiddlewareAfterAction(): void
public function testPrependMiddlewareBeforeAction(): void
{
$route = Route::get('/')->action([TestController::class, 'index']);
$route = Route::get('/')
->prependMiddleware(TestMiddleware1::class)
->action([TestController::class, 'index']);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('middleware() can not be used after action().');
$route->middleware(static fn() => new Response());
$this->assertSame(
[TestMiddleware1::class, [TestController::class, 'index']],
$route->getData('enabledMiddlewares'),
);
}

public function testPrependMiddlewareBeforeAction(): void
public function testMiddlewareAfterAction(): void
{
$route = Route::get('/');
$route = $route->middleware(TestMiddleware1::class)
->action([TestController::class, 'index'])
->middleware(TestMiddleware2::class)
->middleware(TestMiddleware3::class);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('prependMiddleware() can not be used before action().');
$route->prependMiddleware(static fn() => new Response());
$this->assertSame(
[TestMiddleware1::class, TestMiddleware2::class, TestMiddleware3::class, [TestController::class, 'index']],
$route->getData('enabledMiddlewares'),
);
}

public function testDisabledMiddlewareDefinitions(): void
Expand Down
Loading