Passed
Pull Request — master (#134)
by Rustam
02:38
created

RouteCollection::ensureItemsInjected()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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