RouteBuilder.php 7.03 KB
Newer Older
1 2
<?php

3 4 5 6 7
/**
 * @file
 * Definition of Drupal\Core\Routing\RouteBuilder.
 */

8 9
namespace Drupal\Core\Routing;

10
use Drupal\Component\Discovery\YamlDiscovery;
11
use Drupal\Core\Controller\ControllerResolverInterface;
12
use Drupal\Core\State\StateInterface;
13
use Symfony\Component\EventDispatcher\Event;
14 15 16 17
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

18
use Drupal\Core\Extension\ModuleHandlerInterface;
19
use Drupal\Core\Lock\LockBackendInterface;
20 21 22 23

/**
 * Managing class for rebuilding the router table.
 */
24
class RouteBuilder implements RouteBuilderInterface {
25

26 27 28
  /**
   * The dumper to which we should send collected routes.
   *
29
   * @var \Drupal\Core\Routing\MatcherDumperInterface
30
   */
31 32
  protected $dumper;

33 34 35 36 37 38 39
  /**
   * The used lock backend instance.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface $lock
   */
  protected $lock;

40 41 42 43 44 45 46
  /**
   * The event dispatcher to notify of routes.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $dispatcher;

47
  /**
48 49 50 51 52 53 54 55
   * The yaml discovery used to find all the .routing.yml files.
   *
   * @var \Drupal\Component\Discovery\YamlDiscovery
   */
  protected $yamlDiscovery;

  /**
   * The module handler.
56 57 58 59 60
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

61 62 63 64 65 66 67
  /**
   * The route builder indicator.
   *
   * @var \Drupal\Core\Routing\RouteBuilderIndicatorInterface
   */
  protected $routeBuilderIndicator;

68 69 70 71 72 73 74
  /**
   * The controller resolver.
   *
   * @var \Drupal\Core\Controller\ControllerResolverInterface
   */
  protected $controllerResolver;

75 76 77 78 79 80 81
  /**
   * The route collection during the rebuild.
   *
   * @var \Symfony\Component\Routing\RouteCollection
   */
  protected $routeCollection;

82 83 84 85 86 87 88
  /**
   * Flag that indiciates if we are currently rebuilding the routes.
   *
   * @var bool
   */
  protected $building = FALSE;

89
  /**
90
   * Constructs the RouteBuilder using the passed MatcherDumperInterface.
91
   *
92
   * @param \Drupal\Core\Routing\MatcherDumperInterface $dumper
93
   *   The matcher dumper used to store the route information.
94 95
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   The lock backend.
96
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
97
   *   The event dispatcher to notify of routes.
98 99
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
100 101
   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
   *   The controller resolver.
102 103
   * @param \Drupal\Core\Routing\RouteBuilderIndicatorInterface $route_build_indicator
   *   The route build indicator.
104
   */
105
  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver, RouteBuilderIndicatorInterface $route_build_indicator = NULL) {
106
    $this->dumper = $dumper;
107
    $this->lock = $lock;
108
    $this->dispatcher = $dispatcher;
109
    $this->moduleHandler = $module_handler;
110
    $this->controllerResolver = $controller_resolver;
111
    $this->routeBuilderIndicator = $route_build_indicator;
112 113
  }

114
  /**
115
   * {@inheritdoc}
116
   */
117
  public function rebuild() {
118 119 120 121
    if ($this->building) {
      throw new \RuntimeException('Recursive router rebuild detected.');
    }

122 123 124 125 126
    if (!$this->lock->acquire('router_rebuild')) {
      // Wait for another request that is already doing this work.
      // We choose to block here since otherwise the routes might not be
      // available, resulting in a 404.
      $this->lock->wait('router_rebuild');
127
      return FALSE;
128 129
    }

130 131
    $this->building = TRUE;

132 133 134
    $collection = new RouteCollection();
    $this->routeCollection = $collection;
    foreach ($this->getRouteDefinitions() as $routes) {
135 136 137
      // The top-level 'routes_callback' is a list of methods in controller
      // syntax, see \Drupal\Core\Controller\ControllerResolver. These methods
      // should return a set of \Symfony\Component\Routing\Route objects, either
138 139 140 141
      // in an associative array keyed by the route name, which will be iterated
      // over and added to the collection for this provider, or as a new
      // \Symfony\Component\Routing\RouteCollection object, which will be added
      // to the collection.
142 143 144 145
      if (isset($routes['route_callbacks'])) {
        foreach ($routes['route_callbacks'] as $route_callback) {
          $callback = $this->controllerResolver->getControllerFromDefinition($route_callback);
          if ($callback_routes = call_user_func($callback)) {
146 147 148 149 150 151 152 153 154
            // If a RouteCollection is returned, add the whole collection.
            if ($callback_routes instanceof RouteCollection) {
              $collection->addCollection($callback_routes);
            }
            // Otherwise, add each Route object individually.
            else {
              foreach ($callback_routes as $name => $callback_route) {
                $collection->add($name, $callback_route);
              }
155 156 157 158 159
            }
          }
        }
        unset($routes['route_callbacks']);
      }
160 161 162 163 164 165 166
      foreach ($routes as $name => $route_info) {
        $route_info += array(
          'defaults' => array(),
          'requirements' => array(),
          'options' => array(),
        );

167
        $route = new Route($route_info['path'], $route_info['defaults'], $route_info['requirements'], $route_info['options']);
168
        $collection->add($name, $route);
169
      }
170

171
    }
172

173 174 175 176 177 178 179 180 181
    // DYNAMIC is supposed to be used to add new routes based upon all the
    // static defined ones.
    $this->dispatcher->dispatch(RoutingEvents::DYNAMIC, new RouteBuildEvent($collection));

    // ALTER is the final step to alter all the existing routes. We cannot stop
    // people from adding new routes here, but we define two separate steps to
    // make it clear.
    $this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection));

182
    $this->dumper->addRoutes($collection);
183
    $this->dumper->dump();
184

185
    $this->routeBuilderIndicator->setRebuildDone();
186
    $this->lock->release('router_rebuild');
187
    $this->dispatcher->dispatch(RoutingEvents::FINISHED, new Event());
188
    $this->building = FALSE;
189 190 191

    $this->routeCollection = NULL;

192 193 194
    return TRUE;
  }

195 196 197 198 199 200 201
  /**
   * {@inheritdoc}
   */
  public function getCollectionDuringRebuild() {
    return $this->routeCollection ?: FALSE;
  }

202 203 204 205
  /**
   * {@inheritdoc}
   */
  public function rebuildIfNeeded() {
206
    if ($this->routeBuilderIndicator->isRebuildNeeded()) {
207 208 209 210 211 212 213 214 215
      return $this->rebuild();
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function setRebuildNeeded() {
216
    $this->routeBuilderIndicator->setRebuildNeeded();
217 218
  }

219
  /**
220
   * Retrieves all defined routes from .routing.yml files.
221
   *
222 223
   * @return array
   *   The defined routes, keyed by provider.
224
   */
225
  protected function getRouteDefinitions() {
226 227 228
    if (!isset($this->yamlDiscovery)) {
      $this->yamlDiscovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories());
    }
229
    return $this->yamlDiscovery->findAll();
230 231 232
  }

}