RouteBuilder.php 6.31 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\KeyValueStore\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 24 25 26

/**
 * Managing class for rebuilding the router table.
 *
 * Because this class makes use of the modules system, it cannot currently
 * be unit tested.
 */
27
class RouteBuilder implements RouteBuilderInterface {
28

29 30 31 32 33
  /**
   * The dumper to which we should send collected routes.
   *
   * @var \Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface
   */
34 35
  protected $dumper;

36 37 38 39 40 41 42
  /**
   * The used lock backend instance.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface $lock
   */
  protected $lock;

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

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

  /**
   * The module handler.
59 60 61 62 63
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

64 65 66 67 68 69 70
  /**
   * The controller resolver.
   *
   * @var \Drupal\Core\Controller\ControllerResolverInterface
   */
  protected $controllerResolver;

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 77
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   The lock backend.
78
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
79
   *   The event dispatcher to notify of routes.
80 81
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
82 83
   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
   *   The controller resolver.
84
   */
85
  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver, StateInterface $state = NULL) {
86
    $this->dumper = $dumper;
87
    $this->lock = $lock;
88
    $this->dispatcher = $dispatcher;
89
    $this->moduleHandler = $module_handler;
90
    $this->controllerResolver = $controller_resolver;
91
    $this->state = $state;
92 93
  }

94
  /**
95
   * {@inheritdoc}
96
   */
97
  public function rebuild() {
98 99 100 101 102
    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');
103
      return FALSE;
104 105
    }

106
    $yaml_discovery = $this->getYamlDiscovery();
107

108
    foreach ($yaml_discovery->findAll() as $provider => $routes) {
109
      $collection = new RouteCollection();
110

111 112 113
      // 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
114 115 116 117
      // 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.
118 119 120 121
      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)) {
122 123 124 125 126 127 128 129 130
            // 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);
              }
131 132 133 134 135
            }
          }
        }
        unset($routes['route_callbacks']);
      }
136 137 138 139 140 141 142
      foreach ($routes as $name => $route_info) {
        $route_info += array(
          'defaults' => array(),
          'requirements' => array(),
          'options' => array(),
        );

143
        $route = new Route($route_info['path'], $route_info['defaults'], $route_info['requirements'], $route_info['options']);
144
        $collection->add($name, $route);
145
      }
146

147
      $this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection, $provider));
148
      $this->dumper->addRoutes($collection);
149
      $this->dumper->dump(array('provider' => $provider));
150
    }
151 152

    // Now allow modules to register additional, dynamic routes.
153
    // @todo Either remove this alter or the per-provider alter.
154 155 156
    $collection = new RouteCollection();
    $this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection, 'dynamic_routes'));
    $this->dumper->addRoutes($collection);
157
    $this->dumper->dump(array('provider' => 'dynamic_routes'));
158

159
    $this->state->delete(static::REBUILD_NEEDED);
160
    $this->lock->release('router_rebuild');
161
    $this->dispatcher->dispatch(RoutingEvents::FINISHED, new Event());
162 163 164
    return TRUE;
  }

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  /**
   * {@inheritdoc}
   */
  public function rebuildIfNeeded() {
    if ($this->state->get(static::REBUILD_NEEDED, FALSE)) {
      return $this->rebuild();
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function setRebuildNeeded() {
    $this->state->set(static::REBUILD_NEEDED, TRUE);
  }

182 183 184 185 186 187 188 189 190 191 192
  /**
   * Returns the YAML discovery for getting all the .routing.yml files.
   *
   * @return \Drupal\Component\Discovery\YamlDiscovery
   *   The yaml discovery.
   */
  protected function getYamlDiscovery() {
    if (!isset($this->yamlDiscovery)) {
      $this->yamlDiscovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories());
    }
    return $this->yamlDiscovery;
193 194 195
  }

}