‪TYPO3CMS  11.5
BestUrlMatcher.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
21 use Symfony\Component\Routing\Matcher\UrlMatcher;
22 use Symfony\Component\Routing\RouteCollection as SymfonyRouteCollection;
24 
28 class ‪BestUrlMatcher extends UrlMatcher
29 {
30  protected function ‪matchCollection(string $pathinfo, SymfonyRouteCollection $routes): array
31  {
32  $matchedRoutes = $this->‪preMatchCollection($pathinfo, $routes);
33  $matches = count($matchedRoutes);
34  if ($matches === 0) {
35  return [];
36  }
37  if ($matches === 1) {
38  return $matchedRoutes[0]->getRouteResult();
39  }
40  usort($matchedRoutes, [$this, 'sortMatchedRoutes']);
41  return array_shift($matchedRoutes)->getRouteResult();
42  }
43 
52  protected function ‪preMatchCollection(string $pathinfo, SymfonyRouteCollection $routes): array
53  {
54  $matchedRoutes = [];
55 
56  // HEAD and GET are equivalent as per RFC
57  $method = $this->context->getMethod();
58  if ($method === 'HEAD') {
59  $method = 'GET';
60  }
61  $supportsTrailingSlash = $method === 'GET' && $this instanceof RedirectableUrlMatcherInterface;
62  $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
63 
64  foreach ($routes as $name => $route) {
65  $compiledRoute = $route->compile();
66  $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');
67  $requiredMethods = $route->getMethods();
68 
69  // check the static prefix of the URL first. Only use the more expensive preg_match when it matches
70  if ($staticPrefix !== '' && !str_starts_with($trimmedPathinfo, $staticPrefix)) {
71  continue;
72  }
73  $regex = $compiledRoute->getRegex();
74 
75  $pos = strrpos($regex, '$');
76  $hasTrailingSlash = $regex[$pos - 1] === '/';
77  $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
78 
79  if (!preg_match($regex, $pathinfo, $matches)) {
80  continue;
81  }
82 
83  $hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
84 
85  if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
86  if ($hasTrailingSlash) {
87  $matches = $m;
88  } else {
89  $hasTrailingVar = false;
90  }
91  }
92 
93  $hostMatches = [];
94  if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
95  continue;
96  }
97 
98  $status = $this->handleRouteRequirements($pathinfo, $name, $route);
99 
100  if ($status[0] === self::REQUIREMENT_MISMATCH) {
101  continue;
102  }
103 
104  if ($pathinfo !== '/' && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
105  if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
106  return $this->allow = $this->allowSchemes = [];
107  }
108  continue;
109  }
110 
111  if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) {
112  $this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
113  continue;
114  }
115 
116  if ($requiredMethods && !\in_array($method, $requiredMethods)) {
117  $this->allow = array_merge($this->allow, $requiredMethods);
118  continue;
119  }
120 
121  $matchedRoute = GeneralUtility::makeInstance(
122  MatchedRoute::class,
123  $route,
124  $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []))
125  );
126  $matchedRoutes[] = $matchedRoute->withPathMatches($matches)->withHostMatches($hostMatches);
127  }
128 
129  return $matchedRoutes;
130  }
131 
135  protected function ‪sortMatchedRoutes(‪MatchedRoute $a, ‪MatchedRoute $b): int
136  {
137  if ($a->‪getFallbackScore() !== $b->‪getFallbackScore()) {
138  // sort fallbacks to the end
139  return $a->‪getFallbackScore() <=> $b->‪getFallbackScore();
140  }
141  if ($b->‪getHostMatchScore() !== $a->‪getHostMatchScore()) {
142  // sort more specific host matches to the beginning
143  return $b->‪getHostMatchScore() <=> $a->‪getHostMatchScore();
144  }
145  // index `1` refers to the array index containing the corresponding `tail` match
146  // @todo not sure, whether `tail` can be defined generic, it's hard coded in `SiteMatcher`
147  if ($b->‪getPathMatchScore(1) !== $a->‪getPathMatchScore(1)) {
148  return $b->‪getPathMatchScore(1) <=> $a->‪getPathMatchScore(1);
149  }
150  // fallback for behavior prior to issue #93240, using reverse sorted site identifier
151  // (side note: site identifier did not contain any URL relevant information)
152  return $b->‪getSiteIdentifier() <=> $a->‪getSiteIdentifier();
153  }
154 }
‪TYPO3\CMS\Core\Routing\MatchedRoute\getSiteIdentifier
‪getSiteIdentifier()
Definition: MatchedRoute.php:89
‪TYPO3\CMS\Core\Routing
‪TYPO3\CMS\Core\Routing\BestUrlMatcher\matchCollection
‪matchCollection(string $pathinfo, SymfonyRouteCollection $routes)
Definition: BestUrlMatcher.php:30
‪TYPO3\CMS\Core\Routing\BestUrlMatcher
Definition: BestUrlMatcher.php:29
‪TYPO3\CMS\Core\Routing\MatchedRoute
Definition: MatchedRoute.php:27
‪TYPO3\CMS\Core\Routing\MatchedRoute\getFallbackScore
‪getFallbackScore()
Definition: MatchedRoute.php:66
‪TYPO3\CMS\Core\Routing\BestUrlMatcher\sortMatchedRoutes
‪sortMatchedRoutes(MatchedRoute $a, MatchedRoute $b)
Definition: BestUrlMatcher.php:135
‪TYPO3\CMS\Core\Routing\BestUrlMatcher\preMatchCollection
‪list< MatchedRoute > preMatchCollection(string $pathinfo, SymfonyRouteCollection $routes)
Definition: BestUrlMatcher.php:52
‪TYPO3\CMS\Core\Routing\MatchedRoute\getHostMatchScore
‪getHostMatchScore()
Definition: MatchedRoute.php:71
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Routing\MatchedRoute\getPathMatchScore
‪getPathMatchScore(int $index)
Definition: MatchedRoute.php:76