You are here

class AliasManager in Drupal 8

Same name in this branch
  1. 8 core/modules/path_alias/src/AliasManager.php \Drupal\path_alias\AliasManager
  2. 8 core/lib/Drupal/Core/Path/AliasManager.php \Drupal\Core\Path\AliasManager

The default alias manager implementation.

Hierarchy

Expanded class hierarchy of AliasManager

Deprecated

in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\path_alias\AliasManager instead.

See also

https://www.drupal.org/node/3092086

5 files declare their use of AliasManager
AliasManager.php in core/modules/path_alias/src/AliasManager.php
CoreServiceProvider.php in core/lib/Drupal/Core/CoreServiceProvider.php
DeprecatedClassesTest.php in core/modules/path_alias/tests/src/Unit/DeprecatedClassesTest.php
DeprecatedServicesTest.php in core/modules/path_alias/tests/src/Kernel/DeprecatedServicesTest.php
OverriddenAliasManager.php in core/modules/path_alias/tests/modules/path_alias_deprecated_test/src/OverriddenAliasManager.php
1 string reference to 'AliasManager'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses AliasManager
path.alias_manager in core/core.services.yml
Drupal\Core\Path\AliasManager

File

core/lib/Drupal/Core/Path/AliasManager.php, line 20

Namespace

Drupal\Core\Path
View source
class AliasManager implements AliasManagerInterface, CacheDecoratorInterface {
  use DeprecatedServicePropertyTrait;

  /**
   * {@inheritdoc}
   */
  protected $deprecatedProperties = [
    'storage' => 'path.alias_storage',
  ];

  /**
   * The path alias repository.
   *
   * @var \Drupal\Core\Path\AliasRepositoryInterface
   */
  protected $pathAliasRepository;

  /**
   * 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 bool
   */
  protected $cacheNeedsWriting = FALSE;

  /**
   * Language manager for retrieving the default langcode when none is specified.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * Holds the map of path lookups per language.
   *
   * @var array
   */
  protected $lookupMap = [];

  /**
   * Holds an array of aliases for which no path was found.
   *
   * @var array
   */
  protected $noPath = [];

  /**
   * Holds the array of whitelisted path aliases.
   *
   * @var \Drupal\Core\Path\AliasWhitelistInterface
   */
  protected $whitelist;

  /**
   * Holds an array of paths that have no alias.
   *
   * @var array
   */
  protected $noAlias = [];

  /**
   * Whether preloaded path lookups has already been loaded.
   *
   * @var array
   */
  protected $langcodePreloaded = [];

  /**
   * Holds an array of previously looked up paths for the current request path.
   *
   * This will only get populated if a cache key has been set, which for example
   * happens if the alias manager is used in the context of a request.
   *
   * @var array
   */
  protected $preloadedPathLookups = FALSE;

  /**
   * Constructs an AliasManager.
   *
   * @param \Drupal\Core\Path\AliasRepositoryInterface $alias_repository
   *   The path alias repository.
   * @param \Drupal\Core\Path\AliasWhitelistInterface $whitelist
   *   The whitelist implementation to use.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   Cache backend.
   */
  public function __construct($alias_repository, AliasWhitelistInterface $whitelist, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
    if (!$alias_repository instanceof AliasRepositoryInterface) {
      @trigger_error('Passing the path.alias_storage service to AliasManager::__construct() is deprecated in drupal:8.8.0 and will be removed before drupal:9.0.0. Pass the new dependencies instead. See https://www.drupal.org/node/3013865.', E_USER_DEPRECATED);
      $alias_repository = \Drupal::service('path_alias.repository');
    }
    $this->pathAliasRepository = $alias_repository;
    $this->languageManager = $language_manager;
    $this->whitelist = $whitelist;
    $this->cache = $cache;

    // This is used as base class by the new class, so we do not trigger
    // deprecation notices when that or any child class is instantiated.
    $new_class = 'Drupal\\path_alias\\AliasManager';
    if (!is_a($this, $new_class) && class_exists($new_class)) {
      @trigger_error('The \\' . __CLASS__ . ' class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \\' . $new_class . '. See https://drupal.org/node/3092086', E_USER_DEPRECATED);

      // Despite being two different services, hence two different class
      // instances, both the new and the legacy alias managers need to share the
      // same internal state to keep the path/alias lookup optimizations
      // working.
      try {
        $alias_manager = \Drupal::service('path_alias.manager');
        if ($alias_manager instanceof $new_class) {
          $synced_properties = [
            'cacheKey',
            'langcodePreloaded',
            'lookupMap',
            'noAlias',
            'noPath',
            'preloadedPathLookups',
          ];
          foreach ($synced_properties as $property) {
            $this->{$property} =& $alias_manager->{$property};
          }
        }
      } catch (ServiceCircularReferenceException $e) {

        // This may happen during installation when the "path_alias" module has
        // not been installed yet. Nothing to do in this case.
      }
    }
  }

  /**
   * {@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 = $this->preloadedPathLookups ?: [];
      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]));
        }
      }
      $twenty_four_hours = 60 * 60 * 24;
      $this->cache
        ->set($this->cacheKey, $path_lookups, $this
        ->getRequestTime() + $twenty_four_hours);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getPathByAlias($alias, $langcode = NULL) {

    // If no language is explicitly specified we default to the current URL
    // language. If we used a language different from the one conveyed by the
    // requested URL, we might end up being unable to check if there is a path
    // alias matching the URL path.
    $langcode = $langcode ?: $this->languageManager
      ->getCurrentLanguage(LanguageInterface::TYPE_URL)
      ->getId();

    // If we already know that there are no paths for this alias simply return.
    if (empty($alias) || !empty($this->noPath[$langcode][$alias])) {
      return $alias;
    }

    // Look for the alias within the cached map.
    if (isset($this->lookupMap[$langcode]) && ($path = array_search($alias, $this->lookupMap[$langcode]))) {
      return $path;
    }

    // Look for path in storage.
    if ($path_alias = $this->pathAliasRepository
      ->lookupByAlias($alias, $langcode)) {
      $this->lookupMap[$langcode][$path_alias['path']] = $alias;
      return $path_alias['path'];
    }

    // We can't record anything into $this->lookupMap because we didn't find any
    // paths for this alias. Thus cache to $this->noPath.
    $this->noPath[$langcode][$alias] = TRUE;
    return $alias;
  }

  /**
   * {@inheritdoc}
   */
  public function getAliasByPath($path, $langcode = NULL) {
    if ($path[0] !== '/') {
      throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $path));
    }

    // If no language is explicitly specified we default to the current URL
    // language. If we used a language different from the one conveyed by the
    // requested URL, we might end up being unable to check if there is a path
    // alias matching the URL path.
    $langcode = $langcode ?: $this->languageManager
      ->getCurrentLanguage(LanguageInterface::TYPE_URL)
      ->getId();

    // Check the path whitelist, if the top-level part before the first /
    // is not in the list, then there is no need to do anything further,
    // it is not in the database.
    if ($path === '/' || !$this->whitelist
      ->get(strtok(trim($path, '/'), '/'))) {
      return $path;
    }

    // During the first call to this method per language, load the expected
    // paths for the page from cache.
    if (empty($this->langcodePreloaded[$langcode])) {
      $this->langcodePreloaded[$langcode] = TRUE;
      $this->lookupMap[$langcode] = [];

      // 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 = [];
        if ($this->cacheKey) {
          if ($cached = $this->cache
            ->get($this->cacheKey)) {
            $this->preloadedPathLookups = $cached->data;
          }
          else {
            $this->cacheNeedsWriting = TRUE;
          }
        }
      }

      // Load paths from cache.
      if (!empty($this->preloadedPathLookups[$langcode])) {
        $this->lookupMap[$langcode] = $this->pathAliasRepository
          ->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode);

        // Keep a record of paths with no alias to avoid querying twice.
        $this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode])));
      }
    }

    // If we already know that there are no aliases for this path simply return.
    if (!empty($this->noAlias[$langcode][$path])) {
      return $path;
    }

    // If the alias has already been loaded, return it from static cache.
    if (isset($this->lookupMap[$langcode][$path])) {
      return $this->lookupMap[$langcode][$path];
    }

    // Try to load alias from storage.
    if ($path_alias = $this->pathAliasRepository
      ->lookupBySystemPath($path, $langcode)) {
      $this->lookupMap[$langcode][$path] = $path_alias['alias'];
      return $path_alias['alias'];
    }

    // We can't record anything into $this->lookupMap because we didn't find any
    // aliases for this path. Thus cache to $this->noAlias.
    $this->noAlias[$langcode][$path] = TRUE;
    return $path;
  }

  /**
   * {@inheritdoc}
   */
  public function cacheClear($source = NULL) {
    if ($source) {
      foreach (array_keys($this->lookupMap) as $lang) {
        unset($this->lookupMap[$lang][$source]);
      }
    }
    else {
      $this->lookupMap = [];
    }
    $this->noPath = [];
    $this->noAlias = [];
    $this->langcodePreloaded = [];
    $this->preloadedPathLookups = [];
    $this->cache
      ->delete($this->cacheKey);
    $this
      ->pathAliasWhitelistRebuild($source);
  }

  /**
   * Rebuild the path alias white list.
   *
   * @param string $path
   *   An optional path for which an alias is being inserted.
   *
   * @return
   *   An array containing a white list of path aliases.
   */
  protected function pathAliasWhitelistRebuild($path = NULL) {

    // When paths are inserted, only rebuild the whitelist if the path has a top
    // level component which is not already in the whitelist.
    if (!empty($path)) {
      if ($this->whitelist
        ->get(strtok($path, '/'))) {
        return;
      }
    }
    $this->whitelist
      ->clear();
  }

  /**
   * Wrapper method for REQUEST_TIME constant.
   *
   * @return int
   */
  protected function getRequestTime() {
    return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME'];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AliasManager::$cache protected property Cache backend service.
AliasManager::$cacheKey protected property The cache key to use when caching paths.
AliasManager::$cacheNeedsWriting protected property Whether the cache needs to be written.
AliasManager::$deprecatedProperties protected property
AliasManager::$langcodePreloaded protected property Whether preloaded path lookups has already been loaded.
AliasManager::$languageManager protected property Language manager for retrieving the default langcode when none is specified.
AliasManager::$lookupMap protected property Holds the map of path lookups per language.
AliasManager::$noAlias protected property Holds an array of paths that have no alias.
AliasManager::$noPath protected property Holds an array of aliases for which no path was found.
AliasManager::$pathAliasRepository protected property The path alias repository.
AliasManager::$preloadedPathLookups protected property Holds an array of previously looked up paths for the current request path.
AliasManager::$whitelist protected property Holds the array of whitelisted path aliases.
AliasManager::cacheClear public function Clear internal caches in alias manager. Overrides AliasManagerInterface::cacheClear
AliasManager::getAliasByPath public function Given a path, return the alias. Overrides AliasManagerInterface::getAliasByPath
AliasManager::getPathByAlias public function Given the alias, return the path it represents. Overrides AliasManagerInterface::getPathByAlias
AliasManager::getRequestTime protected function Wrapper method for REQUEST_TIME constant.
AliasManager::pathAliasWhitelistRebuild protected function Rebuild the path alias white list.
AliasManager::setCacheKey public function Specify the key to use when writing the cache. Overrides CacheDecoratorInterface::setCacheKey
AliasManager::writeCache public function 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. Overrides CacheDecoratorInterface::writeCache
AliasManager::__construct public function Constructs an AliasManager.
DeprecatedServicePropertyTrait::__get public function Allows to access deprecated/removed properties.