annotate core/lib/Drupal/Core/Routing/RouteBuilder.php @ 19:fa3358dc1485 tip

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