RouteCollection::injectGroup()   C
last analyzed

Complexity

Conditions 13
Paths 57

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 13

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 13
eloc 30
c 2
b 0
f 0
nc 57
nop 4
dl 0
loc 53
ccs 31
cts 31
cp 1
crap 13
rs 6.6166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\ResponseFactoryInterface;
9
use Yiisoft\Http\Method;
10
11
use function array_key_exists;
12
use function in_array;
13
use function is_array;
14
15
/**
16
 * @psalm-type Items = array<array-key,array|string>
17
 */
18
final class RouteCollection implements RouteCollectionInterface
19
{
20
    /**
21
     * @psalm-var Items
22
     */
23
    private array $items = [];
24
25
    /**
26
     * All attached routes as Route instances.
27
     *
28
     * @var Route[]
29
     */
30
    private array $routes = [];
31
32 29
    public function __construct(private RouteCollectorInterface $collector)
33
    {
34 29
    }
35
36 10
    public function getRoutes(): array
37
    {
38 10
        $this->ensureItemsInjected();
39 8
        return $this->routes;
40
    }
41
42 22
    public function getRoute(string $name): Route
43
    {
44 22
        $this->ensureItemsInjected();
45 22
        if (!array_key_exists($name, $this->routes)) {
46 1
            throw new RouteNotFoundException($name);
47
        }
48
49 21
        return $this->routes[$name];
50
    }
51
52 2
    public function getRouteTree(bool $routeAsString = true): array
53
    {
54 2
        $this->ensureItemsInjected();
55 2
        return $this->buildTree($this->items, $routeAsString);
56
    }
57
58 29
    private function ensureItemsInjected(): void
59
    {
60 29
        if ($this->items === []) {
61 29
            $this->injectItems($this->collector->getItems());
62
        }
63
    }
64
65
    /**
66
     * Build routes array.
67
     *
68
     * @param Group[]|Route[] $items
69
     */
70 29
    private function injectItems(array $items): void
71
    {
72 29
        foreach ($items as $item) {
73 29
            if (!$this->isStaticRoute($item)) {
74 28
                $item = $item->prependMiddleware(...$this->collector->getMiddlewareDefinitions());
75
            }
76 29
            $this->injectItem($item);
77
        }
78
    }
79
80
    /**
81
     * Add an item into routes array.
82
     */
83 29
    private function injectItem(Group|Route $route): void
84
    {
85 29
        if ($route instanceof Group) {
86 22
            $this->injectGroup($route, $this->items);
87 21
            return;
88
        }
89
90 10
        $routeName = $route->getData('name');
91 10
        $this->items[] = $routeName;
92 10
        if (isset($this->routes[$routeName]) && !$route->getData('override')) {
93 1
            throw new InvalidArgumentException("A route with name '$routeName' already exists.");
94
        }
95 9
        $this->routes[$routeName] = $route;
96
    }
97
98
    /**
99
     * Inject a Group instance into route and item arrays.
100
     *
101
     * @psalm-param Items $tree
102
     */
103 22
    private function injectGroup(Group $group, array &$tree, string $prefix = '', string $namePrefix = ''): void
104
    {
105 22
        $prefix .= (string) $group->getData('prefix');
106 22
        $namePrefix .= (string) $group->getData('namePrefix');
107 22
        $items = $group->getData('routes');
108 22
        $pattern = null;
109 22
        $hosts = [];
110 22
        foreach ($items as $item) {
111 22
            if (!$this->isStaticRoute($item)) {
112 17
                $item = $item->prependMiddleware(...$group->getData('enabledMiddlewares'));
113
            }
114
115 22
            if (!empty($group->getData('hosts')) && empty($item->getData('hosts'))) {
116 1
                $item = $item->hosts(...$group->getData('hosts'));
117
            }
118
119 22
            if ($item instanceof Group) {
120 7
                if ($group->getData('hasCorsMiddleware')) {
121 2
                    $item = $item->withCors($group->getData('corsMiddleware'));
122
                }
123 7
                if (empty($item->getData('prefix'))) {
124 3
                    $this->injectGroup($item, $tree, $prefix, $namePrefix);
125 3
                    continue;
126
                }
127
                /** @psalm-suppress PossiblyNullArrayOffset Checked group prefix on not empty above. */
128 5
                if (!isset($tree[$item->getData('prefix')])) {
129 5
                    $tree[$item->getData('prefix')] = [];
130
                }
131
                /**
132
                 * @psalm-suppress MixedArgumentTypeCoercion
133
                 * @psalm-suppress MixedArgument,PossiblyNullArrayOffset
134
                 * Checked group prefix on not empty above.
135
                 */
136 5
                $this->injectGroup($item, $tree[$item->getData('prefix')], $prefix, $namePrefix);
137 5
                continue;
138
            }
139
140 22
            $modifiedItem = $item->pattern($prefix . $item->getData('pattern'));
141
142 22
            if (!str_contains($modifiedItem->getData('name'), implode(', ', $modifiedItem->getData('methods')))) {
143 14
                $modifiedItem = $modifiedItem->name($namePrefix . $modifiedItem->getData('name'));
144
            }
145
146 22
            if ($group->getData('hasCorsMiddleware')) {
147 7
                $this->processCors($group, $hosts, $pattern, $modifiedItem, $tree);
148
            }
149
150 22
            $routeName = $modifiedItem->getData('name');
151 22
            $tree[] = $routeName;
152 22
            if (isset($this->routes[$routeName]) && !$modifiedItem->getData('override')) {
153 1
                throw new InvalidArgumentException("A route with name '$routeName' already exists.");
154
            }
155 22
            $this->routes[$routeName] = $modifiedItem;
156
        }
157
    }
158
159
    /**
160
     * @psalm-param Items $tree
161
     */
162 7
    private function processCors(
163
        Group $group,
164
        array &$hosts,
165
        ?string &$pattern,
166
        Route &$modifiedItem,
167
        array &$tree
168
    ): void {
169
        /** @var array|callable|string $middleware */
170 7
        $middleware = $group->getData('corsMiddleware');
171 7
        $isNotDuplicate = !in_array(Method::OPTIONS, $modifiedItem->getData('methods'), true)
172 7
            && ($pattern !== $modifiedItem->getData('pattern') || $hosts !== $modifiedItem->getData('hosts'));
173
174 7
        $pattern = $modifiedItem->getData('pattern');
175 7
        $hosts = $modifiedItem->getData('hosts');
176 7
        $optionsRoute = Route::options($pattern);
177 7
        if (!empty($hosts)) {
178 2
            $optionsRoute = $optionsRoute->hosts(...$hosts);
179
        }
180 7
        if ($isNotDuplicate) {
181 7
            $optionsRoute = $optionsRoute->middleware($middleware);
182
183 7
            $routeName = $optionsRoute->getData('name');
184 7
            $tree[] = $routeName;
185 7
            $this->routes[$routeName] = $optionsRoute->action(
186 7
                static fn (ResponseFactoryInterface $responseFactory) => $responseFactory->createResponse(204)
187 7
            );
188
        }
189 7
        $modifiedItem = $modifiedItem->prependMiddleware($middleware);
190
    }
191
192
    /**
193
     * Builds route tree from items.
194
     *
195
     * @psalm-param Items $items
196
     */
197 2
    private function buildTree(array $items, bool $routeAsString): array
198
    {
199 2
        $tree = [];
200 2
        foreach ($items as $key => $item) {
201 2
            if (is_array($item)) {
202
                /** @psalm-var Items $item */
203 1
                $tree[$key] = $this->buildTree($item, $routeAsString);
204
            } else {
205 2
                $tree[] = $routeAsString ? (string) $this->getRoute($item) : $this->getRoute($item);
206
            }
207
        }
208 2
        return $tree;
209
    }
210
211 29
    private function isStaticRoute(Group|Route $item): bool
212
    {
213 29
        return $item instanceof Route && !$item->getData('hasMiddlewares');
214
    }
215
}
216