Commit 09702f84 authored by catch's avatar catch

Issue #2233623 by Berdir, slashrsm, xjm: Fixed Merge AliasManagerCacheDecorator into AliasManager.

parent d0d3e533
...@@ -184,7 +184,7 @@ services: ...@@ -184,7 +184,7 @@ services:
arguments: [path_alias_whitelist, '@cache.default', '@lock', '@state', '@path.alias_storage'] arguments: [path_alias_whitelist, '@cache.default', '@lock', '@state', '@path.alias_storage']
path.alias_manager: path.alias_manager:
class: Drupal\Core\Path\AliasManager class: Drupal\Core\Path\AliasManager
arguments: ['@path.alias_storage', '@path.alias_whitelist', '@language_manager'] arguments: ['@path.alias_storage', '@path.alias_whitelist', '@language_manager', '@cache.data']
http_client: http_client:
class: Drupal\Core\Http\Client class: Drupal\Core\Http\Client
tags: tags:
...@@ -393,9 +393,6 @@ services: ...@@ -393,9 +393,6 @@ services:
arguments: ['@router.builder', '@lock'] arguments: ['@router.builder', '@lock']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
path.alias_manager.cached:
class: Drupal\Core\CacheDecorator\AliasManagerCacheDecorator
arguments: ['@path.alias_manager', '@cache.data']
path.alias_storage: path.alias_storage:
class: Drupal\Core\Path\AliasStorage class: Drupal\Core\Path\AliasStorage
arguments: ['@database', '@module_handler'] arguments: ['@database', '@module_handler']
...@@ -588,7 +585,7 @@ services: ...@@ -588,7 +585,7 @@ services:
class: Drupal\Core\EventSubscriber\PathSubscriber class: Drupal\Core\EventSubscriber\PathSubscriber
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
arguments: ['@path.alias_manager.cached', '@path_processor_manager'] arguments: ['@path.alias_manager', '@path_processor_manager']
legacy_request_subscriber: legacy_request_subscriber:
class: Drupal\Core\EventSubscriber\LegacyRequestSubscriber class: Drupal\Core\EventSubscriber\LegacyRequestSubscriber
tags: tags:
......
...@@ -887,7 +887,7 @@ function l($text, $path, array $options = array()) { ...@@ -887,7 +887,7 @@ function l($text, $path, array $options = array()) {
// Add a "data-drupal-link-system-path" attribute to let the // Add a "data-drupal-link-system-path" attribute to let the
// drupal.active-link library know the path in a standardized manner. // drupal.active-link library know the path in a standardized manner.
if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) { if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
$variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager.cached')->getPathByAlias($path); $variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager')->getPathByAlias($path);
} }
} }
......
...@@ -1233,7 +1233,7 @@ function template_preprocess_links(&$variables) { ...@@ -1233,7 +1233,7 @@ function template_preprocess_links(&$variables) {
// Add a "data-drupal-link-system-path" attribute to let the // Add a "data-drupal-link-system-path" attribute to let the
// drupal.active-link library know the path in a standardized manner. // drupal.active-link library know the path in a standardized manner.
$li_attributes['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager.cached')->getPathByAlias($path); $li_attributes['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager')->getPathByAlias($path);
} }
$item['link'] = $link_element; $item['link'] = $link_element;
......
<?php
/**
* @file
* Contains Drupal\Core\CacheDecorator\AliasManagerCacheDecorator.
*/
namespace Drupal\Core\CacheDecorator;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Path\AliasManagerInterface;
/**
* Class used by the PathSubscriber to get the system path and cache path lookups.
*/
class AliasManagerCacheDecorator implements CacheDecoratorInterface, AliasManagerInterface {
/**
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* @var \Drupal\Core\Cache\CacheBackendInterface;
*/
protected $cache;
/**
* The cache key to use when caching system paths.
*
* @var string
*/
protected $cacheKey;
/**
* Holds an array of previously cached paths based on a request path.
*
* @var array
*/
protected $preloadedPathLookups = array();
/**
* Whether the cache needs to be written.
*
* @var boolean
*/
protected $cacheNeedsWriting = TRUE;
/**
* Constructs a \Drupal\Core\CacheDecorator\AliasManagerCacheDecorator.
*/
public function __construct(AliasManagerInterface $alias_manager, CacheBackendInterface $cache) {
$this->aliasManager = $alias_manager;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
public function setCacheKey($key) {
$this->cacheKey = $key;
}
/**
* {@inheritdoc}
*
* Cache an array of the system paths available on each page. We assume
* that aliases will be needed for the majority of these paths during
* subsequent requests, and load them in a single query during path alias
* lookup.
*/
public function writeCache() {
$path_lookups = $this->getPathLookups();
// Check if the system paths for this page were loaded from cache in this
// request to avoid writing to cache on every request.
if ($this->cacheNeedsWriting && !empty($path_lookups) && !empty($this->cacheKey)) {
// Set the path cache to expire in 24 hours.
$expire = REQUEST_TIME + (60 * 60 * 24);
$this->cache->set($this->cacheKey, $path_lookups, $expire);
}
}
/**
* {@inheritdoc}
*/
public function getPathByAlias($alias, $langcode = NULL) {
$path = $this->aliasManager->getPathByAlias($alias, $langcode);
// We need to pass on the list of previously cached system paths for this
// key to the alias manager for use in subsequent lookups.
$cached = $this->cache->get($path);
$cached_paths = array();
if ($cached) {
$cached_paths = $cached->data;
$this->cacheNeedsWriting = FALSE;
}
$this->preloadPathLookups($cached_paths);
return $path;
}
/**
* {@inheritdoc}
*/
public function getAliasByPath($path, $langcode = NULL) {
return $this->aliasManager->getAliasByPath($path, $langcode);
}
/**
* {@inheritdoc}
*/
public function getPathLookups() {
return $this->aliasManager->getPathLookups();
}
/**
* {@inheritdoc}
*/
public function preloadPathLookups(array $path_list) {
$this->aliasManager->preloadPathLookups($path_list);
}
/**
* {@inheritdoc}
*/
public function cacheClear($source = NULL) {
$this->cache->delete($this->cacheKey);
$this->aliasManager->cacheClear($source);
}
}
...@@ -7,10 +7,13 @@ ...@@ -7,10 +7,13 @@
namespace Drupal\Core\Path; namespace Drupal\Core\Path;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\CacheDecorator\CacheDecoratorInterface;
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager; use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Language\LanguageManagerInterface;
class AliasManager implements AliasManagerInterface { class AliasManager implements AliasManagerInterface, CacheDecoratorInterface {
/** /**
* The alias storage service. * The alias storage service.
...@@ -19,6 +22,27 @@ class AliasManager implements AliasManagerInterface { ...@@ -19,6 +22,27 @@ class AliasManager implements AliasManagerInterface {
*/ */
protected $storage; protected $storage;
/**
* Cache backend service.
*
* @var \Drupal\Core\Cache\CacheBackendInterface;
*/
protected $cache;
/**
* The cache key to use when caching paths.
*
* @var string
*/
protected $cacheKey;
/**
* Whether the cache needs to be written.
*
* @var boolean
*/
protected $cacheNeedsWriting = FALSE;
/** /**
* Language manager for retrieving the default langcode when none is specified. * Language manager for retrieving the default langcode when none is specified.
* *
...@@ -64,12 +88,12 @@ class AliasManager implements AliasManagerInterface { ...@@ -64,12 +88,12 @@ class AliasManager implements AliasManagerInterface {
/** /**
* Holds an array of previously looked up paths for the current request path. * Holds an array of previously looked up paths for the current request path.
* *
* This will only ever get populated if the alias manager is being used in * This will only get populated if a cache key has been set, which for example
* the context of a request. * happens if the alias manager is used in the context of a request.
* *
* @var array * @var array
*/ */
protected $preloadedPathLookups = array(); protected $preloadedPathLookups = FALSE;
/** /**
* Constructs an AliasManager. * Constructs an AliasManager.
...@@ -78,13 +102,52 @@ class AliasManager implements AliasManagerInterface { ...@@ -78,13 +102,52 @@ class AliasManager implements AliasManagerInterface {
* The alias storage service. * The alias storage service.
* @param \Drupal\Core\Path\AliasWhitelistInterface $whitelist * @param \Drupal\Core\Path\AliasWhitelistInterface $whitelist
* The whitelist implementation to use. * The whitelist implementation to use.
* @param \Drupal\Core\Language\LanguageManager $language_manager * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager. * The language manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* Cache backend.
*/ */
public function __construct(AliasStorageInterface $storage, AliasWhitelistInterface $whitelist, LanguageManager $language_manager) { public function __construct(AliasStorageInterface $storage, AliasWhitelistInterface $whitelist, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
$this->storage = $storage; $this->storage = $storage;
$this->languageManager = $language_manager; $this->languageManager = $language_manager;
$this->whitelist = $whitelist; $this->whitelist = $whitelist;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
public function setCacheKey($key) {
// Prefix the cache key to avoid clashes with other caches.
$this->cacheKey = 'preload-paths:' . $key;
}
/**
* {@inheritdoc}
*
* Cache an array of the paths available on each page. We assume that aliases
* will be needed for the majority of these paths during subsequent requests,
* and load them in a single query during path alias lookup.
*/
public function writeCache() {
// Check if the paths for this page were loaded from cache in this request
// to avoid writing to cache on every request.
if ($this->cacheNeedsWriting && !empty($this->cacheKey)) {
// Start with the preloaded path lookups, so that cached entries for other
// languages will not be lost.
$path_lookups_lookups = $this->preloadedPathLookups ?: array();
foreach ($this->lookupMap as $langcode => $lookups) {
$path_lookups[$langcode] = array_keys($lookups);
if (!empty($this->noAlias[$langcode])) {
$path_lookups[$langcode] = array_merge($path_lookups[$langcode], array_keys($this->noAlias[$langcode]));
}
}
if (!empty($path_lookups)) {
$twenty_four_hours_four_hours = 60 * 60 * 24;
$this->cache->set($this->cacheKey, $path_lookups, REQUEST_TIME + $twenty_four_hours);
}
}
} }
/** /**
...@@ -110,12 +173,14 @@ public function getPathByAlias($alias, $langcode = NULL) { ...@@ -110,12 +173,14 @@ public function getPathByAlias($alias, $langcode = NULL) {
// Look for path in storage. // Look for path in storage.
if ($path = $this->storage->lookupPathSource($alias, $langcode)) { if ($path = $this->storage->lookupPathSource($alias, $langcode)) {
$this->lookupMap[$langcode][$path] = $alias; $this->lookupMap[$langcode][$path] = $alias;
$this->cacheNeedsWriting = TRUE;
return $path; return $path;
} }
// We can't record anything into $this->lookupMap because we didn't find any // We can't record anything into $this->lookupMap because we didn't find any
// paths for this alias. Thus cache to $this->noPath. // paths for this alias. Thus cache to $this->noPath.
$this->noPath[$langcode][$alias] = TRUE; $this->noPath[$langcode][$alias] = TRUE;
return $alias; return $alias;
} }
...@@ -141,11 +206,21 @@ public function getAliasByPath($path, $langcode = NULL) { ...@@ -141,11 +206,21 @@ public function getAliasByPath($path, $langcode = NULL) {
if (empty($this->langcodePreloaded[$langcode])) { if (empty($this->langcodePreloaded[$langcode])) {
$this->langcodePreloaded[$langcode] = TRUE; $this->langcodePreloaded[$langcode] = TRUE;
$this->lookupMap[$langcode] = array(); $this->lookupMap[$langcode] = array();
// Load the cached paths that should be used for preloading. This only
// happens if a cache key has been set.
if ($this->preloadedPathLookups === FALSE) {
$this->preloadedPathLookups = array();
if ($this->cacheKey && $cached = $this->cache->get($this->cacheKey)) {
$this->preloadedPathLookups = $cached->data;
}
}
// Load paths from cache. // Load paths from cache.
if (!empty($this->preloadedPathLookups)) { if (!empty($this->preloadedPathLookups[$langcode])) {
$this->lookupMap[$langcode] = $this->storage->preloadPathAlias($this->preloadedPathLookups, $langcode); $this->lookupMap[$langcode] = $this->storage->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode);
// Keep a record of paths with no alias to avoid querying twice. // Keep a record of paths with no alias to avoid querying twice.
$this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups, array_keys($this->lookupMap[$langcode]))); $this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode])));
} }
} }
...@@ -162,12 +237,14 @@ public function getAliasByPath($path, $langcode = NULL) { ...@@ -162,12 +237,14 @@ public function getAliasByPath($path, $langcode = NULL) {
// Try to load alias from storage. // Try to load alias from storage.
if ($alias = $this->storage->lookupPathAlias($path, $langcode)) { if ($alias = $this->storage->lookupPathAlias($path, $langcode)) {
$this->lookupMap[$langcode][$path] = $alias; $this->lookupMap[$langcode][$path] = $alias;
$this->cacheNeedsWriting = TRUE;
return $alias; return $alias;
} }
// We can't record anything into $this->lookupMap because we didn't find any // We can't record anything into $this->lookupMap because we didn't find any
// aliases for this path. Thus cache to $this->noAlias. // aliases for this path. Thus cache to $this->noAlias.
$this->noAlias[$langcode][$path] = TRUE; $this->noAlias[$langcode][$path] = TRUE;
$this->cacheNeedsWriting = TRUE;
return $path; return $path;
} }
...@@ -187,41 +264,24 @@ public function cacheClear($source = NULL) { ...@@ -187,41 +264,24 @@ public function cacheClear($source = NULL) {
$this->noAlias = array(); $this->noAlias = array();
$this->langcodePreloaded = array(); $this->langcodePreloaded = array();
$this->preloadedPathLookups = array(); $this->preloadedPathLookups = array();
$this->cache->delete($this->cacheKey);
$this->pathAliasWhitelistRebuild($source); $this->pathAliasWhitelistRebuild($source);
} }
/**
* Implements \Drupal\Core\Path\AliasManagerInterface::getPathLookups().
*/
public function getPathLookups() {
$current = current($this->lookupMap);
if ($current) {
return array_keys($current);
}
return array();
}
/**
* Implements \Drupal\Core\Path\AliasManagerInterface::preloadPathLookups().
*/
public function preloadPathLookups(array $path_list) {
$this->preloadedPathLookups = $path_list;
}
/** /**
* Rebuild the path alias white list. * Rebuild the path alias white list.
* *
* @param $source * @param string $path
* An optional system path for which an alias is being inserted. * An optional path for which an alias is being inserted.
* *
* @return * @return
* An array containing a white list of path aliases. * An array containing a white list of path aliases.
*/ */
protected function pathAliasWhitelistRebuild($source = NULL) { protected function pathAliasWhitelistRebuild($path = NULL) {
// When paths are inserted, only rebuild the whitelist if the system path // When paths are inserted, only rebuild the whitelist if the path has a top
// has a top level component which is not already in the whitelist. // level component which is not already in the whitelist.
if (!empty($source)) { if (!empty($path)) {
if ($this->whitelist->get(strtok($source, '/'))) { if ($this->whitelist->get(strtok($path, '/'))) {
return; return;
} }
} }
......
...@@ -35,23 +35,6 @@ public function getPathByAlias($alias, $langcode = NULL); ...@@ -35,23 +35,6 @@ public function getPathByAlias($alias, $langcode = NULL);
*/ */
public function getAliasByPath($path, $langcode = NULL); public function getAliasByPath($path, $langcode = NULL);
/**
* Returns an array of system paths that have been looked up.
*
* @return array
* An array of all system paths that have been looked up during the current
* request.
*/
public function getPathLookups();
/**
* Preload a set of paths for bulk alias lookups.
*
* @param $path_list
* An array of system paths.
*/
public function preloadPathLookups(array $path_list);
/** /**
* Clear internal caches in alias manager. * Clear internal caches in alias manager.
* *
......
...@@ -210,7 +210,7 @@ public function massageFormValues(array $values, array $form, array &$form_state ...@@ -210,7 +210,7 @@ public function massageFormValues(array $values, array $form, array &$form_state
// If internal links are supported, look up whether the given value is // If internal links are supported, look up whether the given value is
// a path alias and store the system path instead. // a path alias and store the system path instead.
if ($this->supportsInternalLinks() && !UrlHelper::isExternal($value['url'])) { if ($this->supportsInternalLinks() && !UrlHelper::isExternal($value['url'])) {
$parsed_url['path'] = \Drupal::service('path.alias_manager.cached')->getPathByAlias($parsed_url['path']); $parsed_url['path'] = \Drupal::service('path.alias_manager')->getPathByAlias($parsed_url['path']);
} }
$url = Url::createFromPath($parsed_url['path']); $url = Url::createFromPath($parsed_url['path']);
......
...@@ -62,7 +62,7 @@ public function __construct(MenuLinkStorageInterface $menu_link_storage, AliasMa ...@@ -62,7 +62,7 @@ public function __construct(MenuLinkStorageInterface $menu_link_storage, AliasMa
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('entity.manager')->getStorage('menu_link'), $container->get('entity.manager')->getStorage('menu_link'),
$container->get('path.alias_manager.cached'), $container->get('path.alias_manager'),
$container->get('url_generator') $container->get('url_generator')
); );
} }
......
...@@ -58,12 +58,12 @@ function testPathCache() { ...@@ -58,12 +58,12 @@ function testPathCache() {
\Drupal::cache('data')->deleteAll(); \Drupal::cache('data')->deleteAll();
// Make sure the path is not converted to the alias. // Make sure the path is not converted to the alias.
$this->drupalGet($edit['source'], array('alias' => TRUE)); $this->drupalGet($edit['source'], array('alias' => TRUE));
$this->assertTrue(\Drupal::cache('data')->get($edit['source']), 'Cache entry was created.'); $this->assertTrue(\Drupal::cache('data')->get('preload-paths:' . $edit['source']), 'Cache entry was created.');
// Visit the alias for the node and confirm a cache entry is created. // Visit the alias for the node and confirm a cache entry is created.
\Drupal::cache('data')->deleteAll(); \Drupal::cache('data')->deleteAll();
$this->drupalGet($edit['alias']); $this->drupalGet($edit['alias']);
$this->assertTrue(\Drupal::cache('data')->get($edit['source']), 'Cache entry was created.'); $this->assertTrue(\Drupal::cache('data')->get('preload-paths:' . $edit['source']), 'Cache entry was created.');
} }
/** /**
......
...@@ -84,7 +84,7 @@ function testLookupPath() { ...@@ -84,7 +84,7 @@ function testLookupPath() {
$this->fixtures->createTables($connection); $this->fixtures->createTables($connection);
//Create AliasManager and Path object. //Create AliasManager and Path object.
$aliasManager = $this->container->get('path.alias_manager.cached'); $aliasManager = $this->container->get('path.alias_manager');
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler')); $aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
// Test the situation where the source is the same for multiple aliases. // Test the situation where the source is the same for multiple aliases.
...@@ -106,7 +106,7 @@ function testLookupPath() { ...@@ -106,7 +106,7 @@ function testLookupPath() {
); );
$aliasStorage->save($path['source'], $path['alias'], $path['langcode']); $aliasStorage->save($path['source'], $path['alias'], $path['langcode']);
// Hook that clears cache is not executed with unit tests. // Hook that clears cache is not executed with unit tests.
\Drupal::service('path.alias_manager.cached')->cacheClear(); \Drupal::service('path.alias_manager')->cacheClear();
$this->assertEqual($aliasManager->getAliasByPath($path['source']), $path['alias'], 'English alias overrides language-neutral alias.'); $this->assertEqual($aliasManager->getAliasByPath($path['source']), $path['alias'], 'English alias overrides language-neutral alias.');
$this->assertEqual($aliasManager->getPathByAlias($path['alias']), $path['source'], 'English source overrides language-neutral source.'); $this->assertEqual($aliasManager->getPathByAlias($path['alias']), $path['source'], 'English source overrides language-neutral source.');
...@@ -170,7 +170,7 @@ function testWhitelist() { ...@@ -170,7 +170,7 @@ function testWhitelist() {
// Create AliasManager and Path object. // Create AliasManager and Path object.
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler')); $aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
$whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $aliasStorage); $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $aliasStorage);
$aliasManager = new AliasManager($aliasStorage, $whitelist, $this->container->get('language_manager')); $aliasManager = new AliasManager($aliasStorage, $whitelist, $this->container->get('language_manager'), $memoryCounterBackend);
// No alias for user and admin yet, so should be NULL. // No alias for user and admin yet, so should be NULL.
$this->assertNull($whitelist->get('user')); $this->assertNull($whitelist->get('user'));
......
...@@ -79,20 +79,6 @@ public function getAliasByPath($path, $langcode = NULL) { ...@@ -79,20 +79,6 @@ public function getAliasByPath($path, $langcode = NULL) {
return $this->aliases[$path][$langcode]; return $this->aliases[$path][$langcode];
} }
/**
* {@inheritdoc}
*/
public function getPathLookups() {
return array_keys($this->lookedUp);
}
/**
* {@inheritdoc}
*/
public function preloadPathLookups(array $path_list) {
// Not needed.
}