Plugin.php
Same filename in this branch
- 11.1.x vendor/dealerdirect/phpcodesniffer-composer-installer/src/Plugin.php
- 11.1.x vendor/phpstan/extension-installer/src/Plugin.php
- 11.1.x vendor/php-http/discovery/src/Composer/Plugin.php
- 11.1.x vendor/composer/installers/src/Composer/Installers/Plugin.php
- 11.1.x composer/Plugin/Scaffold/Plugin.php
- 11.1.x core/lib/Drupal/Component/Annotation/Plugin.php
- 11.1.x core/lib/Drupal/Component/Plugin/Attribute/Plugin.php
Namespace
Nevay\SPI\ComposerFile
-
vendor/
tbachert/ spi/ src/ Composer/ Plugin.php
View source
<?php
declare (strict_types=1);
namespace Nevay\SPI\Composer;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Nevay\SPI\ServiceLoader;
use Nevay\SPI\ServiceProviderRequirementRuntimeValidated;
use ReflectionAttribute;
use ReflectionClass;
use RuntimeException;
use function array_diff;
use function array_fill_keys;
use function array_unique;
use function class_exists;
use function getcwd;
use function implode;
use function is_string;
use function json_encode;
use function preg_match;
use function realpath;
use function sprintf;
use function var_export;
use const JSON_UNESCAPED_SLASHES;
use const JSON_UNESCAPED_UNICODE;
final class Plugin implements PluginInterface, EventSubscriberInterface {
private const FQCN_REGEX = '/^\\\\?[a-zA-Z_\\x80-\\xff][a-zA-Z0-9_\\x80-\\xff]*(?:\\\\[a-zA-Z_\\x80-\\xff][a-zA-Z0-9_\\x80-\\xff]*)*$/';
public static function getSubscribedEvents() : array {
return [
ScriptEvents::PRE_AUTOLOAD_DUMP => 'preAutoloadDump',
];
}
public function activate(Composer $composer, IOInterface $io) : void {
// no-op
}
public function deactivate(Composer $composer, IOInterface $io) : void {
// no-op
}
public function uninstall(Composer $composer, IOInterface $io) : void {
$filesystem = new Filesystem();
$vendorDir = $this->vendorDir($composer, $filesystem);
$filesystem->remove($vendorDir . '/composer/GeneratedServiceProviderData.php');
}
public function preAutoloadDump(Event $event) : void {
$package = $event->getComposer()
->getPackage();
$packages = $event->getComposer()
->getRepositoryManager()
->getLocalRepository()
->getCanonicalPackages();
$packageMap = $event->getComposer()
->getAutoloadGenerator()
->buildPackageMap($event->getComposer()
->getInstallationManager(), $package, $packages);
$map = $event->getComposer()
->getAutoloadGenerator()
->parseAutoloads($packageMap, $package);
$loader = $event->getComposer()
->getAutoloadGenerator()
->createLoader($map, $event->getComposer()
->getConfig()
->get('vendor-dir'));
$loader->register();
try {
$this->dumpGeneratedServiceProviderData($event);
} finally {
$loader->unregister();
}
}
private function dumpGeneratedServiceProviderData(Event $event) : void {
$match = '';
foreach ($this->serviceProviders($event->getComposer(), $event->getIO()) as $service => $providers) {
if (!preg_match(self::FQCN_REGEX, $service)) {
$event->getIO()
->warning(sprintf('Invalid extra.spi configuration, expected class name, got "%s" (%s)', $service, implode(', ', array_unique($providers))));
continue;
}
if (!ServiceLoader::serviceAvailable($service)) {
$event->getIO()
->info(sprintf('Skipping extra.spi service "%s", service not available (%s)', $service, implode(', ', array_unique($providers))));
continue;
}
if ($service[0] !== '\\') {
$service = '\\' . $service;
}
$match .= "\n {$service}::class => [";
foreach ($providers as $provider => $package) {
if (!preg_match(self::FQCN_REGEX, $provider)) {
$event->getIO()
->warning(sprintf('Invalid extra.spi configuration, expected class name, got "%s" for "%s" (%s)', $provider, $service, $package));
continue;
}
if (!class_exists($provider)) {
$event->getIO()
->info(sprintf('Skipping extra.spi configuration, provider class "%s" for "%s" does not exist (%s)', $provider, $service, $package));
continue;
}
if (!ServiceLoader::providerAvailable($provider, skipRuntimeValidatedRequirements: true)) {
$event->getIO()
->info(sprintf('Skipping extra.spi provider "%s" for "%s", provider not available (%s)', $provider, $service, $package));
continue;
}
if ($provider[0] !== '\\') {
$provider = '\\' . $provider;
}
if ($condition = self::providerRuntimeValidatedRequirements($provider)) {
$match .= "\n ...(({$condition}) ? [";
$match .= "\n {$provider}::class, // {$package}";
$match .= "\n ] : []),";
}
else {
$match .= "\n {$provider}::class, // {$package}";
}
}
$match .= "\n ],";
}
$code = <<<PHP
<?php declare(strict_types=1);
namespace Nevay\\SPI;
/**
* @internal
*/
final class GeneratedServiceProviderData {
public const VERSION = 1;
/**
* @param class-string \$service
* @return list<class-string>
*/
public static function providers(string \$service): array {
return match (\$service) {
default => [],{<span class="php-variable">$match</span>}
};
}
}
PHP;
$filesystem = new Filesystem();
$vendorDir = $this->vendorDir($event->getComposer(), $filesystem);
$filesystem->ensureDirectoryExists($vendorDir . '/composer');
$filesystem->filePutContentsIfModified($vendorDir . '/composer/GeneratedServiceProviderData.php', $code);
$package = $event->getComposer()
->getPackage();
$autoload = $package->getAutoload();
$autoload['classmap'][] = $vendorDir . '/composer/GeneratedServiceProviderData.php';
$package->setAutoload($autoload);
}
private static function providerRuntimeValidatedRequirements(string $provider) : ?string {
// The current implementation is suboptimal if multiple runtime validated requirements are specified
// - should check all hashes of not satisfied requirements before calling ::isSatisfied()
// - should deduplicate requirements
$condition = var_export(true, true);
/** @var ReflectionAttribute<ServiceProviderRequirementRuntimeValidated> $attribute */
foreach ((new ReflectionClass($provider))->getAttributes(ServiceProviderRequirementRuntimeValidated::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$requirement = $attribute->newInstance();
$class = '\\' . $requirement::class;
$args = '';
foreach ($attribute->getArguments() as $key => $value) {
$args and $args .= ', ';
if (is_string($key)) {
$args .= $key;
$args .= ': ';
}
$args .= var_export($value, true);
}
$hash = var_export($requirement->hash(), true);
$condition .= $requirement->isSatisfied() ? " && ((\$r = new {$class}({$args}))->hash() === {$hash} || \$r->isSatisfied())" : " && ((\$r = new {$class}({$args}))->hash() !== {$hash} && \$r->isSatisfied())";
}
if (!isset($requirement)) {
return null;
}
return $condition;
}
private function vendorDir(Composer $composer, Filesystem $filesystem) : string {
return $filesystem->normalizePath($composer->getConfig()
->get('vendor-dir'));
}
/**
* @return array<class-string, array<class-string, string>>
*/
private function serviceProviders(Composer $composer, IOInterface $io) : array {
$mappings = [];
$this->serviceProvidersFromExtraSpi($composer->getPackage(), $mappings);
$this->serviceProvidersFromAutoloadFiles($composer->getPackage(), $mappings, self::getCwd(), $io);
foreach ($composer->getRepositoryManager()
->getLocalRepository()
->getPackages() as $package) {
$this->serviceProvidersFromExtraSpi($package, $mappings);
if (($installPath = $composer->getInstallationManager()
->getInstallPath($package)) !== null) {
$this->serviceProvidersFromAutoloadFiles($package, $mappings, $installPath, $io);
}
}
return $mappings;
}
private function serviceProvidersFromExtraSpi(PackageInterface $package, array &$mappings) : void {
foreach ($package->getExtra()['spi'] ?? [] as $service => $providers) {
$providers = (array) $providers;
$mappings[$service] ??= [];
$mappings[$service] += array_fill_keys($providers, $package->getPrettyString() . ' (extra.spi)');
}
}
private function serviceProvidersFromAutoloadFiles(PackageInterface $package, array &$mappings, string $installPath, IOInterface $io) : void {
$autoloadFiles = $package->getAutoload()['files'] ?? [];
$spiAutoloadFiles = $package->getExtra()['spi-config']['autoload-files'] ?? false ?: [];
$spiPruneAutoloadFiles = $package->getExtra()['spi-config']['prune-autoload-files'] ?? null;
if ($spiAutoloadFiles === true) {
$spiAutoloadFiles = $autoloadFiles;
}
if ($spiPruneAutoloadFiles === true) {
$spiPruneAutoloadFiles = $spiAutoloadFiles;
}
$includeFile = (static fn(string $file) => require $file)->bindTo(null, null);
foreach ($spiAutoloadFiles as $index => $file) {
$io->debug(sprintf('Loading service providers from "%s" (%s)', $file, $package->getPrettyString()));
if (!($includedProviders = ServiceLoader::collectProviders($includeFile, $installPath . '/' . $file))) {
unset($spiAutoloadFiles[$index]);
continue;
}
foreach ($includedProviders as $service => $providers) {
$mappings[$service] ??= [];
$mappings[$service] += array_fill_keys($providers, $package->getPrettyString() . ' (' . json_encode($file, flags: JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ')');
}
}
$spiPruneAutoloadFiles ??= $spiAutoloadFiles;
if ($spiPruneAutoloadFiles && $autoloadFiles && $package instanceof Package) {
$io->debug(sprintf('Pruning autoload.files (%s): %s', $package->getPrettyString(), implode(', ', $spiPruneAutoloadFiles)));
$autoload = $package->getAutoload();
$autoload['files'] = array_diff($autoloadFiles, $spiPruneAutoloadFiles);
$package->setAutoload($autoload);
}
}
/**
* @see Platform::getCwd()
*/
private static function getCwd() : string {
$cwd = getcwd();
if ($cwd === false) {
$cwd = realpath('');
}
if ($cwd === false) {
throw new RuntimeException('Could not determine the current working directory');
}
return $cwd;
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
Plugin |