Passed
Pull Request — master (#168)
by Rustam
02:26
created

Group::hosts()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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