class Plugin
Same name in this branch
- 11.1.x vendor/dealerdirect/phpcodesniffer-composer-installer/src/Plugin.php \PHPCSStandards\Composer\Plugin\Installers\PHPCodeSniffer\Plugin
- 11.1.x vendor/phpstan/extension-installer/src/Plugin.php \PHPStan\ExtensionInstaller\Plugin
- 11.1.x vendor/composer/installers/src/Composer/Installers/Plugin.php \Composer\Installers\Plugin
- 11.1.x vendor/tbachert/spi/src/Composer/Plugin.php \Nevay\SPI\Composer\Plugin
- 11.1.x composer/Plugin/Scaffold/Plugin.php \Drupal\Composer\Plugin\Scaffold\Plugin
- 11.1.x core/lib/Drupal/Component/Annotation/Plugin.php \Drupal\Component\Annotation\Plugin
- 11.1.x core/lib/Drupal/Component/Plugin/Attribute/Plugin.php \Drupal\Component\Plugin\Attribute\Plugin
Auto-installs missing implementations.
When a dependency requires both this package and one of the supported `*-implementation` virtual packages, this plugin will auto-install a well-known implementation if none is found. The plugin will first look at already installed packages and figure out the preferred implementation to install based on the below stickyness rules (or on the first listed implementation if no rules match.)
Don't miss updating src/Strategy/Common*Strategy.php when adding a new supported package.
@author Nicolas Grekas <p@tchwork.com>
@internal
Hierarchy
- class \Http\Discovery\Composer\Plugin implements \Composer\Plugin\PluginInterface, \Composer\EventDispatcher\EventSubscriberInterface
Expanded class hierarchy of Plugin
14 string references to 'Plugin'
- Action::create in core/
modules/ system/ src/ Entity/ Action.php - Constructs a new entity object, without permanently saving it.
- block.schema.yml in core/
modules/ block/ config/ schema/ block.schema.yml - core/modules/block/config/schema/block.schema.yml
- DisplayPluginBase::calculateCacheMetadata in core/
modules/ views/ src/ Plugin/ views/ display/ DisplayPluginBase.php - Calculates the display's cache metadata by inspecting each handler/plugin.
- DisplayPluginBase::getAllPlugins in core/
modules/ views/ src/ Plugin/ views/ display/ DisplayPluginBase.php - Gets all the plugins used by the display.
- EntityBlock::getEntityId in core/
modules/ block/ src/ Plugin/ migrate/ destination/ EntityBlock.php - Gets the entity ID of the row.
File
-
vendor/
php-http/ discovery/ src/ Composer/ Plugin.php, line 39
Namespace
Http\Discovery\ComposerView source
class Plugin implements PluginInterface, EventSubscriberInterface {
/**
* Describes, for every supported virtual implementation, which packages
* provide said implementation and which extra dependencies each package
* requires to provide the implementation.
*/
private const PROVIDE_RULES = [
'php-http/async-client-implementation' => [
'symfony/http-client:>=6.3' => [
'guzzlehttp/promises',
'psr/http-factory-implementation',
'php-http/httplug',
],
'symfony/http-client' => [
'guzzlehttp/promises',
'php-http/message-factory',
'psr/http-factory-implementation',
'php-http/httplug',
],
'php-http/guzzle7-adapter' => [],
'php-http/guzzle6-adapter' => [],
'php-http/curl-client' => [],
'php-http/react-adapter' => [],
],
'php-http/client-implementation' => [
'symfony/http-client:>=6.3' => [
'psr/http-factory-implementation',
'php-http/httplug',
],
'symfony/http-client' => [
'php-http/message-factory',
'psr/http-factory-implementation',
'php-http/httplug',
],
'php-http/guzzle7-adapter' => [],
'php-http/guzzle6-adapter' => [],
'php-http/cakephp-adapter' => [],
'php-http/curl-client' => [],
'php-http/react-adapter' => [],
'php-http/buzz-adapter' => [],
'php-http/artax-adapter' => [],
'kriswallsmith/buzz:^1' => [],
],
'psr/http-client-implementation' => [
'symfony/http-client' => [
'psr/http-factory-implementation',
'psr/http-client',
],
'guzzlehttp/guzzle' => [],
'kriswallsmith/buzz:^1' => [],
],
'psr/http-message-implementation' => [
'php-http/discovery' => [
'psr/http-factory-implementation',
],
],
'psr/http-factory-implementation' => [
'nyholm/psr7' => [],
'guzzlehttp/psr7:>=2' => [],
'slim/psr7' => [],
'laminas/laminas-diactoros' => [],
'phalcon/cphalcon:^4' => [],
'http-interop/http-factory-guzzle' => [],
'http-interop/http-factory-diactoros' => [],
'http-interop/http-factory-slim' => [],
'httpsoft/http-message' => [],
],
];
/**
* Describes which package should be preferred on the left side
* depending on which one is already installed on the right side.
*/
private const STICKYNESS_RULES = [
'symfony/http-client' => 'symfony/framework-bundle',
'php-http/guzzle7-adapter' => 'guzzlehttp/guzzle:^7',
'php-http/guzzle6-adapter' => 'guzzlehttp/guzzle:^6',
'php-http/guzzle5-adapter' => 'guzzlehttp/guzzle:^5',
'php-http/cakephp-adapter' => 'cakephp/cakephp',
'php-http/react-adapter' => 'react/event-loop',
'php-http/buzz-adapter' => 'kriswallsmith/buzz:^0.15.1',
'php-http/artax-adapter' => 'amphp/artax:^3',
'http-interop/http-factory-guzzle' => 'guzzlehttp/psr7:^1',
'http-interop/http-factory-slim' => 'slim/slim:^3',
];
private const INTERFACE_MAP = [
'php-http/async-client-implementation' => [
'Http\\Client\\HttpAsyncClient',
],
'php-http/client-implementation' => [
'Http\\Client\\HttpClient',
],
'psr/http-client-implementation' => [
'Psr\\Http\\Client\\ClientInterface',
],
'psr/http-factory-implementation' => [
'Psr\\Http\\Message\\RequestFactoryInterface',
'Psr\\Http\\Message\\ResponseFactoryInterface',
'Psr\\Http\\Message\\ServerRequestFactoryInterface',
'Psr\\Http\\Message\\StreamFactoryInterface',
'Psr\\Http\\Message\\UploadedFileFactoryInterface',
'Psr\\Http\\Message\\UriFactoryInterface',
],
];
public static function getSubscribedEvents() : array {
return [
ScriptEvents::PRE_AUTOLOAD_DUMP => 'preAutoloadDump',
ScriptEvents::POST_UPDATE_CMD => 'postUpdate',
];
}
public function activate(Composer $composer, IOInterface $io) : void {
}
public function deactivate(Composer $composer, IOInterface $io) {
}
public function uninstall(Composer $composer, IOInterface $io) {
}
public function postUpdate(Event $event) {
$composer = $event->getComposer();
$repo = $composer->getRepositoryManager()
->getLocalRepository();
$requires = [
$composer->getPackage()
->getRequires(),
$composer->getPackage()
->getDevRequires(),
];
$pinnedAbstractions = [];
$pinned = $composer->getPackage()
->getExtra()['discovery'] ?? [];
foreach (self::INTERFACE_MAP as $abstraction => $interfaces) {
foreach (isset($pinned[$abstraction]) ? [] : $interfaces as $interface) {
if (!isset($pinned[$interface])) {
continue 2;
}
}
$pinnedAbstractions[$abstraction] = true;
}
$missingRequires = $this->getMissingRequires($repo, $requires, 'project' === $composer->getPackage()
->getType(), $pinnedAbstractions);
$missingRequires = [
'require' => array_fill_keys(array_merge([], ...array_values($missingRequires[0])), '*'),
'require-dev' => array_fill_keys(array_merge([], ...array_values($missingRequires[1])), '*'),
'remove' => array_fill_keys(array_merge([], ...array_values($missingRequires[2])), '*'),
];
if (!($missingRequires = array_filter($missingRequires))) {
return;
}
$composerJsonContents = file_get_contents(Factory::getComposerFile());
$this->updateComposerJson($missingRequires, $composer->getConfig()
->get('sort-packages'));
$installer = null;
// Find the composer installer, hack borrowed from symfony/flex
foreach (debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT) as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Installer) {
$installer = $trace['object'];
break;
}
}
if (!$installer) {
return;
}
$event->stopPropagation();
$dispatcher = $composer->getEventDispatcher();
$disableScripts = !method_exists($dispatcher, 'setRunScripts') || !((array) $dispatcher)["\x00*\x00runScripts"];
$composer = Factory::create($event->getIO(), null, false, $disableScripts);
/** @var Installer $installer */
$installer = clone $installer;
if (method_exists($installer, 'setAudit')) {
$trace['object']->setAudit(false);
}
// we need a clone of the installer to preserve its configuration state but with our own service objects
$installer->__construct($event->getIO(), $composer->getConfig(), $composer->getPackage(), $composer->getDownloadManager(), $composer->getRepositoryManager(), $composer->getLocker(), $composer->getInstallationManager(), $composer->getEventDispatcher(), $composer->getAutoloadGenerator());
if (method_exists($installer, 'setPlatformRequirementFilter')) {
$installer->setPlatformRequirementFilter(((array) $trace['object'])["\x00*\x00platformRequirementFilter"]);
}
if (0 !== $installer->run()) {
file_put_contents(Factory::getComposerFile(), $composerJsonContents);
return;
}
$versionSelector = new VersionSelector(ClassDiscovery::safeClassExists(RepositorySet::class) ? new RepositorySet() : new Pool());
$updateComposerJson = false;
foreach ($composer->getRepositoryManager()
->getLocalRepository()
->getPackages() as $package) {
foreach ([
'require',
'require-dev',
] as $key) {
if (!isset($missingRequires[$key][$package->getName()])) {
continue;
}
$updateComposerJson = true;
$missingRequires[$key][$package->getName()] = $versionSelector->findRecommendedRequireVersion($package);
}
}
if ($updateComposerJson) {
$this->updateComposerJson($missingRequires, $composer->getConfig()
->get('sort-packages'));
$this->updateComposerLock($composer, $event->getIO());
}
}
public function getMissingRequires(InstalledRepositoryInterface $repo, array $requires, bool $isProject, array $pinnedAbstractions) : array {
$allPackages = [];
$devPackages = method_exists($repo, 'getDevPackageNames') ? array_fill_keys($repo->getDevPackageNames(), true) : [];
// One must require "php-http/discovery"
// to opt-in for auto-installation of virtual package implementations
if (!isset($requires[0]['php-http/discovery'])) {
$requires = [
[],
[],
];
}
foreach ($repo->getPackages() as $package) {
$allPackages[$package->getName()] = true;
if (1 < \count($names = $package->getNames(false))) {
$allPackages += array_fill_keys($names, false);
if (isset($devPackages[$package->getName()])) {
$devPackages += $names;
}
}
if (isset($package->getRequires()['php-http/discovery'])) {
$requires[(int) isset($devPackages[$package->getName()])] += $package->getRequires();
}
}
$missingRequires = [
[],
[],
[],
];
$versionParser = new VersionParser();
if (ClassDiscovery::safeClassExists(\Phalcon\Http\Message\RequestFactory::class, false)) {
$missingRequires[0]['psr/http-factory-implementation'] = [];
$missingRequires[1]['psr/http-factory-implementation'] = [];
}
foreach ($requires as $dev => $rules) {
$abstractions = [];
$rules = array_intersect_key(self::PROVIDE_RULES, $rules);
while ($rules) {
$abstraction = key($rules);
if (isset($pinnedAbstractions[$abstraction])) {
unset($rules[$abstraction]);
continue;
}
$abstractions[] = $abstraction;
foreach (array_shift($rules) as $candidate => $deps) {
[
$candidate,
$version,
] = explode(':', $candidate, 2) + [
1 => null,
];
if (!isset($allPackages[$candidate])) {
continue;
}
if (null !== $version && !$repo->findPackage($candidate, $versionParser->parseConstraints($version))) {
continue;
}
if ($isProject && !$dev && isset($devPackages[$candidate])) {
$missingRequires[0][$abstraction] = [
$candidate,
];
$missingRequires[2][$abstraction] = [
$candidate,
];
}
else {
$missingRequires[$dev][$abstraction] = [];
}
foreach ($deps as $dep) {
if (isset(self::PROVIDE_RULES[$dep])) {
$rules[$dep] = self::PROVIDE_RULES[$dep];
}
elseif (!isset($allPackages[$dep])) {
$missingRequires[$dev][$abstraction][] = $dep;
}
elseif ($isProject && !$dev && isset($devPackages[$dep])) {
$missingRequires[0][$abstraction][] = $dep;
$missingRequires[2][$abstraction][] = $dep;
}
}
break;
}
}
while ($abstractions) {
$abstraction = array_shift($abstractions);
if (isset($missingRequires[$dev][$abstraction])) {
continue;
}
$candidates = self::PROVIDE_RULES[$abstraction];
foreach ($candidates as $candidate => $deps) {
[
$candidate,
$version,
] = explode(':', $candidate, 2) + [
1 => null,
];
if (null !== $version && !$repo->findPackage($candidate, $versionParser->parseConstraints($version))) {
continue;
}
if (isset($allPackages[$candidate]) && (!$isProject || $dev || !isset($devPackages[$candidate]))) {
continue 2;
}
}
foreach (array_intersect_key(self::STICKYNESS_RULES, $candidates) as $candidate => $stickyRule) {
[
$stickyName,
$stickyVersion,
] = explode(':', $stickyRule, 2) + [
1 => null,
];
if (!isset($allPackages[$stickyName]) || $isProject && !$dev && isset($devPackages[$stickyName])) {
continue;
}
if (null !== $stickyVersion && !$repo->findPackage($stickyName, $versionParser->parseConstraints($stickyVersion))) {
continue;
}
$candidates = [
$candidate => $candidates[$candidate],
];
break;
}
$dep = key($candidates);
[
$dep,
] = explode(':', $dep, 2);
$missingRequires[$dev][$abstraction] = [
$dep,
];
if ($isProject && !$dev && isset($devPackages[$dep])) {
$missingRequires[2][$abstraction][] = $dep;
}
}
}
$missingRequires[1] = array_diff_key($missingRequires[1], $missingRequires[0]);
return $missingRequires;
}
public function preAutoloadDump(Event $event) {
$filesystem = new Filesystem();
// Double realpath() on purpose, see https://bugs.php.net/72738
$vendorDir = $filesystem->normalizePath(realpath(realpath($event->getComposer()
->getConfig()
->get('vendor-dir'))));
$filesystem->ensureDirectoryExists($vendorDir . '/composer');
$pinned = $event->getComposer()
->getPackage()
->getExtra()['discovery'] ?? [];
$candidates = [];
$allInterfaces = array_merge(...array_values(self::INTERFACE_MAP));
foreach ($pinned as $abstraction => $class) {
if (isset(self::INTERFACE_MAP[$abstraction])) {
$interfaces = self::INTERFACE_MAP[$abstraction];
}
elseif (false !== ($k = array_search($abstraction, $allInterfaces, true))) {
$interfaces = [
$allInterfaces[$k],
];
}
else {
throw new \UnexpectedValueException(sprintf('Invalid "extra.discovery" pinned in composer.json: "%s" is not one of ["%s"].', $abstraction, implode('", "', array_keys(self::INTERFACE_MAP))));
}
foreach ($interfaces as $interface) {
$candidates[] = sprintf("case %s: return [['class' => %s]];\n", var_export($interface, true), var_export($class, true));
}
}
$file = $vendorDir . '/composer/GeneratedDiscoveryStrategy.php';
if (!$candidates) {
if (file_exists($file)) {
unlink($file);
}
return;
}
$candidates = implode(' ', $candidates);
$code = <<<EOPHP
<?php
namespace Http\\Discovery\\Strategy;
class GeneratedDiscoveryStrategy implements DiscoveryStrategy
{
public static function getCandidates(\$type)
{
switch (\$type) {
{<span class="php-variable">$candidates</span>}
default: return [];
}
}
}
EOPHP;
if (!file_exists($file) || $code !== file_get_contents($file)) {
file_put_contents($file, $code);
}
$rootPackage = $event->getComposer()
->getPackage();
$autoload = $rootPackage->getAutoload();
$autoload['classmap'][] = $vendorDir . '/composer/GeneratedDiscoveryStrategy.php';
$rootPackage->setAutoload($autoload);
}
private function updateComposerJson(array $missingRequires, bool $sortPackages) {
$file = Factory::getComposerFile();
$contents = file_get_contents($file);
$manipulator = new JsonManipulator($contents);
foreach ($missingRequires as $key => $packages) {
foreach ($packages as $package => $constraint) {
if ('remove' === $key) {
$manipulator->removeSubNode('require-dev', $package);
}
else {
$manipulator->addLink($key, $package, $constraint, $sortPackages);
}
}
}
file_put_contents($file, $manipulator->getContents());
}
private function updateComposerLock(Composer $composer, IOInterface $io) {
if (false === $composer->getConfig()
->get('lock')) {
return;
}
$lock = substr(Factory::getComposerFile(), 0, -4) . 'lock';
$composerJson = file_get_contents(Factory::getComposerFile());
$lockFile = new JsonFile($lock, null, $io);
$locker = ClassDiscovery::safeClassExists(RepositorySet::class) ? new Locker($io, $lockFile, $composer->getInstallationManager(), $composerJson) : new Locker($io, $lockFile, $composer->getRepositoryManager(), $composer->getInstallationManager(), $composerJson);
if (!$locker->isLocked()) {
return;
}
$lockData = $locker->getLockData();
$lockData['content-hash'] = Locker::getContentHash($composerJson);
$lockFile->write($lockData);
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overriden Title |
---|---|---|---|---|
Plugin::activate | public | function | Apply plugin modifications to Composer | Overrides PluginInterface::activate |
Plugin::deactivate | public | function | Remove any hooks from Composer | Overrides PluginInterface::deactivate |
Plugin::getMissingRequires | public | function | ||
Plugin::getSubscribedEvents | public static | function | Returns an array of event names this subscriber wants to listen to. | Overrides EventSubscriberInterface::getSubscribedEvents |
Plugin::INTERFACE_MAP | private | constant | ||
Plugin::postUpdate | public | function | ||
Plugin::preAutoloadDump | public | function | ||
Plugin::PROVIDE_RULES | private | constant | Describes, for every supported virtual implementation, which packages provide said implementation and which extra dependencies each package requires to provide the implementation. |
|
Plugin::STICKYNESS_RULES | private | constant | Describes which package should be preferred on the left side depending on which one is already installed on the right side. |
|
Plugin::uninstall | public | function | Prepare the plugin to be uninstalled | Overrides PluginInterface::uninstall |
Plugin::updateComposerJson | private | function | ||
Plugin::updateComposerLock | private | function | ||
PluginInterface::PLUGIN_API_VERSION | public | constant | Version number of the internal composer-plugin-api package |