PageRenderTime 26ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/web/core/lib/Drupal/Core/Routing/Router.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 339 lines | 133 code | 36 blank | 170 comment | 13 complexity | 737c66af9e07c9bae3bfe53c1f716b76 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Routing;
  3. use Drupal\Core\Path\CurrentPathStack;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\Routing\Exception\MethodNotAllowedException;
  6. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  7. use Symfony\Component\Routing\Generator\UrlGeneratorInterface as BaseUrlGeneratorInterface;
  8. use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
  9. use Symfony\Component\Routing\RouteCollection;
  10. use Symfony\Component\Routing\RouterInterface;
  11. /**
  12. * Router implementation in Drupal.
  13. *
  14. * A router determines, for an incoming request, the active controller, which is
  15. * a callable that creates a response.
  16. *
  17. * It consists of several steps, of which each are explained in more details
  18. * below:
  19. * 1. Get a collection of routes which potentially match the current request.
  20. * This is done by the route provider. See ::getInitialRouteCollection().
  21. * 2. Filter the collection down further more. For example this filters out
  22. * routes applying to other formats: See ::applyRouteFilters()
  23. * 3. Find the best matching route out of the remaining ones, by applying a
  24. * regex. See ::matchCollection().
  25. * 4. Enhance the list of route attributes, for example loading entity objects.
  26. * See ::applyRouteEnhancers().
  27. */
  28. class Router extends UrlMatcher implements RequestMatcherInterface, RouterInterface {
  29. /**
  30. * The route provider responsible for the first-pass match.
  31. *
  32. * @var \Drupal\Core\Routing\RouteProviderInterface
  33. */
  34. protected $routeProvider;
  35. /**
  36. * The list of available enhancers.
  37. *
  38. * @var \Drupal\Core\Routing\EnhancerInterface[]
  39. */
  40. protected $enhancers = [];
  41. /**
  42. * The list of available route filters.
  43. *
  44. * @var \Drupal\Core\Routing\FilterInterface[]
  45. */
  46. protected $filters = [];
  47. /**
  48. * The URL generator.
  49. *
  50. * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
  51. */
  52. protected $urlGenerator;
  53. /**
  54. * Constructs a new Router.
  55. *
  56. * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
  57. * The route provider.
  58. * @param \Drupal\Core\Path\CurrentPathStack $current_path
  59. * The current path stack.
  60. * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $url_generator
  61. * The URL generator.
  62. */
  63. public function __construct(RouteProviderInterface $route_provider, CurrentPathStack $current_path, BaseUrlGeneratorInterface $url_generator) {
  64. parent::__construct($current_path);
  65. $this->routeProvider = $route_provider;
  66. $this->urlGenerator = $url_generator;
  67. }
  68. /**
  69. * Adds a route filter.
  70. *
  71. * @param \Drupal\Core\Routing\FilterInterface $route_filter
  72. * The route filter.
  73. */
  74. public function addRouteFilter(FilterInterface $route_filter) {
  75. $this->filters[] = $route_filter;
  76. }
  77. /**
  78. * Adds a route enhancer.
  79. *
  80. * @param \Drupal\Core\Routing\EnhancerInterface $route_enhancer
  81. * The route enhancer.
  82. */
  83. public function addRouteEnhancer(EnhancerInterface $route_enhancer) {
  84. $this->enhancers[] = $route_enhancer;
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. public function match($pathinfo): array {
  90. $request = Request::create($pathinfo);
  91. return $this->matchRequest($request);
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function matchRequest(Request $request): array {
  97. try {
  98. $collection = $this->getInitialRouteCollection($request);
  99. }
  100. // PHP 7.4 introduces changes to its serialization format, which mean that
  101. // older versions of PHP are unable to unserialize data that is serialized
  102. // in PHP 7.4. If the site's version of PHP has been downgraded, then
  103. // attempting to unserialize routes from the database will fail, and so the
  104. // router needs to be rebuilt on the current PHP version.
  105. // See https://www.php.net/manual/en/migration74.incompatible.php.
  106. catch (\TypeError $e) {
  107. \Drupal::service('router.builder')->rebuild();
  108. $collection = $this->getInitialRouteCollection($request);
  109. }
  110. if ($collection->count() === 0) {
  111. throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $this->currentPath->getPath()));
  112. }
  113. $collection = $this->applyRouteFilters($collection, $request);
  114. $collection = $this->applyFitOrder($collection);
  115. if ($ret = $this->matchCollection(rawurldecode($this->currentPath->getPath($request)), $collection)) {
  116. return $this->applyRouteEnhancers($ret, $request);
  117. }
  118. throw 0 < count($this->allow)
  119. ? new MethodNotAllowedException(array_unique($this->allow))
  120. : new ResourceNotFoundException(sprintf('No routes found for "%s".', $this->currentPath->getPath()));
  121. }
  122. /**
  123. * Tries to match a URL with a set of routes.
  124. *
  125. * @param string $pathinfo
  126. * The path info to be parsed
  127. * @param \Symfony\Component\Routing\RouteCollection $routes
  128. * The set of routes.
  129. *
  130. * @return array|null
  131. * An array of parameters. NULL when there is no match.
  132. */
  133. protected function matchCollection($pathinfo, RouteCollection $routes) {
  134. // Try a case-sensitive match.
  135. $match = $this->doMatchCollection($pathinfo, $routes, TRUE);
  136. // Try a case-insensitive match.
  137. if ($match === NULL && $routes->count() > 0) {
  138. $match = $this->doMatchCollection($pathinfo, $routes, FALSE);
  139. }
  140. return $match;
  141. }
  142. /**
  143. * Tries to match a URL with a set of routes.
  144. *
  145. * This code is very similar to Symfony's UrlMatcher::matchCollection() but it
  146. * supports case-insensitive matching. The static prefix optimization is
  147. * removed as this duplicates work done by the query in
  148. * RouteProvider::getRoutesByPath().
  149. *
  150. * @param string $pathinfo
  151. * The path info to be parsed
  152. * @param \Symfony\Component\Routing\RouteCollection $routes
  153. * The set of routes.
  154. * @param bool $case_sensitive
  155. * Determines if the match should be case-sensitive of not.
  156. *
  157. * @return array|null
  158. * An array of parameters. NULL when there is no match.
  159. *
  160. * @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection()
  161. * @see \Drupal\Core\Routing\RouteProvider::getRoutesByPath()
  162. */
  163. protected function doMatchCollection($pathinfo, RouteCollection $routes, $case_sensitive) {
  164. foreach ($routes as $name => $route) {
  165. $compiledRoute = $route->compile();
  166. // Set the regex to use UTF-8.
  167. $regex = $compiledRoute->getRegex() . 'u';
  168. if (!$case_sensitive) {
  169. $regex = $regex . 'i';
  170. }
  171. if (!preg_match($regex, $pathinfo, $matches)) {
  172. continue;
  173. }
  174. $hostMatches = [];
  175. if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
  176. $routes->remove($name);
  177. continue;
  178. }
  179. // Check HTTP method requirement.
  180. if ($requiredMethods = $route->getMethods()) {
  181. // HEAD and GET are equivalent as per RFC.
  182. if ('HEAD' === $method = $this->context->getMethod()) {
  183. $method = 'GET';
  184. }
  185. if (!in_array($method, $requiredMethods)) {
  186. $this->allow = array_merge($this->allow, $requiredMethods);
  187. $routes->remove($name);
  188. continue;
  189. }
  190. }
  191. $status = $this->handleRouteRequirements($pathinfo, $name, $route);
  192. if (self::ROUTE_MATCH === $status[0]) {
  193. return $status[1];
  194. }
  195. if (self::REQUIREMENT_MISMATCH === $status[0]) {
  196. $routes->remove($name);
  197. continue;
  198. }
  199. return $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
  200. }
  201. }
  202. /**
  203. * Returns a collection of potential matching routes for a request.
  204. *
  205. * @param \Symfony\Component\HttpFoundation\Request $request
  206. * The current request.
  207. *
  208. * @return \Symfony\Component\Routing\RouteCollection
  209. * The initial fetched route collection.
  210. */
  211. protected function getInitialRouteCollection(Request $request) {
  212. return $this->routeProvider->getRouteCollectionForRequest($request);
  213. }
  214. /**
  215. * Apply the route enhancers to the defaults, according to priorities.
  216. *
  217. * @param array $defaults
  218. * The defaults coming from the final matched route.
  219. * @param \Symfony\Component\HttpFoundation\Request $request
  220. * The request.
  221. *
  222. * @return array
  223. * The request attributes after applying the enhancers. This might consist
  224. * raw values from the URL but also upcasted values, like entity objects,
  225. * from route enhancers.
  226. */
  227. protected function applyRouteEnhancers($defaults, Request $request) {
  228. foreach ($this->enhancers as $enhancer) {
  229. $defaults = $enhancer->enhance($defaults, $request);
  230. }
  231. return $defaults;
  232. }
  233. /**
  234. * Applies all route filters to a given route collection.
  235. *
  236. * This method reduces the sets of routes further down, for example by
  237. * checking the HTTP method.
  238. *
  239. * @param \Symfony\Component\Routing\RouteCollection $collection
  240. * The route collection.
  241. * @param \Symfony\Component\HttpFoundation\Request $request
  242. * The request.
  243. *
  244. * @return \Symfony\Component\Routing\RouteCollection
  245. * The filtered/sorted route collection.
  246. */
  247. protected function applyRouteFilters(RouteCollection $collection, Request $request) {
  248. // Route filters are expected to throw an exception themselves if they
  249. // end up filtering the list down to 0.
  250. foreach ($this->filters as $filter) {
  251. $collection = $filter->filter($collection, $request);
  252. }
  253. return $collection;
  254. }
  255. /**
  256. * Reapplies the fit order to a RouteCollection object.
  257. *
  258. * Route filters can reorder route collections. For example, routes with an
  259. * explicit _format requirement will be preferred. This can result in a less
  260. * fit route being used. For example, as a result of filtering /user/% comes
  261. * before /user/login. In order to not break this fundamental property of
  262. * routes, we need to reapply the fit order. We also need to ensure that order
  263. * within each group of the same fit is preserved.
  264. *
  265. * @param \Symfony\Component\Routing\RouteCollection $collection
  266. * The route collection.
  267. *
  268. * @return \Symfony\Component\Routing\RouteCollection
  269. * The reordered route collection.
  270. */
  271. protected function applyFitOrder(RouteCollection $collection) {
  272. $buckets = [];
  273. // Sort all the routes by fit descending.
  274. foreach ($collection->all() as $name => $route) {
  275. $fit = $route->compile()->getFit();
  276. $buckets += [$fit => []];
  277. $buckets[$fit][] = [$name, $route];
  278. }
  279. krsort($buckets);
  280. $flattened = array_reduce($buckets, 'array_merge', []);
  281. // Add them back onto a new route collection.
  282. $collection = new RouteCollection();
  283. foreach ($flattened as $pair) {
  284. $name = $pair[0];
  285. $route = $pair[1];
  286. $collection->add($name, $route);
  287. }
  288. return $collection;
  289. }
  290. /**
  291. * {@inheritdoc}
  292. */
  293. public function getRouteCollection(): RouteCollection {
  294. return new LazyRouteCollection($this->routeProvider);
  295. }
  296. /**
  297. * {@inheritdoc}
  298. */
  299. public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH): string {
  300. @trigger_error(__METHOD__ . '() is deprecated in drupal:8.3.0 and will throw an exception from drupal:10.0.0. Use the \Drupal\Core\Url object instead. See https://www.drupal.org/node/2820197', E_USER_DEPRECATED);
  301. return $this->urlGenerator->generate($name, $parameters, $referenceType);
  302. }
  303. }