Passed
Pull Request — master (#196)
by Rustam
12:45
created

Group::withDispatcher()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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