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 }
|