-
Notifications
You must be signed in to change notification settings - Fork 638
/
Router.php
177 lines (161 loc) · 6.81 KB
/
Router.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Backend\Routing;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Route as SymfonyRoute;
use TYPO3\CMS\Backend\Routing\Exception\MethodNotAllowedException;
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
use TYPO3\CMS\Core\Routing\RequestContextFactory;
use TYPO3\CMS\Core\Routing\RouteCollection;
use TYPO3\CMS\Core\SingletonInterface;
/**
* Implementation of a class for adding routes, collecting throughout the Bootstrap
* to register all sorts of Backend Routes, and to fetch the main Collection in order
* to resolve a route (see ->match() and ->matchRequest()).
*
* Ideally, the Router is solely instantiated and accessed via the Bootstrap, the RequestHandler and the UriBuilder.
*
* See \TYPO3\CMS\Backend\Http\RequestHandler for more details on route matching() and Bootstrap->initializeBackendRouting().
*
* The architecture is inspired by the Symfony Routing Component.
*/
class Router implements SingletonInterface
{
/**
* All routes used in the TYPO3 Backend
*/
protected RouteCollection $routeCollection;
public function __construct(
protected readonly RequestContextFactory $requestContextFactory,
protected readonly BackendEntryPointResolver $backendEntryPointResolver,
) {
$this->routeCollection = new RouteCollection();
}
/**
* Adds a new route with a given identifier
*/
public function addRoute(string $routeIdentifier, Route $route, array $aliases = []): void
{
$symfonyRoute = new SymfonyRoute($route->getPath(), [], [], $route->getOptions());
$symfonyRoute->setMethods($route->getMethods());
$this->routeCollection->add($routeIdentifier, $symfonyRoute);
foreach ($aliases as $aliasName) {
$this->routeCollection->addAlias($aliasName, $routeIdentifier);
}
}
public function addRouteCollection(RouteCollection $routeCollection): void
{
$this->routeCollection->addCollection($routeCollection);
}
/**
* Fetch all registered routes, only use in UriBuilder. Does not care about aliases,
* so be careful with using this method.
*
* @return Route[]
*/
public function getRoutes(): iterable
{
return $this->routeCollection->getIterator();
}
public function hasRoute(string $routeName): bool
{
return $this->routeCollection->get($routeName) !== null;
}
/**
* Returns a route by its identifier, or null if nothing found.
*
* @return SymfonyRoute|Route|null
*/
public function getRoute(string $routeName)
{
return $this->routeCollection->get($routeName);
}
/**
* @internal only use in Core, this should not be exposed
*/
public function getRouteCollection(): RouteCollection
{
return $this->routeCollection;
}
/**
* Tries to match a URL path with a set of routes.
*
* @param string $pathInfo The path info to be parsed
* @return Route the first Route object found
* @throws ResourceNotFoundException If the resource could not be found
*/
public function match($pathInfo): Route
{
foreach ($this->routeCollection->getIterator() as $routeIdentifier => $route) {
// This check is done in a simple way as there are no parameters yet (get parameters only)
if ($route->getPath() === $pathInfo) {
$routeResult = new Route($route->getPath(), $route->getOptions());
// Store the name of the Route in the _identifier option so the token can be checked against that
$routeResult->setOption('_identifier', $routeIdentifier);
return $routeResult;
}
}
throw new ResourceNotFoundException('The requested resource "' . $pathInfo . '" was not found.', 1425389240);
}
/**
* Matches a PSR-7 Request and returns a RouteResult with parameters and the resolved route.
*/
public function matchResult(ServerRequestInterface $request): RouteResult
{
$path = $this->backendEntryPointResolver->getBackendRoutePath($request);
if ($path === null) {
throw new ResourceNotFoundException('The requested resource "' . $request->getUri()->getPath() . '" does not contain a known backend route prefix.', 1704787661);
}
if ($path === '' || $path === '/' || $path === '/index.php') {
// Allow the login page to be displayed if routing is not used and on index.php
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'login' to something different)
$path = '/login';
}
$requestContext = $this->requestContextFactory->fromBackendRequest($request);
try {
$result = (new UrlMatcher($this->routeCollection, $requestContext))->match($path);
$matchedSymfonyRoute = $this->routeCollection->get($result['_route']);
if ($matchedSymfonyRoute === null) {
throw new ResourceNotFoundException('The requested resource "' . $path . '" was not found.', 1607596900);
}
} catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
throw new MethodNotAllowedException($e->getMessage(), 1612649842);
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
throw new ResourceNotFoundException('The requested resource "' . $path . '" was not found.', 1612649840);
}
// Apply matched method to route
$matchedOptions = $matchedSymfonyRoute->getOptions();
$methods = $matchedOptions['methods'] ?? [];
unset($matchedOptions['methods']);
$route = new Route($matchedSymfonyRoute->getPath(), $matchedOptions);
if (count($methods) > 0) {
$route->setMethods($methods);
}
$route->setOption('_identifier', $result['_route']);
unset($result['_route']);
return new RouteResult($route, $result);
}
/**
* Tries to match a URI against the registered routes.
* Use ->matchResult() instead, as this method will be deprecated in the future.
*
* @return Route the first Route object found
*/
public function matchRequest(ServerRequestInterface $request): Route
{
return $this->matchResult($request)->getRoute();
}
}