diff --git a/.phpstorm.meta.php/Group.php b/.phpstorm.meta.php/Group.php index 70eb3db..c2f0bb0 100644 --- a/.phpstorm.meta.php/Group.php +++ b/.phpstorm.meta.php/Group.php @@ -11,9 +11,8 @@ 'host', 'hosts', 'corsMiddleware', - 'items', - 'middlewareDefinitions', - 'hasDispatcher', - 'hasCorsMiddleware' + 'routes', + 'hasCorsMiddleware', + 'enabledMiddlewares', ); -} \ No newline at end of file +} diff --git a/.phpstorm.meta.php/Route.php b/.phpstorm.meta.php/Route.php index e30885c..4a7df73 100644 --- a/.phpstorm.meta.php/Route.php +++ b/.phpstorm.meta.php/Route.php @@ -7,14 +7,13 @@ registerArgumentsSet( 'routeDataKeys', 'name', + 'pattern', 'host', 'hosts', - 'pattern', 'methods', - 'override', 'defaults', - 'dispatcherWithMiddlewares', - 'hasDispatcher', - 'hasMiddlewares' + 'override', + 'hasMiddlewares', + 'enabledMiddlewares', ); -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index fda7b26..50b1277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - New #195: Add debug collector for `yiisoft/yii-debug` (@xepozz) - Chg #207: Replace two `RouteCollectorInterface` methods `addRoute()` and `addGroup()` to single `addRoute()` (@vjik) - Enh #202: Add support for `psr/http-message` version `^2.0` (@vjik) +- Chg #222: Make `Route`, `Group` and `MatchingResult` dispatcher independent (@rustamwin, @vjik) ## 3.0.0 February 17, 2023 diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..08d323a --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,34 @@ +# Upgrading Instructions for Yii Router + +This file contains the upgrade notes for the Yii Router. +These notes highlight changes that could break your application when you upgrade it from one major version to another. + +## 4.0.0 + +In this release classes `Route`, `Group` and `MatchingResult` are made dispatcher independent. Now you don't can inject +own middleware dispatcher to group or to route. + +The following backward incompatible changes have been made. + +### `Route` + +- Removed parameter `$dispatcher` from `Route` creating methods: `get()`, `post()`, `put()`, `delete()`, `patch()`, + `head()`, `options()`, `methods()`. +- Removed methods `Route::injectDispatcher()` and `Route::withDispatcher()`. +- `Route::getData()` changes: + - removed elements `dispatcherWithMiddlewares` and `hasDispatcher`; + - added element `enabledMiddlewares`. + +### `Group` + +- Removed parameter `$dispatcher` from `Group::create()` method. +- Removed method `Group::withDispatcher()`. +- `Group::getData()` changes: + - removed element `hasDispatcher`; + - key `items` renamed to `routes`; + - key `middlewareDefinitions` renamed to `enabledMiddlewares`. + +### `MatchingResult` + +- Removed `MatchingResult` implementation from `MiddlewareInterface`, so it is no longer middleware. +- Removed method `MatchingResult::process()`. diff --git a/src/Group.php b/src/Group.php index ad9e44d..83dfd19 100644 --- a/src/Group.php +++ b/src/Group.php @@ -6,7 +6,6 @@ use InvalidArgumentException; use RuntimeException; -use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; use function in_array; @@ -15,12 +14,12 @@ final class Group /** * @var Group[]|Route[] */ - private array $items = []; + private array $routes = []; /** * @var array[]|callable[]|string[] */ - private array $middlewareDefinitions = []; + private array $middlewares = []; /** * @var string[] @@ -29,28 +28,25 @@ final class Group private ?string $namePrefix = null; private bool $routesAdded = false; private bool $middlewareAdded = false; - private array $disabledMiddlewareDefinitions = []; + private array $disabledMiddlewares = []; /** * @var array|callable|string|null Middleware definition for CORS requests. */ private $corsMiddleware = null; - private function __construct(private ?string $prefix = null, private ?MiddlewareDispatcher $dispatcher = null) - { + private function __construct( + private ?string $prefix = null + ) { } /** * Create a new group instance. * * @param string|null $prefix URL prefix to prepend to all routes of the group. - * @param MiddlewareDispatcher|null $dispatcher Middleware dispatcher to use for the group. */ - public static function create( - ?string $prefix = null, - MiddlewareDispatcher $dispatcher = null - ): self { - return new self($prefix, $dispatcher); + public static function create(?string $prefix = null): self { + return new self($prefix); } public function routes(self|Route ...$routes): self @@ -58,33 +54,14 @@ public function routes(self|Route ...$routes): self if ($this->middlewareAdded) { throw new RuntimeException('routes() can not be used after prependMiddleware().'); } - $new = clone $this; - foreach ($routes as $route) { - if ($new->dispatcher !== null && !$route->getData('hasDispatcher')) { - $route = $route->withDispatcher($new->dispatcher); - } - $new->items[] = $route; - } + $new = clone $this; + $new->routes = $routes; $new->routesAdded = true; return $new; } - public function withDispatcher(MiddlewareDispatcher $dispatcher): self - { - $group = clone $this; - $group->dispatcher = $dispatcher; - foreach ($group->items as $index => $item) { - if (!$item->getData('hasDispatcher')) { - $item = $item->withDispatcher($dispatcher); - $group->items[$index] = $item; - } - } - - return $group; - } - /** * Adds a middleware definition that handles CORS requests. * If set, routes for {@see Method::OPTIONS} request will be added automatically. @@ -103,16 +80,18 @@ public function withCors(array|callable|string|null $middlewareDefinition): self * Appends a handler middleware definition that should be invoked for a matched route. * First added handler will be executed first. */ - public function middleware(array|callable|string ...$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->middlewareDefinitions, - ...array_values($middlewareDefinition) + $new->middlewares, + ...array_values($definition) ); + return $new; } @@ -120,12 +99,12 @@ public function middleware(array|callable|string ...$middlewareDefinition): self * Prepends a handler middleware definition that should be invoked for a matched route. * First added handler will be executed last. */ - public function prependMiddleware(array|callable|string ...$middlewareDefinition): self + public function prependMiddleware(array|callable|string ...$definition): self { $new = clone $this; array_unshift( - $new->middlewareDefinitions, - ...array_values($middlewareDefinition) + $new->middlewares, + ...array_values($definition) ); $new->middlewareAdded = true; return $new; @@ -163,12 +142,12 @@ public function hosts(string ...$hosts): self * It is useful to avoid invoking one of the parent group middleware for * a certain route. */ - public function disableMiddleware(mixed ...$middlewareDefinition): self + public function disableMiddleware(mixed ...$definition): self { $new = clone $this; array_push( - $new->disabledMiddlewareDefinitions, - ...array_values($middlewareDefinition), + $new->disabledMiddlewares, + ...array_values($definition), ); return $new; } @@ -180,10 +159,10 @@ public function disableMiddleware(mixed ...$middlewareDefinition): self * * @psalm-return ( * T is ('prefix'|'namePrefix'|'host') ? string|null : - * (T is 'items' ? Group[]|Route[] : + * (T is 'routes' ? Group[]|Route[] : * (T is 'hosts' ? array : - * (T is ('hasCorsMiddleware'|'hasDispatcher') ? bool : - * (T is 'middlewareDefinitions' ? list : + * (T is ('hasCorsMiddleware') ? bool : + * (T is 'enabledMiddlewares' ? list : * (T is 'corsMiddleware' ? array|callable|string|null : mixed) * ) * ) @@ -199,23 +178,21 @@ public function getData(string $key): mixed 'host' => $this->hosts[0] ?? null, 'hosts' => $this->hosts, 'corsMiddleware' => $this->corsMiddleware, - 'items' => $this->items, + 'routes' => $this->routes, 'hasCorsMiddleware' => $this->corsMiddleware !== null, - 'hasDispatcher' => $this->dispatcher !== null, - 'middlewareDefinitions' => $this->getMiddlewareDefinitions(), + 'enabledMiddlewares' => $this->getEnabledMiddlewares(), default => throw new InvalidArgumentException('Unknown data key: ' . $key), }; } - private function getMiddlewareDefinitions(): array + private function getEnabledMiddlewares(): array { - /** @var mixed $definition */ - foreach ($this->middlewareDefinitions as $index => $definition) { - if (in_array($definition, $this->disabledMiddlewareDefinitions, true)) { - unset($this->middlewareDefinitions[$index]); + foreach ($this->middlewares as $index => $definition) { + if (in_array($definition, $this->disabledMiddlewares, true)) { + unset($this->middlewares[$index]); } } - return array_values($this->middlewareDefinitions); + return array_values($this->middlewares); } } diff --git a/src/MatchingResult.php b/src/MatchingResult.php index 30c5918..997d8dd 100644 --- a/src/MatchingResult.php +++ b/src/MatchingResult.php @@ -4,18 +4,14 @@ namespace Yiisoft\Router; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; use Yiisoft\Http\Method; -use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; -final class MatchingResult implements MiddlewareInterface +final class MatchingResult { /** - * @var array + * @var string[] + * @psalm-var array */ private array $arguments = []; @@ -24,21 +20,13 @@ final class MatchingResult implements MiddlewareInterface */ private array $methods = []; - private ?MiddlewareDispatcher $dispatcher = null; - private function __construct(private ?Route $route) { } - public function withDispatcher(MiddlewareDispatcher $dispatcher): self - { - $new = clone $this; - $new->dispatcher = $dispatcher; - return $new; - } - /** - * @param array $arguments + * @param string[] $arguments + * @psalm-param array $arguments */ public static function fromSuccess(Route $route, array $arguments): self { @@ -71,7 +59,8 @@ public function isMethodFailure(): bool } /** - * @return array + * @return string[] + * @psalm-return array */ public function arguments(): array { @@ -94,21 +83,4 @@ public function route(): Route return $this->route; } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - if (!$this->isSuccess()) { - return $handler->handle($request); - } - - // Inject dispatcher only if we have not previously injected. - // This improves performance in event-loop applications. - if ($this->dispatcher !== null && !$this->route->getData('hasDispatcher')) { - $this->route->injectDispatcher($this->dispatcher); - } - - return $this->route - ->getData('dispatcherWithMiddlewares') - ->dispatch($request, $handler); - } } diff --git a/src/Middleware/Router.php b/src/Middleware/Router.php index 631823f..644bc8c 100644 --- a/src/Middleware/Router.php +++ b/src/Middleware/Router.php @@ -54,8 +54,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $this->currentRoute->setRouteWithArguments($result->route(), $result->arguments()); - return $result - ->withDispatcher($this->dispatcher) - ->process($request, $handler); + return $this->dispatcher + ->withMiddlewares($result->route()->getData('enabledMiddlewares')) + ->dispatch($request, $handler); } } diff --git a/src/Route.php b/src/Route.php index bed2bc7..3318a08 100644 --- a/src/Route.php +++ b/src/Route.php @@ -8,7 +8,6 @@ use RuntimeException; use Stringable; use Yiisoft\Http\Method; -use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; use function in_array; @@ -28,10 +27,16 @@ final class Route implements Stringable /** * @var array[]|callable[]|string[] + * @psalm-var list */ - private array $middlewareDefinitions = []; + private array $middlewares = []; - private array $disabledMiddlewareDefinitions = []; + private array $disabledMiddlewares = []; + + /** + * @psalm-var list|null + */ + private ?array $enabledMiddlewares = null; /** * @var array @@ -44,69 +49,50 @@ final class Route implements Stringable private function __construct( private array $methods, private string $pattern, - private ?MiddlewareDispatcher $dispatcher = null ) { } - /** - * @psalm-assert MiddlewareDispatcher $this->dispatcher - */ - public function injectDispatcher(MiddlewareDispatcher $dispatcher): void + public static function get(string $pattern): self { - $this->dispatcher = $dispatcher; + return self::methods([Method::GET], $pattern); } - public function withDispatcher(MiddlewareDispatcher $dispatcher): self + public static function post(string $pattern): self { - $route = clone $this; - $route->dispatcher = $dispatcher; - return $route; + return self::methods([Method::POST], $pattern); } - public static function get(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self + public static function put(string $pattern): self { - return self::methods([Method::GET], $pattern, $dispatcher); + return self::methods([Method::PUT], $pattern); } - public static function post(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self + public static function delete(string $pattern): self { - return self::methods([Method::POST], $pattern, $dispatcher); + return self::methods([Method::DELETE], $pattern); } - public static function put(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self + public static function patch(string $pattern): self { - return self::methods([Method::PUT], $pattern, $dispatcher); + return self::methods([Method::PATCH], $pattern); } - public static function delete(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self + public static function head(string $pattern): self { - return self::methods([Method::DELETE], $pattern, $dispatcher); + return self::methods([Method::HEAD], $pattern); } - public static function patch(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self + public static function options(string $pattern): self { - return self::methods([Method::PATCH], $pattern, $dispatcher); - } - - public static function head(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self - { - return self::methods([Method::HEAD], $pattern, $dispatcher); - } - - public static function options(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self - { - return self::methods([Method::OPTIONS], $pattern, $dispatcher); + return self::methods([Method::OPTIONS], $pattern); } /** * @param string[] $methods */ - public static function methods( - array $methods, - string $pattern, - ?MiddlewareDispatcher $dispatcher = null - ): self { - return new self($methods, $pattern, $dispatcher); + public static function methods(array $methods, string $pattern): self + { + return new self($methods, $pattern); } public function name(string $name): self @@ -170,15 +156,15 @@ 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. */ - public function middleware(array|callable|string ...$middlewareDefinition): self + public function middleware(array|callable|string ...$definition): self { if ($this->actionAdded) { throw new RuntimeException('middleware() can not be used after action().'); } $route = clone $this; array_push( - $route->middlewareDefinitions, - ...array_values($middlewareDefinition) + $route->middlewares, + ...array_values($definition) ); return $route; } @@ -187,15 +173,15 @@ public function middleware(array|callable|string ...$middlewareDefinition): self * Prepends a handler middleware definition that should be invoked for a matched route. * Last added handler will be executed first. */ - public function prependMiddleware(array|callable|string ...$middlewareDefinition): 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->middlewareDefinitions, - ...array_values($middlewareDefinition) + $route->middlewares, + ...array_values($definition) ); return $route; } @@ -206,7 +192,7 @@ public function prependMiddleware(array|callable|string ...$middlewareDefinition public function action(array|callable|string $middlewareDefinition): self { $route = clone $this; - $route->middlewareDefinitions[] = $middlewareDefinition; + $route->middlewares[] = $middlewareDefinition; $route->actionAdded = true; return $route; } @@ -216,12 +202,12 @@ public function action(array|callable|string $middlewareDefinition): self * It is useful to avoid invoking one of the parent group middleware for * a certain route. */ - public function disableMiddleware(mixed ...$middlewareDefinition): self + public function disableMiddleware(mixed ...$definition): self { $route = clone $this; array_push( - $route->disabledMiddlewareDefinitions, - ...array_values($middlewareDefinition) + $route->disabledMiddlewares, + ...array_values($definition) ); return $route; } @@ -237,8 +223,8 @@ public function disableMiddleware(mixed ...$middlewareDefinition): self * (T is 'hosts' ? array : * (T is 'methods' ? array : * (T is 'defaults' ? array : - * (T is ('override'|'hasMiddlewares'|'hasDispatcher') ? bool : - * (T is 'dispatcherWithMiddlewares' ? MiddlewareDispatcher : mixed) + * (T is ('override'|'hasMiddlewares') ? bool : + * (T is 'enabledMiddlewares' ? array : mixed) * ) * ) * ) @@ -257,9 +243,8 @@ public function getData(string $key): mixed 'methods' => $this->methods, 'defaults' => $this->defaults, 'override' => $this->override, - 'dispatcherWithMiddlewares' => $this->getDispatcherWithMiddlewares(), - 'hasMiddlewares' => $this->middlewareDefinitions !== [], - 'hasDispatcher' => $this->dispatcher !== null, + 'hasMiddlewares' => $this->middlewares !== [], + 'enabledMiddlewares' => $this->getEnabledMiddlewares(), default => throw new InvalidArgumentException('Unknown data key: ' . $key), }; } @@ -297,31 +282,29 @@ public function __debugInfo() 'defaults' => $this->defaults, 'override' => $this->override, 'actionAdded' => $this->actionAdded, - 'middlewareDefinitions' => $this->middlewareDefinitions, - 'disabledMiddlewareDefinitions' => $this->disabledMiddlewareDefinitions, - 'middlewareDispatcher' => $this->dispatcher, + 'middlewares' => $this->middlewares, + 'disabledMiddlewares' => $this->disabledMiddlewares, + 'enabledMiddlewares' => $this->getEnabledMiddlewares(), ]; } - private function getDispatcherWithMiddlewares(): MiddlewareDispatcher + /** + * @return array[]|callable[]|string[] + * @psalm-return list + */ + private function getEnabledMiddlewares(): array { - if ($this->dispatcher === null) { - throw new RuntimeException(sprintf('There is no dispatcher in the route %s.', $this->getData('name'))); + if ($this->enabledMiddlewares !== null) { + return $this->enabledMiddlewares; } - // Don't add middlewares to dispatcher if we did it earlier. - // This improves performance in event-loop applications. - if ($this->dispatcher->hasMiddlewares()) { - return $this->dispatcher; - } - - /** @var mixed $definition */ - foreach ($this->middlewareDefinitions as $index => $definition) { - if (in_array($definition, $this->disabledMiddlewareDefinitions, true)) { - unset($this->middlewareDefinitions[$index]); - } - } + $this->enabledMiddlewares = array_values( + array_filter( + $this->middlewares, + fn ($definition) => !in_array($definition, $this->disabledMiddlewares, true) + ) + ); - return $this->dispatcher = $this->dispatcher->withMiddlewares($this->middlewareDefinitions); + return $this->enabledMiddlewares; } } diff --git a/src/RouteCollection.php b/src/RouteCollection.php index 629ed54..01be086 100644 --- a/src/RouteCollection.php +++ b/src/RouteCollection.php @@ -104,12 +104,12 @@ private function injectGroup(Group $group, array &$tree, string $prefix = '', st { $prefix .= (string) $group->getData('prefix'); $namePrefix .= (string) $group->getData('namePrefix'); - $items = $group->getData('items'); + $items = $group->getData('routes'); $pattern = null; $hosts = []; foreach ($items as $item) { if (!$this->isStaticRoute($item)) { - $item = $item->prependMiddleware(...$group->getData('middlewareDefinitions')); + $item = $item->prependMiddleware(...$group->getData('enabledMiddlewares')); } if (!empty($group->getData('hosts')) && empty($item->getData('hosts'))) { diff --git a/tests/GroupTest.php b/tests/GroupTest.php index 9e8999c..4ea105b 100644 --- a/tests/GroupTest.php +++ b/tests/GroupTest.php @@ -23,7 +23,6 @@ use Yiisoft\Router\Tests\Support\TestMiddleware1; use Yiisoft\Router\Tests\Support\TestMiddleware2; use Yiisoft\Router\Tests\Support\TestMiddleware3; -use Yiisoft\Test\Support\Container\SimpleContainer; final class GroupTest extends TestCase { @@ -37,9 +36,9 @@ public function testAddMiddleware(): void $group = $group ->middleware($middleware1) ->middleware($middleware2); - $this->assertCount(2, $group->getData('middlewareDefinitions')); - $this->assertSame($middleware1, $group->getData('middlewareDefinitions')[0]); - $this->assertSame($middleware2, $group->getData('middlewareDefinitions')[1]); + $this->assertCount(2, $group->getData('enabledMiddlewares')); + $this->assertSame($middleware1, $group->getData('enabledMiddlewares')[0]); + $this->assertSame($middleware2, $group->getData('enabledMiddlewares')[1]); } public function testDisabledMiddlewareDefinitions(): void @@ -49,19 +48,19 @@ public function testDisabledMiddlewareDefinitions(): void ->prependMiddleware(TestMiddleware1::class, TestMiddleware2::class) ->disableMiddleware(TestMiddleware1::class, TestMiddleware3::class); - $this->assertCount(1, $group->getData('middlewareDefinitions')); - $this->assertSame(TestMiddleware2::class, $group->getData('middlewareDefinitions')[0]); + $this->assertCount(1, $group->getData('enabledMiddlewares')); + $this->assertSame(TestMiddleware2::class, $group->getData('enabledMiddlewares')[0]); } public function testNamedArgumentsInMiddlewareMethods(): void { $group = Group::create() - ->middleware(middleware: TestMiddleware3::class) - ->prependMiddleware(middleware1: TestMiddleware1::class, middleware2: TestMiddleware2::class) - ->disableMiddleware(middleware1: TestMiddleware1::class, middleware2: TestMiddleware3::class); + ->middleware(TestMiddleware3::class) + ->prependMiddleware(TestMiddleware1::class, TestMiddleware2::class) + ->disableMiddleware(TestMiddleware1::class, TestMiddleware3::class); - $this->assertCount(1, $group->getData('middlewareDefinitions')); - $this->assertSame(TestMiddleware2::class, $group->getData('middlewareDefinitions')[0]); + $this->assertCount(1, $group->getData('enabledMiddlewares')); + $this->assertSame(TestMiddleware2::class, $group->getData('enabledMiddlewares')[0]); } public function testRoutesAfterMiddleware(): void @@ -111,8 +110,8 @@ public function testAddNestedMiddleware(): void $routeCollection = new RouteCollection($collector); $route = $routeCollection->getRoute('request1'); - $response = $route - ->getData('dispatcherWithMiddlewares') + $response = $this->getDispatcher() + ->withMiddlewares($route->getData('enabledMiddlewares')) ->dispatch($request, $this->getRequestHandler()); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('middleware2', $response->getReasonPhrase()); @@ -132,7 +131,7 @@ public function testGroupMiddlewareFullStackCalled(): void return $handler->handle($request); }; - $group = Group::create('/group', $this->getDispatcher()) + $group = Group::create('/group') ->middleware($middleware1) ->middleware($middleware2) ->routes( @@ -146,9 +145,11 @@ public function testGroupMiddlewareFullStackCalled(): void $routeCollection = new RouteCollection($collector); $route = $routeCollection->getRoute('request1'); - $response = $route - ->getData('dispatcherWithMiddlewares') + + $response = $this->getDispatcher() + ->withMiddlewares($route->getData('enabledMiddlewares')) ->dispatch($request, $this->getRequestHandler()); + $this->assertSame(200, $response->getStatusCode()); $this->assertSame('middleware2', $response->getReasonPhrase()); } @@ -161,7 +162,7 @@ public function testGroupMiddlewareStackInterrupted(): void $middleware1 = fn () => new Response(403); $middleware2 = fn () => new Response(405); - $group = Group::create('/group', $this->getDispatcher()) + $group = Group::create('/group') ->middleware($middleware1) ->middleware($middleware2) ->routes( @@ -175,9 +176,11 @@ public function testGroupMiddlewareStackInterrupted(): void $routeCollection = new RouteCollection($collector); $route = $routeCollection->getRoute('request1'); - $response = $route - ->getData('dispatcherWithMiddlewares') + + $response = $this->getDispatcher() + ->withMiddlewares($route->getData('enabledMiddlewares')) ->dispatch($request, $this->getRequestHandler()); + $this->assertSame(403, $response->getStatusCode()); } @@ -205,27 +208,27 @@ public function testAddGroup(): void ), ); - $this->assertCount(1, $root->getData('items')); + $this->assertCount(1, $root->getData('routes')); /** @var Group $api */ - $api = $root->getData('items')[0]; + $api = $root->getData('routes')[0]; $this->assertSame('/api', $api->getData('prefix')); - $this->assertCount(2, $api->getData('items')); - $this->assertSame($logoutRoute, $api->getData('items')[0]); + $this->assertCount(2, $api->getData('routes')); + $this->assertSame($logoutRoute, $api->getData('routes')[0]); /** @var Group $postGroup */ - $postGroup = $api->getData('items')[1]; + $postGroup = $api->getData('routes')[1]; $this->assertInstanceOf(Group::class, $postGroup); - $this->assertCount(2, $api->getData('middlewareDefinitions')); - $this->assertSame($middleware1, $api->getData('middlewareDefinitions')[0]); - $this->assertSame($middleware2, $api->getData('middlewareDefinitions')[1]); + $this->assertCount(2, $api->getData('enabledMiddlewares')); + $this->assertSame($middleware1, $api->getData('enabledMiddlewares')[0]); + $this->assertSame($middleware2, $api->getData('enabledMiddlewares')[1]); $this->assertSame('/post', $postGroup->getData('prefix')); - $this->assertCount(2, $postGroup->getData('items')); - $this->assertSame($listRoute, $postGroup->getData('items')[0]); - $this->assertSame($viewRoute, $postGroup->getData('items')[1]); - $this->assertEmpty($postGroup->getData('middlewareDefinitions')); + $this->assertCount(2, $postGroup->getData('routes')); + $this->assertSame($listRoute, $postGroup->getData('routes')[0]); + $this->assertSame($viewRoute, $postGroup->getData('routes')[1]); + $this->assertEmpty($postGroup->getData('enabledMiddlewares')); } public function testHost(): void @@ -259,54 +262,6 @@ public function testGetDataWithWrongKey(): void $group->getData('wrong'); } - public function testDispatcherInjected(): void - { - $dispatcher = $this->getDispatcher(); - - $apiGroup = Group::create('/api', $dispatcher) - ->routes( - Route::get('/info')->name('api-info'), - Group::create('/v1') - ->routes( - Route::get('/user')->name('api-v1-user/index'), - Route::get('/user/{id}')->name('api-v1-user/view'), - Group::create('/news') - ->routes( - Route::get('/post')->name('api-v1-news-post/index'), - Route::get('/post/{id}')->name('api-v1-news-post/view'), - ), - Group::create('/blog') - ->routes( - Route::get('/post')->name('api-v1-blog-post/index'), - Route::get('/post/{id}')->name('api-v1-blog-post/view'), - ), - Route::get('/note')->name('api-v1-note/index'), - Route::get('/note/{id}')->name('api-v1-note/view'), - ), - Group::create('/v2') - ->routes( - Route::get('/user')->name('api-v2-user/index'), - Route::get('/user/{id}')->name('api-v2-user/view'), - Group::create('/news') - ->routes( - Route::get('/post')->name('api-v2-news-post/index'), - Route::get('/post/{id}')->name('api-v2-news-post/view'), - Group::create('/blog') - ->routes( - Route::get('/post')->name('api-v2-blog-post/index'), - Route::get('/post/{id}')->name('api-v2-blog-post/view'), - Route::get('/note')->name('api-v2-note/index'), - Route::get('/note/{id}')->name('api-v2-note/view') - ) - ) - ) - ); - - $items = $apiGroup->getData('items'); - - $this->assertAllRoutesAndGroupsHaveDispatcher($items); - } - public function testWithCors(): void { $group = Group::create() @@ -440,15 +395,9 @@ public function testDuplicateHosts(): void public function testImmutability(): void { - $container = new SimpleContainer(); - $middlewareDispatcher = new MiddlewareDispatcher( - new MiddlewareFactory($container), - ); - $group = Group::create(); $this->assertNotSame($group, $group->routes()); - $this->assertNotSame($group, $group->withDispatcher($middlewareDispatcher)); $this->assertNotSame($group, $group->withCors(null)); $this->assertNotSame($group, $group->middleware()); $this->assertNotSame($group, $group->prependMiddleware()); @@ -475,16 +424,4 @@ private function getDispatcher(): MiddlewareDispatcher $this->createMock(EventDispatcherInterface::class) ); } - - private function assertAllRoutesAndGroupsHaveDispatcher(array $items): void - { - $func = function ($item) use (&$func) { - $this->assertTrue($item->getData('hasDispatcher')); - if ($item instanceof Group) { - $items = $item->getData('items'); - array_walk($items, $func); - } - }; - array_walk($items, $func); - } } diff --git a/tests/MatchingResultTest.php b/tests/MatchingResultTest.php index b75ec6b..10d67b8 100644 --- a/tests/MatchingResultTest.php +++ b/tests/MatchingResultTest.php @@ -4,21 +4,11 @@ namespace Yiisoft\Router\Tests; -use Nyholm\Psr7\Response; -use Nyholm\Psr7\ServerRequest; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; -use Psr\EventDispatcher\EventDispatcherInterface; -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; use Yiisoft\Router\MatchingResult; use Yiisoft\Router\Route; -use Yiisoft\Test\Support\Container\SimpleContainer; final class MatchingResultTest extends TestCase { @@ -48,31 +38,6 @@ public function testFromFailureOnNotFoundFailure(): void $this->assertFalse($result->isMethodFailure()); } - public function testProcessSuccess(): void - { - $container = $this->createMock(ContainerInterface::class); - $dispatcher = new MiddlewareDispatcher( - new MiddlewareFactory($container), - $this->createMock(EventDispatcherInterface::class) - ); - $route = Route::post('/', $dispatcher)->middleware($this->getMiddleware()); - $result = MatchingResult::fromSuccess($route, []); - $request = new ServerRequest('POST', '/'); - - $response = $result->process($request, $this->getRequestHandler()); - $this->assertSame(201, $response->getStatusCode()); - } - - public function testProcessFailure(): void - { - $request = new ServerRequest('POST', '/'); - - $response = MatchingResult::fromFailure([Method::GET, Method::HEAD]) - ->process($request, $this->getRequestHandler()); - - $this->assertSame(404, $response->getStatusCode()); - } - public function testRouteOnFailure(): void { $result = MatchingResult::fromFailure([Method::GET, Method::HEAD]); @@ -81,31 +46,4 @@ public function testRouteOnFailure(): void $this->expectExceptionMessage('There is no route in the matching result.'); $result->route(); } - - public function testImmutability(): void - { - $container = new SimpleContainer(); - $middlewareDispatcher = new MiddlewareDispatcher( - new MiddlewareFactory($container), - ); - - $result = MatchingResult::fromFailure([Method::GET]); - - $this->assertNotSame($result, $result->withDispatcher($middlewareDispatcher)); - } - - private function getMiddleware(): callable - { - return static fn () => new Response(201); - } - - private function getRequestHandler(): RequestHandlerInterface - { - return new class () implements RequestHandlerInterface { - public function handle(ServerRequestInterface $request): ResponseInterface - { - return new Response(404); - } - }; - } } diff --git a/tests/RouteCollectionTest.php b/tests/RouteCollectionTest.php index 23967fe..d2db3f2 100644 --- a/tests/RouteCollectionTest.php +++ b/tests/RouteCollectionTest.php @@ -276,11 +276,11 @@ public function testCollectorMiddlewareFullstackCalled(): void $route1 = $routeCollection->getRoute('list'); $route2 = $routeCollection->getRoute('view'); $request = new ServerRequest('GET', '/'); - $response1 = $route1 - ->getData('dispatcherWithMiddlewares') + $response1 = $this->getDispatcher() + ->withMiddlewares($route1->getData('enabledMiddlewares')) ->dispatch($request, $this->getRequestHandler()); - $response2 = $route2 - ->getData('dispatcherWithMiddlewares') + $response2 = $this->getDispatcher() + ->withMiddlewares($route2->getData('enabledMiddlewares')) ->dispatch($request, $this->getRequestHandler()); $this->assertEquals('middleware1', $response1->getReasonPhrase()); @@ -302,15 +302,6 @@ public function testMiddlewaresOrder(bool $groupWrapped): void { $request = new ServerRequest('GET', '/'); - $injectDispatcher = $this->getDispatcher( - new SimpleContainer([ - TestMiddleware1::class => new TestMiddleware1(), - TestMiddleware2::class => new TestMiddleware2(), - TestMiddleware3::class => new TestMiddleware3(), - TestController::class => new TestController(), - ]) - ); - $collector = new RouteCollector(); $collector @@ -327,9 +318,17 @@ public function testMiddlewaresOrder(bool $groupWrapped): void ); $route = (new RouteCollection($collector))->getRoute('main'); - $route->injectDispatcher($injectDispatcher); - $dispatcher = $route->getData('dispatcherWithMiddlewares'); + $dispatcher = $this + ->getDispatcher( + new SimpleContainer([ + TestMiddleware1::class => new TestMiddleware1(), + TestMiddleware2::class => new TestMiddleware2(), + TestMiddleware3::class => new TestMiddleware3(), + TestController::class => new TestController(), + ]) + ) + ->withMiddlewares($route->getData('enabledMiddlewares')); $response = $dispatcher->dispatch($request, $this->getRequestHandler()); $this->assertSame(200, $response->getStatusCode()); @@ -340,12 +339,6 @@ public function testStaticRouteWithCollectorMiddlewares(): void { $request = new ServerRequest('GET', '/'); - $injectDispatcher = $this->getDispatcher( - new SimpleContainer([ - TestMiddleware1::class => new TestMiddleware1(), - ]) - ); - $collector = new RouteCollector(); $collector->middleware(TestMiddleware1::class); @@ -354,9 +347,14 @@ public function testStaticRouteWithCollectorMiddlewares(): void ); $route = (new RouteCollection($collector))->getRoute('image'); - $route->injectDispatcher($injectDispatcher); - $dispatcher = $route->getData('dispatcherWithMiddlewares'); + $dispatcher = $this + ->getDispatcher( + new SimpleContainer([ + TestMiddleware1::class => new TestMiddleware1(), + ]) + ) + ->withMiddlewares($route->getData('enabledMiddlewares')); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Stack is empty.'); diff --git a/tests/RouteTest.php b/tests/RouteTest.php index d2cc255..8329bd2 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -220,12 +220,14 @@ public function testDispatcherInjecting(): void TestController::class => new TestController(), ] ); - $dispatcher = $this->getDispatcher($container); + $route = Route::get('/')->action([TestController::class, 'index']); - $route->injectDispatcher($dispatcher); - $response = $route - ->getData('dispatcherWithMiddlewares') + + $response = $this + ->getDispatcher($container) + ->withMiddlewares($route->getData('enabledMiddlewares')) ->dispatch($request, $this->getRequestHandler()); + $this->assertSame(200, $response->getStatusCode()); } @@ -251,22 +253,21 @@ public function testDisabledMiddlewareDefinitions(): void { $request = new ServerRequest('GET', '/'); - $injectDispatcher = $this->getDispatcher( - $this->getContainer([ - TestMiddleware1::class => new TestMiddleware1(), - TestMiddleware2::class => new TestMiddleware2(), - TestMiddleware3::class => new TestMiddleware3(), - TestController::class => new TestController(), - ]) - ); - $route = Route::get('/') ->middleware(TestMiddleware1::class, TestMiddleware2::class, TestMiddleware3::class) ->action([TestController::class, 'index']) ->disableMiddleware(TestMiddleware1::class, TestMiddleware3::class); - $route->injectDispatcher($injectDispatcher); - $dispatcher = $route->getData('dispatcherWithMiddlewares'); + $dispatcher = $this + ->getDispatcher( + $this->getContainer([ + TestMiddleware1::class => new TestMiddleware1(), + TestMiddleware2::class => new TestMiddleware2(), + TestMiddleware3::class => new TestMiddleware3(), + TestController::class => new TestController(), + ]) + ) + ->withMiddlewares($route->getData('enabledMiddlewares')); $response = $dispatcher->dispatch($request, $this->getRequestHandler()); $this->assertSame(200, $response->getStatusCode()); @@ -277,63 +278,25 @@ public function testPrependMiddlewareDefinitions(): void { $request = new ServerRequest('GET', '/'); - $injectDispatcher = $this->getDispatcher( - $this->getContainer([ - TestMiddleware1::class => new TestMiddleware1(), - TestMiddleware2::class => new TestMiddleware2(), - TestMiddleware3::class => new TestMiddleware3(), - TestController::class => new TestController(), - ]) - ); - $route = Route::get('/') ->middleware(TestMiddleware3::class) ->action([TestController::class, 'index']) ->prependMiddleware(TestMiddleware1::class, TestMiddleware2::class); - $route->injectDispatcher($injectDispatcher); - - $dispatcher = $route->getData('dispatcherWithMiddlewares'); - $response = $dispatcher->dispatch($request, $this->getRequestHandler()); - $this->assertSame(200, $response->getStatusCode()); - $this->assertSame('123', (string) $response->getBody()); - } - - public function testGetDispatcherWithoutDispatcher(): void - { - $route = Route::get('/')->name('test'); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('There is no dispatcher in the route test.'); - $route->getData('dispatcherWithMiddlewares'); - } - - public function testGetDispatcherWithMiddlewares(): void - { - $request = new ServerRequest('GET', '/'); - - $injectDispatcher = $this + $response = $this ->getDispatcher( $this->getContainer([ TestMiddleware1::class => new TestMiddleware1(), TestMiddleware2::class => new TestMiddleware2(), + TestMiddleware3::class => new TestMiddleware3(), TestController::class => new TestController(), ]) ) - ->withMiddlewares([ - TestMiddleware1::class, - TestMiddleware2::class, - [TestController::class, 'index'], - ]); - - $route = Route::get('/'); - $route->injectDispatcher($injectDispatcher); - - $dispatcher = $route->getData('dispatcherWithMiddlewares'); + ->withMiddlewares($route->getData('enabledMiddlewares')) + ->dispatch($request, $this->getRequestHandler()); - $response = $dispatcher->dispatch($request, $this->getRequestHandler()); $this->assertSame(200, $response->getStatusCode()); - $this->assertSame('12', (string) $response->getBody()); + $this->assertSame('123', (string) $response->getBody()); } public function testDebugInfo(): void @@ -343,10 +306,10 @@ public function testDebugInfo(): void ->host('example.com') ->defaults(['age' => 42]) ->override() - ->middleware(middleware: TestMiddleware1::class) - ->disableMiddleware(middleware: TestMiddleware2::class) + ->middleware(TestMiddleware1::class, TestMiddleware2::class) + ->disableMiddleware(TestMiddleware2::class) ->action('go') - ->prependMiddleware(middleware: TestMiddleware3::class); + ->prependMiddleware(TestMiddleware3::class); $expected = << 1 [actionAdded] => 1 - [middlewareDefinitions] => Array + [middlewares] => Array ( [0] => Yiisoft\Router\Tests\Support\TestMiddleware3 [1] => Yiisoft\Router\Tests\Support\TestMiddleware1 - [2] => go + [2] => Yiisoft\Router\Tests\Support\TestMiddleware2 + [3] => go ) - [disabledMiddlewareDefinitions] => Array + [disabledMiddlewares] => Array ( [0] => Yiisoft\Router\Tests\Support\TestMiddleware2 ) - [middlewareDispatcher] => + [enabledMiddlewares] => Array + ( + [0] => Yiisoft\Router\Tests\Support\TestMiddleware3 + [1] => Yiisoft\Router\Tests\Support\TestMiddleware1 + [2] => go + ) ) EOL; @@ -399,15 +368,9 @@ public function testDuplicateHosts(): void public function testImmutability(): void { - $container = new SimpleContainer(); - $middlewareDispatcher = new MiddlewareDispatcher( - new MiddlewareFactory($container), - ); - $route = Route::get('/'); $routeWithAction = $route->action(''); - $this->assertNotSame($route, $route->withDispatcher($middlewareDispatcher)); $this->assertNotSame($route, $route->name('')); $this->assertNotSame($route, $route->pattern('')); $this->assertNotSame($route, $route->host(''));