Commit 3b84f7b2 authored by catch's avatar catch

Issue #2238217 by effulgentsia, neclimdul, martin107, tim.plunkett, cweagans,...

Issue #2238217 by effulgentsia, neclimdul, martin107, tim.plunkett, cweagans, kim.pepper, xjm: Introduce a RouteMatch class.
parent 6140e65c
......@@ -19,7 +19,7 @@ services:
- { name: cache.context}
cache_context.theme:
class: Drupal\Core\Cache\ThemeCacheContext
arguments: ['@request_stack', '@theme.negotiator']
arguments: ['@current_route_match', '@theme.negotiator']
tags:
- { name: cache.context}
cache_context.timezone:
......@@ -201,7 +201,7 @@ services:
- { name: service_collector, tag: http_client_subscriber, call: attach }
theme.negotiator:
class: Drupal\Core\Theme\ThemeNegotiator
arguments: ['@access_check.theme', '@request_stack']
arguments: ['@access_check.theme']
tags:
- { name: service_collector, tag: theme_negotiator, call: addNegotiator }
theme.negotiator.default:
......@@ -211,7 +211,7 @@ services:
- { name: theme_negotiator, priority: -100 }
theme.negotiator.ajax_base_page:
class: Drupal\Core\Theme\AjaxBasePageNegotiator
arguments: ['@csrf_token', '@config.factory']
arguments: ['@csrf_token', '@config.factory', '@request_stack']
tags:
- { name: theme_negotiator, priority: 1000 }
container.namespaces:
......@@ -273,6 +273,9 @@ services:
class: Symfony\Component\HttpFoundation\RequestStack
tags:
- { name: persist }
current_route_match:
class: Drupal\Core\Routing\CurrentRouteMatch
arguments: ['@request_stack']
event_dispatcher:
class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
arguments: ['@service_container']
......
......@@ -8,7 +8,6 @@
use Drupal\Component\Utility\String;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
* @defgroup menu Menu and routing system
......@@ -479,7 +478,7 @@ function menu_local_tasks($level = 0) {
$data['tabs'] = array();
$data['actions'] = array();
$route_name = \Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME);
$route_name = \Drupal::routeMatch()->getRouteName();
if (!empty($route_name)) {
$manager = \Drupal::service('plugin.manager.menu.local_task');
$local_tasks = $manager->getTasksBuild($route_name);
......@@ -534,7 +533,7 @@ function menu_secondary_local_tasks() {
*/
function menu_get_local_actions() {
$links = menu_local_tasks();
$route_name = Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME);
$route_name = Drupal::routeMatch()->getRouteName();
$manager = \Drupal::service('plugin.manager.menu.local_action');
return $manager->getActionsForRoute($route_name) + $links['actions'];
}
......
......@@ -104,8 +104,7 @@ function drupal_theme_initialize() {
// @todo Let the theme.negotiator listen to the kernel request event.
// Determine the active theme for the theme negotiator service. This includes
// the default theme as well as really specific ones like the ajax base theme.
$request = \Drupal::request();
$theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request);
$theme = \Drupal::service('theme.negotiator')->determineActiveTheme(\Drupal::routeMatch());
// If no theme could be negotiated, or if the negotiated theme is not within
// the list of enabled themes, fall back to the default theme output of core
......@@ -2133,7 +2132,7 @@ function template_preprocess_page(&$variables) {
);
}
if ($node = \Drupal::request()->attributes->get('node')) {
if ($node = \Drupal::routeMatch()->getParameter('node')) {
$variables['node'] = $node;
}
......
......@@ -187,6 +187,16 @@ public static function request() {
return static::$container->get('request');
}
/**
* Retrieves the currently active route match object.
*
* @return \Drupal\Core\Routing\RouteMatchInterface
* The currently active route match object.
*/
public static function routeMatch() {
return static::$container->get('current_route_match');
}
/**
* Gets the current active user.
*
......
......@@ -7,7 +7,7 @@
namespace Drupal\Core\Cache;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
/**
......@@ -16,11 +16,11 @@
class ThemeCacheContext implements CacheContextInterface {
/**
* The request stack.
* The current route match.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
* @var \Drupal\Core\Routing\RouteMatch
*/
protected $requestStack;
protected $routeMatch;
/**
* The theme negotiator.
......@@ -32,13 +32,13 @@ class ThemeCacheContext implements CacheContextInterface {
/**
* Constructs a new ThemeCacheContext service.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
* The theme negotiator.
*/
public function __construct(RequestStack $request_stack, ThemeNegotiatorInterface $theme_negotiator) {
$this->requestStack = $request_stack;
public function __construct(RouteMatchInterface $route_match, ThemeNegotiatorInterface $theme_negotiator) {
$this->routeMatch = $route_match;
$this->themeNegotiator = $theme_negotiator;
}
......@@ -53,8 +53,7 @@ public static function getLabel() {
* {@inheritdoc}
*/
public function getContext() {
$request = $this->requestStack->getCurrentRequest();
return $this->themeNegotiator->determineActiveTheme($request) ?: 'stark';
return $this->themeNegotiator->determineActiveTheme($this->routeMatch) ?: 'stark';
}
}
<?php
/**
* @file
* Contains Drupal\Core\Routing\CurrentRouteMatch.
*/
namespace Drupal\Core\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Default object for current_route_match service.
*/
class CurrentRouteMatch implements RouteMatchInterface {
/**
* The related request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Internal cache of RouteMatch objects.
*
* @var \SplObjectStorage
*/
protected $routeMatches;
/**
* Constructs a CurrentRouteMatch object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*/
public function __construct(RequestStack $request_stack) {
$this->requestStack = $request_stack;
$this->routeMatches = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
return $this->getCurrentRouteMatch()->getRouteName();
}
/**
* {@inheritdoc}
*/
public function getRouteObject() {
return $this->getCurrentRouteMatch()->getRouteObject();
}
/**
* {@inheritdoc}
*/
public function getParameter($parameter_name) {
return $this->getCurrentRouteMatch()->getParameter($parameter_name);
}
/**
* {@inheritdoc}
*/
public function getParameters() {
return $this->getCurrentRouteMatch()->getParameters();
}
/**
* {@inheritdoc}
*/
public function getRawParameter($parameter_name) {
return $this->getCurrentRouteMatch()->getRawParameter($parameter_name);
}
/**
* {@inheritdoc}
*/
public function getRawParameters() {
return $this->getCurrentRouteMatch()->getRawParameters();
}
/**
* Returns the route match for the current request.
*
* @return \Drupal\Core\Routing\RouteMatchInterface
* The current route match object.
*/
protected function getCurrentRouteMatch() {
return $this->getRouteMatch($this->requestStack->getCurrentRequest());
}
/**
* Returns the route match for a passed in request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* A request object.
*
* @return \Drupal\Core\Routing\RouteMatchInterface
* A route match object created from the request.
*/
protected function getRouteMatch(Request $request) {
if (isset($this->routeMatches[$request])) {
$route_match = $this->routeMatches[$request];
}
else {
$route_match = RouteMatch::createFromRequest($request);
// Since getRouteMatch() might be invoked both before and after routing
// is completed, only statically cache the route match after there's a
// matched route.
if ($route_match->getRouteObject()) {
$this->routeMatches[$request] = $route_match;
}
}
return $route_match;
}
}
<?php
/**
* @file
* Contains Drupal\Core\Routing\NullRouteMatch.
*/
namespace Drupal\Core\Routing;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Stub implementation of RouteMatchInterface for when there's no matched route.
*/
class NullRouteMatch implements RouteMatchInterface {
/**
* {@inheritdoc}
*/
public function getRouteName() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getRouteObject() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getParameter($parameter_name) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getParameters() {
return new ParameterBag();
}
/**
* {@inheritdoc}
*/
public function getRawParameter($parameter_name) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getRawParameters() {
return new ParameterBag();
}
}
<?php
/**
* @file
* Contains Drupal\Core\Routing\RouteMatch.
*/
namespace Drupal\Core\Routing;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Default object representing the results of routing.
*/
class RouteMatch implements RouteMatchInterface {
/**
* The route name.
*
* @var string
*/
protected $routeName;
/**
* The route.
*
* @var \Symfony\Component\Routing\Route
*/
protected $route;
/**
* A key|value store of parameters.
*
* @var \Symfony\Component\HttpFoundation\ParameterBag
*/
protected $parameters;
/**
* A key|value store of raw parameters.
*
* @var \Symfony\Component\HttpFoundation\ParameterBag
*/
protected $rawParameters;
/**
* Constructs a RouteMatch object.
*
* @param string $route_name
* The name of the route.
* @param \Symfony\Component\Routing\Route $route
* The route.
* @param array $parameters
* The parameters array.
* @param array $raw_parameters
* The raw $parameters array.
*/
public function __construct($route_name, Route $route, array $parameters = array(), array $raw_parameters = array()) {
$this->routeName = $route_name;
$this->route = $route;
// Pre-filter parameters.
$route_params = $this->getParameterNames();
$parameters = array_intersect_key($parameters, $route_params);
$raw_parameters = array_intersect_key($raw_parameters, $route_params);
$this->parameters = new ParameterBag($parameters);
$this->rawParameters = new ParameterBag($raw_parameters);
}
/**
* Creates a RouteMatch from a request.
*
* @param Request $request
* A request object.
*
* @return \Drupal\Core\Routing\RouteMatchInterface
* A new RouteMatch object if there's a matched route for the request.
* A new NullRouteMatch object otherwise (e.g., on a 404 page or when
* invoked prior to routing).
*/
public static function createFromRequest(Request $request) {
if ($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
$raw_variables = array();
if ($raw = $request->attributes->get('_raw_variables')) {
$raw_variables = $raw->all();
}
return new static(
$request->attributes->get(RouteObjectInterface::ROUTE_NAME),
$request->attributes->get(RouteObjectInterface::ROUTE_OBJECT),
$request->attributes->all(),
$raw_variables);
}
else {
return new NullRouteMatch();
}
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
return $this->routeName;
}
/**
* {@inheritdoc}
*/
public function getRouteObject() {
return $this->route;
}
/**
* {@inheritdoc}
*/
public function getParameter($parameter_name) {
return $this->parameters->get($parameter_name);
}
/**
* {@inheritdoc}
*/
public function getParameters() {
return $this->parameters;
}
/**
* {@inheritdoc}
*/
public function getRawParameter($parameter_name) {
return $this->rawParameters->get($parameter_name);
}
/**
* {@inheritdoc}
*/
public function getRawParameters() {
return $this->rawParameters;
}
/**
* Returns the names of all parameters for the currently matched route.
*
* @return array
* Route parameter names as both the keys and values.
*/
protected function getParameterNames() {
$names = array();
if ($route = $this->getRouteObject()) {
// Variables defined in path and host patterns are route parameters.
$variables = $route->compile()->getVariables();
$names = array_combine($variables, $variables);
// Route defaults that do not start with a leading "_" are also
// parameters, even if they are not included in path or host patterns.
foreach ($route->getDefaults() as $name => $value) {
if (!isset($names[$name]) && substr($name, 0, 1) !== '_') {
$names[$name] = $name;
}
}
}
return $names;
}
}
<?php
/**
* @file
* Contains Drupal\Core\Routing\RouteMatchInterface.
*/
namespace Drupal\Core\Routing;
/**
* Provides an interface for classes representing the result of routing.
*
* Routing is the process of selecting the best matching candidate from a
* collection of routes for an incoming request. The relevant properties of a
* request include the path as well as a list of raw parameter values derived
* from the URL. If an appropriate route is found, raw parameter values will be
* upcast automatically if possible.
*
* The route match object contains useful information about the selected route
* as well as the raw and upcast parameters derived from the incoming
* request.
*/
interface RouteMatchInterface {
/**
* Returns the route name.
*
* @return string|null
* The route name. NULL if no route is matched.
*/
public function getRouteName();
/**
* Returns the route object.
*
* @return \Symfony\Component\Routing\Route|null
* The route object. NULL if no route is matched.
*/
public function getRouteObject();
/**
* Returns the value of a named route parameter.
*
* @param string $parameter_name
* The parameter name.
*
* @return mixed|null
* The parameter value. NULL if the route doesn't define the parameter or
* if the parameter value can't be determined from the request.
*/
public function getParameter($parameter_name);
/**
* Returns the bag of all route parameters.
*
* @return \Symfony\Component\HttpFoundation\ParameterBag
* The parameter bag.
*/
public function getParameters();
/**
* Returns the raw value of a named route parameter.
*
* @param string $parameter_name
* The parameter name.
*
* @return string|null
* The raw (non-upcast) parameter value. NULL if the route doesn't define
* the parameter or if the raw parameter value can't be determined from the
* request.
*/
public function getRawParameter($parameter_name);
/**
* Returns the bag of all raw route parameters.
*
* @return \Symfony\Component\HttpFoundation\ParameterBag
* The parameter bag.
*/
public function getRawParameters();
}
......@@ -9,8 +9,8 @@
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Defines a theme negotiator that deals with the active theme on ajax requests.
......@@ -45,6 +45,13 @@ class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
*/
protected $configFactory;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a new AjaxBasePageNegotiator.
*
......@@ -52,18 +59,21 @@ class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
* The CSRF token generator.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack used to retrieve the current request.
*/
public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryInterface $config_factory) {
public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryInterface $config_factory, RequestStack $request_stack) {
$this->csrfGenerator = $token_generator;
$this->configFactory = $config_factory;
$this->requestStack = $request_stack;
}
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
public function applies(RouteMatchInterface $route_match) {
// Check whether the route was configured to use the base page theme.
return ($route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT))
return ($route = $route_match->getRouteObject())
&& $route->hasOption('_theme')
&& $route->getOption('_theme') == 'ajax_base_page';
}
......@@ -71,8 +81,8 @@ public function applies(Request $request) {
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
if (($ajax_page_state = $request->request->get('ajax_page_state')) && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
public function determineActiveTheme(RouteMatchInterface $route_match) {
if (($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state')) && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
$theme = $ajax_page_state['theme'];
$token = $ajax_page_state['theme_token'];
......
......@@ -8,7 +8,7 @@
namespace Drupal\Core\Theme;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Determines the default theme of the site.
......@@ -35,14 +35,14 @@ public function __construct(ConfigFactoryInterface $config_factory) {
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
public function applies(RouteMatchInterface $route_match) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
public function determineActiveTheme(RouteMatchInterface $route_match) {
return $this->config->get('default');
}
......
......@@ -7,8 +7,7 @@
namespace Drupal\Core\Theme;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Provides a class which determines the active theme of the page.
......@@ -37,13 +36,6 @@ class ThemeNegotiator implements ThemeNegotiatorInterface {
*/
protected $sortedNegotiators;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The access checker for themes.
*
......@@ -57,9 +49,8 @@ class ThemeNegotiator implements ThemeNegotiatorInterface {
* @param \Drupal\Core\Theme\ThemeAccessCheck $theme_access
* The access checker for themes.
*/
public function __construct(ThemeAccessCheck $theme_access, RequestStack $request_stack) {
public function __construct(ThemeAccessCheck $theme_access) {
$this->themeAccess = $theme_access;
$this->requestStack = $request_stack;
}
/**
......@@ -96,37 +87,22 @@ protected function getSortedNegotiators() {
return $this->sortedNegotiators;
}
/**
* Get the current active theme.
*
* @return string
* The current active string.
*/
public function getActiveTheme() {
$request = $this->requestStack->getCurrentRequest();
if (!$request->attributes->has('_theme_active')) {
$this->determineActiveTheme($request);
}
return $request->attributes->get('_theme_active');
}
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
public function applies(RouteMatchInterface $route_match) {
return TRUE;
}