function AttributeClassDiscovery::getDefinitions

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Component/Plugin/Discovery/AttributeClassDiscovery.php \Drupal\Component\Plugin\Discovery\AttributeClassDiscovery::getDefinitions()
  2. 10 core/lib/Drupal/Component/Plugin/Discovery/AttributeClassDiscovery.php \Drupal\Component\Plugin\Discovery\AttributeClassDiscovery::getDefinitions()

File

core/lib/Drupal/Component/Plugin/Discovery/AttributeClassDiscovery.php, line 83

Class

AttributeClassDiscovery
Defines a discovery mechanism to find plugins with attributes.

Namespace

Drupal\Component\Plugin\Discovery

Code

public function getDefinitions() {
  $definitions = [];
  $autoloader = new MissingClassDetectionClassLoader();
  spl_autoload_register([
    $autoloader,
    'loadClass',
  ]);
  // Search for classes within all PSR-4 namespace locations.
  foreach ($this->getPluginNamespaces() as $namespace => $dirs) {
    foreach ($dirs as $dir) {
      if (file_exists($dir)) {
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS));
        foreach ($iterator as $fileinfo) {
          assert($fileinfo instanceof \SplFileInfo);
          if ($fileinfo->getExtension() === 'php') {
            if ($cached = $this->fileCache
              ->get($fileinfo->getPathName())) {
              if (isset($cached['id'])) {
                // Explicitly unserialize this to create a new object
                // instance.
                $dependencies = isset($cached['dependencies']) ? unserialize($cached['dependencies']) : [];
                if (!$this->hasMissingDependencies($dependencies ?? [])) {
                  $definitions[$cached['id']] = unserialize($cached['content']);
                }
              }
              continue;
            }
            $sub_path = $iterator->getSubIterator()
              ->getSubPath();
            $sub_path = $sub_path ? str_replace(DIRECTORY_SEPARATOR, '\\', $sub_path) . '\\' : '';
            $class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
            // 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. However, this is an unavoidable
            // situation with optional dependencies and plugins. Therefore,
            // silently skip over this class and avoid writing to the cache,
            // so that it is scanned each time. This ensures that the plugin
            // definition will be found if the module it requires is
            // enabled.
            // PHP handles missing traits as an unrecoverable error.
            // Register a special classloader that prevents a missing
            // trait from causing an error. When it encounters a missing
            // trait it stores that it was unable to find the trait.
            // Because the classloader will result in the class being
            // autoloaded we store an array of classes to skip if this
            // method is called again.
            // If discovery runs twice in a single request, first without
            // the module that defines the missing trait, and second after it
            // has been installed, we want the plugin to be discovered in the
            // second case. Therefore, if a module has been added to skipped
            // classes, check if the trait's namespace is available.
            // If it is available, allow discovery.
            // @todo a fix for this has been committed to PHP. Once that is
            // available, attempt to make the class loader registration
            // conditional on PHP version, then remove the logic entirely once
            // Drupal requires PHP 8.5.
            // @see https://github.com/php/php-src/issues/17959
            // @see https://github.com/php/php-src/commit/8731c95b35f6838bacd12a07c50886e020aad5a6
            if (array_key_exists($class, self::$skipClasses)) {
              $missing_classes = self::$skipClasses[$class];
              foreach ($missing_classes as $missing_class) {
                $missing_class_namespace = $this->getTwoLevelNamespace($missing_class);
                // If we arrive here a second time, and the namespace is still
                // unavailable, ensure discovery is skipped. Without this
                // explicit check for already checked classes, an invalid
                // class would be discovered, because once we've detected a
                // a missing trait and aliased the stub instead, this can't
                // happen again, so the class appears valid. However, if the
                // namespace has become available in the meantime, assume that
                // the class actually should be discovered since this probably
                // means the optional module it depends on has been enabled.
                if (!in_array($missing_class_namespace, $this->rootTwoLevelNamespaces)) {
                  $autoloader->reset();
                  continue 2;
                }
              }
            }
            try {
              $class_exists = class_exists($class, TRUE);
              if (!$class_exists || \count($autoloader->getMissingTraits()) > 0) {
                // @todo remove this workaround once PHP treats missing traits
                // as catchable fatal errors.
                if (\count($autoloader->getMissingTraits()) > 0) {
                  self::$skipClasses[$class] = $autoloader->getMissingTraits();
                }
                $autoloader->reset();
                continue;
              }
            } catch (\Error $e) {
              if (!$autoloader->hasMissingClass()) {
                // @todo Add test coverage for unexpected Error exceptions in
                // https://www.drupal.org/project/drupal/issues/3520811.
                $autoloader->reset();
                spl_autoload_unregister([
                  $autoloader,
                  'loadClass',
                ]);
                throw $e;
              }
              $autoloader->reset();
              continue;
            }
            $result = $this->parseClass($class, $fileinfo);
            ['id' => $id, 'content' => $content] = $result;
            if ($id) {
              if (!$this->hasMissingDependencies($result['dependencies'] ?? [])) {
                $definitions[$id] = $content;
              }
              // Explicitly serialize this to create a new object instance.
              if (!isset(self::$skipClasses[$class])) {
                $this->fileCache
                  ->set($fileinfo->getPathName(), [
                  'id' => $id,
                  'content' => serialize($content),
                  'dependencies' => serialize($result['dependencies'] ?? NULL),
                ]);
              }
            }
            else {
              // Store a NULL object, so that the file is not parsed again.
              $this->fileCache
                ->set($fileinfo->getPathName(), [
                NULL,
              ]);
            }
          }
        }
      }
    }
  }
  spl_autoload_unregister([
    $autoloader,
    'loadClass',
  ]);
  return $definitions;
}

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