RoutePreloader.php 4.0 KB

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