Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
35 / 35 |
|
100.00% |
28 / 28 |
|
78.95% |
15 / 19 |
|
100.00% |
12 / 12 |
CRAP | |
100.00% |
1 / 1 |
| Group | |
100.00% |
35 / 35 |
|
100.00% |
28 / 28 |
|
78.95% |
15 / 19 |
|
100.00% |
12 / 12 |
19.70 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| register | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| controller | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| middleware | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| before | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| after | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| addRoute | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| group | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| collect | |
100.00% |
7 / 7 |
|
100.00% |
10 / 10 |
|
20.00% |
1 / 5 |
|
100.00% |
1 / 1 |
7.61 | |||
| assertCollecting | |
100.00% |
2 / 2 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| forwardRoute | |
100.00% |
4 / 4 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| applyTo | |
100.00% |
6 / 6 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Router; |
| 6 | |
| 7 | use Celema\Router\Exception\RuntimeException; |
| 8 | use Closure; |
| 9 | use Override; |
| 10 | use Psr\Http\Server\MiddlewareInterface as Middleware; |
| 11 | |
| 12 | /** @psalm-api */ |
| 13 | final class Group implements RouteAdder |
| 14 | { |
| 15 | use AddsBeforeAfter { |
| 16 | before as private addBeforeHandler; |
| 17 | after as private addAfterHandler; |
| 18 | } |
| 19 | use AddsMiddleware { |
| 20 | middleware as private addMiddlewareHandlers; |
| 21 | } |
| 22 | use AddsRoutes; |
| 23 | |
| 24 | /** @var list<Route|Group> */ |
| 25 | private array $entries = []; |
| 26 | |
| 27 | private ?string $controller = null; |
| 28 | private bool $collecting = false; |
| 29 | |
| 30 | /** |
| 31 | * Groups are callback-scoped. Use Router::group() or nested Group::group() |
| 32 | * so the router controls registration, ordering, and group finalization. |
| 33 | */ |
| 34 | private function __construct( |
| 35 | private readonly RouteAdder $routeAdder, |
| 36 | private readonly string $patternPrefix, |
| 37 | private readonly Closure $createClosure, |
| 38 | private readonly string $namePrefix, |
| 39 | ) {} |
| 40 | |
| 41 | /** @internal */ |
| 42 | public static function register( |
| 43 | RouteAdder $adder, |
| 44 | string $patternPrefix, |
| 45 | Closure $createClosure, |
| 46 | string $namePrefix = '', |
| 47 | ): void { |
| 48 | new self($adder, $patternPrefix, $createClosure, $namePrefix)->collect(); |
| 49 | } |
| 50 | |
| 51 | public function controller(string $controller): static |
| 52 | { |
| 53 | $this->assertCollecting(); |
| 54 | $this->controller = $controller; |
| 55 | |
| 56 | return $this; |
| 57 | } |
| 58 | |
| 59 | public function middleware(Middleware ...$middleware): static |
| 60 | { |
| 61 | $this->assertCollecting(); |
| 62 | |
| 63 | return $this->addMiddlewareHandlers(...$middleware); |
| 64 | } |
| 65 | |
| 66 | public function before(Before $beforeHandler): static |
| 67 | { |
| 68 | $this->assertCollecting(); |
| 69 | |
| 70 | return $this->addBeforeHandler($beforeHandler); |
| 71 | } |
| 72 | |
| 73 | public function after(After $afterHandler): static |
| 74 | { |
| 75 | $this->assertCollecting(); |
| 76 | |
| 77 | return $this->addAfterHandler($afterHandler); |
| 78 | } |
| 79 | |
| 80 | #[Override] |
| 81 | public function addRoute(Route $route): Route |
| 82 | { |
| 83 | $this->assertCollecting(); |
| 84 | $this->entries[] = $route; |
| 85 | |
| 86 | return $route; |
| 87 | } |
| 88 | |
| 89 | #[Override] |
| 90 | public function group( |
| 91 | string $patternPrefix, |
| 92 | Closure $createClosure, |
| 93 | string $namePrefix = '', |
| 94 | ): void { |
| 95 | $this->assertCollecting(); |
| 96 | $this->entries[] = new self($this, $patternPrefix, $createClosure, $namePrefix); |
| 97 | } |
| 98 | |
| 99 | private function collect(): void |
| 100 | { |
| 101 | $this->collecting = true; |
| 102 | |
| 103 | try { |
| 104 | ($this->createClosure)($this); |
| 105 | } finally { |
| 106 | $this->collecting = false; |
| 107 | } |
| 108 | |
| 109 | foreach ($this->entries as $entry) { |
| 110 | if ($entry instanceof Route) { |
| 111 | $this->forwardRoute($entry); |
| 112 | } else { |
| 113 | $entry->collect(); |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | private function assertCollecting(): void |
| 119 | { |
| 120 | if (!$this->collecting) { |
| 121 | throw new RuntimeException('Cannot modify group outside the group callback.'); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | private function forwardRoute(Route $route): void |
| 126 | { |
| 127 | $this->applyTo($route); |
| 128 | |
| 129 | if ($this->routeAdder instanceof self) { |
| 130 | $this->routeAdder->forwardRoute($route); |
| 131 | } else { |
| 132 | $this->routeAdder->addRoute($route); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | private function applyTo(Route $route): void |
| 137 | { |
| 138 | $route->prefix($this->patternPrefix, $this->namePrefix); |
| 139 | |
| 140 | if ($this->controller !== null) { |
| 141 | $route->controller($this->controller); |
| 142 | } |
| 143 | |
| 144 | $route->replaceMiddleware(array_merge($this->middleware, $route->getMiddleware())); |
| 145 | $route->setBeforeHandlers($this->mergeBeforeHandlers($route->beforeHandlers())); |
| 146 | $route->setAfterHandlers($this->mergeAfterHandlers($route->afterHandlers())); |
| 147 | } |
| 148 | } |