• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / routing / 15763612701

19 Jun 2025 05:45PM UTC coverage: 95.508% (+0.01%) from 95.498%
15763612701

push

github

dg
typo

404 of 423 relevant lines covered (95.51%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.16
/src/Routing/RouteList.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Routing;
11

12
use Nette;
13
use function array_column, array_filter, array_keys, array_reverse, array_splice, count, explode, ip2long, is_scalar, rtrim, strlen, strncmp, strtr;
14

15

16
/**
17
 * The router broker.
18
 */
19
class RouteList implements Router
20
{
21
        protected ?self $parent;
22

23
        /** @var array<array{Router, int}> */
24
        private array $list = [];
25

26
        /** @var Router[][]|null */
27
        private ?array $ranks = null;
28
        private ?string $cacheKey;
29
        private ?string $domain = null;
30
        private ?string $path = null;
31
        private ?\SplObjectStorage $refUrlCache;
32

33

34
        public function __construct()
35
        {
36
        }
1✔
37

38

39
        /**
40
         * Maps HTTP request to an array.
41
         */
42
        final public function match(Nette\Http\IRequest $httpRequest): ?array
1✔
43
        {
44
                if ($httpRequest = $this->prepareRequest($httpRequest)) {
1✔
45
                        foreach ($this->list as [$router]) {
1✔
46
                                if (
47
                                        ($params = $router->match($httpRequest)) !== null
1✔
48
                                        && ($params = $this->completeParameters($params)) !== null
1✔
49
                                ) {
50
                                        return $params;
1✔
51
                                }
52
                        }
53
                }
54
                return null;
1✔
55
        }
56

57

58
        protected function prepareRequest(Nette\Http\IRequest $httpRequest): ?Nette\Http\IRequest
1✔
59
        {
60
                if ($this->domain) {
1✔
61
                        $host = $httpRequest->getUrl()->getHost();
1✔
62
                        if ($host !== $this->expandDomain($host)) {
1✔
63
                                return null;
1✔
64
                        }
65
                }
66

67
                if ($this->path) {
1✔
68
                        $url = $httpRequest->getUrl();
1✔
69
                        $relativePath = $url->getRelativePath();
1✔
70
                        if (strncmp($relativePath, $this->path, strlen($this->path)) === 0) {
1✔
71
                                $url = $url->withPath($url->getPath(), $url->getBasePath() . $this->path);
1✔
72
                        } elseif ($relativePath . '/' === $this->path) {
1✔
73
                                $url = $url->withPath($url->getPath() . '/');
1✔
74
                        } else {
75
                                return null;
1✔
76
                        }
77

78
                        $httpRequest = $httpRequest->withUrl($url);
1✔
79
                }
80

81
                return $httpRequest;
1✔
82
        }
83

84

85
        protected function completeParameters(array $params): ?array
1✔
86
        {
87
                return $params;
1✔
88
        }
89

90

91
        /**
92
         * Constructs absolute URL from array.
93
         */
94
        public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string
1✔
95
        {
96
                if ($this->domain) {
1✔
97
                        if (!isset($this->refUrlCache[$refUrl])) {
1✔
98
                                $this->refUrlCache[$refUrl] = $refUrl->withHost(
1✔
99
                                        $this->expandDomain($refUrl->getHost()),
1✔
100
                                );
101
                        }
102

103
                        $refUrl = $this->refUrlCache[$refUrl];
1✔
104
                }
105

106
                if ($this->path) {
1✔
107
                        if (!isset($this->refUrlCache[$refUrl])) {
1✔
108
                                $this->refUrlCache[$refUrl] = $refUrl->withPath($refUrl->getBasePath() . $this->path);
1✔
109
                        }
110

111
                        $refUrl = $this->refUrlCache[$refUrl];
1✔
112
                }
113

114
                if ($this->ranks === null) {
1✔
115
                        $this->warmupCache();
1✔
116
                }
117

118
                $key = $params[$this->cacheKey] ?? null;
1✔
119
                if (!is_scalar($key) || !isset($this->ranks[$key])) {
1✔
120
                        $key = '*';
1✔
121
                }
122

123
                foreach ($this->ranks[$key] as $router) {
1✔
124
                        $url = $router->constructUrl($params, $refUrl);
1✔
125
                        if ($url !== null) {
1✔
126
                                return $url;
1✔
127
                        }
128
                }
129

130
                return null;
1✔
131
        }
132

133

134
        public function warmupCache(): void
135
        {
136
                // find best key
137
                $candidates = [];
1✔
138
                $routers = [];
1✔
139
                foreach ($this->list as [$router, $oneWay]) {
1✔
140
                        if ($oneWay) {
1✔
141
                                continue;
1✔
142
                        } elseif ($router instanceof self) {
1✔
143
                                $router->warmupCache();
1✔
144
                        }
145

146
                        $params = $router instanceof Route
1✔
147
                                ? $router->getConstantParameters()
1✔
148
                                : [];
1✔
149

150
                        foreach (array_filter($params, 'is_scalar') as $name => $value) {
1✔
151
                                $candidates[$name][$value] = true;
1✔
152
                        }
153

154
                        $routers[] = [$router, $params];
1✔
155
                }
156

157
                $this->cacheKey = $count = null;
1✔
158
                foreach ($candidates as $name => $items) {
1✔
159
                        if (count($items) > $count) {
1✔
160
                                $count = count($items);
1✔
161
                                $this->cacheKey = $name;
1✔
162
                        }
163
                }
164

165
                // classify routers
166
                $ranks = ['*' => []];
1✔
167

168
                foreach ($routers as [$router, $params]) {
1✔
169
                        $value = $params[$this->cacheKey] ?? null;
1✔
170
                        $values = $value === null
1✔
171
                                ? array_keys($ranks)
1✔
172
                                : [is_scalar($value) ? $value : '*'];
1✔
173

174
                        foreach ($values as $value) {
1✔
175
                                if (!isset($ranks[$value])) {
1✔
176
                                        $ranks[$value] = $ranks['*'];
1✔
177
                                }
178

179
                                $ranks[$value][] = $router;
1✔
180
                        }
181
                }
182

183
                $this->ranks = $ranks;
1✔
184
        }
1✔
185

186

187
        /**
188
         * Adds a router.
189
         */
190
        public function add(Router $router, bool $oneWay = false): static
1✔
191
        {
192
                $this->list[] = [$router, $oneWay];
1✔
193
                $this->ranks = null;
1✔
194
                return $this;
1✔
195
        }
196

197

198
        /**
199
         * Prepends a router.
200
         */
201
        public function prepend(Router $router, bool $oneWay = false): void
1✔
202
        {
203
                array_splice($this->list, 0, 0, [[$router, $oneWay]]);
1✔
204
                $this->ranks = null;
1✔
205
        }
1✔
206

207

208
        /** @internal */
209
        protected function modify(int $index, ?Router $router): void
210
        {
211
                if (!isset($this->list[$index])) {
×
212
                        throw new Nette\OutOfRangeException('Offset invalid or out of range');
×
213
                } elseif ($router) {
×
214
                        $this->list[$index] = [$router, 0];
×
215
                } else {
216
                        array_splice($this->list, $index, 1);
×
217
                }
218

219
                $this->ranks = null;
×
220
        }
221

222

223
        public function addRoute(string $mask, array $metadata = [], bool $oneWay = false): static
1✔
224
        {
225
                $this->add(new Route($mask, $metadata), $oneWay);
1✔
226
                return $this;
1✔
227
        }
228

229

230
        /**
231
         * Returns an iterator over all routers.
232
         */
233
        public function withDomain(string $domain): static
1✔
234
        {
235
                $router = new static;
1✔
236
                $router->domain = $domain;
1✔
237
                $router->refUrlCache = new \SplObjectStorage;
1✔
238
                $router->parent = $this;
1✔
239
                $this->add($router);
1✔
240
                return $router;
1✔
241
        }
242

243

244
        public function withPath(string $path): static
1✔
245
        {
246
                $router = new static;
1✔
247
                $router->path = rtrim($path, '/') . '/';
1✔
248
                $router->refUrlCache = new \SplObjectStorage;
1✔
249
                $router->parent = $this;
1✔
250
                $this->add($router);
1✔
251
                return $router;
1✔
252
        }
253

254

255
        public function end(): ?self
256
        {
257
                return $this->parent;
1✔
258
        }
259

260

261
        /**
262
         * @return Router[]
263
         */
264
        public function getRouters(): array
265
        {
266
                return array_column($this->list, 0);
1✔
267
        }
268

269

270
        /**
271
         * @return bool[][]
272
         */
273
        public function getFlags(): array
274
        {
275
                return array_map(fn($info) => ['oneWay' => (bool) $info[1]], $this->list);
1✔
276
        }
277

278

279
        public function getDomain(): ?string
280
        {
281
                return $this->domain;
×
282
        }
283

284

285
        public function getPath(): ?string
286
        {
287
                return $this->path;
×
288
        }
289

290

291
        private function expandDomain(string $host): string
1✔
292
        {
293
                $parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
1✔
294
                return strtr($this->domain, [
1✔
295
                        '%tld%' => $parts[0],
1✔
296
                        '%domain%' => isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0],
1✔
297
                        '%sld%' => $parts[1] ?? '',
1✔
298
                ]);
299
        }
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc