Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Route, Group and MatchingResult dispatcher independent #222

Merged
merged 11 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 3 additions & 1 deletion .github/workflows/rector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ name: rector
jobs:
rector:
uses: yiisoft/actions/.github/workflows/rector.yml@master
secrets:
token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}
with:
os: >-
['ubuntu-latest']
php: >-
['8.0']
['8.2']
9 changes: 4 additions & 5 deletions .phpstorm.meta.php/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
'host',
'hosts',
'corsMiddleware',
'items',
'middlewareDefinitions',
'hasDispatcher',
'hasCorsMiddleware'
'routes',
'hasCorsMiddleware',
'enabledMiddlewares',
);
}
}
11 changes: 5 additions & 6 deletions .phpstorm.meta.php/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
registerArgumentsSet(
'routeDataKeys',
'name',
'pattern',
'host',
'hosts',
'pattern',
'methods',
'override',
'defaults',
'dispatcherWithMiddlewares',
'hasDispatcher',
'hasMiddlewares'
'override',
'hasMiddlewares',
'enabledMiddlewares',
);
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
vjik marked this conversation as resolved.
Show resolved Hide resolved

## 3.0.0 February 17, 2023

Expand Down
34 changes: 34 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -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
vjik marked this conversation as resolved.
Show resolved Hide resolved
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()`.
3 changes: 3 additions & 0 deletions src/Debug/DebugRoutesCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
use Yiisoft\VarDumper\VarDumper;
use Yiisoft\Yii\Debug\Debugger;

/**
* @codeCoverageIgnore
*/
final class DebugRoutesCommand extends Command
{
public const COMMAND_NAME = 'debug:routes';
Expand Down
3 changes: 3 additions & 0 deletions src/Debug/RouterCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use Yiisoft\Yii\Debug\Collector\CollectorTrait;
use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface;

/**
* @codeCoverageIgnore
*/
final class RouterCollector implements SummaryCollectorInterface
{
use CollectorTrait;
Expand Down
3 changes: 3 additions & 0 deletions src/Debug/UrlMatcherInterfaceProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
use Yiisoft\Router\MatchingResult;
use Yiisoft\Router\UrlMatcherInterface;

/**
* @codeCoverageIgnore
*/
final class UrlMatcherInterfaceProxy implements UrlMatcherInterface
{
public function __construct(private UrlMatcherInterface $urlMatcher, private RouterCollector $routerCollector)
Expand Down
86 changes: 32 additions & 54 deletions src/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use InvalidArgumentException;
use RuntimeException;
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;

use function in_array;

Expand All @@ -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[]
Expand All @@ -29,62 +28,41 @@ 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
{
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.
Expand All @@ -103,29 +81,31 @@ 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;
}

/**
* 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;
Expand Down Expand Up @@ -163,12 +143,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;
}
Expand All @@ -180,10 +160,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<array-key, string> :
* (T is ('hasCorsMiddleware'|'hasDispatcher') ? bool :
* (T is 'middlewareDefinitions' ? list<array|callable|string> :
* (T is ('hasCorsMiddleware') ? bool :
* (T is 'enabledMiddlewares' ? list<array|callable|string> :
* (T is 'corsMiddleware' ? array|callable|string|null : mixed)
* )
* )
Expand All @@ -199,23 +179,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);
}
}
42 changes: 7 additions & 35 deletions src/MatchingResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string,string>
* @var string[]
* @psalm-var array<string,string>
*/
private array $arguments = [];

Expand All @@ -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<string,string> $arguments
* @param string[] $arguments
* @psalm-param array<string,string> $arguments
*/
public static function fromSuccess(Route $route, array $arguments): self
{
Expand Down Expand Up @@ -71,7 +59,8 @@ public function isMethodFailure(): bool
}

/**
* @return array<string,string>
* @return string[]
* @psalm-return array<string,string>
*/
public function arguments(): array
{
Expand All @@ -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);
}
}
Loading
Loading