Passed
Pull Request — master (#173)
by Rustam
02:30
created

Group::middleware()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
10
11
use function in_array;
12
13
final class Group
14
{
15
    /**
16
     * @var Group[]|Route[]
17
     */
18
    private array $items = [];
19
20
    /**
21
     * @var array[]|callable[]|string[]
22
     */
23
    private array $middlewareDefinitions = [];
24
25
    /**
26
     * @var string[]
27
     */
28
    private array $hosts = [];
29
    private ?string $namePrefix = null;
30
    private bool $routesAdded = false;
31
    private bool $middlewareAdded = false;
32
    private array $disabledMiddlewareDefinitions = [];
33
    /**
34
     * @var array|callable|string|null Middleware definition for CORS requests.
35
     */
36
    private $corsMiddleware = null;
37
38 30
    private function __construct(private ?string $prefix = null, private ?MiddlewareDispatcher $dispatcher = null)
39
    {
40
    }
41
42
    /**
43
     * Create a new group instance.
44
     *
45
     * @param string|null $prefix URL prefix to prepend to all routes of the group.
46
     * @param MiddlewareDispatcher|null $dispatcher Middleware dispatcher to use for the group.
47
     */
48 30
    public static function create(
49
        ?string $prefix = null,
50
        MiddlewareDispatcher $dispatcher = null
51
    ): self {
52 30
        return new self($prefix, $dispatcher);
53
    }
54
55 24
    public function routes(self|Route ...$routes): self
56
    {
57 24
        if ($this->middlewareAdded) {
58 1
            throw new RuntimeException('routes() can not be used after prependMiddleware().');
59
        }
60 23
        $new = clone $this;
61 23
        foreach ($routes as $route) {
62 23
            if ($new->dispatcher !== null && !$route->getData('hasDispatcher')) {
63 5
                $route = $route->withDispatcher($new->dispatcher);
64
            }
65 23
            $new->items[] = $route;
66
        }
67
68 23
        $new->routesAdded = true;
69
70 23
        return $new;
71
    }
72
73 2
    public function withDispatcher(MiddlewareDispatcher $dispatcher): self
74
    {
75 2
        $group = clone $this;
76 2
        $group->dispatcher = $dispatcher;
77 2
        foreach ($group->items as $index => $item) {
78 2
            if (!$item->getData('hasDispatcher')) {
79 2
                $item = $item->withDispatcher($dispatcher);
80 2
                $group->items[$index] = $item;
81
            }
82
        }
83
84 2
        return $group;
85
    }
86
87
    /**
88
     * Adds a middleware definition that handles CORS requests.
89
     * If set, routes for {@see Method::OPTIONS} request will be added automatically.
90
     *
91
     * @param array|callable|string|null $middlewareDefinition Middleware definition for CORS requests.
92
     */
93 5
    public function withCors($middlewareDefinition): self
94
    {
95 5
        $group = clone $this;
96 5
        $group->corsMiddleware = $middlewareDefinition;
97
98 5
        return $group;
99
    }
100
101
    /**
102
     * Appends a handler middleware definition that should be invoked for a matched route.
103
     * First added handler will be executed first.
104
     */
105 9
    public function middleware(array|callable|string ...$middlewareDefinition): self
106
    {
107 9
        if ($this->routesAdded) {
108 1
            throw new RuntimeException('middleware() can not be used after routes().');
109
        }
110 8
        $new = clone $this;
111 8
        array_push(
112 8
            $new->middlewareDefinitions,
113 8
            ...array_values($middlewareDefinition)
114
        );
115 8
        return $new;
116
    }
117
118
    /**
119
     * Prepends a handler middleware definition that should be invoked for a matched route.
120
     * First added handler will be executed last.
121
     */
122 21
    public function prependMiddleware(array|callable|string ...$middlewareDefinition): self
123
    {
124 21
        $new = clone $this;
125 21
        array_unshift(
126 21
            $new->middlewareDefinitions,
127 21
            ...array_values($middlewareDefinition)
128
        );
129 21
        $new->middlewareAdded = true;
130 21
        return $new;
131
    }
132
133 3
    public function namePrefix(string $namePrefix): self
134
    {
135 3
        $new = clone $this;
136 3
        $new->namePrefix = $namePrefix;
137 3
        return $new;
138
    }
139
140 2
    public function host(string $host): self
141
    {
142 2
        return $this->hosts($host);
143
    }
144
145 3
    public function hosts(string ...$hosts): self
146
    {
147 3
        $new = clone $this;
148
149 3
        foreach ($hosts as $host) {
150 3
            $host = rtrim($host, '/');
151
152 3
            if ($host !== '' && !in_array($host, $new->hosts, true)) {
153 3
                $new->hosts[] = $host;
154
            }
155
        }
156
157 3
        return $new;
158
    }
159
160
    /**
161
     * Excludes middleware from being invoked when action is handled.
162
     * It is useful to avoid invoking one of the parent group middleware for
163
     * a certain route.
164
     */
165 1
    public function disableMiddleware(mixed ...$middlewareDefinition): self
166
    {
167 1
        $new = clone $this;
168 1
        array_push(
169 1
            $new->disabledMiddlewareDefinitions,
170 1
            ...array_values($middlewareDefinition),
171
        );
172 1
        return $new;
173
    }
174
175
    /**
176
     * @psalm-template T as string
177
     * @psalm-param T $key
178
     * @psalm-return (
179
     *   T is ('prefix'|'namePrefix'|'host') ? string|null :
180
     *   (T is 'items' ? Group[]|Route[] :
181
     *     (T is 'hosts' ? array<array-key, string> :
182
     *       (T is ('hasCorsMiddleware'|'hasDispatcher') ? bool :
183
     *         (T is 'middlewareDefinitions' ? list<array|callable|string> :
184
     *           (T is 'corsMiddleware' ? array|callable|string|null : mixed)
185
     *         )
186
     *       )
187
     *     )
188
     *   )
189
     * )
190
     */
191 27
    public function getData(string $key): mixed
192
    {
193 27
        return match ($key) {
194 20
            'prefix' => $this->prefix,
195 20
            'namePrefix' => $this->namePrefix,
196 1
            'host' => $this->hosts[0] ?? null,
197 20
            'hosts' => $this->hosts,
198 5
            'corsMiddleware' => $this->corsMiddleware,
199 21
            'items' => $this->items,
200 19
            'hasCorsMiddleware' => $this->corsMiddleware !== null,
201 2
            'hasDispatcher' => $this->dispatcher !== null,
202 18
            'middlewareDefinitions' => $this->getMiddlewareDefinitions(),
203 27
            default => throw new InvalidArgumentException('Unknown data key: ' . $key),
204
        };
205
    }
206
207 18
    private function getMiddlewareDefinitions(): array
208
    {
209
        /** @var mixed $definition */
210 18
        foreach ($this->middlewareDefinitions as $index => $definition) {
211 10
            if (in_array($definition, $this->disabledMiddlewareDefinitions, true)) {
212 1
                unset($this->middlewareDefinitions[$index]);
213
            }
214
        }
215
216 18
        return array_values($this->middlewareDefinitions);
217
    }
218
}
219