Unverified Commit fd5a6fda authored by Alex Pott's avatar Alex Pott
Browse files

task: #3486503 Add a file parsing cache collector to replace some uses of FileCache

By: alexpott
By: catch
By: nicxvan
By: daffie
By: berdir
By: godotislate
(cherry picked from commit 0be9248e)
parent a9cd479a
Loading
Loading
Loading
Loading
Loading
+17 −2
Original line number Diff line number Diff line
@@ -367,6 +367,13 @@ services:
      - { name: cache.bin.memory, default_backend: cache.backend.memory.memory }
    factory: ['@cache_factory', 'get']
    arguments: [memory]
  cache.file_parsing:
    # The cache.bin service tag is intentionally omitted because items in this
    # bin are designed to persist between cache clears. Invalidation happens via
    # filemtime checks instead.
    class: Drupal\Core\Cache\CacheBackendInterface
    factory: ['@cache_factory', 'get']
    arguments: [file_parsing]
  cache_prewarmer:
    class: Drupal\Core\PreWarm\CachePreWarmer
    arguments: ['@class_resolver']
@@ -1214,9 +1221,12 @@ services:
  router.builder.attributes:
    class: Drupal\Core\Routing\AttributeRouteDiscovery
    arguments: ['@container.namespaces']
  router.yaml_cache_collector:
    class: Drupal\Core\Utility\YamlCacheCollector
    arguments: ['router.parsing_cache', '@cache.file_parsing', '@lock', '@datetime.time']
  router.builder.yaml:
    class: Drupal\Core\Routing\YamlRouteDiscovery
    arguments: ['@module_handler', '@controller_resolver']
    arguments: ['@module_handler', '@controller_resolver', '@router.yaml_cache_collector']
  menu.rebuild_subscriber:
    class: Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber
    arguments: ['@lock', '@plugin.manager.menu.link', '@database', '@database.replica_kill_switch', '@logger.channel.menu']
@@ -1805,8 +1815,13 @@ services:
    deprecated: The "%service_id%" service is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use LibraryDiscovery instead. See https://www.drupal.org/node/3462970
  library.discovery.parser:
    class: Drupal\Core\Asset\LibraryDiscoveryParser
    arguments: ['%app.root%', '@module_handler', '@theme.manager', '@stream_wrapper_manager', '@library.libraries_directory_file_finder', '@extension.path.resolver', '@plugin.manager.sdc']
    arguments: ['%app.root%', '@module_handler', '@theme.manager', '@stream_wrapper_manager', '@library.libraries_directory_file_finder', '@extension.path.resolver', '@plugin.manager.sdc', '@library.parsing_cache']
  Drupal\Core\Asset\LibraryDiscoveryParser: '@library.discovery.parser'
  library.parsing_cache:
    class: Drupal\Core\Utility\YamlCacheCollector
    arguments: ['library.parsing_cache', '@cache.file_parsing', '@lock', '@datetime.time']
    tags:
      - { name: needs_destruction }
  library.libraries_directory_file_finder:
    class: Drupal\Core\Asset\LibrariesDirectoryFileFinder
    arguments: ['%app.root%', '%site.path%', '@extension.list.profile', '%install_profile%']
+18 −94
Original line number Diff line number Diff line
@@ -2,8 +2,6 @@

namespace Drupal\Core\Asset;

use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\FileCache\FileCacheInterface;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException;
@@ -12,11 +10,11 @@
use Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException;
use Drupal\Core\Extension\ExtensionPathResolver;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\Core\Theme\ActiveTheme;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Utility\YamlCacheCollector;
use Drupal\Core\Plugin\Component;

/**
@@ -24,89 +22,20 @@
 */
class LibraryDiscoveryParser {

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The theme manager.
   *
   * @var \Drupal\Core\Theme\ThemeManagerInterface
   */
  protected $themeManager;

  /**
   * The app root.
   *
   * @var string
   */
  protected $root;

  /**
   * The stream wrapper manager.
   *
   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
   */
  protected $streamWrapperManager;

  /**
   * The libraries directory file finder.
   *
   * @var \Drupal\Core\Asset\LibrariesDirectoryFileFinder
   */
  protected $librariesDirectoryFileFinder;

  /**
   * The component plugin manager.
   *
   * @var \Drupal\Core\Theme\ComponentPluginManager
   */
  protected $componentPluginManager;

  /**
   * The extension path resolver.
   *
   * @var \Drupal\Core\Extension\ExtensionPathResolver
   */
  protected $extensionPathResolver;

  /**
   * The file cache.
   *
   * @var \Drupal\Component\FileCache\FileCacheInterface
   */
  protected FileCacheInterface $fileCache;

  /**
   * Constructs a new LibraryDiscoveryParser instance.
   *
   * @param string $root
   *   The app root.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
   *   The theme manager.
   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
   *   The stream wrapper manager.
   * @param \Drupal\Core\Asset\LibrariesDirectoryFileFinder $libraries_directory_file_finder
   *   The libraries directory file finder.
   * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver
   *   The extension path resolver.
   * @param \Drupal\Core\Theme\ComponentPluginManager $component_plugin_manager
   *   The component plugin manager.
   */
  public function __construct($root, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, StreamWrapperManagerInterface $stream_wrapper_manager, LibrariesDirectoryFileFinder $libraries_directory_file_finder, ExtensionPathResolver $extension_path_resolver, ComponentPluginManager $component_plugin_manager) {
    $this->root = $root;
    $this->moduleHandler = $module_handler;
    $this->themeManager = $theme_manager;
    $this->streamWrapperManager = $stream_wrapper_manager;
    $this->librariesDirectoryFileFinder = $libraries_directory_file_finder;
    $this->extensionPathResolver = $extension_path_resolver;
    $this->fileCache = FileCacheFactory::get('library_parser');
    $this->componentPluginManager = $component_plugin_manager;
  public function __construct(
    protected string $appRoot,
    protected ModuleHandlerInterface $moduleHandler,
    protected ThemeManagerInterface $themeManager,
    protected StreamWrapperManagerInterface $streamWrapperManager,
    protected LibrariesDirectoryFileFinder $librariesDirectoryFileFinder,
    protected ExtensionPathResolver $extensionPathResolver,
    protected ComponentPluginManager $componentPluginManager,
    protected ?YamlCacheCollector $yamlCacheCollector = NULL,
  ) {
    if (!isset($yamlCacheCollector)) {
      $this->yamlCacheCollector = \Drupal::service('libraries.parsing_cache');
      @trigger_error('Calling ' . __METHOD__ . '() without the $yamlCacheCollector argument is deprecated in drupal:11.4.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/project/drupal/issues/3486503', E_USER_DEPRECATED);
    }
  }

  /**
@@ -386,21 +315,16 @@ protected function parseLibraryInfo($extension, $path) {
    $libraries = [];

    $library_file = $path . '/' . $extension . '.libraries.yml';
    $library_path = $this->root . '/' . $library_file;
    $library_path = $this->appRoot . '/' . $library_file;

    if (file_exists($library_path)) {
      $libraries = $this->fileCache->get($library_path);
      if ($libraries === NULL) {
    try {
          $libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file)) ?? [];
          $this->fileCache->set($library_path, $libraries);
      $libraries = $this->yamlCacheCollector->get($library_path);
    }
    catch (InvalidDataTypeException $e) {
      // Rethrow a more helpful exception to provide context.
      throw new InvalidLibraryFileException(sprintf('Invalid library definition in %s: %s', $library_file, $e->getMessage()), 0, $e);
    }
      }
    }

    // Core also provides additional libraries that don't come from the YAML,
    // file nor the hook_library_info_build. They come from single-directory
    // component definitions.
+1 −0
Original line number Diff line number Diff line
@@ -311,6 +311,7 @@ public function clear() {
   */
  public function destruct() {
    $this->updateCache();
    $this->reset();
  }

  /**
+68 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Discovery;

use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Cache\CacheCollectorInterface;

/**
 * Provides discovery for YAML files within a given set of directories.
 *
 * This overrides the Component file decoding with the Core YAML implementation.
 */
class YamlCacheCollectorDiscovery extends YamlDiscovery {

  /**
   * Constructs a YamlDiscovery object.
   *
   * @param string $name
   *   The base filename to look for in each directory. The format will be
   *   $provider.$name.yml.
   * @param array $directories
   *   An array of directories to scan, keyed by the provider.
   * @param Drupal\Core\Cache\CacheCollectorInterface $yamlCacheCollector
   *   An instance of YamlCacheCollector.
   */
  public function __construct(string $name, array $directories, protected CacheCollectorInterface $yamlCacheCollector) {
    parent::__construct($name, $directories);
  }

  /**
   * {@inheritdoc}
   */
  public function findAll(): array {
    $all = [];

    $files = $this->findFiles();
    foreach ($files as $provider => $file) {
      $all[$provider] = $this->yamlCacheCollector->get($file);
    }
    // Once discovery is complete, call ::destruct() on the cache collector to
    // free up memory.
    $this->yamlCacheCollector->destruct();
    return $all;
  }

  /**
   * {@inheritdoc}
   */
  protected function decode($file): array {

    try {
      return $this->yamlCacheCollector->get($file);
    }
    catch (InvalidDataTypeException $e) {
      throw new InvalidDataTypeException($file . ': ' . $e->getMessage(), $e->getCode(), $e);
    }
  }

  /**
   * Calls cache collector ::destruct() method when this goes out of scope.
   */
  public function __destruct() {
    $this->yamlCacheCollector->destruct();
  }

}
+8 −2
Original line number Diff line number Diff line
@@ -4,8 +4,9 @@

namespace Drupal\Core\Routing;

use Drupal\Core\Cache\CacheCollectorInterface;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Discovery\YamlDiscovery;
use Drupal\Core\Discovery\YamlCacheCollectorDiscovery;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\Routing\RouteCollection;

@@ -17,7 +18,12 @@ class YamlRouteDiscovery extends StaticRouteDiscoveryBase {
  public function __construct(
    protected readonly ModuleHandlerInterface $moduleHandler,
    protected readonly ControllerResolverInterface $controllerResolver,
    protected ?CacheCollectorInterface $yamlCacheCollector,
  ) {
    if (!isset($yamlCacheCollector)) {
      $this->yamlCacheCollector = \Drupal::service('routing.yaml_cache_collector');
      @trigger_error('Calling ' . __METHOD__ . '() without the $yamlCacheCollector argument is deprecated in drupal:11.4.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/project/drupal/issues/3486503', E_USER_DEPRECATED);
    }
  }

  /**
@@ -90,7 +96,7 @@ protected function collectRoutes(): iterable {
  protected function getRouteDefinitions() {
    // Always instantiate a new YamlDiscovery object so that we always search on
    // the up-to-date list of modules.
    $discovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories());
    $discovery = new YamlCacheCollectorDiscovery('routing', $this->moduleHandler->getModuleDirectories(), $this->yamlCacheCollector);
    return $discovery->findAll();
  }

Loading