Add phpstan-symfony to get type info about console InputInterface, fix many errors (#10476)

Extract common init/require commands functionality into PackageDiscoveryTrait
Extract some helper methods into BaseCommand for better types
main
Jordi Boggiano 2 years ago committed by GitHub
parent 48758c0207
commit 5c98a2cf8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -37,14 +37,17 @@
"symfony/finder": "^5.4 || ^6.0", "symfony/finder": "^5.4 || ^6.0",
"symfony/process": "^5.4 || ^6.0", "symfony/process": "^5.4 || ^6.0",
"react/promise": "^2.8", "react/promise": "^2.8",
"composer/pcre": "^1.0" "composer/pcre": "^1.0",
"symfony/polyfill-php73": "^1.24",
"symfony/polyfill-php80": "^1.24"
}, },
"require-dev": { "require-dev": {
"symfony/phpunit-bridge": "^6.0", "symfony/phpunit-bridge": "^6.0",
"phpstan/phpstan": "^1.4.1", "phpstan/phpstan": "^1.4.1",
"phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1", "phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-strict-rules": "^1" "phpstan/phpstan-strict-rules": "^1",
"phpstan/phpstan-symfony": "^1.1"
}, },
"suggest": { "suggest": {
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
@ -83,7 +86,7 @@
"scripts-descriptions": { "scripts-descriptions": {
"compile": "Compile composer.phar", "compile": "Compile composer.phar",
"test": "Run all tests", "test": "Run all tests",
"phpstan": "Runs PHPStan (after phpstan-setup was executed, must be run with PHP7.4)" "phpstan": "Runs PHPStan"
}, },
"support": { "support": {
"issues": "https://github.com/composer/composer/issues", "issues": "https://github.com/composer/composer/issues",

74
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "36248b1f2f20c084208c57c0758261e8", "content-hash": "03b82664ef6f80651118f0c06a49280c",
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
@ -2042,6 +2042,78 @@
}, },
"time": "2021-11-18T09:30:29+00:00" "time": "2021-11-18T09:30:29+00:00"
}, },
{
"name": "phpstan/phpstan-symfony",
"version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git",
"reference": "4e20d2e6b65a6eb2d0b8ff285c6c8c049faff03d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/4e20d2e6b65a6eb2d0b8ff285c6c8c049faff03d",
"reference": "4e20d2e6b65a6eb2d0b8ff285c6c8c049faff03d",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"php": "^7.1 || ^8.0",
"phpstan/phpstan": "^1.4"
},
"conflict": {
"symfony/framework-bundle": "<3.0"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5",
"symfony/config": "^4.2 || ^5.0",
"symfony/console": "^4.0 || ^5.0",
"symfony/form": "^4.0 || ^5.0",
"symfony/framework-bundle": "^4.4 || ^5.0",
"symfony/http-foundation": "^5.1",
"symfony/messenger": "^4.2 || ^5.0",
"symfony/polyfill-php80": "^1.24",
"symfony/serializer": "^4.0 || ^5.0"
},
"type": "phpstan-extension",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
},
"phpstan": {
"includes": [
"extension.neon",
"rules.neon"
]
}
},
"autoload": {
"psr-4": {
"PHPStan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Lukáš Unger",
"email": "looky.msc@gmail.com",
"homepage": "https://lookyman.net"
}
],
"description": "Symfony Framework extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.1.5"
},
"time": "2022-02-05T13:48:27+00:00"
},
{ {
"name": "symfony/phpunit-bridge", "name": "symfony/phpunit-bridge",
"version": "v6.0.3", "version": "v6.0.3",

@ -263,12 +263,12 @@ class CommandProvider implements CommandProviderCapability
class Command extends BaseCommand class Command extends BaseCommand
{ {
protected function configure() protected function configure(): void
{ {
$this->setName('custom-plugin-command'); $this->setName('custom-plugin-command');
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$output->writeln('Executing'); $output->writeln('Executing');
} }

@ -30,11 +30,6 @@ parameters:
count: 1 count: 1
path: ../src/Composer/Command/ExecCommand.php path: ../src/Composer/Command/ExecCommand.php
-
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
count: 1
path: ../src/Composer/Command/InitCommand.php
- -
message: "#^Parameter \\#1 \\$from of function rename expects string, string\\|false given\\.$#" message: "#^Parameter \\#1 \\$from of function rename expects string, string\\|false given\\.$#"
count: 1 count: 1
@ -247,7 +242,7 @@ parameters:
- -
message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#" message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#"
count: 17 count: 15
path: ../src/Composer/Util/Git.php path: ../src/Composer/Util/Git.php
- -
@ -315,6 +310,11 @@ parameters:
count: 1 count: 1
path: ../src/Composer/Util/Http/CurlDownloader.php path: ../src/Composer/Util/Http/CurlDownloader.php
-
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int, retries\\: int, storeAuth\\: bool\\}, options\\: array, progress\\: array, curlHandle\\: resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int, retries\\: int, storeAuth\\: bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
count: 1
path: ../src/Composer/Util/Http/CurlDownloader.php
- -
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$multiHandle \\(resource\\|null\\) does not accept CurlMultiHandle\\.$#" message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$multiHandle \\(resource\\|null\\) does not accept CurlMultiHandle\\.$#"
count: 1 count: 1

File diff suppressed because it is too large Load Diff

@ -2,6 +2,8 @@ includes:
- ../vendor/phpstan/phpstan-phpunit/extension.neon - ../vendor/phpstan/phpstan-phpunit/extension.neon
- ../vendor/phpstan/phpstan-deprecation-rules/rules.neon - ../vendor/phpstan/phpstan-deprecation-rules/rules.neon
- ../vendor/phpstan/phpstan-strict-rules/rules.neon - ../vendor/phpstan/phpstan-strict-rules/rules.neon
- ../vendor/phpstan/phpstan-symfony/extension.neon
- ../vendor/phpstan/phpstan-symfony/rules.neon
- ./baseline.neon - ./baseline.neon
- ./ignore-by-php-version.neon.php - ./ignore-by-php-version.neon.php
@ -51,6 +53,9 @@ parameters:
- ../src - ../src
- ../tests - ../tests
symfony:
consoleApplicationLoader: ../tests/console-application.php
dynamicConstantNames: dynamicConstantNames:
- Composer\Composer::BRANCH_ALIAS_VERSION - Composer\Composer::BRANCH_ALIAS_VERSION
- Composer\Composer::VERSION - Composer\Composer::VERSION

@ -37,7 +37,7 @@ EOT
; ;
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$composerVersion = Composer::getVersion(); $composerVersion = Composer::getVersion();

@ -69,9 +69,9 @@ EOT
; ;
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$composer = $this->getComposer(false); $composer = $this->tryComposer();
$config = null; $config = null;
if ($composer) { if ($composer) {
@ -86,20 +86,16 @@ EOT
$config = Factory::createConfig(); $config = Factory::createConfig();
} }
if (null === $input->getOption('format')) { $format = $input->getOption('format') ?? $config->get('archive-format');
$input->setOption('format', $config->get('archive-format')); $dir = $input->getOption('dir') ?? $config->get('archive-dir');
}
if (null === $input->getOption('dir')) {
$input->setOption('dir', $config->get('archive-dir'));
}
$returnCode = $this->archive( $returnCode = $this->archive(
$this->getIO(), $this->getIO(),
$config, $config,
$input->getArgument('package'), $input->getArgument('package'),
$input->getArgument('version'), $input->getArgument('version'),
$input->getOption('format'), $format,
$input->getOption('dir'), $dir,
$input->getOption('file'), $input->getOption('file'),
$input->getOption('ignore-filters'), $input->getOption('ignore-filters'),
$composer $composer
@ -113,17 +109,9 @@ EOT
} }
/** /**
* @param string|null $packageName
* @param string|null $version
* @param string $format
* @param string $dest
* @param string|null $fileName
* @param bool $ignoreFilters
*
* @return int
* @throws \Exception * @throws \Exception
*/ */
protected function archive(IOInterface $io, Config $config, $packageName = null, $version = null, $format = 'tar', $dest = '.', $fileName = null, $ignoreFilters = false, Composer $composer = null) protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer): int
{ {
if ($composer) { if ($composer) {
$archiveManager = $composer->getArchiveManager(); $archiveManager = $composer->getArchiveManager();
@ -142,7 +130,7 @@ EOT
return 1; return 1;
} }
} else { } else {
$package = $this->getComposer()->getPackage(); $package = $this->requireComposer()->getPackage();
} }
$io->writeError('<info>Creating the archive into "'.$dest.'".</info>'); $io->writeError('<info>Creating the archive into "'.$dest.'".</info>');
@ -166,7 +154,7 @@ EOT
{ {
$io->writeError('<info>Searching for the specified package.</info>'); $io->writeError('<info>Searching for the specified package.</info>');
if ($composer = $this->getComposer(false)) { if ($composer = $this->tryComposer()) {
$localRepo = $composer->getRepositoryManager()->getLocalRepository(); $localRepo = $composer->getRepositoryManager()->getLocalRepository();
$repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories())); $repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories()));
} else { } else {

@ -16,11 +16,15 @@ use Composer\Composer;
use Composer\Config; use Composer\Config;
use Composer\Console\Application; use Composer\Console\Application;
use Composer\Factory; use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\NullIO; use Composer\IO\NullIO;
use Composer\Pcre\Preg;
use Composer\Plugin\PreCommandRunEvent; use Composer\Plugin\PreCommandRunEvent;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Repository\PlatformRepository;
use Composer\Util\Platform; use Composer\Util\Platform;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Helper\TableSeparator;
@ -32,8 +36,6 @@ use Symfony\Component\Console\Terminal;
/** /**
* Base class for Composer commands * Base class for Composer commands
* *
* @method Application getApplication()
*
* @author Ryan Weaver <ryan@knplabs.com> * @author Ryan Weaver <ryan@knplabs.com>
* @author Konstantin Kudryashov <ever.zet@gmail.com> * @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
@ -50,21 +52,52 @@ abstract class BaseCommand extends Command
private $io; private $io;
/** /**
* @param bool $required * Gets the application instance for this command.
* @param bool|null $disablePlugins */
* @param bool|null $disableScripts public function getApplication(): Application
{
$application = parent::getApplication();
if (!$application instanceof Application) {
throw new \RuntimeException('Composer commands can only work with an '.Application::class.' instance set');
}
return $application;
}
/**
* @param bool $required Should be set to false, or use `requireComposer` instead
* @param bool|null $disablePlugins If null, reads --no-plugins as default
* @param bool|null $disableScripts If null, reads --no-scripts as default
* @throws \RuntimeException * @throws \RuntimeException
* @return Composer|null * @return Composer|null
* @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
*/ */
public function getComposer($required = true, $disablePlugins = null, $disableScripts = null) public function getComposer($required = true, $disablePlugins = null, $disableScripts = null)
{
if ($required) {
return $this->requireComposer($disablePlugins, $disableScripts);
}
return $this->tryComposer($disablePlugins, $disableScripts);
}
/**
* Retrieves the default Composer\Composer instance or throws
*
* Use this instead of getComposer if you absolutely need an instance
*
* @param bool|null $disablePlugins If null, reads --no-plugins as default
* @param bool|null $disableScripts If null, reads --no-scripts as default
* @throws \RuntimeException
*/
public function requireComposer(bool $disablePlugins = null, bool $disableScripts = null): Composer
{ {
if (null === $this->composer) { if (null === $this->composer) {
$application = $this->getApplication(); $application = parent::getApplication();
if ($application instanceof Application) { if ($application instanceof Application) {
/* @var $application Application */ $this->composer = $application->getComposer(true, $disablePlugins, $disableScripts);
$this->composer = $application->getComposer($required, $disablePlugins, $disableScripts); assert($this->composer instanceof Composer);
/** @phpstan-ignore-next-line */ } else {
} elseif ($required) {
throw new \RuntimeException( throw new \RuntimeException(
'Could not create a Composer\Composer instance, you must inject '. 'Could not create a Composer\Composer instance, you must inject '.
'one if this command is not used with a Composer\Console\Application instance' 'one if this command is not used with a Composer\Console\Application instance'
@ -75,6 +108,26 @@ abstract class BaseCommand extends Command
return $this->composer; return $this->composer;
} }
/**
* Retrieves the default Composer\Composer instance or null
*
* Use this instead of getComposer(false)
*
* @param bool|null $disablePlugins If null, reads --no-plugins as default
* @param bool|null $disableScripts If null, reads --no-scripts as default
*/
public function tryComposer(bool $disablePlugins = null, bool $disableScripts = null): ?Composer
{
if (null === $this->composer) {
$application = parent::getApplication();
if ($application instanceof Application) {
$this->composer = $application->getComposer(false, $disablePlugins, $disableScripts);
}
}
return $this->composer;
}
/** /**
* @return void * @return void
*/ */
@ -112,10 +165,9 @@ abstract class BaseCommand extends Command
public function getIO() public function getIO()
{ {
if (null === $this->io) { if (null === $this->io) {
$application = $this->getApplication(); $application = parent::getApplication();
if ($application instanceof Application) { if ($application instanceof Application) {
$this->io = $application->getIO(); $this->io = $application->getIO();
/** @phpstan-ignore-next-line */
} else { } else {
$this->io = new NullIO(); $this->io = new NullIO();
} }
@ -147,7 +199,7 @@ abstract class BaseCommand extends Command
$disableScripts = true; $disableScripts = true;
} }
$composer = $this->getComposer(false, $disablePlugins, $disableScripts); $composer = $this->tryComposer($disablePlugins, $disableScripts);
if (null === $composer) { if (null === $composer) {
$composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts); $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts);
} }
@ -194,7 +246,11 @@ abstract class BaseCommand extends Command
break; break;
} }
if ($input->hasOption('prefer-install') && $input->getOption('prefer-install')) { if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) {
return [$preferSource, $preferDist];
}
if ($input->hasOption('prefer-install') && is_string($input->getOption('prefer-install'))) {
if ($input->getOption('prefer-source')) { if ($input->getOption('prefer-source')) {
throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install'); throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install');
} }
@ -219,14 +275,32 @@ abstract class BaseCommand extends Command
if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
$preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
$preferDist = (bool) $input->getOption('prefer-dist'); $preferDist = $input->getOption('prefer-dist');
} }
return array($preferSource, $preferDist); return array($preferSource, $preferDist);
} }
protected function getPlatformRequirementFilter(InputInterface $input): PlatformRequirementFilterInterface
{
if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) {
throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.');
}
if (true === $input->getOption('ignore-platform-reqs')) {
return PlatformRequirementFilterFactory::ignoreAll();
}
$ignores = $input->getOption('ignore-platform-req');
if (count($ignores) > 0) {
return PlatformRequirementFilterFactory::fromBoolOrList($ignores);
}
return PlatformRequirementFilterFactory::ignoreNothing();
}
/** /**
* @param array<string, string> $requirements * @param array<string> $requirements
* *
* @return array<string, string> * @return array<string, string>
*/ */

@ -53,7 +53,7 @@ class BaseDependencyCommand extends BaseCommand
protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false) protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
{ {
// Emit command event on startup // Emit command event on startup
$composer = $this->getComposer(); $composer = $this->requireComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

@ -46,12 +46,9 @@ EOT
); );
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$requires = array(); $requires = array();
$removePackages = array(); $removePackages = array();

@ -42,10 +42,7 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$config = Factory::createConfig(); $config = Factory::createConfig();
$io = $this->getIO(); $io = $this->getIO();

@ -208,10 +208,9 @@ EOT
} }
/** /**
* @return int
* @throws \Seld\JsonLint\ParsingException * @throws \Seld\JsonLint\ParsingException
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
// Open file in editor // Open file in editor
if (true === $input->getOption('editor')) { if (true === $input->getOption('editor')) {
@ -242,7 +241,7 @@ EOT
// List the configuration of the file settings // List the configuration of the file settings
if (true === $input->getOption('list')) { if (true === $input->getOption('list')) {
$this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, (bool) $input->getOption('source')); $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source'));
return 0; return 0;
} }

@ -122,10 +122,7 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$config = Factory::createConfig(); $config = Factory::createConfig();
$io = $this->getIO(); $io = $this->getIO();
@ -141,12 +138,14 @@ EOT
} }
if ($input->isInteractive() && $input->getOption('ask')) { if ($input->isInteractive() && $input->getOption('ask')) {
$parts = explode("/", strtolower($input->getArgument('package')), 2); $package = $input->getArgument('package');
if (null === $package) {
throw new \RuntimeException('Not enough arguments (missing: "package").');
}
$parts = explode("/", strtolower($package), 2);
$input->setArgument('directory', $io->ask('New project directory [<comment>'.array_pop($parts).'</comment>]: ')); $input->setArgument('directory', $io->ask('New project directory [<comment>'.array_pop($parts).'</comment>]: '));
} }
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
return $this->installProject( return $this->installProject(
$io, $io,
$config, $config,
@ -163,7 +162,7 @@ EOT
$input->getOption('no-scripts'), $input->getOption('no-scripts'),
$input->getOption('no-progress'), $input->getOption('no-progress'),
$input->getOption('no-install'), $input->getOption('no-install'),
PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs), $this->getPlatformRequirementFilter($input),
!$input->getOption('no-secure-http'), !$input->getOption('no-secure-http'),
$input->getOption('add-repository') $input->getOption('add-repository')
); );
@ -173,7 +172,7 @@ EOT
* @param string|null $packageName * @param string|null $packageName
* @param string|null $directory * @param string|null $directory
* @param string|null $packageVersion * @param string|null $packageVersion
* @param string $stability * @param string|null $stability
* @param bool $preferSource * @param bool $preferSource
* @param bool $preferDist * @param bool $preferDist
* @param bool $installDevPackages * @param bool $installDevPackages

@ -50,12 +50,7 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* Execute the function.
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
return parent::doExecute($input, $output); return parent::doExecute($input, $output);
} }

@ -69,12 +69,9 @@ EOT
; ;
} }
/**
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(false); $composer = $this->tryComposer();
$io = $this->getIO(); $io = $this->getIO();
if ($composer) { if ($composer) {

@ -53,12 +53,9 @@ EOT
; ;
} }
/**
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
@ -81,8 +78,6 @@ EOT
$this->getIO()->write('<info>Generating autoload files</info>'); $this->getIO()->write('<info>Generating autoload files</info>');
} }
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$generator = $composer->getAutoloadGenerator(); $generator = $composer->getAutoloadGenerator();
if ($input->getOption('no-dev')) { if ($input->getOption('no-dev')) {
$generator->setDevMode(false); $generator->setDevMode(false);
@ -96,7 +91,7 @@ EOT
$generator->setClassMapAuthoritative($authoritative); $generator->setClassMapAuthoritative($authoritative);
$generator->setRunScripts(true); $generator->setRunScripts(true);
$generator->setApcu($apcu, $apcuPrefix); $generator->setApcu($apcu, $apcuPrefix);
$generator->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
$numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
if ($authoritative) { if ($authoritative) {

@ -49,14 +49,11 @@ EOT
; ;
} }
/**
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$binDir = $composer->getConfig()->get('bin-dir'); $binDir = $composer->getConfig()->get('bin-dir');
if ($input->getOption('list') || !$input->getArgument('binary')) { if ($input->getOption('list') || null === $input->getArgument('binary')) {
$bins = glob($binDir . '/*'); $bins = glob($binDir . '/*');
$bins = array_merge($bins, array_map(function ($e) { $bins = array_merge($bins, array_map(function ($e) {
return "$e (local)"; return "$e (local)";

@ -43,12 +43,9 @@ class FundCommand extends BaseCommand
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$repo = $composer->getRepositoryManager()->getLocalRepository(); $repo = $composer->getRepositoryManager()->getLocalRepository();
$remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());

@ -57,19 +57,16 @@ EOT
); );
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$repos = $this->initializeRepos(); $repos = $this->initializeRepos();
$io = $this->getIO(); $io = $this->getIO();
$return = 0; $return = 0;
$packages = $input->getArgument('packages'); $packages = $input->getArgument('packages');
if (!$packages) { if (count($packages) === 0) {
$io->writeError('No package specified, opening homepage for the root package'); $io->writeError('No package specified, opening homepage for the root package');
$packages = array($this->getComposer()->getPackage()->getName()); $packages = array($this->requireComposer()->getPackage()->getName());
} }
foreach ($packages as $packageName) { foreach ($packages as $packageName) {
@ -163,7 +160,7 @@ EOT
*/ */
private function initializeRepos() private function initializeRepos()
{ {
$composer = $this->getComposer(false); $composer = $this->tryComposer();
if ($composer) { if ($composer) {
return array_merge( return array_merge(

@ -46,15 +46,17 @@ use Symfony\Component\Console\Helper\FormatterHelper;
*/ */
class InitCommand extends BaseCommand class InitCommand extends BaseCommand
{ {
use PackageDiscoveryTrait;
// Properties for PackageDiscoveryTrait
/** @var ?CompositeRepository */ /** @var ?CompositeRepository */
protected $repos; protected $repos;
/** @var RepositorySet[] */
private $repositorySets;
/** @var array<string, string> */ /** @var array<string, string> */
private $gitConfig; private $gitConfig;
/** @var RepositorySet[] */
private $repositorySets;
/** /**
* @inheritDoc * @inheritDoc
* *
@ -93,9 +95,6 @@ EOT
} }
/** /**
* @inheritDoc
*
* @return int
* @throws \Seld\JsonLint\ParsingException * @throws \Seld\JsonLint\ParsingException
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
@ -117,7 +116,7 @@ EOT
} }
$repositories = $input->getOption('repository'); $repositories = $input->getOption('repository');
if ($repositories) { if (count($repositories) > 0) {
$config = Factory::createConfig($io); $config = Factory::createConfig($io);
foreach ($repositories as $repo) { foreach ($repositories as $repo) {
$options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true); $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true);
@ -145,7 +144,7 @@ EOT
$autoloadPath = null; $autoloadPath = null;
if (isset($options['autoload'])) { if (isset($options['autoload'])) {
$autoloadPath = $options['autoload']; $autoloadPath = $options['autoload'];
$namespace = $this->namespaceFromPackageName($input->getOption('name')); $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
$options['autoload'] = (object) array( $options['autoload'] = (object) array(
'psr-4' => array( 'psr-4' => array(
$namespace . '\\' => $autoloadPath, $namespace . '\\' => $autoloadPath,
@ -217,7 +216,7 @@ EOT
// --autoload - Show post-install configuration info // --autoload - Show post-install configuration info
if ($autoloadPath) { if ($autoloadPath) {
$namespace = $this->namespaceFromPackageName($input->getOption('name')); $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
$io->writeError('PSR-4 autoloading configured. Use "<comment>namespace '.$namespace.';</comment>" in '.$autoloadPath); $io->writeError('PSR-4 autoloading configured. Use "<comment>namespace '.$namespace.';</comment>" in '.$autoloadPath);
$io->writeError('Include the Composer autoloader with: <comment>require \'vendor/autoload.php\';</comment>'); $io->writeError('Include the Composer autoloader with: <comment>require \'vendor/autoload.php\';</comment>');
@ -240,7 +239,7 @@ EOT
// initialize repos if configured // initialize repos if configured
$repositories = $input->getOption('repository'); $repositories = $input->getOption('repository');
if ($repositories) { if (count($repositories) > 0) {
$config = Factory::createConfig($io); $config = Factory::createConfig($io);
$repos = array(new PlatformRepository); $repos = array(new PlatformRepository);
$createDefaultPackagistRepo = true; $createDefaultPackagistRepo = true;
@ -282,7 +281,8 @@ EOT
$cwd = realpath("."); $cwd = realpath(".");
if (!$name = $input->getOption('name')) { $name = $input->getOption('name');
if (null === $name) {
$name = basename($cwd); $name = basename($cwd);
$name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
$name = strtolower($name); $name = strtolower($name);
@ -323,7 +323,7 @@ EOT
); );
$input->setOption('name', $name); $input->setOption('name', $name);
$description = $input->getOption('description') ?: false; $description = $input->getOption('description') ?: null;
$description = $io->ask( $description = $io->ask(
'Description [<comment>'.$description.'</comment>]: ', 'Description [<comment>'.$description.'</comment>]: ',
$description $description
@ -423,7 +423,7 @@ EOT
$question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? '; $question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
$require = $input->getOption('require'); $require = $input->getOption('require');
$requirements = array(); $requirements = array();
if ($require || $io->askConfirmation($question)) { if (count($require) > 0 || $io->askConfirmation($question)) {
$requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability); $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability);
} }
$input->setOption('require', $requirements); $input->setOption('require', $requirements);
@ -431,14 +431,14 @@ EOT
$question = 'Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? '; $question = 'Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? ';
$requireDev = $input->getOption('require-dev'); $requireDev = $input->getOption('require-dev');
$devRequirements = array(); $devRequirements = array();
if ($requireDev || $io->askConfirmation($question)) { if (count($requireDev) > 0 || $io->askConfirmation($question)) {
$devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability); $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability);
} }
$input->setOption('require-dev', $devRequirements); $input->setOption('require-dev', $devRequirements);
// --autoload - input and validation // --autoload - input and validation
$autoload = $input->getOption('autoload') ?: 'src/'; $autoload = $input->getOption('autoload') ?: 'src/';
$namespace = $this->namespaceFromPackageName($input->getOption('name')); $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
$autoload = $io->askAndValidate( $autoload = $io->askAndValidate(
'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. [<comment>'.$autoload.'</comment>, n to skip]: ', 'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. [<comment>'.$autoload.'</comment>, n to skip]: ',
function ($value) use ($autoload) { function ($value) use ($autoload) {
@ -489,197 +489,6 @@ EOT
); );
} }
/**
* @return CompositeRepository
*/
protected function getRepos()
{
if (!$this->repos) {
$this->repos = new CompositeRepository(array_merge(
array(new PlatformRepository),
RepositoryFactory::defaultRepos($this->getIO())
));
}
return $this->repos;
}
/**
* @param array<string> $requires
* @param PlatformRepository|null $platformRepo
* @param string $preferredStability
* @param bool $checkProvidedVersions
* @param bool $fixed
*
* @return array<string>
* @throws \Exception
*/
final protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), PlatformRepository $platformRepo = null, $preferredStability = 'stable', $checkProvidedVersions = true, $fixed = false)
{
if ($requires) {
$requires = $this->normalizeRequirements($requires);
$result = array();
$io = $this->getIO();
foreach ($requires as $requirement) {
if (!isset($requirement['version'])) {
// determine the best version automatically
list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, null, null, $fixed);
$requirement['version'] = $version;
// replace package name from packagist.org
$requirement['name'] = $name;
$io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>',
$requirement['version'],
$requirement['name']
));
} else {
// check that the specified version/constraint exists before we proceed
list($name) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev', $fixed);
// replace package name from packagist.org
$requirement['name'] = $name;
}
$result[] = $requirement['name'] . ' ' . $requirement['version'];
}
return $result;
}
$versionParser = new VersionParser();
// Collect existing packages
$composer = $this->getComposer(false);
$installedRepo = $composer ? $composer->getRepositoryManager()->getLocalRepository() : null;
$existingPackages = array();
if ($installedRepo) {
foreach ($installedRepo->getPackages() as $package) {
$existingPackages[] = $package->getName();
}
}
unset($composer, $installedRepo);
$io = $this->getIO();
while (null !== $package = $io->ask('Search for a package: ')) {
$matches = $this->getRepos()->search($package);
if (count($matches)) {
// Remove existing packages from search results.
foreach ($matches as $position => $foundPackage) {
if (in_array($foundPackage['name'], $existingPackages, true)) {
unset($matches[$position]);
}
}
$matches = array_values($matches);
$exactMatch = null;
$choices = array();
foreach ($matches as $position => $foundPackage) {
$abandoned = '';
if (isset($foundPackage['abandoned'])) {
if (is_string($foundPackage['abandoned'])) {
$replacement = sprintf('Use %s instead', $foundPackage['abandoned']);
} else {
$replacement = 'No replacement was suggested';
}
$abandoned = sprintf('<warning>Abandoned. %s.</warning>', $replacement);
}
$choices[] = sprintf(' <info>%5s</info> %s %s', "[$position]", $foundPackage['name'], $abandoned);
if ($foundPackage['name'] === $package) {
$exactMatch = true;
break;
}
}
// no match, prompt which to pick
if (!$exactMatch) {
$io->writeError(array(
'',
sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
'',
));
$io->writeError($choices);
$io->writeError('');
$validator = function ($selection) use ($matches, $versionParser) {
if ('' === $selection) {
return false;
}
if (is_numeric($selection) && isset($matches[(int) $selection])) {
$package = $matches[(int) $selection];
return $package['name'];
}
if (Preg::isMatch('{^\s*(?P<name>[\S/]+)(?:\s+(?P<version>\S+))?\s*$}', $selection, $packageMatches)) {
if (isset($packageMatches['version'])) {
// parsing `acme/example ~2.3`
// validate version constraint
$versionParser->parseConstraints($packageMatches['version']);
return $packageMatches['name'].' '.$packageMatches['version'];
}
// parsing `acme/example`
return $packageMatches['name'];
}
throw new \Exception('Not a valid selection');
};
$package = $io->askAndValidate(
'Enter package # to add, or the complete package name if it is not listed: ',
$validator,
3,
false
);
}
// no constraint yet, determine the best version automatically
if (false !== $package && false === strpos($package, ' ')) {
$validator = function ($input) {
$input = trim($input);
return $input ?: false;
};
$constraint = $io->askAndValidate(
'Enter the version constraint to require (or leave blank to use the latest version): ',
$validator,
3,
false
);
if (false === $constraint) {
list(, $constraint) = $this->findBestVersionAndNameForPackage($input, $package, $platformRepo, $preferredStability);
$io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>',
$constraint,
$package
));
}
$package .= ' '.$constraint;
}
if (false !== $package) {
$requires[] = $package;
$existingPackages[] = substr($package, 0, strpos($package, ' '));
}
}
}
return $requires;
}
/** /**
* @param string $author * @param string $author
* *
@ -815,229 +624,6 @@ EOT
return false !== filter_var($email, FILTER_VALIDATE_EMAIL); return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
} }
/**
* @param string|null $minimumStability
*
* @return RepositorySet
*/
private function getRepositorySet(InputInterface $input, $minimumStability = null)
{
$key = $minimumStability ?: 'default';
if (!isset($this->repositorySets[$key])) {
$this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?: $this->getMinimumStability($input));
$repositorySet->addRepository($this->getRepos());
}
return $this->repositorySets[$key];
}
/**
* @return string
*/
private function getMinimumStability(InputInterface $input)
{
if ($input->hasOption('stability')) {
return VersionParser::normalizeStability($input->getOption('stability') ?: 'stable');
}
$file = Factory::getComposerFile();
if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
if (!empty($composer['minimum-stability'])) {
return VersionParser::normalizeStability($composer['minimum-stability']);
}
}
return 'stable';
}
/**
* Given a package name, this determines the best version to use in the require key.
*
* This returns a version with the ~ operator prefixed when possible.
*
* @param string $name
* @param PlatformRepository|null $platformRepo
* @param string $preferredStability
* @param string|null $requiredVersion
* @param string $minimumStability
* @param bool $fixed
* @throws \InvalidArgumentException
* @return array{string, string} name version
*/
private function findBestVersionAndNameForPackage(InputInterface $input, $name, PlatformRepository $platformRepo = null, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = null)
{
// handle ignore-platform-reqs flag if present
$ignorePlatformReqs = false;
if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) {
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
}
$platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs);
// find the latest version allowed in this repo set
$versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability), $platformRepo);
$effectiveMinimumStability = $minimumStability ?: $this->getMinimumStability($input);
$package = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter);
if (!$package) {
// platform packages can not be found in the pool in versions other than the local platform's has
// so if platform reqs are ignored we just take the user's word for it
if ($platformRequirementFilter->isIgnored($name)) {
return array($name, $requiredVersion ?: '*');
}
// Check whether the package requirements were the problem
if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && ($candidate = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) {
throw new \InvalidArgumentException(sprintf(
'Package %s%s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo),
$name,
$requiredVersion ? ' at version '.$requiredVersion : ''
));
}
// Check whether the minimum stability was the problem but the package exists
if ($package = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
// we must first verify if a valid package would be found in a lower priority repository
if ($allReposPackage = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
throw new \InvalidArgumentException(
'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'
);
}
throw new \InvalidArgumentException(sprintf(
'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.',
$name,
$effectiveMinimumStability
));
}
// Check whether the required version was the problem
if ($requiredVersion && $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter)) {
// we must first verify if a valid package would be found in a lower priority repository
if ($allReposPackage = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, PlatformRequirementFilterFactory::ignoreNothing(), RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
throw new \InvalidArgumentException(
'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your constraint and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'
);
}
throw new \InvalidArgumentException(sprintf(
'Could not find package %s in a version matching "%s" and a stability matching "'.$effectiveMinimumStability.'".',
$name,
$requiredVersion
));
}
// Check whether the PHP version was the problem for all versions
if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
$additional = '';
if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) {
$additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".';
}
throw new \InvalidArgumentException(sprintf(
'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s',
$name,
$additional
));
}
// Check for similar names/typos
$similar = $this->findSimilar($name);
if ($similar) {
if (in_array($name, $similar, true)) {
throw new \InvalidArgumentException(sprintf(
"Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.",
$name
));
}
throw new \InvalidArgumentException(sprintf(
"Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s",
$name,
implode("\n ", $similar)
));
}
throw new \InvalidArgumentException(sprintf(
'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).',
$name,
$effectiveMinimumStability
));
}
return array(
$package->getPrettyName(),
$fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
);
}
/**
* @return string
*/
private function getPlatformExceptionDetails(PackageInterface $candidate, PlatformRepository $platformRepo = null)
{
$details = array();
if (!$platformRepo) {
return '';
}
foreach ($candidate->getRequires() as $link) {
if (!PlatformRepository::isPlatformPackage($link->getTarget())) {
continue;
}
$platformPkg = $platformRepo->findPackage($link->getTarget(), '*');
if (!$platformPkg) {
if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".';
} else {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.';
}
continue;
}
if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) {
$platformPkgVersion = $platformPkg->getPrettyVersion();
$platformExtra = $platformPkg->getExtra();
if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) {
$platformPkgVersion .= ' ('.$platformPkg->getDescription().')';
}
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.';
}
}
if (!$details) {
return '';
}
return ':'.PHP_EOL.' - ' . implode(PHP_EOL.' - ', $details);
}
/**
* @param string $package
*
* @return array<string>
*/
private function findSimilar($package)
{
try {
$results = $this->repos->search($package);
} catch (\Exception $e) {
// ignore search errors
return array();
}
$similarPackages = array();
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
foreach ($results as $result) {
if ($installedRepo->findPackage($result['name'], '*')) {
// Ignore installed package
continue;
}
$similarPackages[$result['name']] = levenshtein($package, $result['name']);
}
asort($similarPackages);
return array_keys(array_slice($similarPackages, 0, 5));
}
/** /**
* @return void * @return void
*/ */

@ -84,7 +84,8 @@ EOT
$io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>'); $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
} }
if ($args = $input->getArgument('packages')) { $args = $input->getArgument('packages');
if (count($args) > 0) {
$io->writeError('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>'); $io->writeError('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>');
return 1; return 1;
@ -96,7 +97,7 @@ EOT
return 1; return 1;
} }
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts')); $composer = $this->requireComposer();
if ((!$composer->getLocker() || !$composer->getLocker()->isLocked()) && !HttpDownloader::isCurlEnabled()) { if ((!$composer->getLocker() || !$composer->getLocker()->isLocked()) && !HttpDownloader::isCurlEnabled()) {
$io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>'); $io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
@ -115,8 +116,6 @@ EOT
$apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
$apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));
$install $install
@ -129,7 +128,7 @@ EOT
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setClassMapAuthoritative($authoritative) ->setClassMapAuthoritative($authoritative)
->setApcuAutoloader($apcu, $apcuPrefix) ->setApcuAutoloader($apcu, $apcuPrefix)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
; ;
if ($input->getOption('no-plugins')) { if ($input->getOption('no-plugins')) {

@ -54,12 +54,9 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
*/ */
class OutdatedCommand extends ShowCommand class OutdatedCommand extends BaseCommand
{ {
/** /**
* @return void * @return void
@ -63,7 +63,7 @@ EOT
; ;
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$args = array( $args = array(
'command' => 'show', 'command' => 'show',
@ -75,7 +75,7 @@ EOT
if ($input->getOption('direct')) { if ($input->getOption('direct')) {
$args['--direct'] = true; $args['--direct'] = true;
} }
if ($input->getArgument('package')) { if (null !== $input->getArgument('package')) {
$args['package'] = $input->getArgument('package'); $args['package'] = $input->getArgument('package');
} }
if ($input->getOption('strict')) { if ($input->getOption('strict')) {
@ -90,9 +90,7 @@ EOT
if ($input->getOption('no-dev')) { if ($input->getOption('no-dev')) {
$args['--no-dev'] = true; $args['--no-dev'] = true;
} }
if ($input->getOption('ignore-platform-req')) {
$args['--ignore-platform-req'] = $input->getOption('ignore-platform-req'); $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
}
if ($input->getOption('ignore-platform-reqs')) { if ($input->getOption('ignore-platform-reqs')) {
$args['--ignore-platform-reqs'] = true; $args['--ignore-platform-reqs'] = true;
} }

@ -0,0 +1,424 @@
<?php
namespace Composer\Command;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
trait PackageDiscoveryTrait
{
/**
* @return CompositeRepository
*/
protected function getRepos()
{
if (null === $this->repos) {
$this->repos = new CompositeRepository(array_merge(
array(new PlatformRepository),
RepositoryFactory::defaultRepos($this->getIO())
));
}
return $this->repos;
}
private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet
{
$key = $minimumStability ?? 'default';
if (!isset($this->repositorySets[$key])) {
$this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input));
$repositorySet->addRepository($this->getRepos());
}
return $this->repositorySets[$key];
}
private function getMinimumStability(InputInterface $input): string
{
if ($input->hasOption('stability')) { // @phpstan-ignore-line as InitCommand does have this option but not all classes using this trait do
return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable');
}
// @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there
$file = Factory::getComposerFile();
if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode((string) file_get_contents($file), true))) {
if (!empty($composer['minimum-stability'])) {
return VersionParser::normalizeStability($composer['minimum-stability']);
}
}
return 'stable';
}
/**
* @param array<string> $requires
*
* @return array<string>
* @throws \Exception
*/
final protected function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = array(), ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $checkProvidedVersions = true, bool $fixed = false): array
{
if (count($requires) > 0) {
$requires = $this->normalizeRequirements($requires);
$result = array();
$io = $this->getIO();
foreach ($requires as $requirement) {
if (!isset($requirement['version'])) {
// determine the best version automatically
list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, null, null, $fixed);
$requirement['version'] = $version;
// replace package name from packagist.org
$requirement['name'] = $name;
$io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>',
$requirement['version'],
$requirement['name']
));
} else {
// check that the specified version/constraint exists before we proceed
list($name) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev', $fixed);
// replace package name from packagist.org
$requirement['name'] = $name;
}
$result[] = $requirement['name'] . ' ' . $requirement['version'];
}
return $result;
}
$versionParser = new VersionParser();
// Collect existing packages
$composer = $this->tryComposer();
$installedRepo = null;
if (null !== $composer) {
$installedRepo = $composer->getRepositoryManager()->getLocalRepository();
}
$existingPackages = array();
if (null !== $installedRepo) {
foreach ($installedRepo->getPackages() as $package) {
$existingPackages[] = $package->getName();
}
}
unset($composer, $installedRepo);
$io = $this->getIO();
while (null !== $package = $io->ask('Search for a package: ')) {
$matches = $this->getRepos()->search($package);
if (count($matches) > 0) {
// Remove existing packages from search results.
foreach ($matches as $position => $foundPackage) {
if (in_array($foundPackage['name'], $existingPackages, true)) {
unset($matches[$position]);
}
}
$matches = array_values($matches);
$exactMatch = false;
$choices = array();
foreach ($matches as $position => $foundPackage) {
$abandoned = '';
if (isset($foundPackage['abandoned'])) {
if (is_string($foundPackage['abandoned'])) {
$replacement = sprintf('Use %s instead', $foundPackage['abandoned']);
} else {
$replacement = 'No replacement was suggested';
}
$abandoned = sprintf('<warning>Abandoned. %s.</warning>', $replacement);
}
$choices[] = sprintf(' <info>%5s</info> %s %s', "[$position]", $foundPackage['name'], $abandoned);
if ($foundPackage['name'] === $package) {
$exactMatch = true;
break;
}
}
// no match, prompt which to pick
if (!$exactMatch) {
$io->writeError(array(
'',
sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
'',
));
$io->writeError($choices);
$io->writeError('');
$validator = function ($selection) use ($matches, $versionParser) {
if ('' === $selection) {
return false;
}
if (is_numeric($selection) && isset($matches[(int) $selection])) {
$package = $matches[(int) $selection];
return $package['name'];
}
if (Preg::isMatch('{^\s*(?P<name>[\S/]+)(?:\s+(?P<version>\S+))?\s*$}', $selection, $packageMatches)) {
if (isset($packageMatches['version'])) {
// parsing `acme/example ~2.3`
// validate version constraint
$versionParser->parseConstraints($packageMatches['version']);
return $packageMatches['name'].' '.$packageMatches['version'];
}
// parsing `acme/example`
return $packageMatches['name'];
}
throw new \Exception('Not a valid selection');
};
$package = $io->askAndValidate(
'Enter package # to add, or the complete package name if it is not listed: ',
$validator,
3,
false
);
}
// no constraint yet, determine the best version automatically
if (false !== $package && false === strpos($package, ' ')) {
$validator = function ($input) {
$input = trim($input);
return strlen($input) > 0 ? $input : false;
};
$constraint = $io->askAndValidate(
'Enter the version constraint to require (or leave blank to use the latest version): ',
$validator,
3,
false
);
if (false === $constraint) {
list(, $constraint) = $this->findBestVersionAndNameForPackage($input, $package, $platformRepo, $preferredStability);
$io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>',
$constraint,
$package
));
}
$package .= ' '.$constraint;
}
if (false !== $package) {
$requires[] = $package;
$existingPackages[] = explode(' ', $package)[0];
}
}
}
return $requires;
}
/**
* Given a package name, this determines the best version to use in the require key.
*
* This returns a version with the ~ operator prefixed when possible.
*
* @throws \InvalidArgumentException
* @return array{string, string} name version
*/
private function findBestVersionAndNameForPackage(InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', ?string $requiredVersion = null, ?string $minimumStability = null, bool $fixed = false): array
{
// handle ignore-platform-reqs flag if present
$platformRequirementFilter = $this->getPlatformRequirementFilter($input);
// find the latest version allowed in this repo set
$versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability), $platformRepo);
$effectiveMinimumStability = $minimumStability ?? $this->getMinimumStability($input);
$package = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter);
if (false === $package) {
// platform packages can not be found in the pool in versions other than the local platform's has
// so if platform reqs are ignored we just take the user's word for it
if ($platformRequirementFilter->isIgnored($name)) {
return array($name, $requiredVersion ?: '*');
}
// Check whether the package requirements were the problem
if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) {
throw new \InvalidArgumentException(sprintf(
'Package %s%s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo),
$name,
is_string($requiredVersion) ? ' at version '.$requiredVersion : ''
));
}
// Check whether the minimum stability was the problem but the package exists
if (false !== ($package = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
// we must first verify if a valid package would be found in a lower priority repository
if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) {
throw new \InvalidArgumentException(
'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'
);
}
throw new \InvalidArgumentException(sprintf(
'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.',
$name,
$effectiveMinimumStability
));
}
// Check whether the required version was the problem
if (is_string($requiredVersion) && false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter))) {
// we must first verify if a valid package would be found in a lower priority repository
if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, $requiredVersion, $preferredStability, PlatformRequirementFilterFactory::ignoreNothing(), RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) {
throw new \InvalidArgumentException(
'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your constraint and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'
);
}
throw new \InvalidArgumentException(sprintf(
'Could not find package %s in a version matching "%s" and a stability matching "'.$effectiveMinimumStability.'".',
$name,
$requiredVersion
));
}
// Check whether the PHP version was the problem for all versions
if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
$additional = '';
if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) {
$additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".';
}
throw new \InvalidArgumentException(sprintf(
'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s',
$name,
$additional
));
}
// Check for similar names/typos
$similar = $this->findSimilar($name);
if (count($similar) > 0) {
if (in_array($name, $similar, true)) {
throw new \InvalidArgumentException(sprintf(
"Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.",
$name
));
}
throw new \InvalidArgumentException(sprintf(
"Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s",
$name,
implode("\n ", $similar)
));
}
throw new \InvalidArgumentException(sprintf(
'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).',
$name,
$effectiveMinimumStability
));
}
return array(
$package->getPrettyName(),
$fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
);
}
/**
* @return array<string>
*/
private function findSimilar(string $package): array
{
try {
if (null === $this->repos) {
throw new \LogicException('findSimilar was called before $this->repos was initialized');
}
$results = $this->repos->search($package);
} catch (\Throwable $e) {
if ($e instanceof \LogicException) {
throw $e;
}
// ignore search errors
return array();
}
$similarPackages = array();
$installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository();
foreach ($results as $result) {
if (null !== $installedRepo->findPackage($result['name'], '*')) {
// Ignore installed package
continue;
}
$similarPackages[$result['name']] = levenshtein($package, $result['name']);
}
asort($similarPackages);
return array_keys(array_slice($similarPackages, 0, 5));
}
private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null): string
{
$details = array();
if (null === $platformRepo) {
return '';
}
foreach ($candidate->getRequires() as $link) {
if (!PlatformRepository::isPlatformPackage($link->getTarget())) {
continue;
}
$platformPkg = $platformRepo->findPackage($link->getTarget(), '*');
if (null === $platformPkg) {
if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".';
} else {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.';
}
continue;
}
if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) {
$platformPkgVersion = $platformPkg->getPrettyVersion();
$platformExtra = $platformPkg->getExtra();
if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) {
$platformPkgVersion .= ' ('.$platformPkg->getDescription().')';
}
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.';
}
}
if (count($details) === 0) {
return '';
}
return ':'.PHP_EOL.' - ' . implode(PHP_EOL.' - ', $details);
}
}

@ -51,12 +51,7 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* Execute the function.
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
return parent::doExecute($input, $output, true); return parent::doExecute($input, $output, true);
} }

@ -70,11 +70,11 @@ EOT
; ;
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$io = $this->getIO(); $io = $this->getIO();
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts')); $composer = $this->requireComposer();
$localRepo = $composer->getRepositoryManager()->getLocalRepository(); $localRepo = $composer->getRepositoryManager()->getLocalRepository();
$packagesToReinstall = array(); $packagesToReinstall = array();
@ -139,8 +139,6 @@ EOT
$downloadManager = $composer->getDownloadManager(); $downloadManager = $composer->getDownloadManager();
$package = $composer->getPackage(); $package = $composer->getPackage();
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$installationManager->setOutputProgress(!$input->getOption('no-progress')); $installationManager->setOutputProgress(!$input->getOption('no-progress'));
if ($input->getOption('no-plugins')) { if ($input->getOption('no-plugins')) {
$installationManager->disablePlugins(); $installationManager->disablePlugins();
@ -166,7 +164,7 @@ EOT
$generator = $composer->getAutoloadGenerator(); $generator = $composer->getAutoloadGenerator();
$generator->setClassMapAuthoritative($authoritative); $generator->setClassMapAuthoritative($authoritative);
$generator->setApcu($apcu, $apcuPrefix); $generator->setApcu($apcu, $apcuPrefix);
$generator->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
$generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
} }

@ -80,7 +80,7 @@ EOT
protected function interact(InputInterface $input, OutputInterface $output) protected function interact(InputInterface $input, OutputInterface $output)
{ {
if ($input->getOption('unused')) { if ($input->getOption('unused')) {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$locker = $composer->getLocker(); $locker = $composer->getLocker();
if (!$locker->isLocked()) { if (!$locker->isLocked()) {
throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused'); throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused');
@ -115,7 +115,7 @@ EOT
} }
$input->setArgument('packages', array_merge($input->getArgument('packages'), $unused)); $input->setArgument('packages', array_merge($input->getArgument('packages'), $unused));
if (!$input->getArgument('packages')) { if (count($input->getArgument('packages')) === 0) {
$this->getIO()->writeError('<info>No unused packages to remove</info>'); $this->getIO()->writeError('<info>No unused packages to remove</info>');
$this->setCode(function () { $this->setCode(function () {
return 0; return 0;
@ -125,7 +125,6 @@ EOT
} }
/** /**
* @return int
* @throws \Seld\JsonLint\ParsingException * @throws \Seld\JsonLint\ParsingException
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
@ -210,13 +209,13 @@ EOT
return 0; return 0;
} }
if ($composer = $this->getComposer(false)) { if ($composer = $this->tryComposer()) {
$composer->getPluginManager()->deactivateInstalledPlugins(); $composer->getPluginManager()->deactivateInstalledPlugins();
} }
// Update packages // Update packages
$this->resetComposer(); $this->resetComposer();
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts')); $composer = $this->requireComposer();
if ($dryRun) { if ($dryRun) {
$rootPackage = $composer->getPackage(); $rootPackage = $composer->getPackage();
@ -258,8 +257,6 @@ EOT
$io->writeError('<info>Running composer update '.implode(' ', $packages).$flags.'</info>'); $io->writeError('<info>Running composer update '.implode(' ', $packages).$flags.'</info>');
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$install $install
->setVerbose($input->getOption('verbose')) ->setVerbose($input->getOption('verbose'))
->setDevMode($updateDevMode) ->setDevMode($updateDevMode)
@ -269,7 +266,7 @@ EOT
->setUpdate(true) ->setUpdate(true)
->setInstall(!$input->getOption('no-install')) ->setInstall(!$input->getOption('no-install'))
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setDryRun($dryRun) ->setDryRun($dryRun)
; ;

@ -14,6 +14,7 @@ namespace Composer\Command;
use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Request;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Repository\RepositorySet;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@ -38,8 +39,16 @@ use Composer\Util\Silencer;
* @author Jérémy Romey <jeremy@free-agent.fr> * @author Jérémy Romey <jeremy@free-agent.fr>
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
*/ */
class RequireCommand extends InitCommand class RequireCommand extends BaseCommand
{ {
use PackageDiscoveryTrait;
// Properties for PackageDiscoveryTrait
/** @var ?CompositeRepository */
protected $repos;
/** @var RepositorySet[] */
private $repositorySets;
/** @var bool */ /** @var bool */
private $newlyCreated; private $newlyCreated;
/** @var bool */ /** @var bool */
@ -110,7 +119,6 @@ EOT
} }
/** /**
* @return int
* @throws \Seld\JsonLint\ParsingException * @throws \Seld\JsonLint\ParsingException
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
@ -177,11 +185,11 @@ EOT
} }
} }
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->requireComposer();
$repos = $composer->getRepositoryManager()->getRepositories(); $repos = $composer->getRepositoryManager()->getRepositories();
$platformOverrides = $composer->getConfig()->get('platform') ?: array(); $platformOverrides = $composer->getConfig()->get('platform');
// initialize $this->repos as it is used by the parent InitCommand // initialize $this->repos as it is used by the PackageDiscoveryTrait
$this->repos = new CompositeRepository(array_merge( $this->repos = new CompositeRepository(array_merge(
array($platformRepo = new PlatformRepository(array(), $platformOverrides)), array($platformRepo = new PlatformRepository(array(), $platformOverrides)),
$repos $repos
@ -256,7 +264,7 @@ EOT
$this->firstRequire = $this->newlyCreated; $this->firstRequire = $this->newlyCreated;
if (!$this->firstRequire) { if (!$this->firstRequire) {
$composerDefinition = $this->json->read(); $composerDefinition = $this->json->read();
if (empty($composerDefinition['require']) && empty($composerDefinition['require-dev'])) { if (count($composerDefinition['require'] ?? []) === 0 && count($composerDefinition['require-dev'] ?? []) === 0) {
$this->firstRequire = true; $this->firstRequire = true;
} }
} }
@ -355,7 +363,7 @@ EOT
{ {
// Update packages // Update packages
$this->resetComposer(); $this->resetComposer();
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts')); $composer = $this->requireComposer();
$this->dependencyResolutionCompleted = false; $this->dependencyResolutionCompleted = false;
$composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, array($this, 'markSolverComplete'), 10000); $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, array($this, 'markSolverComplete'), 10000);
@ -401,7 +409,6 @@ EOT
$install = Installer::create($io, $composer); $install = Installer::create($io, $composer);
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
list($preferSource, $preferDist) = $this->getPreferredInstallOptions($composer->getConfig(), $input); list($preferSource, $preferDist) = $this->getPreferredInstallOptions($composer->getConfig(), $input);
$install $install
@ -416,7 +423,7 @@ EOT
->setUpdate(true) ->setUpdate(true)
->setInstall(!$input->getOption('no-install')) ->setInstall(!$input->getOption('no-install'))
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setPreferStable($input->getOption('prefer-stable')) ->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest')) ->setPreferLowest($input->getOption('prefer-lowest'))
; ;
@ -472,7 +479,7 @@ EOT
return true; return true;
} }
protected function interact(InputInterface $input, OutputInterface $output) protected function interact(InputInterface $input, OutputInterface $output): void
{ {
return; return;
} }

@ -54,7 +54,7 @@ class RunScriptCommand extends BaseCommand
->setAliases(array('run')) ->setAliases(array('run'))
->setDescription('Runs the scripts defined in composer.json.') ->setDescription('Runs the scripts defined in composer.json.')
->setDefinition(array( ->setDefinition(array(
new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), new InputArgument('script', InputArgument::REQUIRED, 'Script name to run.'),
new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
@ -73,14 +73,11 @@ EOT
; ;
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
if ($input->getOption('list')) { if ($input->getOption('list')) {
return $this->listScripts($output); return $this->listScripts($output);
} }
if (!$input->getArgument('script')) {
throw new \RuntimeException('Missing required argument "script"');
}
$script = $input->getArgument('script'); $script = $input->getArgument('script');
if (!in_array($script, $this->scriptEvents)) { if (!in_array($script, $this->scriptEvents)) {
@ -89,7 +86,7 @@ EOT
} }
} }
$composer = $this->getComposer(); $composer = $this->requireComposer();
$devMode = $input->getOption('dev') || !$input->getOption('no-dev'); $devMode = $input->getOption('dev') || !$input->getOption('no-dev');
$event = new ScriptEvent($script, $composer, $this->getIO(), $devMode); $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode);
$hasListeners = $composer->getEventDispatcher()->hasEventListeners($event); $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event);
@ -117,7 +114,7 @@ EOT
*/ */
protected function listScripts(OutputInterface $output) protected function listScripts(OutputInterface $output)
{ {
$scripts = $this->getComposer()->getPackage()->getScripts(); $scripts = $this->requireComposer()->getPackage()->getScripts();
if (!count($scripts)) { if (!count($scripts)) {
return 0; return 0;

@ -64,12 +64,9 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$args = $input->getArguments(); $args = $input->getArguments();

@ -56,7 +56,7 @@ EOT
; ;
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
// init repos // init repos
$platformRepo = new PlatformRepository; $platformRepo = new PlatformRepository;
@ -69,7 +69,7 @@ EOT
return 1; return 1;
} }
if (!($composer = $this->getComposer(false))) { if (!($composer = $this->tryComposer())) {
$composer = Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins')); $composer = Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins'));
} }
$localRepo = $composer->getRepositoryManager()->getLocalRepository(); $localRepo = $composer->getRepositoryManager()->getLocalRepository();

@ -75,10 +75,9 @@ EOT
} }
/** /**
* @return int
* @throws FilesystemException * @throws FilesystemException
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
// trigger autoloading of a few classes which may be needed when verifying/swapping the phar file // trigger autoloading of a few classes which may be needed when verifying/swapping the phar file
// to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252 // to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252

@ -15,6 +15,7 @@ namespace Composer\Command;
use Composer\Composer; use Composer\Composer;
use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\DefaultPolicy;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface; use Composer\Package\CompletePackageInterface;
@ -114,7 +115,7 @@ EOT
$this->initStyles($output); $this->initStyles($output);
} }
$composer = $this->getComposer(false); $composer = $this->tryComposer();
$io = $this->getIO(); $io = $this->getIO();
if ($input->getOption('installed')) { if ($input->getOption('installed')) {
@ -123,7 +124,7 @@ EOT
if ($input->getOption('outdated')) { if ($input->getOption('outdated')) {
$input->setOption('latest', true); $input->setOption('latest', true);
} elseif ($input->getOption('ignore')) { } elseif (count($input->getOption('ignore')) > 0) {
$io->writeError('<warning>You are using the option "ignore" for action other than "outdated", it will be ignored.</warning>'); $io->writeError('<warning>You are using the option "ignore" for action other than "outdated", it will be ignored.</warning>');
} }
@ -158,7 +159,7 @@ EOT
return 1; return 1;
} }
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false); $platformReqFilter = $this->getPlatformRequirementFilter($input);
// init repos // init repos
$platformOverrides = array(); $platformOverrides = array();
@ -169,12 +170,15 @@ EOT
$lockedRepo = null; $lockedRepo = null;
if ($input->getOption('self')) { if ($input->getOption('self')) {
$package = $this->getComposer()->getPackage(); $package = $this->requireComposer()->getPackage();
if ($input->getOption('name-only')) { if ($input->getOption('name-only')) {
$io->write($package->getName()); $io->write($package->getName());
return 0; return 0;
} }
if ($input->getArgument('package')) {
throw new \InvalidArgumentException('You cannot use --self together with a package name');
}
$repos = $installedRepo = new InstalledRepository(array(new RootPackageRepository($package))); $repos = $installedRepo = new InstalledRepository(array(new RootPackageRepository($package)));
} elseif ($input->getOption('platform')) { } elseif ($input->getOption('platform')) {
$repos = $installedRepo = new InstalledRepository(array($platformRepo)); $repos = $installedRepo = new InstalledRepository(array($platformRepo));
@ -213,7 +217,7 @@ EOT
} else { } else {
// --installed / default case // --installed / default case
if (!$composer) { if (!$composer) {
$composer = $this->getComposer(); $composer = $this->requireComposer();
} }
$rootPkg = $composer->getPackage(); $rootPkg = $composer->getPackage();
$repos = $installedRepo = new InstalledRepository(array($composer->getRepositoryManager()->getLocalRepository())); $repos = $installedRepo = new InstalledRepository(array($composer->getRepositoryManager()->getLocalRepository()));
@ -243,11 +247,12 @@ EOT
$packageFilter = $input->getArgument('package'); $packageFilter = $input->getArgument('package');
// show single package or single version // show single package or single version
if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) { if (isset($package)) {
if (empty($package)) { $versions = array($package->getPrettyVersion() => $package->getVersion());
list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version')); } elseif (null !== $packageFilter && str_contains($packageFilter, '*')) {
list($package, $versions) = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version'));
if (empty($package)) { if (!isset($package)) {
$options = $input->getOptions(); $options = $input->getOptions();
$hint = ''; $hint = '';
if ($input->getOption('locked')) { if ($input->getOption('locked')) {
@ -256,7 +261,7 @@ EOT
if (isset($options['working-dir'])) { if (isset($options['working-dir'])) {
$hint .= ' in ' . $options['working-dir'] . '/composer.json'; $hint .= ' in ' . $options['working-dir'] . '/composer.json';
} }
if (PlatformRepository::isPlatformPackage($input->getArgument('package')) && !$input->getOption('platform')) { if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
$hint .= ', try using --platform (-p) to show platform packages'; $hint .= ', try using --platform (-p) to show platform packages';
} }
if (!$input->getOption('all')) { if (!$input->getOption('all')) {
@ -265,10 +270,11 @@ EOT
throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.'); throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.');
} }
} else {
$versions = array($package->getPrettyVersion() => $package->getVersion());
} }
if (isset($package)) {
assert(isset($versions));
$exitCode = 0; $exitCode = 0;
if ($input->getOption('tree')) { if ($input->getOption('tree')) {
$arrayTree = $this->generatePackageTree($package, $installedRepo, $repos); $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos);
@ -278,10 +284,13 @@ EOT
} else { } else {
$this->displayPackageTree(array($arrayTree)); $this->displayPackageTree(array($arrayTree));
} }
} else {
return $exitCode;
}
$latestPackage = null; $latestPackage = null;
if ($input->getOption('latest')) { if ($input->getOption('latest')) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('minor-only'), $ignorePlatformReqs); $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('minor-only'), $platformReqFilter);
} }
if ( if (
$input->getOption('outdated') $input->getOption('outdated')
@ -304,7 +313,6 @@ EOT
} else { } else {
$this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null); $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null);
} }
}
return $exitCode; return $exitCode;
} }
@ -407,7 +415,7 @@ EOT
if ($showLatest && $showVersion) { if ($showLatest && $showVersion) {
foreach ($packages[$type] as $package) { foreach ($packages[$type] as $package) {
if (is_object($package)) { if (is_object($package)) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMinorOnly, $ignorePlatformReqs); $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMinorOnly, $platformReqFilter);
if ($latestPackage === false) { if ($latestPackage === false) {
continue; continue;
} }
@ -597,7 +605,7 @@ EOT
*/ */
protected function getRootRequires() protected function getRootRequires()
{ {
$rootPackage = $this->getComposer()->getPackage(); $rootPackage = $this->requireComposer()->getPackage();
return array_map( return array_map(
'strtolower', 'strtolower',
@ -717,7 +725,7 @@ EOT
$io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
$io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); $io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
if ($installedRepo->hasPackage($package)) { if ($installedRepo->hasPackage($package)) {
$io->write('<info>path</info> : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)))); $io->write('<info>path</info> : ' . sprintf('%s', realpath($this->requireComposer()->getInstallationManager()->getInstallPath($package))));
} }
$io->write('<info>names</info> : ' . implode(', ', $package->getNames())); $io->write('<info>names</info> : ' . implode(', ', $package->getNames()));
@ -865,7 +873,7 @@ EOT
$latestPackage = $package; $latestPackage = $package;
} }
if ($package->getSourceType()) { if (null !== $package->getSourceType()) {
$json['source'] = array( $json['source'] = array(
'type' => $package->getSourceType(), 'type' => $package->getSourceType(),
'url' => $package->getSourceUrl(), 'url' => $package->getSourceUrl(),
@ -873,7 +881,7 @@ EOT
); );
} }
if ($package->getDistType()) { if (null !== $package->getDistType()) {
$json['dist'] = array( $json['dist'] = array(
'type' => $package->getDistType(), 'type' => $package->getDistType(),
'url' => $package->getDistUrl(), 'url' => $package->getDistUrl(),
@ -882,7 +890,7 @@ EOT
} }
if ($installedRepo->hasPackage($package)) { if ($installedRepo->hasPackage($package)) {
$json['path'] = realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)); $json['path'] = realpath($this->requireComposer()->getInstallationManager()->getInstallPath($package));
if ($json['path'] === false) { if ($json['path'] === false) {
unset($json['path']); unset($json['path']);
} }
@ -1281,12 +1289,9 @@ EOT
/** /**
* Given a package, this finds the latest package matching it * Given a package, this finds the latest package matching it
* *
* @param bool $minorOnly
* @param bool|string $ignorePlatformReqs
*
* @return PackageInterface|false * @return PackageInterface|false
*/ */
private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, $minorOnly = false, $ignorePlatformReqs = false) private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $minorOnly, PlatformRequirementFilterInterface $platformReqFilter)
{ {
// find the latest version allowed in this repo set // find the latest version allowed in this repo set
$name = $package->getName(); $name = $package->getName();
@ -1311,7 +1316,7 @@ EOT
$targetVersion = '^' . $package->getVersion(); $targetVersion = '^' . $package->getVersion();
} }
$candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter);
while ($candidate instanceof AliasPackage) { while ($candidate instanceof AliasPackage) {
$candidate = $candidate->getAliasOf(); $candidate = $candidate->getAliasOf();
} }

@ -59,12 +59,9 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
@ -86,7 +83,7 @@ EOT
private function doExecute(InputInterface $input) private function doExecute(InputInterface $input)
{ {
// init repos // init repos
$composer = $this->getComposer(); $composer = $this->requireComposer();
$installedRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = $composer->getRepositoryManager()->getLocalRepository();

@ -50,12 +50,9 @@ EOT
; ;
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$composer = $this->getComposer(); $composer = $this->requireComposer();
$installedRepos = array( $installedRepos = array(
new RootPackageRepository(clone $composer->getPackage()), new RootPackageRepository(clone $composer->getPackage()),

@ -109,10 +109,6 @@ EOT
; ;
} }
/**
* @return int
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$io = $this->getIO(); $io = $this->getIO();
@ -123,7 +119,7 @@ EOT
$io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>'); $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
} }
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts')); $composer = $this->requireComposer();
if (!HttpDownloader::isCurlEnabled()) { if (!HttpDownloader::isCurlEnabled()) {
$io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>'); $io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
@ -133,7 +129,7 @@ EOT
$reqs = $this->formatRequirements($input->getOption('with')); $reqs = $this->formatRequirements($input->getOption('with'));
// extract --with shorthands from the allowlist // extract --with shorthands from the allowlist
if ($packages) { if (count($packages) > 0) {
$allowlistPackagesWithRequirements = array_filter($packages, function ($pkg) { $allowlistPackagesWithRequirements = array_filter($packages, function ($pkg) {
return Preg::isMatch('{\S+[ =:]\S+}', $pkg); return Preg::isMatch('{\S+[ =:]\S+}', $pkg);
}); });
@ -219,8 +215,6 @@ EOT
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
} }
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$install $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
->setVerbose($input->getOption('verbose')) ->setVerbose($input->getOption('verbose'))
@ -236,7 +230,7 @@ EOT
->setUpdateMirrors($updateMirrors) ->setUpdateMirrors($updateMirrors)
->setUpdateAllowList($packages) ->setUpdateAllowList($packages)
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setPreferStable($input->getOption('prefer-stable')) ->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest')) ->setPreferLowest($input->getOption('prefer-lowest'))
; ;

@ -66,10 +66,7 @@ EOT
); );
} }
/** protected function execute(InputInterface $input, OutputInterface $output): int
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$file = $input->getArgument('file') ?: Factory::getComposerFile(); $file = $input->getArgument('file') ?: Factory::getComposerFile();
$io = $this->getIO(); $io = $this->getIO();

@ -63,7 +63,7 @@ interface ConfigSourceInterface
* Add a property * Add a property
* *
* @param string $name Name * @param string $name Name
* @param string $value Value * @param string|string[] $value Value
* *
* @return void * @return void
*/ */

@ -62,7 +62,7 @@ class JsonConfigSource implements ConfigSourceInterface
*/ */
public function addRepository($name, $config, $append = true) public function addRepository($name, $config, $append = true)
{ {
$this->manipulateJson('addRepository', $name, $config, $append, function (&$config, $repo, $repoConfig) use ($append) { $this->manipulateJson('addRepository', function (&$config, $repo, $repoConfig) use ($append) {
// if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have // if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have
// to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation // to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation
if (isset($config['repositories'])) { if (isset($config['repositories'])) {
@ -83,7 +83,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else { } else {
$config['repositories'] = array($repo => $repoConfig) + $config['repositories']; $config['repositories'] = array($repo => $repoConfig) + $config['repositories'];
} }
}); }, $name, $config, $append);
} }
/** /**
@ -91,9 +91,9 @@ class JsonConfigSource implements ConfigSourceInterface
*/ */
public function removeRepository($name) public function removeRepository($name)
{ {
$this->manipulateJson('removeRepository', $name, function (&$config, $repo) { $this->manipulateJson('removeRepository', function (&$config, $repo) {
unset($config['repositories'][$repo]); unset($config['repositories'][$repo]);
}); }, $name);
} }
/** /**
@ -102,7 +102,7 @@ class JsonConfigSource implements ConfigSourceInterface
public function addConfigSetting($name, $value) public function addConfigSetting($name, $value)
{ {
$authConfig = $this->authConfig; $authConfig = $this->authConfig;
$this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) use ($authConfig) { $this->manipulateJson('addConfigSetting', function (&$config, $key, $val) use ($authConfig) {
if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) { if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
list($key, $host) = explode('.', $key, 2); list($key, $host) = explode('.', $key, 2);
if ($authConfig) { if ($authConfig) {
@ -113,7 +113,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else { } else {
$config['config'][$key] = $val; $config['config'][$key] = $val;
} }
}); }, $name, $value);
} }
/** /**
@ -122,7 +122,7 @@ class JsonConfigSource implements ConfigSourceInterface
public function removeConfigSetting($name) public function removeConfigSetting($name)
{ {
$authConfig = $this->authConfig; $authConfig = $this->authConfig;
$this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) use ($authConfig) { $this->manipulateJson('removeConfigSetting', function (&$config, $key) use ($authConfig) {
if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) { if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
list($key, $host) = explode('.', $key, 2); list($key, $host) = explode('.', $key, 2);
if ($authConfig) { if ($authConfig) {
@ -133,7 +133,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else { } else {
unset($config['config'][$key]); unset($config['config'][$key]);
} }
}); }, $name);
} }
/** /**
@ -141,7 +141,7 @@ class JsonConfigSource implements ConfigSourceInterface
*/ */
public function addProperty($name, $value) public function addProperty($name, $value)
{ {
$this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) { $this->manipulateJson('addProperty', function (&$config, $key, $val) {
if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) { if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) {
$bits = explode('.', $key); $bits = explode('.', $key);
$last = array_pop($bits); $last = array_pop($bits);
@ -156,7 +156,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else { } else {
$config[$key] = $val; $config[$key] = $val;
} }
}); }, $name, $value);
} }
/** /**
@ -164,7 +164,7 @@ class JsonConfigSource implements ConfigSourceInterface
*/ */
public function removeProperty($name) public function removeProperty($name)
{ {
$this->manipulateJson('removeProperty', $name, function (&$config, $key) { $this->manipulateJson('removeProperty', function (&$config, $key) {
if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) { if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) {
$bits = explode('.', $key); $bits = explode('.', $key);
$last = array_pop($bits); $last = array_pop($bits);
@ -179,7 +179,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else { } else {
unset($config[$key]); unset($config[$key]);
} }
}); }, $name);
} }
/** /**
@ -187,9 +187,9 @@ class JsonConfigSource implements ConfigSourceInterface
*/ */
public function addLink($type, $name, $value) public function addLink($type, $name, $value)
{ {
$this->manipulateJson('addLink', $type, $name, $value, function (&$config, $type, $name, $value) { $this->manipulateJson('addLink', function (&$config, $type, $name, $value) {
$config[$type][$name] = $value; $config[$type][$name] = $value;
}); }, $type, $name, $value);
} }
/** /**
@ -197,30 +197,25 @@ class JsonConfigSource implements ConfigSourceInterface
*/ */
public function removeLink($type, $name) public function removeLink($type, $name)
{ {
$this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) { $this->manipulateJson('removeSubNode', function (&$config, $type, $name) {
unset($config[$type][$name]); unset($config[$type][$name]);
}); }, $type, $name);
$this->manipulateJson('removeMainKeyIfEmpty', $type, function (&$config, $type) { $this->manipulateJson('removeMainKeyIfEmpty', function (&$config, $type) {
if (0 === count($config[$type])) { if (0 === count($config[$type])) {
unset($config[$type]); unset($config[$type]);
} }
}); }, $type);
} }
/** /**
* @param string $method * @param string $method
* @param mixed ...$args
* @param callable $fallback * @param callable $fallback
* @param mixed ...$args
* *
* @return void * @return void
*/ */
protected function manipulateJson($method, $args, $fallback) private function manipulateJson($method, $fallback, ...$args)
{ {
$args = func_get_args();
// remove method & fallback
array_shift($args);
$fallback = array_pop($args);
if ($this->file->exists()) { if ($this->file->exists()) {
if (!is_writable($this->file->getPath())) { if (!is_writable($this->file->getPath())) {
throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath())); throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath()));

@ -33,7 +33,7 @@ use Symfony\Component\Process\ExecutableFinder;
* The Event Dispatcher. * The Event Dispatcher.
* *
* Example in command: * Example in command:
* $dispatcher = new EventDispatcher($this->getComposer(), $this->getApplication()->getIO()); * $dispatcher = new EventDispatcher($this->requireComposer(), $this->getApplication()->getIO());
* // ... * // ...
* $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD); * $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);
* // ... * // ...

@ -135,10 +135,10 @@ interface IOInterface extends LoggerInterface
* Asks a question to the user. * Asks a question to the user.
* *
* @param string $question The question to ask * @param string $question The question to ask
* @param string $default The default answer if none is given by the user * @param string|bool|int|float|null $default The default answer if none is given by the user
* *
* @throws \RuntimeException If there is no data to read in the input stream * @throws \RuntimeException If there is no data to read in the input stream
* @return string|null The user answer * @return mixed The user answer
*/ */
public function ask($question, $default = null); public function ask($question, $default = null);

@ -237,7 +237,7 @@ class Git
$storeAuth = $this->config->get('store-auths'); $storeAuth = $this->config->get('store-auths');
} }
if ($auth) { if (null !== $auth) {
$authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3];
$command = call_user_func($commandCallable, $authUrl); $command = call_user_func($commandCallable, $authUrl);

@ -28,7 +28,7 @@ use React\Promise\Promise;
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int, retries: int, storeAuth: bool} * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int, retries: int, storeAuth: bool}
* @phpstan-type Job array{url: string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: resource, filename: string|false, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable} * @phpstan-type Job array{url: string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: resource, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable}
*/ */
class CurlDownloader class CurlDownloader
{ {
@ -158,12 +158,11 @@ class CurlDownloader
*/ */
private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array()) private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array())
{ {
$attributes = array_merge(array( // set defaults in a PHPStan-happy way (array_merge is not well supported)
'retryAuthFailure' => true, $attributes['retryAuthFailure'] = $attributes['retryAuthFailure'] ?? true;
'redirects' => 0, $attributes['redirects'] = $attributes['redirects'] ?? 0;
'retries' => 0, $attributes['retries'] = $attributes['retries'] ?? 0;
'storeAuth' => false, $attributes['storeAuth'] = $attributes['storeAuth'] ?? false;
), $attributes);
$originalOptions = $options; $originalOptions = $options;
@ -300,7 +299,7 @@ class CurlDownloader
if (is_resource($job['bodyHandle'])) { if (is_resource($job['bodyHandle'])) {
fclose($job['bodyHandle']); fclose($job['bodyHandle']);
} }
if ($job['filename']) { if (null !== $job['filename']) {
@unlink($job['filename'].'~'); @unlink($job['filename'].'~');
} }
unset($this->jobs[$id]); unset($this->jobs[$id]);
@ -314,7 +313,7 @@ class CurlDownloader
{ {
static $timeoutWarning = false; static $timeoutWarning = false;
if (!$this->jobs) { if (count($this->jobs) === 0) {
return; return;
} }
@ -382,7 +381,7 @@ class CurlDownloader
} }
// prepare response object // prepare response object
if ($job['filename']) { if (null !== $job['filename']) {
$contents = $job['filename'].'~'; $contents = $job['filename'].'~';
if ($statusCode >= 300) { if ($statusCode >= 300) {
rewind($job['bodyHandle']); rewind($job['bodyHandle']);
@ -437,7 +436,7 @@ class CurlDownloader
} }
// resolve promise // resolve promise
if ($job['filename']) { if (null !== $job['filename']) {
rename($job['filename'].'~', $job['filename']); rename($job['filename'].'~', $job['filename']);
call_user_func($job['resolve'], $response); call_user_func($job['resolve'], $response);
} else { } else {
@ -582,7 +581,7 @@ class CurlDownloader
*/ */
private function restartJob(array $job, $url, array $attributes = array()) private function restartJob(array $job, $url, array $attributes = array())
{ {
if ($job['filename']) { if (null !== $job['filename']) {
@unlink($job['filename'].'~'); @unlink($job['filename'].'~');
} }
@ -599,7 +598,7 @@ class CurlDownloader
*/ */
private function failResponse(array $job, Response $response, $errorMessage) private function failResponse(array $job, Response $response, $errorMessage)
{ {
if ($job['filename']) { if (null !== $job['filename']) {
@unlink($job['filename'].'~'); @unlink($job['filename'].'~');
} }
@ -623,7 +622,7 @@ class CurlDownloader
if (is_resource($job['bodyHandle'])) { if (is_resource($job['bodyHandle'])) {
fclose($job['bodyHandle']); fclose($job['bodyHandle']);
} }
if ($job['filename']) { if (null !== $job['filename']) {
@unlink($job['filename'].'~'); @unlink($job['filename'].'~');
} }
call_user_func($job['reject'], $e); call_user_func($job['reject'], $e);

@ -61,7 +61,7 @@ class ApplicationTest extends TestCase
$inputMock->expects($this->any()) $inputMock->expects($this->any())
->method('getFirstArgument') ->method('getFirstArgument')
->will($this->returnValue('show')); ->will($this->returnValue('about'));
$output = new BufferedOutput(); $output = new BufferedOutput();
$expectedOutput = ''; $expectedOutput = '';
@ -122,7 +122,7 @@ class ApplicationTest extends TestCase
$inputMock->expects($this->any()) $inputMock->expects($this->any())
->method('getFirstArgument') ->method('getFirstArgument')
->will($this->returnValue('show')); ->will($this->returnValue('about'));
$outputMock->expects($this->never()) $outputMock->expects($this->never())
->method("writeln"); ->method("writeln");

@ -53,9 +53,12 @@ class ArchiveCommandTest extends TestCase
'mergeApplicationDefinition', 'mergeApplicationDefinition',
'getSynopsis', 'getSynopsis',
'initialize', 'initialize',
'getComposer', 'tryComposer',
'requireComposer',
))->getMock(); ))->getMock();
$command->expects($this->atLeastOnce())->method('getComposer') $command->expects($this->atLeastOnce())->method('tryComposer')
->willReturn($composer);
$command->expects($this->atLeastOnce())->method('requireComposer')
->willReturn($composer); ->willReturn($composer);
$command->run($input, $output); $command->run($input, $output);
@ -74,10 +77,10 @@ class ArchiveCommandTest extends TestCase
'mergeApplicationDefinition', 'mergeApplicationDefinition',
'getSynopsis', 'getSynopsis',
'initialize', 'initialize',
'getComposer', 'tryComposer',
'archive', 'archive',
))->getMock(); ))->getMock();
$command->expects($this->once())->method('getComposer') $command->expects($this->once())->method('tryComposer')
->willReturn(null); ->willReturn(null);
$command->expects($this->once())->method('archive') $command->expects($this->once())->method('archive')
->with( ->with(

@ -80,10 +80,10 @@ class RunScriptCommandTest extends TestCase
'mergeApplicationDefinition', 'mergeApplicationDefinition',
'getSynopsis', 'getSynopsis',
'initialize', 'initialize',
'getComposer', 'requireComposer',
)) ))
->getMock(); ->getMock();
$command->expects($this->any())->method('getComposer')->willReturn($composer); $command->expects($this->any())->method('requireComposer')->willReturn($composer);
$command->run($input, $output); $command->run($input, $output);
} }

@ -64,10 +64,23 @@ class HttpDownloaderMock extends HttpDownloader
throw new \UnexpectedValueException('Unexpected keys in process execution step: '.implode(', ', array_keys($diff))); throw new \UnexpectedValueException('Unexpected keys in process execution step: '.implode(', ', array_keys($diff)));
} }
return array_merge($default, $expect); // set defaults in a PHPStan-happy way (array_merge is not well supported)
$expect['url'] = $expect['url'] ?? $default['url'];
$expect['options'] = $expect['options'] ?? $default['options'];
$expect['status'] = $expect['status'] ?? $default['status'];
$expect['body'] = $expect['body'] ?? $default['body'];
$expect['headers'] = $expect['headers'] ?? $default['headers'];
return $expect;
}, $expectations); }, $expectations);
$this->strict = $strict; $this->strict = $strict;
$this->defaultHandler = array_merge($this->defaultHandler, $defaultHandler);
// set defaults in a PHPStan-happy way (array_merge is not well supported)
$defaultHandler['status'] = $defaultHandler['status'] ?? $this->defaultHandler['status'];
$defaultHandler['body'] = $defaultHandler['body'] ?? $this->defaultHandler['body'];
$defaultHandler['headers'] = $defaultHandler['headers'] ?? $this->defaultHandler['headers'];
$this->defaultHandler = $defaultHandler;
} }
public function assertComplete(): void public function assertComplete(): void

@ -55,15 +55,30 @@ class ProcessExecutorMock extends ProcessExecutor
$default = array('cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null); $default = array('cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null);
$this->expectations = array_map(function ($expect) use ($default) { $this->expectations = array_map(function ($expect) use ($default) {
if (is_string($expect)) { if (is_string($expect)) {
$expect = array('cmd' => $expect); $command = $expect;
$expect = $default;
$expect['cmd'] = $command;
} elseif (count($diff = array_diff_key(array_merge($default, $expect), $default)) > 0) { } elseif (count($diff = array_diff_key(array_merge($default, $expect), $default)) > 0) {
throw new \UnexpectedValueException('Unexpected keys in process execution step: '.implode(', ', array_keys($diff))); throw new \UnexpectedValueException('Unexpected keys in process execution step: '.implode(', ', array_keys($diff)));
} }
return array_merge($default, $expect); // set defaults in a PHPStan-happy way (array_merge is not well supported)
$expect['cmd'] = $expect['cmd'] ?? $default['cmd'];
$expect['return'] = $expect['return'] ?? $default['return'];
$expect['stdout'] = $expect['stdout'] ?? $default['stdout'];
$expect['stderr'] = $expect['stderr'] ?? $default['stderr'];
$expect['callback'] = $expect['callback'] ?? $default['callback'];
return $expect;
}, $expectations); }, $expectations);
$this->strict = $strict; $this->strict = $strict;
$this->defaultHandler = array_merge($this->defaultHandler, $defaultHandler);
// set defaults in a PHPStan-happy way (array_merge is not well supported)
$defaultHandler['return'] = $defaultHandler['return'] ?? $this->defaultHandler['return'];
$defaultHandler['stdout'] = $defaultHandler['stdout'] ?? $this->defaultHandler['stdout'];
$defaultHandler['stderr'] = $defaultHandler['stderr'] ?? $this->defaultHandler['stderr'];
$this->defaultHandler = $defaultHandler;
} }
public function assertComplete(): void public function assertComplete(): void

@ -35,7 +35,7 @@ class Command extends BaseCommand
$this->setName('custom-plugin-command'); $this->setName('custom-plugin-command');
} }
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$output->writeln('Executing'); $output->writeln('Executing');

@ -0,0 +1,5 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
return new \Composer\Console\Application();
Loading…
Cancel
Save