
| Current Path : /var/www/html/rocksensor/web/core/lib/Drupal/Component/Plugin/Discovery/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/html/rocksensor/web/core/lib/Drupal/Component/Plugin/Discovery/AttributeClassDiscovery.php |
<?php
namespace Drupal\Component\Plugin\Discovery;
use Drupal\Component\Plugin\Attribute\AttributeInterface;
use Drupal\Component\Plugin\Attribute\Plugin;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\FileCache\FileCacheInterface;
/**
* Defines a discovery mechanism to find plugins with attributes.
*/
class AttributeClassDiscovery implements DiscoveryInterface {
use DiscoveryTrait;
/**
* The file cache object.
*/
protected FileCacheInterface $fileCache;
/**
* Constructs a new instance.
*
* @param string[] $pluginNamespaces
* (optional) An array of namespace that may contain plugin implementations.
* Defaults to an empty array.
* @param string $pluginDefinitionAttributeName
* (optional) The name of the attribute that contains the plugin definition.
* Defaults to 'Drupal\Component\Plugin\Attribute\Plugin'.
*/
public function __construct(
protected readonly array $pluginNamespaces = [],
protected readonly string $pluginDefinitionAttributeName = Plugin::class,
) {
$file_cache_suffix = str_replace('\\', '_', $this->pluginDefinitionAttributeName);
$this->fileCache = FileCacheFactory::get('attribute_discovery:' . $this->getFileCacheSuffix($file_cache_suffix));
}
/**
* Gets the file cache suffix.
*
* This method allows classes that extend this class to add additional
* information to the file cache collection name.
*
* @param string $default_suffix
* The default file cache suffix.
*
* @return string
* The file cache suffix.
*/
protected function getFileCacheSuffix(string $default_suffix): string {
return $default_suffix;
}
/**
* {@inheritdoc}
*/
public function getDefinitions() {
$definitions = [];
// 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');
try {
['id' => $id, 'content' => $content] = $this->parseClass($class, $fileinfo);
if ($id) {
$definitions[$id] = $content;
// Explicitly serialize this to create a new object instance.
$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]);
}
}
// 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.
catch (\Error $e) {
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 array
* 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): array {
// @todo Consider performance improvements over using reflection.
// @see https://www.drupal.org/project/drupal/issues/3395260.
$reflection_class = new \ReflectionClass($class);
$id = $content = NULL;
if ($attributes = $reflection_class->getAttributes($this->pluginDefinitionAttributeName, \ReflectionAttribute::IS_INSTANCEOF)) {
/** @var \Drupal\Component\Plugin\Attribute\AttributeInterface $attribute */
$attribute = $attributes[0]->newInstance();
$this->prepareAttributeDefinition($attribute, $class);
$id = $attribute->getId();
$content = $attribute->get();
}
return ['id' => $id, 'content' => $content];
}
/**
* Prepares the attribute definition.
*
* @param \Drupal\Component\Plugin\Attribute\AttributeInterface $attribute
* The attribute derived from the plugin.
* @param string $class
* The class used for the plugin.
*/
protected function prepareAttributeDefinition(AttributeInterface $attribute, string $class): void {
$attribute->setClass($class);
}
/**
* Gets an array of PSR-4 namespaces to search for plugin classes.
*
* @return string[][]
*/
protected function getPluginNamespaces(): array {
return $this->pluginNamespaces;
}
}