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/process": "^5.4 || ^6.0",
"react/promise": "^2.8",
"composer/pcre": "^1.0"
"composer/pcre": "^1.0",
"symfony/polyfill-php73": "^1.24",
"symfony/polyfill-php80": "^1.24"
},
"require-dev": {
"symfony/phpunit-bridge": "^6.0",
"phpstan/phpstan": "^1.4.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-strict-rules": "^1"
"phpstan/phpstan-strict-rules": "^1",
"phpstan/phpstan-symfony": "^1.1"
},
"suggest": {
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
@ -83,7 +86,7 @@
"scripts-descriptions": {
"compile": "Compile composer.phar",
"test": "Run all tests",
"phpstan": "Runs PHPStan (after phpstan-setup was executed, must be run with PHP7.4)"
"phpstan": "Runs PHPStan"
},
"support": {
"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",
"This file is @generated automatically"
],
"content-hash": "36248b1f2f20c084208c57c0758261e8",
"content-hash": "03b82664ef6f80651118f0c06a49280c",
"packages": [
{
"name": "composer/ca-bundle",
@ -2042,6 +2042,78 @@
},
"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",
"version": "v6.0.3",

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

@ -30,11 +30,6 @@ parameters:
count: 1
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\\.$#"
count: 1
@ -247,7 +242,7 @@ parameters:
-
message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#"
count: 17
count: 15
path: ../src/Composer/Util/Git.php
-
@ -315,6 +310,11 @@ parameters:
count: 1
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\\.$#"
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-deprecation-rules/rules.neon
- ../vendor/phpstan/phpstan-strict-rules/rules.neon
- ../vendor/phpstan/phpstan-symfony/extension.neon
- ../vendor/phpstan/phpstan-symfony/rules.neon
- ./baseline.neon
- ./ignore-by-php-version.neon.php
@ -51,6 +53,9 @@ parameters:
- ../src
- ../tests
symfony:
consoleApplicationLoader: ../tests/console-application.php
dynamicConstantNames:
- Composer\Composer::BRANCH_ALIAS_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();

@ -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;
if ($composer) {
@ -86,20 +86,16 @@ EOT
$config = Factory::createConfig();
}
if (null === $input->getOption('format')) {
$input->setOption('format', $config->get('archive-format'));
}
if (null === $input->getOption('dir')) {
$input->setOption('dir', $config->get('archive-dir'));
}
$format = $input->getOption('format') ?? $config->get('archive-format');
$dir = $input->getOption('dir') ?? $config->get('archive-dir');
$returnCode = $this->archive(
$this->getIO(),
$config,
$input->getArgument('package'),
$input->getArgument('version'),
$input->getOption('format'),
$input->getOption('dir'),
$format,
$dir,
$input->getOption('file'),
$input->getOption('ignore-filters'),
$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
*/
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) {
$archiveManager = $composer->getArchiveManager();
@ -142,7 +130,7 @@ EOT
return 1;
}
} else {
$package = $this->getComposer()->getPackage();
$package = $this->requireComposer()->getPackage();
}
$io->writeError('<info>Creating the archive into "'.$dest.'".</info>');
@ -166,7 +154,7 @@ EOT
{
$io->writeError('<info>Searching for the specified package.</info>');
if ($composer = $this->getComposer(false)) {
if ($composer = $this->tryComposer()) {
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
$repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories()));
} else {

@ -16,11 +16,15 @@ use Composer\Composer;
use Composer\Config;
use Composer\Console\Application;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Pcre\Preg;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents;
use Composer\Repository\PlatformRepository;
use Composer\Util\Platform;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
@ -32,8 +36,6 @@ use Symfony\Component\Console\Terminal;
/**
* Base class for Composer commands
*
* @method Application getApplication()
*
* @author Ryan Weaver <ryan@knplabs.com>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
@ -50,21 +52,52 @@ abstract class BaseCommand extends Command
private $io;
/**
* @param bool $required
* @param bool|null $disablePlugins
* @param bool|null $disableScripts
* Gets the application instance for this command.
*/
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
* @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)
{
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) {
$application = $this->getApplication();
$application = parent::getApplication();
if ($application instanceof Application) {
/* @var $application Application */
$this->composer = $application->getComposer($required, $disablePlugins, $disableScripts);
/** @phpstan-ignore-next-line */
} elseif ($required) {
$this->composer = $application->getComposer(true, $disablePlugins, $disableScripts);
assert($this->composer instanceof Composer);
} else {
throw new \RuntimeException(
'Could not create a Composer\Composer instance, you must inject '.
'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;
}
/**
* 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
*/
@ -112,10 +165,9 @@ abstract class BaseCommand extends Command
public function getIO()
{
if (null === $this->io) {
$application = $this->getApplication();
$application = parent::getApplication();
if ($application instanceof Application) {
$this->io = $application->getIO();
/** @phpstan-ignore-next-line */
} else {
$this->io = new NullIO();
}
@ -147,7 +199,7 @@ abstract class BaseCommand extends Command
$disableScripts = true;
}
$composer = $this->getComposer(false, $disablePlugins, $disableScripts);
$composer = $this->tryComposer($disablePlugins, $disableScripts);
if (null === $composer) {
$composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts);
}
@ -194,7 +246,11 @@ abstract class BaseCommand extends Command
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')) {
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'))) {
$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);
}
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>
*/

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

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

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

@ -208,10 +208,9 @@ EOT
}
/**
* @return int
* @throws \Seld\JsonLint\ParsingException
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// Open file in editor
if (true === $input->getOption('editor')) {
@ -242,7 +241,7 @@ EOT
// List the configuration of the file settings
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;
}

@ -122,10 +122,7 @@ EOT
;
}
/**
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$config = Factory::createConfig();
$io = $this->getIO();
@ -141,12 +138,14 @@ EOT
}
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>]: '));
}
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
return $this->installProject(
$io,
$config,
@ -163,7 +162,7 @@ EOT
$input->getOption('no-scripts'),
$input->getOption('no-progress'),
$input->getOption('no-install'),
PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs),
$this->getPlatformRequirementFilter($input),
!$input->getOption('no-secure-http'),
$input->getOption('add-repository')
);
@ -173,7 +172,7 @@ EOT
* @param string|null $packageName
* @param string|null $directory
* @param string|null $packageVersion
* @param string $stability
* @param string|null $stability
* @param bool $preferSource
* @param bool $preferDist
* @param bool $installDevPackages

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

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

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

@ -49,14 +49,11 @@ EOT
;
}
/**
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$composer = $this->getComposer();
$composer = $this->requireComposer();
$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 = array_merge($bins, array_map(function ($e) {
return "$e (local)";

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

@ -57,19 +57,16 @@ EOT
);
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$repos = $this->initializeRepos();
$io = $this->getIO();
$return = 0;
$packages = $input->getArgument('packages');
if (!$packages) {
if (count($packages) === 0) {
$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) {
@ -163,7 +160,7 @@ EOT
*/
private function initializeRepos()
{
$composer = $this->getComposer(false);
$composer = $this->tryComposer();
if ($composer) {
return array_merge(

@ -46,15 +46,17 @@ use Symfony\Component\Console\Helper\FormatterHelper;
*/
class InitCommand extends BaseCommand
{
use PackageDiscoveryTrait;
// Properties for PackageDiscoveryTrait
/** @var ?CompositeRepository */
protected $repos;
/** @var RepositorySet[] */
private $repositorySets;
/** @var array<string, string> */
private $gitConfig;
/** @var RepositorySet[] */
private $repositorySets;
/**
* @inheritDoc
*
@ -93,9 +95,6 @@ EOT
}
/**
* @inheritDoc
*
* @return int
* @throws \Seld\JsonLint\ParsingException
*/
protected function execute(InputInterface $input, OutputInterface $output)
@ -117,7 +116,7 @@ EOT
}
$repositories = $input->getOption('repository');
if ($repositories) {
if (count($repositories) > 0) {
$config = Factory::createConfig($io);
foreach ($repositories as $repo) {
$options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true);
@ -145,7 +144,7 @@ EOT
$autoloadPath = null;
if (isset($options['autoload'])) {
$autoloadPath = $options['autoload'];
$namespace = $this->namespaceFromPackageName($input->getOption('name'));
$namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
$options['autoload'] = (object) array(
'psr-4' => array(
$namespace . '\\' => $autoloadPath,
@ -217,7 +216,7 @@ EOT
// --autoload - Show post-install configuration info
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('Include the Composer autoloader with: <comment>require \'vendor/autoload.php\';</comment>');
@ -240,7 +239,7 @@ EOT
// initialize repos if configured
$repositories = $input->getOption('repository');
if ($repositories) {
if (count($repositories) > 0) {
$config = Factory::createConfig($io);
$repos = array(new PlatformRepository);
$createDefaultPackagistRepo = true;
@ -282,7 +281,8 @@ EOT
$cwd = realpath(".");
if (!$name = $input->getOption('name')) {
$name = $input->getOption('name');
if (null === $name) {
$name = basename($cwd);
$name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
$name = strtolower($name);
@ -323,7 +323,7 @@ EOT
);
$input->setOption('name', $name);
$description = $input->getOption('description') ?: false;
$description = $input->getOption('description') ?: null;
$description = $io->ask(
'Description [<comment>'.$description.'</comment>]: ',
$description
@ -423,7 +423,7 @@ EOT
$question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
$require = $input->getOption('require');
$requirements = array();
if ($require || $io->askConfirmation($question)) {
if (count($require) > 0 || $io->askConfirmation($question)) {
$requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability);
}
$input->setOption('require', $requirements);
@ -431,14 +431,14 @@ EOT
$question = 'Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? ';
$requireDev = $input->getOption('require-dev');
$devRequirements = array();
if ($requireDev || $io->askConfirmation($question)) {
if (count($requireDev) > 0 || $io->askConfirmation($question)) {
$devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability);
}
$input->setOption('require-dev', $devRequirements);
// --autoload - input and validation
$autoload = $input->getOption('autoload') ?: 'src/';
$namespace = $this->namespaceFromPackageName($input->getOption('name'));
$namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
$autoload = $io->askAndValidate(
'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. [<comment>'.$autoload.'</comment>, n to skip]: ',
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
*
@ -815,229 +624,6 @@ EOT
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
*/

@ -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>');
}
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>');
return 1;
@ -96,7 +97,7 @@ EOT
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()) {
$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');
$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'));
$install
@ -129,7 +128,7 @@ EOT
->setOptimizeAutoloader($optimize)
->setClassMapAuthoritative($authoritative)
->setApcuAutoloader($apcu, $apcuPrefix)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
;
if ($input->getOption('no-plugins')) {

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

@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class OutdatedCommand extends ShowCommand
class OutdatedCommand extends BaseCommand
{
/**
* @return void
@ -63,7 +63,7 @@ EOT
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$args = array(
'command' => 'show',
@ -75,7 +75,7 @@ EOT
if ($input->getOption('direct')) {
$args['--direct'] = true;
}
if ($input->getArgument('package')) {
if (null !== $input->getArgument('package')) {
$args['package'] = $input->getArgument('package');
}
if ($input->getOption('strict')) {
@ -90,9 +90,7 @@ EOT
if ($input->getOption('no-dev')) {
$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')) {
$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
;
}
/**
* Execute the function.
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
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();
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts'));
$composer = $this->requireComposer();
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
$packagesToReinstall = array();
@ -139,8 +139,6 @@ EOT
$downloadManager = $composer->getDownloadManager();
$package = $composer->getPackage();
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$installationManager->setOutputProgress(!$input->getOption('no-progress'));
if ($input->getOption('no-plugins')) {
$installationManager->disablePlugins();
@ -166,7 +164,7 @@ EOT
$generator = $composer->getAutoloadGenerator();
$generator->setClassMapAuthoritative($authoritative);
$generator->setApcu($apcu, $apcuPrefix);
$generator->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
$generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
$generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
}

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

@ -14,6 +14,7 @@ namespace Composer\Command;
use Composer\DependencyResolver\Request;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Repository\RepositorySet;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
@ -38,8 +39,16 @@ use Composer\Util\Silencer;
* @author Jérémy Romey <jeremy@free-agent.fr>
* @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 */
private $newlyCreated;
/** @var bool */
@ -110,7 +119,6 @@ EOT
}
/**
* @return int
* @throws \Seld\JsonLint\ParsingException
*/
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();
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
// initialize $this->repos as it is used by the parent InitCommand
$platformOverrides = $composer->getConfig()->get('platform');
// initialize $this->repos as it is used by the PackageDiscoveryTrait
$this->repos = new CompositeRepository(array_merge(
array($platformRepo = new PlatformRepository(array(), $platformOverrides)),
$repos
@ -256,7 +264,7 @@ EOT
$this->firstRequire = $this->newlyCreated;
if (!$this->firstRequire) {
$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;
}
}
@ -355,7 +363,7 @@ EOT
{
// Update packages
$this->resetComposer();
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts'));
$composer = $this->requireComposer();
$this->dependencyResolutionCompleted = false;
$composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, array($this, 'markSolverComplete'), 10000);
@ -401,7 +409,6 @@ EOT
$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);
$install
@ -416,7 +423,7 @@ EOT
->setUpdate(true)
->setInstall(!$input->getOption('no-install'))
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
;
@ -472,7 +479,7 @@ EOT
return true;
}
protected function interact(InputInterface $input, OutputInterface $output)
protected function interact(InputInterface $input, OutputInterface $output): void
{
return;
}

@ -54,7 +54,7 @@ class RunScriptCommand extends BaseCommand
->setAliases(array('run'))
->setDescription('Runs the scripts defined in composer.json.')
->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 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.'),
@ -73,14 +73,11 @@ EOT
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
if ($input->getOption('list')) {
return $this->listScripts($output);
}
if (!$input->getArgument('script')) {
throw new \RuntimeException('Missing required argument "script"');
}
$script = $input->getArgument('script');
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');
$event = new ScriptEvent($script, $composer, $this->getIO(), $devMode);
$hasListeners = $composer->getEventDispatcher()->hasEventListeners($event);
@ -117,7 +114,7 @@ EOT
*/
protected function listScripts(OutputInterface $output)
{
$scripts = $this->getComposer()->getPackage()->getScripts();
$scripts = $this->requireComposer()->getPackage()->getScripts();
if (!count($scripts)) {
return 0;

@ -64,12 +64,9 @@ EOT
;
}
/**
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$composer = $this->getComposer();
$composer = $this->requireComposer();
$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
$platformRepo = new PlatformRepository;
@ -69,7 +69,7 @@ EOT
return 1;
}
if (!($composer = $this->getComposer(false))) {
if (!($composer = $this->tryComposer())) {
$composer = Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins'));
}
$localRepo = $composer->getRepositoryManager()->getLocalRepository();

@ -75,10 +75,9 @@ EOT
}
/**
* @return int
* @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
// 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\DependencyResolver\DefaultPolicy;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
@ -114,7 +115,7 @@ EOT
$this->initStyles($output);
}
$composer = $this->getComposer(false);
$composer = $this->tryComposer();
$io = $this->getIO();
if ($input->getOption('installed')) {
@ -123,7 +124,7 @@ EOT
if ($input->getOption('outdated')) {
$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>');
}
@ -158,7 +159,7 @@ EOT
return 1;
}
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$platformReqFilter = $this->getPlatformRequirementFilter($input);
// init repos
$platformOverrides = array();
@ -169,12 +170,15 @@ EOT
$lockedRepo = null;
if ($input->getOption('self')) {
$package = $this->getComposer()->getPackage();
$package = $this->requireComposer()->getPackage();
if ($input->getOption('name-only')) {
$io->write($package->getName());
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)));
} elseif ($input->getOption('platform')) {
$repos = $installedRepo = new InstalledRepository(array($platformRepo));
@ -213,7 +217,7 @@ EOT
} else {
// --installed / default case
if (!$composer) {
$composer = $this->getComposer();
$composer = $this->requireComposer();
}
$rootPkg = $composer->getPackage();
$repos = $installedRepo = new InstalledRepository(array($composer->getRepositoryManager()->getLocalRepository()));
@ -243,31 +247,33 @@ EOT
$packageFilter = $input->getArgument('package');
// show single package or single version
if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) {
if (empty($package)) {
list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));
if (empty($package)) {
$options = $input->getOptions();
$hint = '';
if ($input->getOption('locked')) {
$hint .= ' in lock file';
}
if (isset($options['working-dir'])) {
$hint .= ' in ' . $options['working-dir'] . '/composer.json';
}
if (PlatformRepository::isPlatformPackage($input->getArgument('package')) && !$input->getOption('platform')) {
$hint .= ', try using --platform (-p) to show platform packages';
}
if (!$input->getOption('all')) {
$hint .= ', try using --all (-a) to show all available packages';
}
throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.');
if (isset($package)) {
$versions = array($package->getPrettyVersion() => $package->getVersion());
} elseif (null !== $packageFilter && str_contains($packageFilter, '*')) {
list($package, $versions) = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version'));
if (!isset($package)) {
$options = $input->getOptions();
$hint = '';
if ($input->getOption('locked')) {
$hint .= ' in lock file';
}
} else {
$versions = array($package->getPrettyVersion() => $package->getVersion());
if (isset($options['working-dir'])) {
$hint .= ' in ' . $options['working-dir'] . '/composer.json';
}
if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
$hint .= ', try using --platform (-p) to show platform packages';
}
if (!$input->getOption('all')) {
$hint .= ', try using --all (-a) to show all available packages';
}
throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.');
}
}
if (isset($package)) {
assert(isset($versions));
$exitCode = 0;
if ($input->getOption('tree')) {
@ -278,32 +284,34 @@ EOT
} else {
$this->displayPackageTree(array($arrayTree));
}
} else {
$latestPackage = null;
if ($input->getOption('latest')) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('minor-only'), $ignorePlatformReqs);
}
if (
$input->getOption('outdated')
&& $input->getOption('strict')
&& $latestPackage
&& $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion()
&& (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned())
) {
$exitCode = 1;
}
if ($input->getOption('path')) {
$io->write($package->getName(), false);
$io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"));
return $exitCode;
}
return $exitCode;
}
if ('json' === $format) {
$this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null);
} else {
$this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null);
}
$latestPackage = null;
if ($input->getOption('latest')) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('minor-only'), $platformReqFilter);
}
if (
$input->getOption('outdated')
&& $input->getOption('strict')
&& $latestPackage
&& $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion()
&& (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned())
) {
$exitCode = 1;
}
if ($input->getOption('path')) {
$io->write($package->getName(), false);
$io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"));
return $exitCode;
}
if ('json' === $format) {
$this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null);
} else {
$this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null);
}
return $exitCode;
@ -407,7 +415,7 @@ EOT
if ($showLatest && $showVersion) {
foreach ($packages[$type] as $package) {
if (is_object($package)) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMinorOnly, $ignorePlatformReqs);
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMinorOnly, $platformReqFilter);
if ($latestPackage === false) {
continue;
}
@ -597,7 +605,7 @@ EOT
*/
protected function getRootRequires()
{
$rootPackage = $this->getComposer()->getPackage();
$rootPackage = $this->requireComposer()->getPackage();
return array_map(
'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>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
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()));
@ -865,7 +873,7 @@ EOT
$latestPackage = $package;
}
if ($package->getSourceType()) {
if (null !== $package->getSourceType()) {
$json['source'] = array(
'type' => $package->getSourceType(),
'url' => $package->getSourceUrl(),
@ -873,7 +881,7 @@ EOT
);
}
if ($package->getDistType()) {
if (null !== $package->getDistType()) {
$json['dist'] = array(
'type' => $package->getDistType(),
'url' => $package->getDistUrl(),
@ -882,7 +890,7 @@ EOT
}
if ($installedRepo->hasPackage($package)) {
$json['path'] = realpath($this->getComposer()->getInstallationManager()->getInstallPath($package));
$json['path'] = realpath($this->requireComposer()->getInstallationManager()->getInstallPath($package));
if ($json['path'] === false) {
unset($json['path']);
}
@ -1281,12 +1289,9 @@ EOT
/**
* Given a package, this finds the latest package matching it
*
* @param bool $minorOnly
* @param bool|string $ignorePlatformReqs
*
* @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
$name = $package->getName();
@ -1311,7 +1316,7 @@ EOT
$targetVersion = '^' . $package->getVersion();
}
$candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
$candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter);
while ($candidate instanceof AliasPackage) {
$candidate = $candidate->getAliasOf();
}

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

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

@ -109,10 +109,6 @@ EOT
;
}
/**
* @return int
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$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>');
}
$composer = $this->getComposer(true, $input->getOption('no-plugins'), $input->getOption('no-scripts'));
$composer = $this->requireComposer();
if (!HttpDownloader::isCurlEnabled()) {
$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'));
// extract --with shorthands from the allowlist
if ($packages) {
if (count($packages) > 0) {
$allowlistPackagesWithRequirements = array_filter($packages, function ($pkg) {
return Preg::isMatch('{\S+[ =:]\S+}', $pkg);
});
@ -219,8 +215,6 @@ EOT
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
}
$ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
$install
->setDryRun($input->getOption('dry-run'))
->setVerbose($input->getOption('verbose'))
@ -236,7 +230,7 @@ EOT
->setUpdateMirrors($updateMirrors)
->setUpdateAllowList($packages)
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
;

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

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

@ -62,7 +62,7 @@ class JsonConfigSource implements ConfigSourceInterface
*/
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
// to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation
if (isset($config['repositories'])) {
@ -83,7 +83,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else {
$config['repositories'] = array($repo => $repoConfig) + $config['repositories'];
}
});
}, $name, $config, $append);
}
/**
@ -91,9 +91,9 @@ class JsonConfigSource implements ConfigSourceInterface
*/
public function removeRepository($name)
{
$this->manipulateJson('removeRepository', $name, function (&$config, $repo) {
$this->manipulateJson('removeRepository', function (&$config, $repo) {
unset($config['repositories'][$repo]);
});
}, $name);
}
/**
@ -102,7 +102,7 @@ class JsonConfigSource implements ConfigSourceInterface
public function addConfigSetting($name, $value)
{
$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)) {
list($key, $host) = explode('.', $key, 2);
if ($authConfig) {
@ -113,7 +113,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else {
$config['config'][$key] = $val;
}
});
}, $name, $value);
}
/**
@ -122,7 +122,7 @@ class JsonConfigSource implements ConfigSourceInterface
public function removeConfigSetting($name)
{
$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)) {
list($key, $host) = explode('.', $key, 2);
if ($authConfig) {
@ -133,7 +133,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else {
unset($config['config'][$key]);
}
});
}, $name);
}
/**
@ -141,7 +141,7 @@ class JsonConfigSource implements ConfigSourceInterface
*/
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) {
$bits = explode('.', $key);
$last = array_pop($bits);
@ -156,7 +156,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else {
$config[$key] = $val;
}
});
}, $name, $value);
}
/**
@ -164,7 +164,7 @@ class JsonConfigSource implements ConfigSourceInterface
*/
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) {
$bits = explode('.', $key);
$last = array_pop($bits);
@ -179,7 +179,7 @@ class JsonConfigSource implements ConfigSourceInterface
} else {
unset($config[$key]);
}
});
}, $name);
}
/**
@ -187,9 +187,9 @@ class JsonConfigSource implements ConfigSourceInterface
*/
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;
});
}, $type, $name, $value);
}
/**
@ -197,30 +197,25 @@ class JsonConfigSource implements ConfigSourceInterface
*/
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]);
});
$this->manipulateJson('removeMainKeyIfEmpty', $type, function (&$config, $type) {
}, $type, $name);
$this->manipulateJson('removeMainKeyIfEmpty', function (&$config, $type) {
if (0 === count($config[$type])) {
unset($config[$type]);
}
});
}, $type);
}
/**
* @param string $method
* @param mixed ...$args
* @param callable $fallback
* @param mixed ...$args
*
* @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 (!is_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.
*
* 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);
* // ...

@ -135,10 +135,10 @@ interface IOInterface extends LoggerInterface
* Asks a question to the user.
*
* @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
* @return string|null The user answer
* @return mixed The user answer
*/
public function ask($question, $default = null);

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

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

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

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

@ -80,10 +80,10 @@ class RunScriptCommandTest extends TestCase
'mergeApplicationDefinition',
'getSynopsis',
'initialize',
'getComposer',
'requireComposer',
))
->getMock();
$command->expects($this->any())->method('getComposer')->willReturn($composer);
$command->expects($this->any())->method('requireComposer')->willReturn($composer);
$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)));
}
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);
$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

@ -55,15 +55,30 @@ class ProcessExecutorMock extends ProcessExecutor
$default = array('cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null);
$this->expectations = array_map(function ($expect) use ($default) {
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) {
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);
$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

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

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