You are here

class RecursiveExtensionFilterIterator in Drupal 8

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php \Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator

Filters a RecursiveDirectoryIterator to discover extensions.

To ensure the best possible performance for extension discovery, this filter implementation hard-codes a range of assumptions about directories in which Drupal extensions may appear and in which not. Every unnecessary subdirectory tree recursion is avoided.

The list of globally ignored directory names is defined in the RecursiveExtensionFilterIterator::$blacklist property.

In addition, all 'config' directories are skipped, unless the directory path ends with 'modules/config', so as to still find the config module provided by Drupal core and still allow that module to be overridden with a custom config module.

Lastly, ExtensionDiscovery instructs this filter to additionally skip all 'tests' directories at regular runtime, since just with Drupal core only, the discovery process yields 4x more extensions when tests are not ignored.

@todo Use RecursiveCallbackFilterIterator instead of the $acceptTests parameter forwarding once PHP 5.4 is available. https://www.drupal.org/node/2532228

Hierarchy

Expanded class hierarchy of RecursiveExtensionFilterIterator

See also

ExtensionDiscovery::scan()

ExtensionDiscovery::scanDirectory()

1 file declares its use of RecursiveExtensionFilterIterator
ExtensionDiscovery.php in core/lib/Drupal/Core/Extension/ExtensionDiscovery.php

File

core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php, line 32

Namespace

Drupal\Core\Extension\Discovery
View source
class RecursiveExtensionFilterIterator extends \RecursiveFilterIterator {

  /**
   * List of base extension type directory names to scan.
   *
   * Only these directory names are considered when starting a filesystem
   * recursion in a search path.
   *
   * @var array
   */
  protected $whitelist = [
    'profiles',
    'modules',
    'themes',
  ];

  /**
   * List of directory names to skip when recursing.
   *
   * These directories are globally ignored in the recursive filesystem scan;
   * i.e., extensions (of all types) are not able to use any of these names,
   * because their directory names will be skipped.
   *
   * @var array
   */
  protected $blacklist = [
    // Object-oriented code subdirectories.
    'src',
    'lib',
    'vendor',
    // Front-end.
    'assets',
    'css',
    'files',
    'images',
    'js',
    'misc',
    'templates',
    // Legacy subdirectories.
    'includes',
    // Test subdirectories.
    'fixtures',
    // @todo ./tests/Drupal should be ./tests/src/Drupal
    'Drupal',
  ];

  /**
   * Whether to include test directories when recursing.
   *
   * @var bool
   */
  protected $acceptTests = FALSE;

  /**
   * Construct a RecursiveExtensionFilterIterator.
   *
   * @param \RecursiveIterator $iterator
   *   The iterator to filter.
   * @param array $blacklist
   *   (optional) Add to the blacklist of directories that should be filtered
   *   out during the iteration.
   */
  public function __construct(\RecursiveIterator $iterator, array $blacklist = []) {
    parent::__construct($iterator);
    $this->blacklist = array_merge($this->blacklist, $blacklist);
  }

  /**
   * Controls whether test directories will be scanned.
   *
   * @param bool $flag
   *   Pass FALSE to skip all test directories in the discovery. If TRUE,
   *   extensions in test directories will be discovered and only the global
   *   directory blacklist in RecursiveExtensionFilterIterator::$blacklist is
   *   applied.
   */
  public function acceptTests($flag = FALSE) {
    $this->acceptTests = $flag;
    if (!$this->acceptTests) {
      $this->blacklist[] = 'tests';
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getChildren() {
    $filter = parent::getChildren();

    // Pass on the blacklist.
    $filter->blacklist = $this->blacklist;

    // Pass the $acceptTests flag forward to child iterators.
    $filter
      ->acceptTests($this->acceptTests);
    return $filter;
  }

  /**
   * {@inheritdoc}
   */
  public function accept() {
    $name = $this
      ->current()
      ->getFilename();

    // FilesystemIterator::SKIP_DOTS only skips '.' and '..', but not hidden
    // directories (like '.git').
    if ($name[0] == '.') {
      return FALSE;
    }
    if ($this
      ->isDir()) {

      // If this is a subdirectory of a base search path, only recurse into the
      // fixed list of expected extension type directory names. Required for
      // scanning the top-level/root directory; without this condition, we would
      // recurse into the whole filesystem tree that possibly contains other
      // files aside from Drupal.
      if ($this
        ->current()
        ->getSubPath() == '') {
        return in_array($name, $this->whitelist, TRUE);
      }

      // 'config' directories are special-cased here, because every extension
      // contains one. However, those default configuration directories cannot
      // contain extensions. The directory name cannot be globally skipped,
      // because core happens to have a directory of an actual module that is
      // named 'config'. By explicitly testing for that case, we can skip all
      // other config directories, and at the same time, still allow the core
      // config module to be overridden/replaced in a profile/site directory
      // (whereas it must be located directly in a modules directory).
      if ($name == 'config') {
        return substr($this
          ->current()
          ->getPathname(), -14) == 'modules/config';
      }

      // Accept the directory unless the name is blacklisted.
      return !in_array($name, $this->blacklist, TRUE);
    }
    else {

      // Only accept extension info files.
      return substr($name, -9) == '.info.yml';
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
RecursiveExtensionFilterIterator::$acceptTests protected property Whether to include test directories when recursing.
RecursiveExtensionFilterIterator::$blacklist protected property List of directory names to skip when recursing.
RecursiveExtensionFilterIterator::$whitelist protected property List of base extension type directory names to scan.
RecursiveExtensionFilterIterator::accept public function
RecursiveExtensionFilterIterator::acceptTests public function Controls whether test directories will be scanned.
RecursiveExtensionFilterIterator::getChildren public function
RecursiveExtensionFilterIterator::__construct public function Construct a RecursiveExtensionFilterIterator.