function AttributeClassDiscovery::getDefinitions

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

Overrides DiscoveryTrait::getDefinitions

1 call to AttributeClassDiscovery::getDefinitions()
AttributeDiscoveryWithAnnotations::getDefinitions in core/lib/Drupal/Core/Plugin/Discovery/AttributeDiscoveryWithAnnotations.php
Gets the definition of all plugins for this type.
1 method overrides AttributeClassDiscovery::getDefinitions()
AttributeDiscoveryWithAnnotations::getDefinitions in core/lib/Drupal/Core/Plugin/Discovery/AttributeDiscoveryWithAnnotations.php
Gets the definition of all plugins for this type.

File

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

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.
                                $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 = implode('\\', array_slice(explode('\\', $missing_class), 0, 2));
                                // 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 (!isset($this->getPluginNamespaces()[$missing_class_namespace])) {
                                    $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;
                        }
                        [
                            'id' => $id,
                            'content' => $content,
                        ] = $this->parseClass($class, $fileinfo);
                        if ($id) {
                            $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),
                                ]);
                            }
                        }
                        else {
                            // Store a NULL object, so that the file is not parsed again.
                            $this->fileCache
                                ->set($fileinfo->getPathName(), [
                                NULL,
                            ]);
                        }
                    }
                }
            }
        }
    }
    spl_autoload_unregister([
        $autoloader,
        'loadClass',
    ]);
    // 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;
}

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