Passed
Pull Request — master (#222)
by Sergei
02:43
created

Group::withCors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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