Commit 7342df62 authored by alexpott's avatar alexpott

Issue #2335661 by Wim Leers, pwolanin, dawehner, Fabianx, larowlan, catch,...

Issue #2335661 by Wim Leers, pwolanin, dawehner, Fabianx, larowlan, catch, Berdir: Outbound path & route processors must specify cacheability metadata
parent 2d35b92e
...@@ -34,8 +34,8 @@ services: ...@@ -34,8 +34,8 @@ services:
arguments: ['@request_stack'] arguments: ['@request_stack']
tags: tags:
- { name: cache.context } - { name: cache.context }
cache_context.url.host: cache_context.url.site:
class: Drupal\Core\Cache\HostCacheContext class: Drupal\Core\Cache\SiteCacheContext
arguments: ['@request_stack'] arguments: ['@request_stack']
tags: tags:
- { name: cache.context } - { name: cache.context }
...@@ -1319,7 +1319,6 @@ services: ...@@ -1319,7 +1319,6 @@ services:
- { name: twig.extension, priority: 100 } - { name: twig.extension, priority: 100 }
calls: calls:
- [setGenerators, ['@url_generator']] - [setGenerators, ['@url_generator']]
- [setLinkGenerator, ['@link_generator']]
# @todo Figure out what to do about debugging functions. # @todo Figure out what to do about debugging functions.
# @see http://drupal.org/node/1804998 # @see http://drupal.org/node/1804998
twig.extension.debug: twig.extension.debug:
......
...@@ -504,17 +504,22 @@ public static function urlGenerator() { ...@@ -504,17 +504,22 @@ public static function urlGenerator() {
* (optional) An associative array of parameter names and values. * (optional) An associative array of parameter names and values.
* @param array $options * @param array $options
* (optional) An associative array of additional options. * (optional) An associative array of additional options.
* @param bool $collect_cacheability_metadata
* (optional) Defaults to FALSE. When TRUE, both the generated URL and its
* associated cacheability metadata are returned.
* *
* @return string * @return string|\Drupal\Core\GeneratedUrl
* The generated URL for the given route. * A string containing a URL to the given path.
* When $collect_cacheability_metadata is TRUE, a GeneratedUrl object is
* returned, containing the generated URL plus cacheability metadata.
* *
* @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute()
* @see \Drupal\Core\Url * @see \Drupal\Core\Url
* @see \Drupal\Core\Url::fromRoute() * @see \Drupal\Core\Url::fromRoute()
* @see \Drupal\Core\Url::fromUri() * @see \Drupal\Core\Url::fromUri()
*/ */
public static function url($route_name, $route_parameters = array(), $options = array()) { public static function url($route_name, $route_parameters = array(), $options = array(), $collect_cacheability_metadata = FALSE) {
return static::getContainer()->get('url_generator')->generateFromRoute($route_name, $route_parameters, $options); return static::getContainer()->get('url_generator')->generateFromRoute($route_name, $route_parameters, $options, $collect_cacheability_metadata);
} }
/** /**
...@@ -537,15 +542,20 @@ public static function linkGenerator() { ...@@ -537,15 +542,20 @@ public static function linkGenerator() {
* The link text for the anchor tag. * The link text for the anchor tag.
* @param \Drupal\Core\Url $url * @param \Drupal\Core\Url $url
* The URL object used for the link. * The URL object used for the link.
* @param bool $collect_cacheability_metadata
* (optional) Defaults to FALSE. When TRUE, both the generated URL and its
* associated cacheability metadata are returned.
* *
* @return string * @return string|\Drupal\Core\GeneratedLink
* An HTML string containing a link to the given route and parameters. * An HTML string containing a link to the given route and parameters.
* When $collect_cacheability_metadata is TRUE, a GeneratedLink object is
* returned, containing the generated link plus cacheability metadata.
* *
* @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate()
* @see \Drupal\Core\Url * @see \Drupal\Core\Url
*/ */
public static function l($text, Url $url) { public static function l($text, Url $url, $collect_cacheability_metadata = FALSE) {
return static::getContainer()->get('link_generator')->generate($text, $url); return static::getContainer()->get('link_generator')->generate($text, $url, $collect_cacheability_metadata);
} }
/** /**
......
...@@ -7,9 +7,8 @@ ...@@ -7,9 +7,8 @@
namespace Drupal\Core\Access; namespace Drupal\Core\Access;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface; use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
use Drupal\Core\Access\CsrfTokenGenerator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
/** /**
...@@ -37,7 +36,7 @@ function __construct(CsrfTokenGenerator $csrf_token) { ...@@ -37,7 +36,7 @@ function __construct(CsrfTokenGenerator $csrf_token) {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function processOutbound($route_name, Route $route, array &$parameters) { public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
if ($route->hasRequirement('_csrf_token')) { if ($route->hasRequirement('_csrf_token')) {
$path = ltrim($route->getPath(), '/'); $path = ltrim($route->getPath(), '/');
// Replace the path parameters with values from the parameters array. // Replace the path parameters with values from the parameters array.
...@@ -47,6 +46,11 @@ public function processOutbound($route_name, Route $route, array &$parameters) { ...@@ -47,6 +46,11 @@ public function processOutbound($route_name, Route $route, array &$parameters) {
// Adding this to the parameters means it will get merged into the query // Adding this to the parameters means it will get merged into the query
// string when the route is compiled. // string when the route is compiled.
$parameters['token'] = $this->csrfToken->get($path); $parameters['token'] = $this->csrfToken->get($path);
if ($cacheable_metadata) {
// Tokens are per user and per session, so not cacheable.
// @todo Improve in https://www.drupal.org/node/2351015.
$cacheable_metadata->setCacheMaxAge(0);
}
} }
} }
......
<?php
/**
* @file
* Contains \Drupal\Core\Cache\HostCacheContext.
*/
namespace Drupal\Core\Cache;
/**
* Defines the HostCacheContext service, for "per host" caching.
*
* A "host" is defined as the combination of URI scheme, domain name and port.
*
* @see Symfony\Component\HttpFoundation::getSchemeAndHttpHost()
*/
class HostCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Host');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost();
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\SiteCacheContext.
*/
namespace Drupal\Core\Cache;
/**
* Defines the SiteCacheContext service, for "per site" caching.
*
* A "site" is defined as the combination of URI scheme, domain name, port and
* base path. It allows for varying between the *same* site being accessed via
* different entry points. (Different sites in a multisite setup have separate
* databases.) For example: http://example.com and http://www.example.com.
*
* @see \Symfony\Component\HttpFoundation\Request::getSchemeAndHttpHost()
* @see \Symfony\Component\HttpFoundation\Request::getBaseUrl()
*/
class SiteCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Site');
}
/**
* {@inheritdoc}
*/
public function getContext() {
$request = $this->requestStack->getCurrentRequest();
return $request->getSchemeAndHttpHost() . $request->getBaseUrl();
}
}
<?php
/**
* @file
* Contains \Drupal\Core\GeneratedLink.
*/
namespace Drupal\Core;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Used to return generated links, along with associated cacheability metadata.
*
* Note: not to be confused with \Drupal\Core\Link, which is for passing around
* ungenerated links (typically link text + route name + route parameters).
*/
class GeneratedLink extends CacheableMetadata {
/**
* The HTML string value containing a link.
*
* @var string
*/
protected $generatedLink = '';
/**
* Gets the generated link.
*
* @return string
*/
public function getGeneratedLink() {
return $this->generatedLink ;
}
/**
* Sets the generated link.
*
* @param string $generated_link
* The generated link.
*
* @return $this
*/
public function setGeneratedLink($generated_link) {
$this->generatedLink = $generated_link;
return $this;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\GeneratedUrl.
*/
namespace Drupal\Core;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Used to return generated URLs, along with associated cacheability metadata.
*
* Note: not to be confused with \Drupal\Core\Url, which is for passing around
* ungenerated URLs (typically route name + route parameters).
*/
class GeneratedUrl extends CacheableMetadata {
/**
* The string value of the URL.
*
* @var string
*/
protected $generatedUrl = '';
/**
* Gets the generated URL.
*
* @return string
*/
public function getGeneratedUrl() {
return $this->generatedUrl ;
}
/**
* Sets the generated URL.
*
* @param string $generated_url
* The generated URL.
*
* @return $this
*/
public function setGeneratedUrl($generated_url) {
$this->generatedUrl = $generated_url;
return $this;
}
}
...@@ -121,9 +121,18 @@ public function setUrl(Url $url) { ...@@ -121,9 +121,18 @@ public function setUrl(Url $url) {
/** /**
* Generates the HTML for this Link object. * Generates the HTML for this Link object.
*
* @param bool $collect_cacheability_metadata
* (optional) Defaults to FALSE. When TRUE, both the generated link and its
* associated cacheability metadata are returned.
*
* @return string|\Drupal\Core\GeneratedLink
* The link HTML markup.
* When $collect_cacheability_metadata is TRUE, a GeneratedLink object is
* returned, containing the generated link plus cacheability metadata.
*/ */
public function toString() { public function toString($collect_cacheability_metadata = FALSE) {
return $this->getLinkGenerator()->generateFromLink($this); return $this->getLinkGenerator()->generateFromLink($this, $collect_cacheability_metadata);
} }
} }
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
namespace Drupal\Core\PathProcessor; namespace Drupal\Core\PathProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/** /**
* Defines an interface for classes that process the outbound path. * Defines an interface for classes that process the outbound path.
...@@ -20,17 +20,17 @@ interface OutboundPathProcessorInterface { ...@@ -20,17 +20,17 @@ interface OutboundPathProcessorInterface {
* *
* @param string $path * @param string $path
* The path to process. * The path to process.
*
* @param array $options * @param array $options
* An array of options such as would be passed to the generator's * An array of options such as would be passed to the generator's
* generateFromPath() method. * generateFromPath() method.
*
* @param \Symfony\Component\HttpFoundation\Request $request * @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request. * The HttpRequest object representing the current request.
* @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
* (optional) Object to collect path processors' cacheability.
* *
* @return * @return
* The processed path. * The processed path.
*/ */
public function processOutbound($path, &$options = array(), Request $request = NULL); public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL);
} }
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\Core\PathProcessor; namespace Drupal\Core\PathProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Path\AliasManagerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
...@@ -43,7 +44,7 @@ public function processInbound($path, Request $request) { ...@@ -43,7 +44,7 @@ public function processInbound($path, Request $request) {
/** /**
* Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound(). * Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
*/ */
public function processOutbound($path, &$options = array(), Request $request = NULL) { public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL) {
if (empty($options['alias'])) { if (empty($options['alias'])) {
$langcode = isset($options['language']) ? $options['language']->getId() : NULL; $langcode = isset($options['language']) ? $options['language']->getId() : NULL;
$path = $this->aliasManager->getAliasByPath($path, $langcode); $path = $this->aliasManager->getAliasByPath($path, $langcode);
......
...@@ -7,11 +7,14 @@ ...@@ -7,11 +7,14 @@
namespace Drupal\Core\PathProcessor; namespace Drupal\Core\PathProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
* Processes the inbound path by resolving it to the front page if empty. * Processes the inbound path by resolving it to the front page if empty.
*
* @todo - remove ::processOutbound() when we remove UrlGenerator::fromPath().
*/ */
class PathProcessorFront implements InboundPathProcessorInterface, OutboundPathProcessorInterface { class PathProcessorFront implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
...@@ -45,7 +48,7 @@ public function processInbound($path, Request $request) { ...@@ -45,7 +48,7 @@ public function processInbound($path, Request $request) {
/** /**
* Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound(). * Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
*/ */
public function processOutbound($path, &$options = array(), Request $request = NULL) { public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL) {
// The special path '<front>' links to the default front page. // The special path '<front>' links to the default front page.
if ($path == '<front>') { if ($path == '<front>') {
$path = ''; $path = '';
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\Core\PathProcessor; namespace Drupal\Core\PathProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
...@@ -107,10 +108,10 @@ public function addOutbound(OutboundPathProcessorInterface $processor, $priority ...@@ -107,10 +108,10 @@ public function addOutbound(OutboundPathProcessorInterface $processor, $priority
/** /**
* Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound(). * Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
*/ */
public function processOutbound($path, &$options = array(), Request $request = NULL) { public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL) {
$processors = $this->getOutbound(); $processors = $this->getOutbound();
foreach ($processors as $processor) { foreach ($processors as $processor) {
$path = $processor->processOutbound($path, $options, $request); $path = $processor->processOutbound($path, $options, $request, $cacheable_metadata);
} }
return $path; return $path;
} }
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Html as HtmlUtility; use Drupal\Component\Utility\Html as HtmlUtility;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Url as CoreUrl;
/** /**
* Provides a link render element. * Provides a link render element.
...@@ -75,9 +77,14 @@ public static function preRenderLink($element) { ...@@ -75,9 +77,14 @@ public static function preRenderLink($element) {
$element = static::preRenderAjaxForm($element); $element = static::preRenderAjaxForm($element);
} }
if (!empty($element['#url'])) { if (!empty($element['#url']) && $element['#url'] instanceof CoreUrl) {
$options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']); $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']);
$element['#markup'] = \Drupal::l($element['#title'], $element['#url']->setOptions($options)); /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
$link_generator = \Drupal::service('link_generator');
$generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options), TRUE);
$element['#markup'] = $generated_link->getGeneratedLink();
$generated_link->merge(CacheableMetadata::createFromRenderArray($element))
->applyTo($element);
} }
return $element; return $element;
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\Core\RouteProcessor; namespace Drupal\Core\RouteProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
/** /**
...@@ -24,10 +25,12 @@ interface OutboundRouteProcessorInterface { ...@@ -24,10 +25,12 @@ interface OutboundRouteProcessorInterface {
* @param array $parameters * @param array $parameters
* An array of parameters to be passed to the route compiler. Passed by * An array of parameters to be passed to the route compiler. Passed by
* reference. * reference.
* @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
* (optional) Object to collect route processors' cacheability.
* *
* @return * @return
* The processed path. * The processed path.
*/ */
public function processOutbound($route_name, Route $route, array &$parameters); public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL);
} }
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\Core\RouteProcessor; namespace Drupal\Core\RouteProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
...@@ -35,7 +36,7 @@ public function __construct(RouteMatchInterface $route_match) { ...@@ -35,7 +36,7 @@ public function __construct(RouteMatchInterface $route_match) {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function processOutbound($route_name, Route $route, array &$parameters) { public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
if ($route_name === '<current>') { if ($route_name === '<current>') {
if ($current_route = $this->routeMatch->getRouteObject()) { if ($current_route = $this->routeMatch->getRouteObject()) {
$route->setPath($current_route->getPath()); $route->setPath($current_route->getPath());
...@@ -43,6 +44,9 @@ public function processOutbound($route_name, Route $route, array &$parameters) { ...@@ -43,6 +44,9 @@ public function processOutbound($route_name, Route $route, array &$parameters) {
$route->setOptions($current_route->getOptions()); $route->setOptions($current_route->getOptions());
$route->setDefaults($current_route->getDefaults()); $route->setDefaults($current_route->getDefaults());
$parameters = array_merge($parameters, $this->routeMatch->getRawParameters()->all()); $parameters = array_merge($parameters, $this->routeMatch->getRawParameters()->all());
if ($cacheable_metadata) {
$cacheable_metadata->addCacheContexts(['route']);
}
} }
else { else {
// If we have no current route match available, point to the frontpage. // If we have no current route match available, point to the frontpage.
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\Core\RouteProcessor; namespace Drupal\Core\RouteProcessor;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
/** /**
...@@ -50,10 +51,10 @@ public function addOutbound(OutboundRouteProcessorInterface $processor, $priorit ...@@ -50,10 +51,10 @@ public function addOutbound(OutboundRouteProcessorInterface $processor, $priorit
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function processOutbound($route_name, Route $route, array &$parameters) { public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
$processors = $this->getOutbound(); $processors = $this->getOutbound();
foreach ($processors as $processor) { foreach ($processors as $processor) {
$processor->processOutbound($route_name, $route, $parameters); $processor->processOutbound($route_name, $route, $parameters, $cacheable_metadata);
} }
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\Core\Routing; namespace Drupal\Core\Routing;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RequestContext as SymfonyRequestContext; use Symfony\Component\Routing\RequestContext as SymfonyRequestContext;
use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Exception\RouteNotFoundException;
...@@ -50,7 +51,7 @@ protected function getRoute($name) { ...@@ -50,7 +51,7 @@ protected function getRoute($name) {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function processRoute($name, Route $route, array &$parameters) { protected function processRoute($name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
} }
/** /**
...@@ -75,7 +76,7 @@ public function getContext() { ...@@ -75,7 +76,7 @@ public function getContext() {
/** /**
* Overrides Drupal\Core\Routing\UrlGenerator::processPath(). * Overrides Drupal\Core\Routing\UrlGenerator::processPath().
*/ */
protected function processPath($path, &$options = array()) { protected function processPath($path, &$options = array(), CacheableMetadata $cacheable_metadata = NULL) {
return $path; return $path;
} }
} }
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
namespace Drupal\Core\Routing; namespace Drupal\Core\Routing;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\GeneratedUrl;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RequestContext as SymfonyRequestContext; use Symfony\Component\Routing\RequestContext as SymfonyRequestContext;
use Symfony\Component\Routing\Route as SymfonyRoute; use Symfony\Component\Routing\Route as SymfonyRoute;
...@@ -275,11 +277,13 @@ public function generate($name, $parameters = array(), $absolute = FALSE) { ...@@ -275,11 +277,13 @@ public function generate($name, $parameters = array(), $absolute = FALSE) {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function generateFromRoute($name, $parameters = array(), $options = array()) { public function generateFromRoute($name, $parameters = array(), $options = array(), $collect_cacheability_metadata = FALSE) {
$generated_url = $collect_cacheability_metadata ? new GeneratedUrl() : NULL;
$options += array('prefix' => ''); $options += array('prefix' => '');
$route = $this->getRoute($name); $route = $this->getRoute($name);
$name = $this->getRouteDebugMessage($name); $name = $this->getRouteDebugMessage($name);
$this->processRoute($name, $route, $parameters); $this->processRoute($name, $route, $parameters, $generated_url);
$query_params = []; $query_params = [];
// Symfony adds any parameters that are not path slugs as query strings. // Symfony adds any parameters that are not path slugs as query strings.
...@@ -288,7 +292,7 @@ public function generateFromRoute($name, $parameters = array(), $options = array ...@@ -288,7 +292,7 @@ public function generateFromRoute($name, $parameters = array(), $options = array
} }