annotate core/lib/Drupal/Core/Path/AliasManager.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Path;
Chris@0 4
Chris@0 5 use Drupal\Core\Cache\CacheBackendInterface;
Chris@0 6 use Drupal\Core\CacheDecorator\CacheDecoratorInterface;
Chris@0 7 use Drupal\Core\Language\LanguageInterface;
Chris@0 8 use Drupal\Core\Language\LanguageManagerInterface;
Chris@0 9
Chris@0 10 /**
Chris@0 11 * The default alias manager implementation.
Chris@0 12 */
Chris@0 13 class AliasManager implements AliasManagerInterface, CacheDecoratorInterface {
Chris@0 14
Chris@0 15 /**
Chris@0 16 * The alias storage service.
Chris@0 17 *
Chris@0 18 * @var \Drupal\Core\Path\AliasStorageInterface
Chris@0 19 */
Chris@0 20 protected $storage;
Chris@0 21
Chris@0 22 /**
Chris@0 23 * Cache backend service.
Chris@0 24 *
Chris@17 25 * @var \Drupal\Core\Cache\CacheBackendInterface
Chris@0 26 */
Chris@0 27 protected $cache;
Chris@0 28
Chris@0 29 /**
Chris@0 30 * The cache key to use when caching paths.
Chris@0 31 *
Chris@0 32 * @var string
Chris@0 33 */
Chris@0 34 protected $cacheKey;
Chris@0 35
Chris@0 36 /**
Chris@0 37 * Whether the cache needs to be written.
Chris@0 38 *
Chris@0 39 * @var bool
Chris@0 40 */
Chris@0 41 protected $cacheNeedsWriting = FALSE;
Chris@0 42
Chris@0 43 /**
Chris@0 44 * Language manager for retrieving the default langcode when none is specified.
Chris@0 45 *
Chris@0 46 * @var \Drupal\Core\Language\LanguageManagerInterface
Chris@0 47 */
Chris@0 48 protected $languageManager;
Chris@0 49
Chris@0 50 /**
Chris@0 51 * Holds the map of path lookups per language.
Chris@0 52 *
Chris@0 53 * @var array
Chris@0 54 */
Chris@0 55 protected $lookupMap = [];
Chris@0 56
Chris@0 57 /**
Chris@0 58 * Holds an array of aliases for which no path was found.
Chris@0 59 *
Chris@0 60 * @var array
Chris@0 61 */
Chris@0 62 protected $noPath = [];
Chris@0 63
Chris@0 64 /**
Chris@0 65 * Holds the array of whitelisted path aliases.
Chris@0 66 *
Chris@0 67 * @var \Drupal\Core\Path\AliasWhitelistInterface
Chris@0 68 */
Chris@0 69 protected $whitelist;
Chris@0 70
Chris@0 71 /**
Chris@0 72 * Holds an array of paths that have no alias.
Chris@0 73 *
Chris@0 74 * @var array
Chris@0 75 */
Chris@0 76 protected $noAlias = [];
Chris@0 77
Chris@0 78 /**
Chris@0 79 * Whether preloaded path lookups has already been loaded.
Chris@0 80 *
Chris@0 81 * @var array
Chris@0 82 */
Chris@0 83 protected $langcodePreloaded = [];
Chris@0 84
Chris@0 85 /**
Chris@0 86 * Holds an array of previously looked up paths for the current request path.
Chris@0 87 *
Chris@0 88 * This will only get populated if a cache key has been set, which for example
Chris@0 89 * happens if the alias manager is used in the context of a request.
Chris@0 90 *
Chris@0 91 * @var array
Chris@0 92 */
Chris@0 93 protected $preloadedPathLookups = FALSE;
Chris@0 94
Chris@0 95 /**
Chris@0 96 * Constructs an AliasManager.
Chris@0 97 *
Chris@0 98 * @param \Drupal\Core\Path\AliasStorageInterface $storage
Chris@0 99 * The alias storage service.
Chris@0 100 * @param \Drupal\Core\Path\AliasWhitelistInterface $whitelist
Chris@0 101 * The whitelist implementation to use.
Chris@0 102 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
Chris@0 103 * The language manager.
Chris@0 104 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
Chris@0 105 * Cache backend.
Chris@0 106 */
Chris@0 107 public function __construct(AliasStorageInterface $storage, AliasWhitelistInterface $whitelist, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
Chris@0 108 $this->storage = $storage;
Chris@0 109 $this->languageManager = $language_manager;
Chris@0 110 $this->whitelist = $whitelist;
Chris@0 111 $this->cache = $cache;
Chris@0 112 }
Chris@0 113
Chris@0 114 /**
Chris@0 115 * {@inheritdoc}
Chris@0 116 */
Chris@0 117 public function setCacheKey($key) {
Chris@0 118 // Prefix the cache key to avoid clashes with other caches.
Chris@0 119 $this->cacheKey = 'preload-paths:' . $key;
Chris@0 120 }
Chris@0 121
Chris@0 122 /**
Chris@0 123 * {@inheritdoc}
Chris@0 124 *
Chris@0 125 * Cache an array of the paths available on each page. We assume that aliases
Chris@0 126 * will be needed for the majority of these paths during subsequent requests,
Chris@0 127 * and load them in a single query during path alias lookup.
Chris@0 128 */
Chris@0 129 public function writeCache() {
Chris@0 130 // Check if the paths for this page were loaded from cache in this request
Chris@0 131 // to avoid writing to cache on every request.
Chris@0 132 if ($this->cacheNeedsWriting && !empty($this->cacheKey)) {
Chris@0 133 // Start with the preloaded path lookups, so that cached entries for other
Chris@0 134 // languages will not be lost.
Chris@0 135 $path_lookups = $this->preloadedPathLookups ?: [];
Chris@0 136 foreach ($this->lookupMap as $langcode => $lookups) {
Chris@0 137 $path_lookups[$langcode] = array_keys($lookups);
Chris@0 138 if (!empty($this->noAlias[$langcode])) {
Chris@0 139 $path_lookups[$langcode] = array_merge($path_lookups[$langcode], array_keys($this->noAlias[$langcode]));
Chris@0 140 }
Chris@0 141 }
Chris@0 142
Chris@0 143 $twenty_four_hours = 60 * 60 * 24;
Chris@0 144 $this->cache->set($this->cacheKey, $path_lookups, $this->getRequestTime() + $twenty_four_hours);
Chris@0 145 }
Chris@0 146 }
Chris@0 147
Chris@0 148 /**
Chris@0 149 * {@inheritdoc}
Chris@0 150 */
Chris@0 151 public function getPathByAlias($alias, $langcode = NULL) {
Chris@0 152 // If no language is explicitly specified we default to the current URL
Chris@0 153 // language. If we used a language different from the one conveyed by the
Chris@0 154 // requested URL, we might end up being unable to check if there is a path
Chris@0 155 // alias matching the URL path.
Chris@0 156 $langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
Chris@0 157
Chris@0 158 // If we already know that there are no paths for this alias simply return.
Chris@0 159 if (empty($alias) || !empty($this->noPath[$langcode][$alias])) {
Chris@0 160 return $alias;
Chris@0 161 }
Chris@0 162
Chris@0 163 // Look for the alias within the cached map.
Chris@0 164 if (isset($this->lookupMap[$langcode]) && ($path = array_search($alias, $this->lookupMap[$langcode]))) {
Chris@0 165 return $path;
Chris@0 166 }
Chris@0 167
Chris@0 168 // Look for path in storage.
Chris@0 169 if ($path = $this->storage->lookupPathSource($alias, $langcode)) {
Chris@0 170 $this->lookupMap[$langcode][$path] = $alias;
Chris@0 171 return $path;
Chris@0 172 }
Chris@0 173
Chris@0 174 // We can't record anything into $this->lookupMap because we didn't find any
Chris@0 175 // paths for this alias. Thus cache to $this->noPath.
Chris@0 176 $this->noPath[$langcode][$alias] = TRUE;
Chris@0 177
Chris@0 178 return $alias;
Chris@0 179 }
Chris@0 180
Chris@0 181 /**
Chris@0 182 * {@inheritdoc}
Chris@0 183 */
Chris@0 184 public function getAliasByPath($path, $langcode = NULL) {
Chris@0 185 if ($path[0] !== '/') {
Chris@0 186 throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $path));
Chris@0 187 }
Chris@0 188 // If no language is explicitly specified we default to the current URL
Chris@0 189 // language. If we used a language different from the one conveyed by the
Chris@0 190 // requested URL, we might end up being unable to check if there is a path
Chris@0 191 // alias matching the URL path.
Chris@0 192 $langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
Chris@0 193
Chris@0 194 // Check the path whitelist, if the top-level part before the first /
Chris@0 195 // is not in the list, then there is no need to do anything further,
Chris@0 196 // it is not in the database.
Chris@0 197 if ($path === '/' || !$this->whitelist->get(strtok(trim($path, '/'), '/'))) {
Chris@0 198 return $path;
Chris@0 199 }
Chris@0 200
Chris@0 201 // During the first call to this method per language, load the expected
Chris@0 202 // paths for the page from cache.
Chris@0 203 if (empty($this->langcodePreloaded[$langcode])) {
Chris@0 204 $this->langcodePreloaded[$langcode] = TRUE;
Chris@0 205 $this->lookupMap[$langcode] = [];
Chris@0 206
Chris@0 207 // Load the cached paths that should be used for preloading. This only
Chris@0 208 // happens if a cache key has been set.
Chris@0 209 if ($this->preloadedPathLookups === FALSE) {
Chris@0 210 $this->preloadedPathLookups = [];
Chris@0 211 if ($this->cacheKey) {
Chris@0 212 if ($cached = $this->cache->get($this->cacheKey)) {
Chris@0 213 $this->preloadedPathLookups = $cached->data;
Chris@0 214 }
Chris@0 215 else {
Chris@0 216 $this->cacheNeedsWriting = TRUE;
Chris@0 217 }
Chris@0 218 }
Chris@0 219 }
Chris@0 220
Chris@0 221 // Load paths from cache.
Chris@0 222 if (!empty($this->preloadedPathLookups[$langcode])) {
Chris@0 223 $this->lookupMap[$langcode] = $this->storage->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode);
Chris@0 224 // Keep a record of paths with no alias to avoid querying twice.
Chris@0 225 $this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode])));
Chris@0 226 }
Chris@0 227 }
Chris@0 228
Chris@0 229 // If we already know that there are no aliases for this path simply return.
Chris@0 230 if (!empty($this->noAlias[$langcode][$path])) {
Chris@0 231 return $path;
Chris@0 232 }
Chris@0 233
Chris@0 234 // If the alias has already been loaded, return it from static cache.
Chris@0 235 if (isset($this->lookupMap[$langcode][$path])) {
Chris@0 236 return $this->lookupMap[$langcode][$path];
Chris@0 237 }
Chris@0 238
Chris@0 239 // Try to load alias from storage.
Chris@0 240 if ($alias = $this->storage->lookupPathAlias($path, $langcode)) {
Chris@0 241 $this->lookupMap[$langcode][$path] = $alias;
Chris@0 242 return $alias;
Chris@0 243 }
Chris@0 244
Chris@0 245 // We can't record anything into $this->lookupMap because we didn't find any
Chris@0 246 // aliases for this path. Thus cache to $this->noAlias.
Chris@0 247 $this->noAlias[$langcode][$path] = TRUE;
Chris@0 248 return $path;
Chris@0 249 }
Chris@0 250
Chris@0 251 /**
Chris@0 252 * {@inheritdoc}
Chris@0 253 */
Chris@0 254 public function cacheClear($source = NULL) {
Chris@0 255 if ($source) {
Chris@0 256 foreach (array_keys($this->lookupMap) as $lang) {
Chris@0 257 unset($this->lookupMap[$lang][$source]);
Chris@0 258 }
Chris@0 259 }
Chris@0 260 else {
Chris@0 261 $this->lookupMap = [];
Chris@0 262 }
Chris@0 263 $this->noPath = [];
Chris@0 264 $this->noAlias = [];
Chris@0 265 $this->langcodePreloaded = [];
Chris@0 266 $this->preloadedPathLookups = [];
Chris@0 267 $this->cache->delete($this->cacheKey);
Chris@0 268 $this->pathAliasWhitelistRebuild($source);
Chris@0 269 }
Chris@0 270
Chris@0 271 /**
Chris@0 272 * Rebuild the path alias white list.
Chris@0 273 *
Chris@0 274 * @param string $path
Chris@0 275 * An optional path for which an alias is being inserted.
Chris@0 276 *
Chris@0 277 * @return
Chris@0 278 * An array containing a white list of path aliases.
Chris@0 279 */
Chris@0 280 protected function pathAliasWhitelistRebuild($path = NULL) {
Chris@0 281 // When paths are inserted, only rebuild the whitelist if the path has a top
Chris@0 282 // level component which is not already in the whitelist.
Chris@0 283 if (!empty($path)) {
Chris@0 284 if ($this->whitelist->get(strtok($path, '/'))) {
Chris@0 285 return;
Chris@0 286 }
Chris@0 287 }
Chris@0 288 $this->whitelist->clear();
Chris@0 289 }
Chris@0 290
Chris@0 291 /**
Chris@0 292 * Wrapper method for REQUEST_TIME constant.
Chris@0 293 *
Chris@0 294 * @return int
Chris@0 295 */
Chris@0 296 protected function getRequestTime() {
Chris@0 297 return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME'];
Chris@0 298 }
Chris@0 299
Chris@0 300 }