Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Routing;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Cache\Cache;
|
Chris@0
|
6 use Drupal\Core\Cache\CacheBackendInterface;
|
Chris@0
|
7 use Drupal\Core\State\StateInterface;
|
Chris@0
|
8 use Symfony\Component\EventDispatcher\Event;
|
Chris@0
|
9 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
Chris@0
|
10 use Symfony\Component\HttpKernel\Event\KernelEvent;
|
Chris@0
|
11 use Symfony\Component\HttpKernel\KernelEvents;
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * Defines a class which preloads non-admin routes.
|
Chris@0
|
15 *
|
Chris@0
|
16 * On an actual site we want to avoid too many database queries so we build a
|
Chris@0
|
17 * list of all routes which most likely appear on the actual site, which are all
|
Chris@0
|
18 * HTML routes not starting with "/admin".
|
Chris@0
|
19 */
|
Chris@0
|
20 class RoutePreloader implements EventSubscriberInterface {
|
Chris@0
|
21
|
Chris@0
|
22 /**
|
Chris@0
|
23 * The route provider.
|
Chris@0
|
24 *
|
Chris@0
|
25 * @var \Drupal\Core\Routing\RouteProviderInterface|\Drupal\Core\Routing\PreloadableRouteProviderInterface
|
Chris@0
|
26 */
|
Chris@0
|
27 protected $routeProvider;
|
Chris@0
|
28
|
Chris@0
|
29 /**
|
Chris@0
|
30 * The state key value store.
|
Chris@0
|
31 *
|
Chris@0
|
32 * @var \Drupal\Core\State\StateInterface
|
Chris@0
|
33 */
|
Chris@0
|
34 protected $state;
|
Chris@0
|
35
|
Chris@0
|
36 /**
|
Chris@0
|
37 * Contains the non-admin routes while rebuilding the routes.
|
Chris@0
|
38 *
|
Chris@0
|
39 * @var array
|
Chris@0
|
40 */
|
Chris@0
|
41 protected $nonAdminRoutesOnRebuild = [];
|
Chris@0
|
42
|
Chris@0
|
43 /**
|
Chris@0
|
44 * The cache backend used to skip the state loading.
|
Chris@0
|
45 *
|
Chris@0
|
46 * @var \Drupal\Core\Cache\CacheBackendInterface
|
Chris@0
|
47 */
|
Chris@0
|
48 protected $cache;
|
Chris@0
|
49
|
Chris@0
|
50 /**
|
Chris@0
|
51 * Constructs a new RoutePreloader.
|
Chris@0
|
52 *
|
Chris@0
|
53 * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
Chris@0
|
54 * The route provider.
|
Chris@0
|
55 * @param \Drupal\Core\State\StateInterface $state
|
Chris@0
|
56 * The state key value store.
|
Chris@0
|
57 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
Chris@0
|
58 */
|
Chris@0
|
59 public function __construct(RouteProviderInterface $route_provider, StateInterface $state, CacheBackendInterface $cache) {
|
Chris@0
|
60 $this->routeProvider = $route_provider;
|
Chris@0
|
61 $this->state = $state;
|
Chris@0
|
62 $this->cache = $cache;
|
Chris@0
|
63 }
|
Chris@0
|
64
|
Chris@0
|
65 /**
|
Chris@0
|
66 * Loads all non-admin routes right before the actual page is rendered.
|
Chris@0
|
67 *
|
Chris@0
|
68 * @param \Symfony\Component\HttpKernel\Event\KernelEvent $event
|
Chris@0
|
69 * The event to process.
|
Chris@0
|
70 */
|
Chris@0
|
71 public function onRequest(KernelEvent $event) {
|
Chris@0
|
72 // Only preload on normal HTML pages, as they will display menu links.
|
Chris@0
|
73 if ($this->routeProvider instanceof PreloadableRouteProviderInterface && $event->getRequest()->getRequestFormat() == 'html') {
|
Chris@0
|
74
|
Chris@0
|
75 // Ensure that the state query is cached to skip the database query, if
|
Chris@0
|
76 // possible.
|
Chris@0
|
77 $key = 'routing.non_admin_routes';
|
Chris@0
|
78 if ($cache = $this->cache->get($key)) {
|
Chris@0
|
79 $routes = $cache->data;
|
Chris@0
|
80 }
|
Chris@0
|
81 else {
|
Chris@0
|
82 $routes = $this->state->get($key, []);
|
Chris@0
|
83 $this->cache->set($key, $routes, Cache::PERMANENT, ['routes']);
|
Chris@0
|
84 }
|
Chris@0
|
85
|
Chris@0
|
86 if ($routes) {
|
Chris@0
|
87 // Preload all the non-admin routes at once.
|
Chris@0
|
88 $this->routeProvider->preLoadRoutes($routes);
|
Chris@0
|
89 }
|
Chris@0
|
90 }
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 /**
|
Chris@0
|
94 * Alters existing routes for a specific collection.
|
Chris@0
|
95 *
|
Chris@0
|
96 * @param \Drupal\Core\Routing\RouteBuildEvent $event
|
Chris@0
|
97 * The route build event.
|
Chris@0
|
98 */
|
Chris@0
|
99 public function onAlterRoutes(RouteBuildEvent $event) {
|
Chris@0
|
100 $collection = $event->getRouteCollection();
|
Chris@0
|
101 foreach ($collection->all() as $name => $route) {
|
Chris@0
|
102 if (strpos($route->getPath(), '/admin/') !== 0 && $route->getPath() != '/admin') {
|
Chris@0
|
103 $this->nonAdminRoutesOnRebuild[] = $name;
|
Chris@0
|
104 }
|
Chris@0
|
105 }
|
Chris@0
|
106 $this->nonAdminRoutesOnRebuild = array_unique($this->nonAdminRoutesOnRebuild);
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 /**
|
Chris@0
|
110 * Store the non admin routes in state when the route building is finished.
|
Chris@0
|
111 *
|
Chris@0
|
112 * @param \Symfony\Component\EventDispatcher\Event $event
|
Chris@0
|
113 * The route finish event.
|
Chris@0
|
114 */
|
Chris@0
|
115 public function onFinishedRoutes(Event $event) {
|
Chris@0
|
116 $this->state->set('routing.non_admin_routes', $this->nonAdminRoutesOnRebuild);
|
Chris@0
|
117 $this->nonAdminRoutesOnRebuild = [];
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 /**
|
Chris@0
|
121 * {@inheritdoc}
|
Chris@0
|
122 */
|
Chris@0
|
123 public static function getSubscribedEvents() {
|
Chris@0
|
124 // Set a really low priority to catch as many as possible routes.
|
Chris@0
|
125 $events[RoutingEvents::ALTER] = ['onAlterRoutes', -1024];
|
Chris@0
|
126 $events[RoutingEvents::FINISHED] = ['onFinishedRoutes'];
|
Chris@0
|
127 // Load the routes before the controller is executed (which happens after
|
Chris@0
|
128 // the kernel request event).
|
Chris@0
|
129 $events[KernelEvents::REQUEST][] = ['onRequest'];
|
Chris@0
|
130 return $events;
|
Chris@0
|
131 }
|
Chris@0
|
132
|
Chris@0
|
133 }
|