Passed
Pull Request — master (#143)
by Sergei
02:10
created

Group::routes()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8

Importance

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