media_mpx-8.x-1.x-dev/src/CustomFieldDiscovery.php

src/CustomFieldDiscovery.php
<?php

namespace Drupal\media_mpx;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Drupal\Component\Annotation\Doctrine\StaticReflectionParser;
use Drupal\Component\Annotation\Reflection\MockFileFinder;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Utility\Crypt;
use Lullabot\Mpx\DataService\Annotation\CustomField;
use Lullabot\Mpx\DataService\CustomFieldInterface;
use Lullabot\Mpx\DataService\CustomFieldDiscoveryInterface;
use Lullabot\Mpx\DataService\DiscoveredCustomField;

/**
 * Discovers custom field implementations in any enabled Drupal module.
 *
 * @see \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
 */
class CustomFieldDiscovery implements CustomFieldDiscoveryInterface {

  /**
   * The possible plugin implementation namespaces.
   *
   * @var \Traversable
   */
  private $rootNamespacesIterator;

  /**
   * A cache (usually APC) for discovered annotations.
   *
   * @var \Drupal\Component\FileCache\FileCacheInterface
   */
  private $fileCache;

  /**
   * Constructs an AnnotatedClassDiscovery object.
   *
   * @param \Traversable $root_namespaces
   *   An object that implements \Traversable which contains the root paths
   *   keyed by the corresponding namespace to look for plugin implementations.
   */
  public function __construct(\Traversable $root_namespaces) {
    $this->rootNamespacesIterator = $root_namespaces;
    $plugin_definition_annotation_name = CustomField::class;
    $file_cache_suffix = str_replace('\\', '_', $plugin_definition_annotation_name);
    $file_cache_suffix .= ':' . Crypt::hashBase64(serialize([]));
    $this->fileCache = FileCacheFactory::get('annotation_discovery:' . $file_cache_suffix);
  }

  /**
   * Returns all the Custom Fields.
   *
   * @return array
   *   An array of all discovered data services, indexed by service name,
   *   object type, and namespace.
   */
  public function getCustomFields(): array {
    $definitions = [];

    // Clear the annotation loaders of any previous annotation classes.
    AnnotationRegistry::reset();
    // Register the namespaces of classes that can be used for annotations.
    // @todo Fix the following deprecation once we can update to
    //   doctrine/annotations:^2.0. This method has no replacement in the
    //   version required by drupal 8.9.x.
    AnnotationRegistry::registerLoader('class_exists');

    // Search for classes within all PSR-0 namespace locations.
    foreach ($this->getPluginNamespaces() as $namespace => $dirs) {
      $this->getDefinitions($definitions, $namespace, $dirs);
    }

    // Don't let annotation loaders pile up.
    AnnotationRegistry::reset();

    return $definitions;
  }

  /**
   * Return an array of possible plugin namespaces.
   *
   * @return array
   *   The possible plugin namespaces, with each array keyed by it's namespace.
   */
  private function getPluginNamespaces() {
    $plugin_namespaces = [];
    $namespaceSuffix = str_replace('/', '\\', '/Plugin/media_mpx/CustomField');
    foreach ($this->rootNamespacesIterator as $namespace => $dirs) {
      // Append the namespace suffix to the base namespace, to obtain the
      // plugin namespace; for example, 'Drupal\Views' may become
      // 'Drupal\Views\Plugin\Block'.
      $namespace .= $namespaceSuffix;
      foreach ((array) $dirs as $dir) {
        // Append the directory suffix to the PSR-4 base directory, to obtain
        // the directory where plugins are found. For example,
        // DRUPAL_ROOT . '/core/modules/views/src' may become
        // DRUPAL_ROOT . '/core/modules/views/src/Plugin/Block'.
        $plugin_namespaces[$namespace][] = $dir . '/Plugin/media_mpx/CustomField';
      }
    }

    return $plugin_namespaces;
  }

  /**
   * Set plugin definitions for a namespace and it's directories.
   *
   * @param array &$definitions
   *   The array of definitions to add to.
   * @param string $namespace
   *   The namespace implementations should belong to.
   * @param string[] $dirs
   *   An array of directory paths, relative to the app root.
   */
  private function getDefinitions(array &$definitions, string $namespace, array $dirs) {
    foreach ($dirs as $dir) {
      if (file_exists($dir)) {
        $this->fetchFromDirectory($definitions, $namespace, $dir);
      }
    }
  }

  /**
   * Fetch plugin definitions from a directory.
   *
   * @param array &$definitions
   *   The array of definitions to add to.
   * @param string $namespace
   *   The namespace implementations in the directory belong to.
   * @param string $dir
   *   The directory to search.
   */
  private function fetchFromDirectory(array &$definitions, string $namespace, string $dir) {
    $iterator = new \RecursiveIteratorIterator(
      new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
    );
    foreach ($iterator as $fileinfo) {
      if ($fileinfo->getExtension() == 'php') {
        $this->fetchFromFile($definitions, $namespace, $fileinfo, $iterator);
      }
    }
  }

  /**
   * Fetch the annotation from a file.
   *
   * @param array &$definitions
   *   The array of definitions to add to.
   * @param string $namespace
   *   The namespace the class belongs to.
   * @param \SplFileInfo $fileinfo
   *   The information about the current file.
   * @param \RecursiveIteratorIterator $iterator
   *   The iterator traversing the directory.
   */
  private function fetchFromFile(array &$definitions, string $namespace, \SplFileInfo $fileinfo, \RecursiveIteratorIterator $iterator) {
    $reader = new AnnotationReader();

    if ($this->cacheGet($definitions, $fileinfo)) {
      return;
    }

    $class = $this->parseClassName($namespace, $fileinfo, $iterator);

    // The filename is already known, so there is no need to find the
    // file. However, StaticReflectionParser needs a finder, so use a
    // mock version.
    $finder = MockFileFinder::create($fileinfo->getPathName());
    $parser = new StaticReflectionParser($class, $finder, TRUE);

    /** @var \Lullabot\Mpx\DataService\Annotation\CustomField $annotation */
    if (!$annotation = $reader->getClassAnnotation($parser->getReflectionClass(), CustomField::class)) {
      // Store a NULL object, so the file is not reparsed again.
      $this->fileCache->set($fileinfo->getPathName(), [NULL]);
      return;
    }

    if (!is_subclass_of($class, CustomFieldInterface::class)) {
      throw new \RuntimeException(sprintf('%s must implement %s.', $class, CustomFieldInterface::class));
    }

    $discovered = new DiscoveredCustomField(
      $class, $annotation
    );
    $definitions[$annotation->service][$annotation->objectType][$annotation->namespace] = $discovered;
    $this->cacheSet($fileinfo, $discovered);
  }

  /**
   * Get the definitions from the cache, if possible.
   *
   * @param array &$definitions
   *   The array of definitions to add to.
   * @param \SplFileInfo $fileinfo
   *   The information about the current file.
   *
   * @return bool
   *   TRUE if the cache was hit and $definitions has been populated, FALSE
   *   otherwise.
   */
  private function cacheGet(array &$definitions, \SplFileInfo $fileinfo): bool {
    if ($cached = $this->fileCache->get($fileinfo->getPathName())) {
      if (isset($cached['namespace'])) {
        // Explicitly unserialize this to create a new object instance.
        /** @var \Lullabot\Mpx\DataService\DiscoveredCustomField $discovered */
        $discovered = unserialize($cached['content']);
        $definitions[$cached['service']][$cached['objectType']][$cached['namespace']] = $discovered;

        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Parse the class name from a file.
   *
   * @param string $namespace
   *   The namespace the class belongs to.
   * @param \SplFileInfo $fileinfo
   *   The information about the current file.
   * @param \RecursiveIteratorIterator $iterator
   *   The iterator traversing the directory.
   *
   * @return string
   *   The fully-qualified class name.
   */
  private function parseClassName(string $namespace, \SplFileInfo $fileinfo, \RecursiveIteratorIterator $iterator): string {
    $sub_path = $iterator->getSubIterator()->getSubPath();
    $sub_path = $sub_path ? str_replace(DIRECTORY_SEPARATOR, '\\', $sub_path) . '\\' : '';
    $class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
    return $class;
  }

  /**
   * Set a discovered custom field class data into the cache.
   *
   * @param \SplFileInfo $fileinfo
   *   The information about the current file.
   * @param \Lullabot\Mpx\DataService\DiscoveredCustomField $discovered
   *   The discovered Custom Field class.
   */
  private function cacheSet(\SplFileInfo $fileinfo, DiscoveredCustomField $discovered) {
    $annotation = $discovered->getAnnotation();
    // Explicitly serialize this to create a new object instance.
    $this->fileCache->set($fileinfo->getPathName(), [
      'service' => $annotation->service,
      'objectType' => $annotation->objectType,
      'namespace' => $annotation->namespace,
      'content' => serialize($discovered),
    ]);
  }

}

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

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