Test Failed
Pull Request — master (#173)
by Rustam
12:30
created

Group::prependMiddleware()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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