namespace Drupal\Core\Routing;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\State\StateInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RouteCollection;
-use \Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Connection;
/**
* A Route Provider front-end for all Drupal-stored routes.
*/
protected $pathProcessor;
+ /**
+ * The language manager.
+ *
+ * @var \Drupal\Core\Language\LanguageManagerInterface
+ */
+ protected $languageManager;
+
/**
* Cache ID prefix used to load routes.
*/
* The cache tag invalidator.
* @param string $table
* (Optional) The table in the database to use for matching. Defaults to 'router'
+ * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+ * (Optional) The language manager.
*/
- public function __construct(Connection $connection, StateInterface $state, CurrentPathStack $current_path, CacheBackendInterface $cache_backend, InboundPathProcessorInterface $path_processor, CacheTagsInvalidatorInterface $cache_tag_invalidator, $table = 'router') {
+ public function __construct(Connection $connection, StateInterface $state, CurrentPathStack $current_path, CacheBackendInterface $cache_backend, InboundPathProcessorInterface $path_processor, CacheTagsInvalidatorInterface $cache_tag_invalidator, $table = 'router', LanguageManagerInterface $language_manager = NULL) {
$this->connection = $connection;
$this->state = $state;
$this->currentPath = $current_path;
$this->cacheTagInvalidator = $cache_tag_invalidator;
$this->pathProcessor = $path_processor;
$this->tableName = $table;
+ $this->languageManager = $language_manager ?: \Drupal::languageManager();
}
/**
* very large route sets to be filtered down to likely candidates, which
* may then be filtered in memory more completely.
*
- * @param Request $request
+ * @param \Symfony\Component\HttpFoundation\Request $request
* A request against which to match.
*
- * @return \Symfony\Component\Routing\RouteCollection with all urls that
- * could potentially match $request. Empty collection if nothing can
- * match. The collection will be sorted from highest to lowest fit (match
- * of path parts) and then in ascending order by route name for routes
- * with the same fit.
+ * @return \Symfony\Component\Routing\RouteCollection
+ * RouteCollection with all urls that could potentially match $request.
+ * Empty collection if nothing can match. The collection will be sorted from
+ * highest to lowest fit (match of path parts) and then in ascending order
+ * by route name for routes with the same fit.
*/
public function getRouteCollectionForRequest(Request $request) {
// Cache both the system path as well as route parameters and matching
// routes.
- $cid = 'route:' . $request->getPathInfo() . ':' . $request->getQueryString();
+ $cid = $this->getRouteCollectionCacheId($request);
if ($cached = $this->cache->get($cid)) {
$this->currentPath->setPath($cached->data['path'], $request);
$request->query->replace($cached->data['query']);
// have a case-insensitive match from the incoming path to the lower case
// pattern outlines from \Drupal\Core\Routing\RouteCompiler::compile().
// @see \Drupal\Core\Routing\CompiledRoute::__construct()
- $parts = preg_split('@/+@', Unicode::strtolower($path), NULL, PREG_SPLIT_NO_EMPTY);
+ $parts = preg_split('@/+@', mb_strtolower($path), NULL, PREG_SPLIT_NO_EMPTY);
$collection = new RouteCollection();
// dump the route pattern without those optional parts.
try {
$routes = $this->connection->query("SELECT name, route, fit FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN ( :patterns[] ) AND number_parts >= :count_parts", [
- ':patterns[]' => $ancestors, ':count_parts' => count($parts),
+ ':patterns[]' => $ancestors,
+ ':count_parts' => count($parts),
])
->fetchAll(\PDO::FETCH_ASSOC);
}
* {@inheritdoc}
*/
public function reset() {
- $this->routes = [];
+ $this->routes = [];
$this->serializedRoutes = [];
$this->cacheTagInvalidator->invalidateTags(['routes']);
}
return $this->connection->query("SELECT COUNT(*) FROM {" . $this->connection->escapeTable($this->tableName) . "}")->fetchField();
}
+ /**
+ * Returns the cache ID for the route collection cache.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ *
+ * @return string
+ * The cache ID.
+ */
+ protected function getRouteCollectionCacheId(Request $request) {
+ // Include the current language code in the cache identifier as
+ // the language information can be elsewhere than in the path, for example
+ // based on the domain.
+ $language_part = $this->getCurrentLanguageCacheIdPart();
+ return 'route:' . $language_part . ':' . $request->getPathInfo() . ':' . $request->getQueryString();
+ }
+
+ /**
+ * Returns the language identifier for the route collection cache.
+ *
+ * @return string
+ * The language identifier.
+ */
+ protected function getCurrentLanguageCacheIdPart() {
+ // This must be in sync with the language logic in
+ // \Drupal\Core\PathProcessor\PathProcessorAlias::processInbound() and
+ // \Drupal\Core\Path\AliasManager::getPathByAlias().
+ // @todo Update this if necessary in https://www.drupal.org/node/1125428.
+ return $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
+ }
+
}