Passed
Pull Request — master (#196)
by Rustam
03:32
created

Group::disableMiddleware()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 6
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 9
ccs 8
cts 8
cp 1
crap 1
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 32
    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 32
        $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 32
    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 32
        return new self(
58 32
            prefix: $prefix,
59 32
            middlewares: $middlewares,
60 32
            hosts: $hosts,
61 32
            namePrefix: $namePrefix,
62 32
            disabledMiddlewares: $disabledMiddlewares,
63 32
            corsMiddleware: $corsMiddleware
64 32
        );
65
    }
66
67 24
    public function routes(self|Route ...$routes): self
68
    {
69 24
        if ($this->middlewareAdded) {
70 1
            throw new RuntimeException('routes() can not be used after prependMiddleware().');
71
        }
72 23
        $new = clone $this;
73 23
        $new->routes = $routes;
74 23
        $new->routesAdded = true;
75
76 23
        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 6
    public function withCors(array|callable|string|null $middlewareDefinition): self
86
    {
87 6
        $group = clone $this;
88 6
        $group->corsMiddleware = $middlewareDefinition;
89
90 6
        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 11
    public function middleware(array|callable|string ...$middlewareDefinition): self
98
    {
99 11
        if ($this->routesAdded) {
100 1
            throw new RuntimeException('middleware() can not be used after routes().');
101
        }
102 10
        $new = clone $this;
103 10
        array_push(
104 10
            $new->middlewares,
105 10
            ...array_values($middlewareDefinition)
106 10
        );
107 10
        $new->builtMiddlewares = [];
108 10
        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 23
    public function prependMiddleware(array|callable|string ...$middlewareDefinition): self
116
    {
117 23
        $new = clone $this;
118 23
        array_unshift(
119 23
            $new->middlewares,
120 23
            ...array_values($middlewareDefinition)
121 23
        );
122 23
        $new->middlewareAdded = true;
123 23
        $new->builtMiddlewares = [];
124 23
        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 28
    public function getData(string $key): mixed
189
    {
190 28
        return match ($key) {
191 28
            'prefix' => $this->prefix,
192 28
            'namePrefix' => $this->namePrefix,
193 28
            'host' => $this->hosts[0] ?? null,
194 28
            'hosts' => $this->hosts,
195 28
            'corsMiddleware' => $this->corsMiddleware,
196 28
            'routes' => $this->routes,
197 28
            'hasCorsMiddleware' => $this->corsMiddleware !== null,
198 28
            'middlewares' => $this->getBuiltMiddlewares(),
199 28
            default => throw new InvalidArgumentException('Unknown data key: ' . $key),
200 28
        };
201
    }
202
203 19
    private function getBuiltMiddlewares(): array
204
    {
205 19
        if ($this->builtMiddlewares !== []) {
206 4
            return $this->builtMiddlewares;
207
        }
208
209 19
        $builtMiddlewares = $this->middlewares;
210
211
        /** @var mixed $definition */
212 19
        foreach ($builtMiddlewares as $index => $definition) {
213 11
            if (in_array($definition, $this->disabledMiddlewares, true)) {
214 2
                unset($builtMiddlewares[$index]);
215
            }
216
        }
217
218 19
        return $this->builtMiddlewares = array_values($builtMiddlewares);
219
    }
220
}
221