DirectoryWithMetadataDiscovery.php

Same filename and directory in other branches
  1. 11.x core/lib/Drupal/Core/Plugin/Discovery/DirectoryWithMetadataDiscovery.php
  2. 10 core/lib/Drupal/Core/Plugin/Discovery/DirectoryWithMetadataDiscovery.php

Namespace

Drupal\Core\Plugin\Discovery

File

core/lib/Drupal/Core/Plugin/Discovery/DirectoryWithMetadataDiscovery.php

View source
<?php

namespace Drupal\Core\Plugin\Discovery;

use Drupal\Component\Discovery\YamlDirectoryDiscovery;
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
use Drupal\Core\File\FileSystemInterface;

/**
 * Does the actual finding of the directories with metadata files.
 */
class DirectoryWithMetadataDiscovery extends YamlDirectoryDiscovery {
  use DeprecatedServicePropertyTrait;
  
  /**
   * Allowed characters of plugin ID.
   *
   * This discovery has the particularity of searching for plugin IDs in
   * filenames.
   */
  const PLUGIN_ID_REGEX = '([a-z0-9._-])+';
  
  /**
   * The deprecated properties.
   *
   * @see https://www.drupal.org/node/3530869
   */
  protected array $deprecatedProperties = [
    'fileSystem' => FileSystemInterface::class,
  ];
  
  /**
   * Constructs a DirectoryWithMetadataDiscovery object.
   *
   * @param array $directories
   *   An array of directories to scan, keyed by the provider. The value can
   *   either be a string or an array of strings. The string values should be
   *   the path of a directory to scan.
   * @param string $file_cache_key_suffix
   *   The file cache key suffix. This should be unique for each type of
   *   discovery.
   * @param string $file_name_suffix
   *   The file name suffix that identifies a plugin metadata file, without the
   *   leading dot or trailing .yml extension. For example, passing 'component'
   *   will match files of the form 'my-plugin.component.yml'.
   */
  public function __construct(array $directories, string $file_cache_key_suffix, protected string $file_name_suffix) {
    parent::__construct($directories, $file_cache_key_suffix);
  }
  
  /**
   * Gets an iterator to loop over the files in the provided directory.
   *
   * This method exists so that it is easy to replace this functionality in a
   * class that extends this one. For example, it could be used to make the scan
   * recursive.
   *
   * @param string $directory
   *   The directory to scan.
   *
   * @return \RecursiveIteratorIterator<\SplFileInfo>
   *   A \RecursiveIteratorIterator object or array where the values are
   *   \SplFileInfo objects.
   */
  protected function getDirectoryIterator($directory) : \RecursiveIteratorIterator {
    // Use FilesystemIterator to not iterate over the . and .. directories.
    $flags = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS;
    $directory_iterator = new \RecursiveDirectoryIterator($directory, $flags);
    // Detect files of the form "my_plugin.{$file_name_suffix}.yml".
    $regex = '/^' . static::PLUGIN_ID_REGEX . '\\.' . preg_quote($this->file_name_suffix, '/') . '\\.yml$/i';
    $filter = new RegexRecursiveFilterIterator($directory_iterator, $regex);
    return new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::LEAVES_ONLY, $flags);
  }
  
  /**
   * {@inheritdoc}
   *
   * The IDs can collide in two different scenarios:
   *
   * 1. Because one plugin is overriding another one via "weight".
   * 2. Because the same plugin exists in different themes.
   */
  protected function getIdentifier($file, array $data) : string {
    $id = basename($file, '.' . $this->file_name_suffix . '.yml');
    $provider_paths = array_flip($this->directories);
    $provider = $this->findProvider($file, $provider_paths);
    // We use the provider to dedupe plugins because it does not make sense
    // for a single provider to fork itself.
    return sprintf('%s:%s', $provider, $id);
  }
  
  /**
   * Finds the provider of the discovered file.
   *
   * The approach here is suboptimal because the provider is actually set in
   * the plugin definition after the getIdentifier is called. So we either do
   * this, or we forego the base class.
   *
   * @param string $file
   *   The discovered file.
   * @param array $provider_paths
   *   The associative array of the path to the provider.
   *
   * @return string
   *   The provider
   */
  private function findProvider(string $file, array $provider_paths) : string {
    $parts = explode(DIRECTORY_SEPARATOR, $file);
    array_pop($parts);
    if (empty($parts)) {
      return '';
    }
    $provider = $provider_paths[implode(DIRECTORY_SEPARATOR, $parts)] ?? '';
    return empty($provider) ? $this->findProvider(implode(DIRECTORY_SEPARATOR, $parts), $provider_paths) : $provider;
  }

}

Classes

Title Deprecated Summary
DirectoryWithMetadataDiscovery Does the actual finding of the directories with metadata files.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.