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

Support Attributes #194

Closed
wants to merge 15 commits into from
3 changes: 1 addition & 2 deletions .phpstorm.meta.php/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
'corsMiddleware',
'items',
'middlewareDefinitions',
'hasDispatcher',
'hasCorsMiddleware'
);
}
}
4 changes: 1 addition & 3 deletions .phpstorm.meta.php/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
'methods',
'override',
'defaults',
'dispatcherWithMiddlewares',
'hasDispatcher',
'hasMiddlewares'
);
}
}
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,30 @@ In addition to commonly used `getArgument()` method, the following methods are a
- `getMethods()` - To get route methods.
- `getUri()` - To get current URI.

## Attributes

`Route` class can be used as an attribute:

```php
use Yiisoft\Router\Route;

class Controller
{
#[Route(
methods: ['GET', 'POST'],
pattern: '/',
)]
public function view()
{
// ...
}
}
```

Only two properties are required: `methods` and `pattern`. The rest properties are optional.

> Parsing attributes is up to user. The package does not provide a solution for parsing and constructing routes tree.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to link to the guide on how to achieve colleccting attributes or to describe it right here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any attributes collectors. I'll add it later if we merge this example


## Testing

### Unit testing
Expand Down
33 changes: 6 additions & 27 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 Down Expand Up @@ -36,21 +35,19 @@ final class Group
*/
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);
return new self($prefix);
}

public function routes(self|Route ...$routes): self
Expand All @@ -59,32 +56,13 @@ public function routes(self|Route ...$routes): self
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->items = $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 Down Expand Up @@ -175,12 +153,14 @@ public function disableMiddleware(mixed ...$middlewareDefinition): self

/**
* @psalm-template T as string
*
* @psalm-param T $key
*
* @psalm-return (
* T is ('prefix'|'namePrefix'|'host') ? string|null :
* (T is 'items' ? Group[]|Route[] :
* (T is 'hosts' ? array<array-key, string> :
* (T is ('hasCorsMiddleware'|'hasDispatcher') ? bool :
* (T is ('hasCorsMiddleware'') ? bool :
* (T is 'middlewareDefinitions' ? list<array|callable|string> :
* (T is 'corsMiddleware' ? array|callable|string|null : mixed)
* )
Expand All @@ -199,7 +179,6 @@ public function getData(string $key): mixed
'corsMiddleware' => $this->corsMiddleware,
'items' => $this->items,
'hasCorsMiddleware' => $this->corsMiddleware !== null,
'hasDispatcher' => $this->dispatcher !== null,
'middlewareDefinitions' => $this->getMiddlewareDefinitions(),
default => throw new InvalidArgumentException('Unknown data key: ' . $key),
};
Expand Down
27 changes: 19 additions & 8 deletions src/MatchingResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,29 @@ public function route(): Route

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$this->isSuccess()) {
return $handler->handle($request);
if ($this->isSuccess() && $this->dispatcher !== null) {
return $this
->buildDispatcher($this->dispatcher, $this->route)
->dispatch($request, $handler);
}

// Inject dispatcher only if we have not previously injected.
return $handler->handle($request);
}

private function buildDispatcher(
?MiddlewareDispatcher $dispatcher,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
?MiddlewareDispatcher $dispatcher,
MiddlewareDispatcher $dispatcher,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dispatcher is nullable

Route $route,
): MiddlewareDispatcher {
if ($dispatcher === null) {
throw new RuntimeException(sprintf('There is no dispatcher in the route %s.', $route->getData('name')));
}

Comment on lines +113 to +116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($dispatcher === null) {
throw new RuntimeException(sprintf('There is no dispatcher in the route %s.', $route->getData('name')));
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

// Don't add middlewares to dispatcher if we did it earlier.
// This improves performance in event-loop applications.
if ($this->dispatcher !== null && !$this->route->getData('hasDispatcher')) {
$this->route->injectDispatcher($this->dispatcher);
if ($dispatcher->hasMiddlewares()) {
return $dispatcher;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Router pass middlware dispatcher to MatchingResult and this dispatcher almost always contain middlewares. In this case route middlewares will not work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you are saying

}

return $this->route
->getData('dispatcherWithMiddlewares')
->dispatch($request, $handler);
return $dispatcher->withMiddlewares($route->getMiddlewares());
}
}
Loading