RouteBuilder.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. namespace Drupal\Core\Routing;
  3. use Drupal\Core\Access\CheckProviderInterface;
  4. use Drupal\Core\Controller\ControllerResolverInterface;
  5. use Drupal\Core\Discovery\YamlDiscovery;
  6. use Drupal\Core\Extension\ModuleHandlerInterface;
  7. use Drupal\Core\Lock\LockBackendInterface;
  8. use Drupal\Core\DestructableInterface;
  9. use Symfony\Component\EventDispatcher\Event;
  10. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  11. use Symfony\Component\Routing\RouteCollection;
  12. use Symfony\Component\Routing\Route;
  13. /**
  14. * Managing class for rebuilding the router table.
  15. */
  16. class RouteBuilder implements RouteBuilderInterface, DestructableInterface {
  17. /**
  18. * The dumper to which we should send collected routes.
  19. *
  20. * @var \Drupal\Core\Routing\MatcherDumperInterface
  21. */
  22. protected $dumper;
  23. /**
  24. * The used lock backend instance.
  25. *
  26. * @var \Drupal\Core\Lock\LockBackendInterface
  27. */
  28. protected $lock;
  29. /**
  30. * The event dispatcher to notify of routes.
  31. *
  32. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  33. */
  34. protected $dispatcher;
  35. /**
  36. * The module handler.
  37. *
  38. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  39. */
  40. protected $moduleHandler;
  41. /**
  42. * The controller resolver.
  43. *
  44. * @var \Drupal\Core\Controller\ControllerResolverInterface
  45. */
  46. protected $controllerResolver;
  47. /**
  48. * The route collection during the rebuild.
  49. *
  50. * @var \Symfony\Component\Routing\RouteCollection
  51. */
  52. protected $routeCollection;
  53. /**
  54. * Flag that indicates if we are currently rebuilding the routes.
  55. *
  56. * @var bool
  57. */
  58. protected $building = FALSE;
  59. /**
  60. * Flag that indicates if we should rebuild at the end of the request.
  61. *
  62. * @var bool
  63. */
  64. protected $rebuildNeeded = FALSE;
  65. /**
  66. * The check provider.
  67. *
  68. * @var \Drupal\Core\Access\CheckProviderInterface
  69. */
  70. protected $checkProvider;
  71. /**
  72. * Constructs the RouteBuilder using the passed MatcherDumperInterface.
  73. *
  74. * @param \Drupal\Core\Routing\MatcherDumperInterface $dumper
  75. * The matcher dumper used to store the route information.
  76. * @param \Drupal\Core\Lock\LockBackendInterface $lock
  77. * The lock backend.
  78. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
  79. * The event dispatcher to notify of routes.
  80. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  81. * The module handler.
  82. * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
  83. * The controller resolver.
  84. * @param \Drupal\Core\Access\CheckProviderInterface $check_provider
  85. * The check provider.
  86. */
  87. public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver, CheckProviderInterface $check_provider) {
  88. $this->dumper = $dumper;
  89. $this->lock = $lock;
  90. $this->dispatcher = $dispatcher;
  91. $this->moduleHandler = $module_handler;
  92. $this->controllerResolver = $controller_resolver;
  93. $this->checkProvider = $check_provider;
  94. }
  95. /**
  96. * {@inheritdoc}
  97. */
  98. public function setRebuildNeeded() {
  99. $this->rebuildNeeded = TRUE;
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function rebuild() {
  105. if ($this->building) {
  106. throw new \RuntimeException('Recursive router rebuild detected.');
  107. }
  108. if (!$this->lock->acquire('router_rebuild')) {
  109. // Wait for another request that is already doing this work.
  110. // We choose to block here since otherwise the routes might not be
  111. // available, resulting in a 404.
  112. $this->lock->wait('router_rebuild');
  113. return FALSE;
  114. }
  115. $this->building = TRUE;
  116. $collection = new RouteCollection();
  117. foreach ($this->getRouteDefinitions() as $routes) {
  118. // The top-level 'routes_callback' is a list of methods in controller
  119. // syntax, see \Drupal\Core\Controller\ControllerResolver. These methods
  120. // should return a set of \Symfony\Component\Routing\Route objects, either
  121. // in an associative array keyed by the route name, which will be iterated
  122. // over and added to the collection for this provider, or as a new
  123. // \Symfony\Component\Routing\RouteCollection object, which will be added
  124. // to the collection.
  125. if (isset($routes['route_callbacks'])) {
  126. foreach ($routes['route_callbacks'] as $route_callback) {
  127. $callback = $this->controllerResolver->getControllerFromDefinition($route_callback);
  128. if ($callback_routes = call_user_func($callback)) {
  129. // If a RouteCollection is returned, add the whole collection.
  130. if ($callback_routes instanceof RouteCollection) {
  131. $collection->addCollection($callback_routes);
  132. }
  133. // Otherwise, add each Route object individually.
  134. else {
  135. foreach ($callback_routes as $name => $callback_route) {
  136. $collection->add($name, $callback_route);
  137. }
  138. }
  139. }
  140. }
  141. unset($routes['route_callbacks']);
  142. }
  143. foreach ($routes as $name => $route_info) {
  144. $route_info += [
  145. 'defaults' => [],
  146. 'requirements' => [],
  147. 'options' => [],
  148. 'host' => NULL,
  149. 'schemes' => [],
  150. 'methods' => [],
  151. 'condition' => '',
  152. ];
  153. // Ensure routes default to using Drupal's route compiler instead of
  154. // Symfony's.
  155. $route_info['options'] += [
  156. 'compiler_class' => RouteCompiler::class,
  157. ];
  158. $route = new Route($route_info['path'], $route_info['defaults'], $route_info['requirements'], $route_info['options'], $route_info['host'], $route_info['schemes'], $route_info['methods'], $route_info['condition']);
  159. $collection->add($name, $route);
  160. }
  161. }
  162. // DYNAMIC is supposed to be used to add new routes based upon all the
  163. // static defined ones.
  164. $this->dispatcher->dispatch(RoutingEvents::DYNAMIC, new RouteBuildEvent($collection));
  165. // ALTER is the final step to alter all the existing routes. We cannot stop
  166. // people from adding new routes here, but we define two separate steps to
  167. // make it clear.
  168. $this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection));
  169. $this->checkProvider->setChecks($collection);
  170. $this->dumper->addRoutes($collection);
  171. $this->dumper->dump();
  172. $this->lock->release('router_rebuild');
  173. $this->dispatcher->dispatch(RoutingEvents::FINISHED, new Event());
  174. $this->building = FALSE;
  175. $this->rebuildNeeded = FALSE;
  176. return TRUE;
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function rebuildIfNeeded() {
  182. if ($this->rebuildNeeded) {
  183. return $this->rebuild();
  184. }
  185. return FALSE;
  186. }
  187. /**
  188. * {@inheritdoc}
  189. */
  190. public function destruct() {
  191. // Rebuild routes only once at the end of the request lifecycle to not
  192. // trigger multiple rebuilds and also make the page more responsive for the
  193. // user.
  194. $this->rebuildIfNeeded();
  195. }
  196. /**
  197. * Retrieves all defined routes from .routing.yml files.
  198. *
  199. * @return array
  200. * The defined routes, keyed by provider.
  201. */
  202. protected function getRouteDefinitions() {
  203. // Always instantiate a new YamlDiscovery object so that we always search on
  204. // the up-to-date list of modules.
  205. $discovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories());
  206. return $discovery->findAll();
  207. }
  208. }