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}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Group->__construct
35        private readonly RouteAdder $routeAdder,
36        private readonly string $patternPrefix,
37        private readonly Closure $createClosure,
38        private readonly string $namePrefix,
39    ) {}
Group->addRoute
81    public function addRoute(Route $route): Route
82    {
83        $this->assertCollecting();
84        $this->entries[] = $route;
85
86        return $route;
87    }
Group->after
73    public function after(After $afterHandler): static
74    {
75        $this->assertCollecting();
76
77        return $this->addAfterHandler($afterHandler);
78    }
Group->applyTo
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()));
144        $route->replaceMiddleware(array_merge($this->middleware, $route->getMiddleware()));
145        $route->setBeforeHandlers($this->mergeBeforeHandlers($route->beforeHandlers()));
146        $route->setAfterHandlers($this->mergeAfterHandlers($route->afterHandlers()));
147    }
Group->assertCollecting
120        if (!$this->collecting) {
121            throw new RuntimeException('Cannot modify group outside the group callback.');
123    }
Group->before
66    public function before(Before $beforeHandler): static
67    {
68        $this->assertCollecting();
69
70        return $this->addBeforeHandler($beforeHandler);
71    }
Group->collect
101        $this->collecting = true;
102
103        try {
104            ($this->createClosure)($this);
105        } finally {
105        } finally {
106            $this->collecting = false;
109        foreach ($this->entries as $entry) {
109        foreach ($this->entries as $entry) {
110            if ($entry instanceof Route) {
110            if ($entry instanceof Route) {
111                $this->forwardRoute($entry);
109        foreach ($this->entries as $entry) {
110            if ($entry instanceof Route) {
111                $this->forwardRoute($entry);
112            } else {
113                $entry->collect();
109        foreach ($this->entries as $entry) {
109        foreach ($this->entries as $entry) {
110            if ($entry instanceof Route) {
111                $this->forwardRoute($entry);
112            } else {
113                $entry->collect();
114            }
115        }
116    }
Group->controller
51    public function controller(string $controller): static
52    {
53        $this->assertCollecting();
54        $this->controller = $controller;
55
56        return $this;
57    }
Group->forwardRoute
125    private function forwardRoute(Route $route): void
126    {
127        $this->applyTo($route);
128
129        if ($this->routeAdder instanceof self) {
129        if ($this->routeAdder instanceof self) {
130            $this->routeAdder->forwardRoute($route);
132            $this->routeAdder->addRoute($route);
133        }
134    }
134    }
Group->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    }
Group->middleware
59    public function middleware(Middleware ...$middleware): static
60    {
61        $this->assertCollecting();
62
63        return $this->addMiddlewareHandlers(...$middleware);
64    }
Group->register
43        RouteAdder $adder,
44        string $patternPrefix,
45        Closure $createClosure,
46        string $namePrefix = '',
47    ): void {
48        new self($adder, $patternPrefix, $createClosure, $namePrefix)->collect();
49    }