toolshed-8.x-1.x-dev/src/Discovery/AttributeDiscovery.php

src/Discovery/AttributeDiscovery.php
<?php

namespace Drupal\toolshed\Discovery;

use Drupal\Component\Discovery\DiscoverableInterface;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\FileCache\FileCacheInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\toolshed\Strategy\Attribute\StrategyInterface;

/**
 * Discover strategies in modules by searching for strategy class attributes.
 *
 * The class attributes provide the strategy definition, and unlike the plugin
 * discovery, the strategies are kept grouped by their providers. This allows
 * better potential for a strategy manager to identify module vs theme provided
 * strategies.
 */
class AttributeDiscovery implements DiscoverableInterface {

  /**
   * The filecache to store attribute discoveries.
   *
   * @var \Drupal\Component\FileCache\FileCacheInterface
   */
  protected FileCacheInterface $fileCache;

  /**
   * The sub-directory to search for strategy class implementations.
   *
   * @var string
   */
  protected string $subdir;

  /**
   * Create new instance of the strategy AttributeDiscovery classe.
   *
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The module extension handler.
   * @param string $subdir
   *   The sub-directory to search for strategy classes.
   * @param class-string<\Drupal\toolshed\Strategy\Attribute\StrategyInterface> $attribute
   *   The attribute class which provides the strategy definition.
   */
  public function __construct(protected ModuleHandlerInterface $moduleHandler, string $subdir, protected string $attribute) {
    $this->subdir = trim($subdir, DIRECTORY_SEPARATOR);
    $this->fileCache = FileCacheFactory::get('attribute_discovery:' . str_replace('/', '_', $attribute));
  }

  /**
   * {@inheritdoc}
   */
  public function findAll(): array {
    $definitions = [];
    $nsSuffix = str_replace(DIRECTORY_SEPARATOR, '\\', $this->subdir);

    // Search for classes within all PSR-4 namespace locations.
    foreach ($this->moduleHandler->getModuleList() as $name => $extension) {
      $namespace = implode('\\', ['Drupal', $name, $nsSuffix]);
      $dir = implode(DIRECTORY_SEPARATOR, [
        '.' . base_path(), $extension->getPath(), 'src', $this->subdir,
      ]);

      if (file_exists($dir)) {
        $prefixOffset = strlen($dir);
        $iter = new \RecursiveCallbackFilterIterator(
          new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
          static fn($file) => 'php' === $file->getExtension()
        );

        foreach ($iter as $fileinfo) {
          if ($cached = $this->fileCache->get($fileinfo->getPathName())) {
            if (isset($cached['id'])) {
              // Explicitly unserialize this to create a new object instance.
              $definitions[$name][$cached['id']] = unserialize($cached['content'], [
                'allowed_classes' => [
                  '\Stringable',
                  TranslatableMarkup::class,
                ],
              ]);
            }
            continue;
          }

          try {
            $class = $namespace . str_replace(DIRECTORY_SEPARATOR, '\\', substr($fileinfo->getPath(), $prefixOffset));
            $class .= '\\' . $fileinfo->getBasename('.php');

            /** @var \Drupal\toolshed\Strategy\Attribute\StrategyInterface $attr */
            $attr = $this->parseClass($class, $fileinfo);
            if ($attr) {
              $id = $attr->setProvider($name)->id();
              $definitions[$name][$id] = $attr->get();

              $this->fileCache->set($fileinfo->getPathName(), [
                'id' => $id,
                'content' => serialize($definitions[$name][$id]),
              ]);
            }
            else {
              $this->fileCache->set($fileinfo->getPathName(), []);
            }
          }
          catch (\Error $e) {
            // Plugins may rely on Attribute classes defined by modules that
            // are not installed. In such a case, a 'class not found' error
            // may be thrown from reflection. Therefore silently skip over this
            // class and avoid writing to the cache so that it can be detected
            // when the dependencies are enabled.
            if (!preg_match('/(Class|Interface) .* not found$/', $e->getMessage())) {
              throw $e;
            }
          }
        }
      }
    }

    // Plugin discovery is a memory expensive process due to reflection and the
    // number of files involved. Collect cycles at the end of discovery to be as
    // efficient as possible.
    gc_collect_cycles();
    return $definitions;
  }

  /**
   * Parses attributes from a class.
   *
   * @param class-string $class
   *   The class to parse.
   * @param \SplFileInfo $fileinfo
   *   The SPL file information for the class.
   *
   * @return \Drupal\toolshed\Strategy\Attribute\StrategyInterface|null
   *   An array with the keys 'id' and 'content'. The 'id' is the plugin ID and
   *   'content' is the plugin definition.
   *
   * @throws \ReflectionException
   * @throws \Error
   */
  protected function parseClass(string $class, \SplFileInfo $fileinfo): ?StrategyInterface {
    $reflected = new \ReflectionClass($class);

    if ($attributes = $reflected->getAttributes($this->attribute, \ReflectionAttribute::IS_INSTANCEOF)) {
      /** @var \Drupal\toolshed\Strategy\Attribute\StrategyInterface $attribute */
      $attribute = $attributes[0]->newInstance();
      $attribute->setClass($class);

      return $attribute;
    }

    return NULL;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc