Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
28 / 28
78.95% covered (warning)
78.95%
15 / 19
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
Group
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
28 / 28
78.95% covered (warning)
78.95%
15 / 19
100.00% covered (success)
100.00%
12 / 12
19.70
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 controller
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 middleware
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 before
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 after
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addRoute
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 group
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 collect
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
10 / 10
20.00% covered (danger)
20.00%
1 / 5
100.00% covered (success)
100.00%
1 / 1
7.61
 assertCollecting
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 forwardRoute
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 applyTo
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Router;
6
7use Celema\Router\Exception\RuntimeException;
8use Closure;
9use Override;
10use Psr\Http\Server\MiddlewareInterface as Middleware;
11
12/** @psalm-api */
13final 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}