From c0b8086af52c6d845411ea9922011824a961b75c Mon Sep 17 00:00:00 2001 From: Michael Newton Date: Fri, 1 Mar 2024 02:14:41 -0700 Subject: [PATCH 001/257] Include PHP information when showing Composer version verbosely (#11866) Co-authored-by: Jordi Boggiano --- src/Composer/Console/Application.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 709ce6ef8a13..bf23b2d79fdd 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -383,6 +383,11 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow $result = parent::doRun($input, $output); + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $io->writeError(sprintf('PHP version %s (%s)', \PHP_VERSION, \PHP_BINARY)); + $io->writeError('Run the "diagnose" command to get more detailed diagnostics output.'); + } + // chdir back to $oldWorkingDir if set if (isset($oldWorkingDir) && '' !== $oldWorkingDir) { Silencer::call('chdir', $oldWorkingDir); From c3efff91f85bf23acdb73cd4296d28f3a1a06370 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 4 Mar 2024 13:44:43 +0100 Subject: [PATCH 002/257] Fix plugins still being available in a few special contexts when running as non-interactive root, mainly create-project, refs #11854 --- src/Composer/Command/BaseCommand.php | 23 +++++++++++++++++++ src/Composer/Command/CreateProjectCommand.php | 10 ++++---- src/Composer/Command/SearchCommand.php | 2 +- src/Composer/Command/ValidateCommand.php | 2 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index b6b1439de5af..bdfbb0d05996 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -289,6 +289,29 @@ protected function initialize(InputInterface $input, OutputInterface $output) parent::initialize($input, $output); } + /** + * Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins + * + * @param mixed $config either a configuration array or a filename to read from, if null it will read from + * the default filename + * @return Composer + */ + protected function createComposerInstance(InputInterface $input, IOInterface $io, $config = null, ?bool $disablePlugins = null, ?bool $disableScripts = null): Composer + { + $disablePlugins = $disablePlugins === true || $input->hasParameterOption('--no-plugins'); + $disableScripts = $disableScripts === true || $input->hasParameterOption('--no-scripts'); + + $application = parent::getApplication(); + if ($application instanceof Application && $application->getDisablePluginsByDefault()) { + $disablePlugins = true; + } + if ($application instanceof Application && $application->getDisableScriptsByDefault()) { + $disableScripts = true; + } + + return Factory::create($io, $config, $disablePlugins, $disableScripts); + } + /** * Returns preferSource and preferDist values based on the configuration. * diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index f0bb7dbb5a24..b7e873684b5b 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -193,7 +193,7 @@ public function installProject(IOInterface $io, Config $config, InputInterface $ $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); if ($packageName !== null) { - $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp); + $installedFromVcs = $this->installRootPackage($input, $io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp); } else { $installedFromVcs = false; } @@ -202,7 +202,7 @@ public function installProject(IOInterface $io, Config $config, InputInterface $ unlink('composer.lock'); } - $composer = Factory::create($io, null, $disablePlugins, $disableScripts); + $composer = $this->createComposerInstance($input, $io, null, $disablePlugins, $disableScripts); // add the repository to the composer.json and use it for the install run later if ($repositories !== null && $addRepository) { @@ -221,7 +221,7 @@ public function installProject(IOInterface $io, Config $config, InputInterface $ $configSource->addRepository($name, $repoConfig, false); } - $composer = Factory::create($io, null, $disablePlugins); + $composer = $this->createComposerInstance($input, $io, null, $disablePlugins); } } @@ -336,7 +336,7 @@ public function installProject(IOInterface $io, Config $config, InputInterface $ * * @throws \Exception */ - protected function installRootPackage(IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool + protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool { if (!$secureHttp) { $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); @@ -388,7 +388,7 @@ protected function installRootPackage(IOInterface $io, Config $config, string $p throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); } - $composer = Factory::create($io, $config->all(), $disablePlugins, $disableScripts); + $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts); $config = $composer->getConfig(); $rm = $composer->getRepositoryManager(); diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 61f0f95eb5c5..d95c94168127 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -67,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (!($composer = $this->tryComposer())) { - $composer = Factory::create($this->getIO(), [], $input->hasParameterOption('--no-plugins')); + $composer = $this->createComposerInstance($input, $this->getIO(), []); } $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository([$localRepo, $platformRepo]); diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index a5d1e9c46092..529401e98293 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -91,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); $lockErrors = []; - $composer = Factory::create($io, $file, $input->hasParameterOption('--no-plugins')); + $composer = $this->createComposerInstance($input, $io, $file); // config.lock = false ~= implicit --no-check-lock; --check-lock overrides $checkLock = ($checkLock && $composer->getConfig()->get('lock')) || $input->getOption('check-lock'); $locker = $composer->getLocker(); From 133447cf51e5c21f77381c575575384bb6de031a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 4 Mar 2024 14:01:23 +0100 Subject: [PATCH 003/257] Output tweak --- src/Composer/Console/Application.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index bf23b2d79fdd..fa7855331bf6 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -293,7 +293,8 @@ public function doRun(InputInterface $input, OutputInterface $output): int } if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) { - $io->writeError('Composer plugins have been disabled for safety in this non-interactive session. Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); + $io->writeError('Composer plugins have been disabled for safety in this non-interactive session.'); + $io->writeError('Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); $this->disablePluginsByDefault = true; } From c42bb68affcce43c453076752abe32293bd18244 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 4 Mar 2024 14:07:27 +0100 Subject: [PATCH 004/257] Optimize outdated --ignore to avoid fetching the latest package info for ignored packages, fixes #11863 --- src/Composer/Command/ShowCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 542482554b6b..92a6e621b3b8 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -478,7 +478,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($showLatest && $showVersion) { foreach ($packages[$type] as $package) { - if (is_object($package)) { + if (is_object($package) && !Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName())) { $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter); if ($latestPackage === null) { continue; From 1dc2c93261e7606d13e905d92deebb2c9cec7d7b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 4 Mar 2024 14:39:30 +0100 Subject: [PATCH 005/257] Fix ensureDirectoryExists not working when a broken symlink appears somewhere in the path, fixes #11864 --- src/Composer/Util/Filesystem.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 7ddcfa69f077..baa3dfa8dd89 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -257,7 +257,21 @@ public function ensureDirectoryExists(string $directory) } if (!@mkdir($directory, 0777, true)) { - throw new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? '')); + $e = new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? '')); + + // in pathological cases with paths like path/to/broken-symlink/../foo is_dir will fail to detect path/to/foo + // but normalizing the ../ away first makes it work so we attempt this just in case, and if it still fails we + // report the initial error we had with the original path, and ignore the normalized path exception + // see https://github.com/composer/composer/issues/11864 + $normalized = $this->normalizePath($directory); + if ($normalized !== $directory) { + try { + $this->ensureDirectoryExists($normalized); + return; + } catch (\Throwable $ignoredEx) {} + } + + throw $e; } } } From 66acb84c12d1d2b3cce7b2967f8e25522f502e60 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 5 Mar 2024 11:32:40 +0100 Subject: [PATCH 006/257] Fix update --lock to avoid updating all metadata except dist/source urls and mirrors (#11850) We now update the existing package instead of reverting changes in the updated package to ensure we keep all metadata intact, fixes #11787 Co-authored-by: Jordi Boggiano --- .../DependencyResolver/LockTransaction.php | 83 +++++++++++++------ .../installer/update-mirrors-changes-url.test | 12 ++- 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index 70603b867a26..d77a211396b8 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -104,36 +104,71 @@ public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): { $packages = []; foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) { - if (!$package instanceof AliasPackage) { - // if we're just updating mirrors we need to reset references to the same as currently "present" packages' references to keep the lock file as-is - // we do not reset references if the currently present package didn't have any, or if the type of VCS has changed - if ($updateMirrors && !isset($this->presentMap[spl_object_hash($package)])) { - foreach ($this->presentMap as $presentPackage) { - if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) { - if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) { - $package->setSourceDistReferences($presentPackage->getSourceReference()); - // if the dist url is not one of those handled gracefully by setSourceDistReferences then we should overwrite it with the old one - if ($package->getDistUrl() !== null && !Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) { - $package->setDistUrl($presentPackage->getDistUrl()); - } - $package->setDistType($presentPackage->getDistType()); - if ($package instanceof Package) { - $package->setDistSha1Checksum($presentPackage->getDistSha1Checksum()); - } - } - if ($presentPackage->getReleaseDate() !== null && $package instanceof Package) { - $package->setReleaseDate($presentPackage->getReleaseDate()); - } - } - } - } - $packages[] = $package; + if ($package instanceof AliasPackage) { + continue; } + + // if we're just updating mirrors we need to reset everything to the same as currently "present" packages' references to keep the lock file as-is + if ($updateMirrors === true && !array_key_exists(spl_object_hash($package), $this->presentMap)) { + $package = $this->updateMirrorAndUrls($package); + } + + $packages[] = $package; } return $packages; } + /** + * Try to return the original package from presentMap with updated URLs/mirrors + * + * If the type of source/dist changed, then we do not update those and keep them as they were + */ + private function updateMirrorAndUrls(BasePackage $package): BasePackage + { + foreach ($this->presentMap as $presentPackage) { + if ($package->getName() !== $presentPackage->getName()) { + continue; + } + + if ($package->getVersion() !== $presentPackage->getVersion()) { + continue; + } + + if ($presentPackage->getSourceReference() === null) { + continue; + } + + if ($presentPackage->getSourceType() !== $package->getSourceType()) { + continue; + } + + if ($presentPackage instanceof Package) { + $presentPackage->setSourceUrl($package->getSourceUrl()); + $presentPackage->setSourceMirrors($package->getSourceMirrors()); + } + + // if the dist type changed, we only update the source url/mirrors + if ($presentPackage->getDistType() !== $package->getDistType()) { + return $presentPackage; + } + + // update dist url if it is in a known format + if ( + $package->getDistUrl() !== null + && $presentPackage->getDistReference() !== null + && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl()) + ) { + $presentPackage->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $presentPackage->getDistReference(), $package->getDistUrl())); + } + $presentPackage->setDistMirrors($package->getDistMirrors()); + + return $presentPackage; + } + + return $package; + } + /** * Checks which of the given aliases from composer.json are actually in use for the lock file * @param list $aliases diff --git a/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test index edb821f8c077..d002d2c94b1d 100644 --- a/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test @@ -16,8 +16,9 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "package": [ { "name": "a/a", "version": "dev-master", - "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" }, + "require": { "b/b": "^2.0.1" }, + "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git", "mirrors": [{"url": "https://example.org/src/%package%/%version%/r%reference%.%type%", "preferred": true}] }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip", "mirrors": [{"url": "https://example.org/dists/%package%/%version%/r%reference%.%type%", "preferred": true}] }, "time": "2021-03-27T14:32:16+00:00" }, { @@ -67,6 +68,7 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an [ { "name": "a/a", "version": "dev-master", + "require": { "b/b": "^2" }, "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" }, "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "time": "2021-03-14T16:24:37+00:00" @@ -102,6 +104,7 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "packages": [ { "name": "a/a", "version": "dev-master", + "require": { "b/b": "^2" }, "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" }, "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "time": "2021-03-14T16:24:37+00:00", @@ -152,8 +155,9 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "packages": [ { "name": "a/a", "version": "dev-master", - "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/newa", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/newa/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "require": { "b/b": "^2" }, + "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/newa", "type": "git", "mirrors": [{"url": "https://example.org/src/%package%/%version%/r%reference%.%type%", "preferred": true}] }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/newa/zipball/1111111111111111111111111111111111111111", "type": "zip", "mirrors": [{"url": "https://example.org/dists/%package%/%version%/r%reference%.%type%", "preferred": true}] }, "time": "2021-03-14T16:24:37+00:00", "type": "library" }, From c5aa3dc02127757fc042c03a19481161deeceb70 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 8 Mar 2024 08:51:56 +0100 Subject: [PATCH 007/257] Update deps, update baseline (1677, 97), fixes #11875 --- phpstan/baseline.neon | 27 +++++++++------------ src/Composer/Command/ValidateCommand.php | 4 +-- src/Composer/Package/Loader/ArrayLoader.php | 4 +-- src/Composer/Platform/Version.php | 4 ++- src/Composer/Util/Git.php | 5 ++-- src/Composer/Util/Http/ProxyHelper.php | 6 +++-- src/Composer/Util/Http/ProxyManager.php | 1 + src/Composer/Util/RemoteFilesystem.php | 2 ++ 8 files changed, 28 insertions(+), 25 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index b17166a49727..45dcac955b24 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -1656,7 +1656,7 @@ parameters: path: ../src/Composer/Downloader/FileDownloader.php - - message: "#^Strict comparison using \\=\\=\\= between null and Composer\\\\Util\\\\Http\\\\Response will always evaluate to false\\.$#" + message: "#^Strict comparison using \\=\\=\\= between null and Composer\\\\Util\\\\Http\\\\Response\\|string will always evaluate to false\\.$#" count: 1 path: ../src/Composer/Downloader/FileDownloader.php @@ -4228,11 +4228,6 @@ parameters: count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - - message: "#^Parameter \\#1 \\$mh of function curl_multi_setopt expects resource, resource\\|false given\\.$#" - count: 2 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$str of function preg_quote expects string, string\\|false\\|null given\\.$#" count: 1 @@ -4313,11 +4308,6 @@ parameters: count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - - message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$multiHandle \\(resource\\|null\\) does not accept resource\\|false\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 @@ -4483,6 +4473,11 @@ parameters: count: 1 path: ../src/Composer/Util/NoProxyPattern.php + - + message: "#^Method Composer\\\\Util\\\\NoProxyPattern\\:\\:ipCheckData\\(\\) never assigns null to &\\$ipdata so it can be removed from the by\\-ref type\\.$#" + count: 1 + path: ../src/Composer/Util/NoProxyPattern.php + - message: "#^Only booleans are allowed in a negated boolean, bool\\|stdClass given\\.$#" count: 1 @@ -4773,6 +4768,11 @@ parameters: count: 1 path: ../src/Composer/Util/RemoteFilesystem.php + - + message: "#^Parameter &\\$responseHeaders @param\\-out type of method Composer\\\\Util\\\\RemoteFilesystem\\:\\:getRemoteContents\\(\\) expects list\\, array\\ given\\.$#" + count: 1 + path: ../src/Composer/Util/RemoteFilesystem.php + - message: "#^Property Composer\\\\Util\\\\RemoteFilesystem\\:\\:\\$scheme \\(string\\) does not accept string\\|false\\|null\\.$#" count: 1 @@ -5321,11 +5321,6 @@ parameters: count: 1 path: ../tests/Composer/Test/Question/StrictConfirmationQuestionTest.php - - - message: "#^Cannot call method getDistType\\(\\) on Composer\\\\Package\\\\BasePackage\\|null\\.$#" - count: 1 - path: ../tests/Composer/Test/Repository/ArtifactRepositoryTest.php - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|null given\\.$#" count: 2 diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 529401e98293..d9d8c7510238 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -106,7 +106,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); // $errors include publish and lock errors when exists - $exitCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0); + $exitCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); if ($input->getOption('with-dependencies')) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); @@ -122,7 +122,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); // $errors include publish errors when exists - $depCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0); + $depCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); $exitCode = max($depCode, $exitCode); } } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 44b778f299f2..daa540bd80ed 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -313,8 +313,8 @@ private function configureObject(PackageInterface $package, array $config): Base } /** - * @param array>>> $linkCache - * @param mixed[] $config + * @param array>>> $linkCache + * @param mixed[] $config */ private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config): void { diff --git a/src/Composer/Platform/Version.php b/src/Composer/Platform/Version.php index 8a3f23e4ade6..56abb1bb1cc9 100644 --- a/src/Composer/Platform/Version.php +++ b/src/Composer/Platform/Version.php @@ -20,7 +20,9 @@ class Version { /** - * @param bool $isFips Set by the method + * @param bool $isFips Set by the method + * + * @param-out bool $isFips */ public static function parseOpenssl(string $opensslVersion, ?bool &$isFips): ?string { diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index f8e503d82855..67af22e8d3d0 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -357,11 +357,12 @@ private function checkRefIsInMirror(string $dir, string $ref): bool } /** - * @param string[] $match + * @param array $match + * @param-out array $match */ private function isAuthenticationFailure(string $url, array &$match): bool { - if (!Preg::isMatch('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { + if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { return false; } diff --git a/src/Composer/Util/Http/ProxyHelper.php b/src/Composer/Util/Http/ProxyHelper.php index 58056e42970c..f2d864f29ded 100644 --- a/src/Composer/Util/Http/ProxyHelper.php +++ b/src/Composer/Util/Http/ProxyHelper.php @@ -104,10 +104,12 @@ public static function setRequestFullUri(string $requestUrl, array &$options): v /** * Searches $_SERVER for case-sensitive values * - * @param string[] $names Names to search for - * @param string|null $name Name of any found value + * @param non-empty-list $names Names to search for + * @param string|null $name Name of any found value, you should only rely on it if the function returned a non-null value * * @return string|null The found value + * + * @param-out string $name */ private static function getProxyEnv(array $names, ?string &$name): ?string { diff --git a/src/Composer/Util/Http/ProxyManager.php b/src/Composer/Util/Http/ProxyManager.php index 5af53ad0c483..731638ab80b1 100644 --- a/src/Composer/Util/Http/ProxyManager.php +++ b/src/Composer/Util/Http/ProxyManager.php @@ -94,6 +94,7 @@ public function getProxyForRequest(string $requestUrl): RequestProxy } else { $proxyUrl = $this->fullProxy[$scheme]; $options = $this->streams[$scheme]['options']; + assert(is_array($options)); ProxyHelper::setRequestFullUri($requestUrl, $options); $formattedProxyUrl = $this->safeProxy[$scheme]; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 8abf015a7eb1..0cc9df772692 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -509,6 +509,8 @@ protected function get(string $originUrl, string $fileUrl, array $additionalOpti * @param int $maxFileSize The maximum allowed file size * * @return string|false The response contents or false on failure + * + * @param-out list $responseHeaders */ protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null) { From 299b2c1f2df7097021fe6ecacc7bd2e8639be238 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 8 Mar 2024 09:52:38 +0100 Subject: [PATCH 008/257] Update deps --- composer.lock | 106 +++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/composer.lock b/composer.lock index 04c822b48fea..5127fa4ebe2f 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "b66d11b7479109ab547f9405b97205640b17d385" + "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", - "reference": "b66d11b7479109ab547f9405b97205640b17d385", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.0" + "source": "https://github.com/composer/ca-bundle/tree/1.4.1" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2023-12-18T12:05:55+00:00" + "time": "2024-02-23T10:16:52+00:00" }, { "name": "composer/class-map-generator", @@ -226,16 +226,16 @@ }, { "name": "composer/pcre", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b439557066cd445732fa57cbc8d905394b4db8a0" + "reference": "02b8774a434b1b71edd8824440ceac1e3e49ee2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b439557066cd445732fa57cbc8d905394b4db8a0", - "reference": "b439557066cd445732fa57cbc8d905394b4db8a0", + "url": "https://api.github.com/repos/composer/pcre/zipball/02b8774a434b1b71edd8824440ceac1e3e49ee2b", + "reference": "02b8774a434b1b71edd8824440ceac1e3e49ee2b", "shasum": "" }, "require": { @@ -277,7 +277,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.1.1" + "source": "https://github.com/composer/pcre/tree/2.1.2" }, "funding": [ { @@ -293,7 +293,7 @@ "type": "tidelift" } ], - "time": "2023-10-11T07:10:55+00:00" + "time": "2024-03-07T14:52:56+00:00" }, { "name": "composer/semver", @@ -938,16 +938,16 @@ }, { "name": "symfony/console", - "version": "v5.4.35", + "version": "v5.4.36", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931" + "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dbdf6adcb88d5f83790e1efb57ef4074309d3931", - "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931", + "url": "https://api.github.com/repos/symfony/console/zipball/39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", + "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", "shasum": "" }, "require": { @@ -1017,7 +1017,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.35" + "source": "https://github.com/symfony/console/tree/v5.4.36" }, "funding": [ { @@ -1033,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:28:09+00:00" + "time": "2024-02-20T16:33:57+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1781,16 +1781,16 @@ }, { "name": "symfony/process", - "version": "v5.4.35", + "version": "v5.4.36", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "cbc28e34015ad50166fc2f9c8962d28d0fe861eb" + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/cbc28e34015ad50166fc2f9c8962d28d0fe861eb", - "reference": "cbc28e34015ad50166fc2f9c8962d28d0fe861eb", + "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", "shasum": "" }, "require": { @@ -1823,7 +1823,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.35" + "source": "https://github.com/symfony/process/tree/v5.4.36" }, "funding": [ { @@ -1839,7 +1839,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-02-12T15:49:53+00:00" }, { "name": "symfony/service-contracts", @@ -1926,16 +1926,16 @@ }, { "name": "symfony/string", - "version": "v5.4.35", + "version": "v5.4.36", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2" + "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/c209c4d0559acce1c9a2067612cfb5d35756edc2", - "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2", + "url": "https://api.github.com/repos/symfony/string/zipball/4e232c83622bd8cd32b794216aa29d0d266d353b", + "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b", "shasum": "" }, "require": { @@ -1992,7 +1992,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.35" + "source": "https://github.com/symfony/string/tree/v5.4.36" }, "funding": [ { @@ -2008,22 +2008,22 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-02-01T08:49:30+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.57", + "version": "1.10.60", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "1627b1d03446904aaa77593f370c5201d2ecc34e" + "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1627b1d03446904aaa77593f370c5201d2ecc34e", - "reference": "1627b1d03446904aaa77593f370c5201d2ecc34e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", "shasum": "" }, "require": { @@ -2072,7 +2072,7 @@ "type": "tidelift" } ], - "time": "2024-01-24T11:51:34+00:00" + "time": "2024-03-07T13:30:19+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2124,16 +2124,16 @@ }, { "name": "phpstan/phpstan-phpunit", - "version": "1.3.15", + "version": "1.3.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a" + "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/70ecacc64fe8090d8d2a33db5a51fe8e88acd93a", - "reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95", + "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95", "shasum": "" }, "require": { @@ -2170,9 +2170,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.15" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16" }, - "time": "2023-10-09T18:58:39+00:00" + "time": "2024-02-23T09:51:20+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -2225,16 +2225,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.7", + "version": "1.3.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1" + "reference": "d8a0bc03a68d95288b6471c37d435647fbdaff1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/ef7db637be9b85fa00278fc3477ac66abe8eb7d1", - "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/d8a0bc03a68d95288b6471c37d435647fbdaff1a", + "reference": "d8a0bc03a68d95288b6471c37d435647fbdaff1a", "shasum": "" }, "require": { @@ -2291,22 +2291,22 @@ "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.3.7" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.8" }, - "time": "2024-01-10T21:54:42+00:00" + "time": "2024-03-05T16:33:08+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.0.3", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "0a2eeb0d9e68bf01660e3e903f8113508bb46132" + "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0a2eeb0d9e68bf01660e3e903f8113508bb46132", - "reference": "0a2eeb0d9e68bf01660e3e903f8113508bb46132", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/54ca13ec990a40411ad978e08d994fca6cdd865f", + "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f", "shasum": "" }, "require": { @@ -2358,7 +2358,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.3" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.4" }, "funding": [ { @@ -2374,7 +2374,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T15:02:46+00:00" + "time": "2024-02-08T19:22:56+00:00" } ], "aliases": [], From 57427e6227f3bd9093daedab2a64feeb6cced4fa Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 8 Mar 2024 10:44:47 +0100 Subject: [PATCH 009/257] Fix filesystem::copy with broken symlinks, refs #11864 --- src/Composer/Util/Filesystem.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index baa3dfa8dd89..ad0aae983bb4 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -363,6 +363,9 @@ public function copyThenRemove(string $source, string $target) */ public function copy(string $source, string $target) { + // refs https://github.com/composer/composer/issues/11864 + $target = $this->normalizePath($target); + if (!is_dir($source)) { return copy($source, $target); } From 58276f2bd1688756b5ed7dc21960db7f52774c5e Mon Sep 17 00:00:00 2001 From: 8ig8 Date: Mon, 11 Mar 2024 11:41:05 -0400 Subject: [PATCH 010/257] Update scripts.md (#11880) Updates the 'Setting environment variables' example to call Composer using `@composer` in order to automatically resolve to whatever composer.phar is currently being used as detailed in the previous section on this same page. --- doc/articles/scripts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index ec4004ebc018..1f31eaeb42e4 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -419,7 +419,7 @@ To set an environment variable in a cross-platform way, you can use `@putenv`: "scripts": { "install-phpstan": [ "@putenv COMPOSER=phpstan-composer.json", - "composer install --prefer-dist" + "@composer install --prefer-dist" ] } } From 1a3474c4e7c465fc0f5010028ef675868bb888cd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Mar 2024 16:44:29 +0100 Subject: [PATCH 011/257] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b53992a5580d..d1aa6d804f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### [2.7.2] 2024-03-11 + + * Added info about the PHP version when running `composer --version` (#11866) + * Added warning when the root version cannot be detected (#11858) + * Fixed plugins still being enabled in a few contexts when running as root (c3efff91f) + * Fixed `outdated --ignore ...` still attempting to load the latest version of the ignored packages (#11863) + * Fixed handling of broken symlinks in the middle of an install path (#11864) + * Fixed `update --lock` still incorrectly updating some metadata (#11850, #11787) + ### [2.7.1] 2024-02-09 * Added several warnings when plugins are disabled to hint at common problems people had with 2.7.0 (#11842) @@ -1829,6 +1838,7 @@ * Initial release +[2.7.2]: https://github.com/composer/composer/compare/2.7.1...2.7.2 [2.7.1]: https://github.com/composer/composer/compare/2.7.0...2.7.1 [2.7.0]: https://github.com/composer/composer/compare/2.6.6...2.7.0 [2.6.6]: https://github.com/composer/composer/compare/2.6.5...2.6.6 From b826edb791571ab1eaf281eb1bd6e181a1192adc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Mar 2024 17:12:18 +0100 Subject: [PATCH 012/257] Release 2.7.2 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..542c5bc21741 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.2'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-03-11 17:12:18'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 96f757f3a4f06a67106773cc7d47a0adf545aae3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Mar 2024 17:12:19 +0100 Subject: [PATCH 013/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 542c5bc21741..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.2'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-03-11 17:12:18'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From 2124f09d75167449e9efa8ae3e2d8ad034e64bdb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Mar 2024 17:23:06 +0100 Subject: [PATCH 014/257] Fix context info being missing from output when using the IO classes as PSR-3 logger, fixes #11882 --- src/Composer/IO/BaseIO.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index 710f5a108b8f..c4d40ba015b7 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -15,6 +15,7 @@ use Composer\Config; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; +use Composer\Util\Silencer; use Psr\Log\LogLevel; abstract class BaseIO implements IOInterface @@ -219,6 +220,13 @@ public function log($level, $message, array $context = []): void { $message = (string) $message; + if ($context !== []) { + $json = Silencer::call('json_encode', $context, JSON_INVALID_UTF8_IGNORE|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); + if ($json !== false) { + $message .= ' ' . $json; + } + } + if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) { $this->writeError(''.$message.''); } elseif ($level === LogLevel::WARNING) { From 94fe2945456df51e122a492b8d14ac4b54c1d2ce Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Mar 2024 17:32:50 +0100 Subject: [PATCH 015/257] Fix self-update tests on releases --- tests/Composer/Test/Command/SelfUpdateCommandTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Composer/Test/Command/SelfUpdateCommandTest.php b/tests/Composer/Test/Command/SelfUpdateCommandTest.php index d0b855563fb5..cc7b579ffbe4 100644 --- a/tests/Composer/Test/Command/SelfUpdateCommandTest.php +++ b/tests/Composer/Test/Command/SelfUpdateCommandTest.php @@ -12,6 +12,7 @@ namespace Composer\Test\Command; +use Composer\Composer; use Composer\Test\TestCase; /** @@ -44,6 +45,10 @@ public function tearDown(): void public function testSuccessfulUpdate(): void { + if (Composer::VERSION !== '@package_version'.'@') { + $this->markTestSkipped('On releases this test can fail to upgrade as we are already on latest version'); + } + $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'self-update']); @@ -74,6 +79,10 @@ public function testUpdateWithInvalidOptionThrowsException(): void */ public function testUpdateToDifferentChannel(string $option, string $expectedOutput): void { + if (Composer::VERSION !== '@package_version'.'@' && in_array($option, ['--stable', '--preview'], true)) { + $this->markTestSkipped('On releases this test can fail to upgrade as we are already on latest version'); + } + $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'self-update', $option => true]); $appTester->assertCommandIsSuccessful(); From 5a20dba768d489496446b0db4987a957af7e1d1b Mon Sep 17 00:00:00 2001 From: Yanick Witschi Date: Thu, 14 Mar 2024 16:38:28 +0100 Subject: [PATCH 016/257] Only show warning about default version when not "project" type (#11885) --- src/Composer/Package/Loader/RootPackageLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 9796bb3c9ad0..64a169019a07 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -99,7 +99,7 @@ public function load(array $config, string $class = 'Composer\Package\RootPackag } if (!isset($config['version'])) { - if ($this->io !== null && $config['name'] !== '__root__') { + if ($this->io !== null && $config['name'] !== '__root__' && 'project' !== ($config['type'] ?? '')) { $this->io->warning( sprintf( "Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version", From 62126e1a402c2f5252a1e6026c20765ceae49252 Mon Sep 17 00:00:00 2001 From: Ayesh Karunaratne Date: Fri, 15 Mar 2024 19:55:25 +0700 Subject: [PATCH 017/257] [PHP 8.4] Fix for implicit nullability deprecation (#11888) Fixes a issue that emits a deprecation notice on PHP 8.4. See: - [RFC](https://wiki.php.net/rfc/deprecate-implicitly-nullable-types) - [PHP 8.4: Implicitly nullable parameter declarations deprecated](https://php.watch/versions/8.4/implicitly-marking-parameter-type-nullable-deprecated) --- src/Composer/Util/Git.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 67af22e8d3d0..64b64321648a 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -298,7 +298,7 @@ public function syncMirror(string $url, string $dir): bool return true; } - public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, string $prettyVersion = null): bool + public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?string $prettyVersion = null): bool { if ($this->checkRefIsInMirror($dir, $ref)) { if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) { From 61e2d246103c84d096aa17352b837451070fb80f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 15 Mar 2024 15:08:43 +0100 Subject: [PATCH 018/257] Update deps --- composer.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/composer.lock b/composer.lock index 5127fa4ebe2f..727057e8097c 100644 --- a/composer.lock +++ b/composer.lock @@ -8,28 +8,28 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", + "phpstan/phpstan": "^1.10", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.0" }, "funding": [ { @@ -80,20 +80,20 @@ "type": "tidelift" } ], - "time": "2024-02-23T10:16:52+00:00" + "time": "2024-03-15T14:00:32+00:00" }, { "name": "composer/class-map-generator", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9" + "reference": "8286a62d243312ed99b3eee20d5005c961adb311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8286a62d243312ed99b3eee20d5005c961adb311", + "reference": "8286a62d243312ed99b3eee20d5005c961adb311", "shasum": "" }, "require": { @@ -137,7 +137,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.1.0" + "source": "https://github.com/composer/class-map-generator/tree/1.1.1" }, "funding": [ { @@ -153,7 +153,7 @@ "type": "tidelift" } ], - "time": "2023-06-30T13:58:57+00:00" + "time": "2024-03-15T12:53:41+00:00" }, { "name": "composer/metadata-minifier", @@ -2014,16 +2014,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.60", + "version": "1.10.62", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + "reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd5c8a1660ed3540b211407c77abf4af193a6af9", + "reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9", "shasum": "" }, "require": { @@ -2072,7 +2072,7 @@ "type": "tidelift" } ], - "time": "2024-03-07T13:30:19+00:00" + "time": "2024-03-13T12:27:20+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", From 504e6c581a92ad409abd7f732355a76f84dd2a01 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Mar 2024 15:13:12 +0100 Subject: [PATCH 019/257] Update deps and baseline (1663, 96) --- composer.lock | 36 ++++++------- phpstan/baseline-8.1.neon | 7 +-- phpstan/baseline.neon | 56 ++------------------- src/Composer/Package/Dumper/ArrayDumper.php | 23 +++++---- 4 files changed, 35 insertions(+), 87 deletions(-) diff --git a/composer.lock b/composer.lock index 727057e8097c..07277ef9e275 100644 --- a/composer.lock +++ b/composer.lock @@ -226,16 +226,16 @@ }, { "name": "composer/pcre", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "02b8774a434b1b71edd8824440ceac1e3e49ee2b" + "reference": "540af382c97b83c628227d5f87cf56466d476191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/02b8774a434b1b71edd8824440ceac1e3e49ee2b", - "reference": "02b8774a434b1b71edd8824440ceac1e3e49ee2b", + "url": "https://api.github.com/repos/composer/pcre/zipball/540af382c97b83c628227d5f87cf56466d476191", + "reference": "540af382c97b83c628227d5f87cf56466d476191", "shasum": "" }, "require": { @@ -277,7 +277,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.1.2" + "source": "https://github.com/composer/pcre/tree/2.1.3" }, "funding": [ { @@ -293,7 +293,7 @@ "type": "tidelift" } ], - "time": "2024-03-07T14:52:56+00:00" + "time": "2024-03-19T09:03:05+00:00" }, { "name": "composer/semver", @@ -2014,16 +2014,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.62", + "version": "1.10.63", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9" + "reference": "ad12836d9ca227301f5fb9960979574ed8628339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd5c8a1660ed3540b211407c77abf4af193a6af9", - "reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ad12836d9ca227301f5fb9960979574ed8628339", + "reference": "ad12836d9ca227301f5fb9960979574ed8628339", "shasum": "" }, "require": { @@ -2072,7 +2072,7 @@ "type": "tidelift" } ], - "time": "2024-03-13T12:27:20+00:00" + "time": "2024-03-18T16:53:53+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2225,22 +2225,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.8", + "version": "1.3.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "d8a0bc03a68d95288b6471c37d435647fbdaff1a" + "reference": "a32bc86da24495025d7aafd1ba62444d4a364a98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/d8a0bc03a68d95288b6471c37d435647fbdaff1a", - "reference": "d8a0bc03a68d95288b6471c37d435647fbdaff1a", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/a32bc86da24495025d7aafd1ba62444d4a364a98", + "reference": "a32bc86da24495025d7aafd1ba62444d4a364a98", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.36" + "phpstan/phpstan": "^1.10.62" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -2291,9 +2291,9 @@ "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.3.8" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.9" }, - "time": "2024-03-05T16:33:08+00:00" + "time": "2024-03-16T16:50:20+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/phpstan/baseline-8.1.neon b/phpstan/baseline-8.1.neon index 5a0aabf76033..6807635f8c0f 100644 --- a/phpstan/baseline-8.1.neon +++ b/phpstan/baseline-8.1.neon @@ -15,11 +15,6 @@ parameters: count: 1 path: ../src/Composer/Command/ArchiveCommand.php - - - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Command/ConfigCommand.php - - message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, array\\{Composer\\\\Config\\\\JsonConfigSource, string\\} given\\.$#" count: 2 @@ -131,7 +126,7 @@ parameters: path: ../src/Composer/Package/Archiver/PharArchiver.php - - message: "#^Parameter \\#1 \\$array of function ksort expects array, array\\\\|string given\\.$#" + message: "#^Parameter \\#1 \\$array of function ksort expects array, array\\|string given\\.$#" count: 1 path: ../src/Composer/Package/Dumper/ArrayDumper.php diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 45dcac955b24..8cc3e84f9370 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -271,7 +271,7 @@ parameters: path: ../src/Composer/Command/ConfigCommand.php - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" + message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" count: 1 path: ../src/Composer/Command/ConfigCommand.php @@ -1135,11 +1135,6 @@ parameters: count: 1 path: ../src/Composer/DependencyResolver/GenericRule.php - - - message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/LockTransaction.php - - message: "#^Cannot access offset 'hash' on array\\|false\\.$#" count: 1 @@ -2385,11 +2380,6 @@ parameters: count: 1 path: ../src/Composer/Installer/SuggestedPackagesReporter.php - - - message: "#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#" - count: 2 - path: ../src/Composer/Installer/SuggestedPackagesReporter.php - - message: "#^Only booleans are allowed in an if condition, Composer\\\\Package\\\\PackageInterface\\|null given\\.$#" count: 2 @@ -2600,28 +2590,18 @@ parameters: count: 2 path: ../src/Composer/Package/Dumper/ArrayDumper.php - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Package/Dumper/ArrayDumper.php - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" count: 1 path: ../src/Composer/Package/Dumper/ArrayDumper.php - - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" - count: 1 - path: ../src/Composer/Package/Dumper/ArrayDumper.php - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 4 + count: 1 path: ../src/Composer/Package/Dumper/ArrayDumper.php - - message: "#^Parameter \\#1 \\$array_arg of function ksort expects array, array\\\\|string given\\.$#" + message: "#^Parameter \\#1 \\$array_arg of function ksort expects array, array\\|string given\\.$#" count: 1 path: ../src/Composer/Package/Dumper/ArrayDumper.php @@ -2705,21 +2685,6 @@ parameters: count: 2 path: ../src/Composer/Package/Loader/RootPackageLoader.php - - - message: "#^Parameter \\#1 \\$requires of method Composer\\\\Package\\\\Loader\\\\RootPackageLoader\\:\\:extractAliases\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Package/Loader/RootPackageLoader.php - - - - message: "#^Parameter \\#1 \\$requires of static method Composer\\\\Package\\\\Loader\\\\RootPackageLoader\\:\\:extractReferences\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Package/Loader/RootPackageLoader.php - - - - message: "#^Parameter \\#1 \\$requires of static method Composer\\\\Package\\\\Loader\\\\RootPackageLoader\\:\\:extractStabilityFlags\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Package/Loader/RootPackageLoader.php - - message: "#^Parameter \\#2 \\$class \\(class\\-string\\\\) of method Composer\\\\Package\\\\Loader\\\\RootPackageLoader\\:\\:load\\(\\) should be contravariant with parameter \\$class \\(class\\-string\\\\) of method Composer\\\\Package\\\\Loader\\\\ArrayLoader\\:\\:load\\(\\)$#" count: 1 @@ -3090,16 +3055,6 @@ parameters: count: 1 path: ../src/Composer/Repository/ComposerRepository.php - - - message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: ../src/Composer/Repository/ComposerRepository.php - - - - message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:whatProvides\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: ../src/Composer/Repository/ComposerRepository.php - - message: "#^Only booleans are allowed in &&, Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\|null given on the left side\\.$#" count: 1 @@ -4473,11 +4428,6 @@ parameters: count: 1 path: ../src/Composer/Util/NoProxyPattern.php - - - message: "#^Method Composer\\\\Util\\\\NoProxyPattern\\:\\:ipCheckData\\(\\) never assigns null to &\\$ipdata so it can be removed from the by\\-ref type\\.$#" - count: 1 - path: ../src/Composer/Util/NoProxyPattern.php - - message: "#^Only booleans are allowed in a negated boolean, bool\\|stdClass given\\.$#" count: 1 diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index f713fbe333aa..046ba6674fa3 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -44,11 +44,11 @@ public function dump(PackageInterface $package): array $data['version'] = $package->getPrettyVersion(); $data['version_normalized'] = $package->getVersion(); - if ($package->getTargetDir()) { + if ($package->getTargetDir() !== null) { $data['target-dir'] = $package->getTargetDir(); } - if ($package->getSourceType()) { + if ($package->getSourceType() !== null) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); if (null !== ($value = $package->getSourceReference())) { @@ -59,7 +59,7 @@ public function dump(PackageInterface $package): array } } - if ($package->getDistType()) { + if ($package->getDistType() !== null) { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); if (null !== ($value = $package->getDistReference())) { @@ -74,15 +74,18 @@ public function dump(PackageInterface $package): array } foreach (BasePackage::$supportedLinkTypes as $type => $opts) { - if ($links = $package->{'get'.ucfirst($opts['method'])}()) { - foreach ($links as $link) { - $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); - } - ksort($data[$type]); + $links = $package->{'get'.ucfirst($opts['method'])}(); + if (\count($links) === 0) { + continue; } + foreach ($links as $link) { + $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); + } + ksort($data[$type]); } - if ($packages = $package->getSuggests()) { + $packages = $package->getSuggests(); + if (\count($packages) > 0) { ksort($packages); $data['suggest'] = $packages; } @@ -130,7 +133,7 @@ public function dump(PackageInterface $package): array if ($package instanceof RootPackageInterface) { $minimumStability = $package->getMinimumStability(); - if ($minimumStability) { + if ($minimumStability !== '') { $data['minimum-stability'] = $minimumStability; } } From 83212118cbaf7ab44b51f8afcd45a7540275e639 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Mar 2024 15:23:35 +0100 Subject: [PATCH 020/257] Revert "Remove docs about light-weight packages" This reverts commit b34220edc8ed738bce3f682248622976543d6ec4. --- doc/02-libraries.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/doc/02-libraries.md b/doc/02-libraries.md index c731f105d956..7b498593719a 100644 --- a/doc/02-libraries.md +++ b/doc/02-libraries.md @@ -150,4 +150,34 @@ allows you to submit the URL to your VCS repository, at which point Packagist will start crawling it. Once it is done, your package will be available to anyone! +## Light-weight distribution packages + +Some useless information like the `.github` directory, or large examples, test +data, etc. should typically not be included in distributed packages. + +The `.gitattributes` file is a git specific file like `.gitignore` also living +at the root directory of your library. It overrides local and global +configuration (`.git/config` and `~/.gitconfig` respectively) when present and +tracked by git. + +Use `.gitattributes` to prevent unwanted files from bloating the zip +distribution packages. + +```text +// .gitattributes +/demo export-ignore +phpunit.xml.dist export-ignore +/.github/ export-ignore +``` + +Test it by inspecting the zip file generated manually: + +```shell +git archive branchName --format zip -o file.zip +``` + +> **Note:** Files would be still tracked by git just not included in the +> zip distribution. This only works for packages installed from +> dist (i.e. tagged releases) coming from GitHub, GitLab or Bitbucket. + ← [Basic usage](01-basic-usage.md) | [Command-line interface](03-cli.md) → From d36cd30d118205f6badab24f2b2698bf91990af7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Mar 2024 15:18:59 +0000 Subject: [PATCH 021/257] HttpDownloader: add option to prevent access to private network (#11895) --- src/Composer/Util/Http/CurlDownloader.php | 16 +++++++++++++++- src/Composer/Util/RemoteFilesystem.php | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index f5bbe24aae10..e512f082422c 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -23,13 +23,14 @@ use Composer\Util\Url; use Composer\Util\HttpDownloader; use React\Promise\Promise; +use Symfony\Component\HttpFoundation\IpUtils; /** * @internal * @author Jordi Boggiano * @author Nicolas Grekas * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null} - * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable} + * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable, primaryIp: string} */ class CurlDownloader { @@ -279,6 +280,7 @@ private function initDownload(callable $resolve, callable $reject, string $origi 'bodyHandle' => $bodyHandle, 'resolve' => $resolve, 'reject' => $reject, + 'primaryIp' => '', ]; $usingProxy = $proxy->getFormattedUrl(' using proxy (%s)'); @@ -505,6 +507,18 @@ public function tick(): void } } + if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) { + if ( + isset($this->jobs[$i]['options']['prevent_ip_access_callable']) && + is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) && + $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip']) + ) { + throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url'])); + } + + $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip']; + } + // TODO progress } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 0cc9df772692..6fa3437a3f0e 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -249,6 +249,10 @@ protected function get(string $originUrl, string $fileUrl, array $additionalOpti $origFileUrl = $fileUrl; + if (isset($options['prevent_ip_access_callable'])) { + throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config."); + } + if (isset($options['gitlab-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; unset($options['gitlab-token']); From d00f59035498e9ac30b30822a8cbb5acc64e1993 Mon Sep 17 00:00:00 2001 From: Brad Jones Date: Tue, 19 Mar 2024 09:24:10 -0600 Subject: [PATCH 022/257] Surface the advisory ID when CVE not present. (#11892) --- src/Composer/Advisory/Auditor.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index f0dc76ae5ad3..38d827dfe190 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -264,6 +264,10 @@ private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void $advisory->affectedVersions->getPrettyString(), $advisory->reportedAt->format(DATE_ATOM), ]; + if ($advisory->cve === null) { + $headers[] = 'Advisory ID'; + $row[] = $advisory->advisoryId; + } if ($advisory instanceof IgnoredSecurityAdvisory) { $headers[] = 'Ignore reason'; $row[] = $advisory->ignoreReason ?? 'None specified'; @@ -294,6 +298,9 @@ private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void $error[] = "Package: ".$advisory->packageName; $error[] = "Severity: ".$this->getSeverity($advisory); $error[] = "CVE: ".$this->getCVE($advisory); + if ($advisory->cve === null) { + $error[] = "Advisory ID: ".$advisory->advisoryId; + } $error[] = "Title: ".OutputFormatter::escape($advisory->title); $error[] = "URL: ".$this->getURL($advisory); $error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString()); From 7e9bc82017eb7e47d333dcf0512c1b92da306794 Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 19 Mar 2024 23:27:29 +0800 Subject: [PATCH 023/257] fix: typo (#11894) Signed-off-by: guoguangwu --- doc/06-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/06-config.md b/doc/06-config.md index c6aa47491db1..f2c914f2e202 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -153,7 +153,7 @@ Defaults to `report` in Composer 2.6, and defaults to `fail` from Composer 2.7 o } ``` -Since Composer 2.7 the option can be overriden via the [`COMPOSER_AUDIT_ABANDONED`](03-cli.md#composer-audit-abandoned) environment variable. +Since Composer 2.7 the option can be overridden via the [`COMPOSER_AUDIT_ABANDONED`](03-cli.md#composer-audit-abandoned) environment variable. ## use-parent-dir From 5a1d506c77c7e182a90f76ce011e2fc5b7ba7f14 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Mar 2024 10:47:52 +0100 Subject: [PATCH 024/257] Fix composer status command handling of failed promises, closes #11889 --- src/Composer/Downloader/FileDownloader.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 2a2847f33f16..b128c8a79993 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -495,10 +495,22 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin $this->filesystem->removeDirectory($targetDir.'_compare'); } - $this->download($package, $targetDir.'_compare', null, false); + $promise = $this->download($package, $targetDir.'_compare', null, false); + $promise->catch(function ($ex) use (&$e) { + $e = $ex; + }); $this->httpDownloader->wait(); - $this->install($package, $targetDir.'_compare', false); + if ($e !== null) { + throw $e; + } + $promise = $this->install($package, $targetDir.'_compare', false); + $promise->catch(function ($ex) use (&$e) { + $e = $ex; + }); $this->process->wait(); + if ($e !== null) { + throw $e; + } $comparer = new Comparer(); $comparer->setSource($targetDir.'_compare'); @@ -511,7 +523,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin $this->io = $prevIO; - if ($e) { + if ($e !== null) { throw $e; } From 59152ad7aa9746b844eb3d3a352b3fd1c68f1b80 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Mar 2024 12:07:38 +0100 Subject: [PATCH 025/257] Fix phpstan errors in FileDownloader, update baseline (1642, 96) --- phpstan/baseline.neon | 70 ---------------- src/Composer/Downloader/FileDownloader.php | 94 +++++++++++----------- src/Composer/Package/PackageInterface.php | 2 + 3 files changed, 49 insertions(+), 117 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 8cc3e84f9370..adbcd83ddcb3 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -1575,81 +1575,11 @@ parameters: count: 1 path: ../src/Composer/Downloader/DownloadManager.php - - - message: "#^Call to function array_search\\(\\) requires parameter \\#3 to be set\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Foreach overwrites \\$path with its value variable\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - message: "#^Method Composer\\\\Downloader\\\\FileDownloader\\:\\:getDistPath\\(\\) should return string but returns array\\\\|string\\.$#" count: 1 path: ../src/Composer/Downloader/FileDownloader.php - - - message: "#^Only booleans are allowed in &&, Composer\\\\Cache\\|null given on the left side\\.$#" - count: 4 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in a negated boolean, int given\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#" - count: 2 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\EventDispatcher\\\\EventDispatcher\\|null given\\.$#" - count: 2 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, Exception\\|null given\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, array\\\\> given\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, int given\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 3 - path: ../src/Composer/Downloader/FileDownloader.php - - message: "#^Strict comparison using \\=\\=\\= between null and Composer\\\\Util\\\\Http\\\\Response\\|string will always evaluate to false\\.$#" count: 1 diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index b128c8a79993..97cf134febd8 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -27,6 +27,7 @@ use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; +use Composer\Util\Http\Response; use Composer\Util\Platform; use Composer\Util\Silencer; use Composer\Util\HttpDownloader; @@ -99,9 +100,9 @@ public function __construct(IOInterface $io, Config $config, HttpDownloader $htt $this->httpDownloader = $httpDownloader; $this->cache = $cache; $this->process = $process ?? new ProcessExecutor($io); - $this->filesystem = $filesystem ?: new Filesystem($this->process); + $this->filesystem = $filesystem ?? new Filesystem($this->process); - if ($this->cache && $this->cache->gcIsNecessary()) { + if ($this->cache !== null && $this->cache->gcIsNecessary()) { $this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE); $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); } @@ -120,7 +121,7 @@ public function getInstallationSource(): string */ public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface { - if (!$package->getDistUrl()) { + if (null === $package->getDistUrl()) { throw new \InvalidArgumentException('The given package is missing url information'); } @@ -152,21 +153,15 @@ public function download(PackageInterface $package, string $path, ?PackageInterf $this->filesystem->ensureDirectoryExists($path); $this->filesystem->ensureDirectoryExists(dirname($fileName)); - $io = $this->io; - $cache = $this->cache; - $httpDownloader = $this->httpDownloader; - $eventDispatcher = $this->eventDispatcher; - $filesystem = $this->filesystem; - $accept = null; $reject = null; - $download = function () use ($io, $output, $httpDownloader, $cache, $cacheKeyGenerator, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject) { + $download = function () use ($output, $cacheKeyGenerator, $package, $fileName, &$urls, &$accept, &$reject) { $url = reset($urls); $index = key($urls); - if ($eventDispatcher) { - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed'], 'package', $package); - $eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + if ($this->eventDispatcher !== null) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $url['processed'], 'package', $package); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); if ($preFileDownloadEvent->getCustomCacheKey() !== null) { $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey()); } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) { @@ -181,27 +176,27 @@ public function download(PackageInterface $package, string $path, ?PackageInterf $cacheKey = $url['cacheKey']; // use from cache if it is present and has a valid checksum or we have no checksum to check against - if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) { + if ($this->cache !== null && ($checksum === null || $checksum === '' || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { if ($output) { - $io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache", true, IOInterface::VERY_VERBOSE); + $this->io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache", true, IOInterface::VERY_VERBOSE); } // mark the file as having been written in cache even though it is only read from cache, so that if // the cache is corrupt the archive will be deleted and the next attempt will re-download it // see https://github.com/composer/composer/issues/10028 - if (!$cache->isReadOnly()) { + if (!$this->cache->isReadOnly()) { $this->lastCacheWrites[$package->getName()] = $cacheKey; } $result = \React\Promise\resolve($fileName); } else { if ($output) { - $io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + $this->io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } - $result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) + $result = $this->httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) ->then($accept, $reject); } - return $result->then(static function ($result) use ($fileName, $checksum, $url, $package, $eventDispatcher): string { + return $result->then(function ($result) use ($fileName, $checksum, $url, $package): string { // in case of retry, the first call's Promise chain finally calls this twice at the end, // once with $result being the returned $fileName from $accept, and then once for every // failed request with a null result, which can be skipped. @@ -214,31 +209,35 @@ public function download(PackageInterface $package, string $path, ?PackageInterf .' directory is writable and you have internet connectivity'); } - if ($checksum && hash_file('sha1', $fileName) !== $checksum) { + if ($checksum !== null && $checksum !== '' && hash_file('sha1', $fileName) !== $checksum) { throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')'); } - if ($eventDispatcher) { + if ($this->eventDispatcher !== null) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package); - $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } return $fileName; }); }; - $accept = function ($response) use ($cache, $package, $fileName, &$urls): string { + $accept = function (Response $response) use ($package, $fileName, &$urls): string { $url = reset($urls); $cacheKey = $url['cacheKey']; - FileDownloader::$downloadMetadata[$package->getName()] = @filesize($fileName) ?: $response->getHeader('Content-Length') ?: '?'; + $fileSize = @filesize($fileName); + if (false === $fileSize) { + $fileSize = $response->getHeader('Content-Length') ?? '?'; + } + FileDownloader::$downloadMetadata[$package->getName()] = $fileSize; if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders(); } - if ($cache && !$cache->isReadOnly()) { + if ($this->cache !== null && !$this->cache->isReadOnly()) { $this->lastCacheWrites[$package->getName()] = $cacheKey; - $cache->copyFrom($cacheKey, $fileName); + $this->cache->copyFrom($cacheKey, $fileName); } $response->collect(); @@ -246,10 +245,10 @@ public function download(PackageInterface $package, string $path, ?PackageInterf return $fileName; }; - $reject = function ($e) use ($io, &$urls, $download, $fileName, $package, &$retries, $filesystem) { + $reject = function ($e) use (&$urls, $download, $fileName, $package, &$retries) { // clean up if (file_exists($fileName)) { - $filesystem->unlink($fileName); + $this->filesystem->unlink($fileName); } $this->clearLastCacheWrite($package); @@ -263,7 +262,7 @@ public function download(PackageInterface $package, string $path, ?PackageInterf if ($e instanceof TransportException) { // if we got an http response with a proper code, then requesting again will probably not help, abort - if ((0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504])) || !$retries) { + if (0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504], true)) { $retries = 0; } } @@ -274,7 +273,7 @@ public function download(PackageInterface $package, string $path, ?PackageInterf $urls = []; } - if ($retries) { + if ($retries > 0) { usleep(500000); $retries--; @@ -282,12 +281,12 @@ public function download(PackageInterface $package, string $path, ?PackageInterf } array_shift($urls); - if ($urls) { - if ($io->isDebug()) { - $io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); - $io->writeError(' Trying the next URL for '.$package->getName()); + if (\count($urls) > 0) { + if ($this->io->isDebug()) { + $this->io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); + $this->io->writeError(' Trying the next URL for '.$package->getName()); } else { - $io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); + $this->io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); } $retries = 3; @@ -328,8 +327,8 @@ public function cleanup(string $type, PackageInterface $package, string $path, ? ]; if (isset($this->additionalCleanupPaths[$package->getName()])) { - foreach ($this->additionalCleanupPaths[$package->getName()] as $path) { - $this->filesystem->remove($path); + foreach ($this->additionalCleanupPaths[$package->getName()] as $pathToClean) { + $this->filesystem->remove($pathToClean); } } @@ -355,19 +354,20 @@ public function install(PackageInterface $package, string $path, bool $output = $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME)); - if ($package->getBinaries()) { - // Single files can not have a mode set like files in archives - // so we make sure if the file is a binary that it is executable - foreach ($package->getBinaries() as $bin) { - if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) { - Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask()); - } + // Single files can not have a mode set like files in archives + // so we make sure if the file is a binary that it is executable + foreach ($package->getBinaries() as $bin) { + if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) { + Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask()); } } return \React\Promise\resolve(null); } + /** + * @param PATHINFO_EXTENSION|PATHINFO_BASENAME $component + */ protected function getDistPath(PackageInterface $package, int $component): string { return pathinfo((string) parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), $component); @@ -375,7 +375,7 @@ protected function getDistPath(PackageInterface $package, int $component): strin protected function clearLastCacheWrite(PackageInterface $package): void { - if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { + if ($this->cache !== null && isset($this->lastCacheWrites[$package->getName()])) { $this->cache->remove($this->lastCacheWrites[$package->getName()]); unset($this->lastCacheWrites[$package->getName()]); } @@ -389,7 +389,7 @@ protected function addCleanupPath(PackageInterface $package, string $path): void protected function removeCleanupPath(PackageInterface $package, string $path): void { if (isset($this->additionalCleanupPaths[$package->getName()])) { - $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()]); + $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()], true); if (false !== $idx) { unset($this->additionalCleanupPaths[$package->getName()][$idx]); } @@ -469,7 +469,7 @@ protected function processUrl(PackageInterface $package, string $url): string throw new \RuntimeException('You must enable the openssl extension to download files via https'); } - if ($package->getDistReference()) { + if ($package->getDistReference() !== null) { $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference()); } diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 4d874ef120a2..04e2f05f8048 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -181,6 +181,8 @@ public function getDistReference(): ?string; /** * Returns the sha1 checksum for the distribution archive of this version * + * Can be an empty string which should be treated as null + * * @return ?string */ public function getDistSha1Checksum(): ?string; From 75ccf6557a64c4ac273e0e1ad170bf76c2683317 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Mar 2024 12:32:54 +0100 Subject: [PATCH 026/257] Use reactphp/promise v2 compatible code --- src/Composer/Downloader/FileDownloader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 97cf134febd8..a4f13f3091be 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -496,7 +496,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin } $promise = $this->download($package, $targetDir.'_compare', null, false); - $promise->catch(function ($ex) use (&$e) { + $promise->then(null, function ($ex) use (&$e) { $e = $ex; }); $this->httpDownloader->wait(); @@ -504,7 +504,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin throw $e; } $promise = $this->install($package, $targetDir.'_compare', false); - $promise->catch(function ($ex) use (&$e) { + $promise->then(null, function ($ex) use (&$e) { $e = $ex; }); $this->process->wait(); From a6947f116a49f0017e74fd2cd481daffbe2de1f7 Mon Sep 17 00:00:00 2001 From: gaxweb <108662458+gaxweb@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:31:25 +0100 Subject: [PATCH 027/257] Allow for SSH URLs when using hg repository type (#11878) --- src/Composer/Util/Hg.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 0e9f6e58ffa0..c687542e0462 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -58,10 +58,20 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd) } // Try with the authentication information available - if (Preg::isMatch('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication((string) $match[5])) { - $auth = $this->io->getAuthentication((string) $match[5]); - $authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . $match[6]; - + if ( + Preg::isMatch('{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi', $url, $matches) + && $this->io->hasAuthentication((string) $matches['host']) + ) { + if ($matches['proto'] === 'ssh') { + $user = ''; + if ($matches['user'] !== '' && $matches['user'] !== null) { + $user = rawurlencode($matches['user']) . '@'; + } + $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path']; + } else { + $auth = $this->io->getAuthentication((string) $matches['host']); + $authenticatedUrl = $matches['proto'] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $matches['host'] . $matches['path']; + } $command = $commandCallable($authenticatedUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { @@ -70,10 +80,10 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd) $error = $this->process->getErrorOutput(); } else { - $error = 'The given URL (' . $url . ') does not match the required format (http(s)://(username:password@)example.com/path-to-repository)'; + $error = 'The given URL (' .$url. ') does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)'; } - $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url); + $this->throwException("Failed to clone $url, \n\n" . $error, $url); } /** @@ -84,7 +94,9 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd) private function throwException($message, string $url): void { if (null === self::getVersion($this->process)) { - throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); + throw new \RuntimeException(Url::sanitize( + 'Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput() + )); } throw new \RuntimeException(Url::sanitize($message)); From 07fa4255d68d9ee1a34c3c8d9e210925562b8327 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Mar 2024 22:04:58 +0100 Subject: [PATCH 028/257] Add support for php extension packages (#11795) * Update schema * Validate php-ext is only set for php-ext or php-ext-zend packages * Make sure the pool builder excludes php-ext/php-ext-zend --- doc/04-schema.md | 3 ++ res/composer-schema.json | 35 +++++++++++++++++++ .../DependencyResolver/PoolBuilder.php | 14 ++++++++ src/Composer/Package/AliasPackage.php | 5 +++ src/Composer/Package/Loader/ArrayLoader.php | 4 +++ .../Package/Loader/ValidatingArrayLoader.php | 6 ++++ src/Composer/Package/Package.php | 20 +++++++++++ src/Composer/Package/PackageInterface.php | 7 ++++ src/Composer/Repository/RepositorySet.php | 1 + ...ckage-and-its-provider-skips-original.test | 10 +++--- 10 files changed, 100 insertions(+), 5 deletions(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index e36ec21115b9..c4dca5027d5b 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -102,6 +102,9 @@ Out of the box, Composer supports four types: - **composer-plugin:** A package of type `composer-plugin` may provide an installer for other packages that have a custom type. Read more in the [dedicated article](articles/custom-installers.md). +- **php-ext** and **php-ext-zend**: These names are reserved for PHP extension + packages which are written in C. Do not use these types for packages written + in PHP. Only use a custom type if you need custom logic during installation. It is recommended to omit this field and have it default to `library`. diff --git a/res/composer-schema.json b/res/composer-schema.json index a77becb4b3e1..dfbd8beb6ff8 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -288,6 +288,41 @@ } } }, + "php-ext": { + "type": "object", + "description": "Settings for PHP extension packages.", + "properties": { + "priority": { + "type": "integer", + "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.", + "minimum": 10, + "maximum": 99, + "example": 80, + "default": 80 + }, + "configure-options": { + "type": "array", + "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.", + "example": "without-xdebug-compression", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$" + }, + "description": { + "type": "string", + "description": "The description of what the flag does or means.", + "example": "Disable compression through zlib" + } + } + } + } + } + }, "config": { "type": "object", "description": "Composer options.", diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index d84d40906671..82b89cb3a718 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -106,6 +106,8 @@ class PoolBuilder private $updateAllowList = []; /** @var array> */ private $skippedLoad = []; + /** @var list */ + private $ignoredTypes = []; /** * If provided, only these package names are loaded @@ -170,6 +172,14 @@ public function __construct(array $acceptableStabilities, array $stabilityFlags, $this->temporaryConstraints = $temporaryConstraints; } + /** + * @param list $types + */ + public function setIgnoredTypes(array $types): void + { + $this->ignoredTypes = $types; + } + /** * @param RepositoryInterface[] $repositories */ @@ -416,6 +426,10 @@ private function loadPackagesMarkedForLoading(Request $request, array $repositor */ private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void { + if (in_array($package->getType(), $this->ignoredTypes, true)) { + return; + } + $index = $this->indexCounter++; $this->packages[$index] = $package; diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 75d3158cc573..932ea3658acf 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -351,6 +351,11 @@ public function getIncludePaths(): array return $this->aliasOf->getIncludePaths(); } + public function getPhpExt(): ?array + { + return $this->aliasOf->getPhpExt(); + } + public function getReleaseDate(): ?\DateTimeInterface { return $this->aliasOf->getReleaseDate(); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index daa540bd80ed..887f2913bb6c 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -228,6 +228,10 @@ private function configureObject(PackageInterface $package, array $config): Base $package->setIncludePaths($config['include-path']); } + if (isset($config['php-ext'])) { + $package->setPhpExt($config['php-ext']); + } + if (!empty($config['time'])) { $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index a94448a6159b..6d1388e234b8 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -247,6 +247,12 @@ public function load(array $config, string $class = 'Composer\Package\CompletePa } } + $this->validateArray('php-ext'); + if (isset($this->config['php-ext']) && !in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) { + $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions'; + unset($this->config['php-ext']); + } + $unboundConstraint = new Constraint('=', '10000000-dev'); foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index 295bbd249cd5..e1b5755f1803 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -98,6 +98,8 @@ class Package extends BasePackage protected $isDefaultBranch = false; /** @var mixed[] */ protected $transportOptions = []; + /** @var array{priority?: int, config?: array}|null */ + protected $phpExt = null; /** * Creates a new in memory package. @@ -590,6 +592,24 @@ public function getIncludePaths(): array return $this->includePaths; } + /** + * Sets the list of paths added to PHP's include path. + * + * @param array{priority?: int, config?: array}|null $phpExt List of directories. + */ + public function setPhpExt(?array $phpExt): void + { + $this->phpExt = $phpExt; + } + + /** + * @inheritDoc + */ + public function getPhpExt(): ?array + { + return $this->phpExt; + } + /** * Sets the notification URL */ diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 04e2f05f8048..a6af933b519c 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -323,6 +323,13 @@ public function getDevAutoload(): array; */ public function getIncludePaths(): array; + /** + * Returns the settings for php extension packages + * + * @return array{priority?: int, config?: array}|null + */ + public function getPhpExt(): ?array; + /** * Stores a reference to the repository that owns the package */ diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 48cf424a4f8a..2a9a49b7de00 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -314,6 +314,7 @@ public function isPackageAcceptable(array $names, string $stability): bool public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null): Pool { $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints); + $poolBuilder->setIgnoredTypes(['php-ext', 'php-ext-zend']); foreach ($this->repositories as $repo) { if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { diff --git a/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test b/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test index 70a74be8ddd2..4f7309e7a40c 100644 --- a/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test +++ b/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test @@ -6,17 +6,17 @@ Install package and it's replacer skips the original { "type": "package", "package": [ - { "name": "ext-foo", "version": "1.0.0" }, - { "name": "ext-foo/fork", "version": "0.5.0", "replace": { "ext-foo": "1.0.*" } } + { "name": "example/foo", "version": "1.0.0" }, + { "name": "example/foo-fork", "version": "0.5.0", "replace": { "example/foo": "1.0.*" } } ] } ], "require": { - "ext-foo": "1.0.0", - "ext-foo/fork": "0.5.*" + "example/foo": "1.0.0", + "example/foo-fork": "0.5.*" } } --RUN-- install --EXPECT-- -Installing ext-foo/fork (0.5.0) +Installing example/foo-fork (0.5.0) From bc157ebea90d79fa880b8ee9e027935b55d84cb4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Mar 2024 22:44:48 +0100 Subject: [PATCH 029/257] Fix phpdoc for new php-ext schema --- src/Composer/Package/Package.php | 4 ++-- src/Composer/Package/PackageInterface.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index e1b5755f1803..8bcd6e60b657 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -98,7 +98,7 @@ class Package extends BasePackage protected $isDefaultBranch = false; /** @var mixed[] */ protected $transportOptions = []; - /** @var array{priority?: int, config?: array}|null */ + /** @var array{priority?: int, configure-options?: list}|null */ protected $phpExt = null; /** @@ -595,7 +595,7 @@ public function getIncludePaths(): array /** * Sets the list of paths added to PHP's include path. * - * @param array{priority?: int, config?: array}|null $phpExt List of directories. + * @param array{priority?: int, configure-options?: list}|null $phpExt List of directories. */ public function setPhpExt(?array $phpExt): void { diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index a6af933b519c..3583a323e1e8 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -326,7 +326,7 @@ public function getIncludePaths(): array; /** * Returns the settings for php extension packages * - * @return array{priority?: int, config?: array}|null + * @return array{priority?: int, configure-options?: list}|null */ public function getPhpExt(): ?array; From 2027d4975a5cf3a077a23d815462bd1fcb43066d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Mar 2024 11:16:56 +0100 Subject: [PATCH 030/257] Fail status more softly unless -vvv is used, refs #11889 --- src/Composer/Downloader/FileDownloader.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index a4f13f3091be..adf26785c1bf 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -524,7 +524,11 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin $this->io = $prevIO; if ($e !== null) { - throw $e; + if ($this->io->isDebug()) { + throw $e; + } + + return 'Failed to detect changes: ['.get_class($e).'] '.$e->getMessage(); } $output = trim($output); From 54870a78c4d4092fa509da4a5d93f3080f6fc956 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Mar 2024 16:59:41 +0100 Subject: [PATCH 031/257] Add a new test to confirm that show --direct works with direct dependents/dev-dependents --- .../Composer/Test/Command/ShowCommandTest.php | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index 331ae0017bc3..d726c5a9208d 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -299,23 +299,57 @@ public function testOutdatedFiltersAccordingToPlatformReqsWithoutWarningForHighe vendor/package 1.1.0 ! 1.2.0", trim($appTester->getDisplay(true))); } - public function testShowDirectWithNameOnlyShowsDirectDependents(): void + public function testShowDirectWithNameDoesNotShowTransientDependencies(): void { self::expectException(InvalidArgumentException::class); self::expectExceptionMessage('Package "vendor/package" is installed but not a direct dependent of the root package.'); $this->initTempComposer([ 'repositories' => [], + 'require' => [ + 'direct/dependent' => '*', + ], ]); $this->createInstalledJson([ + $direct = self::getPackage('direct/dependent', '1.0.0'), self::getPackage('vendor/package', '1.0.0'), ]); + $this->configureLinks($direct, ['require' => ['vendor/package' => '*']]); + $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'show', '--direct' => true, 'package' => 'vendor/package']); } + public function testShowDirectWithNameOnlyShowsDirectDependents(): void + { + $this->initTempComposer([ + 'repositories' => [], + 'require' => [ + 'direct/dependent' => '*', + ], + 'require-dev' => [ + 'direct/dependent2' => '*', + ], + ]); + + $this->createInstalledJson([ + self::getPackage('direct/dependent', '1.0.0'), + self::getPackage('direct/dependent2', '1.0.0'), + ]); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'show', '--direct' => true, 'package' => 'direct/dependent']); + $appTester->assertCommandIsSuccessful(); + self::assertStringContainsString('name : direct/dependent' . "\n", $appTester->getDisplay(true)); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'show', '--direct' => true, 'package' => 'direct/dependent2']); + $appTester->assertCommandIsSuccessful(); + self::assertStringContainsString('name : direct/dependent2' . "\n", $appTester->getDisplay(true)); + } + public function testShowPlatformOnlyShowsPlatformPackages(): void { $this->initTempComposer([ From b12a88b7f3313e9dbae5b58085323b8328d10296 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 22 Mar 2024 09:29:43 +0100 Subject: [PATCH 032/257] Fix call --- tests/Composer/Test/Command/ShowCommandTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index d726c5a9208d..a8f6a2500b5c 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -316,7 +316,7 @@ public function testShowDirectWithNameDoesNotShowTransientDependencies(): void self::getPackage('vendor/package', '1.0.0'), ]); - $this->configureLinks($direct, ['require' => ['vendor/package' => '*']]); + self::configureLinks($direct, ['require' => ['vendor/package' => '*']]); $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'show', '--direct' => true, 'package' => 'vendor/package']); From 94be5b5c14f1e0b535718291b3506d826cf3665a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 2 Apr 2024 17:38:41 +0200 Subject: [PATCH 033/257] Allow restricting allowed types as well, and allow configured ignored/allowed types in Installer class --- .../DependencyResolver/PoolBuilder.php | 22 ++++++++++++--- src/Composer/Installer.php | 28 +++++++++++++++++-- src/Composer/Repository/RepositorySet.php | 8 ++++-- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 82b89cb3a718..52aefa251cdb 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -108,6 +108,8 @@ class PoolBuilder private $skippedLoad = []; /** @var list */ private $ignoredTypes = []; + /** @var list|null */ + private $allowedTypes = null; /** * If provided, only these package names are loaded @@ -173,6 +175,8 @@ public function __construct(array $acceptableStabilities, array $stabilityFlags, } /** + * Packages of those types are ignored + * * @param list $types */ public function setIgnoredTypes(array $types): void @@ -180,6 +184,16 @@ public function setIgnoredTypes(array $types): void $this->ignoredTypes = $types; } + /** + * Only packages of those types are allowed if set to non-null + * + * @param list|null $types + */ + public function setAllowedTypes(?array $types): void + { + $this->allowedTypes = $types; + } + /** * @param RepositoryInterface[] $repositories */ @@ -416,6 +430,10 @@ private function loadPackagesMarkedForLoading(Request $request, array $repositor } foreach ($result['packages'] as $package) { $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package; + + if (in_array($package->getType(), $this->ignoredTypes, true) || ($this->allowedTypes !== null && !in_array($package->getType(), $this->allowedTypes, true))) { + continue; + } $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()])); } } @@ -426,10 +444,6 @@ private function loadPackagesMarkedForLoading(Request $request, array $repositor */ private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void { - if (in_array($package->getType(), $this->ignoredTypes, true)) { - return; - } - $index = $this->indexCounter++; $this->packages[$index] = $package; diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index fe50c8fdeda6..d1d943d36aa0 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -176,6 +176,10 @@ class Installer protected $errorOnAudit = false; /** @var Auditor::FORMAT_* */ protected $auditFormat = Auditor::FORMAT_SUMMARY; + /** @var list */ + private $ignoredTypes = ['php-ext', 'php-ext-zend']; + /** @var list|null */ + private $allowedTypes = null; /** @var bool */ protected $updateMirrors = false; @@ -492,7 +496,7 @@ protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doIns $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies); } - $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy)); + $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy), $this->ignoredTypes, $this->allowedTypes); $this->io->writeError('Updating dependencies'); @@ -750,7 +754,7 @@ protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alre $request->requireName($link->getTarget(), $link->getConstraint()); } - $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher); + $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes); // solve dependencies $solver = new Solver($policy, $pool, $this->io); @@ -1103,6 +1107,26 @@ public static function create(IOInterface $io, Composer $composer): self ); } + /** + * Packages of those types are ignored, by default php-ext and php-ext-zend are ignored + * + * @param list $types + */ + public function setIgnoredTypes(array $types): void + { + $this->ignoredTypes = $types; + } + + /** + * Only packages of those types are allowed if set to non-null + * + * @param list|null $types + */ + public function setAllowedTypes(?array $types): void + { + $this->allowedTypes = $types; + } + /** * @return $this */ diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 2a9a49b7de00..f6e8c7802ec7 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -310,11 +310,15 @@ public function isPackageAcceptable(array $names, string $stability): bool /** * Create a pool for dependency resolution from the packages in this repository set. + * + * @param list $ignoredTypes Packages of those types are ignored + * @param list|null $allowedTypes Only packages of those types are allowed if set to non-null */ - public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null): Pool + public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $ignoredTypes = [], ?array $allowedTypes = null): Pool { $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints); - $poolBuilder->setIgnoredTypes(['php-ext', 'php-ext-zend']); + $poolBuilder->setIgnoredTypes($ignoredTypes); + $poolBuilder->setAllowedTypes($allowedTypes); foreach ($this->repositories as $repo) { if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { From 9ced107af20f8646ab0fc6117a11f044f48ac5b2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 2 Apr 2024 17:39:00 +0200 Subject: [PATCH 034/257] Ensure extension packages in platform repo have php-ext type set --- src/Composer/Repository/PlatformRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 2f8f206e8ff6..61c70cf32d3b 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -661,6 +661,7 @@ private function addExtension(string $name, string $prettyVersion): void $packageName = $this->buildPackageName($name); $ext = new CompletePackage($packageName, $version, $prettyVersion); $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); + $ext->setType('php-ext'); if ($name === 'uuid') { $ext->setReplaces([ From dd18a5fe553472a45a4a583bb16ef68e497ef772 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 2 Apr 2024 17:40:35 +0200 Subject: [PATCH 035/257] Make methods chainable --- src/Composer/Installer.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index d1d943d36aa0..5d7e7fb5132d 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1111,20 +1111,26 @@ public static function create(IOInterface $io, Composer $composer): self * Packages of those types are ignored, by default php-ext and php-ext-zend are ignored * * @param list $types + * @return $this */ - public function setIgnoredTypes(array $types): void + public function setIgnoredTypes(array $types): self { $this->ignoredTypes = $types; + + return $this; } /** * Only packages of those types are allowed if set to non-null * * @param list|null $types + * @return $this */ - public function setAllowedTypes(?array $types): void + public function setAllowedTypes(?array $types): self { $this->allowedTypes = $types; + + return $this; } /** From f01ec4a98fe5b0516588dde6d76b4b3ac2f0d1cd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 3 Apr 2024 10:36:39 +0200 Subject: [PATCH 036/257] Ensure integer env vars do not cause a crash, fixes #11908 --- src/Composer/Factory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 03a5fa2ae713..0d455b7e3296 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -681,7 +681,7 @@ public static function createHttpDownloader(IOInterface $io, Config $config, arr private static function useXdg(): bool { foreach (array_keys($_SERVER) as $key) { - if (strpos($key, 'XDG_') === 0) { + if (strpos((string) $key, 'XDG_') === 0) { return true; } } From c779570bb72b6f92e434d05192c319253c8ab89c Mon Sep 17 00:00:00 2001 From: SvenRtbg Date: Wed, 3 Apr 2024 10:50:28 +0200 Subject: [PATCH 037/257] Update keywords documentation (#11905) --- doc/04-schema.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/04-schema.md b/doc/04-schema.md index c4dca5027d5b..bae038fc09a4 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -126,6 +126,11 @@ Examples: > `--dev` option to prompt users if they would like to add these packages to > `require-dev` instead of `require`. These are: `dev`, `testing`, `static analysis`. +> **Note**: The range of characters allowed inside the string is restricted to +> unicode letters or numbers, space `" "`, dot `.`, underscore `_` and dash `-`. (Regex: `'{^[\p{N}\p{L} ._-]+$}u'`) +> Using other characters will emit a warning when running `composer validate` and +> will cause the package to fail updating on Packagist.org. + Optional. ### homepage From a1a6185ca2193765ae484d8908a33754a0263fce Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 3 Apr 2024 10:52:44 +0200 Subject: [PATCH 038/257] Update deps --- composer.lock | 74 +++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/composer.lock b/composer.lock index 07277ef9e275..58cc3d257efe 100644 --- a/composer.lock +++ b/composer.lock @@ -458,16 +458,16 @@ }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255", + "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255", "shasum": "" }, "require": { @@ -478,7 +478,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -502,9 +502,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.4" }, "funding": [ { @@ -520,7 +520,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-03-26T18:29:49+00:00" }, { "name": "justinrainbow/json-schema", @@ -1037,16 +1037,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "80d075412b557d41002320b96a096ca65aa2c98d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d", "shasum": "" }, "require": { @@ -1084,7 +1084,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" }, "funding": [ { @@ -1100,20 +1100,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-01-24T14:02:46+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.35", + "version": "v5.4.38", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086" + "reference": "899330a01056077271e2f614c7b28b0379a671eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/5a553607d4ffbfa9c0ab62facadea296c9db7086", - "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/899330a01056077271e2f614c7b28b0379a671eb", + "reference": "899330a01056077271e2f614c7b28b0379a671eb", "shasum": "" }, "require": { @@ -1148,7 +1148,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.35" + "source": "https://github.com/symfony/filesystem/tree/v5.4.38" }, "funding": [ { @@ -1164,7 +1164,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-03-21T08:05:07+00:00" }, { "name": "symfony/finder", @@ -1843,16 +1843,16 @@ }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", "shasum": "" }, "require": { @@ -1906,7 +1906,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" }, "funding": [ { @@ -1922,7 +1922,7 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-04-21T15:04:16+00:00" }, { "name": "symfony/string", @@ -2014,16 +2014,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.63", + "version": "1.10.66", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ad12836d9ca227301f5fb9960979574ed8628339" + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ad12836d9ca227301f5fb9960979574ed8628339", - "reference": "ad12836d9ca227301f5fb9960979574ed8628339", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", "shasum": "" }, "require": { @@ -2072,7 +2072,7 @@ "type": "tidelift" } ], - "time": "2024-03-18T16:53:53+00:00" + "time": "2024-03-28T16:17:31+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2297,16 +2297,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v7.0.4", + "version": "v7.0.6", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f" + "reference": "a014167aa1f66cb9990675840da65609d3e61612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/54ca13ec990a40411ad978e08d994fca6cdd865f", - "reference": "54ca13ec990a40411ad978e08d994fca6cdd865f", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/a014167aa1f66cb9990675840da65609d3e61612", + "reference": "a014167aa1f66cb9990675840da65609d3e61612", "shasum": "" }, "require": { @@ -2358,7 +2358,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.4" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.6" }, "funding": [ { @@ -2374,7 +2374,7 @@ "type": "tidelift" } ], - "time": "2024-02-08T19:22:56+00:00" + "time": "2024-03-19T11:57:22+00:00" } ], "aliases": [], From 645cd58f979d877f7a30691ed17b8b0b5565e357 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 3 Apr 2024 11:02:06 +0200 Subject: [PATCH 039/257] Update baseline --- phpstan/baseline-8.1.neon | 5 ----- phpstan/baseline.neon | 5 ----- 2 files changed, 10 deletions(-) diff --git a/phpstan/baseline-8.1.neon b/phpstan/baseline-8.1.neon index 6807635f8c0f..2090b8e2b0c3 100644 --- a/phpstan/baseline-8.1.neon +++ b/phpstan/baseline-8.1.neon @@ -265,11 +265,6 @@ parameters: count: 1 path: ../src/Composer/Util/RemoteFilesystem.php - - - message: "#^Parameter \\#1 \\$include_path of function set_include_path expects string, string\\|false given\\.$#" - count: 2 - path: ../tests/Composer/Test/Autoload/AutoloadGeneratorTest.php - - message: "#^Parameter \\#1 \\$string of function rtrim expects string, string\\|false given\\.$#" count: 2 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index adbcd83ddcb3..64828b6226b3 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -4843,11 +4843,6 @@ parameters: count: 2 path: ../tests/Composer/Test/Autoload/AutoloadGeneratorTest.php - - - message: "#^Parameter \\#1 \\$new_include_path of function set_include_path expects string, string\\|false given\\.$#" - count: 2 - path: ../tests/Composer/Test/Autoload/AutoloadGeneratorTest.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 From c5ff69ed584449de3dbd01ecc0c145d80fa51fd2 Mon Sep 17 00:00:00 2001 From: Fabrizio Balliano Date: Wed, 3 Apr 2024 10:05:07 +0100 Subject: [PATCH 040/257] Added support for buy_me_a_coffee (#11902) --- src/Composer/Repository/Vcs/GitHubDriver.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 44766a185543..83c58887c0c0 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -286,6 +286,9 @@ private function getFundingInfo() case 'community_bridge': $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']); break; + case 'buy_me_a_coffee': + $result[$key]['url'] = 'https://www.buymeacoffee.com/' . basename($item['url']); + break; } } From b3cc3c4efc5a9c7a661a8e7bffc7352cf81a8703 Mon Sep 17 00:00:00 2001 From: findseat <166101790+findseat@users.noreply.github.com> Date: Sat, 13 Apr 2024 15:08:12 +0800 Subject: [PATCH 041/257] chore: fix some comments (#11922) Signed-off-by: findseat --- CHANGELOG.md | 2 +- UPGRADE-2.0.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1aa6d804f5b..f38b993afe3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1682,7 +1682,7 @@ * Break: Removed support for lock files created before 2012-09-15 due to their outdated unusable format * Added `prefer-stable` flag to pick stable packages over unstable ones when possible * Added `preferred-install` config option to always enable --prefer-source or --prefer-dist - * Added `diagnose` command to to system/network checks and find common problems + * Added `diagnose` command to system/network checks and find common problems * Added wildcard support in the update whitelist, e.g. to update all packages of a vendor do `composer update vendor/*` * Added `archive` command to archive the current directory or a given package * Added `run-script` command to manually trigger scripts diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 3e5878ea38b5..99c2876cf62f 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -100,7 +100,7 @@ If your repository only has a small number of packages, and you want to avoid th `"providers-api": "https://packagist.org/providers/%package%.json",` -The providers-api is optional, but if you implement it it should return packages which provide a given package name, but not the package which has that name. For example https://packagist.org/providers/monolog/monolog.json lists some package which have a "provide" rule for monolog/monolog, but it does not list monolog/monolog itself. +The providers-api is optional, but if you implement it, it should return packages which provide a given package name, but not the package which has that name. For example https://packagist.org/providers/monolog/monolog.json lists some package which have a "provide" rule for monolog/monolog, but it does not list monolog/monolog itself. ### list From 43955765fd417eeb05eabff6592722ac7ce74341 Mon Sep 17 00:00:00 2001 From: William David Edwards Date: Mon, 15 Apr 2024 09:45:27 +0200 Subject: [PATCH 042/257] Link to README instead of duplicating install command. These are currently out of sync. This change fixes that, and prevent this from happening again. Relates to https://github.com/composer/satis/issues/825 (#11931) --- doc/articles/handling-private-packages.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/articles/handling-private-packages.md b/doc/articles/handling-private-packages.md index 9548809f96a5..eb71b75ce03f 100644 --- a/doc/articles/handling-private-packages.md +++ b/doc/articles/handling-private-packages.md @@ -25,10 +25,8 @@ set up your own package archive on [Packagist.com](https://packagist.com). Satis on the other hand is open source but only a static `composer` repository generator. It is a bit like an ultra-lightweight, static file-based version of packagist and can be used to host the metadata of your company's private -packages, or your own. You can get it from -[GitHub](https://github.com/composer/satis) or install via CLI: - - php composer.phar create-project composer/satis --stability=dev --keep-vcs +packages, or your own. You can install it using [Composer](https://github.com/composer/satis?tab=readme-ov-file#run-from-source) +or [Docker](https://github.com/composer/satis?tab=readme-ov-file#run-as-docker-container). ## Setup From 89f057e0df965020dcac4102a1a0a099eab66707 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 15 Apr 2024 11:49:04 +0200 Subject: [PATCH 043/257] Ensure we clear the locally configured cache dir instead of default one, fixes #11921 --- src/Composer/Command/ClearCacheCommand.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index 028aa4212639..77ed517a801c 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -45,7 +45,13 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $config = Factory::createConfig(); + $composer = $this->tryComposer(); + if ($composer !== null) { + $config = $composer->getConfig(); + } else { + $config = Factory::createConfig(); + } + $io = $this->getIO(); $cachePaths = [ From 92f641ac3db7aa7f46296f0b0c6b14dc21855828 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 15 Apr 2024 13:23:25 +0200 Subject: [PATCH 044/257] Fix show command output to remove v prefixes on versions, making for more uniform output, fixes #11925 --- src/Composer/Command/ShowCommand.php | 10 ++++++++-- tests/Composer/Test/Command/ShowCommandTest.php | 16 ++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 92a6e621b3b8..a1e7eb059fdc 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -535,10 +535,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null; $packageViewData['source'] = PackageInfo::getViewSourceUrl($package); } - $nameLength = max($nameLength, strlen($package->getPrettyName())); + $nameLength = max($nameLength, strlen($packageViewData['name'])); if ($writeVersion) { $packageViewData['version'] = $package->getFullPrettyVersion(); - $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); + if ($format === 'text') { + $packageViewData['version'] = ltrim($packageViewData['version'], 'v'); + } + $versionLength = max($versionLength, strlen($packageViewData['version'])); } if ($writeReleaseDate) { if ($package->getReleaseDate() !== null) { @@ -553,6 +556,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($writeLatest && $latestPackage) { $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); + if ($format === 'text') { + $packageViewData['latest'] = ltrim($packageViewData['latest'], 'v'); + } $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); $latestLength = max($latestLength, strlen($packageViewData['latest'])); } elseif ($writeLatest) { diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index a8f6a2500b5c..f807d188e8b6 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -36,13 +36,13 @@ public function testShow(array $command, string $expected, array $requires = []) 'packages' => [ 'type' => 'package', 'package' => [ - ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.0.0'], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => 'v1.0.0'], - ['name' => 'outdated/major', 'description' => 'outdated/major v1.0.0 description', 'version' => '1.0.0'], - ['name' => 'outdated/major', 'description' => 'outdated/major v1.0.1 description', 'version' => '1.0.1'], - ['name' => 'outdated/major', 'description' => 'outdated/major v1.1.0 description', 'version' => '1.1.0'], - ['name' => 'outdated/major', 'description' => 'outdated/major v1.1.1 description', 'version' => '1.1.1'], - ['name' => 'outdated/major', 'description' => 'outdated/major v2.0.0 description', 'version' => '2.0.0'], + ['name' => 'outdated/major', 'description' => 'outdated/major v1.0.0 description', 'version' => 'v1.0.0'], + ['name' => 'outdated/major', 'description' => 'outdated/major v1.0.1 description', 'version' => 'v1.0.1'], + ['name' => 'outdated/major', 'description' => 'outdated/major v1.1.0 description', 'version' => 'v1.1.0'], + ['name' => 'outdated/major', 'description' => 'outdated/major v1.1.1 description', 'version' => 'v1.1.1'], + ['name' => 'outdated/major', 'description' => 'outdated/major v2.0.0 description', 'version' => 'v2.0.0'], ['name' => 'outdated/minor', 'description' => 'outdated/minor v1.0.0 description', 'version' => '1.0.0'], ['name' => 'outdated/minor', 'description' => 'outdated/minor v1.0.1 description', 'version' => '1.0.1'], @@ -57,9 +57,9 @@ public function testShow(array $command, string $expected, array $requires = []) 'require' => $requires === [] ? new \stdClass : $requires, ]); - $pkg = self::getPackage('vendor/package', '1.0.0'); + $pkg = self::getPackage('vendor/package', 'v1.0.0'); $pkg->setDescription('description of installed package'); - $major = self::getPackage('outdated/major', '1.0.0'); + $major = self::getPackage('outdated/major', 'v1.0.0'); $major->setReleaseDate(new DateTimeImmutable()); $minor = self::getPackage('outdated/minor', '1.0.0'); $minor->setReleaseDate(new DateTimeImmutable('-2 years')); From 3cc490d4c413ce37376b3ff0f0a73c86749b678d Mon Sep 17 00:00:00 2001 From: John Stevenson Date: Wed, 17 Apr 2024 13:34:26 +0100 Subject: [PATCH 045/257] Refactor proxy handling to require https_proxy (#11915) Composer has always allowed a single http_proxy (or CGI_HTTP_PROXY) environment variable to be used for both HTTP and HTTPS requests. But many other tools and libraries require scheme-specific values. The landscape is already complicated by the use of and need for upper and lower case values, so to bring matters inline with current practice https_proxy is now required for HTTPS requests. The new proxy handler incorporates a transition mechanism, which allows http_proxy to be used for all requests when https_proxy is not set and provides a `needsTransitionWarning` method for the main application. Moving to scheme-specific environment variables means that a user may set a single proxy for either HTTP or HTTPS requests. To accomodate this situation during the transition period, an https_proxy value can be set to an empty string which will prevent http_proxy being used for HTTPS requests. --- phpstan/baseline.neon | 93 +------- src/Composer/Console/Application.php | 8 + src/Composer/Util/Http/CurlDownloader.php | 33 +-- src/Composer/Util/Http/ProxyHelper.php | 183 ---------------- src/Composer/Util/Http/ProxyItem.php | 119 +++++++++++ src/Composer/Util/Http/ProxyManager.php | 185 +++++++++------- src/Composer/Util/Http/RequestProxy.php | 139 +++++++++--- src/Composer/Util/RemoteFilesystem.php | 7 +- src/Composer/Util/StreamContextFactory.php | 3 +- .../Test/Util/Http/ProxyHelperTest.php | 201 ------------------ .../Composer/Test/Util/Http/ProxyItemTest.php | 72 +++++++ .../Test/Util/Http/ProxyManagerTest.php | 191 +++++++++++------ .../Test/Util/Http/RequestProxyTest.php | 157 ++++++++++++-- 13 files changed, 687 insertions(+), 704 deletions(-) delete mode 100644 src/Composer/Util/Http/ProxyHelper.php create mode 100644 src/Composer/Util/Http/ProxyItem.php delete mode 100644 tests/Composer/Test/Util/Http/ProxyHelperTest.php create mode 100644 tests/Composer/Test/Util/Http/ProxyItemTest.php diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 64828b6226b3..801dd2336d9c 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -4008,16 +4008,6 @@ parameters: count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - - message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Constant CURLOPT_PROXY_CAPATH not found\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -4199,72 +4189,17 @@ parameters: path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyHelper.php - - - - message: "#^Foreach overwrites \\$name with its value variable\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyHelper.php - - - - message: "#^Implicit array creation is not allowed \\- variable \\$options does not exist\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyHelper.php - - - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, 65535\\>\\|false\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyHelper.php - - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 3 - path: ../src/Composer/Util/Http/ProxyHelper.php - - - - message: "#^Parameter \\#1 \\$proxy of static method Composer\\\\Util\\\\Http\\\\ProxyHelper\\:\\:formatParsedUrl\\(\\) expects array\\{scheme\\?\\: string, host\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string\\}, array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyHelper.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\Util\\\\NoProxyPattern\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyManager.php - - - - message: "#^Only booleans are allowed in &&, string\\|null given on the right side\\.$#" + message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy::getCurlOptions\\(\\) should return array\\ but returns array\\.$#" count: 1 - path: ../src/Composer/Util/Http/ProxyManager.php - - - - message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Util\\\\Http\\\\ProxyManager\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyManager.php - - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 4 - path: ../src/Composer/Util/Http/ProxyManager.php - - - - message: "#^Parameter \\#3 \\$formattedUrl of class Composer\\\\Util\\\\Http\\\\RequestProxy constructor expects string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyManager.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: ../src/Composer/Util/Http/ProxyManager.php + path: ../src/Composer/Util/Http/RequestProxy.php - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" + message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#" count: 1 path: ../src/Composer/Util/Http/RequestProxy.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + message: "#^Constant CURLOPT_PROXY_CAPATH not found\\.$#" count: 1 path: ../src/Composer/Util/Http/RequestProxy.php @@ -5281,26 +5216,6 @@ parameters: count: 1 path: ../tests/Composer/Test/Util/GitTest.php - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'Composer\\\\\\\\Util\\\\\\\\Http…' and Composer\\\\Util\\\\Http\\\\ProxyManager will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/Http/ProxyManagerTest.php - - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'Composer\\\\\\\\Util\\\\\\\\Http…' and Composer\\\\Util\\\\Http\\\\RequestProxy will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/Http/ProxyManagerTest.php - - - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/Http/ProxyManagerTest.php - - - - message: "#^Parameter \\#1 \\$haystack of function stripos expects string, string\\|null given\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/Http/ProxyManagerTest.php - - message: "#^Only booleans are allowed in an if condition, string\\|false\\|null given\\.$#" count: 1 diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index fa7855331bf6..d045bdf88ecb 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -41,6 +41,7 @@ use Composer\Exception\NoSslException; use Composer\XdebugHandler\XdebugHandler; use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Composer\Util\Http\ProxyManager; /** * The console application that handles the commands @@ -398,6 +399,13 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } + if (ProxyManager::getInstance()->needsTransitionWarning()) { + $io->writeError(''); + $io->writeError('Composer now requires separate proxy environment variables for HTTP and HTTPS requests.'); + $io->writeError('Please set `https_proxy` in addition to your existing proxy environment variables.'); + $io->writeError('This fallback (and warning) will be removed in Composer 2.8.0.'); + } + return $result; } catch (ScriptExecutionException $e) { if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index e512f082422c..4dd554065c72 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -52,10 +52,6 @@ class CurlDownloader private $maxRedirects = 20; /** @var int */ private $maxRetries = 3; - /** @var ProxyManager */ - private $proxyManager; - /** @var bool */ - private $supportsSecureProxy; /** @var array */ protected $multiErrors = [ CURLM_BAD_HANDLE => ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'], @@ -117,11 +113,6 @@ public function __construct(IOInterface $io, Config $config, array $options = [] } $this->authHelper = new AuthHelper($io, $config); - $this->proxyManager = ProxyManager::getInstance(); - - $version = curl_version(); - $features = $version['features']; - $this->supportsSecureProxy = defined('CURL_VERSION_HTTPS_PROXY') && ($features & CURL_VERSION_HTTPS_PROXY); } /** @@ -245,26 +236,8 @@ private function initDownload(callable $resolve, callable $reject, string $origi } } - // Always set CURLOPT_PROXY to enable/disable proxy handling - // Any proxy authorization is included in the proxy url - $proxy = $this->proxyManager->getProxyForRequest($url); - if ($proxy->getUrl() !== '') { - curl_setopt($curlHandle, CURLOPT_PROXY, $proxy->getUrl()); - } - - // Curl needs certificate locations for secure proxies. - // CURLOPT_PROXY_SSL_VERIFY_PEER/HOST are enabled by default - if ($proxy->isSecure()) { - if (!$this->supportsSecureProxy) { - throw new TransportException('Connecting to a secure proxy using curl is not supported on PHP versions below 7.3.0.'); - } - if (!empty($options['ssl']['cafile'])) { - curl_setopt($curlHandle, CURLOPT_PROXY_CAINFO, $options['ssl']['cafile']); - } - if (!empty($options['ssl']['capath'])) { - curl_setopt($curlHandle, CURLOPT_PROXY_CAPATH, $options['ssl']['capath']); - } - } + $proxy = ProxyManager::getInstance()->getProxyForRequest($url); + curl_setopt_array($curlHandle, $proxy->getCurlOptions($options['ssl'] ?? [])); $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); @@ -283,7 +256,7 @@ private function initDownload(callable $resolve, callable $reject, string $origi 'primaryIp' => '', ]; - $usingProxy = $proxy->getFormattedUrl(' using proxy (%s)'); + $usingProxy = $proxy->getStatus(' using proxy (%s)'); $ifModified = false !== stripos(implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : ''; if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) { $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG); diff --git a/src/Composer/Util/Http/ProxyHelper.php b/src/Composer/Util/Http/ProxyHelper.php deleted file mode 100644 index f2d864f29ded..000000000000 --- a/src/Composer/Util/Http/ProxyHelper.php +++ /dev/null @@ -1,183 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Util\Http; - -/** - * Proxy discovery and helper class - * - * @internal - * @author John Stevenson - */ -class ProxyHelper -{ - /** - * Returns proxy environment values - * - * @return array{string|null, string|null, string|null} httpProxy, httpsProxy, noProxy values - * - * @throws \RuntimeException on malformed url - */ - public static function getProxyData(): array - { - $httpProxy = null; - $httpsProxy = null; - - // Handle http_proxy/HTTP_PROXY on CLI only for security reasons - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - if ($env = self::getProxyEnv(['http_proxy', 'HTTP_PROXY'], $name)) { - $httpProxy = self::checkProxy($env, $name); - } - } - - // Prefer CGI_HTTP_PROXY if available - if ($env = self::getProxyEnv(['CGI_HTTP_PROXY'], $name)) { - $httpProxy = self::checkProxy($env, $name); - } - - // Handle https_proxy/HTTPS_PROXY - if ($env = self::getProxyEnv(['https_proxy', 'HTTPS_PROXY'], $name)) { - $httpsProxy = self::checkProxy($env, $name); - } else { - $httpsProxy = $httpProxy; - } - - // Handle no_proxy - $noProxy = self::getProxyEnv(['no_proxy', 'NO_PROXY'], $name); - - return [$httpProxy, $httpsProxy, $noProxy]; - } - - /** - * Returns http context options for the proxy url - * - * @return array{http: array{proxy: string, header?: string}} - */ - public static function getContextOptions(string $proxyUrl): array - { - $proxy = parse_url($proxyUrl); - - // Remove any authorization - $proxyUrl = self::formatParsedUrl($proxy, false); - $proxyUrl = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $proxyUrl); - - $options['http']['proxy'] = $proxyUrl; - - // Handle any authorization - if (isset($proxy['user'])) { - $auth = rawurldecode($proxy['user']); - - if (isset($proxy['pass'])) { - $auth .= ':' . rawurldecode($proxy['pass']); - } - $auth = base64_encode($auth); - // Set header as a string - $options['http']['header'] = "Proxy-Authorization: Basic {$auth}"; - } - - return $options; - } - - /** - * Sets/unsets request_fulluri value in http context options array - * - * @param mixed[] $options Set by method - */ - public static function setRequestFullUri(string $requestUrl, array &$options): void - { - if ('http' === parse_url($requestUrl, PHP_URL_SCHEME)) { - $options['http']['request_fulluri'] = true; - } else { - unset($options['http']['request_fulluri']); - } - } - - /** - * Searches $_SERVER for case-sensitive values - * - * @param non-empty-list $names Names to search for - * @param string|null $name Name of any found value, you should only rely on it if the function returned a non-null value - * - * @return string|null The found value - * - * @param-out string $name - */ - private static function getProxyEnv(array $names, ?string &$name): ?string - { - foreach ($names as $name) { - if (!empty($_SERVER[$name])) { - return $_SERVER[$name]; - } - } - - return null; - } - - /** - * Checks and formats a proxy url from the environment - * - * @throws \RuntimeException on malformed url - * @return string The formatted proxy url - */ - private static function checkProxy(string $proxyUrl, string $envName): string - { - $error = sprintf('malformed %s url', $envName); - $proxy = parse_url($proxyUrl); - - // We need parse_url to have identified a host - if (!isset($proxy['host'])) { - throw new \RuntimeException($error); - } - - $proxyUrl = self::formatParsedUrl($proxy, true); - - // We need a port because streams and curl use different defaults - if (!parse_url($proxyUrl, PHP_URL_PORT)) { - throw new \RuntimeException($error); - } - - return $proxyUrl; - } - - /** - * Formats a url from its component parts - * - * @param array{scheme?: string, host: string, port?: int, user?: string, pass?: string} $proxy - * - * @return string The formatted value - */ - private static function formatParsedUrl(array $proxy, bool $includeAuth): string - { - $proxyUrl = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' : ''; - - if ($includeAuth && isset($proxy['user'])) { - $proxyUrl .= $proxy['user']; - - if (isset($proxy['pass'])) { - $proxyUrl .= ':' . $proxy['pass']; - } - $proxyUrl .= '@'; - } - - $proxyUrl .= $proxy['host']; - - if (isset($proxy['port'])) { - $proxyUrl .= ':' . $proxy['port']; - } elseif (strpos($proxyUrl, 'http://') === 0) { - $proxyUrl .= ':80'; - } elseif (strpos($proxyUrl, 'https://') === 0) { - $proxyUrl .= ':443'; - } - - return $proxyUrl; - } -} diff --git a/src/Composer/Util/Http/ProxyItem.php b/src/Composer/Util/Http/ProxyItem.php new file mode 100644 index 000000000000..2839be923e79 --- /dev/null +++ b/src/Composer/Util/Http/ProxyItem.php @@ -0,0 +1,119 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +/** + * @internal + * @author John Stevenson + */ +class ProxyItem +{ + /** @var non-empty-string */ + private $url; + /** @var non-empty-string */ + private $safeUrl; + /** @var ?non-empty-string */ + private $curlAuth; + /** @var string */ + private $optionsProxy; + /** @var ?non-empty-string */ + private $optionsAuth; + + /** + * @param string $proxyUrl The value from the environment + * @param string $envName The name of the environment variable + * @throws \RuntimeException If the proxy url is invalid + */ + public function __construct(string $proxyUrl, string $envName) + { + $syntaxError = sprintf('unsupported `%s` syntax', $envName); + + if (strpbrk($proxyUrl, "\r\n\t") !== false) { + throw new \RuntimeException($syntaxError); + } + if (false === ($proxy = parse_url($proxyUrl))) { + throw new \RuntimeException($syntaxError); + } + if (!isset($proxy['host'])) { + throw new \RuntimeException('unable to find proxy host in ' . $envName); + } + + $scheme = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' : 'http://'; + $safe = ''; + + if (isset($proxy['user'])) { + $safe = '***'; + $user = $proxy['user']; + $auth = rawurldecode($proxy['user']); + + if (isset($proxy['pass'])) { + $safe .= ':***'; + $user .= ':' . $proxy['pass']; + $auth .= ':' . rawurldecode($proxy['pass']); + } + + $safe .= '@'; + + if (strlen($user) > 0) { + $this->curlAuth = $user; + $this->optionsAuth = 'Proxy-Authorization: Basic ' . base64_encode($auth); + } + } + + $host = $proxy['host']; + $port = null; + + if (isset($proxy['port'])) { + $port = $proxy['port']; + } elseif ($scheme === 'http://') { + $port = 80; + } elseif ($scheme === 'https://') { + $port = 443; + } + + // We need a port because curl uses 1080 for http. Port 0 is reserved, + // but is considered valid depending on the PHP or Curl version. + if ($port === null) { + throw new \RuntimeException('unable to find proxy port in ' . $envName); + } + if ($port === 0) { + throw new \RuntimeException('port 0 is reserved in ' . $envName); + } + + $this->url = sprintf('%s%s:%d', $scheme, $host, $port); + $this->safeUrl = sprintf('%s%s%s:%d', $scheme, $safe, $host, $port); + + $scheme = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $scheme); + $this->optionsProxy = sprintf('%s%s:%d', $scheme, $host, $port); + } + + /** + * Returns a RequestProxy instance for the scheme of the request url + * + * @param string $scheme The scheme of the request url + */ + public function toRequestProxy(string $scheme): RequestProxy + { + $options = ['http' => ['proxy' => $this->optionsProxy]]; + + if ($this->optionsAuth !== null) { + $options['http']['header'] = $this->optionsAuth; + } + + if ($scheme === 'http') { + $options['http']['request_fulluri'] = true; + } + + return new RequestProxy($this->url, $this->curlAuth, $options, $this->safeUrl); + } +} diff --git a/src/Composer/Util/Http/ProxyManager.php b/src/Composer/Util/Http/ProxyManager.php index 731638ab80b1..0571780fe053 100644 --- a/src/Composer/Util/Http/ProxyManager.php +++ b/src/Composer/Util/Http/ProxyManager.php @@ -14,7 +14,6 @@ use Composer\Downloader\TransportException; use Composer\Util\NoProxyPattern; -use Composer\Util\Url; /** * @internal @@ -24,40 +23,40 @@ class ProxyManager { /** @var ?string */ private $error = null; - /** @var array{http: ?string, https: ?string} */ - private $fullProxy; - /** @var array{http: ?string, https: ?string} */ - private $safeProxy; - /** @var array{http: array{options: mixed[]|null}, https: array{options: mixed[]|null}} */ - private $streams; - /** @var bool */ - private $hasProxy; - /** @var ?string */ - private $info = null; + /** @var ?ProxyItem */ + private $httpProxy = null; + /** @var ?ProxyItem */ + private $httpsProxy = null; /** @var ?NoProxyPattern */ private $noProxyHandler = null; - /** @var ?ProxyManager */ + /** @var ?self */ private static $instance = null; + /** The following 3 properties can be removed after the transition period */ + + /** @var bool */ + private $ignoreHttpsProxy = false; + /** @var bool */ + private $isTransitional = false; + /** @var bool */ + private $needsTransitionWarning = false; + private function __construct() { - $this->fullProxy = $this->safeProxy = [ - 'http' => null, - 'https' => null, - ]; - - $this->streams['http'] = $this->streams['https'] = [ - 'options' => null, - ]; + // this can be removed after the transition period + $this->isTransitional = true; - $this->hasProxy = false; - $this->initProxyData(); + try { + $this->getProxyData(); + } catch (\RuntimeException $e) { + $this->error = $e->getMessage(); + } } public static function getInstance(): ProxyManager { - if (!self::$instance) { + if (self::$instance === null) { self::$instance = new self(); } @@ -79,96 +78,116 @@ public static function reset(): void */ public function getProxyForRequest(string $requestUrl): RequestProxy { - if ($this->error) { + if ($this->error !== null) { throw new TransportException('Unable to use a proxy: '.$this->error); } - $scheme = parse_url($requestUrl, PHP_URL_SCHEME) ?: 'http'; - $proxyUrl = ''; - $options = []; - $formattedProxyUrl = ''; - - if ($this->hasProxy && in_array($scheme, ['http', 'https'], true) && $this->fullProxy[$scheme]) { - if ($this->noProxy($requestUrl)) { - $formattedProxyUrl = 'excluded by no_proxy'; - } else { - $proxyUrl = $this->fullProxy[$scheme]; - $options = $this->streams[$scheme]['options']; - assert(is_array($options)); - ProxyHelper::setRequestFullUri($requestUrl, $options); - $formattedProxyUrl = $this->safeProxy[$scheme]; - } + $scheme = (string) parse_url($requestUrl, PHP_URL_SCHEME); + $proxy = $this->getProxyForScheme($scheme); + + if ($proxy === null) { + return RequestProxy::none(); + } + + if ($this->noProxy($requestUrl)) { + return RequestProxy::noProxy(); } - return new RequestProxy($proxyUrl, $options, $formattedProxyUrl); + return $proxy->toRequestProxy($scheme); } /** - * Returns true if a proxy is being used + * Returns true if the user needs to set an https_proxy environment variable * - * @return bool If false any error will be in $message + * This method can be removed after the transition period */ - public function isProxying(): bool + public function needsTransitionWarning(): bool { - return $this->hasProxy; + return $this->needsTransitionWarning; } /** - * Returns proxy configuration info which can be shown to the user - * - * @return string|null Safe proxy URL or an error message if setting up proxy failed or null if no proxy was configured + * Returns a ProxyItem if one is set for the scheme, otherwise null */ - public function getFormattedProxy(): ?string + private function getProxyForScheme(string $scheme): ?ProxyItem { - return $this->hasProxy ? $this->info : $this->error; + if ($scheme === 'http') { + return $this->httpProxy; + } + + if ($scheme === 'https') { + // this can be removed after the transition period + if ($this->isTransitional && $this->httpsProxy === null) { + if ($this->httpProxy !== null && !$this->ignoreHttpsProxy) { + $this->needsTransitionWarning = true; + + return $this->httpProxy; + } + } + + return $this->httpsProxy; + } + + return null; } /** - * Initializes proxy values from the environment + * Finds proxy values from the environment and sets class properties */ - private function initProxyData(): void + private function getProxyData(): void { - try { - [$httpProxy, $httpsProxy, $noProxy] = ProxyHelper::getProxyData(); - } catch (\RuntimeException $e) { - $this->error = $e->getMessage(); - - return; + // Handle http_proxy/HTTP_PROXY on CLI only for security reasons + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + [$env, $name] = $this->getProxyEnv('http_proxy'); + if ($env !== null) { + $this->httpProxy = new ProxyItem($env, $name); + } } - $info = []; - - if ($httpProxy) { - $info[] = $this->setData($httpProxy, 'http'); + // Handle cgi_http_proxy/CGI_HTTP_PROXY if needed + if ($this->httpProxy === null) { + [$env, $name] = $this->getProxyEnv('cgi_http_proxy'); + if ($env !== null) { + $this->httpProxy = new ProxyItem($env, $name); + } } - if ($httpsProxy) { - $info[] = $this->setData($httpsProxy, 'https'); + + // Handle https_proxy/HTTPS_PROXY + [$env, $name] = $this->getProxyEnv('https_proxy'); + if ($env !== null) { + $this->httpsProxy = new ProxyItem($env, $name); } - if ($this->hasProxy) { - $this->info = implode(', ', $info); - if ($noProxy) { - $this->noProxyHandler = new NoProxyPattern($noProxy); - } + + // Handle no_proxy/NO_PROXY + [$env, $name] = $this->getProxyEnv('no_proxy'); + if ($env !== null) { + $this->noProxyHandler = new NoProxyPattern($env); } } /** - * Sets initial data - * - * @param non-empty-string $url Proxy url - * @param 'http'|'https' $scheme Environment variable scheme + * Searches $_SERVER for case-sensitive values * - * @return non-empty-string + * @return array{0: string|null, 1: string} value, name */ - private function setData($url, $scheme): string + private function getProxyEnv(string $envName): array { - $safeProxy = Url::sanitize($url); - $this->fullProxy[$scheme] = $url; - $this->safeProxy[$scheme] = $safeProxy; - $this->streams[$scheme]['options'] = ProxyHelper::getContextOptions($url); - $this->hasProxy = true; + $names = [strtolower($envName), strtoupper($envName)]; + + foreach ($names as $name) { + if (is_string($_SERVER[$name] ?? null)) { + if ($_SERVER[$name] !== '') { + return [$_SERVER[$name], $name]; + } + // this can be removed after the transition period + if ($this->isTransitional && strtolower($name) === 'https_proxy') { + $this->ignoreHttpsProxy = true; + break; + } + } + } - return sprintf('%s=%s', $scheme, $safeProxy); + return [null, '']; } /** @@ -176,6 +195,10 @@ private function setData($url, $scheme): string */ private function noProxy(string $requestUrl): bool { - return $this->noProxyHandler && $this->noProxyHandler->test($requestUrl); + if ($this->noProxyHandler === null) { + return false; + } + + return $this->noProxyHandler->test($requestUrl); } } diff --git a/src/Composer/Util/Http/RequestProxy.php b/src/Composer/Util/Http/RequestProxy.php index c80c8799ea4f..d9df68861f03 100644 --- a/src/Composer/Util/Http/RequestProxy.php +++ b/src/Composer/Util/Http/RequestProxy.php @@ -12,78 +12,157 @@ namespace Composer\Util\Http; -use Composer\Util\Url; +use Composer\Downloader\TransportException; /** * @internal * @author John Stevenson + * + * @phpstan-type contextOptions array{http: array{proxy: string, header?: string, request_fulluri?: bool}} */ class RequestProxy { - /** @var mixed[] */ + /** @var ?contextOptions */ private $contextOptions; - /** @var bool */ - private $isSecure; - /** @var string */ - private $formattedUrl; - /** @var string */ + /** @var ?non-empty-string */ + private $status; + /** @var ?non-empty-string */ private $url; + /** @var ?non-empty-string */ + private $auth; /** - * @param mixed[] $contextOptions + * @param ?non-empty-string $url The proxy url, without authorization + * @param ?non-empty-string $auth Authorization for curl + * @param ?contextOptions $contextOptions + * @param ?non-empty-string $status */ - public function __construct(string $url, array $contextOptions, string $formattedUrl) + public function __construct(?string $url, ?string $auth, ?array $contextOptions, ?string $status) { $this->url = $url; + $this->auth = $auth; $this->contextOptions = $contextOptions; - $this->formattedUrl = $formattedUrl; - $this->isSecure = 0 === strpos($url, 'https://'); + $this->status = $status; + } + + public static function none(): RequestProxy + { + return new self(null, null, null, null); + } + + public static function noProxy(): RequestProxy + { + return new self(null, null, null, 'excluded by no_proxy'); } /** - * Returns an array of context options + * Returns the context options to use for this request, otherwise null * - * @return mixed[] + * @return ?contextOptions */ - public function getContextOptions(): array + public function getContextOptions(): ?array { return $this->contextOptions; } /** - * Returns the safe proxy url from the last request + * Returns an array of curl proxy options + * + * @param array $sslOptions + * @return array + */ + public function getCurlOptions(array $sslOptions): array + { + if ($this->isSecure() && !$this->supportsSecureProxy()) { + throw new TransportException('Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.'); + } + + // Always set a proxy url, even an empty value, because it tells curl + // to ignore proxy environment variables + $options = [CURLOPT_PROXY => (string) $this->url]; + + // If using a proxy, tell curl to ignore no_proxy environment variables + if ($this->url !== null) { + $options[CURLOPT_NOPROXY] = ''; + } + + // Set any authorization + if ($this->auth !== null) { + $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC; + $options[CURLOPT_PROXYUSERPWD] = $this->auth; + } + + if ($this->isSecure()) { + if (isset($sslOptions['cafile'])) { + $options[CURLOPT_PROXY_CAINFO] = $sslOptions['cafile']; + } + if (isset($sslOptions['capath'])) { + $options[CURLOPT_PROXY_CAPATH] = $sslOptions['capath']; + } + } + + return $options; + } + + /** + * Returns proxy info associated with this request * - * @param string|null $format Output format specifier - * @return string Safe proxy, no proxy or empty + * An empty return value means that the user has not set a proxy. + * A non-empty value will either be the sanitized proxy url if a proxy is + * required, or a message indicating that a no_proxy value has disabled the + * proxy. + * + * @param ?string $format Output format specifier */ - public function getFormattedUrl(?string $format = ''): string + public function getStatus(?string $format = null): string { - $result = ''; - if ($this->formattedUrl) { - $format = $format ?: '%s'; - $result = sprintf($format, $this->formattedUrl); + if ($this->status === null) { + return ''; + } + + $format = $format ?? '%s'; + if (strpos($format, '%s') !== false) { + return sprintf($format, $this->status); } - return $result; + throw new \InvalidArgumentException('String format specifier is missing'); } /** - * Returns the proxy url + * Returns true if the request url has been excluded by a no_proxy value * - * @return string Proxy url or empty + * A false value can also mean that the user has not set a proxy. */ - public function getUrl(): string + public function isExcludedByNoProxy(): bool { - return $this->url; + return $this->status !== null && $this->url === null; } /** - * Returns true if this is a secure-proxy + * Returns true if this is a secure (HTTPS) proxy * - * @return bool False if not secure or there is no proxy + * A false value means that this is either an HTTP proxy, or that a proxy + * is not required for this request, or that the user has not set a proxy. */ public function isSecure(): bool { - return $this->isSecure; + return 0 === strpos((string) $this->url, 'https://'); + } + + /** + * Returns true if an HTTPS proxy can be used. + * + * This depends on PHP7.3+ for CURL_VERSION_HTTPS_PROXY + * and curl including the feature (from version 7.52.0) + */ + public function supportsSecureProxy(): bool + { + if (false === ($version = curl_version()) || !defined('CURL_VERSION_HTTPS_PROXY')) { + return false; + } + + $features = $version['features']; + + return (bool) ($features & CURL_VERSION_HTTPS_PROXY); } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 6fa3437a3f0e..560ef35db6c5 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -64,8 +64,6 @@ class RemoteFilesystem private $redirects; /** @var int */ private $maxRedirects = 20; - /** @var ProxyManager */ - private $proxyManager; /** * Constructor. @@ -91,7 +89,6 @@ public function __construct(IOInterface $io, Config $config, array $options = [] $this->options = array_replace_recursive($this->options, $options); $this->config = $config; $this->authHelper = $authHelper ?? new AuthHelper($io, $config); - $this->proxyManager = ProxyManager::getInstance(); } /** @@ -276,8 +273,8 @@ protected function get(string $originUrl, string $fileUrl, array $additionalOpti $ctx = StreamContextFactory::getContext($fileUrl, $options, ['notification' => [$this, 'callbackGet']]); - $proxy = $this->proxyManager->getProxyForRequest($fileUrl); - $usingProxy = $proxy->getFormattedUrl(' using proxy (%s)'); + $proxy = ProxyManager::getInstance()->getProxyForRequest($fileUrl); + $usingProxy = $proxy->getStatus(' using proxy (%s)'); $this->io->writeError((strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG); unset($origFileUrl, $proxy, $usingProxy); diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 57fbe0f0e7af..be4c976a9055 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -76,7 +76,8 @@ public static function initOptions(string $url, array $options, bool $forCurl = // Add stream proxy options if there is a proxy if (!$forCurl) { $proxy = ProxyManager::getInstance()->getProxyForRequest($url); - if ($proxyOptions = $proxy->getContextOptions()) { + $proxyOptions = $proxy->getContextOptions(); + if ($proxyOptions !== null) { $isHttpsRequest = 0 === strpos($url, 'https://'); if ($proxy->isSecure()) { diff --git a/tests/Composer/Test/Util/Http/ProxyHelperTest.php b/tests/Composer/Test/Util/Http/ProxyHelperTest.php deleted file mode 100644 index 8b9dfb1b106c..000000000000 --- a/tests/Composer/Test/Util/Http/ProxyHelperTest.php +++ /dev/null @@ -1,201 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Util\Http; - -use Composer\Util\Http\ProxyHelper; -use Composer\Test\TestCase; - -class ProxyHelperTest extends TestCase -{ - protected function setUp(): void - { - unset( - $_SERVER['HTTP_PROXY'], - $_SERVER['http_proxy'], - $_SERVER['HTTPS_PROXY'], - $_SERVER['https_proxy'], - $_SERVER['NO_PROXY'], - $_SERVER['no_proxy'], - $_SERVER['CGI_HTTP_PROXY'] - ); - } - - protected function tearDown(): void - { - parent::tearDown(); - unset( - $_SERVER['HTTP_PROXY'], - $_SERVER['http_proxy'], - $_SERVER['HTTPS_PROXY'], - $_SERVER['https_proxy'], - $_SERVER['NO_PROXY'], - $_SERVER['no_proxy'], - $_SERVER['CGI_HTTP_PROXY'] - ); - } - - /** - * @dataProvider dataMalformed - */ - public function testThrowsOnMalformedUrl(string $url): void - { - $_SERVER['http_proxy'] = $url; - - self::expectException('RuntimeException'); - ProxyHelper::getProxyData(); - } - - public static function dataMalformed(): array - { - return [ - 'no-host' => ['localhost'], - 'no-port' => ['scheme://localhost'], - ]; - } - - /** - * @dataProvider dataFormatting - */ - public function testUrlFormatting(string $url, string $expected): void - { - $_SERVER['http_proxy'] = $url; - - [$httpProxy, $httpsProxy, $noProxy] = ProxyHelper::getProxyData(); - $this->assertSame($expected, $httpProxy); - } - - public static function dataFormatting(): array - { - // url, expected - return [ - 'lowercases-scheme' => ['HTTP://proxy.com:8888', 'http://proxy.com:8888'], - 'adds-http-port' => ['http://proxy.com', 'http://proxy.com:80'], - 'adds-https-port' => ['https://proxy.com', 'https://proxy.com:443'], - ]; - } - - /** - * @dataProvider dataCaseOverrides - * - * @param array $server - */ - public function testLowercaseOverridesUppercase(array $server, string $expected, int $index): void - { - $_SERVER = array_merge($_SERVER, $server); - - $list = ProxyHelper::getProxyData(); - $this->assertSame($expected, $list[$index]); - } - - public static function dataCaseOverrides(): array - { - // server, expected, list index - return [ - [['HTTP_PROXY' => 'http://upper.com', 'http_proxy' => 'http://lower.com'], 'http://lower.com:80', 0], - [['HTTPS_PROXY' => 'http://upper.com', 'https_proxy' => 'http://lower.com'], 'http://lower.com:80', 1], - [['NO_PROXY' => 'upper.com', 'no_proxy' => 'lower.com'], 'lower.com', 2], - ]; - } - - /** - * @dataProvider dataCGIOverrides - * - * @param array $server - */ - public function testCGIUpperCaseOverridesHttp(array $server, string $expected, int $index): void - { - $_SERVER = array_merge($_SERVER, $server); - - $list = ProxyHelper::getProxyData(); - $this->assertSame($expected, $list[$index]); - } - - public static function dataCGIOverrides(): array - { - // server, expected, list index - return [ - [['http_proxy' => 'http://http.com', 'CGI_HTTP_PROXY' => 'http://cgi.com'], 'http://cgi.com:80', 0], - [['http_proxy' => 'http://http.com', 'cgi_http_proxy' => 'http://cgi.com'], 'http://http.com:80', 0], - ]; - } - - public function testNoHttpsProxyUsesHttpProxy(): void - { - $_SERVER['http_proxy'] = 'http://http.com'; - - [$httpProxy, $httpsProxy, $noProxy] = ProxyHelper::getProxyData(); - $this->assertSame('http://http.com:80', $httpsProxy); - } - - public function testNoHttpProxyDoesNotUseHttpsProxy(): void - { - $_SERVER['https_proxy'] = 'http://https.com'; - - [$httpProxy, $httpsProxy, $noProxy] = ProxyHelper::getProxyData(); - $this->assertSame(null, $httpProxy); - } - - /** - * @dataProvider dataContextOptions - * - * @param array $expected - * - * @phpstan-param array{http: array{proxy: string, header?: string}} $expected - */ - public function testGetContextOptions(string $url, array $expected): void - { - $this->assertEquals($expected, ProxyHelper::getContextOptions($url)); - } - - public static function dataContextOptions(): array - { - // url, expected - return [ - ['http://proxy.com', ['http' => [ - 'proxy' => 'tcp://proxy.com:80', - ]]], - ['https://proxy.com', ['http' => [ - 'proxy' => 'ssl://proxy.com:443', - ]]], - ['http://user:p%40ss@proxy.com', ['http' => [ - 'proxy' => 'tcp://proxy.com:80', - 'header' => 'Proxy-Authorization: Basic dXNlcjpwQHNz', - ]]], - ]; - } - - /** - * @dataProvider dataRequestFullUri - * - * @param mixed[] $expected - */ - public function testSetRequestFullUri(string $requestUrl, array $expected): void - { - $options = []; - ProxyHelper::setRequestFullUri($requestUrl, $options); - - $this->assertEquals($expected, $options); - } - - public static function dataRequestFullUri(): array - { - $options = ['http' => ['request_fulluri' => true]]; - - // $requestUrl, expected - return [ - 'http' => ['http://repo.org', $options], - 'https' => ['https://repo.org', []], - 'no-scheme' => ['repo.org', []], - ]; - } -} diff --git a/tests/Composer/Test/Util/Http/ProxyItemTest.php b/tests/Composer/Test/Util/Http/ProxyItemTest.php new file mode 100644 index 000000000000..ccca76ec67d0 --- /dev/null +++ b/tests/Composer/Test/Util/Http/ProxyItemTest.php @@ -0,0 +1,72 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util\Http; + +use Composer\Util\Http\ProxyItem; +use Composer\Test\TestCase; + +class ProxyItemTest extends TestCase +{ + /** + * @dataProvider dataMalformed + */ + public function testThrowsOnMalformedUrl(string $url): void + { + self::expectException('RuntimeException'); + $proxyItem = new ProxyItem($url, 'http_proxy'); + } + + /** + * @return array> + */ + public static function dataMalformed(): array + { + return [ + 'ws-r' => ["http://user\rname@localhost:80"], + 'ws-n' => ["http://user\nname@localhost:80"], + 'ws-t' => ["http://user\tname@localhost:80"], + 'no-host' => ['localhost'], + 'no-port' => ['scheme://localhost'], + 'port-0' => ['http://localhost:0'], + 'port-big' => ['http://localhost:65536'], + ]; + } + + /** + * @dataProvider dataFormatting + */ + public function testUrlFormatting(string $url, string $expected): void + { + $proxyItem = new ProxyItem($url, 'http_proxy'); + $proxy = $proxyItem->toRequestProxy('http'); + + self::assertSame($expected, $proxy->getStatus()); + } + + /** + * @return array> + */ + public static function dataFormatting(): array + { + // url, expected + return [ + 'none' => ['http://proxy.com:8888', 'http://proxy.com:8888'], + 'lowercases-scheme' => ['HTTP://proxy.com:8888', 'http://proxy.com:8888'], + 'adds-http-scheme' => ['proxy.com:80', 'http://proxy.com:80'], + 'adds-http-port' => ['http://proxy.com', 'http://proxy.com:80'], + 'adds-https-port' => ['https://proxy.com', 'https://proxy.com:443'], + 'removes-user' => ['http://user@proxy.com:6180', 'http://***@proxy.com:6180'], + 'removes-user-pass' => ['http://user:p%40ss@proxy.com:6180', 'http://***:***@proxy.com:6180'], + ]; + } +} diff --git a/tests/Composer/Test/Util/Http/ProxyManagerTest.php b/tests/Composer/Test/Util/Http/ProxyManagerTest.php index 6321df6be876..80c88a84455b 100644 --- a/tests/Composer/Test/Util/Http/ProxyManagerTest.php +++ b/tests/Composer/Test/Util/Http/ProxyManagerTest.php @@ -15,8 +15,16 @@ use Composer\Util\Http\ProxyManager; use Composer\Test\TestCase; +/** + * @phpstan-import-type contextOptions from \Composer\Util\Http\RequestProxy + */ class ProxyManagerTest extends TestCase { + // isTransitional can be removed after the transition period + + /** @var bool */ + private $isTransitional = true; + protected function setUp(): void { unset( @@ -26,7 +34,8 @@ protected function setUp(): void $_SERVER['https_proxy'], $_SERVER['NO_PROXY'], $_SERVER['no_proxy'], - $_SERVER['CGI_HTTP_PROXY'] + $_SERVER['CGI_HTTP_PROXY'], + $_SERVER['cgi_http_proxy'] ); ProxyManager::reset(); } @@ -41,7 +50,8 @@ protected function tearDown(): void $_SERVER['https_proxy'], $_SERVER['NO_PROXY'], $_SERVER['no_proxy'], - $_SERVER['CGI_HTTP_PROXY'] + $_SERVER['CGI_HTTP_PROXY'], + $_SERVER['cgi_http_proxy'] ); ProxyManager::reset(); } @@ -49,54 +59,137 @@ protected function tearDown(): void public function testInstantiation(): void { $originalInstance = ProxyManager::getInstance(); - $this->assertInstanceOf('Composer\Util\Http\ProxyManager', $originalInstance); - $sameInstance = ProxyManager::getInstance(); - $this->assertTrue($originalInstance === $sameInstance); + self::assertTrue($originalInstance === $sameInstance); ProxyManager::reset(); $newInstance = ProxyManager::getInstance(); - $this->assertFalse($sameInstance === $newInstance); + self::assertFalse($sameInstance === $newInstance); } public function testGetProxyForRequestThrowsOnBadProxyUrl(): void { $_SERVER['http_proxy'] = 'localhost'; $proxyManager = ProxyManager::getInstance(); + self::expectException('Composer\Downloader\TransportException'); $proxyManager->getProxyForRequest('http://example.com'); } /** - * @dataProvider dataRequest + * @dataProvider dataCaseOverrides * - * @param array $server - * @param mixed[] $expectedOptions - * @param non-empty-string $url + * @param array $server + * @param non-empty-string $url */ - public function testGetProxyForRequest(array $server, string $url, string $expectedUrl, array $expectedOptions, bool $expectedSecure, string $expectedMessage): void + public function testLowercaseOverridesUppercase(array $server, string $url, string $expectedUrl): void { $_SERVER = array_merge($_SERVER, $server); $proxyManager = ProxyManager::getInstance(); $proxy = $proxyManager->getProxyForRequest($url); - $this->assertInstanceOf('Composer\Util\Http\RequestProxy', $proxy); + self::assertSame($expectedUrl, $proxy->getStatus()); + } + + /** + * @return list, 1: string, 2: string}> + */ + public static function dataCaseOverrides(): array + { + // server, url, expectedUrl + return [ + [['HTTP_PROXY' => 'http://upper.com', 'http_proxy' => 'http://lower.com'], 'http://repo.org', 'http://lower.com:80'], + [['CGI_HTTP_PROXY' => 'http://upper.com', 'cgi_http_proxy' => 'http://lower.com'], 'http://repo.org', 'http://lower.com:80'], + [['HTTPS_PROXY' => 'http://upper.com', 'https_proxy' => 'http://lower.com'], 'https://repo.org', 'http://lower.com:80'], + ]; + } + + /** + * @dataProvider dataCGIProxy + * + * @param array $server + */ + public function testCGIProxyIsOnlyUsedWhenNoHttpProxy(array $server, string $expectedUrl): void + { + $_SERVER = array_merge($_SERVER, $server); + $proxyManager = ProxyManager::getInstance(); - $this->assertSame($expectedUrl, $proxy->getUrl()); - $this->assertSame($expectedOptions, $proxy->getContextOptions()); - $this->assertSame($expectedSecure, $proxy->isSecure()); + $proxy = $proxyManager->getProxyForRequest('http://repo.org'); + self::assertSame($expectedUrl, $proxy->getStatus()); + } - $message = $proxy->getFormattedUrl(); + /** + * @return list, 1: string}> + */ + public static function dataCGIProxy(): array + { + // server, expectedUrl + return [ + [['CGI_HTTP_PROXY' => 'http://cgi.com:80'], 'http://cgi.com:80'], + [['http_proxy' => 'http://http.com:80', 'CGI_HTTP_PROXY' => 'http://cgi.com:80'], 'http://http.com:80'], + ]; + } - if ($expectedMessage) { - $condition = stripos($message, $expectedMessage) !== false; - } else { - $condition = $expectedMessage === $message; + public function testNoHttpProxyDoesNotUseHttpsProxy(): void + { + $_SERVER['https_proxy'] = 'https://proxy.com:443'; + $proxyManager = ProxyManager::getInstance(); + + $proxy = $proxyManager->getProxyForRequest('http://repo.org'); + self::assertSame('', $proxy->getStatus()); + } + + public function testNoHttpsProxyDoesNotUseHttpProxy(): void + { + $_SERVER['http_proxy'] = 'http://proxy.com:80'; + + // This can be removed after the transition period. + // An empty https_proxy value prevents using any http_proxy + if ($this->isTransitional) { + $_SERVER['https_proxy'] = ''; } - $this->assertTrue($condition, 'lastProxy check'); + $proxyManager = ProxyManager::getInstance(); + $proxy = $proxyManager->getProxyForRequest('https://repo.org'); + self::assertSame('', $proxy->getStatus()); + } + + /** + * This test can be removed after the transition period + */ + public function testTransitional(): void + { + $_SERVER['http_proxy'] = 'http://proxy.com:80'; + $proxyManager = ProxyManager::getInstance(); + + $proxy = $proxyManager->getProxyForRequest('https://repo.org'); + self::assertSame('http://proxy.com:80', $proxy->getStatus()); + self::assertTrue($proxyManager->needsTransitionWarning()); } + /** + * @dataProvider dataRequest + * + * @param array $server + * @param non-empty-string $url + * @param ?contextOptions $options + */ + public function testGetProxyForRequest(array $server, string $url, ?array $options, string $status, bool $excluded): void + { + $_SERVER = array_merge($_SERVER, $server); + $proxyManager = ProxyManager::getInstance(); + + $proxy = $proxyManager->getProxyForRequest($url); + self::assertSame($options, $proxy->getContextOptions()); + self::assertSame($status, $proxy->getStatus()); + self::assertSame($excluded, $proxy->isExcludedByNoProxy()); + } + + /** + * Tests context options. curl options are tested in RequestProxyTest.php + * + * @return list, 1: string, 2: ?contextOptions, 3: string, 4: bool}> + */ public static function dataRequest(): array { $server = [ @@ -105,68 +198,26 @@ public static function dataRequest(): array 'no_proxy' => 'other.repo.org', ]; - // server, url, expectedUrl, expectedOptions, expectedSecure, expectedMessage + // server, url, options, status, excluded return [ - [[], 'http://repo.org', '', [], false, ''], - [$server, 'http://repo.org', 'http://user:p%40ss@proxy.com:80', + [[], 'http://repo.org', null, '', false], + [$server, 'http://repo.org', ['http' => [ 'proxy' => 'tcp://proxy.com:80', 'header' => 'Proxy-Authorization: Basic dXNlcjpwQHNz', 'request_fulluri' => true, ]], + 'http://***:***@proxy.com:80', false, - 'http://user:***@proxy.com:80', ], - [ - $server, 'https://repo.org', 'https://proxy.com:443', + [$server, 'https://repo.org', ['http' => [ 'proxy' => 'ssl://proxy.com:443', ]], - true, 'https://proxy.com:443', + false, ], - [$server, 'https://other.repo.org', '', [], false, 'no_proxy'], - ]; - } - - /** - * @dataProvider dataStatus - * - * @param array $server - */ - public function testGetStatus(array $server, bool $expectedStatus, ?string $expectedMessage): void - { - $_SERVER = array_merge($_SERVER, $server); - $proxyManager = ProxyManager::getInstance(); - $status = $proxyManager->isProxying(); - $message = $proxyManager->getFormattedProxy(); - - $this->assertSame($expectedStatus, $status); - - if ($expectedMessage !== null) { - $condition = stripos($message, $expectedMessage) !== false; - } else { - $condition = $expectedMessage === $message; - } - $this->assertTrue($condition, 'message check'); - } - - public static function dataStatus(): array - { - // server, expectedStatus, expectedMessage - return [ - [[], false, null], - [['http_proxy' => 'localhost'], false, 'malformed'], - [ - ['http_proxy' => 'http://user:p%40ss@proxy.com:80'], - true, - 'http=http://user:***@proxy.com:80', - ], - [ - ['http_proxy' => 'proxy.com:80', 'https_proxy' => 'proxy.com:80'], - true, - 'http=proxy.com:80, https=proxy.com:80', - ], + [$server, 'https://other.repo.org', null, 'excluded by no_proxy', true], ]; } } diff --git a/tests/Composer/Test/Util/Http/RequestProxyTest.php b/tests/Composer/Test/Util/Http/RequestProxyTest.php index 66a03fccb330..c01948598717 100644 --- a/tests/Composer/Test/Util/Http/RequestProxyTest.php +++ b/tests/Composer/Test/Util/Http/RequestProxyTest.php @@ -17,45 +17,174 @@ class RequestProxyTest extends TestCase { + public function testFactoryNone(): void + { + $proxy = RequestProxy::none(); + + $options = extension_loaded('curl') ? [CURLOPT_PROXY => ''] : []; + self::assertSame($options, $proxy->getCurlOptions([])); + self::assertNull($proxy->getContextOptions()); + self::assertSame('', $proxy->getStatus()); + } + + public function testFactoryNoProxy(): void + { + $proxy = RequestProxy::noProxy(); + + $options = extension_loaded('curl') ? [CURLOPT_PROXY => ''] : []; + self::assertSame($options, $proxy->getCurlOptions([])); + self::assertNull($proxy->getContextOptions()); + self::assertSame('excluded by no_proxy', $proxy->getStatus()); + } + /** * @dataProvider dataSecure + * + * @param ?non-empty-string $url */ - public function testIsSecure(string $url, bool $expectedSecure): void + public function testIsSecure(?string $url, bool $expected): void { - $proxy = new RequestProxy($url, [], ''); - - $this->assertSame($expectedSecure, $proxy->isSecure()); + $proxy = new RequestProxy($url, null, null, null); + self::assertSame($expected, $proxy->isSecure()); } + /** + * @return array + */ public static function dataSecure(): array { - // url, secure + // url, expected return [ 'basic' => ['http://proxy.com:80', false], 'secure' => ['https://proxy.com:443', true], - 'none' => ['', false], + 'none' => [null, false], ]; } + public function testGetStatusThrowsOnBadFormatSpecifier(): void + { + $proxy = new RequestProxy('http://proxy.com:80', null, null, 'http://proxy.com:80'); + self::expectException('InvalidArgumentException'); + $proxy->getStatus('using proxy'); + } + /** - * @dataProvider dataProxyUrl + * @dataProvider dataStatus + * + * @param ?non-empty-string $url */ - public function testGetFormattedUrlFormat(string $url, string $format, string $expected): void + public function testGetStatus(?string $url, ?string $format, string $expected): void { - $proxy = new RequestProxy($url, [], $url); + $proxy = new RequestProxy($url, null, null, $url); - $message = $proxy->getFormattedUrl($format); - $this->assertSame($expected, $message); + if ($format === null) { + // try with and without optional param + self::assertSame($expected, $proxy->getStatus()); + self::assertSame($expected, $proxy->getStatus($format)); + } else { + self::assertSame($expected, $proxy->getStatus($format)); + } } - public static function dataProxyUrl(): array + /** + * @return array + */ + public static function dataStatus(): array { $format = 'proxy (%s)'; // url, format, expected return [ - ['', $format, ''], - ['http://proxy.com:80', $format, 'proxy (http://proxy.com:80)'], + 'no-proxy' => [null, $format, ''], + 'null-format' => ['http://proxy.com:80', null, 'http://proxy.com:80'], + 'with-format' => ['http://proxy.com:80', $format, 'proxy (http://proxy.com:80)'], + ]; + } + + /** + * This test avoids HTTPS proxies so that it can be run on PHP < 7.3 + * + * @requires extension curl + * @dataProvider dataCurlOptions + * + * @param ?non-empty-string $url + * @param ?non-empty-string $auth + * @param array $expected + */ + public function testGetCurlOptions(?string $url, ?string $auth, array $expected): void + { + $proxy = new RequestProxy($url, $auth, null, null); + self::assertSame($expected, $proxy->getCurlOptions([])); + } + + /** + * @return list}> + */ + public static function dataCurlOptions(): array + { + // url, auth, expected + return [ + [null, null, [CURLOPT_PROXY => '']], + ['http://proxy.com:80', null, + [ + CURLOPT_PROXY => 'http://proxy.com:80', + CURLOPT_NOPROXY => '', + ], + ], + ['http://proxy.com:80', 'user:p%40ss', + [ + CURLOPT_PROXY => 'http://proxy.com:80', + CURLOPT_NOPROXY => '', + CURLOPT_PROXYAUTH => CURLAUTH_BASIC, + CURLOPT_PROXYUSERPWD => 'user:p%40ss', + ], + ], + ]; + } + + /** + * @requires PHP >= 7.3.0 + * @requires extension curl >= 7.52.0 + * @dataProvider dataCurlSSLOptions + * + * @param non-empty-string $url + * @param ?non-empty-string $auth + * @param array $sslOptions + * @param array $expected + */ + public function testGetCurlOptionsWithSSL(string $url, ?string $auth, array $sslOptions, array $expected): void + { + $proxy = new RequestProxy($url, $auth, null, null); + self::assertSame($expected, $proxy->getCurlOptions($sslOptions)); + } + + /** + * @return list, 3: array}> + */ + public static function dataCurlSSLOptions(): array + { + // for PHPStan on PHP < 7.3 + $caInfo = 10246; // CURLOPT_PROXY_CAINFO + $caPath = 10247; // CURLOPT_PROXY_CAPATH + + // url, auth, sslOptions, expected + return [ + ['https://proxy.com:443', null, ['cafile' => '/certs/bundle.pem'], + [ + CURLOPT_PROXY => 'https://proxy.com:443', + CURLOPT_NOPROXY => '', + $caInfo => '/certs/bundle.pem', + ], + ], + ['https://proxy.com:443', 'user:p%40ss', ['capath' => '/certs'], + [ + CURLOPT_PROXY => 'https://proxy.com:443', + CURLOPT_NOPROXY => '', + CURLOPT_PROXYAUTH => CURLAUTH_BASIC, + CURLOPT_PROXYUSERPWD => 'user:p%40ss', + $caPath => '/certs', + ], + ], ]; } } From 41fb6146b08e029c5736ef01fd62c032cb5b360f Mon Sep 17 00:00:00 2001 From: John Stevenson Date: Fri, 19 Apr 2024 13:18:55 +0100 Subject: [PATCH 046/257] Improve proxy reporting in Diagnose command (#11932) --- src/Composer/Command/DiagnoseCommand.php | 40 +++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index fc594f7d94f7..d06031076a08 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -44,6 +44,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\ExecutableFinder; +use Composer\Util\Http\ProxyManager; +use Composer\Util\Http\RequestProxy; /** * @author Jordi Boggiano @@ -115,10 +117,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->write('Checking https connectivity to packagist: ', false); $this->outputResult($this->checkHttp('https', $config)); - $opts = stream_context_get_options(StreamContextFactory::getContext('http://example.org')); - if (!empty($opts['http']['proxy'])) { + $proxyManager = ProxyManager::getInstance(); + $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https']; + try { + foreach ($protos as $proto) { + $proxy = $proxyManager->getProxyForRequest($proto.'://repo.packagist.org'); + if ($proxy->getStatus() !== '') { + $type = $proxy->isSecure() ? 'HTTPS' : 'HTTP'; + $io->write('Checking '.$type.' proxy with '.$proto.': ', false); + $this->outputResult($this->checkHttpProxy($proxy, $proto)); + } + } + } catch (TransportException $e) { $io->write('Checking HTTP proxy: ', false); - $this->outputResult($this->checkHttpProxy()); + $status = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + $this->outputResult(is_string($status) ? $status : $e); } if (count($oauth = $config->get('github-oauth')) > 0) { @@ -298,31 +311,36 @@ private function checkHttp(string $proto, Config $config) } /** - * @return string|true|\Exception + * @return string|\Exception */ - private function checkHttpProxy() + private function checkHttpProxy(RequestProxy $proxy, string $protocol) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } - $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { - $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson(); + $proxyStatus = $proxy->getStatus(); + + if ($proxy->isExcludedByNoProxy()) { + return 'SKIP Because repo.packagist.org is '.$proxyStatus.''; + } + + $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson(); $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); - $provider = $this->httpDownloader->get($protocol . '://repo.packagist.org/'.$path)->getBody(); + $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody(); if (hash('sha256', $provider) !== $hash) { - return 'It seems that your proxy is modifying http traffic on the fly'; + return 'It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly'; } + + return 'OK '.$proxyStatus.''; } catch (\Exception $e) { return $e; } - - return true; } /** From 3604996464d9a2e3df8b77867f502d2ecb50bf2e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 14:20:56 +0200 Subject: [PATCH 047/257] Ensure diagnose command works even if provider-includes disappears --- src/Composer/Command/DiagnoseCommand.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index d06031076a08..28a38282a134 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -328,13 +328,15 @@ private function checkHttpProxy(RequestProxy $proxy, string $protocol) } $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson(); - $hash = reset($json['provider-includes']); - $hash = $hash['sha256']; - $path = str_replace('%hash%', $hash, key($json['provider-includes'])); - $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody(); - - if (hash('sha256', $provider) !== $hash) { - return 'It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly'; + if (isset($json['provider-includes'])) { + $hash = reset($json['provider-includes']); + $hash = $hash['sha256']; + $path = str_replace('%hash%', $hash, key($json['provider-includes'])); + $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody(); + + if (hash('sha256', $provider) !== $hash) { + return 'It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly'; + } } return 'OK '.$proxyStatus.''; From b0ec0f96ad7d1ef1fb229acc1e980149c5c58b09 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 16:54:04 +0200 Subject: [PATCH 048/257] Update phpstan deps and fix a few array_filter issues --- composer.lock | 40 +++++++++---------- src/Composer/Command/GlobalCommand.php | 10 +++-- src/Composer/Command/InitCommand.php | 2 +- src/Composer/Command/UpdateCommand.php | 4 +- .../Package/Archiver/ArchiveManager.php | 4 +- src/Composer/Util/ErrorHandler.php | 4 +- src/Composer/Util/TlsHelper.php | 17 +++++--- .../Repository/PlatformRepositoryTest.php | 2 +- 8 files changed, 47 insertions(+), 36 deletions(-) diff --git a/composer.lock b/composer.lock index 58cc3d257efe..d701c6a42642 100644 --- a/composer.lock +++ b/composer.lock @@ -2014,16 +2014,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.66", + "version": "1.10.67", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", - "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", "shasum": "" }, "require": { @@ -2066,13 +2066,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-03-28T16:17:31+00:00" + "time": "2024-04-16T07:22:02+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2176,21 +2172,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.5.2", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542" + "reference": "8afd4af32bbff7b1f6df3d42bda45b2e661ba297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542", - "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8afd4af32bbff7b1f6df3d42bda45b2e661ba297", + "reference": "8afd4af32bbff7b1f6df3d42bda45b2e661ba297", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.34" + "phpstan/phpstan": "^1.10.60" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -2219,22 +2215,22 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.4" }, - "time": "2023-10-30T14:35:06+00:00" + "time": "2024-04-19T14:48:30+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.9", + "version": "1.3.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "a32bc86da24495025d7aafd1ba62444d4a364a98" + "reference": "f4b9407fa3203aebafd422ae8f0eb1ef94659a80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/a32bc86da24495025d7aafd1ba62444d4a364a98", - "reference": "a32bc86da24495025d7aafd1ba62444d4a364a98", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f4b9407fa3203aebafd422ae8f0eb1ef94659a80", + "reference": "f4b9407fa3203aebafd422ae8f0eb1ef94659a80", "shasum": "" }, "require": { @@ -2291,9 +2287,9 @@ "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.3.9" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.12" }, - "time": "2024-03-16T16:50:20+00:00" + "time": "2024-04-14T13:30:23+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index c6fc7fe7a6ff..2841c2ae178a 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -33,9 +33,13 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti { $application = $this->getApplication(); if ($input->mustSuggestArgumentValuesFor('command-name')) { - $suggestions->suggestValues(array_values(array_filter(array_map(static function (Command $command) { - return $command->isHidden() ? null : $command->getName(); - }, $application->all())))); + $suggestions->suggestValues(array_values(array_filter( + array_map(static function (Command $command) { + return $command->isHidden() ? null : $command->getName(); + }, $application->all()), function (?string $cmd) { + return $cmd !== null; + } + ))); return; } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 606bff8207bd..15228af29a1d 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -87,7 +87,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = $this->getIO(); $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload']; - $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist))); + $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)), function ($val) { return $val !== null && $val !== []; }); if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $options['name'])) { throw new \InvalidArgumentException( diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 40e566136c4f..b4478cd04f26 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -296,7 +296,9 @@ private function getPackagesInteractively(IOInterface $io, InputInterface $input } } while (true); - $packages = array_filter($packages); + $packages = array_filter($packages, function (string $pkg) { + return $pkg !== ''; + }); if (!$packages) { throw new \InvalidArgumentException('You must enter minimum one package.'); } diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 6efa4793887b..4b15fa844f3c 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -99,7 +99,9 @@ public function getPackageFilenameParts(CompletePackageInterface $package): arra $parts['source_reference'] = substr(sha1($sourceReference), 0, 6); } - $parts = array_filter($parts); + $parts = array_filter($parts, function (?string $part) { + return $part !== null; + }); foreach ($parts as $key => $part) { $parts[$key] = str_replace('/', '-', $part); } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 4ed0cfefba1c..38cf84e44807 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -63,7 +63,9 @@ public static function handle(int $level, string $message, string $file, int $li } return null; - }, array_slice(debug_backtrace(), 2)))); + }, array_slice(debug_backtrace(), 2)), function (?string $line) { + return $line !== null; + })); } } diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index aca227270bfe..5ab2bf9c96c0 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -76,13 +76,18 @@ public static function getCertificateNames($certificate): ?array if (isset($info['extensions']['subjectAltName'])) { $subjectAltNames = Preg::split('{\s*,\s*}', $info['extensions']['subjectAltName']); - $subjectAltNames = array_filter(array_map(static function ($name): ?string { - if (0 === strpos($name, 'DNS:')) { - return strtolower(ltrim(substr($name, 4))); + $subjectAltNames = array_filter( + array_map(static function ($name): ?string { + if (0 === strpos($name, 'DNS:')) { + return strtolower(ltrim(substr($name, 4))); + } + + return null; + }, $subjectAltNames), + function (?string $san) { + return $san !== null; } - - return null; - }, $subjectAltNames)); + ); $subjectAltNames = array_values($subjectAltNames); } diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index 74f6c3179587..aa7ddb8ddc2b 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -1237,7 +1237,7 @@ static function ($package): bool { $expectedLibraries = array_keys(array_filter($expectations, static function ($expectation): bool { return $expectation !== false; })); - self::assertCount(count(array_filter($expectedLibraries)), $libraries, sprintf('Expected: %s, got %s', var_export($expectedLibraries, true), var_export($libraries, true))); + self::assertCount(count($expectedLibraries), $libraries, sprintf('Expected: %s, got %s', var_export($expectedLibraries, true), var_export($libraries, true))); foreach ($extensions as $extension) { $expectations['ext-'.$extension] = $extensionVersion; From 70927f728ee5ccde0671cbbf0eee01f01a0a0815 Mon Sep 17 00:00:00 2001 From: John Stevenson Date: Fri, 19 Apr 2024 16:27:54 +0100 Subject: [PATCH 049/257] Add FAQ about using a proxy (#11933) --- doc/03-cli.md | 40 +----- .../how-to-use-composer-behind-a-proxy.md | 118 ++++++++++++++++++ src/Composer/Console/Application.php | 7 ++ 3 files changed, 130 insertions(+), 35 deletions(-) create mode 100644 doc/faqs/how-to-use-composer-behind-a-proxy.md diff --git a/doc/03-cli.md b/doc/03-cli.md index d7d1fd136638..bd05c04c2b44 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1232,20 +1232,12 @@ environment variable if you use Vagrant or VirtualBox and experience issues with being found during installation even though they should be present. ### http_proxy or HTTP_PROXY +### HTTP_PROXY_REQUEST_FULLURI +### HTTPS_PROXY_REQUEST_FULLURI +### no_proxy or NO_PROXY -If you are using Composer from behind an HTTP proxy, you can use the standard -`http_proxy` or `HTTP_PROXY` env vars. Set it to the URL of your proxy. -Many operating systems already set this variable for you. - -Using `http_proxy` (lowercased) or even defining both might be preferable since -some tools like git or curl will only use the lower-cased `http_proxy` version. -Alternatively you can also define the git proxy using -`git config --global http.proxy `. - -If you are using Composer in a non-CLI context (i.e. integration into a CMS or -similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY` -environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further -details. +See the [proxy documentation](faqs/how-to-use-composer-behind-a-proxy.md) for more details +on how to use proxy env vars. ### COMPOSER_AUDIT_ABANDONED @@ -1264,32 +1256,10 @@ in performance gains. Set to `4` or `6` to force IPv4 or IPv6 DNS resolution. This only works when the curl extension is used for downloads. -### HTTP_PROXY_REQUEST_FULLURI - -If you use a proxy, but it does not support the request_fulluri flag, then you -should set this env var to `false` or `0` to prevent Composer from setting the -request_fulluri option. - -### HTTPS_PROXY_REQUEST_FULLURI - -If you use a proxy, but it does not support the request_fulluri flag for HTTPS -requests, then you should set this env var to `false` or `0` to prevent Composer -from setting the request_fulluri option. - ### COMPOSER_SELF_UPDATE_TARGET If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on a read-only filesystem. -### no_proxy or NO_PROXY - -If you are behind a proxy and would like to disable it for certain domains, you -can use the `no_proxy` or `NO_PROXY` env var. Set it to a comma separated list of -domains the proxy should *not* be used for. - -The env var accepts domains, IP addresses, and IP address blocks in CIDR -notation. You can restrict the filter to a particular port (e.g. `:80`). You -can also set it to `*` to ignore the proxy for all HTTP requests. - ### COMPOSER_DISABLE_NETWORK If set to `1`, disables network access (best effort). This can be used for debugging or diff --git a/doc/faqs/how-to-use-composer-behind-a-proxy.md b/doc/faqs/how-to-use-composer-behind-a-proxy.md new file mode 100644 index 000000000000..58f82ebcb690 --- /dev/null +++ b/doc/faqs/how-to-use-composer-behind-a-proxy.md @@ -0,0 +1,118 @@ +# How to use Composer behind a proxy + +Composer, like many other tools, uses environment variables to control the use of a proxy server and +supports: + +- `http_proxy` - the proxy to use for HTTP requests +- `https_proxy` - the proxy to use for HTTPS requests +- `CGI_HTTP_PROXY` - the proxy to use for HTTP requests in a non-CLI context +- `no_proxy` - domains that do not require a proxy + +These named variables are a convention, rather than an official standard, and their evolution and +usage across different operating systems and tools is complex. Composer prefers the use of lowercase +names, but accepts uppercase names where appropriate. + +## Usage + +Composer requires specific environment variables for HTTP and HTTPS requests. For example: + +``` +http_proxy=http://proxy.com:80 +https_proxy=http://proxy.com:80 +``` + +Uppercase names can also be used. + +### Non-CLI usage + +Composer does not look for `http_proxy` or `HTTP_PROXY` in a non-CLI context. If you are running it +this way (i.e. integration into a CMS or similar use case) you must use `CGI_HTTP_PROXY` for HTTP +requests: + +``` +CGI_HTTP_PROXY=http://proxy.com:80 +https_proxy=http://proxy.com:80 + +# cgi_http_proxy can also be used +``` + +> **Note:** CGI_HTTP_PROXY was introduced by Perl in 2001 to prevent request header manipulation and +was popularized in 2016 when this vulnerability was widely reported: https://httpoxy.org + +## Syntax + +Use `scheme://host:port` as in the examples above. Although a missing scheme defaults to http and a +missing port defaults to 80/443 for http/https schemes, other tools might require these values. + +The host can be specified as an IP address using dotted quad notation for IPv4, or enclosed in +square brackets for IPv6. + +### Authorization + +Composer supports Basic authorization, using the `scheme://user:pass@host:port` syntax. Reserved url +characters in either the user name or password must be percent-encoded. For example: + +``` +user: me@company +pass: p@ssw$rd +proxy: http://proxy.com:80 + +# percent-encoded authorization +me%40company:p%40ssw%24rd + +scheme://me%40company:p%40ssw%24rd@proxy.com:80 +``` + +> **Note:** The user name and password components must be percent-encoded individually and then +combined with the colon separator. The user name cannot contain a colon (even if percent-encoded), +because the proxy will split the components on the first colon it finds. + +## HTTPS proxy servers + +Composer supports HTTPS proxy servers, where HTTPS is the scheme used to connect to the proxy, but +only from PHP 7.3 with curl version 7.52.0 and above. + +``` +http_proxy=https://proxy.com:443 +https_proxy=https://proxy.com:443 +``` + +## Bypassing the proxy for specific domains + +Use the `no_proxy` (or `NO_PROXY`) environment variable to set a comma-separated list of domains +that the proxy should **not** be used for. + +``` +no_proxy=example.com +# Bypasses the proxy for example.com and its sub-domains + +no_proxy=www.example.com +# Bypasses the proxy for www.example.com and its sub-domains, but not for example.com +``` + +A domain can be restricted to a particular port (e.g. `:80`) and can also be specified as an IP +address or an IP address block in CIDR notation. + +IPv6 addresses do not need to be enclosed in square brackets, like they are for +http_proxy/https_proxy values, although this format is accepted. + +Setting the value to `*` will bypass the proxy for all requests. + +> **Note:** A leading dot in the domain name has no significance and is removed prior to processing. + +## Deprecated environment variables + +Composer originally provided `HTTP_PROXY_REQUEST_FULLURI` and `HTTPS_PROXY_REQUEST_FULLURI` to help +mitigate issues with misbehaving proxies. These are no longer required or used. + +## Requirement changes + +Composer <2.8 used `http_proxy` for both HTTP and HTTPS requests if `https_proxy` was not set, +but as of Composer 2.8.0 it requires [scheme-specific](#usage) environment variables. + +The reason for this change is to align Composer with current practice across other popular tools. To help +with the transition, as of Composer 2.7.3 the original behaviour remains but a warning message is +shown instructing the user to add an `https_proxy` environment variable. + +To prevent the original behaviour during the transition period, set an empty environment variable +(`https_proxy=`). diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index d045bdf88ecb..0eae431d1d95 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -32,6 +32,7 @@ use Composer\Command; use Composer\Composer; use Composer\Factory; +use Composer\Downloader\TransportException; use Composer\IO\IOInterface; use Composer\IO\ConsoleIO; use Composer\Json\JsonValidationException; @@ -404,6 +405,7 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow $io->writeError('Composer now requires separate proxy environment variables for HTTP and HTTPS requests.'); $io->writeError('Please set `https_proxy` in addition to your existing proxy environment variables.'); $io->writeError('This fallback (and warning) will be removed in Composer 2.8.0.'); + $io->writeError('https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md'); } return $result; @@ -480,6 +482,11 @@ private function hintCommonErrors(\Throwable $exception, OutputInterface $output } Silencer::restore(); + if ($exception instanceof TransportException && str_contains($exception->getMessage(), 'Unable to use a proxy')) { + $io->writeError('The following exception indicates your proxy is misconfigured', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details', true, IOInterface::QUIET); + } + if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); From 3238d7de40f5093c2ddb4f0c394c95b942b0a293 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 17:15:36 +0200 Subject: [PATCH 050/257] Upgrade phpstan-strict-rules --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index d701c6a42642..2f1cd76936d0 100644 --- a/composer.lock +++ b/composer.lock @@ -2172,16 +2172,16 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.5.4", + "version": "1.5.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "8afd4af32bbff7b1f6df3d42bda45b2e661ba297" + "reference": "2e193a07651a6f4be3baa44ddb21d822681f5918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8afd4af32bbff7b1f6df3d42bda45b2e661ba297", - "reference": "8afd4af32bbff7b1f6df3d42bda45b2e661ba297", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2e193a07651a6f4be3baa44ddb21d822681f5918", + "reference": "2e193a07651a6f4be3baa44ddb21d822681f5918", "shasum": "" }, "require": { @@ -2215,9 +2215,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.4" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.5" }, - "time": "2024-04-19T14:48:30+00:00" + "time": "2024-04-19T15:12:26+00:00" }, { "name": "phpstan/phpstan-symfony", From 69dc828ba7b5ee6d496cc7303f82123ba0fc6dfb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 17:15:47 +0200 Subject: [PATCH 051/257] Ensure type must be provided in init command --- src/Composer/Command/InitCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 15228af29a1d..5f6773daf3dd 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -57,7 +57,7 @@ protected function configure() new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), - new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), + new InputOption('type', null, InputOption::VALUE_REQUIRED, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), From 1dd63ba7acf5541c8f32a5b8ab9f3c092796ff06 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 21:34:40 +0200 Subject: [PATCH 052/257] Update changelog --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f38b993afe3e..7bee868d6366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +### [2.7.3] 2024-04-19 + + * BC Warning: Fixed `https_proxy` env var falling back to `http_proxy`'s value, this is still in place but with a warning for now, and https_proxy can now be set empty to remove the fallback. Composer 2.8.0 will remove the fallback so make sure you heed the warnings (#11915) + * Fixed `show` and `outdated` commands to remove leading `v` in e.g. `v1.2.3` when showing lists of packages (#11925) + * Fixed `audit` command not showing any id when no CVE is present, the advisory ID is now shown (#11892) + * Fixed the warning about a missing default version showing for packages with `project` type as those are typically not versioned and do not have cyclic dependencies (#11885) + * Fixed PHP 8.4 deprecation warnings + * Fixed `clear-cache` command to respect the config.cache-dir setting from the local composer.json (#11921) + * Fixed `status` command not handling failed download/install promises correctly (#11889) + * Added support for `buy_me_a_coffee` in GitHub funding files (#11902) + * Added `hg` support for SSH urls (#11878) + * Fixed some env vars with an integer value causing a crash (#11908) + * Fixed context data not being output when using IOInterface as a PSR-3 logger (#11882) + ### [2.7.2] 2024-03-11 * Added info about the PHP version when running `composer --version` (#11866) @@ -1838,6 +1852,7 @@ * Initial release +[2.7.3]: https://github.com/composer/composer/compare/2.7.2...2.7.3 [2.7.2]: https://github.com/composer/composer/compare/2.7.1...2.7.2 [2.7.1]: https://github.com/composer/composer/compare/2.7.0...2.7.1 [2.7.0]: https://github.com/composer/composer/compare/2.6.6...2.7.0 From e49be96f3bccd183c9ff1313686c06cf898ba4be Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 21:40:57 +0200 Subject: [PATCH 053/257] Release 2.7.3 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..97c9923e128d 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.3'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-04-19 21:40:57'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 9f84f0c32bdf15bce9e6cf14a96dec8b2bd443c4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 19 Apr 2024 21:40:58 +0200 Subject: [PATCH 054/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 97c9923e128d..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.3'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-04-19 21:40:57'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From b0d98b9301680be15aaf038d7aab3fd29cff8fbb Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Mon, 22 Apr 2024 21:12:57 +0200 Subject: [PATCH 055/257] Load ProxyManager before running command to fix autoload order (#11943) --- src/Composer/Console/Application.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 0eae431d1d95..1d34df527736 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -379,6 +379,8 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow } try { + $proxyManager = ProxyManager::getInstance(); + if ($input->hasParameterOption('--profile')) { $startTime = microtime(true); $this->io->enableDebugging($startTime); @@ -400,7 +402,7 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } - if (ProxyManager::getInstance()->needsTransitionWarning()) { + if ($proxyManager->needsTransitionWarning()) { $io->writeError(''); $io->writeError('Composer now requires separate proxy environment variables for HTTP and HTTPS requests.'); $io->writeError('Please set `https_proxy` in addition to your existing proxy environment variables.'); From 68b1a1254afe6b7ed407390ca91f27f32cd6c3cc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Apr 2024 21:16:48 +0200 Subject: [PATCH 056/257] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bee868d6366..0fb1228011fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### [2.7.4] 2024-04-22 + + * Fixed regression (`Call to undefined method ProxyManager::needsTransitionWarning()`) with projects requiring composer/composer in an pre-2.7.3 version (#11943, #11940) + ### [2.7.3] 2024-04-19 * BC Warning: Fixed `https_proxy` env var falling back to `http_proxy`'s value, this is still in place but with a warning for now, and https_proxy can now be set empty to remove the fallback. Composer 2.8.0 will remove the fallback so make sure you heed the warnings (#11915) @@ -1852,6 +1856,7 @@ * Initial release +[2.7.4]: https://github.com/composer/composer/compare/2.7.3...2.7.4 [2.7.3]: https://github.com/composer/composer/compare/2.7.2...2.7.3 [2.7.2]: https://github.com/composer/composer/compare/2.7.1...2.7.2 [2.7.1]: https://github.com/composer/composer/compare/2.7.0...2.7.1 From a625e50598e12171d3f60b1149eb530690c43474 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Apr 2024 21:17:03 +0200 Subject: [PATCH 057/257] Release 2.7.4 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..42bcc09e6295 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.4'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-04-22 21:17:03'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 0d5549f503675185a81b57317cd50abedeb64129 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Apr 2024 21:17:04 +0200 Subject: [PATCH 058/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 42bcc09e6295..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.4'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-04-22 21:17:03'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From a7c6125ee48415a6afe8914dc4fac5fbf49a22ab Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 28 Apr 2024 17:09:38 +0200 Subject: [PATCH 059/257] Workaround curl bug in 8.7.0/8.7.1, fixes #11913 --- src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Util/Http/CurlDownloader.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 28a38282a134..fa5e9aa567b3 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -199,7 +199,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? ''.OPENSSL_VERSION_TEXT.'' : 'missing')); - $io->write('cURL version: ' . $this->getCurlVersion()); + $io->write('curl version: ' . $this->getCurlVersion()); $finder = new ExecutableFinder; $hasSystemUnzip = (bool) $finder->find('unzip'); diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 4dd554065c72..635eb6ba5e0d 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -221,6 +221,12 @@ private function initDownload(callable $resolve, callable $reject, string $origi curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); } + // curl 8.7.0 - 8.7.1 has a bug whereas automatic accept-encoding header results in an error when reading the response + // https://github.com/composer/composer/issues/11913 + if (in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features)) { + curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); + } + $options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin, $url); $options = StreamContextFactory::initOptions($url, $options, true); From b64e38eb8610c91df5c8da0e127959d10f6bb288 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 28 Apr 2024 17:10:36 +0200 Subject: [PATCH 060/257] Fix phpstan reports --- src/Composer/Util/Http/CurlDownloader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 635eb6ba5e0d..827d2a3694e0 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -217,13 +217,13 @@ private function initDownload(callable $resolve, callable $reject, string $origi $version = curl_version(); $features = $version['features']; - if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features)) { + if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features) !== 0) { curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); } // curl 8.7.0 - 8.7.1 has a bug whereas automatic accept-encoding header results in an error when reading the response // https://github.com/composer/composer/issues/11913 - if (in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features)) { + if (isset($version['version']) && in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features) !== 0) { curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); } From 6778f1f79a56926e4c6131f7c30358ff2c301669 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Sun, 28 Apr 2024 16:28:11 +0100 Subject: [PATCH 061/257] Updated array shape of php-ext options (#11950) --- res/composer-schema.json | 11 +++++++++++ src/Composer/Package/Package.php | 2 +- src/Composer/Package/PackageInterface.php | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/res/composer-schema.json b/res/composer-schema.json index dfbd8beb6ff8..90714875c014 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -292,6 +292,11 @@ "type": "object", "description": "Settings for PHP extension packages.", "properties": { + "extension-name": { + "type": "string", + "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.", + "example": "ext-xdebug" + }, "priority": { "type": "integer", "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.", @@ -300,6 +305,12 @@ "example": 80, "default": 80 }, + "support-zts": { + "type": "boolean", + "description": "Does this package support Zend Thread Safety", + "example": false, + "default": true + }, "configure-options": { "type": "array", "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index 8bcd6e60b657..9d1b0ffc1a79 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -595,7 +595,7 @@ public function getIncludePaths(): array /** * Sets the list of paths added to PHP's include path. * - * @param array{priority?: int, configure-options?: list}|null $phpExt List of directories. + * @param array{extension-name?: string, priority?: int, support-zts?: bool, configure-options?: list}|null $phpExt List of directories. */ public function setPhpExt(?array $phpExt): void { diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 3583a323e1e8..b7c9ecd45de7 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -326,7 +326,7 @@ public function getIncludePaths(): array; /** * Returns the settings for php extension packages * - * @return array{priority?: int, configure-options?: list}|null + * @return array{extension-name?: string, priority?: int, support-zts?: bool, configure-options?: list}|null */ public function getPhpExt(): ?array; From ea28853305bb8dd0bf4c16b965c64ca4f36933f3 Mon Sep 17 00:00:00 2001 From: maximilian-walter Date: Sun, 28 Apr 2024 17:34:36 +0200 Subject: [PATCH 062/257] Don't show root warning for Podman containers (#11946) --- src/Composer/Console/Application.php | 6 +++++- src/Composer/Util/Platform.php | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 1d34df527736..7c989098fcc6 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -217,7 +217,11 @@ public function doRun(InputInterface $input, OutputInterface $output): int $needsSudoCheck = !Platform::isWindows() && function_exists('exec') && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER') - && (ini_get('open_basedir') || !file_exists('/.dockerenv')); + && (ini_get('open_basedir') || ( + !file_exists('/.dockerenv') + && !file_exists('/run/.containerenv') + && !file_exists('/var/run/.containerenv') + )); $isNonAllowedRoot = false; // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 33ff620560e3..45060c85c4e2 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -152,7 +152,9 @@ public static function isWindowsSubsystemForLinux(): bool !ini_get('open_basedir') && is_readable('/proc/version') && false !== stripos((string)Silencer::call('file_get_contents', '/proc/version'), 'microsoft') - && !file_exists('/.dockerenv') // docker running inside WSL should not be seen as WSL + && !file_exists('/.dockerenv') // Docker and Podman running inside WSL should not be seen as WSL + && !file_exists('/run/.containerenv') + && !file_exists('/var/run/.containerenv') ) { return self::$isWindowsSubsystemForLinux = true; } From 232f4e7a5c09cb138a9647c11d9191c59078b8e5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Apr 2024 10:59:21 +0200 Subject: [PATCH 063/257] Fix config command issue handling objects in some conditions, fixes #11945 --- src/Composer/Command/ConfigCommand.php | 8 +++-- src/Composer/Json/JsonManipulator.php | 20 +++++++++--- .../Test/Command/ConfigCommandTest.php | 20 ++++++++++++ .../Test/Json/JsonManipulatorTest.php | 32 +++++++++++++++++++ 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index cbdc174bce86..b625cbf1a522 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -771,8 +771,12 @@ static function ($vals) { foreach ($bits as $bit) { $currentValue = $currentValue[$bit] ?? null; } - if (is_array($currentValue)) { - $value = array_merge($currentValue, $value); + if (is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + $value = array_merge($currentValue, $value); + } else { + $value = $value + $currentValue; + } } } } diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 8d6759671080..809cea57ac2b 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -416,6 +416,9 @@ public function removeSubNode(string $mainNode, string $name): bool if ($subName !== null) { $curVal = json_decode($children, true); unset($curVal[$name][$subName]); + if ($curVal[$name] === []) { + $curVal[$name] = new \ArrayObject(); + } $this->addSubNode($mainNode, $name, $curVal[$name]); } @@ -427,7 +430,7 @@ public function removeSubNode(string $mainNode, string $name): bool if ($subName !== null) { $curVal = json_decode($matches['content'], true); unset($curVal[$name][$subName]); - $childrenClean = $this->format($curVal); + $childrenClean = $this->format($curVal, 0, true); } return $matches['start'] . $childrenClean . $matches['end']; @@ -534,12 +537,19 @@ public function removeMainKeyIfEmpty(string $key): bool /** * @param mixed $data */ - public function format($data, int $depth = 0): string + public function format($data, int $depth = 0, bool $wasObject = false): string { + if ($data instanceof \stdClass || $data instanceof \ArrayObject) { + $data = (array) $data; + $wasObject = true; + } + if (is_array($data)) { - reset($data); + if (\count($data) === 0) { + return $wasObject ? '{' . $this->newline . str_repeat($this->indent, $depth + 1) . '}' : '[]'; + } - if (is_numeric(key($data))) { + if (array_is_list($data)) { foreach ($data as $key => $val) { $data[$key] = $this->format($val, $depth + 1); } @@ -550,7 +560,7 @@ public function format($data, int $depth = 0): string $out = '{' . $this->newline; $elems = []; foreach ($data as $key => $val) { - $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode($key). ': '.$this->format($val, $depth + 1); + $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode((string) $key). ': '.$this->format($val, $depth + 1); } return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}'; diff --git a/tests/Composer/Test/Command/ConfigCommandTest.php b/tests/Composer/Test/Command/ConfigCommandTest.php index 68653c622903..27ee92b4a5f0 100644 --- a/tests/Composer/Test/Command/ConfigCommandTest.php +++ b/tests/Composer/Test/Command/ConfigCommandTest.php @@ -82,6 +82,26 @@ public static function provideConfigUpdates(): \Generator ['setting-key' => 'preferred-install.foo/*', '--unset' => true], ['config' => ['preferred-install' => []]], ]; + yield 'set extra with merge' => [ + [], + ['setting-key' => 'extra.patches.foo/bar', 'setting-value' => ['{"123":"value"}'], '--json' => true, '--merge' => true], + ['extra' => ['patches' => ['foo/bar' => [123 => 'value']]]], + ]; + yield 'combine extra with merge' => [ + ['extra' => ['patches' => ['foo/bar' => [5 => 'oldvalue']]]], + ['setting-key' => 'extra.patches.foo/bar', 'setting-value' => ['{"123":"value"}'], '--json' => true, '--merge' => true], + ['extra' => ['patches' => ['foo/bar' => [123 => 'value', 5 => 'oldvalue']]]], + ]; + yield 'combine extra with list' => [ + ['extra' => ['patches' => ['foo/bar' => ['oldvalue']]]], + ['setting-key' => 'extra.patches.foo/bar', 'setting-value' => ['{"123":"value"}'], '--json' => true, '--merge' => true], + ['extra' => ['patches' => ['foo/bar' => [123 => 'value', 0 => 'oldvalue']]]], + ]; + yield 'overwrite extra with merge' => [ + ['extra' => ['patches' => ['foo/bar' => [123 => 'oldvalue']]]], + ['setting-key' => 'extra.patches.foo/bar', 'setting-value' => ['{"123":"value"}'], '--json' => true, '--merge' => true], + ['extra' => ['patches' => ['foo/bar' => [123 => 'value']]]], + ]; } /** diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index 8ee8b58629ea..1d27069bd678 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -1721,6 +1721,38 @@ public function testRemoveSubNodeFromRequire(): void ', $manipulator->getContents()); } + public function testRemoveSubNodePreservesObjectTypeWhenEmpty(): void + { + $manipulator = new JsonManipulator('{ + "test": {"0": "foo"} +}'); + + $this->assertTrue($manipulator->removeSubNode('test', '0')); + $this->assertEquals('{ + "test": { + } +} +', $manipulator->getContents()); + } + + public function testRemoveSubNodePreservesObjectTypeWhenEmpty2(): void + { + $manipulator = new JsonManipulator('{ + "config": { + "preferred-install": {"foo/*": "source"} + } +}'); + + $this->assertTrue($manipulator->removeConfigSetting('preferred-install.foo/*')); + $this->assertEquals('{ + "config": { + "preferred-install": { + } + } +} +', $manipulator->getContents()); + } + public function testAddSubNodeInRequire(): void { $manipulator = new JsonManipulator('{ From 7c66169b7d7313426ef9d5d45fa25ab6108dde88 Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Mon, 29 Apr 2024 04:03:03 -0500 Subject: [PATCH 064/257] Add "uninstall" as alias to "remove". (#11951) --- src/Composer/Command/RemoveCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index b40fb774e45a..9803190a4760 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -43,7 +43,7 @@ protected function configure() { $this ->setName('remove') - ->setAliases(['rm']) + ->setAliases(['rm', 'uninstall']) ->setDescription('Removes a package from the require or require-dev') ->setDefinition([ new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()), From 877f1b150ffdc7b894e392ec05aa4a130fbb40ae Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Apr 2024 11:03:24 +0200 Subject: [PATCH 065/257] Add new uninstall alias to docs --- doc/03-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index bd05c04c2b44..5b29fd9b2f38 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -313,7 +313,7 @@ If you do not want to install the new dependencies immediately you can call it w * **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. Implicitly enables `--apcu-autoloader`. -## remove / rm +## remove / rm / uninstall The `remove` command removes packages from the `composer.json` file from the current directory. From 80631d2fc8d91591c8ce42d158bea24aff86c2af Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Apr 2024 11:19:52 +0200 Subject: [PATCH 066/257] Fix one more case of unsetting a key in an object --- src/Composer/Json/JsonManipulator.php | 3 +++ tests/Composer/Test/Command/ConfigCommandTest.php | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 809cea57ac2b..2e0f70411693 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -430,6 +430,9 @@ public function removeSubNode(string $mainNode, string $name): bool if ($subName !== null) { $curVal = json_decode($matches['content'], true); unset($curVal[$name][$subName]); + if ($curVal[$name] === []) { + $curVal[$name] = new \ArrayObject(); + } $childrenClean = $this->format($curVal, 0, true); } diff --git a/tests/Composer/Test/Command/ConfigCommandTest.php b/tests/Composer/Test/Command/ConfigCommandTest.php index 27ee92b4a5f0..cf3a55acc79d 100644 --- a/tests/Composer/Test/Command/ConfigCommandTest.php +++ b/tests/Composer/Test/Command/ConfigCommandTest.php @@ -82,6 +82,11 @@ public static function provideConfigUpdates(): \Generator ['setting-key' => 'preferred-install.foo/*', '--unset' => true], ['config' => ['preferred-install' => []]], ]; + yield 'unset platform' => [ + ['config' => ['platform' => ['php' => '7.2.5'], 'platform-check' => false]], + ['setting-key' => 'platform.php', '--unset' => true], + ['config' => ['platform' => [], 'platform-check' => false]], + ]; yield 'set extra with merge' => [ [], ['setting-key' => 'extra.patches.foo/bar', 'setting-value' => ['{"123":"value"}'], '--json' => true, '--merge' => true], From d4396a85bf14d3364a10f8d57a64b6aa80b9949d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Apr 2024 11:32:47 +0200 Subject: [PATCH 067/257] Fix binary proxies having an absolute path to vendor dir when project dir is a symlink, fixes #11947 --- src/Composer/Installer/BinaryInstaller.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index ba50fe17f5eb..9e3123ff8c27 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -225,7 +225,12 @@ protected function generateUnixyProxyCode(string $bin, string $link): string $phpunitHack1 = $phpunitHack2 = ''; // Don't expose autoload path when vendor dir was not set in custom installers if ($this->vendorDir) { - $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $this->vendorDir . '/autoload.php', false, true).";\n"; + // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already + $vendorDirReal = realpath($this->vendorDir); + if ($vendorDirReal === false) { + $vendorDirReal = $this->vendorDir; + } + $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true).";\n"; } // Add workaround for PHPUnit process isolation if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir.'/phpunit/phpunit/phpunit')) { From bcab1c4b8ecf7b239dd9e08fc07c5458cf80cb0a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Apr 2024 11:41:33 +0200 Subject: [PATCH 068/257] Fix Composer autoloader being hijackable by script/plugin event handlers (#11955) --- phpstan/baseline-8.1.neon | 5 --- phpstan/baseline.neon | 25 +++++------- .../EventDispatcher/EventDispatcher.php | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/phpstan/baseline-8.1.neon b/phpstan/baseline-8.1.neon index 2090b8e2b0c3..1a2a74b812df 100644 --- a/phpstan/baseline-8.1.neon +++ b/phpstan/baseline-8.1.neon @@ -180,11 +180,6 @@ parameters: count: 2 path: ../src/Composer/Util/Hg.php - - - message: "#^Only booleans are allowed in &&, int\\<0, 2097152\\> given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_add_handle expects CurlMultiHandle, resource\\|null given\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 801dd2336d9c..d207f39d1ee9 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -332,7 +332,7 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 4 + count: 3 path: ../src/Composer/Command/DiagnoseCommand.php - @@ -1850,6 +1850,11 @@ parameters: count: 1 path: ../src/Composer/Downloader/ZipDownloader.php + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: ../src/Composer/EventDispatcher/EventDispatcher.php + - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -4000,7 +4005,7 @@ parameters: - message: "#^Cannot access offset 'features' on array\\|false\\.$#" - count: 2 + count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - @@ -4010,11 +4015,6 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 3 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Only booleans are allowed in &&, int given on the right side\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php @@ -4189,17 +4189,17 @@ parameters: path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy::getCurlOptions\\(\\) should return array\\ but returns array\\.$#" + message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#" count: 1 path: ../src/Composer/Util/Http/RequestProxy.php - - message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#" + message: "#^Constant CURLOPT_PROXY_CAPATH not found\\.$#" count: 1 path: ../src/Composer/Util/Http/RequestProxy.php - - message: "#^Constant CURLOPT_PROXY_CAPATH not found\\.$#" + message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy\\:\\:getCurlOptions\\(\\) should return array\\ but returns array\\\\.$#" count: 1 path: ../src/Composer/Util/Http/RequestProxy.php @@ -4618,11 +4618,6 @@ parameters: count: 1 path: ../src/Composer/Util/StreamContextFactory.php - - - message: "#^Only booleans are allowed in an if condition, array given\\.$#" - count: 1 - path: ../src/Composer/Util/StreamContextFactory.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index b91b4e8edb7f..2fabbbeadfd1 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -194,6 +194,8 @@ protected function doDispatch(Event $event) $this->pushEvent($event); + $autoloadersBefore = spl_autoload_functions(); + try { $returnMax = 0; foreach ($listeners as $callable) { @@ -411,6 +413,26 @@ protected function doDispatch(Event $event) } } finally { $this->popEvent(); + + $knownIdentifiers = []; + foreach ($autoloadersBefore as $key => $cb) { + $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb]; + } + foreach (spl_autoload_functions() as $cb) { + // once we get to the first known autoloader, we can leave any appended autoloader without problems + if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) { + break; + } + + // other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first + if ($cb instanceof ClassLoader) { + $cb->unregister(); + $cb->register(false); + } else { + spl_autoload_unregister($cb); + spl_autoload_register($cb); + } + } } return $returnMax; @@ -638,4 +660,20 @@ private function ensureBinDirIsInPath(): void } } } + + private function getCallbackIdentifier(callable $cb): string + { + if (is_string($cb)) { + return 'fn:'.$cb; + } + if (is_object($cb)) { + return 'obj:'.spl_object_hash($cb); + } + if (is_array($cb)) { + return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1]; + } + + // not great but also do not want to break everything here + return 'unsupported'; + } } From acf398281c089dd608ae59a7055efea23b86927f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 2 May 2024 16:06:26 +0200 Subject: [PATCH 069/257] Fix transport exception not always using 255 exit code, fixes #11954 --- src/Composer/Downloader/TransportException.php | 5 +++++ src/Composer/Installer.php | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Composer/Downloader/TransportException.php b/src/Composer/Downloader/TransportException.php index c91897c1c144..5ab5ca6f38d2 100644 --- a/src/Composer/Downloader/TransportException.php +++ b/src/Composer/Downloader/TransportException.php @@ -26,6 +26,11 @@ class TransportException extends \RuntimeException /** @var array */ protected $responseInfo = []; + public function __construct(string $message = "", int $code = 400, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } + /** * @param array $headers */ diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 5d7e7fb5132d..32a14985677d 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -80,6 +80,8 @@ class Installer // used/declared in SolverProblemsException, carried over here for completeness public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; public const ERROR_AUDIT_FAILED = 5; + // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 255 + public const ERROR_TRANSPORT_EXCEPTION = 255; /** * @var IOInterface From 762f2a37f595fce402a3a418f2e2f3d1a70688f8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 2 May 2024 16:49:44 +0200 Subject: [PATCH 070/257] Tweak exit code for network errors to be 100, refs #11954 --- src/Composer/Console/Application.php | 8 ++++++++ src/Composer/Installer.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 7c989098fcc6..7919c29d96c1 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -12,6 +12,7 @@ namespace Composer\Console; +use Composer\Installer; use Composer\IO\NullIO; use Composer\Util\Filesystem; use Composer\Util\Platform; @@ -441,6 +442,13 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow return max(1, $e->getCode()); } + // override TransportException's code for the purpose of parent::run() using it as process exit code + // as http error codes are all beyond the 255 range of permitted exit codes + if ($e instanceof TransportException) { + $reflProp = new \ReflectionProperty($e, 'code'); + $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION); + } + throw $e; } finally { restore_error_handler(); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 32a14985677d..5f07b0e17db7 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -80,8 +80,8 @@ class Installer // used/declared in SolverProblemsException, carried over here for completeness public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; public const ERROR_AUDIT_FAILED = 5; - // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 255 - public const ERROR_TRANSPORT_EXCEPTION = 255; + // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 100 + public const ERROR_TRANSPORT_EXCEPTION = 100; /** * @var IOInterface From 6cb3070203ac40309cf58ee8ce1e290281fa2cc5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 3 May 2024 16:20:07 +0200 Subject: [PATCH 071/257] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb1228011fa..e26dfe69c0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### [2.7.5] 2024-05-03 + + * Added `uninstall` alias to `remove` command (#11951) + * Added workaround for broken curl versions 8.7.0/8.7.1 causing transport exceptions (#11913) + * Fixed root usage warnings showing up within Podman containers (#11946) + * Fixed config command not handling objects correctly in some conditions (#11945) + * Fixed binary proxies not containing the correct path if the project dir is a symlink (#11947) + * Fixed Composer autoloader being overruled by project autoloaders when they are loaded by event handlers (scripts/plugins) (#11955) + * Fixed TransportException (http failures) not having a distinct exit code, should now exit with `100` as code (#11954) + ### [2.7.4] 2024-04-22 * Fixed regression (`Call to undefined method ProxyManager::needsTransitionWarning()`) with projects requiring composer/composer in an pre-2.7.3 version (#11943, #11940) @@ -1856,6 +1866,7 @@ * Initial release +[2.7.5]: https://github.com/composer/composer/compare/2.7.4...2.7.5 [2.7.4]: https://github.com/composer/composer/compare/2.7.3...2.7.4 [2.7.3]: https://github.com/composer/composer/compare/2.7.2...2.7.3 [2.7.2]: https://github.com/composer/composer/compare/2.7.1...2.7.2 From 29ac9cce40969dc2e5c51209d4fe9bdecbbb1d7e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 3 May 2024 16:23:40 +0200 Subject: [PATCH 072/257] Release 2.7.5 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..a21739e88b06 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.5'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-05-03 16:23:40'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From f81e84164e3a148b35e5b3c51f62a362386f7e14 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 3 May 2024 16:23:40 +0200 Subject: [PATCH 073/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index a21739e88b06..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.5'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-05-03 16:23:40'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From c2fd4d3ebb8312bb5b4f288908cf2affae40c8dc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 4 May 2024 23:01:17 +0200 Subject: [PATCH 074/257] Fix private autoloader callbacks breaking the new runtime autoloader handling code --- src/Composer/EventDispatcher/EventDispatcher.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 2fabbbeadfd1..cab8562520ba 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -661,7 +661,10 @@ private function ensureBinDirIsInPath(): void } } - private function getCallbackIdentifier(callable $cb): string + /** + * @param callable $cb DO NOT MOVE TO TYPE HINT as private autoload callbacks are not technically callable + */ + private function getCallbackIdentifier($cb): string { if (is_string($cb)) { return 'fn:'.$cb; From 7b5ee8224076b41f9cf040bb45f9654e65711ee9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 4 May 2024 23:03:05 +0200 Subject: [PATCH 075/257] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e26dfe69c0c6..189759a5a5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### [2.7.6] 2024-05-04 + + * Fixed regression when script handlers add an autoloader which uses a private callback (#11960) + ### [2.7.5] 2024-05-03 * Added `uninstall` alias to `remove` command (#11951) @@ -1866,6 +1870,7 @@ * Initial release +[2.7.6]: https://github.com/composer/composer/compare/2.7.5...2.7.6 [2.7.5]: https://github.com/composer/composer/compare/2.7.4...2.7.5 [2.7.4]: https://github.com/composer/composer/compare/2.7.3...2.7.4 [2.7.3]: https://github.com/composer/composer/compare/2.7.2...2.7.3 From fabd995783b633829fd4280e272284b39b6ae702 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 4 May 2024 23:03:15 +0200 Subject: [PATCH 076/257] Release 2.7.6 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..8d87940c3c2b 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.6'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-05-04 23:03:15'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 2fe3244ddb68aa5260844a57c18ff027e279b83d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 4 May 2024 23:03:15 +0200 Subject: [PATCH 077/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 8d87940c3c2b..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.6'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-05-04 23:03:15'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From 4d7476ca300de5ac1918cfa8b6c011b5ca690cde Mon Sep 17 00:00:00 2001 From: Krzysztof Ciszewski Date: Wed, 8 May 2024 11:19:05 +0200 Subject: [PATCH 078/257] composer#11852 fix: ability to remove autoload* keys (#11967) --- src/Composer/Config/JsonConfigSource.php | 2 +- src/Composer/Json/JsonManipulator.php | 8 ++++++++ tests/Composer/Test/Command/ConfigCommandTest.php | 10 ++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php index db3d36dc4806..596d14f52848 100644 --- a/src/Composer/Config/JsonConfigSource.php +++ b/src/Composer/Config/JsonConfigSource.php @@ -162,7 +162,7 @@ public function addProperty(string $name, $value): void public function removeProperty(string $name): void { $this->manipulateJson('removeProperty', static function (&$config, $key): void { - if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) { + if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0 || stripos($key, 'autoload.') === 0 || stripos($key, 'autoload-dev.') === 0) { $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config[reset($bits)]; diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 2e0f70411693..c6c5eb555649 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -214,6 +214,14 @@ public function removeProperty(string $name): bool return $this->removeSubNode('scripts', substr($name, 8)); } + if (strpos($name, 'autoload.') === 0) { + return $this->removeSubNode('autoload', substr($name, 9)); + } + + if (strpos($name, 'autoload-dev.') === 0) { + return $this->removeSubNode('autoload-dev', substr($name, 13)); + } + return $this->removeMainKey($name); } diff --git a/tests/Composer/Test/Command/ConfigCommandTest.php b/tests/Composer/Test/Command/ConfigCommandTest.php index cf3a55acc79d..9d08f7cc5bf8 100644 --- a/tests/Composer/Test/Command/ConfigCommandTest.php +++ b/tests/Composer/Test/Command/ConfigCommandTest.php @@ -107,6 +107,16 @@ public static function provideConfigUpdates(): \Generator ['setting-key' => 'extra.patches.foo/bar', 'setting-value' => ['{"123":"value"}'], '--json' => true, '--merge' => true], ['extra' => ['patches' => ['foo/bar' => [123 => 'value']]]], ]; + yield 'unset autoload' => [ + ['autoload' => ['psr-4' => ['test'], 'classmap' => ['test']]], + ['setting-key' => 'autoload.psr-4', '--unset' => true], + ['autoload' => ['classmap' => ['test']]], + ]; + yield 'unset autoload-dev' => [ + ['autoload-dev' => ['psr-4' => ['test'], 'classmap' => ['test']]], + ['setting-key' => 'autoload-dev.psr-4', '--unset' => true], + ['autoload-dev' => ['classmap' => ['test']]], + ]; } /** From 8254811c0776c020932a856e04485152ae12328e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 8 May 2024 11:29:27 +0200 Subject: [PATCH 079/257] Update deps --- composer.lock | 87 ++++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/composer.lock b/composer.lock index 2f1cd76936d0..63f0fbabd7aa 100644 --- a/composer.lock +++ b/composer.lock @@ -458,16 +458,16 @@ }, { "name": "composer/xdebug-handler", - "version": "3.0.4", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255", - "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -504,7 +504,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.4" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -520,7 +520,7 @@ "type": "tidelift" } ], - "time": "2024-03-26T18:29:49+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "justinrainbow/json-schema", @@ -938,16 +938,16 @@ }, { "name": "symfony/console", - "version": "v5.4.36", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e" + "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", - "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", + "url": "https://api.github.com/repos/symfony/console/zipball/f3e591c48688a0cfa1a3296205926c05e84b22b1", + "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1", "shasum": "" }, "require": { @@ -1017,7 +1017,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.36" + "source": "https://github.com/symfony/console/tree/v5.4.39" }, "funding": [ { @@ -1033,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2024-02-20T16:33:57+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1104,23 +1104,24 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.38", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "899330a01056077271e2f614c7b28b0379a671eb" + "reference": "e6edd875d5d39b03de51f3c3951148cfa79a4d12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/899330a01056077271e2f614c7b28b0379a671eb", - "reference": "899330a01056077271e2f614c7b28b0379a671eb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e6edd875d5d39b03de51f3c3951148cfa79a4d12", + "reference": "e6edd875d5d39b03de51f3c3951148cfa79a4d12", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php80": "^1.16", + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -1148,7 +1149,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.38" + "source": "https://github.com/symfony/filesystem/tree/v5.4.39" }, "funding": [ { @@ -1164,20 +1165,20 @@ "type": "tidelift" } ], - "time": "2024-03-21T08:05:07+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/finder", - "version": "v5.4.35", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435" + "reference": "f6a96e4fcd468a25fede16ee665f50ced856bd0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/abe6d6f77d9465fed3cd2d029b29d03b56b56435", - "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435", + "url": "https://api.github.com/repos/symfony/finder/zipball/f6a96e4fcd468a25fede16ee665f50ced856bd0a", + "reference": "f6a96e4fcd468a25fede16ee665f50ced856bd0a", "shasum": "" }, "require": { @@ -1211,7 +1212,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.35" + "source": "https://github.com/symfony/finder/tree/v5.4.39" }, "funding": [ { @@ -1227,7 +1228,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1781,16 +1782,16 @@ }, { "name": "symfony/process", - "version": "v5.4.36", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" + "reference": "85a554acd7c28522241faf2e97b9541247a0d3d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", - "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", + "url": "https://api.github.com/repos/symfony/process/zipball/85a554acd7c28522241faf2e97b9541247a0d3d5", + "reference": "85a554acd7c28522241faf2e97b9541247a0d3d5", "shasum": "" }, "require": { @@ -1823,7 +1824,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.36" + "source": "https://github.com/symfony/process/tree/v5.4.39" }, "funding": [ { @@ -1839,7 +1840,7 @@ "type": "tidelift" } ], - "time": "2024-02-12T15:49:53+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/service-contracts", @@ -1926,16 +1927,16 @@ }, { "name": "symfony/string", - "version": "v5.4.36", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b" + "reference": "495e71bae5862308051b9e63cc3e34078eed83ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/4e232c83622bd8cd32b794216aa29d0d266d353b", - "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b", + "url": "https://api.github.com/repos/symfony/string/zipball/495e71bae5862308051b9e63cc3e34078eed83ef", + "reference": "495e71bae5862308051b9e63cc3e34078eed83ef", "shasum": "" }, "require": { @@ -1992,7 +1993,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.36" + "source": "https://github.com/symfony/string/tree/v5.4.39" }, "funding": [ { @@ -2008,7 +2009,7 @@ "type": "tidelift" } ], - "time": "2024-02-01T08:49:30+00:00" + "time": "2024-04-18T08:26:06+00:00" } ], "packages-dev": [ @@ -2293,16 +2294,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v7.0.6", + "version": "v7.0.7", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "a014167aa1f66cb9990675840da65609d3e61612" + "reference": "0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/a014167aa1f66cb9990675840da65609d3e61612", - "reference": "a014167aa1f66cb9990675840da65609d3e61612", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9", + "reference": "0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9", "shasum": "" }, "require": { @@ -2354,7 +2355,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.6" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.7" }, "funding": [ { @@ -2370,7 +2371,7 @@ "type": "tidelift" } ], - "time": "2024-03-19T11:57:22+00:00" + "time": "2024-04-18T09:29:19+00:00" } ], "aliases": [], From 829e0e767ffc4533904612668ea9479dba8f3997 Mon Sep 17 00:00:00 2001 From: Yanick Witschi Date: Wed, 8 May 2024 14:27:20 +0200 Subject: [PATCH 080/257] Re-use precalculated information (#11968) Co-authored-by: Jordi Boggiano --- src/Composer/DependencyResolver/DefaultPolicy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index f8176ae7288b..c608534281c7 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -57,7 +57,7 @@ public function versionCompare(PackageInterface $a, PackageInterface $b, string } // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher - if (strpos($a->getVersion(), 'dev-') === 0 || strpos($b->getVersion(), 'dev-') === 0) { + if (($a->isDev() && str_starts_with($a->getVersion(), 'dev-')) || ($b->isDev() && str_starts_with($b->getVersion(), 'dev-'))) { $constraint = new Constraint($operator, $b->getVersion()); $version = new Constraint('==', $a->getVersion()); From ede152bd65a7425774306b4fff058030427f5b73 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Sun, 12 May 2024 21:55:40 +0100 Subject: [PATCH 081/257] Close style tags to avoid bleed (#11972) --- src/Composer/Command/PackageDiscoveryTrait.php | 2 +- src/Composer/Console/Application.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/PackageDiscoveryTrait.php b/src/Composer/Command/PackageDiscoveryTrait.php index d95e06de8ead..f0aafda807ef 100644 --- a/src/Composer/Command/PackageDiscoveryTrait.php +++ b/src/Composer/Command/PackageDiscoveryTrait.php @@ -96,7 +96,7 @@ final protected function determineRequirements(InputInterface $input, OutputInte foreach ($requires as $requirement) { if (isset($requirement['version']) && Preg::isMatch('{^\d+(\.\d+)?$}', $requirement['version'])) { - $io->writeError('The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints'); + $io->writeError('The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints'); } if (!isset($requirement['version'])) { diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 7919c29d96c1..f1d5d9ffb837 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -404,7 +404,7 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow } if (isset($startTime)) { - $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } if ($proxyManager->needsTransitionWarning()) { From 8d90eb694affe98c696e6195445004f760d6bd21 Mon Sep 17 00:00:00 2001 From: John Stevenson Date: Tue, 21 May 2024 21:42:10 +0100 Subject: [PATCH 082/257] Add uopz warning from installer code (#11988) --- src/Composer/Command/DiagnoseCommand.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index fa5e9aa567b3..06a8a84af014 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -678,6 +678,12 @@ private function checkPlatform() $warnings['onedrive'] = PHP_VERSION; } + if (extension_loaded('uopz') + && !(filter_var(ini_get('uopz.disable'), FILTER_VALIDATE_BOOLEAN) + || filter_var(ini_get('uopz.exit'), FILTER_VALIDATE_BOOLEAN))) { + $warnings['uopz'] = true; + } + if (!empty($errors)) { foreach ($errors as $error => $current) { switch ($error) { @@ -791,6 +797,11 @@ private function checkPlatform() $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL; break; + case 'uopz': + $text = "The uopz extension ignores exit calls and may not work with all Composer commands.".PHP_EOL; + $text .= "Disabling it when using Composer is recommended."; + break; + default: throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning)); } From d4b071bd1e700816fec1167ca973e012eda87864 Mon Sep 17 00:00:00 2001 From: Sam B <130986804+sam-bee@users.noreply.github.com> Date: Tue, 21 May 2024 21:45:37 +0100 Subject: [PATCH 083/257] To enable to the TransportException code to be accessed in PHP < 8.1, make reflection property accessible (#11974) --- src/Composer/Console/Application.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index f1d5d9ffb837..2f3e2c4b1dfb 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -446,6 +446,7 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow // as http error codes are all beyond the 255 range of permitted exit codes if ($e instanceof TransportException) { $reflProp = new \ReflectionProperty($e, 'code'); + $reflProp->setAccessible(true); $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION); } From 5bb30ca170e9f494446c473a1552db80b8ff8f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Wed, 22 May 2024 09:09:04 +0200 Subject: [PATCH 084/257] Update PHPStan (#11976) * Update PHPStan * Update inline ignores to `@phpstan-ignore` with error identifier --- composer.json | 10 +-- composer.lock | 69 +++++++++---------- src/Composer/Command/SelfUpdateCommand.php | 2 +- src/Composer/Console/Input/InputArgument.php | 2 +- src/Composer/Console/Input/InputOption.php | 2 +- src/Composer/Downloader/ZipDownloader.php | 2 +- src/Composer/Installer.php | 2 +- .../PHPStan/ConfigReturnTypeExtension.php | 4 +- src/Composer/Plugin/PostFileDownloadEvent.php | 2 +- .../Repository/ComposerRepository.php | 2 +- src/Composer/Util/Http/CurlDownloader.php | 2 +- src/Composer/Util/Http/Response.php | 4 +- tests/Composer/Test/AllFunctionalTest.php | 2 +- tests/Composer/Test/ConfigTest.php | 2 +- .../Test/DependencyResolver/RuleSetTest.php | 2 +- ...reNothingPlatformRequirementFilterTest.php | 4 +- tests/Composer/Test/IO/NullIOTest.php | 2 +- tests/Composer/Test/InstalledVersionsTest.php | 2 +- tests/Composer/Test/InstallerTest.php | 2 +- .../Composer/Test/Json/JsonFormatterTest.php | 4 +- .../Composer/Test/Mock/HttpDownloaderMock.php | 2 +- tests/Composer/Test/Mock/IOMock.php | 2 +- .../Test/Mock/ProcessExecutorMock.php | 2 +- .../Test/Package/Dumper/ArrayDumperTest.php | 2 +- .../Test/Plugin/PluginInstallerTest.php | 18 ++--- .../Fixtures/installed_relative.php | 10 +-- tests/Composer/Test/Script/EventTest.php | 2 +- tests/Composer/Test/Util/ErrorHandlerTest.php | 4 +- tests/Composer/Test/Util/TlsHelperTest.php | 4 +- 29 files changed, 84 insertions(+), 85 deletions(-) diff --git a/composer.json b/composer.json index 2cbcfa73d7a8..865413970c22 100644 --- a/composer.json +++ b/composer.json @@ -46,11 +46,11 @@ }, "require-dev": { "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1", - "phpstan/phpstan": "^1.9.3", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-strict-rules": "^1", - "phpstan/phpstan-symfony": "^1.2.10" + "phpstan/phpstan": "^1.11.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", diff --git a/composer.lock b/composer.lock index 63f0fbabd7aa..0abf3ae26772 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "bbb9ffc97dcec54f38fdc5b4bf9a287d", + "content-hash": "213c5ab8912ca04b1f7a77c008875ccc", "packages": [ { "name": "composer/ca-bundle", @@ -2015,16 +2015,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.67", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" + "reference": "666cb1703742cea9cc80fee631f0940e1592fa6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", - "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/666cb1703742cea9cc80fee631f0940e1592fa6e", + "reference": "666cb1703742cea9cc80fee631f0940e1592fa6e", "shasum": "" }, "require": { @@ -2069,29 +2069,28 @@ "type": "github" } ], - "time": "2024-04-16T07:22:02+00:00" + "time": "2024-05-13T06:02:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.1.4", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa" + "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa", - "reference": "089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.3" + "phpstan/phpstan": "^1.11" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-php-parser": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, @@ -2115,27 +2114,27 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.1.4" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" }, - "time": "2023-08-05T09:02:04+00:00" + "time": "2024-04-20T06:39:48+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.3.16", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95" + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95", - "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.11" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -2167,27 +2166,27 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" }, - "time": "2024-02-23T09:51:20+00:00" + "time": "2024-04-20T06:39:00+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.5.5", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "2e193a07651a6f4be3baa44ddb21d822681f5918" + "reference": "363f921dd8441777d4fc137deb99beb486c77df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2e193a07651a6f4be3baa44ddb21d822681f5918", - "reference": "2e193a07651a6f4be3baa44ddb21d822681f5918", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", + "reference": "363f921dd8441777d4fc137deb99beb486c77df1", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.60" + "phpstan/phpstan": "^1.11" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -2216,28 +2215,28 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.5" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" }, - "time": "2024-04-19T15:12:26+00:00" + "time": "2024-04-20T06:37:51+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.12", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "f4b9407fa3203aebafd422ae8f0eb1ef94659a80" + "reference": "51183fefbaf4713aa81eddbd273dc59dd5e5ff71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f4b9407fa3203aebafd422ae8f0eb1ef94659a80", - "reference": "f4b9407fa3203aebafd422ae8f0eb1ef94659a80", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/51183fefbaf4713aa81eddbd273dc59dd5e5ff71", + "reference": "51183fefbaf4713aa81eddbd273dc59dd5e5ff71", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.62" + "phpstan/phpstan": "^1.11" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -2288,9 +2287,9 @@ "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.3.12" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.0" }, - "time": "2024-04-14T13:30:23+00:00" + "time": "2024-04-20T06:38:35+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 26d9f75467d8..1a8797a5bc2b 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -329,7 +329,7 @@ class_exists('Composer\Downloader\FilesystemException'); // PHP 8 automatically frees the key instance and deprecates the function if (PHP_VERSION_ID < 80000) { - // @phpstan-ignore-next-line + // @phpstan-ignore function.deprecated openssl_free_key($pubkeyid); } diff --git a/src/Composer/Console/Input/InputArgument.php b/src/Composer/Console/Input/InputArgument.php index b6d064fc82f8..a64724d6be46 100644 --- a/src/Composer/Console/Input/InputArgument.php +++ b/src/Composer/Console/Input/InputArgument.php @@ -59,7 +59,7 @@ public function __construct(string $name, ?int $mode = null, string $description public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; - if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore-line + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); } if ([] !== $values) { diff --git a/src/Composer/Console/Input/InputOption.php b/src/Composer/Console/Input/InputOption.php index 75bfe90d94a9..c742daee7bb0 100644 --- a/src/Composer/Console/Input/InputOption.php +++ b/src/Composer/Console/Input/InputOption.php @@ -62,7 +62,7 @@ public function __construct(string $name, $shortcut = null, ?int $mode = null, s public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; - if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore-line + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); } if ([] !== $values) { diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 9d0f35353e67..cbd7d04528d8 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -35,7 +35,7 @@ class ZipDownloader extends ArchiveDownloader private static $isWindows; /** @var ZipArchive|null */ - private $zipArchiveObject; // @phpstan-ignore-line helper property that is set via reflection for testing purposes + private $zipArchiveObject; // @phpstan-ignore property.onlyRead (helper property that is set via reflection for testing purposes) /** * @inheritDoc diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 5f07b0e17db7..856d441e02ed 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -539,7 +539,7 @@ protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doIns } // exists as of composer/semver 3.3.0 - if (method_exists('Composer\Semver\CompilingMatcher', 'clear')) { // @phpstan-ignore-line + if (method_exists('Composer\Semver\CompilingMatcher', 'clear')) { // @phpstan-ignore function.alreadyNarrowedType \Composer\Semver\CompilingMatcher::clear(); } diff --git a/src/Composer/PHPStan/ConfigReturnTypeExtension.php b/src/Composer/PHPStan/ConfigReturnTypeExtension.php index e7ff6e52e7d6..756ad24b56e5 100644 --- a/src/Composer/PHPStan/ConfigReturnTypeExtension.php +++ b/src/Composer/PHPStan/ConfigReturnTypeExtension.php @@ -72,11 +72,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } $keyType = $scope->getType($args[0]->value); - if (method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore-line - depending on PHPStan version, this method will always exist, or not. + if (method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore function.alreadyNarrowedType (- depending on PHPStan version, this method will always exist, or not.) $strings = $keyType->getConstantStrings(); } else { // for compat with old phpstan versions, we use a deprecated phpstan method. - $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore-line ignore deprecation + $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore staticMethod.deprecated (ignore deprecation) } if ($strings !== []) { $types = []; diff --git a/src/Composer/Plugin/PostFileDownloadEvent.php b/src/Composer/Plugin/PostFileDownloadEvent.php index e789916460dc..a8d9a025f7d3 100644 --- a/src/Composer/Plugin/PostFileDownloadEvent.php +++ b/src/Composer/Plugin/PostFileDownloadEvent.php @@ -59,7 +59,7 @@ class PostFileDownloadEvent extends Event */ public function __construct(string $name, ?string $fileName, ?string $checksum, string $url, string $type, $context = null) { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore instanceof.alwaysFalse, booleanAnd.alwaysFalse */ if ($context === null && $type instanceof PackageInterface) { $context = $type; $type = 'package'; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index ec244b129963..47874f829eea 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -432,7 +432,7 @@ private function getVendorNames(): array $uniques = []; foreach ($names as $name) { - // @phpstan-ignore-next-line + // @phpstan-ignore argument.type $uniques[substr($name, 0, strpos($name, '/'))] = true; } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 827d2a3694e0..d3e68f838393 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -176,7 +176,7 @@ private function initDownload(callable $resolve, callable $reject, string $origi } $errorMessage = ''; - // @phpstan-ignore-next-line + // @phpstan-ignore argument.type set_error_handler(static function ($code, $msg) use (&$errorMessage): void { if ($errorMessage) { $errorMessage .= "\n"; diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php index 5e5d18a2e9a2..0821e054a36d 100644 --- a/src/Composer/Util/Http/Response.php +++ b/src/Composer/Util/Http/Response.php @@ -36,7 +36,7 @@ class Response */ public function __construct(array $request, ?int $code, array $headers, ?string $body) { - if (!isset($request['url'])) { // @phpstan-ignore-line + if (!isset($request['url'])) { throw new \LogicException('url key missing from request array'); } $this->request = $request; @@ -101,7 +101,7 @@ public function decodeJson() */ public function collect(): void { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore assign.propertyType, assign.propertyType, assign.propertyType */ $this->request = $this->code = $this->headers = $this->body = null; } diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 7d68344313c0..7fb22795d436 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -248,7 +248,7 @@ private function parseTestFile(string $file): array throw new \RuntimeException('The test file must have a section named "EXPECT", "EXPECT-REGEX", or "EXPECT-REGEXES".'); } - return $data; // @phpstan-ignore-line + return $data; // @phpstan-ignore return.type } private function cleanOutput(string $output): string diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 8a169b745a3d..08cdfac363e7 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -420,7 +420,7 @@ public function testGetDefaultsToAnEmptyArray(): void ]; foreach ($keys as $key) { $value = $config->get($key); - $this->assertIsArray($value); // @phpstan-ignore-line - PHPStan knows that its an array for all given keys + $this->assertIsArray($value); // @phpstan-ignore staticMethod.dynamicCall (- PHPStan knows that its an array for all given keys) $this->assertCount(0, $value); } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index ee84c877059d..cc8e154c917b 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -68,7 +68,7 @@ public function testAddWhenTypeIsNotRecognized(): void $ruleSet = new RuleSet; self::expectException('OutOfBoundsException'); - // @phpstan-ignore-next-line + // @phpstan-ignore argument.type $ruleSet->add(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]), 7); } diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php index 70c3fabd204d..36fb69ba5601 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php @@ -24,8 +24,8 @@ public function testIsIgnored(string $req): void { $platformRequirementFilter = new IgnoreNothingPlatformRequirementFilter(); - $this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore-line - $this->assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); // @phpstan-ignore-line + $this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore staticMethod.dynamicCall + $this->assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); // @phpstan-ignore staticMethod.dynamicCall } /** diff --git a/tests/Composer/Test/IO/NullIOTest.php b/tests/Composer/Test/IO/NullIOTest.php index 9aa6bc6a91b2..6bc3aa0432c8 100644 --- a/tests/Composer/Test/IO/NullIOTest.php +++ b/tests/Composer/Test/IO/NullIOTest.php @@ -42,7 +42,7 @@ public function testgetAuthentications(): void { $io = new NullIO(); - $this->assertIsArray($io->getAuthentications()); // @phpstan-ignore-line + $this->assertIsArray($io->getAuthentications()); // @phpstan-ignore staticMethod.dynamicCall $this->assertEmpty($io->getAuthentications()); $this->assertEquals(['username' => null, 'password' => null], $io->getAuthentication('foo')); } diff --git a/tests/Composer/Test/InstalledVersionsTest.php b/tests/Composer/Test/InstalledVersionsTest.php index 3fa9b1bc1fc5..7e34f7e37d20 100644 --- a/tests/Composer/Test/InstalledVersionsTest.php +++ b/tests/Composer/Test/InstalledVersionsTest.php @@ -41,7 +41,7 @@ public static function tearDownAfterClass(): void $prop = new \ReflectionProperty('Composer\Autoload\ClassLoader', 'registeredLoaders'); $prop->setAccessible(true); $prop->setValue(null, self::$previousRegisteredLoaders); - InstalledVersions::reload(null); // @phpstan-ignore-line + InstalledVersions::reload(null); // @phpstan-ignore argument.type } public function setUp(): void diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 858d88e7c03c..490eac99606f 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -279,7 +279,7 @@ private function doTestIntegration(string $file, string $message, ?string $condi { if ($condition) { eval('$res = '.$condition.';'); - if (!$res) { // @phpstan-ignore-line + if (!$res) { // @phpstan-ignore variable.undefined $this->markTestSkipped($condition); } } diff --git a/tests/Composer/Test/Json/JsonFormatterTest.php b/tests/Composer/Test/Json/JsonFormatterTest.php index 88f76dfece5d..9bc38c673510 100644 --- a/tests/Composer/Test/Json/JsonFormatterTest.php +++ b/tests/Composer/Test/Json/JsonFormatterTest.php @@ -29,7 +29,7 @@ public function testUnicodeWithPrependedSlash(): void $backslash = chr(92); $data = '"' . $backslash . $backslash . $backslash . 'u0119"'; $expected = '"' . $backslash . $backslash . 'ę"'; - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore staticMethod.dynamicCall, staticMethod.deprecatedClass */ $this->assertEquals($expected, JsonFormatter::format($data, true, true)); } @@ -44,7 +44,7 @@ public function testUtf16SurrogatePair(): void } $escaped = '"\ud83d\ude00"'; - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore staticMethod.dynamicCall, staticMethod.deprecatedClass */ $this->assertEquals($escaped, JsonFormatter::format($escaped, true, true)); } } diff --git a/tests/Composer/Test/Mock/HttpDownloaderMock.php b/tests/Composer/Test/Mock/HttpDownloaderMock.php index 0005a5d87770..a0325118cac7 100644 --- a/tests/Composer/Test/Mock/HttpDownloaderMock.php +++ b/tests/Composer/Test/Mock/HttpDownloaderMock.php @@ -90,7 +90,7 @@ public function assertComplete(): void } // dummy assertion to ensure the test is not marked as having no assertions - Assert::assertTrue(true); // @phpstan-ignore-line + Assert::assertTrue(true); // @phpstan-ignore staticMethod.alreadyNarrowedType } public function get($fileUrl, $options = []): Response diff --git a/tests/Composer/Test/Mock/IOMock.php b/tests/Composer/Test/Mock/IOMock.php index 13a87d1b5ba7..42ff1b258cc8 100644 --- a/tests/Composer/Test/Mock/IOMock.php +++ b/tests/Composer/Test/Mock/IOMock.php @@ -142,7 +142,7 @@ public function assertComplete(): void } // dummy assertion to ensure the test is not marked as having no assertions - Assert::assertTrue(true); // @phpstan-ignore-line + Assert::assertTrue(true); // @phpstan-ignore staticMethod.alreadyNarrowedType } /** diff --git a/tests/Composer/Test/Mock/ProcessExecutorMock.php b/tests/Composer/Test/Mock/ProcessExecutorMock.php index 9f633a6269d7..c90b6bc1dd81 100644 --- a/tests/Composer/Test/Mock/ProcessExecutorMock.php +++ b/tests/Composer/Test/Mock/ProcessExecutorMock.php @@ -100,7 +100,7 @@ public function assertComplete(): void } // dummy assertion to ensure the test is not marked as having no assertions - Assert::assertTrue(true); // @phpstan-ignore-line + Assert::assertTrue(true); // @phpstan-ignore staticMethod.alreadyNarrowedType } public function execute($command, &$output = null, ?string $cwd = null): int diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index a7738a469ba2..ca885243afef 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -81,7 +81,7 @@ public function testKeys(string $key, $value, ?string $method = null, $expectedV { $package = self::getRootPackage(); - // @phpstan-ignore-next-line + // @phpstan-ignore method.dynamicName, ternary.shortNotAllowed $package->{'set'.ucfirst($method ?: $key)}($value); $config = $this->dumper->dump($package); diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index a5eb07d3ecf6..ecaa615a27c2 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -160,7 +160,7 @@ public function testInstallNewPlugin(): void $installer->install($this->repository, $this->packages[0]); $plugins = $this->pm->getPlugins(); - $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore-line + $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound $this->assertEquals( 'activate v1'.PHP_EOL, $this->io->getOutput() @@ -186,7 +186,7 @@ public function testInstallPluginWithRootPackageHavingFilesAutoload(): void 'activate v1'.PHP_EOL, $this->io->getOutput() ); - $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore-line + $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound } public function testInstallMultiplePlugins(): void @@ -201,10 +201,10 @@ public function testInstallMultiplePlugins(): void $installer->install($this->repository, $this->packages[3]); $plugins = $this->pm->getPlugins(); - $this->assertEquals('plugin1', $plugins[0]->name); // @phpstan-ignore-line - $this->assertEquals('installer-v4', $plugins[0]->version); // @phpstan-ignore-line - $this->assertEquals('plugin2', $plugins[1]->name); // @phpstan-ignore-line - $this->assertEquals('installer-v4', $plugins[1]->version); // @phpstan-ignore-line + $this->assertEquals('plugin1', $plugins[0]->name); // @phpstan-ignore staticMethod.dynamicCall, property.notFound + $this->assertEquals('installer-v4', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound + $this->assertEquals('plugin2', $plugins[1]->name); // @phpstan-ignore staticMethod.dynamicCall, property.notFound + $this->assertEquals('installer-v4', $plugins[1]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound $this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput()); } @@ -225,7 +225,7 @@ public function testUpgradeWithNewClassName(): void $plugins = $this->pm->getPlugins(); $this->assertCount(1, $plugins); - $this->assertEquals('installer-v2', $plugins[1]->version); // @phpstan-ignore-line + $this->assertEquals('installer-v2', $plugins[1]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput()); } @@ -265,7 +265,7 @@ public function testUpgradeWithSameClassName(): void $installer->update($this->repository, $this->packages[1], $this->packages[2]); $plugins = $this->pm->getPlugins(); - $this->assertEquals('installer-v3', $plugins[1]->version); // @phpstan-ignore-line + $this->assertEquals('installer-v3', $plugins[1]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound $this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput()); } @@ -283,7 +283,7 @@ public function testRegisterPluginOnlyOneTime(): void $plugins = $this->pm->getPlugins(); $this->assertCount(1, $plugins); - $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore-line + $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); } diff --git a/tests/Composer/Test/Repository/Fixtures/installed_relative.php b/tests/Composer/Test/Repository/Fixtures/installed_relative.php index 443e460c124e..93dc5d89049b 100644 --- a/tests/Composer/Test/Repository/Fixtures/installed_relative.php +++ b/tests/Composer/Test/Repository/Fixtures/installed_relative.php @@ -5,7 +5,7 @@ 'version' => 'dev-master', 'reference' => 'sourceref-by-default', 'type' => 'library', - // @phpstan-ignore-next-line + // @phpstan-ignore variable.undefined 'install_path' => $dir . '/./', 'aliases' => array( '1.10.x-dev', @@ -18,7 +18,7 @@ 'version' => 'dev-master', 'reference' => 'sourceref-by-default', 'type' => 'library', - // @phpstan-ignore-next-line + // @phpstan-ignore variable.undefined 'install_path' => $dir . '/./', 'aliases' => array( '1.10.x-dev', @@ -30,7 +30,7 @@ 'version' => '1.1.0.0', 'reference' => 'distref-as-no-source', 'type' => 'library', - // @phpstan-ignore-next-line + // @phpstan-ignore variable.undefined 'install_path' => $dir . '/vendor/a/provider', 'aliases' => array(), 'dev_requirement' => false, @@ -40,7 +40,7 @@ 'version' => '1.2.0.0', 'reference' => 'distref-as-installed-from-dist', 'type' => 'library', - // @phpstan-ignore-next-line + // @phpstan-ignore variable.undefined 'install_path' => $dir . '/vendor/a/provider2', 'aliases' => array( '1.4', @@ -52,7 +52,7 @@ 'version' => '2.2.0.0', 'reference' => null, 'type' => 'library', - // @phpstan-ignore-next-line + // @phpstan-ignore variable.undefined 'install_path' => $dir . '/vendor/b/replacer', 'aliases' => array(), 'dev_requirement' => false, diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php index c085ad110fec..1647f7169ebf 100644 --- a/tests/Composer/Test/Script/EventTest.php +++ b/tests/Composer/Test/Script/EventTest.php @@ -35,7 +35,7 @@ public function testEventSetsOriginatingEvent(): void $scriptEvent->setOriginatingEvent($originatingEvent); - // @phpstan-ignore-next-line + // @phpstan-ignore staticMethod.dynamicCall $this->assertSame( $originatingEvent, $scriptEvent->getOriginatingEvent(), diff --git a/tests/Composer/Test/Util/ErrorHandlerTest.php b/tests/Composer/Test/Util/ErrorHandlerTest.php index a177f6fe0e9d..29f10463f546 100644 --- a/tests/Composer/Test/Util/ErrorHandlerTest.php +++ b/tests/Composer/Test/Util/ErrorHandlerTest.php @@ -45,7 +45,7 @@ public function testErrorHandlerCaptureNotice(): void } $array = ['foo' => 'bar']; - // @phpstan-ignore-next-line + // @phpstan-ignore offsetAccess.notFound, expr.resultUnused $array['baz']; } @@ -62,7 +62,7 @@ public function testErrorHandlerCaptureWarning(): void self::expectExceptionMessage('array_merge'); } - // @phpstan-ignore-next-line + // @phpstan-ignore function.resultUnused, argument.type array_merge([], 'string'); } diff --git a/tests/Composer/Test/Util/TlsHelperTest.php b/tests/Composer/Test/Util/TlsHelperTest.php index bda7f53ce1c8..58144a425a37 100644 --- a/tests/Composer/Test/Util/TlsHelperTest.php +++ b/tests/Composer/Test/Util/TlsHelperTest.php @@ -27,7 +27,7 @@ public function testCheckCertificateHost(bool $expectedResult, string $hostname, $certificate['subject']['commonName'] = $expectedCn = array_shift($certNames); $certificate['extensions']['subjectAltName'] = $certNames ? 'DNS:'.implode(',DNS:', $certNames) : ''; - // @phpstan-ignore-next-line + // @phpstan-ignore staticMethod.deprecatedClass $result = TlsHelper::checkCertificateHost($certificate, $hostname, $foundCn); if (true === $expectedResult) { @@ -70,7 +70,7 @@ public function testGetCertificateNames(): void $certificate['subject']['commonName'] = 'example.net'; $certificate['extensions']['subjectAltName'] = 'DNS: example.com, IP: 127.0.0.1, DNS: getcomposer.org, Junk: blah, DNS: composer.example.org'; - // @phpstan-ignore-next-line + // @phpstan-ignore staticMethod.deprecatedClass $names = TlsHelper::getCertificateNames($certificate); self::assertIsArray($names); From 818e3fc64b5d4ef3b696321000f97ca0137d7fbc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 24 May 2024 12:59:34 +0200 Subject: [PATCH 085/257] Update deps --- composer.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.lock b/composer.lock index 0abf3ae26772..3159851c3c26 100644 --- a/composer.lock +++ b/composer.lock @@ -527,12 +527,12 @@ "version": "v5.2.13", "source": { "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", + "url": "https://github.com/jsonrainbow/json-schema.git", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", "shasum": "" }, @@ -587,8 +587,8 @@ "schema" ], "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13" + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" }, "time": "2023-09-26T02:20:38+00:00" }, @@ -692,16 +692,16 @@ }, { "name": "react/promise", - "version": "v3.1.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", - "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { @@ -753,7 +753,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.1.0" + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, "funding": [ { @@ -761,7 +761,7 @@ "type": "open_collective" } ], - "time": "2023-11-16T16:21:57+00:00" + "time": "2024-05-24T10:39:05+00:00" }, { "name": "seld/jsonlint", @@ -2015,16 +2015,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "666cb1703742cea9cc80fee631f0940e1592fa6e" + "reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/666cb1703742cea9cc80fee631f0940e1592fa6e", - "reference": "666cb1703742cea9cc80fee631f0940e1592fa6e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e524358f930e41a2b4cca1320e3b04fc26b39e0b", + "reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b", "shasum": "" }, "require": { @@ -2069,7 +2069,7 @@ "type": "github" } ], - "time": "2024-05-13T06:02:22+00:00" + "time": "2024-05-15T08:00:59+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", From 81b121bbdf52917f2ccd058ae840dbefbf80dedb Mon Sep 17 00:00:00 2001 From: Krzysztof Ciszewski Date: Mon, 27 May 2024 14:56:27 +0200 Subject: [PATCH 086/257] Fix composer error when git config safe.bareRepository is set to explicit (#11969) --- src/Composer/Util/ProcessExecutor.php | 84 ++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 25e4c903b57c..b546b1529d78 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -33,6 +33,13 @@ class ProcessExecutor private const STATUS_FAILED = 4; private const STATUS_ABORTED = 5; + private const GIT_CMDS_NEED_GIT_DIR = [ + ['show'], + ['log'], + ['branch'], + ['remote', 'set-url'] + ]; + /** @var int */ protected static $timeout = 300; @@ -97,22 +104,18 @@ public function executeTty($command, ?string $cwd = null): int /** * @param string|list $command + * @param array|null $env * @param mixed $output */ - private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int + private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int { - $this->outputCommandRun($command, $cwd, false); - - $this->captureOutput = func_num_args() > 3; - $this->errorOutput = ''; - if (is_string($command)) { - $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); + $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout()); } else { - $process = new Process($command, $cwd, null, null, static::getTimeout()); + $process = new Process($command, $cwd, $env, null, static::getTimeout()); } - if (!Platform::isWindows() && $tty) { + if (! Platform::isWindows() && $tty) { try { $process->setTty(true); } catch (RuntimeException $e) { @@ -124,11 +127,18 @@ private function doExecute($command, ?string $cwd, bool $tty, &$output = null): $this->outputHandler($type, $buffer); }; - $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal) { - if ($this->io !== null) { - $this->io->writeError('Received '.$signal.', aborting when child process is done', true, IOInterface::DEBUG); + $signalHandler = SignalHandler::create( + [SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], + function (string $signal) { + if ($this->io !== null) { + $this->io->writeError( + 'Received '.$signal.', aborting when child process is done', + true, + IOInterface::DEBUG + ); + } } - }); + ); try { $process->run($callback); @@ -150,6 +160,35 @@ private function doExecute($command, ?string $cwd, bool $tty, &$output = null): return $process->getExitCode(); } + /** + * @param string|list $command + * @param mixed $output + */ + private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int + { + $this->outputCommandRun($command, $cwd, false); + + $this->captureOutput = func_num_args() > 3; + $this->errorOutput = ''; + + $env = null; + + $requiresGitDirEnv = $this->requiresGitDirEnv($command); + if ($cwd !== null && $requiresGitDirEnv) { + $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/'))); + if ($isBareRepository) { + $configValue = ''; + $this->runProcess('git config safe.bareRepository', $cwd, ['GIT_DIR' => $cwd], $tty, $configValue); + $configValue = trim($configValue); + if ($configValue === 'explicit') { + $env = ['GIT_DIR' => $cwd]; + } + } + } + + return $this->runProcess($command, $cwd, $env, $tty, $output); + } + /** * starts a process on the commandline in async mode * @@ -478,4 +517,23 @@ private static function escapeArgument($argument): string return $argument; } + + /** + * @param string[]|string $command + */ + public function requiresGitDirEnv($command): bool + { + $cmd = !is_array($command) ? explode(' ', $command) : $command; + if ($cmd[0] !== 'git') { + return false; + } + + foreach (self::GIT_CMDS_NEED_GIT_DIR as $gitCmd) { + if (array_intersect($cmd, $gitCmd) === $gitCmd) { + return true; + } + } + + return false; + } } From 09e616fa1d50f8c6129124a2b424c0ab93f73715 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 27 May 2024 15:14:10 +0200 Subject: [PATCH 087/257] Update phpstan --- composer.lock | 22 +++++++++++----------- src/Composer/Json/JsonFormatter.php | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index 3159851c3c26..02c5ee3b65ff 100644 --- a/composer.lock +++ b/composer.lock @@ -2015,16 +2015,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.1", + "version": "1.11.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b" + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e524358f930e41a2b4cca1320e3b04fc26b39e0b", - "reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec", + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec", "shasum": "" }, "require": { @@ -2069,7 +2069,7 @@ "type": "github" } ], - "time": "2024-05-15T08:00:59+00:00" + "time": "2024-05-24T13:23:04+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2221,16 +2221,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "51183fefbaf4713aa81eddbd273dc59dd5e5ff71" + "reference": "d530cfebba55763732bc2421f79d2576d9d7942f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/51183fefbaf4713aa81eddbd273dc59dd5e5ff71", - "reference": "51183fefbaf4713aa81eddbd273dc59dd5e5ff71", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/d530cfebba55763732bc2421f79d2576d9d7942f", + "reference": "d530cfebba55763732bc2421f79d2576d9d7942f", "shasum": "" }, "require": { @@ -2287,9 +2287,9 @@ "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.4.0" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.1" }, - "time": "2024-04-20T06:38:35+00:00" + "time": "2024-05-24T14:00:29+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php index 006365675474..417067241f83 100644 --- a/src/Composer/Json/JsonFormatter.php +++ b/src/Composer/Json/JsonFormatter.php @@ -67,7 +67,7 @@ public static function format(string $json, bool $unescapeUnicode, bool $unescap if ($unescapeUnicode && function_exists('mb_convert_encoding')) { // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha - $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match) { + $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match): string { assert(is_string($match[1])); assert(is_string($match[2])); $l = strlen($match[1]); @@ -77,7 +77,7 @@ public static function format(string $json, bool $unescapeUnicode, bool $unescap // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped // see https://github.com/composer/composer/issues/7510 if (0xD800 <= $code && 0xDFFF >= $code) { - return $match[0]; + return (string) $match[0]; } return str_repeat('\\', $l - 1) . mb_convert_encoding( @@ -87,7 +87,7 @@ public static function format(string $json, bool $unescapeUnicode, bool $unescap ); } - return $match[0]; + return (string) $match[0]; }, $buffer); } From f83b6b1026db0f00dd947aef3959a24eb921d8d2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 27 May 2024 17:11:31 +0200 Subject: [PATCH 088/257] Enable new phpstan option --- phpstan/config.neon | 1 + src/Composer/Command/RequireCommand.php | 2 ++ tests/Composer/Test/Command/LicensesCommandTest.php | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/phpstan/config.neon b/phpstan/config.neon index 6ffdbe68e3bd..6790168b916f 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -21,6 +21,7 @@ parameters: reportUnmatchedIgnoredErrors: false treatPhpDocTypesAsCertain: false + reportPossiblyNonexistentConstantArrayOffset: true ignoreErrors: # unused parameters diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index b7ee15681f55..1b6f5e7b8d7f 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -402,6 +402,8 @@ private function getPackagesByRequireKey(): array /** * @param array $requirements + * @param 'require'|'require-dev' $requireKey + * @param 'require'|'require-dev' $removeKey * @throws \Exception */ private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey): int diff --git a/tests/Composer/Test/Command/LicensesCommandTest.php b/tests/Composer/Test/Command/LicensesCommandTest.php index b48e625f3b88..fa92d5496b4f 100644 --- a/tests/Composer/Test/Command/LicensesCommandTest.php +++ b/tests/Composer/Test/Command/LicensesCommandTest.php @@ -78,6 +78,9 @@ public function testBasicRun(): void continue; } + if (!isset($expected[$i])) { + $this->fail('Got more output lines than expected'); + } $this->assertMatchesRegularExpression("/" . implode("\s+", $expected[$i]) . "/", $line); } } @@ -108,6 +111,9 @@ public function testNoDev(): void continue; } + if (!isset($expected[$i])) { + $this->fail('Got more output lines than expected'); + } $this->assertMatchesRegularExpression("/" . implode("\s+", $expected[$i]) . "/", $line); } } From f38df849c24643fd2efbe960f4fa50e815cf7737 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 29 May 2024 12:00:27 +0100 Subject: [PATCH 089/257] BlockedIPs: reject job like other exceptions (#11992) --- src/Composer/Util/Http/CurlDownloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index d3e68f838393..33753dba92a5 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -492,7 +492,7 @@ public function tick(): void is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) && $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip']) ) { - throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url'])); + $this->rejectJob($this->jobs[$i], new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url']))); } $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip']; From 3c37a67c0c062bd2ddfa2cc59f3b39ba346edaf9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 29 May 2024 13:42:12 +0200 Subject: [PATCH 090/257] Fix Filesystem::isLocalPath including windows checks on linux --- src/Composer/Util/Filesystem.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index ad0aae983bb4..a8b1e8ea7488 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -638,7 +638,13 @@ public static function trimTrailingSlash(string $path) */ public static function isLocalPath(string $path) { - return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); + // on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe + // on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path + if (Platform::isWindows()) { + return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); + } + + return Preg::isMatch('{^(file://|/|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); } /** From 3773f775270097b7bdb903111de043f32928db91 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 29 May 2024 15:03:59 +0200 Subject: [PATCH 091/257] Fix perforce arg not being escaped correctly --- src/Composer/Util/Perforce.php | 2 +- tests/Composer/Test/Util/PerforceTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index d24209a42183..ff931158c178 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -342,7 +342,7 @@ public function isLoggedIn(): bool public function connectClient(): void { $p4CreateClientCommand = $this->generateP4Command( - 'client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec()) + 'client -i < ' . ProcessExecutor::escape($this->getP4ClientSpec()) ); $this->executeCommand($p4CreateClientCommand); } diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index 6fd37e559d84..414cca112620 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -328,7 +328,7 @@ public function testIsLoggedIn(): void public function testConnectClient(): void { $this->processExecutor->expects( - ['p4 -u user -c composer_perforce_TEST_depot -p port client -i < path/composer_perforce_TEST_depot.p4.spec'], + ['p4 -u user -c composer_perforce_TEST_depot -p port client -i < '.ProcessExecutor::escape('path/composer_perforce_TEST_depot.p4.spec')], true ); From de5f7e3241333b7098610b594410a2d305b2a032 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 29 May 2024 15:40:58 +0200 Subject: [PATCH 092/257] Fix handling of zip bombs when unzipping archives --- src/Composer/Downloader/ZipDownloader.php | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index cbd7d04528d8..5b6571dc1128 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -144,6 +144,10 @@ private function extractWithSystemUnzip(PackageInterface $package, string $file, throw $processError; } + if (str_contains($processError->getMessage(), 'zip bomb')) { + throw $processError; + } + if (!is_file($file)) { $io->writeError(' '.$processError->getMessage().''); $io->writeError(' This most likely is due to a custom installer plugin not handling the returned Promise from the downloader'); @@ -208,6 +212,26 @@ private function extractWithZipArchive(PackageInterface $package, string $file, } else { $retval = $zipArchive->open($file); } + + $totalSize = 0; + $archiveSize = filesize($file); + $totalFiles = $zipArchive->count(); + if ($totalFiles > 0) { + for ($i = 0; $i < min($totalFiles, 5); $i++) { + $stat = $zipArchive->statIndex(random_int(0, $totalFiles - 1)); + if ($stat === false) { + continue; + } + $totalSize += $stat['size']; + if ($stat['size'] > $stat['comp_size'] * 200) { + throw new \RuntimeException('Invalid zip file with compression ratio >99% (possible zip bomb)'); + } + } + if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50*1024*1024) { + throw new \RuntimeException('Invalid zip file with compression ratio >99% (possible zip bomb)'); + } + } + if (true === $retval) { $extractResult = $zipArchive->extractTo($path); From dd8af946fd89ba0075833a22b3a61a6bcb5095bb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 29 May 2024 22:08:42 +0200 Subject: [PATCH 093/257] Fix tests --- src/Composer/Downloader/ZipDownloader.php | 30 +++++++++---------- .../Test/Downloader/ZipDownloaderTest.php | 6 ++++ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 5b6571dc1128..1904668bea9e 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -213,26 +213,26 @@ private function extractWithZipArchive(PackageInterface $package, string $file, $retval = $zipArchive->open($file); } - $totalSize = 0; - $archiveSize = filesize($file); - $totalFiles = $zipArchive->count(); - if ($totalFiles > 0) { - for ($i = 0; $i < min($totalFiles, 5); $i++) { - $stat = $zipArchive->statIndex(random_int(0, $totalFiles - 1)); - if ($stat === false) { - continue; + if (true === $retval) { + $totalSize = 0; + $archiveSize = filesize($file); + $totalFiles = $zipArchive->count(); + if ($totalFiles > 0) { + for ($i = 0; $i < min($totalFiles, 5); $i++) { + $stat = $zipArchive->statIndex(random_int(0, $totalFiles - 1)); + if ($stat === false) { + continue; + } + $totalSize += $stat['size']; + if ($stat['size'] > $stat['comp_size'] * 200) { + throw new \RuntimeException('Invalid zip file with compression ratio >99% (possible zip bomb)'); + } } - $totalSize += $stat['size']; - if ($stat['size'] > $stat['comp_size'] * 200) { + if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50*1024*1024) { throw new \RuntimeException('Invalid zip file with compression ratio >99% (possible zip bomb)'); } } - if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50*1024*1024) { - throw new \RuntimeException('Invalid zip file with compression ratio >99% (possible zip bomb)'); - } - } - if (true === $retval) { $extractResult = $zipArchive->extractTo($path); if (true === $extractResult) { diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 80e9378d99fd..1155ec6b89fd 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -167,6 +167,9 @@ public function testZipArchiveOnlyGood(): void $zipArchive->expects($this->once()) ->method('extractTo') ->will($this->returnValue(true)); + $zipArchive->expects($this->once()) + ->method('count') + ->will($this->returnValue(0)); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $promise = $downloader->extract($this->package, $this->filename, 'vendor/dir'); @@ -261,6 +264,9 @@ public function testNonWindowsFallbackGood(): void $zipArchive->expects($this->once()) ->method('extractTo') ->will($this->returnValue(true)); + $zipArchive->expects($this->once()) + ->method('count') + ->will($this->returnValue(0)); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); From 37d722e73c847fb74fb9fd542e350cd77997a72d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 29 May 2024 23:12:06 +0200 Subject: [PATCH 094/257] PHPStan/tests updates (#11996) * Remove a bunch of inline ignores and migrate all PHPUnit assertions to static calls * Update baseline (1573, 93) * Update commit hash --- composer.json | 2 +- composer.lock | 2 +- phpstan/baseline.neon | 60 ----- src/Composer/Installer.php | 5 +- .../Repository/ComposerRepository.php | 3 +- src/Composer/Util/Http/CurlDownloader.php | 5 +- src/Composer/Util/Http/Response.php | 3 +- tests/Composer/Test/Advisory/AuditorTest.php | 6 +- tests/Composer/Test/AllFunctionalTest.php | 8 +- tests/Composer/Test/ApplicationTest.php | 4 +- .../Test/Autoload/AutoloadGeneratorTest.php | 250 +++++++++--------- .../Test/Autoload/ClassLoaderTest.php | 4 +- tests/Composer/Test/CacheTest.php | 10 +- .../Test/Command/AboutCommandTest.php | 8 +- .../Test/Command/ArchiveCommandTest.php | 2 +- .../Command/BaseDependencyCommandTest.php | 14 +- .../Composer/Test/Command/BumpCommandTest.php | 12 +- .../Command/CheckPlatformReqsCommandTest.php | 6 +- .../Test/Command/ClearCacheCommandTest.php | 6 +- .../Test/Command/ConfigCommandTest.php | 6 +- .../Test/Command/DiagnoseCommandTest.php | 10 +- .../Test/Command/DumpAutoloadCommandTest.php | 46 ++-- .../Composer/Test/Command/ExecCommandTest.php | 2 +- .../Composer/Test/Command/FundCommandTest.php | 2 +- .../Composer/Test/Command/HomeCommandTest.php | 2 +- .../Composer/Test/Command/InitCommandTest.php | 102 +++---- .../Test/Command/InstallCommandTest.php | 8 +- .../Test/Command/LicensesCommandTest.php | 16 +- .../Test/Command/ReinstallCommandTest.php | 4 +- .../Test/Command/RemoveCommandTest.php | 8 +- .../Test/Command/RequireCommandTest.php | 4 +- .../Test/Command/RunScriptCommandTest.php | 6 +- .../Test/Command/SelfUpdateCommandTest.php | 8 +- .../Test/Command/StatusCommandTest.php | 6 +- .../Test/Command/UpdateCommandTest.php | 2 +- .../Test/Command/ValidateCommandTest.php | 10 +- .../Test/CompletionFunctionalTest.php | 4 +- tests/Composer/Test/ComposerTest.php | 10 +- .../Test/Config/JsonConfigSourceTest.php | 14 +- tests/Composer/Test/ConfigTest.php | 66 ++--- .../Test/Console/HtmlOutputFormatterTest.php | 2 +- tests/Composer/Test/DefaultConfigTest.php | 2 +- .../DependencyResolver/DefaultPolicyTest.php | 32 +-- .../DependencyResolver/PoolBuilderTest.php | 4 +- .../DependencyResolver/PoolOptimizerTest.php | 2 +- .../Test/DependencyResolver/PoolTest.php | 12 +- .../Test/DependencyResolver/RequestTest.php | 4 +- .../RuleSetIteratorTest.php | 4 +- .../Test/DependencyResolver/RuleSetTest.php | 18 +- .../Test/DependencyResolver/RuleTest.php | 24 +- .../Test/DependencyResolver/SolverTest.php | 26 +- .../DependencyResolver/TransactionTest.php | 2 +- .../Test/Downloader/ArchiveDownloaderTest.php | 14 +- .../Test/Downloader/DownloadManagerTest.php | 12 +- .../Test/Downloader/FileDownloaderTest.php | 34 +-- .../Test/Downloader/FossilDownloaderTest.php | 2 +- .../Test/Downloader/GitDownloaderTest.php | 6 +- .../Test/Downloader/HgDownloaderTest.php | 2 +- .../Test/Downloader/XzDownloaderTest.php | 2 +- .../Test/Downloader/ZipDownloaderTest.php | 2 +- .../EventDispatcher/EventDispatcherTest.php | 12 +- ...IgnoreAllPlatformRequirementFilterTest.php | 4 +- ...gnoreListPlatformRequirementFilterTest.php | 4 +- ...reNothingPlatformRequirementFilterTest.php | 4 +- .../PlatformRequirementFilterFactoryTest.php | 6 +- tests/Composer/Test/IO/BufferIOTest.php | 6 +- tests/Composer/Test/IO/ConsoleIOTest.php | 16 +- tests/Composer/Test/IO/NullIOTest.php | 20 +- tests/Composer/Test/InstalledVersionsTest.php | 26 +- .../Test/Installer/BinaryInstallerTest.php | 4 +- .../Installer/InstallationManagerTest.php | 8 +- .../Test/Installer/InstallerEventTest.php | 10 +- .../Test/Installer/LibraryInstallerTest.php | 24 +- .../SuggestedPackagesReporterTest.php | 8 +- tests/Composer/Test/InstallerTest.php | 18 +- .../Composer/Test/Json/ComposerSchemaTest.php | 26 +- tests/Composer/Test/Json/JsonFileTest.php | 68 ++--- .../Composer/Test/Json/JsonFormatterTest.php | 4 +- .../Test/Json/JsonManipulatorTest.php | 207 +++++++-------- .../Test/Json/JsonValidationExceptionTest.php | 6 +- .../Archiver/ArchivableFilesFinderTest.php | 8 +- .../Package/Archiver/ArchiveManagerTest.php | 8 +- .../Package/Archiver/GitExcludeFilterTest.php | 2 +- .../Package/Archiver/PharArchiverTest.php | 4 +- .../Composer/Test/Package/BasePackageTest.php | 4 +- .../Test/Package/CompletePackageTest.php | 24 +- .../Test/Package/Dumper/ArrayDumperTest.php | 14 +- .../Test/Package/Loader/ArrayLoaderTest.php | 90 +++---- .../Package/Loader/RootPackageLoaderTest.php | 14 +- .../Loader/ValidatingArrayLoaderTest.php | 6 +- tests/Composer/Test/Package/LockerTest.php | 16 +- .../Test/Package/RootAliasPackageTest.php | 20 +- .../Package/Version/VersionBumperTest.php | 2 +- .../Package/Version/VersionGuesserTest.php | 66 ++--- .../Package/Version/VersionParserTest.php | 4 +- .../Package/Version/VersionSelectorTest.php | 38 +-- .../Test/Plugin/PluginInstallerTest.php | 78 +++--- .../StrictConfirmationQuestionTest.php | 8 +- .../Test/Repository/ArrayRepositoryTest.php | 42 +-- .../Repository/ArtifactRepositoryTest.php | 10 +- .../Repository/ComposerRepositoryTest.php | 22 +- .../Repository/CompositeRepositoryTest.php | 48 ++-- .../Repository/FilesystemRepositoryTest.php | 12 +- .../Test/Repository/FilterRepositoryTest.php | 10 +- .../Repository/InstalledRepositoryTest.php | 6 +- .../Test/Repository/PathRepositoryTest.php | 32 +-- .../Repository/PlatformRepositoryTest.php | 4 +- .../Test/Repository/RepositoryFactoryTest.php | 4 +- .../Test/Repository/RepositoryManagerTest.php | 48 +++- .../Test/Repository/Vcs/FossilDriverTest.php | 2 +- .../Repository/Vcs/GitBitbucketDriverTest.php | 24 +- .../Test/Repository/Vcs/GitDriverTest.php | 10 +- .../Test/Repository/Vcs/GitHubDriverTest.php | 78 +++--- .../Test/Repository/Vcs/GitLabDriverTest.php | 64 ++--- .../Test/Repository/Vcs/HgDriverTest.php | 6 +- .../Repository/Vcs/PerforceDriverTest.php | 12 +- .../Test/Repository/Vcs/SvnDriverTest.php | 2 +- .../Test/Repository/VcsRepositoryTest.php | 2 +- tests/Composer/Test/Script/EventTest.php | 9 +- tests/Composer/Test/Util/AuthHelperTest.php | 24 +- tests/Composer/Test/Util/BitbucketTest.php | 28 +- .../Test/Util/ConfigValidatorTest.php | 12 +- tests/Composer/Test/Util/FilesystemTest.php | 86 +++--- tests/Composer/Test/Util/GitHubTest.php | 4 +- tests/Composer/Test/Util/GitLabTest.php | 2 +- tests/Composer/Test/Util/GitTest.php | 4 +- .../Composer/Test/Util/HttpDownloaderTest.php | 6 +- tests/Composer/Test/Util/IniHelperTest.php | 14 +- .../Test/Util/MetadataMinifierTest.php | 4 +- .../Composer/Test/Util/NoProxyPatternTest.php | 8 +- tests/Composer/Test/Util/PerforceTest.php | 72 ++--- tests/Composer/Test/Util/PlatformTest.php | 10 +- .../Test/Util/ProcessExecutorTest.php | 34 +-- .../Test/Util/RemoteFilesystemTest.php | 58 ++-- tests/Composer/Test/Util/SilencerTest.php | 4 +- .../Test/Util/StreamContextFactoryTest.php | 24 +- tests/Composer/Test/Util/SvnTest.php | 10 +- tests/Composer/Test/Util/TarTest.php | 8 +- tests/Composer/Test/Util/TlsHelperTest.php | 12 +- tests/Composer/Test/Util/UrlTest.php | 4 +- tests/Composer/Test/Util/ZipTest.php | 10 +- 141 files changed, 1342 insertions(+), 1382 deletions(-) diff --git a/composer.json b/composer.json index 865413970c22..3513b8c58dbf 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "composer/ca-bundle": "^1.0", "composer/class-map-generator": "^1.0", "composer/metadata-minifier": "^1.0", - "composer/semver": "^3.2.5", + "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", "justinrainbow/json-schema": "^5.2.11", diff --git a/composer.lock b/composer.lock index 02c5ee3b65ff..9dc770cf1454 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "213c5ab8912ca04b1f7a77c008875ccc", + "content-hash": "9cc98d4fd8dcc30565a6570841b7ec85", "packages": [ { "name": "composer/ca-bundle", diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index d207f39d1ee9..d3122d737706 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -4018,11 +4018,6 @@ parameters: count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - - message: "#^Only booleans are allowed in &&, int\\<0, 65536\\> given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" count: 1 @@ -4758,11 +4753,6 @@ parameters: count: 1 path: ../tests/Composer/Test/AllFunctionalTest.php - - - message: "#^Dynamic call to static method Composer\\\\Test\\\\Autoload\\\\AutoloadGeneratorTest\\:\\:assertFileContentEquals\\(\\)\\.$#" - count: 28 - path: ../tests/Composer/Test/Autoload/AutoloadGeneratorTest.php - - message: "#^Dynamic call to static method Composer\\\\Test\\\\TestCase\\:\\:ensureDirectoryExistsAndClear\\(\\)\\.$#" count: 1 @@ -4941,16 +4931,6 @@ parameters: count: 3 path: ../tests/Composer/Test/Installer/BinaryInstallerTest.php - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'Composer\\\\\\\\Composer' and Composer\\\\Composer will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Installer/InstallerEventTest.php - - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'Composer\\\\\\\\IO…' and Composer\\\\IO\\\\IOInterface will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Installer/InstallerEventTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 9 @@ -5021,11 +5001,6 @@ parameters: count: 1 path: ../tests/Composer/Test/Json/JsonManipulatorTest.php - - - message: "#^Parameter \\#1 \\$expectedJson of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertJsonStringEqualsJsonString\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../tests/Composer/Test/Json/JsonManipulatorTest.php - - message: "#^Offset 'ask' might not exist on array\\{ask\\: string, reply\\?\\: string\\}\\|array\\{auth\\: array\\{string, string, string\\|null\\}\\}\\|array\\{text\\: string, verbosity\\?\\: 1\\|2\\|4\\|8\\|16\\}\\.$#" count: 2 @@ -5091,11 +5066,6 @@ parameters: count: 1 path: ../tests/Composer/Test/Package/Dumper/ArrayDumperTest.php - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with true will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" count: 1 @@ -5156,11 +5126,6 @@ parameters: count: 1 path: ../tests/Composer/Test/Repository/PathRepositoryTest.php - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'Composer\\\\\\\\Repository…' and Composer\\\\Repository\\\\RepositoryInterface will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Repository/RepositoryManagerTest.php - - message: "#^Parameter \\#1 \\$objectOrValue of method ReflectionProperty\\:\\:setValue\\(\\) expects object\\|null, object\\|string given\\.$#" count: 1 @@ -5231,31 +5196,16 @@ parameters: count: 2 path: ../tests/Composer/Test/Util/PerforceTest.php - - - message: "#^Parameter \\#2 \\$string of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringStartsWith\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: ../tests/Composer/Test/Util/PerforceTest.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 path: ../tests/Composer/Test/Util/PlatformTest.php - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNotNull\\(\\) with string will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/ProcessExecutorTest.php - - message: "#^Dynamic call to static method Composer\\\\Util\\\\ProcessExecutor\\:\\:getTimeout\\(\\)\\.$#" count: 1 path: ../tests/Composer/Test/Util/ProcessExecutorTest.php - - - message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with true and 'callbackGet must…' will always evaluate to true\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/RemoteFilesystemTest.php - - message: "#^Parameter \\#1 \\$object of method ReflectionProperty\\:\\:getValue\\(\\) expects object, object\\|string given\\.$#" count: 1 @@ -5271,16 +5221,6 @@ parameters: count: 2 path: ../tests/Composer/Test/Util/RemoteFilesystemTest.php - - - message: "#^Parameter \\#2 \\$haystack of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, bool\\|string given\\.$#" - count: 1 - path: ../tests/Composer/Test/Util/RemoteFilesystemTest.php - - - - message: "#^Parameter \\#2 \\$haystack of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: ../tests/Composer/Test/Util/RemoteFilesystemTest.php - - message: "#^Implicit array creation is not allowed \\- variable \\$certificate does not exist\\.$#" count: 2 diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 856d441e02ed..c0d2ddb2e114 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -538,10 +538,7 @@ protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doIns return $exitCode; } - // exists as of composer/semver 3.3.0 - if (method_exists('Composer\Semver\CompilingMatcher', 'clear')) { // @phpstan-ignore function.alreadyNarrowedType - \Composer\Semver\CompilingMatcher::clear(); - } + \Composer\Semver\CompilingMatcher::clear(); // write lock $platformReqs = $this->extractPlatformRequirements($this->package->getRequires()); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 47874f829eea..529279b1f136 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -432,8 +432,7 @@ private function getVendorNames(): array $uniques = []; foreach ($names as $name) { - // @phpstan-ignore argument.type - $uniques[substr($name, 0, strpos($name, '/'))] = true; + $uniques[explode('/', $name, 2)[0]] = true; } $vendors = array_keys($uniques); diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 33753dba92a5..8e755551accd 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -176,12 +176,13 @@ private function initDownload(callable $resolve, callable $reject, string $origi } $errorMessage = ''; - // @phpstan-ignore argument.type - set_error_handler(static function ($code, $msg) use (&$errorMessage): void { + set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg); + + return true; }); $bodyHandle = fopen($bodyTarget, 'w+b'); restore_error_handler(); diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php index 0821e054a36d..e355f256ab6f 100644 --- a/src/Composer/Util/Http/Response.php +++ b/src/Composer/Util/Http/Response.php @@ -101,8 +101,7 @@ public function decodeJson() */ public function collect(): void { - /** @phpstan-ignore assign.propertyType, assign.propertyType, assign.propertyType */ - $this->request = $this->code = $this->headers = $this->body = null; + unset($this->request, $this->code, $this->headers, $this->body); } /** diff --git a/tests/Composer/Test/Advisory/AuditorTest.php b/tests/Composer/Test/Advisory/AuditorTest.php index 748f6a5f8055..5e66951a0a37 100644 --- a/tests/Composer/Test/Advisory/AuditorTest.php +++ b/tests/Composer/Test/Advisory/AuditorTest.php @@ -158,8 +158,8 @@ public function testAudit(array $data, int $expected, string $output): void } $auditor = new Auditor(); $result = $auditor->audit($io = new BufferIO(), $this->getRepoSet(), $data['packages'], $data['format'] ?? Auditor::FORMAT_PLAIN, $data['warningOnly'], [], $data['abandoned'] ?? Auditor::ABANDONED_IGNORE); - $this->assertSame($expected, $result); - $this->assertSame($output, trim(str_replace("\r", '', $io->getOutput()))); + self::assertSame($expected, $result); + self::assertSame($output, trim(str_replace("\r", '', $io->getOutput()))); } public function ignoredIdsProvider(): \Generator { @@ -311,7 +311,7 @@ public function testAuditWithIgnore($packages, $ignoredIds, $exitCode, $expected $auditor = new Auditor(); $result = $auditor->audit($io = $this->getIOMock(), $this->getRepoSet(), $packages, Auditor::FORMAT_PLAIN, false, $ignoredIds); $io->expects($expectedOutput, true); - $this->assertSame($exitCode, $result); + self::assertSame($exitCode, $result); } private function getRepoSet(): RepositorySet diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 7fb22795d436..452862273b38 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -93,7 +93,7 @@ public function testBuildPhar(): void $this->fail($proc->getOutput()); } - $this->assertFileExists(self::$pharPath); + self::assertFileExists(self::$pharPath); copy(self::$pharPath, __DIR__.'/../../composer-test.phar'); } @@ -164,16 +164,16 @@ public function testIntegration(string $testFile): void } } if (isset($testData['EXPECT-REGEX'])) { - $this->assertMatchesRegularExpression($testData['EXPECT-REGEX'], $this->cleanOutput($output)); + self::assertMatchesRegularExpression($testData['EXPECT-REGEX'], $this->cleanOutput($output)); } if (isset($testData['EXPECT-REGEXES'])) { $cleanOutput = $this->cleanOutput($output); foreach (explode("\n", $testData['EXPECT-REGEXES']) as $regex) { - $this->assertMatchesRegularExpression($regex, $cleanOutput, 'Output: '.$output); + self::assertMatchesRegularExpression($regex, $cleanOutput, 'Output: '.$output); } } if (isset($testData['EXPECT-EXIT-CODE'])) { - $this->assertSame($testData['EXPECT-EXIT-CODE'], $exitCode); + self::assertSame($testData['EXPECT-EXIT-CODE'], $exitCode); } } diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 0cd0d1b3886e..8e4bce1b51db 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -49,7 +49,7 @@ public function testDevWarning(): void $application->doRun(new ArrayInput(['command' => 'about']), $output); $expectedOutput = sprintf('Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF']).PHP_EOL; - $this->assertStringContainsString($expectedOutput, $output->fetch()); + self::assertStringContainsString($expectedOutput, $output->fetch()); } /** @@ -72,6 +72,6 @@ public function testDevWarningSuppressedForSelfUpdate(): void $output = new BufferedOutput(); $application->doRun(new ArrayInput(['command' => 'self-update']), $output); - $this->assertSame('', $output->fetch()); + self::assertSame('', $output->fetch()); } } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 08d51ac749dd..67930eabb04f 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -210,13 +210,13 @@ public function testRootPackageAutoloading(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); // Assert that autoload_namespaces.php was correctly generated. - $this->assertAutoloadFiles('main', $this->vendorDir.'/composer'); + self::assertAutoloadFiles('main', $this->vendorDir.'/composer'); // Assert that autoload_psr4.php was correctly generated. - $this->assertAutoloadFiles('psr4', $this->vendorDir.'/composer', 'psr4'); + self::assertAutoloadFiles('psr4', $this->vendorDir.'/composer', 'psr4'); // Assert that autoload_classmap.php was correctly generated. - $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); } public function testRootPackageDevAutoloading(): void @@ -250,11 +250,11 @@ public function testRootPackageDevAutoloading(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); // check standard autoload - $this->assertAutoloadFiles('main5', $this->vendorDir.'/composer'); - $this->assertAutoloadFiles('classmap7', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('main5', $this->vendorDir.'/composer'); + self::assertAutoloadFiles('classmap7', $this->vendorDir.'/composer', 'classmap'); // make sure dev autoload is correctly dumped - $this->assertAutoloadFiles('files2', $this->vendorDir.'/composer', 'files'); + self::assertAutoloadFiles('files2', $this->vendorDir.'/composer', 'files'); } public function testRootPackageDevAutoloadingDisabledByDefault(): void @@ -283,11 +283,11 @@ public function testRootPackageDevAutoloadingDisabledByDefault(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); // check standard autoload - $this->assertAutoloadFiles('main4', $this->vendorDir.'/composer'); - $this->assertAutoloadFiles('classmap7', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('main4', $this->vendorDir.'/composer'); + self::assertAutoloadFiles('classmap7', $this->vendorDir.'/composer', 'classmap'); // make sure dev autoload is disabled when dev mode is set to false - $this->assertFalse(is_file($this->vendorDir.'/composer/autoload_files.php')); + self::assertFalse(is_file($this->vendorDir.'/composer/autoload_files.php')); } public function testVendorDirSameAsWorkingDir(): void @@ -316,9 +316,9 @@ public function testVendorDirSameAsWorkingDir(): void file_put_contents($this->vendorDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_2'); - $this->assertAutoloadFiles('main3', $this->vendorDir.'/composer'); - $this->assertAutoloadFiles('psr4_3', $this->vendorDir.'/composer', 'psr4'); - $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('main3', $this->vendorDir.'/composer'); + self::assertAutoloadFiles('psr4_3', $this->vendorDir.'/composer', 'psr4'); + self::assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap'); } public function testRootPackageAutoloadingAlternativeVendorDir(): void @@ -345,9 +345,9 @@ public function testRootPackageAutoloadingAlternativeVendorDir(): void $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc'); file_put_contents($this->workingDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_3'); - $this->assertAutoloadFiles('main2', $this->vendorDir.'/composer'); - $this->assertAutoloadFiles('psr4_2', $this->vendorDir.'/composer', 'psr4'); - $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('main2', $this->vendorDir.'/composer'); + self::assertAutoloadFiles('psr4_2', $this->vendorDir.'/composer', 'psr4'); + self::assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap'); } public function testRootPackageAutoloadingWithTargetDir(): void @@ -374,11 +374,11 @@ public function testRootPackageAutoloadingWithTargetDir(): void file_put_contents($this->workingDir.'/bar.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'TargetDir'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_target_dir.php', $this->vendorDir.'/autoload.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_target_dir.php', $this->vendorDir.'/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_target_dir.php', $this->vendorDir.'/composer/autoload_static.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_target_dir.php', $this->vendorDir.'/composer/autoload_files.php'); - $this->assertAutoloadFiles('classmap6', $this->vendorDir.'/composer', 'classmap'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_target_dir.php', $this->vendorDir.'/autoload.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_target_dir.php', $this->vendorDir.'/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_target_dir.php', $this->vendorDir.'/composer/autoload_static.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_target_dir.php', $this->vendorDir.'/composer/autoload_files.php'); + self::assertAutoloadFiles('classmap6', $this->vendorDir.'/composer', 'classmap'); } public function testDuplicateFilesWarning(): void @@ -431,8 +431,8 @@ public function testVendorsAutoloading(): void $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); - $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + self::assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } public function testVendorsAutoloadingWithMetapackages(): void @@ -465,8 +465,8 @@ public function testVendorsAutoloadingWithMetapackages(): void $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); - $this->assertAutoloadFiles('vendors_meta', $this->vendorDir.'/composer'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + self::assertAutoloadFiles('vendors_meta', $this->vendorDir.'/composer'); + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } public function testNonDevAutoloadExclusionWithRecursion(): void @@ -498,8 +498,8 @@ public function testNonDevAutoloadExclusionWithRecursion(): void $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); - $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + self::assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } public function testNonDevAutoloadShouldIncludeReplacedPackages(): void @@ -527,7 +527,7 @@ public function testNonDevAutoloadShouldIncludeReplacedPackages(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_5'); - $this->assertEquals( + self::assertEquals( [ 'B\\C\\C' => $this->vendorDir.'/b/b/src/C/C.php', 'Composer\\InstalledVersions' => $this->vendorDir . '/composer/InstalledVersions.php', @@ -565,8 +565,8 @@ public function testNonDevAutoloadExclusionWithRecursionReplace(): void $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); - $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + self::assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } public function testNonDevAutoloadReplacesNestedRequirements(): void @@ -618,7 +618,7 @@ public function testNonDevAutoloadReplacesNestedRequirements(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); - $this->assertAutoloadFiles('classmap9', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('classmap9', $this->vendorDir.'/composer', 'classmap'); } public function testPharAutoload(): void @@ -657,9 +657,9 @@ public function testPharAutoload(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, 'Phar'); - $this->assertAutoloadFiles('phar', $this->vendorDir . '/composer'); - $this->assertAutoloadFiles('phar_psr4', $this->vendorDir . '/composer', 'psr4'); - $this->assertAutoloadFiles('phar_static', $this->vendorDir . '/composer', 'static'); + self::assertAutoloadFiles('phar', $this->vendorDir . '/composer'); + self::assertAutoloadFiles('phar_psr4', $this->vendorDir . '/composer', 'psr4'); + self::assertAutoloadFiles('phar_static', $this->vendorDir . '/composer', 'static'); } public function testPSRToClassMapIgnoresNonExistingDir(): void @@ -676,8 +676,8 @@ public function testPSRToClassMapIgnoresNonExistingDir(): void ->will($this->returnValue([])); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_8'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); - $this->assertEquals( + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertEquals( [ 'Composer\\InstalledVersions' => $this->vendorDir.'/composer/InstalledVersions.php', ], @@ -706,7 +706,7 @@ public function testPSRToClassMapIgnoresNonPSRClasses(): void file_put_contents($this->workingDir.'/psr4/badfile.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $expectedClassmap = <<assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); } public function testVendorsClassMapAutoloading(): void @@ -753,8 +753,8 @@ public function testVendorsClassMapAutoloading(): void file_put_contents($this->vendorDir.'/b/b/lib/c.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_6'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); - $this->assertEquals( + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertEquals( [ 'ClassMapBar' => $this->vendorDir.'/b/b/src/b.php', 'ClassMapBaz' => $this->vendorDir.'/b/b/lib/c.php', @@ -763,7 +763,7 @@ public function testVendorsClassMapAutoloading(): void ], include $this->vendorDir.'/composer/autoload_classmap.php' ); - $this->assertAutoloadFiles('classmap4', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('classmap4', $this->vendorDir.'/composer', 'classmap'); } public function testVendorsClassMapAutoloadingWithTargetDir(): void @@ -794,8 +794,8 @@ public function testVendorsClassMapAutoloadingWithTargetDir(): void file_put_contents($this->vendorDir.'/b/b/src/c.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_6'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); - $this->assertEquals( + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertEquals( [ 'ClassMapBar' => $this->vendorDir.'/a/a/target/lib/b.php', 'ClassMapBaz' => $this->vendorDir.'/b/b/src/c.php', @@ -836,8 +836,8 @@ public function testClassMapAutoloadingEmptyDirAndExactFile(): void file_put_contents($this->vendorDir.'/c/c/foo/test.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); - $this->assertEquals( + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertEquals( [ 'ClassMapBar' => $this->vendorDir.'/b/b/test.php', 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php', @@ -846,9 +846,9 @@ public function testClassMapAutoloadingEmptyDirAndExactFile(): void ], include $this->vendorDir.'/composer/autoload_classmap.php' ); - $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap'); - $this->assertStringNotContainsString('$loader->setClassMapAuthoritative(true);', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); - $this->assertStringNotContainsString('$loader->setApcuPrefix(', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap'); + self::assertStringNotContainsString('$loader->setClassMapAuthoritative(true);', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertStringNotContainsString('$loader->setApcuPrefix(', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } public function testClassMapAutoloadingAuthoritativeAndApcu(): void @@ -884,8 +884,8 @@ public function testClassMapAutoloadingAuthoritativeAndApcu(): void $this->generator->setApcu(true); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); - $this->assertEquals( + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertEquals( [ 'ClassMapBar' => $this->vendorDir.'/b/b/ClassMapBar.php', 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/ClassMapBaz.php', @@ -894,10 +894,10 @@ public function testClassMapAutoloadingAuthoritativeAndApcu(): void ], include $this->vendorDir.'/composer/autoload_classmap.php' ); - $this->assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap'); - $this->assertStringContainsString('$loader->setClassMapAuthoritative(true);', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); - $this->assertStringContainsString('$loader->setApcuPrefix(', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertStringContainsString('$loader->setClassMapAuthoritative(true);', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertStringContainsString('$loader->setApcuPrefix(', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } public function testClassMapAutoloadingAuthoritativeAndApcuPrefix(): void @@ -933,8 +933,8 @@ public function testClassMapAutoloadingAuthoritativeAndApcuPrefix(): void $this->generator->setApcu(true, 'custom\'Prefix'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); - $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); - $this->assertEquals( + self::assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); + self::assertEquals( [ 'ClassMapBar' => $this->vendorDir.'/b/b/ClassMapBar.php', 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/ClassMapBaz.php', @@ -943,10 +943,10 @@ public function testClassMapAutoloadingAuthoritativeAndApcuPrefix(): void ], include $this->vendorDir.'/composer/autoload_classmap.php' ); - $this->assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap'); - $this->assertStringContainsString('$loader->setClassMapAuthoritative(true);', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); - $this->assertStringContainsString('$loader->setApcuPrefix(\'custom\\\'Prefix\');', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertStringContainsString('$loader->setClassMapAuthoritative(true);', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertStringContainsString('$loader->setApcuPrefix(\'custom\\\'Prefix\');', (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } public function testFilesAutoloadGeneration(): void @@ -982,18 +982,18 @@ public function testFilesAutoloadGeneration(): void file_put_contents($this->workingDir.'/root.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'FilesAutoload'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions.php', $this->vendorDir.'/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions.php', $this->vendorDir.'/composer/autoload_static.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions.php', $this->vendorDir.'/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions.php', $this->vendorDir.'/composer/autoload_static.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); $loader = require $this->vendorDir . '/autoload.php'; $loader->unregister(); - $this->assertTrue(function_exists('testFilesAutoloadGeneration1')); - $this->assertTrue(function_exists('testFilesAutoloadGeneration2')); - $this->assertTrue(function_exists('testFilesAutoloadGeneration3')); - $this->assertTrue(function_exists('testFilesAutoloadGeneration4')); - $this->assertTrue(function_exists('testFilesAutoloadGenerationRoot')); + self::assertTrue(function_exists('testFilesAutoloadGeneration1')); + self::assertTrue(function_exists('testFilesAutoloadGeneration2')); + self::assertTrue(function_exists('testFilesAutoloadGeneration3')); + self::assertTrue(function_exists('testFilesAutoloadGeneration4')); + self::assertTrue(function_exists('testFilesAutoloadGenerationRoot')); } public function testFilesAutoloadGenerationRemoveExtraEntitiesFromAutoloadFiles(): void @@ -1047,24 +1047,24 @@ public function testFilesAutoloadGenerationRemoveExtraEntitiesFromAutoloadFiles( file_put_contents($this->workingDir.'/root.php', 'generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_static.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/include_paths_functions.php', $this->vendorDir.'/composer/include_paths.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_static.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/include_paths_functions.php', $this->vendorDir.'/composer/include_paths.php'); $this->generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions_with_removed_extra.php', $this->vendorDir.'/composer/autoload_files.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/include_paths_functions_with_removed_extra.php', $this->vendorDir.'/composer/include_paths.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions_with_removed_extra.php', $this->vendorDir.'/composer/autoload_files.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/include_paths_functions_with_removed_extra.php', $this->vendorDir.'/composer/include_paths.php'); $this->generator->dump($this->config, $this->repository, $notAutoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_static.php'); - $this->assertFileDoesNotExist($this->vendorDir.'/composer/autoload_files.php'); - $this->assertFileDoesNotExist($this->vendorDir.'/composer/include_paths.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_static.php'); + self::assertFileDoesNotExist($this->vendorDir.'/composer/autoload_files.php'); + self::assertFileDoesNotExist($this->vendorDir.'/composer/include_paths.php'); } public function testFilesAutoloadOrderByDependencies(): void @@ -1124,19 +1124,19 @@ public function testFilesAutoloadOrderByDependencies(): void file_put_contents($this->workingDir . '/root2.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'FilesAutoloadOrder'); - $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_functions_by_dependency.php', $this->vendorDir . '/autoload.php'); - $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_real_files_by_dependency.php', $this->vendorDir . '/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_static_files_by_dependency.php', $this->vendorDir . '/composer/autoload_static.php'); + self::assertFileContentEquals(__DIR__ . '/Fixtures/autoload_functions_by_dependency.php', $this->vendorDir . '/autoload.php'); + self::assertFileContentEquals(__DIR__ . '/Fixtures/autoload_real_files_by_dependency.php', $this->vendorDir . '/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__ . '/Fixtures/autoload_static_files_by_dependency.php', $this->vendorDir . '/composer/autoload_static.php'); $loader = require $this->vendorDir . '/autoload.php'; $loader->unregister(); - $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency1')); - $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency2')); - $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency3')); - $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency4')); - $this->assertTrue(function_exists('testFilesAutoloadOrderByDependency5')); - $this->assertTrue(function_exists('testFilesAutoloadOrderByDependencyRoot')); + self::assertTrue(function_exists('testFilesAutoloadOrderByDependency1')); + self::assertTrue(function_exists('testFilesAutoloadOrderByDependency2')); + self::assertTrue(function_exists('testFilesAutoloadOrderByDependency3')); + self::assertTrue(function_exists('testFilesAutoloadOrderByDependency4')); + self::assertTrue(function_exists('testFilesAutoloadOrderByDependency5')); + self::assertTrue(function_exists('testFilesAutoloadOrderByDependencyRoot')); } /** @@ -1235,9 +1235,9 @@ public function testOverrideVendorsAutoloading(): void EOF; $this->generator->dump($this->config, $this->repository, $rootPackage, $this->im, 'composer', true, '_9'); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); } public function testIncludePathFileGeneration(): void @@ -1266,8 +1266,8 @@ public function testIncludePathFileGeneration(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_10'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/include_paths.php', $this->vendorDir.'/composer/include_paths.php'); - $this->assertEquals( + self::assertFileContentEquals(__DIR__.'/Fixtures/include_paths.php', $this->vendorDir.'/composer/include_paths.php'); + self::assertEquals( [ $this->vendorDir."/a/a/lib", $this->vendorDir."/b/b/library", @@ -1300,7 +1300,7 @@ public function testIncludePathsArePrependedInAutoloadFile(): void $loader = require $this->vendorDir."/autoload.php"; $loader->unregister(); - $this->assertEquals( + self::assertEquals( $this->vendorDir."/a/a/lib".PATH_SEPARATOR.$oldIncludePath, get_include_path() ); @@ -1329,7 +1329,7 @@ public function testIncludePathsInRootPackage(): void $loader = require $this->vendorDir."/autoload.php"; $loader->unregister(); - $this->assertEquals( + self::assertEquals( $this->workingDir."/lib".PATH_SEPARATOR.$this->workingDir."/src".PATH_SEPARATOR.$this->vendorDir."/a/a/lib".PATH_SEPARATOR.$oldIncludePath, get_include_path() ); @@ -1353,7 +1353,7 @@ public function testIncludePathFileWithoutPathsIsSkipped(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_12'); - $this->assertFileDoesNotExist($this->vendorDir."/composer/include_paths.php"); + self::assertFileDoesNotExist($this->vendorDir."/composer/include_paths.php"); } public function testPreAndPostEventsAreDispatchedDuringAutoloadDump(): void @@ -1367,7 +1367,7 @@ public function testPreAndPostEventsAreDispatchedDuringAutoloadDump(): void [ScriptEvents::POST_AUTOLOAD_DUMP, false] ]; - $this->assertSame(array_shift($series), [$type, $dev]); + self::assertSame(array_shift($series), [$type, $dev]); return 0; }); @@ -1400,8 +1400,8 @@ public function testUseGlobalIncludePath(): void $this->fs->ensureDirectoryExists($this->vendorDir.'/a'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'IncludePath'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_include_path.php', $this->vendorDir.'/composer/autoload_real.php'); - $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_include_path.php', $this->vendorDir.'/composer/autoload_static.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_include_path.php', $this->vendorDir.'/composer/autoload_real.php'); + self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_include_path.php', $this->vendorDir.'/composer/autoload_static.php'); } public function testVendorDirExcludedFromWorkingDir(): void @@ -1511,11 +1511,11 @@ public function testVendorDirExcludedFromWorkingDir(): void EOF; - $this->assertStringEqualsFile($vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); - $this->assertStringEqualsFile($vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); - $this->assertStringEqualsFile($vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); - $this->assertStringContainsString("\$vendorDir . '/b/b/bootstrap.php',\n", (string) file_get_contents($vendorDir.'/composer/autoload_files.php')); - $this->assertStringContainsString("\$baseDir . '/test.php',\n", (string) file_get_contents($vendorDir.'/composer/autoload_files.php')); + self::assertStringEqualsFile($vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); + self::assertStringEqualsFile($vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); + self::assertStringEqualsFile($vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); + self::assertStringContainsString("\$vendorDir . '/b/b/bootstrap.php',\n", (string) file_get_contents($vendorDir.'/composer/autoload_files.php')); + self::assertStringContainsString("\$baseDir . '/test.php',\n", (string) file_get_contents($vendorDir.'/composer/autoload_files.php')); } public function testUpLevelRelativePaths(): void @@ -1596,10 +1596,10 @@ public function testUpLevelRelativePaths(): void EOF; - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); - $this->assertStringContainsString("\$baseDir . '/../test.php',\n", (string) file_get_contents($this->vendorDir.'/composer/autoload_files.php')); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); + self::assertStringContainsString("\$baseDir . '/../test.php',\n", (string) file_get_contents($this->vendorDir.'/composer/autoload_files.php')); } public function testAutoloadRulesInPackageThatDoesNotExistOnDisk(): void @@ -1632,7 +1632,7 @@ public function testAutoloadRulesInPackageThatDoesNotExistOnDisk(): void ); EOF; - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); $dep->setAutoload([ 'psr-4' => ['Acme\Foo\\' => './src-psr4'], @@ -1652,7 +1652,7 @@ public function testAutoloadRulesInPackageThatDoesNotExistOnDisk(): void ); EOF; - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); $dep->setAutoload([ 'classmap' => ['classmap'], @@ -1660,14 +1660,14 @@ public function testAutoloadRulesInPackageThatDoesNotExistOnDisk(): void try { $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_19'); } catch (\RuntimeException $e) { - $this->assertSame('Could not scan for classes inside "'.$this->vendorDir.'/dep/a/classmap" which does not appear to be a file nor a folder', $e->getMessage()); + self::assertSame('Could not scan for classes inside "'.$this->vendorDir.'/dep/a/classmap" which does not appear to be a file nor a folder', $e->getMessage()); } $dep->setAutoload([ 'files' => ['./test.php'], ]); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_19'); - $this->assertStringContainsString("\$vendorDir . '/dep/a/test.php',\n", (string) file_get_contents($this->vendorDir.'/composer/autoload_files.php')); + self::assertStringContainsString("\$vendorDir . '/dep/a/test.php',\n", (string) file_get_contents($this->vendorDir.'/composer/autoload_files.php')); $package->setAutoload([ 'exclude-from-classmap' => ['../excludedroot', 'root/excl'], @@ -1677,7 +1677,7 @@ public function testAutoloadRulesInPackageThatDoesNotExistOnDisk(): void ]); $map = $this->generator->buildPackageMap($this->im, $package, [$dep]); $parsed = $this->generator->parseAutoloads($map, $package); - $this->assertSame([ + self::assertSame([ preg_quote(strtr((string) realpath(dirname($this->workingDir)), '\\', '/')).'/excludedroot($|/)', preg_quote(strtr((string) realpath($this->workingDir), '\\', '/')).'/root/excl($|/)', ], $parsed['exclude-from-classmap']); @@ -1746,9 +1746,9 @@ public function testEmptyPaths(): void EOF; - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); } public function testVendorSubstringPath(): void @@ -1794,8 +1794,8 @@ public function testVendorSubstringPath(): void EOF; $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'VendorSubstring'); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); - $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); + self::assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); } public function testExcludeFromClassmap(): void @@ -1864,7 +1864,7 @@ public function testExcludeFromClassmap(): void $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); // Assert that autoload_classmap.php was correctly generated. - $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); + self::assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); } /** @@ -1896,11 +1896,11 @@ public function testGeneratesPlatformCheck(array $requires, ?string $expectedFix $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); if (null === $expectedFixture) { - $this->assertFileDoesNotExist($this->vendorDir . '/composer/platform_check.php'); - $this->assertStringNotContainsString("require __DIR__ . '/platform_check.php';", (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertFileDoesNotExist($this->vendorDir . '/composer/platform_check.php'); + self::assertStringNotContainsString("require __DIR__ . '/platform_check.php';", (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } else { - $this->assertFileContentEquals(__DIR__ . '/Fixtures/platform/' . $expectedFixture . '.php', $this->vendorDir . '/composer/platform_check.php'); - $this->assertStringContainsString("require __DIR__ . '/platform_check.php';", (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); + self::assertFileContentEquals(__DIR__ . '/Fixtures/platform/' . $expectedFixture . '.php', $this->vendorDir . '/composer/platform_check.php'); + self::assertStringContainsString("require __DIR__ . '/platform_check.php';", (string) file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } } @@ -2025,7 +2025,7 @@ private function assertAutoloadFiles(string $name, string $dir, string $type = ' { $a = __DIR__.'/Fixtures/autoload_'.$name.'.php'; $b = $dir.'/autoload_'.$type.'.php'; - $this->assertFileContentEquals($a, $b); + self::assertFileContentEquals($a, $b); } public static function assertFileContentEquals(string $expected, string $actual, ?string $message = null): void diff --git a/tests/Composer/Test/Autoload/ClassLoaderTest.php b/tests/Composer/Test/Autoload/ClassLoaderTest.php index 96ba28c0c789..5d385c4fe1e8 100644 --- a/tests/Composer/Test/Autoload/ClassLoaderTest.php +++ b/tests/Composer/Test/Autoload/ClassLoaderTest.php @@ -34,7 +34,7 @@ public function testLoadClass(string $class): void $loader->add('Pearlike_', __DIR__ . '/Fixtures'); $loader->addPsr4('ShinyVendor\\ShinyPackage\\', __DIR__ . '/Fixtures'); $loader->loadClass($class); - $this->assertTrue(class_exists($class, false), "->loadClass() loads '$class'"); + self::assertTrue(class_exists($class, false), "->loadClass() loads '$class'"); } /** @@ -57,7 +57,7 @@ public static function getLoadClassTests(): array public function testGetPrefixesWithNoPSR0Configuration(): void { $loader = new ClassLoader(); - $this->assertEmpty($loader->getPrefixes()); + self::assertEmpty($loader->getPrefixes()); } public function testSerializability(): void diff --git a/tests/Composer/Test/CacheTest.php b/tests/Composer/Test/CacheTest.php index 81a07b6d1368..e5eb0e2524d5 100644 --- a/tests/Composer/Test/CacheTest.php +++ b/tests/Composer/Test/CacheTest.php @@ -77,9 +77,9 @@ public function testRemoveOutdatedFiles(): void $this->cache->gc(600, 1024 * 1024 * 1024); for ($i = 1; $i < 4; $i++) { - $this->assertFileDoesNotExist("{$this->root}/cached.file{$i}.zip"); + self::assertFileDoesNotExist("{$this->root}/cached.file{$i}.zip"); } - $this->assertFileExists("{$this->root}/cached.file0.zip"); + self::assertFileExists("{$this->root}/cached.file0.zip"); } public function testRemoveFilesWhenCacheIsTooLarge(): void @@ -106,15 +106,15 @@ public function testRemoveFilesWhenCacheIsTooLarge(): void $this->cache->gc(600, 1500); for ($i = 0; $i < 3; $i++) { - $this->assertFileDoesNotExist("{$this->root}/cached.file{$i}.zip"); + self::assertFileDoesNotExist("{$this->root}/cached.file{$i}.zip"); } - $this->assertFileExists("{$this->root}/cached.file3.zip"); + self::assertFileExists("{$this->root}/cached.file3.zip"); } public function testClearCache(): void { $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $cache = new Cache($io, $this->root, 'a-z0-9.', $this->filesystem); - $this->assertTrue($cache->clear()); + self::assertTrue($cache->clear()); } } diff --git a/tests/Composer/Test/Command/AboutCommandTest.php b/tests/Composer/Test/Command/AboutCommandTest.php index 7701fbfd3dcf..ad560435dcd6 100644 --- a/tests/Composer/Test/Command/AboutCommandTest.php +++ b/tests/Composer/Test/Command/AboutCommandTest.php @@ -12,10 +12,10 @@ public function testAbout(): void { $composerVersion = Composer::getVersion(); $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'about'])); - $this->assertStringContainsString("Composer - Dependency Manager for PHP - version $composerVersion", $appTester->getDisplay()); + self::assertSame(0, $appTester->run(['command' => 'about'])); + self::assertStringContainsString("Composer - Dependency Manager for PHP - version $composerVersion", $appTester->getDisplay()); - $this->assertStringContainsString("Composer is a dependency manager tracking local dependencies of your projects and libraries.", $appTester->getDisplay()); - $this->assertStringContainsString("See https://getcomposer.org/ for more information.", $appTester->getDisplay()); + self::assertStringContainsString("Composer is a dependency manager tracking local dependencies of your projects and libraries.", $appTester->getDisplay()); + self::assertStringContainsString("See https://getcomposer.org/ for more information.", $appTester->getDisplay()); } } diff --git a/tests/Composer/Test/Command/ArchiveCommandTest.php b/tests/Composer/Test/Command/ArchiveCommandTest.php index 1ed93f5fdd81..be9e8df57f35 100644 --- a/tests/Composer/Test/Command/ArchiveCommandTest.php +++ b/tests/Composer/Test/Command/ArchiveCommandTest.php @@ -103,7 +103,7 @@ public function testUsesConfigFromFactoryWhenComposerIsNotDefined(): void null )->willReturn(0); - $this->assertEquals(0, $command->run($input, $output)); + self::assertEquals(0, $command->run($input, $output)); } public function testUsesConfigFromComposerObjectWithPackageName(): void diff --git a/tests/Composer/Test/Command/BaseDependencyCommandTest.php b/tests/Composer/Test/Command/BaseDependencyCommandTest.php index 6442b7f70285..c663b70f75d3 100644 --- a/tests/Composer/Test/Command/BaseDependencyCommandTest.php +++ b/tests/Composer/Test/Command/BaseDependencyCommandTest.php @@ -45,7 +45,7 @@ public function testExceptionWhenNoRequiredParameters( $this->expectExceptionMessage($expectedExceptionMessage); $appTester = $this->getApplicationTester(); - $this->assertEquals(Command::FAILURE, $appTester->run(['command' => $command] + $parameters)); + self::assertEquals(Command::FAILURE, $appTester->run(['command' => $command] + $parameters)); } /** @@ -97,7 +97,7 @@ public function testExceptionWhenRunningLockedWithoutLockFile(string $command, a $this->expectExceptionMessage('A valid composer.lock file is required to run this command with --locked'); $appTester = $this->getApplicationTester(); - $this->assertEquals( + self::assertEquals( Command::FAILURE, $appTester->run(['command' => $command] + $parameters + ['--locked' => true] ) @@ -125,7 +125,7 @@ public function testExceptionWhenItCouldNotFoundThePackage(string $command, arra $this->expectExceptionMessage(sprintf('Could not find package "%s" in your project', $packageToBeInspected)); $appTester = $this->getApplicationTester(); - $this->assertEquals( + self::assertEquals( Command::FAILURE, $appTester->run(['command' => $command] + $parameters) ); @@ -164,7 +164,7 @@ public function testExceptionWhenPackageWasNotFoundInProject(string $command, ar $appTester = $this->getApplicationTester(); - $this->assertEquals(Command::FAILURE, $appTester->run(['command' => $command] + $parameters)); + self::assertEquals(Command::FAILURE, $appTester->run(['command' => $command] + $parameters)); } /** @@ -199,7 +199,7 @@ public function testWarningWhenDependenciesAreNotInstalled(string $command, arra $appTester = $this->getApplicationTester(); $appTester->run(['command' => $command] + $parameters); - $this->assertSame($expectedWarningMessage, trim($appTester->getDisplay(true))); + self::assertSame($expectedWarningMessage, trim($appTester->getDisplay(true))); } /** @@ -296,7 +296,7 @@ public function testWhyCommandOutputs(array $parameters, string $expectedOutput, self::assertSame($expectedStatusCode, $appTester->getStatusCode()); - $this->assertEquals(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); + self::assertEquals(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); } /** @@ -420,7 +420,7 @@ public function testWhyNotCommandOutputs(array $parameters, string $expectedOutp ]); self::assertSame($expectedStatusCode, $appTester->getStatusCode()); - $this->assertSame(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); + self::assertSame(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); } /** diff --git a/tests/Composer/Test/Command/BumpCommandTest.php b/tests/Composer/Test/Command/BumpCommandTest.php index 383aaf90c144..8e8161dcfa17 100644 --- a/tests/Composer/Test/Command/BumpCommandTest.php +++ b/tests/Composer/Test/Command/BumpCommandTest.php @@ -41,10 +41,10 @@ public function testBump(array $composerJson, array $command, array $expected, b } $appTester = $this->getApplicationTester(); - $this->assertSame($exitCode, $appTester->run(array_merge(['command' => 'bump'], $command))); + self::assertSame($exitCode, $appTester->run(array_merge(['command' => 'bump'], $command))); $json = new JsonFile('./composer.json'); - $this->assertSame($expected, $json->read()); + self::assertSame($expected, $json->read()); } public function testBumpFailsOnNonExistingComposerFile(): void @@ -54,9 +54,9 @@ public function testBumpFailsOnNonExistingComposerFile(): void unlink($composerJsonPath); $appTester = $this->getApplicationTester(); - $this->assertSame(1, $appTester->run(['command' => 'bump'], ['capture_stderr_separately' => true])); + self::assertSame(1, $appTester->run(['command' => 'bump'], ['capture_stderr_separately' => true])); - $this->assertStringContainsString("./composer.json is not readable.", $appTester->getErrorOutput()); + self::assertStringContainsString("./composer.json is not readable.", $appTester->getErrorOutput()); } public function testBumpFailsOnWriteErrorToComposerFile(): void @@ -66,9 +66,9 @@ public function testBumpFailsOnWriteErrorToComposerFile(): void chmod($composerJsonPath, 0444); $appTester = $this->getApplicationTester(); - $this->assertSame(1, $appTester->run(['command' => 'bump'], ['capture_stderr_separately' => true])); + self::assertSame(1, $appTester->run(['command' => 'bump'], ['capture_stderr_separately' => true])); - $this->assertStringContainsString("./composer.json is not writable.", $appTester->getErrorOutput()); + self::assertStringContainsString("./composer.json is not writable.", $appTester->getErrorOutput()); } public static function provideTests(): \Generator diff --git a/tests/Composer/Test/Command/CheckPlatformReqsCommandTest.php b/tests/Composer/Test/Command/CheckPlatformReqsCommandTest.php index 9f9d56df6713..27d852697ca0 100644 --- a/tests/Composer/Test/Command/CheckPlatformReqsCommandTest.php +++ b/tests/Composer/Test/Command/CheckPlatformReqsCommandTest.php @@ -47,7 +47,7 @@ public function testPlatformReqsAreSatisfied( $appTester->run(array_merge(['command' => 'check-platform-reqs'], $command)); $appTester->assertCommandIsSuccessful(); - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } public function testExceptionThrownIfNoLockfileFound(): void @@ -89,7 +89,7 @@ public static function caseProvider(): \Generator ]; } - public function testFailedPlatformRequirement(): void + public function testFailedPlatformRequirement(): void { $this->initTempComposer([ 'require' => [ @@ -137,6 +137,6 @@ public function testFailedPlatformRequirement(): void } ]'; - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } } diff --git a/tests/Composer/Test/Command/ClearCacheCommandTest.php b/tests/Composer/Test/Command/ClearCacheCommandTest.php index 7a528ffaebe7..b76414423273 100644 --- a/tests/Composer/Test/Command/ClearCacheCommandTest.php +++ b/tests/Composer/Test/Command/ClearCacheCommandTest.php @@ -32,7 +32,7 @@ public function testClearCacheCommandSuccess(): void $output = $appTester->getDisplay(true); - $this->assertStringContainsString('All caches cleared.', $output); + self::assertStringContainsString('All caches cleared.', $output); } public function testClearCacheCommandWithOptionGarbageCollection(): void @@ -44,7 +44,7 @@ public function testClearCacheCommandWithOptionGarbageCollection(): void $output = $appTester->getDisplay(true); - $this->assertStringContainsString('All caches garbage-collected.', $output); + self::assertStringContainsString('All caches garbage-collected.', $output); } public function testClearCacheCommandWithOptionNoCache(): void @@ -56,6 +56,6 @@ public function testClearCacheCommandWithOptionNoCache(): void $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Cache is not enabled', $output); + self::assertStringContainsString('Cache is not enabled', $output); } } diff --git a/tests/Composer/Test/Command/ConfigCommandTest.php b/tests/Composer/Test/Command/ConfigCommandTest.php index 9d08f7cc5bf8..5542eb828c82 100644 --- a/tests/Composer/Test/Command/ConfigCommandTest.php +++ b/tests/Composer/Test/Command/ConfigCommandTest.php @@ -32,7 +32,7 @@ public function testConfigUpdates(array $before, array $command, array $expected $appTester->assertCommandIsSuccessful($appTester->getDisplay()); - $this->assertSame($expected, json_decode((string) file_get_contents('composer.json'), true)); + self::assertSame($expected, json_decode((string) file_get_contents('composer.json'), true)); } public static function provideConfigUpdates(): \Generator @@ -133,8 +133,8 @@ public function testConfigReads(array $composerJson, array $command, string $exp $appTester->assertCommandIsSuccessful(); - $this->assertSame($expected, trim($appTester->getDisplay(true))); - $this->assertSame($composerJson, json_decode((string) file_get_contents('composer.json'), true), 'The composer.json should not be modified by config reads'); + self::assertSame($expected, trim($appTester->getDisplay(true))); + self::assertSame($composerJson, json_decode((string) file_get_contents('composer.json'), true), 'The composer.json should not be modified by config reads'); } public static function provideConfigReads(): \Generator diff --git a/tests/Composer/Test/Command/DiagnoseCommandTest.php b/tests/Composer/Test/Command/DiagnoseCommandTest.php index 5ca6702655ad..28ef18f50ce0 100644 --- a/tests/Composer/Test/Command/DiagnoseCommandTest.php +++ b/tests/Composer/Test/Command/DiagnoseCommandTest.php @@ -23,13 +23,13 @@ public function testCmdFail(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'diagnose']); - $this->assertSame(1, $appTester->getStatusCode()); + self::assertSame(1, $appTester->getStatusCode()); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Checking composer.json: WARNING + self::assertStringContainsString('Checking composer.json: WARNING No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.', $output); - $this->assertStringContainsString('Checking http connectivity to packagist: OK + self::assertStringContainsString('Checking http connectivity to packagist: OK Checking https connectivity to packagist: OK Checking github.com rate limit: ', $output); } @@ -44,9 +44,9 @@ public function testCmdSuccess(): void $appTester->assertCommandIsSuccessful(); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Checking composer.json: OK', $output); + self::assertStringContainsString('Checking composer.json: OK', $output); - $this->assertStringContainsString('Checking http connectivity to packagist: OK + self::assertStringContainsString('Checking http connectivity to packagist: OK Checking https connectivity to packagist: OK Checking github.com rate limit: ', $output); } diff --git a/tests/Composer/Test/Command/DumpAutoloadCommandTest.php b/tests/Composer/Test/Command/DumpAutoloadCommandTest.php index 88d1287723a5..fd58156ab9c0 100644 --- a/tests/Composer/Test/Command/DumpAutoloadCommandTest.php +++ b/tests/Composer/Test/Command/DumpAutoloadCommandTest.php @@ -10,41 +10,41 @@ class DumpAutoloadCommandTest extends TestCase public function testDumpAutoload(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload'])); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Generating autoload files', $output); - $this->assertStringContainsString('Generated autoload files', $output); + self::assertStringContainsString('Generating autoload files', $output); + self::assertStringContainsString('Generated autoload files', $output); } public function testDumpDevAutoload(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload', '--dev' => true])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload', '--dev' => true])); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Generating autoload files', $output); - $this->assertStringContainsString('Generated autoload files', $output); + self::assertStringContainsString('Generating autoload files', $output); + self::assertStringContainsString('Generated autoload files', $output); } public function testDumpNoDevAutoload(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload', '--dev' => true])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload', '--dev' => true])); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Generating autoload files', $output); - $this->assertStringContainsString('Generated autoload files', $output); + self::assertStringContainsString('Generating autoload files', $output); + self::assertStringContainsString('Generated autoload files', $output); } public function testUsingOptimizeAndStrictPsr(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload', '--optimize' => true, '--strict-psr' => true])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload', '--optimize' => true, '--strict-psr' => true])); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Generating optimized autoload files', $output); - $this->assertMatchesRegularExpression('/Generated optimized autoload files containing \d+ classes/', $output); + self::assertStringContainsString('Generating optimized autoload files', $output); + self::assertMatchesRegularExpression('/Generated optimized autoload files containing \d+ classes/', $output); } public function testFailsUsingStrictPsrIfClassMapViolationsAreFound(): void @@ -59,30 +59,30 @@ public function testFailsUsingStrictPsrIfClassMapViolationsAreFound(): void mkdir($dir . '/src/'); file_put_contents($dir . '/src/Foo.php', 'getApplicationTester(); - $this->assertSame(1, $appTester->run(['command' => 'dump-autoload', '--optimize' => true, '--strict-psr' => true])); + self::assertSame(1, $appTester->run(['command' => 'dump-autoload', '--optimize' => true, '--strict-psr' => true])); $output = $appTester->getDisplay(true); - $this->assertMatchesRegularExpression('/Class Application\\\Src\\\Foo located in .*? does not comply with psr-4 autoloading standard. Skipping./', $output); + self::assertMatchesRegularExpression('/Class Application\\\Src\\\Foo located in .*? does not comply with psr-4 autoloading standard. Skipping./', $output); } public function testUsingClassmapAuthoritative(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload', '--classmap-authoritative' => true])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload', '--classmap-authoritative' => true])); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Generating optimized autoload files (authoritative)', $output); - $this->assertMatchesRegularExpression('/Generated optimized autoload files \(authoritative\) containing \d+ classes/', $output); + self::assertStringContainsString('Generating optimized autoload files (authoritative)', $output); + self::assertMatchesRegularExpression('/Generated optimized autoload files \(authoritative\) containing \d+ classes/', $output); } public function testUsingClassmapAuthoritativeAndStrictPsr(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload', '--classmap-authoritative' => true, '--strict-psr' => true])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload', '--classmap-authoritative' => true, '--strict-psr' => true])); $output = $appTester->getDisplay(true); - $this->assertStringContainsString('Generating optimized autoload files', $output); - $this->assertMatchesRegularExpression('/Generated optimized autoload files \(authoritative\) containing \d+ classes/', $output); + self::assertStringContainsString('Generating optimized autoload files', $output); + self::assertMatchesRegularExpression('/Generated optimized autoload files \(authoritative\) containing \d+ classes/', $output); } public function testStrictPsrDoesNotWorkWithoutOptimizedAutoloader(): void @@ -112,7 +112,7 @@ public function testWithCustomAutoloaderSuffix(): void ]); $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload'])); self::assertStringContainsString('ComposerAutoloaderInitFoobar', (string) file_get_contents($dir . '/vendor/autoload.php')); } @@ -147,7 +147,7 @@ public function testWithExistingComposerLockAndAutoloaderSuffix(): void ); $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload'])); self::assertStringContainsString('ComposerAutoloaderInitFoobar', (string) file_get_contents($dir . '/vendor/autoload.php')); } @@ -180,7 +180,7 @@ public function testWithExistingComposerLockWithoutAutoloaderSuffix(): void ); $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + self::assertSame(0, $appTester->run(['command' => 'dump-autoload'])); self::assertStringContainsString('ComposerAutoloaderInit2d4a6be9a93712c5d6a119b26734a047', (string) file_get_contents($dir . '/vendor/autoload.php')); } diff --git a/tests/Composer/Test/Command/ExecCommandTest.php b/tests/Composer/Test/Command/ExecCommandTest.php index ee195c4b5e3b..102122b64d0f 100644 --- a/tests/Composer/Test/Command/ExecCommandTest.php +++ b/tests/Composer/Test/Command/ExecCommandTest.php @@ -49,7 +49,7 @@ public function testList(): void $output = $appTester->getDisplay(true); - $this->assertSame( + self::assertSame( 'Available binaries: - b - c diff --git a/tests/Composer/Test/Command/FundCommandTest.php b/tests/Composer/Test/Command/FundCommandTest.php index b1563b8ad9f0..45e31fb14ea7 100644 --- a/tests/Composer/Test/Command/FundCommandTest.php +++ b/tests/Composer/Test/Command/FundCommandTest.php @@ -56,7 +56,7 @@ public function testFundCommand( $appTester->run(array_merge(['command' => 'fund'], $command)); $appTester->assertCommandIsSuccessful(); - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } public static function useCaseProvider(): Generator diff --git a/tests/Composer/Test/Command/HomeCommandTest.php b/tests/Composer/Test/Command/HomeCommandTest.php index fd37e2f89fe3..d0b722ec20fd 100644 --- a/tests/Composer/Test/Command/HomeCommandTest.php +++ b/tests/Composer/Test/Command/HomeCommandTest.php @@ -54,7 +54,7 @@ public function testHomeCommandWithShowFlag( $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'home', '--show' => true], $command)); - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } public static function useCaseProvider(): Generator diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 6030868ef4e4..18847c1e4ec1 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -23,24 +23,24 @@ public function testParseValidAuthorString(): void { $command = new InitCommand; $author = $this->callParseAuthorString($command, 'John Smith '); - $this->assertEquals('John Smith', $author['name']); - $this->assertEquals('john@example.com', $author['email']); + self::assertEquals('John Smith', $author['name']); + self::assertEquals('john@example.com', $author['email']); } public function testParseValidAuthorStringWithoutEmail(): void { $command = new InitCommand; $author = $this->callParseAuthorString($command, 'John Smith'); - $this->assertEquals('John Smith', $author['name']); - $this->assertNull($author['email']); + self::assertEquals('John Smith', $author['name']); + self::assertNull($author['email']); } public function testParseValidUtf8AuthorString(): void { $command = new InitCommand; $author = $this->callParseAuthorString($command, 'Matti Meikäläinen '); - $this->assertEquals('Matti Meikäläinen', $author['name']); - $this->assertEquals('matti@example.com', $author['email']); + self::assertEquals('Matti Meikäläinen', $author['name']); + self::assertEquals('matti@example.com', $author['email']); } public function testParseValidUtf8AuthorStringWithNonSpacingMarks(): void @@ -49,16 +49,16 @@ public function testParseValidUtf8AuthorStringWithNonSpacingMarks(): void $utf8_expected = "Matti Meika\xCC\x88la\xCC\x88inen"; $command = new InitCommand; $author = $this->callParseAuthorString($command, $utf8_expected." "); - $this->assertEquals($utf8_expected, $author['name']); - $this->assertEquals('matti@example.com', $author['email']); + self::assertEquals($utf8_expected, $author['name']); + self::assertEquals('matti@example.com', $author['email']); } public function testParseNumericAuthorString(): void { $command = new InitCommand; $author = $this->callParseAuthorString($command, 'h4x0r '); - $this->assertEquals('h4x0r', $author['name']); - $this->assertEquals('h4x@example.com', $author['email']); + self::assertEquals('h4x0r', $author['name']); + self::assertEquals('h4x@example.com', $author['email']); } /** @@ -72,8 +72,8 @@ public function testParseValidAlias1AuthorString(): void $command, 'Johnathon "Johnny" Smith ' ); - $this->assertEquals('Johnathon "Johnny" Smith', $author['name']); - $this->assertEquals('john@example.com', $author['email']); + self::assertEquals('Johnathon "Johnny" Smith', $author['name']); + self::assertEquals('john@example.com', $author['email']); } /** @@ -87,8 +87,8 @@ public function testParseValidAlias2AuthorString(): void $command, 'Johnathon (Johnny) Smith ' ); - $this->assertEquals('Johnathon (Johnny) Smith', $author['name']); - $this->assertEquals('john@example.com', $author['email']); + self::assertEquals('Johnathon (Johnny) Smith', $author['name']); + self::assertEquals('john@example.com', $author['email']); } public function testParseEmptyAuthorString(): void @@ -109,21 +109,21 @@ public function testNamespaceFromValidPackageName(): void { $command = new InitCommand; $namespace = $command->namespaceFromPackageName('new_projects.acme-extra/package-name'); - $this->assertEquals('NewProjectsAcmeExtra\PackageName', $namespace); + self::assertEquals('NewProjectsAcmeExtra\PackageName', $namespace); } public function testNamespaceFromInvalidPackageName(): void { $command = new InitCommand; $namespace = $command->namespaceFromPackageName('invalid-package-name'); - $this->assertNull($namespace); + self::assertNull($namespace); } public function testNamespaceFromMissingPackageName(): void { $command = new InitCommand; $namespace = $command->namespaceFromPackageName(''); - $this->assertNull($namespace); + self::assertNull($namespace); } /** @@ -168,7 +168,7 @@ public function testRunNameArgument(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'init', '--no-interaction' => true, '--name' => 'test/pkg']); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -176,7 +176,7 @@ public function testRunNameArgument(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunInvalidAuthorArgumentInvalidEmail(): void @@ -210,7 +210,7 @@ public function testRunAuthorArgument(): void '--author' => 'Mr. Test ', ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -224,7 +224,7 @@ public function testRunAuthorArgument(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunAuthorArgumentMissingEmail(): void @@ -241,7 +241,7 @@ public function testRunAuthorArgumentMissingEmail(): void '--author' => 'Mr. Test', ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -254,7 +254,7 @@ public function testRunAuthorArgumentMissingEmail(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunSingleRepositoryArgument(): void @@ -273,7 +273,7 @@ public function testRunSingleRepositoryArgument(): void ], ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -287,7 +287,7 @@ public function testRunSingleRepositoryArgument(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunMultipleRepositoryArguments(): void @@ -308,7 +308,7 @@ public function testRunMultipleRepositoryArguments(): void ], ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -335,7 +335,7 @@ public function testRunMultipleRepositoryArguments(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunStability(): void @@ -352,7 +352,7 @@ public function testRunStability(): void '--stability' => 'dev', ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -361,7 +361,7 @@ public function testRunStability(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunInvalidStability(): void @@ -378,9 +378,9 @@ public function testRunInvalidStability(): void '--stability' => 'bogus', ], ['capture_stderr_separately' => true]); - $this->assertSame(1, $appTester->getStatusCode()); + self::assertSame(1, $appTester->getStatusCode()); - $this->assertMatchesRegularExpression("/minimum-stability\s+:\s+Does not have a value in the enumeration/", $appTester->getErrorOutput()); + self::assertMatchesRegularExpression("/minimum-stability\s+:\s+Does not have a value in the enumeration/", $appTester->getErrorOutput()); } public function testRunRequireOne(): void @@ -399,7 +399,7 @@ public function testRunRequireOne(): void ], ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -409,7 +409,7 @@ public function testRunRequireOne(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunRequireMultiple(): void @@ -429,7 +429,7 @@ public function testRunRequireMultiple(): void ], ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -440,7 +440,7 @@ public function testRunRequireMultiple(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunInvalidRequire(): void @@ -479,7 +479,7 @@ public function testRunRequireDevOne(): void ], ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -490,7 +490,7 @@ public function testRunRequireDevOne(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunRequireDevMultiple(): void @@ -510,7 +510,7 @@ public function testRunRequireDevMultiple(): void ], ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -522,7 +522,7 @@ public function testRunRequireDevMultiple(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunInvalidRequireDev(): void @@ -559,7 +559,7 @@ public function testRunAutoload(): void '--autoload' => 'testMapping/' ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -572,7 +572,7 @@ public function testRunAutoload(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunHomepage(): void @@ -589,7 +589,7 @@ public function testRunHomepage(): void '--homepage' => 'https://example.org/' ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -598,7 +598,7 @@ public function testRunHomepage(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunInvalidHomepage(): void @@ -615,8 +615,8 @@ public function testRunInvalidHomepage(): void '--homepage' => 'not-a-url', ], ['capture_stderr_separately' => true]); - $this->assertSame(1, $appTester->getStatusCode()); - $this->assertMatchesRegularExpression("/homepage\s*:\s*Invalid URL format/", $appTester->getErrorOutput()); + self::assertSame(1, $appTester->getStatusCode()); + self::assertMatchesRegularExpression("/homepage\s*:\s*Invalid URL format/", $appTester->getErrorOutput()); } public function testRunDescription(): void @@ -633,7 +633,7 @@ public function testRunDescription(): void '--description' => 'My first example package' ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -642,7 +642,7 @@ public function testRunDescription(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunType(): void @@ -659,7 +659,7 @@ public function testRunType(): void '--type' => 'library' ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -668,7 +668,7 @@ public function testRunType(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } public function testRunLicense(): void @@ -685,7 +685,7 @@ public function testRunLicense(): void '--license' => 'MIT' ]); - $this->assertSame(0, $appTester->getStatusCode()); + self::assertSame(0, $appTester->getStatusCode()); $expected = [ 'name' => 'test/pkg', @@ -694,6 +694,6 @@ public function testRunLicense(): void ]; $file = new JsonFile($dir . '/composer.json'); - $this->assertEquals($expected, $file->read()); + self::assertEquals($expected, $file->read()); } } diff --git a/tests/Composer/Test/Command/InstallCommandTest.php b/tests/Composer/Test/Command/InstallCommandTest.php index 8787de8893a9..ebaf15cbe4c6 100644 --- a/tests/Composer/Test/Command/InstallCommandTest.php +++ b/tests/Composer/Test/Command/InstallCommandTest.php @@ -42,7 +42,7 @@ public function testInstallCommandErrors( $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'install'], $command)); - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } public function errorCaseProvider(): Generator @@ -118,7 +118,7 @@ public function testInstallFromEmptyVendor(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'install', '--no-progress' => true]); - $this->assertSame('Installing dependencies from lock file (including require-dev) + self::assertSame('Installing dependencies from lock file (including require-dev) Verifying lock file contents can be installed on current platform. Package operations: 2 installs, 0 updates, 0 removals - Installing root/another (1.0.0) @@ -148,7 +148,7 @@ public function testInstallFromEmptyVendorNoDev(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'install', '--no-progress' => true, '--no-dev' => true]); - $this->assertSame('Installing dependencies from lock file + self::assertSame('Installing dependencies from lock file Verifying lock file contents can be installed on current platform. Package operations: 1 install, 0 updates, 0 removals - Installing root/req (1.0.0) @@ -175,7 +175,7 @@ public function testInstallNewPackagesWithExistingPartialVendor(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'install', '--no-progress' => true]); - $this->assertSame('Installing dependencies from lock file (including require-dev) + self::assertSame('Installing dependencies from lock file (including require-dev) Verifying lock file contents can be installed on current platform. Package operations: 1 install, 0 updates, 0 removals - Installing root/another (1.0.0) diff --git a/tests/Composer/Test/Command/LicensesCommandTest.php b/tests/Composer/Test/Command/LicensesCommandTest.php index fa92d5496b4f..b7c86bd04263 100644 --- a/tests/Composer/Test/Command/LicensesCommandTest.php +++ b/tests/Composer/Test/Command/LicensesCommandTest.php @@ -54,7 +54,7 @@ protected function setUp(): void public function testBasicRun(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'license'])); + self::assertSame(0, $appTester->run(['command' => 'license'])); $expected = [ ["Name:", "test/pkg"], @@ -81,14 +81,14 @@ public function testBasicRun(): void if (!isset($expected[$i])) { $this->fail('Got more output lines than expected'); } - $this->assertMatchesRegularExpression("/" . implode("\s+", $expected[$i]) . "/", $line); + self::assertMatchesRegularExpression("/" . implode("\s+", $expected[$i]) . "/", $line); } } public function testNoDev(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'license', '--no-dev' => true])); + self::assertSame(0, $appTester->run(['command' => 'license', '--no-dev' => true])); $expected = [ ["Name:", "test/pkg"], @@ -114,14 +114,14 @@ public function testNoDev(): void if (!isset($expected[$i])) { $this->fail('Got more output lines than expected'); } - $this->assertMatchesRegularExpression("/" . implode("\s+", $expected[$i]) . "/", $line); + self::assertMatchesRegularExpression("/" . implode("\s+", $expected[$i]) . "/", $line); } } public function testFormatJson(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'license', '--format' => 'json'], ['capture_stderr_separately' => true])); + self::assertSame(0, $appTester->run(['command' => 'license', '--format' => 'json'], ['capture_stderr_separately' => true])); $expected = [ "name" => "test/pkg", @@ -153,13 +153,13 @@ public function testFormatJson(): void ] ]; - $this->assertSame($expected, json_decode($appTester->getDisplay(), true)); + self::assertSame($expected, json_decode($appTester->getDisplay(), true)); } public function testFormatSummary(): void { $appTester = $this->getApplicationTester(); - $this->assertSame(0, $appTester->run(['command' => 'license', '--format' => 'summary'])); + self::assertSame(0, $appTester->run(['command' => 'license', '--format' => 'summary'])); $expected = [ ['-', '-'], @@ -176,7 +176,7 @@ public function testFormatSummary(): void foreach ($expected as $i => $expect) { [$key, $value] = $expect; - $this->assertMatchesRegularExpression("/$key\s+$value/", $lines[$i]); + self::assertMatchesRegularExpression("/$key\s+$value/", $lines[$i]); } } diff --git a/tests/Composer/Test/Command/ReinstallCommandTest.php b/tests/Composer/Test/Command/ReinstallCommandTest.php index c25c00202a8a..1e81a7790d4d 100644 --- a/tests/Composer/Test/Command/ReinstallCommandTest.php +++ b/tests/Composer/Test/Command/ReinstallCommandTest.php @@ -47,10 +47,10 @@ public function testReinstallCommand(array $packages, string $expected): void 'packages' => $packages ]); - $this->assertSame($expected, trim($appTester->getDisplay(true))); + self::assertSame($expected, trim($appTester->getDisplay(true))); } - public function caseProvider(): Generator + public function caseProvider(): Generator { yield 'reinstall a package' => [ ['root/req', 'root/anotherreq'], diff --git a/tests/Composer/Test/Command/RemoveCommandTest.php b/tests/Composer/Test/Command/RemoveCommandTest.php index f34b0c7618d2..f8719932346a 100644 --- a/tests/Composer/Test/Command/RemoveCommandTest.php +++ b/tests/Composer/Test/Command/RemoveCommandTest.php @@ -28,7 +28,7 @@ public function testExceptionRunningWithNoRemovePackages(): void $this->expectExceptionMessage('Not enough arguments (missing: "packages").'); $appTester = $this->getApplicationTester(); - $this->assertEquals(Command::FAILURE, $appTester->run(['command' => 'remove'])); + self::assertEquals(Command::FAILURE, $appTester->run(['command' => 'remove'])); } public function testExceptionWhenRunningUnusedWithoutLockFile(): void @@ -39,7 +39,7 @@ public function testExceptionWhenRunningUnusedWithoutLockFile(): void $this->expectExceptionMessage('A valid composer.lock file is required to run this command with --unused'); $appTester = $this->getApplicationTester(); - $this->assertEquals(Command::FAILURE, $appTester->run(['command' => 'remove', '--unused' => true])); + self::assertEquals(Command::FAILURE, $appTester->run(['command' => 'remove', '--unused' => true])); } public function testWarningWhenRemovingNonExistentPackage(): void @@ -64,7 +64,7 @@ public function testWarningWhenRemovingPackageFromWrongType(): void self::assertEquals(Command::SUCCESS, $appTester->run(['command' => 'remove', 'packages' => ['root/req'], '--dev' => true, '--no-update' => true, '--no-interaction' => true])); self::assertSame('root/req could not be found in require-dev but it is present in require ./composer.json has been updated', trim($appTester->getDisplay(true))); - $this->assertEquals(['require' => ['root/req' => '1.*']], (new JsonFile('./composer.json'))->read()); + self::assertEquals(['require' => ['root/req' => '1.*']], (new JsonFile('./composer.json'))->read()); } public function testWarningWhenRemovingPackageWithDeprecatedDependenciesFlag(): void @@ -79,7 +79,7 @@ public function testWarningWhenRemovingPackageWithDeprecatedDependenciesFlag(): self::assertEquals(Command::SUCCESS, $appTester->run(['command' => 'remove', 'packages' => ['root/req'], '--update-with-dependencies' => true, '--no-update' => true, '--no-interaction' => true])); self::assertSame('You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies. ./composer.json has been updated', trim($appTester->getDisplay(true))); - $this->assertEmpty((new JsonFile('./composer.json'))->read()); + self::assertEmpty((new JsonFile('./composer.json'))->read()); } public function testMessageOutputWhenNoUnusedPackagesToRemove(): void diff --git a/tests/Composer/Test/Command/RequireCommandTest.php b/tests/Composer/Test/Command/RequireCommandTest.php index c819a35296ed..0ab7508d069a 100644 --- a/tests/Composer/Test/Command/RequireCommandTest.php +++ b/tests/Composer/Test/Command/RequireCommandTest.php @@ -96,9 +96,9 @@ public function testRequire(array $composerJson, array $command, string $expecte if (str_contains($expected, '%d')) { $pattern = '{^'.str_replace('%d', '[0-9.]+', preg_quote(trim($expected))).'$}'; - $this->assertMatchesRegularExpression($pattern, trim($appTester->getDisplay(true))); + self::assertMatchesRegularExpression($pattern, trim($appTester->getDisplay(true))); } else { - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } } diff --git a/tests/Composer/Test/Command/RunScriptCommandTest.php b/tests/Composer/Test/Command/RunScriptCommandTest.php index dc724ae8d296..f69791048355 100644 --- a/tests/Composer/Test/Command/RunScriptCommandTest.php +++ b/tests/Composer/Test/Command/RunScriptCommandTest.php @@ -105,8 +105,8 @@ public function testCanListScripts(): void $output = $appTester->getDisplay(); - $this->assertStringContainsString('Runs the test script as defined in composer.json', $output, 'The default description for the test script should be printed'); - $this->assertStringContainsString('Run the codestyle fixer', $output, 'The custom description for the fix-cs script should be printed'); + self::assertStringContainsString('Runs the test script as defined in composer.json', $output, 'The default description for the test script should be printed'); + self::assertStringContainsString('Run the codestyle fixer', $output, 'The custom description for the fix-cs script should be printed'); } public function testCanDefineAliases(): void @@ -132,7 +132,7 @@ public function testCanDefineAliases(): void $actualAliases = $array['usage']; array_shift($actualAliases); - $this->assertSame($expectedAliases, $actualAliases, 'The custom aliases for the test command should be printed'); + self::assertSame($expectedAliases, $actualAliases, 'The custom aliases for the test command should be printed'); } public function testExecutionOfCustomSymfonyCommand(): void diff --git a/tests/Composer/Test/Command/SelfUpdateCommandTest.php b/tests/Composer/Test/Command/SelfUpdateCommandTest.php index cc7b579ffbe4..d919dc9d6e96 100644 --- a/tests/Composer/Test/Command/SelfUpdateCommandTest.php +++ b/tests/Composer/Test/Command/SelfUpdateCommandTest.php @@ -53,7 +53,7 @@ public function testSuccessfulUpdate(): void $appTester->run(['command' => 'self-update']); $appTester->assertCommandIsSuccessful(); - $this->assertStringContainsString('Upgrading to version', $appTester->getDisplay()); + self::assertStringContainsString('Upgrading to version', $appTester->getDisplay()); } public function testUpdateToSpecificVersion(): void @@ -62,7 +62,7 @@ public function testUpdateToSpecificVersion(): void $appTester->run(['command' => 'self-update', 'version' => '2.4.0']); $appTester->assertCommandIsSuccessful(); - $this->assertStringContainsString('Upgrading to version 2.4.0', $appTester->getDisplay()); + self::assertStringContainsString('Upgrading to version 2.4.0', $appTester->getDisplay()); } public function testUpdateWithInvalidOptionThrowsException(): void @@ -87,8 +87,8 @@ public function testUpdateToDifferentChannel(string $option, string $expectedOut $appTester->run(['command' => 'self-update', $option => true]); $appTester->assertCommandIsSuccessful(); - $this->assertStringContainsString('Upgrading to version', $appTester->getDisplay()); - $this->assertStringContainsString($expectedOutput, $appTester->getDisplay()); + self::assertStringContainsString('Upgrading to version', $appTester->getDisplay()); + self::assertStringContainsString($expectedOutput, $appTester->getDisplay()); } /** diff --git a/tests/Composer/Test/Command/StatusCommandTest.php b/tests/Composer/Test/Command/StatusCommandTest.php index fce072b029ad..17d65c3c810e 100644 --- a/tests/Composer/Test/Command/StatusCommandTest.php +++ b/tests/Composer/Test/Command/StatusCommandTest.php @@ -30,7 +30,7 @@ public function testNoLocalChanges(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'status']); - $this->assertSame('No local changes', trim($appTester->getDisplay(true))); + self::assertSame('No local changes', trim($appTester->getDisplay(true))); } /** @@ -73,8 +73,8 @@ public function testLocallyModifiedPackages( $expected = 'You have changes in the following dependencies:'; $actual = trim($appTester->getDisplay(true)); - $this->assertStringContainsString($expected, $actual); - $this->assertStringContainsString($packageData['name'], $actual); + self::assertStringContainsString($expected, $actual); + self::assertStringContainsString($packageData['name'], $actual); } public static function locallyModifiedPackagesUseCaseProvider(): Generator diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index fce735087872..5b58e52d91c3 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -29,7 +29,7 @@ public function testUpdate(array $composerJson, array $command, string $expected $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command)); - $this->assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true))); + self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true))); } public static function provideUpdates(): \Generator diff --git a/tests/Composer/Test/Command/ValidateCommandTest.php b/tests/Composer/Test/Command/ValidateCommandTest.php index 363cd0391284..bae0b261c3e5 100644 --- a/tests/Composer/Test/Command/ValidateCommandTest.php +++ b/tests/Composer/Test/Command/ValidateCommandTest.php @@ -30,7 +30,7 @@ public function testValidate(array $composerJson, array $command, string $expect $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'validate'], $command)); - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } public function testValidateOnFileIssues(): void @@ -42,7 +42,7 @@ public function testValidateOnFileIssues(): void $appTester->run(['command' => 'validate']); $expected = './composer.json not found.'; - $this->assertSame($expected, trim($appTester->getDisplay(true))); + self::assertSame($expected, trim($appTester->getDisplay(true))); } public function testWithComposerLock(): void @@ -63,7 +63,7 @@ public function testWithComposerLock(): void and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r OUTPUT; - $this->assertSame(trim($expected), trim($appTester->getDisplay(true))); + self::assertSame(trim($expected), trim($appTester->getDisplay(true))); } public function testUnaccessibleFile(): void @@ -79,8 +79,8 @@ public function testUnaccessibleFile(): void $appTester->run(['command' => 'validate']); $expected = './composer.json is not readable.'; - $this->assertSame($expected, trim($appTester->getDisplay(true))); - $this->assertSame(3, $appTester->getStatusCode()); + self::assertSame($expected, trim($appTester->getDisplay(true))); + self::assertSame(3, $appTester->getStatusCode()); chmod($directory.'/composer.json', 0700); } diff --git a/tests/Composer/Test/CompletionFunctionalTest.php b/tests/Composer/Test/CompletionFunctionalTest.php index cb452f1300aa..1a691af7d518 100644 --- a/tests/Composer/Test/CompletionFunctionalTest.php +++ b/tests/Composer/Test/CompletionFunctionalTest.php @@ -136,13 +136,13 @@ public function testComplete(string $input, ?array $expectedSuggestions): void $suggestions = $tester->complete($input); if (null === $expectedSuggestions) { - $this->assertEmpty($suggestions); + self::assertEmpty($suggestions); return; } $diff = array_diff($expectedSuggestions, $suggestions); - $this->assertEmpty($diff, sprintf('Suggestions must contain "%s". Got "%s".', implode('", "', $diff), implode('", "', $suggestions))); + self::assertEmpty($diff, sprintf('Suggestions must contain "%s". Got "%s".', implode('", "', $diff), implode('", "', $suggestions))); } private function getApplication(): Application diff --git a/tests/Composer/Test/ComposerTest.php b/tests/Composer/Test/ComposerTest.php index dea0f6a6b48b..c4bde7df80f0 100644 --- a/tests/Composer/Test/ComposerTest.php +++ b/tests/Composer/Test/ComposerTest.php @@ -22,7 +22,7 @@ public function testSetGetPackage(): void $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); $composer->setPackage($package); - $this->assertSame($package, $composer->getPackage()); + self::assertSame($package, $composer->getPackage()); } public function testSetGetLocker(): void @@ -31,7 +31,7 @@ public function testSetGetLocker(): void $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock(); $composer->setLocker($locker); - $this->assertSame($locker, $composer->getLocker()); + self::assertSame($locker, $composer->getLocker()); } public function testSetGetRepositoryManager(): void @@ -40,7 +40,7 @@ public function testSetGetRepositoryManager(): void $manager = $this->getMockBuilder('Composer\Repository\RepositoryManager')->disableOriginalConstructor()->getMock(); $composer->setRepositoryManager($manager); - $this->assertSame($manager, $composer->getRepositoryManager()); + self::assertSame($manager, $composer->getRepositoryManager()); } public function testSetGetDownloadManager(): void @@ -50,7 +50,7 @@ public function testSetGetDownloadManager(): void $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')->setConstructorArgs([$io])->getMock(); $composer->setDownloadManager($manager); - $this->assertSame($manager, $composer->getDownloadManager()); + self::assertSame($manager, $composer->getDownloadManager()); } public function testSetGetInstallationManager(): void @@ -59,6 +59,6 @@ public function testSetGetInstallationManager(): void $manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock(); $composer->setInstallationManager($manager); - $this->assertSame($manager, $composer->getInstallationManager()); + self::assertSame($manager, $composer->getInstallationManager()); } } diff --git a/tests/Composer/Test/Config/JsonConfigSourceTest.php b/tests/Composer/Test/Config/JsonConfigSourceTest.php index 536764fc01ee..9ec1c6868f28 100644 --- a/tests/Composer/Test/Config/JsonConfigSourceTest.php +++ b/tests/Composer/Test/Config/JsonConfigSourceTest.php @@ -50,7 +50,7 @@ public function testAddRepository(): void $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); $jsonConfigSource->addRepository('example_tld', ['type' => 'git', 'url' => 'example.tld']); - $this->assertFileEquals(self::fixturePath('config/config-with-exampletld-repository.json'), $config); + self::assertFileEquals(self::fixturePath('config/config-with-exampletld-repository.json'), $config); } public function testAddRepositoryWithOptions(): void @@ -68,7 +68,7 @@ public function testAddRepositoryWithOptions(): void ], ]); - $this->assertFileEquals(self::fixturePath('config/config-with-exampletld-repository-and-options.json'), $config); + self::assertFileEquals(self::fixturePath('config/config-with-exampletld-repository-and-options.json'), $config); } public function testRemoveRepository(): void @@ -78,7 +78,7 @@ public function testRemoveRepository(): void $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); $jsonConfigSource->removeRepository('example_tld'); - $this->assertFileEquals(self::fixturePath('composer-repositories.json'), $config); + self::assertFileEquals(self::fixturePath('composer-repositories.json'), $config); } public function testAddPackagistRepositoryWithFalseValue(): void @@ -88,7 +88,7 @@ public function testAddPackagistRepositoryWithFalseValue(): void $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); $jsonConfigSource->addRepository('packagist', false); - $this->assertFileEquals(self::fixturePath('config/config-with-packagist-false.json'), $config); + self::assertFileEquals(self::fixturePath('config/config-with-packagist-false.json'), $config); } public function testRemovePackagist(): void @@ -98,7 +98,7 @@ public function testRemovePackagist(): void $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); $jsonConfigSource->removeRepository('packagist'); - $this->assertFileEquals(self::fixturePath('composer-repositories.json'), $config); + self::assertFileEquals(self::fixturePath('composer-repositories.json'), $config); } /** @@ -120,7 +120,7 @@ public function testAddLink(string $sourceFile, string $type, string $name, stri $jsonConfigSource->addLink($type, $name, $value); - $this->assertFileEquals($compareAgainst, $composerJson); + self::assertFileEquals($compareAgainst, $composerJson); } /** @@ -141,7 +141,7 @@ public function testRemoveLink(string $sourceFile, string $type, string $name, s $jsonConfigSource->removeLink($type, $name); - $this->assertFileEquals($compareAgainst, $composerJson); + self::assertFileEquals($compareAgainst, $composerJson); } /** diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 08cdfac363e7..c8677962be5a 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -33,7 +33,7 @@ public function testAddPackagistRepository(array $expected, array $localConfig, } $config->merge(['repositories' => $localConfig]); - $this->assertEquals($expected, $config->getRepositories()); + self::assertEquals($expected, $config->getRepositories()); } public static function dataAddPackagistRepository(): array @@ -149,7 +149,7 @@ public function testPreferredInstallAsString(): void $config->merge(['config' => ['preferred-install' => 'source']]); $config->merge(['config' => ['preferred-install' => 'dist']]); - $this->assertEquals('dist', $config->get('preferred-install')); + self::assertEquals('dist', $config->get('preferred-install')); } public function testMergePreferredInstall(): void @@ -161,7 +161,7 @@ public function testMergePreferredInstall(): void // This assertion needs to make sure full wildcard preferences are placed last // Handled by composer because we convert string preferences for BC, all other // care for ordering and collision prevention is up to the user - $this->assertEquals(['foo/*' => 'source', '*' => 'dist'], $config->get('preferred-install')); + self::assertEquals(['foo/*' => 'source', '*' => 'dist'], $config->get('preferred-install')); } public function testMergeGithubOauth(): void @@ -170,7 +170,7 @@ public function testMergeGithubOauth(): void $config->merge(['config' => ['github-oauth' => ['foo' => 'bar']]]); $config->merge(['config' => ['github-oauth' => ['bar' => 'baz']]]); - $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $config->get('github-oauth')); + self::assertEquals(['foo' => 'bar', 'bar' => 'baz'], $config->get('github-oauth')); } public function testVarReplacement(): void @@ -180,9 +180,9 @@ public function testVarReplacement(): void $config->merge(['config' => ['bin-dir' => '$HOME', 'cache-dir' => '~/foo/']]); $home = rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '\\/'); - $this->assertEquals('b', $config->get('c')); - $this->assertEquals($home, $config->get('bin-dir')); - $this->assertEquals($home.'/foo', $config->get('cache-dir')); + self::assertEquals('b', $config->get('c')); + self::assertEquals($home, $config->get('bin-dir')); + self::assertEquals($home.'/foo', $config->get('cache-dir')); } public function testRealpathReplacement(): void @@ -195,9 +195,9 @@ public function testRealpathReplacement(): void ]]); $home = rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '\\/'); - $this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir')); - $this->assertEquals($home.'/foo', $config->get('bin-dir')); - $this->assertEquals('/baz', $config->get('cache-dir')); + self::assertEquals('/foo/bar/vendor', $config->get('vendor-dir')); + self::assertEquals($home.'/foo', $config->get('bin-dir')); + self::assertEquals('/baz', $config->get('cache-dir')); } public function testStreamWrapperDirs(): void @@ -207,7 +207,7 @@ public function testStreamWrapperDirs(): void 'cache-dir' => 's3://baz/', ]]); - $this->assertEquals('s3://baz', $config->get('cache-dir')); + self::assertEquals('s3://baz', $config->get('cache-dir')); } public function testFetchingRelativePaths(): void @@ -218,10 +218,10 @@ public function testFetchingRelativePaths(): void 'vendor-dir' => 'vendor', ]]); - $this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir')); - $this->assertEquals('/foo/bar/vendor/foo', $config->get('bin-dir')); - $this->assertEquals('vendor', $config->get('vendor-dir', Config::RELATIVE_PATHS)); - $this->assertEquals('vendor/foo', $config->get('bin-dir', Config::RELATIVE_PATHS)); + self::assertEquals('/foo/bar/vendor', $config->get('vendor-dir')); + self::assertEquals('/foo/bar/vendor/foo', $config->get('bin-dir')); + self::assertEquals('vendor', $config->get('vendor-dir', Config::RELATIVE_PATHS)); + self::assertEquals('vendor/foo', $config->get('bin-dir', Config::RELATIVE_PATHS)); } public function testOverrideGithubProtocols(): void @@ -230,17 +230,17 @@ public function testOverrideGithubProtocols(): void $config->merge(['config' => ['github-protocols' => ['https', 'ssh']]]); $config->merge(['config' => ['github-protocols' => ['https']]]); - $this->assertEquals(['https'], $config->get('github-protocols')); + self::assertEquals(['https'], $config->get('github-protocols')); } public function testGitDisabledByDefaultInGithubProtocols(): void { $config = new Config(false); $config->merge(['config' => ['github-protocols' => ['https', 'git']]]); - $this->assertEquals(['https'], $config->get('github-protocols')); + self::assertEquals(['https'], $config->get('github-protocols')); $config->merge(['config' => ['secure-http' => false]]); - $this->assertEquals(['https', 'git'], $config->get('github-protocols')); + self::assertEquals(['https', 'git'], $config->get('github-protocols')); } /** @@ -330,11 +330,11 @@ public function testDisableTlsCanBeOverridden(): void $config->merge( ['config' => ['disable-tls' => 'false']] ); - $this->assertFalse($config->get('disable-tls')); + self::assertFalse($config->get('disable-tls')); $config->merge( ['config' => ['disable-tls' => 'true']] ); - $this->assertTrue($config->get('disable-tls')); + self::assertTrue($config->get('disable-tls')); } public function testProcessTimeout(): void @@ -344,7 +344,7 @@ public function testProcessTimeout(): void $result = $config->get('process-timeout'); Platform::clearEnv('COMPOSER_PROCESS_TIMEOUT'); - $this->assertEquals(0, $result); + self::assertEquals(0, $result); } public function testHtaccessProtect(): void @@ -354,7 +354,7 @@ public function testHtaccessProtect(): void $result = $config->get('htaccess-protect'); Platform::clearEnv('COMPOSER_HTACCESS_PROTECT'); - $this->assertEquals(0, $result); + self::assertEquals(0, $result); } public function testGetSourceOfValue(): void @@ -363,14 +363,14 @@ public function testGetSourceOfValue(): void $config = new Config; - $this->assertSame(Config::SOURCE_DEFAULT, $config->getSourceOfValue('process-timeout')); + self::assertSame(Config::SOURCE_DEFAULT, $config->getSourceOfValue('process-timeout')); $config->merge( ['config' => ['process-timeout' => 1]], 'phpunit-test' ); - $this->assertSame('phpunit-test', $config->getSourceOfValue('process-timeout')); + self::assertSame('phpunit-test', $config->getSourceOfValue('process-timeout')); } public function testGetSourceOfValueEnvVariables(): void @@ -380,7 +380,7 @@ public function testGetSourceOfValueEnvVariables(): void $result = $config->getSourceOfValue('htaccess-protect'); Platform::clearEnv('COMPOSER_HTACCESS_PROTECT'); - $this->assertEquals('COMPOSER_HTACCESS_PROTECT', $result); + self::assertEquals('COMPOSER_HTACCESS_PROTECT', $result); } public function testAudit(): void @@ -420,8 +420,8 @@ public function testGetDefaultsToAnEmptyArray(): void ]; foreach ($keys as $key) { $value = $config->get($key); - $this->assertIsArray($value); // @phpstan-ignore staticMethod.dynamicCall (- PHPStan knows that its an array for all given keys) - $this->assertCount(0, $value); + self::assertIsArray($value); + self::assertCount(0, $value); } } @@ -429,29 +429,29 @@ public function testMergesPluginConfig(): void { $config = new Config(false); $config->merge(['config' => ['allow-plugins' => ['some/plugin' => true]]]); - $this->assertEquals(['some/plugin' => true], $config->get('allow-plugins')); + self::assertEquals(['some/plugin' => true], $config->get('allow-plugins')); $config->merge(['config' => ['allow-plugins' => ['another/plugin' => true]]]); - $this->assertEquals(['some/plugin' => true, 'another/plugin' => true], $config->get('allow-plugins')); + self::assertEquals(['some/plugin' => true, 'another/plugin' => true], $config->get('allow-plugins')); } public function testOverridesGlobalBooleanPluginsConfig(): void { $config = new Config(false); $config->merge(['config' => ['allow-plugins' => true]]); - $this->assertEquals(true, $config->get('allow-plugins')); + self::assertEquals(true, $config->get('allow-plugins')); $config->merge(['config' => ['allow-plugins' => ['another/plugin' => true]]]); - $this->assertEquals(['another/plugin' => true], $config->get('allow-plugins')); + self::assertEquals(['another/plugin' => true], $config->get('allow-plugins')); } public function testAllowsAllPluginsFromLocalBoolean(): void { $config = new Config(false); $config->merge(['config' => ['allow-plugins' => ['some/plugin' => true]]]); - $this->assertEquals(['some/plugin' => true], $config->get('allow-plugins')); + self::assertEquals(['some/plugin' => true], $config->get('allow-plugins')); $config->merge(['config' => ['allow-plugins' => true]]); - $this->assertEquals(true, $config->get('allow-plugins')); + self::assertEquals(true, $config->get('allow-plugins')); } } diff --git a/tests/Composer/Test/Console/HtmlOutputFormatterTest.php b/tests/Composer/Test/Console/HtmlOutputFormatterTest.php index 6d02a7396f1a..4337fcf60bb9 100644 --- a/tests/Composer/Test/Console/HtmlOutputFormatterTest.php +++ b/tests/Composer/Test/Console/HtmlOutputFormatterTest.php @@ -24,7 +24,7 @@ public function testFormatting(): void 'warning' => new OutputFormatterStyle('black', 'yellow'), ]); - $this->assertEquals( + self::assertEquals( 'text green yellow black w/ yello bg', $formatter->format('text green yellow black w/ yello bg') ); diff --git a/tests/Composer/Test/DefaultConfigTest.php b/tests/Composer/Test/DefaultConfigTest.php index a49a98ef80aa..b98da30da5a7 100644 --- a/tests/Composer/Test/DefaultConfigTest.php +++ b/tests/Composer/Test/DefaultConfigTest.php @@ -22,6 +22,6 @@ class DefaultConfigTest extends TestCase public function testDefaultValuesAreAsExpected(): void { $config = new Config; - $this->assertFalse($config->get('disable-tls')); + self::assertFalse($config->get('disable-tls')); } } diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 2f888d768c1e..8cb3662474a0 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -53,7 +53,7 @@ public function testSelectSingle(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewest(): void @@ -69,7 +69,7 @@ public function testSelectNewest(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewestPicksLatest(): void @@ -85,7 +85,7 @@ public function testSelectNewestPicksLatest(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewestPicksLatestStableWithPreferStable(): void @@ -102,7 +102,7 @@ public function testSelectNewestPicksLatestStableWithPreferStable(): void $policy = new DefaultPolicy(true); $selected = $policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewestWithDevPicksNonDev(): void @@ -118,7 +118,7 @@ public function testSelectNewestWithDevPicksNonDev(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewestWithPreferredVersionPicksPreferredVersionIfAvailable(): void @@ -137,7 +137,7 @@ public function testSelectNewestWithPreferredVersionPicksPreferredVersionIfAvail $policy = new DefaultPolicy(false, false, ['a' => '1.1.0.0']); $selected = $policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewestWithPreferredVersionPicksNewestOtherwise(): void @@ -154,7 +154,7 @@ public function testSelectNewestWithPreferredVersionPicksNewestOtherwise(): void $policy = new DefaultPolicy(false, false, ['a' => '1.1.0.0']); $selected = $policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectNewestWithPreferredVersionPicksLowestIfPreferLowest(): void @@ -171,7 +171,7 @@ public function testSelectNewestWithPreferredVersionPicksLowestIfPreferLowest(): $policy = new DefaultPolicy(false, true, ['a' => '1.1.0.0']); $selected = $policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testRepositoryOrderingAffectsPriority(): void @@ -193,7 +193,7 @@ public function testRepositoryOrderingAffectsPriority(): void $expected = [$package2->getId()]; $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); $this->repositorySet = new RepositorySet('dev'); $this->repositorySet->addRepository($repo2); @@ -204,7 +204,7 @@ public function testRepositoryOrderingAffectsPriority(): void $expected = [$package4->getId()]; $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectLocalReposFirst(): void @@ -235,7 +235,7 @@ public function testSelectLocalReposFirst(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectAllProviders(): void @@ -255,7 +255,7 @@ public function testSelectAllProviders(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testPreferNonReplacingFromSameRepo(): void @@ -274,7 +274,7 @@ public function testPreferNonReplacingFromSameRepo(): void $selected = $this->policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testPreferReplacingPackageFromSameVendor(): void @@ -294,7 +294,7 @@ public function testPreferReplacingPackageFromSameVendor(): void $expected = $literals; $selected = $this->policy->selectPreferredPackages($pool, $literals, 'vendor-a/package'); - $this->assertEquals($expected, $selected); + self::assertEquals($expected, $selected); // test with reversed order in repo $repo = new ArrayRepository; @@ -310,7 +310,7 @@ public function testPreferReplacingPackageFromSameVendor(): void $expected = $literals; $selected = $this->policy->selectPreferredPackages($pool, $literals, 'vendor-a/package'); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } public function testSelectLowest(): void @@ -328,6 +328,6 @@ public function testSelectLowest(): void $selected = $policy->selectPreferredPackages($pool, $literals); - $this->assertSame($expected, $selected); + self::assertSame($expected, $selected); } } diff --git a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php index 1b66f43195ce..4feb297ed2e8 100644 --- a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php @@ -139,13 +139,13 @@ public function testPoolBuilder(string $file, string $message, array $expect, ar sort($expect); sort($result); - $this->assertSame($expect, $result, 'Unoptimized pool does not match expected package set'); + self::assertSame($expect, $result, 'Unoptimized pool does not match expected package set'); $optimizer = new PoolOptimizer(new DefaultPolicy()); $result = $this->getPackageResultSet($optimizer->optimize($request, $pool), $packageIds); sort($expectOptimized); sort($result); - $this->assertSame($expectOptimized, $result, 'Optimized pool does not match expected package set'); + self::assertSame($expectOptimized, $result, 'Optimized pool does not match expected package set'); chdir($oldCwd); } diff --git a/tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php b/tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php index abba7f433149..d89745130d18 100644 --- a/tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php @@ -63,7 +63,7 @@ public function testPoolOptimizer(array $requestData, array $packagesBefore, arr $pool = $poolOptimizer->optimize($request, $pool); - $this->assertSame( + self::assertSame( $this->reducePackagesInfoForComparison($expectedPackages), $this->reducePackagesInfoForComparison($pool->getPackages()), $message diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index 7e96474aebe7..839b6c89ca6e 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -23,8 +23,8 @@ public function testPool(): void $pool = $this->createPool([$package]); - $this->assertEquals([$package], $pool->whatProvides('foo')); - $this->assertEquals([$package], $pool->whatProvides('foo')); + self::assertEquals([$package], $pool->whatProvides('foo')); + self::assertEquals([$package], $pool->whatProvides('foo')); } public function testWhatProvidesPackageWithConstraint(): void @@ -37,8 +37,8 @@ public function testWhatProvidesPackageWithConstraint(): void $secondPackage, ]); - $this->assertEquals([$firstPackage, $secondPackage], $pool->whatProvides('foo')); - $this->assertEquals([$secondPackage], $pool->whatProvides('foo', self::getVersionConstraint('==', '2'))); + self::assertEquals([$firstPackage, $secondPackage], $pool->whatProvides('foo')); + self::assertEquals([$secondPackage], $pool->whatProvides('foo', self::getVersionConstraint('==', '2'))); } public function testPackageById(): void @@ -47,14 +47,14 @@ public function testPackageById(): void $pool = $this->createPool([$package]); - $this->assertSame($package, $pool->packageById(1)); + self::assertSame($package, $pool->packageById(1)); } public function testWhatProvidesWhenPackageCannotBeFound(): void { $pool = $this->createPool(); - $this->assertEquals([], $pool->whatProvides('foo')); + self::assertEquals([], $pool->whatProvides('foo')); } /** diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index dec915744f81..5fe4f9286d3d 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -33,7 +33,7 @@ public function testRequestInstall(): void $request = new Request(); $request->requireName('foo'); - $this->assertEquals( + self::assertEquals( [ 'foo' => new MatchAllConstraint(), ], @@ -55,7 +55,7 @@ public function testRequestInstallSamePackageFromDifferentRepositories(): void $request = new Request(); $request->requireName('foo', $constraint = self::getVersionConstraint('=', '1')); - $this->assertEquals( + self::assertEquals( [ 'foo' => $constraint, ], diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index d158335d3eea..1df257dc2e52 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -58,7 +58,7 @@ public function testForeach(): void $this->rules[RuleSet::TYPE_LEARNED][0], ]; - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } public function testKeys(): void @@ -76,6 +76,6 @@ public function testKeys(): void RuleSet::TYPE_LEARNED, ]; - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index cc8e154c917b..acebb74b3ded 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -41,7 +41,7 @@ public function testAdd(): void $ruleSet->add($rules[RuleSet::TYPE_LEARNED][0], RuleSet::TYPE_LEARNED); $ruleSet->add($rules[RuleSet::TYPE_REQUEST][1], RuleSet::TYPE_REQUEST); - $this->assertEquals($rules, $ruleSet->getRules()); + self::assertEquals($rules, $ruleSet->getRules()); } public function testAddIgnoresDuplicates(): void @@ -60,7 +60,7 @@ public function testAddIgnoresDuplicates(): void $ruleSet->add($rules[RuleSet::TYPE_REQUEST][1], RuleSet::TYPE_REQUEST); $ruleSet->add($rules[RuleSet::TYPE_REQUEST][2], RuleSet::TYPE_REQUEST); - $this->assertCount(1, $ruleSet->getIteratorFor([RuleSet::TYPE_REQUEST])); + self::assertCount(1, $ruleSet->getIteratorFor([RuleSet::TYPE_REQUEST])); } public function testAddWhenTypeIsNotRecognized(): void @@ -79,7 +79,7 @@ public function testCount(): void $ruleSet->add(new GenericRule([1], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]), RuleSet::TYPE_REQUEST); $ruleSet->add(new GenericRule([2], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]), RuleSet::TYPE_REQUEST); - $this->assertEquals(2, $ruleSet->count()); + self::assertEquals(2, $ruleSet->count()); } public function testRuleById(): void @@ -89,7 +89,7 @@ public function testRuleById(): void $rule = new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $ruleSet->add($rule, RuleSet::TYPE_REQUEST); - $this->assertSame($rule, $ruleSet->ruleById[0]); + self::assertSame($rule, $ruleSet->ruleById[0]); } public function testGetIterator(): void @@ -103,9 +103,9 @@ public function testGetIterator(): void $iterator = $ruleSet->getIterator(); - $this->assertSame($rule1, $iterator->current()); + self::assertSame($rule1, $iterator->current()); $iterator->next(); - $this->assertSame($rule2, $iterator->current()); + self::assertSame($rule2, $iterator->current()); } public function testGetIteratorFor(): void @@ -119,7 +119,7 @@ public function testGetIteratorFor(): void $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_LEARNED); - $this->assertSame($rule2, $iterator->current()); + self::assertSame($rule2, $iterator->current()); } public function testGetIteratorWithout(): void @@ -133,7 +133,7 @@ public function testGetIteratorWithout(): void $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_REQUEST); - $this->assertSame($rule2, $iterator->current()); + self::assertSame($rule2, $iterator->current()); } public function testPrettyString(): void @@ -151,6 +151,6 @@ public function testPrettyString(): void $ruleSet->add($rule, RuleSet::TYPE_REQUEST); - $this->assertStringContainsString('REQUEST : No package found to satisfy root composer.json require foo/bar', $ruleSet->getPrettyString($repositorySetMock, $requestMock, $pool)); + self::assertStringContainsString('REQUEST : No package found to satisfy root composer.json require foo/bar', $ruleSet->getPrettyString($repositorySetMock, $requestMock, $pool)); } } diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index 4c31c5f77aa9..fe13c2c6c083 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -27,7 +27,7 @@ public function testGetHash(): void $rule = new GenericRule([123], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $hash = unpack('ihash', md5('123', true)); - $this->assertEquals($hash['hash'], $rule->getHash()); + self::assertEquals($hash['hash'], $rule->getHash()); } public function testEqualsForRulesWithDifferentHashes(): void @@ -35,7 +35,7 @@ public function testEqualsForRulesWithDifferentHashes(): void $rule = new GenericRule([1, 2], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $rule2 = new GenericRule([1, 3], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); - $this->assertFalse($rule->equals($rule2)); + self::assertFalse($rule->equals($rule2)); } public function testEqualsForRulesWithDifferLiteralsQuantity(): void @@ -43,7 +43,7 @@ public function testEqualsForRulesWithDifferLiteralsQuantity(): void $rule = new GenericRule([1, 12], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $rule2 = new GenericRule([1], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); - $this->assertFalse($rule->equals($rule2)); + self::assertFalse($rule->equals($rule2)); } public function testEqualsForRulesWithSameLiterals(): void @@ -51,7 +51,7 @@ public function testEqualsForRulesWithSameLiterals(): void $rule = new GenericRule([1, 12], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $rule2 = new GenericRule([1, 12], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); - $this->assertTrue($rule->equals($rule2)); + self::assertTrue($rule->equals($rule2)); } public function testSetAndGetType(): void @@ -59,7 +59,7 @@ public function testSetAndGetType(): void $rule = new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $rule->setType(RuleSet::TYPE_REQUEST); - $this->assertEquals(RuleSet::TYPE_REQUEST, $rule->getType()); + self::assertEquals(RuleSet::TYPE_REQUEST, $rule->getType()); } public function testEnable(): void @@ -68,8 +68,8 @@ public function testEnable(): void $rule->disable(); $rule->enable(); - $this->assertTrue($rule->isEnabled()); - $this->assertFalse($rule->isDisabled()); + self::assertTrue($rule->isEnabled()); + self::assertFalse($rule->isDisabled()); } public function testDisable(): void @@ -78,8 +78,8 @@ public function testDisable(): void $rule->enable(); $rule->disable(); - $this->assertTrue($rule->isDisabled()); - $this->assertFalse($rule->isEnabled()); + self::assertTrue($rule->isDisabled()); + self::assertFalse($rule->isEnabled()); } public function testIsAssertions(): void @@ -87,8 +87,8 @@ public function testIsAssertions(): void $rule = new GenericRule([1, 12], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); $rule2 = new GenericRule([1], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); - $this->assertFalse($rule->isAssertion()); - $this->assertTrue($rule2->isAssertion()); + self::assertFalse($rule->isAssertion()); + self::assertTrue($rule2->isAssertion()); } public function testPrettyString(): void @@ -106,6 +106,6 @@ public function testPrettyString(): void $rule = new GenericRule([$p1->getId(), -$p2->getId()], Rule::RULE_PACKAGE_REQUIRES, new Link('baz', 'foo', $emptyConstraint)); - $this->assertEquals('baz 1.1 relates to foo * -> satisfiable by foo[2.1].', $rule->getPrettyString($repositorySetMock, $requestMock, $pool, false)); + self::assertEquals('baz 1.1 relates to foo * -> satisfiable by foo[2.1].', $rule->getPrettyString($repositorySetMock, $requestMock, $pool, false)); } } diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 23d0c94e241a..c04f3f978040 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -94,9 +94,9 @@ public function testInstallNonExistingPackageFails(): void $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { $problems = $e->getProblems(); - $this->assertCount(1, $problems); - $this->assertEquals(2, $e->getCode()); - $this->assertEquals("\n - Root composer.json requires b, it could not be found in any version, there may be a typo in the package name.", $problems[0]->getPrettyString($this->repoSet, $this->request, $this->pool, false)); + self::assertCount(1, $problems); + self::assertEquals(2, $e->getCode()); + self::assertEquals("\n - Root composer.json requires b, it could not be found in any version, there may be a typo in the package name.", $problems[0]->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } @@ -772,14 +772,14 @@ public function testConflictResultEmpty(): void $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { $problems = $e->getProblems(); - $this->assertCount(1, $problems); + self::assertCount(1, $problems); $msg = "\n"; $msg .= " Problem 1\n"; $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; $msg .= " - A 1.0 conflicts with B 1.0.\n"; $msg .= " - Root composer.json requires b * -> satisfiable by B[1.0].\n"; - $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); + self::assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } @@ -802,14 +802,14 @@ public function testUnsatisfiableRequires(): void $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { $problems = $e->getProblems(); - $this->assertCount(1, $problems); + self::assertCount(1, $problems); // TODO assert problem properties $msg = "\n"; $msg .= " Problem 1\n"; $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; $msg .= " - A 1.0 requires b >= 2.0 -> found B[1.0] but it does not match the constraint.\n"; - $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); + self::assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } @@ -847,7 +847,7 @@ public function testRequireMismatchException(): void $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { $problems = $e->getProblems(); - $this->assertCount(1, $problems); + self::assertCount(1, $problems); $msg = "\n"; $msg .= " Problem 1\n"; @@ -857,7 +857,7 @@ public function testRequireMismatchException(): void $msg .= " - You can only install one version of a package, so only one of these can be installed: B[0.9, 1.0].\n"; $msg .= " - A 1.0 requires b >= 1.0 -> satisfiable by B[1.0].\n"; $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; - $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); + self::assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } @@ -1023,7 +1023,7 @@ public function testLearnPositiveLiteral(): void $this->createSolver(); // check correct setup for assertion later - $this->assertFalse($this->solver->testFlagLearnedPositiveLiteral); + self::assertFalse($this->solver->testFlagLearnedPositiveLiteral); $this->checkSolverResult([ ['job' => 'install', 'package' => $packageF1], @@ -1037,7 +1037,7 @@ public function testLearnPositiveLiteral(): void // verify that the code path leading to a negative literal resulting in a positive learned literal is actually // executed - $this->assertTrue($this->solver->testFlagLearnedPositiveLiteral); + self::assertTrue($this->solver->testFlagLearnedPositiveLiteral); } protected function reposComplete(): void @@ -1094,7 +1094,7 @@ protected function checkSolverResult(array $expected): void $resultReadable[] = array_map('strval', $op); } - $this->assertEquals($expectedReadable, $resultReadable); - $this->assertEquals($expected, $result); + self::assertEquals($expectedReadable, $resultReadable); + self::assertEquals($expected, $result); } } diff --git a/tests/Composer/Test/DependencyResolver/TransactionTest.php b/tests/Composer/Test/DependencyResolver/TransactionTest.php index 554ae3b3fcd6..6b5e230f5c0d 100644 --- a/tests/Composer/Test/DependencyResolver/TransactionTest.php +++ b/tests/Composer/Test/DependencyResolver/TransactionTest.php @@ -122,6 +122,6 @@ protected function checkTransactionOperations(Transaction $transaction, array $e } } - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } } diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index 36c6404d88c2..8a56de0e9dbd 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -37,8 +37,8 @@ public function testGetFileName(): void ->will($this->returnValue('/vendor')); $first = $method->invoke($downloader, $packageMock, '/path'); - $this->assertMatchesRegularExpression('#/vendor/composer/tmp-[a-z0-9]+\.js#', $first); - $this->assertSame($first, $method->invoke($downloader, $packageMock, '/path')); + self::assertMatchesRegularExpression('#/vendor/composer/tmp-[a-z0-9]+\.js#', $first); + self::assertSame($first, $method->invoke($downloader, $packageMock, '/path')); } public function testProcessUrl(): void @@ -54,7 +54,7 @@ public function testProcessUrl(): void $expected = 'https://github.com/composer/composer/zipball/master'; $url = $method->invoke($downloader, $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(), $expected); - $this->assertEquals($expected, $url); + self::assertEquals($expected, $url); } public function testProcessUrl2(): void @@ -70,7 +70,7 @@ public function testProcessUrl2(): void $expected = 'https://github.com/composer/composer/archive/master.tar.gz'; $url = $method->invoke($downloader, $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(), $expected); - $this->assertEquals($expected, $url); + self::assertEquals($expected, $url); } public function testProcessUrl3(): void @@ -86,7 +86,7 @@ public function testProcessUrl3(): void $expected = 'https://api.github.com/repos/composer/composer/zipball/master'; $url = $method->invoke($downloader, $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(), $expected); - $this->assertEquals($expected, $url); + self::assertEquals($expected, $url); } /** @@ -111,7 +111,7 @@ public function testProcessUrlRewriteDist(string $url): void ->will($this->returnValue('ref')); $url = $method->invoke($downloader, $package, $url); - $this->assertEquals($expected, $url); + self::assertEquals($expected, $url); } public static function provideUrls(): array @@ -148,7 +148,7 @@ public function testProcessUrlRewriteBitbucketDist(string $url, string $extensio ->will($this->returnValue('ref')); $url = $method->invoke($downloader, $package, $url); - $this->assertEquals($expected, $url); + self::assertEquals($expected, $url); } public static function provideBitbucketUrls(): array diff --git a/tests/Composer/Test/Downloader/DownloadManagerTest.php b/tests/Composer/Test/Downloader/DownloadManagerTest.php index 05d169cad0dc..885a17424d13 100644 --- a/tests/Composer/Test/Downloader/DownloadManagerTest.php +++ b/tests/Composer/Test/Downloader/DownloadManagerTest.php @@ -36,7 +36,7 @@ public function testSetGetDownloader(): void $manager = new DownloadManager($this->io, false, $this->filesystem); $manager->setDownloader('test', $downloader); - $this->assertSame($downloader, $manager->getDownloader('test')); + self::assertSame($downloader, $manager->getDownloader('test')); self::expectException('InvalidArgumentException'); $manager->getDownloader('unregistered'); @@ -86,7 +86,7 @@ public function testGetDownloaderForCorrectlyInstalledDistPackage(): void ->with('pear') ->will($this->returnValue($downloader)); - $this->assertSame($downloader, $manager->getDownloaderForPackage($package)); + self::assertSame($downloader, $manager->getDownloaderForPackage($package)); } public function testGetDownloaderForIncorrectlyInstalledDistPackage(): void @@ -152,7 +152,7 @@ public function testGetDownloaderForCorrectlyInstalledSourcePackage(): void ->with('git') ->will($this->returnValue($downloader)); - $this->assertSame($downloader, $manager->getDownloaderForPackage($package)); + self::assertSame($downloader, $manager->getDownloaderForPackage($package)); } public function testGetDownloaderForIncorrectlyInstalledSourcePackage(): void @@ -199,7 +199,7 @@ public function testGetDownloaderForMetapackage(): void $manager = new DownloadManager($this->io, false, $this->filesystem); - $this->assertNull($manager->getDownloaderForPackage($package)); + self::assertNull($manager->getDownloaderForPackage($package)); } public function testFullPackageDownload(): void @@ -264,7 +264,7 @@ public function testFullPackageDownloadFailover(): void 'source', ]; - $this->assertSame(array_shift($series), $type); + self::assertSame(array_shift($series), $type); }); $downloaderFail = $this->createDownloaderMock(); @@ -674,7 +674,7 @@ public function testGetAvailableSourcesUpdateSticksToSameSource(?string $prevPkg $manager = new DownloadManager($this->io, false, $this->filesystem); $method = new \ReflectionMethod($manager, 'getAvailableSources'); $method->setAccessible(true); - $this->assertEquals($expected, $method->invoke($manager, $target, $initial ?? null)); + self::assertEquals($expected, $method->invoke($manager, $target, $initial ?? null)); } public static function updatesProvider(): array diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 0978392f0408..967c726bb4a5 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -81,8 +81,8 @@ public function testDownloadToExistingFile(): void } elseif (is_file($path)) { unlink($path); } - $this->assertInstanceOf('RuntimeException', $e); - $this->assertStringContainsString('exists and is not a directory', $e->getMessage()); + self::assertInstanceOf('RuntimeException', $e); + self::assertStringContainsString('exists and is not a directory', $e->getMessage()); } } @@ -96,7 +96,7 @@ public function testGetFileName(): void $method = new \ReflectionMethod($downloader, 'getFileName'); $method->setAccessible(true); - $this->assertMatchesRegularExpression('#/vendor/composer/tmp-[a-z0-9]+\.js#', $method->invoke($downloader, $package, '/path')); + self::assertMatchesRegularExpression('#/vendor/composer/tmp-[a-z0-9]+\.js#', $method->invoke($downloader, $package, '/path')); } public function testDownloadButFileIsUnsaved(): void @@ -134,8 +134,8 @@ public function testDownloadButFileIsUnsaved(): void unlink($path); } - $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); - $this->assertStringContainsString('could not be saved to', $e->getMessage()); + self::assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); + self::assertStringContainsString('could not be saved to', $e->getMessage()); } } @@ -176,7 +176,7 @@ public function testDownloadWithCustomProcessedUrl(): void ->expects($this->any()) ->method('copyTo') ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey): bool { - $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); + self::assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); return false; })); @@ -184,7 +184,7 @@ public function testDownloadWithCustomProcessedUrl(): void ->expects($this->any()) ->method('copyFrom') ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey): bool { - $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); + self::assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); return false; })); @@ -194,7 +194,7 @@ public function testDownloadWithCustomProcessedUrl(): void ->expects($this->any()) ->method('addCopy') ->will($this->returnCallback(function ($url) use ($expectedUrl) { - $this->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); + self::assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); return \React\Promise\resolve( new Response(['url' => 'http://example.org/'], 200, [], 'file~') @@ -217,8 +217,8 @@ public function testDownloadWithCustomProcessedUrl(): void unlink($path); } - $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); - $this->assertStringContainsString('could not be saved to', $e->getMessage()); + self::assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); + self::assertStringContainsString('could not be saved to', $e->getMessage()); } } @@ -260,7 +260,7 @@ public function testDownloadWithCustomCacheKey(): void ->expects($this->any()) ->method('copyTo') ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey): bool { - $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); + self::assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); return false; })); @@ -268,7 +268,7 @@ public function testDownloadWithCustomCacheKey(): void ->expects($this->any()) ->method('copyFrom') ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey): bool { - $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); + self::assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); return false; })); @@ -278,7 +278,7 @@ public function testDownloadWithCustomCacheKey(): void ->expects($this->any()) ->method('addCopy') ->will($this->returnCallback(function ($url) use ($expectedUrl) { - $this->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); + self::assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); return \React\Promise\resolve( new Response(['url' => 'http://example.org/'], 200, [], 'file~') @@ -301,8 +301,8 @@ public function testDownloadWithCustomCacheKey(): void unlink($path); } - $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); - $this->assertStringContainsString('could not be saved to', $e->getMessage()); + self::assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); + self::assertStringContainsString('could not be saved to', $e->getMessage()); } } @@ -364,8 +364,8 @@ public function testDownloadFileWithInvalidChecksum(): void unlink($path); } - $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); - $this->assertStringContainsString('checksum verification', $e->getMessage()); + self::assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); + self::assertStringContainsString('checksum verification', $e->getMessage()); } } diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index c04450ab16f5..7ab784357871 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -162,6 +162,6 @@ public function testGetInstallationSource(): void { $downloader = $this->getDownloaderMock(null); - $this->assertEquals('source', $downloader->getInstallationSource()); + self::assertEquals('source', $downloader->getInstallationSource()); } } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 5411767e56b3..0abc0df59a42 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -304,7 +304,7 @@ public function testDownloadThrowsRuntimeExceptionIfGitCommandFails(): void if ('RuntimeException' !== get_class($e)) { throw $e; } - $this->assertEquals('RuntimeException', get_class($e)); + self::assertEquals('RuntimeException', get_class($e)); } } @@ -461,7 +461,7 @@ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails(): void if ('RuntimeException' !== get_class($e)) { throw $e; } - $this->assertEquals('RuntimeException', get_class($e)); + self::assertEquals('RuntimeException', get_class($e)); } } @@ -631,7 +631,7 @@ public function testGetInstallationSource(): void { $downloader = $this->getDownloaderMock(); - $this->assertEquals('source', $downloader->getInstallationSource()); + self::assertEquals('source', $downloader->getInstallationSource()); } private function winCompat(string $cmd): string diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index bdb8bde3ea3c..544d529941db 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -154,6 +154,6 @@ public function testGetInstallationSource(): void { $downloader = $this->getDownloaderMock(null); - $this->assertEquals('source', $downloader->getInstallationSource()); + self::assertEquals('source', $downloader->getInstallationSource()); } } diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index 83f54c8847f2..1cabefb568f4 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -66,7 +66,7 @@ public function testErrorMessages(): void $this->fail('Download of invalid tarball should throw an exception'); } catch (\RuntimeException $e) { - $this->assertMatchesRegularExpression('/(File format not recognized|Unrecognized archive format)/i', $e->getMessage()); + self::assertMatchesRegularExpression('/(File format not recognized|Unrecognized archive format)/i', $e->getMessage()); } } } diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 1155ec6b89fd..c5c168831504 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -102,7 +102,7 @@ public function testErrorMessages(): void $this->fail('Download of invalid zip files should throw an exception'); } catch (\Exception $e) { - $this->assertStringContainsString('is not a zip archive', $e->getMessage()); + self::assertStringContainsString('is not a zip archive', $e->getMessage()); } } diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index f0a7ecc5f8f3..233317c03c29 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -200,7 +200,7 @@ public function testDispatcherRemoveListener(): void .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL; - $this->assertEquals($expected, $io->getOutput()); + self::assertEquals($expected, $io->getOutput()); $dispatcher->removeListener($this); $dispatcher->dispatch('ev1'); @@ -208,7 +208,7 @@ public function testDispatcherRemoveListener(): void $expected .= '> ev1: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL; - $this->assertEquals($expected, $io->getOutput()); + self::assertEquals($expected, $io->getOutput()); } public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack(): void @@ -245,7 +245,7 @@ public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack(): void $expected = '> post-install-cmd: echo -n foo'.PHP_EOL. '> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL. '> post-install-cmd: echo -n bar'.PHP_EOL; - $this->assertEquals($expected, $io->getOutput()); + self::assertEquals($expected, $io->getOutput()); } public function testDispatcherCanPutEnv(): void @@ -274,7 +274,7 @@ public function testDispatcherCanPutEnv(): void $expected = '> post-install-cmd: @putenv ABC=123'.PHP_EOL. '> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::getTestEnv'.PHP_EOL; - $this->assertEquals($expected, $io->getOutput()); + self::assertEquals($expected, $io->getOutput()); } public function testDispatcherAppendsDirBinOnPathForEveryListener(): void @@ -386,7 +386,7 @@ public function testDispatcherCanExecuteComposerScriptGroups(): void '> group: @subgroup'.PHP_EOL. '> subgroup: echo -n baz'.PHP_EOL. '> group: echo -n bar'.PHP_EOL; - $this->assertEquals($expected, $io->getOutput()); + self::assertEquals($expected, $io->getOutput()); } public function testRecursionInScriptsNames(): void @@ -425,7 +425,7 @@ public function testRecursionInScriptsNames(): void $expected = "> helloWorld: @hello World".PHP_EOL. "> hello: echo Hello " .self::getCmd("'World'").PHP_EOL; - $this->assertEquals($expected, $io->getOutput()); + self::assertEquals($expected, $io->getOutput()); } public function testDispatcherDetectInfiniteRecursion(): void diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php index 8d148ec7cab6..67d212720f51 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php @@ -24,8 +24,8 @@ public function testIsIgnored(string $req, bool $expectIgnored): void { $platformRequirementFilter = new IgnoreAllPlatformRequirementFilter(); - $this->assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req)); - $this->assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req)); + self::assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req)); + self::assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req)); } /** diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php index 5f3cc1f514dd..30ff79bbc5b6 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php @@ -26,7 +26,7 @@ public function testIsIgnored(array $reqList, string $req, bool $expectIgnored): { $platformRequirementFilter = new IgnoreListPlatformRequirementFilter($reqList); - $this->assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req)); + self::assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req)); } /** @@ -58,7 +58,7 @@ public function testIsUpperBoundIgnored(array $reqList, string $req, bool $expec { $platformRequirementFilter = new IgnoreListPlatformRequirementFilter($reqList); - $this->assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req)); + self::assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req)); } /** diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php index 36fb69ba5601..512e747719b5 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php @@ -24,8 +24,8 @@ public function testIsIgnored(string $req): void { $platformRequirementFilter = new IgnoreNothingPlatformRequirementFilter(); - $this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore staticMethod.dynamicCall - $this->assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); // @phpstan-ignore staticMethod.dynamicCall + self::assertFalse($platformRequirementFilter->isIgnored($req)); + self::assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); } /** diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactoryTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactoryTest.php index 7fa9feba3cd5..b8c1c795dd36 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactoryTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactoryTest.php @@ -25,7 +25,7 @@ final class PlatformRequirementFilterFactoryTest extends TestCase */ public function testFromBoolOrList($boolOrList, $expectedInstance): void { - $this->assertInstanceOf($expectedInstance, PlatformRequirementFilterFactory::fromBoolOrList($boolOrList)); + self::assertInstanceOf($expectedInstance, PlatformRequirementFilterFactory::fromBoolOrList($boolOrList)); } /** @@ -52,13 +52,13 @@ public function testIgnoreAll(): void { $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreAll(); - $this->assertInstanceOf('Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter', $platformRequirementFilter); + self::assertInstanceOf('Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter', $platformRequirementFilter); } public function testIgnoreNothing(): void { $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); - $this->assertInstanceOf('Composer\Filter\PlatformRequirementFilter\IgnoreNothingPlatformRequirementFilter', $platformRequirementFilter); + self::assertInstanceOf('Composer\Filter\PlatformRequirementFilter\IgnoreNothingPlatformRequirementFilter', $platformRequirementFilter); } } diff --git a/tests/Composer/Test/IO/BufferIOTest.php b/tests/Composer/Test/IO/BufferIOTest.php index 0cf9796772f5..9ee7d7f0ec62 100644 --- a/tests/Composer/Test/IO/BufferIOTest.php +++ b/tests/Composer/Test/IO/BufferIOTest.php @@ -37,8 +37,8 @@ public function testSetUserInputs(): void '', ]); - $this->assertTrue($bufferIO->askConfirmation('Please say yes!', false)); - $this->assertFalse($bufferIO->askConfirmation('Now please say no!', true)); - $this->assertSame('default', $bufferIO->ask('Empty string last', 'default')); + self::assertTrue($bufferIO->askConfirmation('Please say yes!', false)); + self::assertFalse($bufferIO->askConfirmation('Now please say no!', true)); + self::assertSame('default', $bufferIO->ask('Empty string last', 'default')); } } diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index 2d152eb616ed..ddac19b27c30 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -34,8 +34,8 @@ public function testIsInteractive(): void $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); - $this->assertTrue($consoleIO->isInteractive()); - $this->assertFalse($consoleIO->isInteractive()); + self::assertTrue($consoleIO->isInteractive()); + self::assertFalse($consoleIO->isInteractive()); } public function testWrite(): void @@ -127,7 +127,7 @@ public function testOverwrite(): void } if (count($series) > 0) { - $this->assertSame(array_shift($series), [$args[0], $args[1]]); + self::assertSame(array_shift($series), [$args[0], $args[1]]); } }); @@ -252,7 +252,7 @@ public function testSelect(): void $consoleIO = new ConsoleIO($inputMock, $outputMock, $setMock); $result = $consoleIO->select('Select item', ["item1", "item2"], 'item1', false, "Error message", true); - $this->assertEquals(['1'], $result); + self::assertEquals(['1'], $result); } public function testSetAndgetAuthentication(): void @@ -264,7 +264,7 @@ public function testSetAndgetAuthentication(): void $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->setAuthentication('repoName', 'l3l0', 'passwd'); - $this->assertEquals( + self::assertEquals( ['username' => 'l3l0', 'password' => 'passwd'], $consoleIO->getAuthentication('repoName') ); @@ -278,7 +278,7 @@ public function testGetAuthenticationWhenDidNotSet(): void $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); - $this->assertEquals( + self::assertEquals( ['username' => null, 'password' => null], $consoleIO->getAuthentication('repoName') ); @@ -293,7 +293,7 @@ public function testHasAuthentication(): void $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->setAuthentication('repoName', 'l3l0', 'passwd'); - $this->assertTrue($consoleIO->hasAuthentication('repoName')); - $this->assertFalse($consoleIO->hasAuthentication('repoName2')); + self::assertTrue($consoleIO->hasAuthentication('repoName')); + self::assertFalse($consoleIO->hasAuthentication('repoName2')); } } diff --git a/tests/Composer/Test/IO/NullIOTest.php b/tests/Composer/Test/IO/NullIOTest.php index 6bc3aa0432c8..987ea7071f6a 100644 --- a/tests/Composer/Test/IO/NullIOTest.php +++ b/tests/Composer/Test/IO/NullIOTest.php @@ -21,51 +21,51 @@ public function testIsInteractive(): void { $io = new NullIO(); - $this->assertFalse($io->isInteractive()); + self::assertFalse($io->isInteractive()); } public function testhasAuthentication(): void { $io = new NullIO(); - $this->assertFalse($io->hasAuthentication('foo')); + self::assertFalse($io->hasAuthentication('foo')); } public function testAskAndHideAnswer(): void { $io = new NullIO(); - $this->assertNull($io->askAndHideAnswer('foo')); + self::assertNull($io->askAndHideAnswer('foo')); } public function testgetAuthentications(): void { $io = new NullIO(); - $this->assertIsArray($io->getAuthentications()); // @phpstan-ignore staticMethod.dynamicCall - $this->assertEmpty($io->getAuthentications()); - $this->assertEquals(['username' => null, 'password' => null], $io->getAuthentication('foo')); + self::assertIsArray($io->getAuthentications()); + self::assertEmpty($io->getAuthentications()); + self::assertEquals(['username' => null, 'password' => null], $io->getAuthentication('foo')); } public function testAsk(): void { $io = new NullIO(); - $this->assertEquals('foo', $io->ask('bar', 'foo')); + self::assertEquals('foo', $io->ask('bar', 'foo')); } public function testAskConfirmation(): void { $io = new NullIO(); - $this->assertEquals(false, $io->askConfirmation('bar', false)); + self::assertEquals(false, $io->askConfirmation('bar', false)); } public function testAskAndValidate(): void { $io = new NullIO(); - $this->assertEquals('foo', $io->askAndValidate('question', static function ($x): bool { + self::assertEquals('foo', $io->askAndValidate('question', static function ($x): bool { return true; }, null, 'foo')); } @@ -74,6 +74,6 @@ public function testSelect(): void { $io = new NullIO(); - $this->assertEquals('1', $io->select('question', ['item1', 'item2'], '1', 2, 'foo', true)); + self::assertEquals('1', $io->select('question', ['item1', 'item2'], '1', 2, 'foo', true)); } } diff --git a/tests/Composer/Test/InstalledVersionsTest.php b/tests/Composer/Test/InstalledVersionsTest.php index 7e34f7e37d20..fdcf28fbba70 100644 --- a/tests/Composer/Test/InstalledVersionsTest.php +++ b/tests/Composer/Test/InstalledVersionsTest.php @@ -65,7 +65,7 @@ public function testGetInstalledPackages(): void 'foo/replaced', 'meta/package', ]; - $this->assertSame($names, InstalledVersions::getInstalledPackages()); + self::assertSame($names, InstalledVersions::getInstalledPackages()); } /** @@ -73,7 +73,7 @@ public function testGetInstalledPackages(): void */ public function testIsInstalled(bool $expected, string $name, bool $includeDevRequirements = true): void { - $this->assertSame($expected, InstalledVersions::isInstalled($name, $includeDevRequirements)); + self::assertSame($expected, InstalledVersions::isInstalled($name, $includeDevRequirements)); } public static function isInstalledProvider(): array @@ -95,7 +95,7 @@ public static function isInstalledProvider(): array */ public function testSatisfies(bool $expected, string $name, string $constraint): void { - $this->assertSame($expected, InstalledVersions::satisfies(new VersionParser, $name, $constraint)); + self::assertSame($expected, InstalledVersions::satisfies(new VersionParser, $name, $constraint)); } public static function satisfiesProvider(): array @@ -133,7 +133,7 @@ public static function satisfiesProvider(): array */ public function testGetVersionRanges(string $expected, string $name): void { - $this->assertSame($expected, InstalledVersions::getVersionRanges($name)); + self::assertSame($expected, InstalledVersions::getVersionRanges($name)); } public static function getVersionRangesProvider(): array @@ -155,7 +155,7 @@ public static function getVersionRangesProvider(): array */ public function testGetVersion(?string $expected, string $name): void { - $this->assertSame($expected, InstalledVersions::getVersion($name)); + self::assertSame($expected, InstalledVersions::getVersion($name)); } public static function getVersionProvider(): array @@ -177,7 +177,7 @@ public static function getVersionProvider(): array */ public function testGetPrettyVersion(?string $expected, string $name): void { - $this->assertSame($expected, InstalledVersions::getPrettyVersion($name)); + self::assertSame($expected, InstalledVersions::getPrettyVersion($name)); } public static function getPrettyVersionProvider(): array @@ -202,7 +202,7 @@ public function testGetVersionOutOfBounds(): void public function testGetRootPackage(): void { - $this->assertSame([ + self::assertSame([ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', @@ -222,7 +222,7 @@ public function testGetRootPackage(): void public function testGetRawData(): void { $dir = $this->root; - $this->assertSame(require __DIR__.'/Repository/Fixtures/installed_relative.php', InstalledVersions::getRawData()); + self::assertSame(require __DIR__.'/Repository/Fixtures/installed_relative.php', InstalledVersions::getRawData()); } /** @@ -230,7 +230,7 @@ public function testGetRawData(): void */ public function testGetReference(?string $expected, string $name): void { - $this->assertSame($expected, InstalledVersions::getReference($name)); + self::assertSame($expected, InstalledVersions::getReference($name)); } public static function getReferenceProvider(): array @@ -257,13 +257,13 @@ public function testGetInstalledPackagesByType(): void 'c/c', ]; - $this->assertSame($names, \Composer\InstalledVersions::getInstalledPackagesByType('library')); + self::assertSame($names, \Composer\InstalledVersions::getInstalledPackagesByType('library')); } public function testGetInstallPath(): void { - $this->assertSame(realpath($this->root), realpath(\Composer\InstalledVersions::getInstallPath('__root__'))); - $this->assertSame('/foo/bar/vendor/c/c', \Composer\InstalledVersions::getInstallPath('c/c')); - $this->assertNull(\Composer\InstalledVersions::getInstallPath('foo/impl')); + self::assertSame(realpath($this->root), realpath(\Composer\InstalledVersions::getInstallPath('__root__'))); + self::assertSame('/foo/bar/vendor/c/c', \Composer\InstalledVersions::getInstallPath('c/c')); + self::assertNull(\Composer\InstalledVersions::getInstallPath('foo/impl')); } } diff --git a/tests/Composer/Test/Installer/BinaryInstallerTest.php b/tests/Composer/Test/Installer/BinaryInstallerTest.php index 8b6d472435e1..00f6ce6184d9 100644 --- a/tests/Composer/Test/Installer/BinaryInstallerTest.php +++ b/tests/Composer/Test/Installer/BinaryInstallerTest.php @@ -82,8 +82,8 @@ public function testInstallAndExecBinaryWithFullCompat(string $contents): void $proc = new ProcessExecutor(); $proc->execute($this->binDir.'/binary arg', $output); - $this->assertEquals('', $proc->getErrorOutput()); - $this->assertEquals('success arg', $output); + self::assertEquals('', $proc->getErrorOutput()); + self::assertEquals('success arg', $output); } public static function executableBinaryProvider(): array diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index d1c7746d0319..f253176bb28d 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -57,7 +57,7 @@ public function testAddGetInstaller(): void $manager = new InstallationManager($this->loop, $this->io); $manager->addInstaller($installer); - $this->assertSame($installer, $manager->getInstaller('vendor')); + self::assertSame($installer, $manager->getInstaller('vendor')); self::expectException('InvalidArgumentException'); $manager->getInstaller('unregistered'); @@ -86,11 +86,11 @@ public function testAddRemoveInstaller(): void $manager = new InstallationManager($this->loop, $this->io); $manager->addInstaller($installer); - $this->assertSame($installer, $manager->getInstaller('vendor')); + self::assertSame($installer, $manager->getInstaller('vendor')); $manager->addInstaller($installer2); - $this->assertSame($installer2, $manager->getInstaller('vendor')); + self::assertSame($installer2, $manager->getInstaller('vendor')); $manager->removeInstaller($installer2); - $this->assertSame($installer, $manager->getInstaller('vendor')); + self::assertSame($installer, $manager->getInstaller('vendor')); } public function testExecute(): void diff --git a/tests/Composer/Test/Installer/InstallerEventTest.php b/tests/Composer/Test/Installer/InstallerEventTest.php index 834f7ac7b0fd..de87ba7b8bdf 100644 --- a/tests/Composer/Test/Installer/InstallerEventTest.php +++ b/tests/Composer/Test/Installer/InstallerEventTest.php @@ -24,11 +24,9 @@ public function testGetter(): void $transaction = $this->getMockBuilder('Composer\DependencyResolver\LockTransaction')->disableOriginalConstructor()->getMock(); $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, true, $transaction); - $this->assertSame('EVENT_NAME', $event->getName()); - $this->assertInstanceOf('Composer\Composer', $event->getComposer()); - $this->assertInstanceOf('Composer\IO\IOInterface', $event->getIO()); - $this->assertTrue($event->isDevMode()); - $this->assertTrue($event->isExecutingOperations()); - $this->assertInstanceOf('Composer\DependencyResolver\Transaction', $event->getTransaction()); + self::assertSame('EVENT_NAME', $event->getName()); + self::assertTrue($event->isDevMode()); + self::assertTrue($event->isExecutingOperations()); + self::assertInstanceOf('Composer\DependencyResolver\Transaction', $event->getTransaction()); } } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 3161687de278..aa63eb2241ac 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -108,7 +108,7 @@ public function testInstallerCreationShouldNotCreateVendorDirectory(): void $this->fs->removeDirectory($this->vendorDir); new LibraryInstaller($this->io, $this->composer); - $this->assertFileDoesNotExist($this->vendorDir); + self::assertFileDoesNotExist($this->vendorDir); } public function testInstallerCreationShouldNotCreateBinDirectory(): void @@ -116,7 +116,7 @@ public function testInstallerCreationShouldNotCreateBinDirectory(): void $this->fs->removeDirectory($this->binDir); new LibraryInstaller($this->io, $this->composer); - $this->assertFileDoesNotExist($this->binDir); + self::assertFileDoesNotExist($this->binDir); } public function testIsInstalled(): void @@ -125,18 +125,18 @@ public function testIsInstalled(): void $package = self::getPackage('test/pkg', '1.0.0'); $repository = new InstalledArrayRepository(); - $this->assertFalse($library->isInstalled($repository, $package)); + self::assertFalse($library->isInstalled($repository, $package)); // package being in repo is not enough to be installed $repository->addPackage($package); - $this->assertFalse($library->isInstalled($repository, $package)); + self::assertFalse($library->isInstalled($repository, $package)); // package being in repo and vendor/pkg/foo dir present means it is seen as installed self::ensureDirectoryExistsAndClear($this->vendorDir.'/'.$package->getPrettyName()); - $this->assertTrue($library->isInstalled($repository, $package)); + self::assertTrue($library->isInstalled($repository, $package)); $repository->removePackage($package); - $this->assertFalse($library->isInstalled($repository, $package)); + self::assertFalse($library->isInstalled($repository, $package)); } /** @@ -160,8 +160,8 @@ public function testInstall(): void ->with($package); $library->install($this->repository, $package); - $this->assertFileExists($this->vendorDir, 'Vendor dir should be created'); - $this->assertFileExists($this->binDir, 'Bin dir should be created'); + self::assertFileExists($this->vendorDir, 'Vendor dir should be created'); + self::assertFileExists($this->binDir, 'Bin dir should be created'); } /** @@ -206,8 +206,8 @@ public function testUpdate(): void $library = new LibraryInstaller($this->io, $this->composer, 'library', $filesystem); $library->update($this->repository, $initial, $target); - $this->assertFileExists($this->vendorDir, 'Vendor dir should be created'); - $this->assertFileExists($this->binDir, 'Bin dir should be created'); + self::assertFileExists($this->vendorDir, 'Vendor dir should be created'); + self::assertFileExists($this->binDir, 'Bin dir should be created'); self::expectException('InvalidArgumentException'); @@ -248,7 +248,7 @@ public function testGetInstallPathWithoutTargetDir(): void $library = new LibraryInstaller($this->io, $this->composer); $package = self::getPackage('Vendor/Pkg', '1.0.0'); - $this->assertEquals($this->vendorDir.'/'.$package->getPrettyName(), $library->getInstallPath($package)); + self::assertEquals($this->vendorDir.'/'.$package->getPrettyName(), $library->getInstallPath($package)); } public function testGetInstallPathWithTargetDir(): void @@ -257,7 +257,7 @@ public function testGetInstallPathWithTargetDir(): void $package = self::getPackage('Foo/Bar', '1.0.0'); $package->setTargetDir('Some/Namespace'); - $this->assertEquals($this->vendorDir.'/'.$package->getPrettyName().'/Some/Namespace', $library->getInstallPath($package)); + self::assertEquals($this->vendorDir.'/'.$package->getPrettyName().'/Some/Namespace', $library->getInstallPath($package)); } /** diff --git a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php index a9969c4f4cc8..3f412c3d4f4d 100644 --- a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php +++ b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php @@ -56,7 +56,7 @@ public function testConstructor(): void */ public function testGetPackagesEmptyByDefault(): void { - $this->assertEmpty($this->suggestedPackagesReporter->getPackages()); + self::assertEmpty($this->suggestedPackagesReporter->getPackages()); } /** @@ -71,7 +71,7 @@ public function testGetPackages(): void $suggestedPackage['target'], $suggestedPackage['reason'] ); - $this->assertSame( + self::assertSame( [$suggestedPackage], $this->suggestedPackagesReporter->getPackages() ); @@ -99,7 +99,7 @@ public function testAddPackageAppends(): void $suggestedPackageB['target'], $suggestedPackageB['reason'] ); - $this->assertSame( + self::assertSame( [$suggestedPackageA, $suggestedPackageB], $this->suggestedPackagesReporter->getPackages() ); @@ -122,7 +122,7 @@ public function testAddSuggestionsFromPackage(): void ->will($this->returnValue('package-pretty-name')); $this->suggestedPackagesReporter->addSuggestionsFromPackage($package); - $this->assertSame([ + self::assertSame([ [ 'source' => 'package-pretty-name', 'target' => 'target-a', diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 490eac99606f..cecbdd942439 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -138,20 +138,20 @@ public function testInstaller(RootPackageInterface $rootPackage, array $reposito $result = $installer->run(); $output = str_replace("\r", '', $io->getOutput()); - $this->assertEquals(0, $result, $output); + self::assertEquals(0, $result, $output); $expectedInstalled = $options['install'] ?? []; $expectedUpdated = $options['update'] ?? []; $expectedUninstalled = $options['uninstall'] ?? []; $installed = $installationManager->getInstalledPackages(); - $this->assertEquals($this->makePackagesComparable($expectedInstalled), $this->makePackagesComparable($installed)); + self::assertEquals($this->makePackagesComparable($expectedInstalled), $this->makePackagesComparable($installed)); $updated = $installationManager->getUpdatedPackages(); - $this->assertSame($expectedUpdated, $updated); + self::assertSame($expectedUpdated, $updated); $uninstalled = $installationManager->getUninstalledPackages(); - $this->assertSame($expectedUninstalled, $uninstalled); + self::assertSame($expectedUninstalled, $uninstalled); } /** @@ -441,10 +441,10 @@ private function doTestIntegration(string $file, string $message, ?string $condi } $output = str_replace("\r", '', $io->getOutput()); - $this->assertEquals($expectResult, $result, $output . stream_get_contents($appOutput)); + self::assertEquals($expectResult, $result, $output . stream_get_contents($appOutput)); if ($expectLock && isset($actualLock)) { unset($actualLock['hash'], $actualLock['content-hash'], $actualLock['_readme'], $actualLock['plugin-api-version']); - $this->assertEquals($expectLock, $actualLock); + self::assertEquals($expectLock, $actualLock); } if ($expectInstalled !== null) { @@ -461,18 +461,18 @@ private function doTestIntegration(string $file, string $message, ?string $condi return strcmp($a['name'], $b['name']); }); - $this->assertSame($expectInstalled, $actualInstalled); + self::assertSame($expectInstalled, $actualInstalled); } /** @var InstallationManagerMock $installationManager */ $installationManager = $composer->getInstallationManager(); - $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); + self::assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); if ($expectOutput) { $output = Preg::replace('{^ - .*?\.ini$}m', '__inilist__', $output); $output = Preg::replace('{(__inilist__\r?\n)+}', "__inilist__\n", $output); - $this->assertStringMatchesFormat(rtrim($expectOutput), rtrim($output)); + self::assertStringMatchesFormat(rtrim($expectOutput), rtrim($output)); } } diff --git a/tests/Composer/Test/Json/ComposerSchemaTest.php b/tests/Composer/Test/Json/ComposerSchemaTest.php index 9a673509102c..4a2acee3db04 100644 --- a/tests/Composer/Test/Json/ComposerSchemaTest.php +++ b/tests/Composer/Test/Json/ComposerSchemaTest.php @@ -33,21 +33,21 @@ public function testNamePattern(): void ]; $json = '{"name": "vendor/-pack__age", "description": "description"}'; - $this->assertEquals($expectedError, $this->check($json)); + self::assertEquals($expectedError, $this->check($json)); $json = '{"name": "Vendor/Package", "description": "description"}'; - $this->assertEquals($expectedError, $this->check($json)); + self::assertEquals($expectedError, $this->check($json)); } public function testOptionalAbandonedProperty(): void { $json = '{"name": "vendor/package", "description": "description", "abandoned": true}'; - $this->assertTrue($this->check($json)); + self::assertTrue($this->check($json)); } public function testRequireTypes(): void { $json = '{"name": "vendor/package", "description": "description", "require": {"a": ["b"]} }'; - $this->assertEquals([ + self::assertEquals([ ['property' => 'require.a', 'message' => 'Array value found, but a string is required', 'constraint' => 'type'], ], $this->check($json)); } @@ -64,31 +64,31 @@ public function testMinimumStabilityValues(): void ]; $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "" }'; - $this->assertEquals($expectedError, $this->check($json), 'empty string'); + self::assertEquals($expectedError, $this->check($json), 'empty string'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "dummy" }'; - $this->assertEquals($expectedError, $this->check($json), 'dummy'); + self::assertEquals($expectedError, $this->check($json), 'dummy'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "devz" }'; - $this->assertEquals($expectedError, $this->check($json), 'devz'); + self::assertEquals($expectedError, $this->check($json), 'devz'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "dev" }'; - $this->assertTrue($this->check($json), 'dev'); + self::assertTrue($this->check($json), 'dev'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "alpha" }'; - $this->assertTrue($this->check($json), 'alpha'); + self::assertTrue($this->check($json), 'alpha'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "beta" }'; - $this->assertTrue($this->check($json), 'beta'); + self::assertTrue($this->check($json), 'beta'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "rc" }'; - $this->assertTrue($this->check($json), 'rc lowercase'); + self::assertTrue($this->check($json), 'rc lowercase'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "RC" }'; - $this->assertTrue($this->check($json), 'rc uppercase'); + self::assertTrue($this->check($json), 'rc uppercase'); $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "stable" }'; - $this->assertTrue($this->check($json), 'stable'); + self::assertTrue($this->check($json), 'stable'); } /** diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 74b124ec3891..ed7623613252 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -110,15 +110,15 @@ public function testSchemaValidationError(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertContains($expectedError, $e->getErrors()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertContains($expectedError, $e->getErrors()); } try { $json->validateSchema(JsonFile::LAX_SCHEMA); $this->fail('Expected exception to be thrown (lax)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertContains($expectedError, $e->getErrors()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertContains($expectedError, $e->getErrors()); } unlink($file); } @@ -132,8 +132,8 @@ public function testSchemaValidationLaxAdditionalProperties(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals(sprintf('"%s" does not match the expected JSON schema', $file), $e->getMessage()); - $this->assertEquals(['The property foo is not defined and the definition does not allow additional properties'], $e->getErrors()); + self::assertEquals(sprintf('"%s" does not match the expected JSON schema', $file), $e->getMessage()); + self::assertEquals(['The property foo is not defined and the definition does not allow additional properties'], $e->getErrors()); } $json->validateSchema(JsonFile::LAX_SCHEMA); unlink($file); @@ -151,10 +151,10 @@ public function testSchemaValidationLaxRequired(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getMessage()); $errors = $e->getErrors(); - $this->assertContains('name : The property name is required', $errors); - $this->assertContains('description : The property description is required', $errors); + self::assertContains('name : The property name is required', $errors); + self::assertContains('description : The property description is required', $errors); } $json->validateSchema(JsonFile::LAX_SCHEMA); @@ -163,8 +163,8 @@ public function testSchemaValidationLaxRequired(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals(['description : The property description is required'], $e->getErrors()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals(['description : The property description is required'], $e->getErrors()); } $json->validateSchema(JsonFile::LAX_SCHEMA); @@ -173,8 +173,8 @@ public function testSchemaValidationLaxRequired(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertEquals(['name : The property name is required'], $e->getErrors()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals(['name : The property name is required'], $e->getErrors()); } $json->validateSchema(JsonFile::LAX_SCHEMA); @@ -183,10 +183,10 @@ public function testSchemaValidationLaxRequired(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getMessage()); $errors = $e->getErrors(); - $this->assertContains('name : The property name is required', $errors); - $this->assertContains('description : The property description is required', $errors); + self::assertContains('name : The property name is required', $errors); + self::assertContains('description : The property description is required', $errors); } $json->validateSchema(JsonFile::LAX_SCHEMA); @@ -195,10 +195,10 @@ public function testSchemaValidationLaxRequired(): void $json->validateSchema(); $this->fail('Expected exception to be thrown (strict)'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); + self::assertEquals($expectedMessage, $e->getMessage()); $errors = $e->getErrors(); - $this->assertContains('name : The property name is required', $errors); - $this->assertContains('description : The property description is required', $errors); + self::assertContains('name : The property name is required', $errors); + self::assertContains('description : The property description is required', $errors); } $json->validateSchema(JsonFile::LAX_SCHEMA); @@ -252,8 +252,8 @@ public function testAuthSchemaValidationWithCustomDataSource(): void JsonFile::validateJsonSchema('COMPOSER_AUTH', $json, JsonFile::AUTH_SCHEMA); $this->fail('Expected exception to be thrown'); } catch (JsonValidationException $e) { - $this->assertEquals($expectedMessage, $e->getMessage()); - $this->assertSame([$expectedError], $e->getErrors()); + self::assertEquals($expectedMessage, $e->getMessage()); + self::assertSame([$expectedError], $e->getErrors()); } } @@ -282,7 +282,7 @@ public function testSimpleJsonString(): void $json = '{ "name": "composer/composer" }'; - $this->assertJsonFormat($json, $data); + self::assertJsonFormat($json, $data); } public function testTrailingBackslash(): void @@ -291,7 +291,7 @@ public function testTrailingBackslash(): void $json = '{ "Metadata\\\\": "src/" }'; - $this->assertJsonFormat($json, $data); + self::assertJsonFormat($json, $data); } public function testFormatEmptyArray(): void @@ -301,7 +301,7 @@ public function testFormatEmptyArray(): void "test": [], "test2": {} }'; - $this->assertJsonFormat($json, $data); + self::assertJsonFormat($json, $data); } public function testEscape(): void @@ -311,7 +311,7 @@ public function testEscape(): void "Metadata\\\\\\"": "src/" }'; - $this->assertJsonFormat($json, $data); + self::assertJsonFormat($json, $data); } public function testUnicode(): void @@ -321,35 +321,35 @@ public function testUnicode(): void "Žluťoučký \" kůň": "úpěl ďábelské ódy za €" }'; - $this->assertJsonFormat($json, $data); + self::assertJsonFormat($json, $data); } public function testOnlyUnicode(): void { $data = "\\/ƌ"; - $this->assertJsonFormat('"\\\\\\/ƌ"', $data, JSON_UNESCAPED_UNICODE); + self::assertJsonFormat('"\\\\\\/ƌ"', $data, JSON_UNESCAPED_UNICODE); } public function testEscapedSlashes(): void { $data = "\\/foo"; - $this->assertJsonFormat('"\\\\\\/foo"', $data, 0); + self::assertJsonFormat('"\\\\\\/foo"', $data, 0); } public function testEscapedBackslashes(): void { $data = "a\\b"; - $this->assertJsonFormat('"a\\\\b"', $data, 0); + self::assertJsonFormat('"a\\\\b"', $data, 0); } public function testEscapedUnicode(): void { $data = "ƌ"; - $this->assertJsonFormat('"\\u018c"', $data, 0); + self::assertJsonFormat('"\\u018c"', $data, 0); } public function testDoubleEscapedUnicode(): void @@ -361,7 +361,7 @@ public function testDoubleEscapedUnicode(): void $decodedData = json_decode($doubleEncodedData, true); $doubleData = json_decode($decodedData['t'], true); - $this->assertEquals($data, $doubleData); + self::assertEquals($data, $doubleData); } public function testPreserveIndentationAfterRead(): void @@ -393,7 +393,7 @@ private function expectParseException(string $text, string $json): void $result = JsonFile::parseJson($json); $this->fail(sprintf("Parsing should have failed but didn't.\nExpected:\n\"%s\"\nFor:\n\"%s\"\nGot:\n\"%s\"", $text, $json, var_export($result, true))); } catch (ParsingException $e) { - $this->assertStringContainsString($text, $e->getMessage()); + self::assertStringContainsString($text, $e->getMessage()); } } @@ -406,9 +406,9 @@ private function assertJsonFormat(string $json, $data, ?int $options = null): vo $json = str_replace("\r", '', $json); if (null === $options) { - $this->assertEquals($json, $file->encode($data)); + self::assertEquals($json, $file->encode($data)); } else { - $this->assertEquals($json, $file->encode($data, $options)); + self::assertEquals($json, $file->encode($data, $options)); } } } diff --git a/tests/Composer/Test/Json/JsonFormatterTest.php b/tests/Composer/Test/Json/JsonFormatterTest.php index 9bc38c673510..44ca8d2cf78d 100644 --- a/tests/Composer/Test/Json/JsonFormatterTest.php +++ b/tests/Composer/Test/Json/JsonFormatterTest.php @@ -30,7 +30,7 @@ public function testUnicodeWithPrependedSlash(): void $data = '"' . $backslash . $backslash . $backslash . 'u0119"'; $expected = '"' . $backslash . $backslash . 'ę"'; /** @phpstan-ignore staticMethod.dynamicCall, staticMethod.deprecatedClass */ - $this->assertEquals($expected, JsonFormatter::format($data, true, true)); + self::assertEquals($expected, JsonFormatter::format($data, true, true)); } /** @@ -45,6 +45,6 @@ public function testUtf16SurrogatePair(): void $escaped = '"\ud83d\ude00"'; /** @phpstan-ignore staticMethod.dynamicCall, staticMethod.deprecatedClass */ - $this->assertEquals($escaped, JsonFormatter::format($escaped, true, true)); + self::assertEquals($escaped, JsonFormatter::format($escaped, true, true)); } } diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index 1d27069bd678..9cce7845ccdc 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -12,6 +12,7 @@ namespace Composer\Test\Json; +use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Test\TestCase; @@ -23,8 +24,8 @@ class JsonManipulatorTest extends TestCase public function testAddLink(string $json, string $type, string $package, string $constraint, string $expected): void { $manipulator = new JsonManipulator($json); - $this->assertTrue($manipulator->addLink($type, $package, $constraint)); - $this->assertEquals($expected, $manipulator->getContents()); + self::assertTrue($manipulator->addLink($type, $package, $constraint)); + self::assertEquals($expected, $manipulator->getContents()); } public static function linkProvider(): array @@ -1293,8 +1294,8 @@ public static function linkProvider(): array public function testAddLinkAndSortPackages(string $json, string $type, string $package, string $constraint, bool $sortPackages, string $expected): void { $manipulator = new JsonManipulator($json); - $this->assertTrue($manipulator->addLink($type, $package, $constraint, $sortPackages)); - $this->assertEquals($expected, $manipulator->getContents()); + self::assertTrue($manipulator->addLink($type, $package, $constraint, $sortPackages)); + self::assertEquals($expected, $manipulator->getContents()); } public static function providerAddLinkAndSortPackages(): array @@ -1374,9 +1375,9 @@ public function testRemoveSubNode(string $json, string $name, bool $expected, ?s { $manipulator = new JsonManipulator($json); - $this->assertEquals($expected, $manipulator->removeSubNode('repositories', $name)); + self::assertEquals($expected, $manipulator->removeSubNode('repositories', $name)); if (null !== $expectedContent) { - $this->assertEquals($expectedContent, $manipulator->getContents()); + self::assertEquals($expectedContent, $manipulator->getContents()); } } @@ -1696,9 +1697,9 @@ public function testRemoveSubNodeFromRequire(): void } }'); - $this->assertTrue($manipulator->removeSubNode('require', 'package/c')); - $this->assertTrue($manipulator->removeSubNode('require-dev', 'package/d')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeSubNode('require', 'package/c')); + self::assertTrue($manipulator->removeSubNode('require-dev', 'package/d')); + self::assertEquals('{ "repositories": [ { "package": { @@ -1727,8 +1728,8 @@ public function testRemoveSubNodePreservesObjectTypeWhenEmpty(): void "test": {"0": "foo"} }'); - $this->assertTrue($manipulator->removeSubNode('test', '0')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeSubNode('test', '0')); + self::assertEquals('{ "test": { } } @@ -1743,8 +1744,8 @@ public function testRemoveSubNodePreservesObjectTypeWhenEmpty2(): void } }'); - $this->assertTrue($manipulator->removeConfigSetting('preferred-install.foo/*')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeConfigSetting('preferred-install.foo/*')); + self::assertEquals('{ "config": { "preferred-install": { } @@ -1777,9 +1778,9 @@ public function testAddSubNodeInRequire(): void } }'); - $this->assertTrue($manipulator->addSubNode('require', 'package/c', '*')); - $this->assertTrue($manipulator->addSubNode('require-dev', 'package/e', '*')); - $this->assertEquals('{ + self::assertTrue($manipulator->addSubNode('require', 'package/c', '*')); + self::assertTrue($manipulator->addSubNode('require-dev', 'package/e', '*')); + self::assertEquals('{ "repositories": [ { "package": { @@ -1825,8 +1826,8 @@ public function testAddExtraWithPackage(): void } }'); - $this->assertTrue($manipulator->addProperty('extra.foo-bar', true)); - $this->assertEquals('{ + self::assertTrue($manipulator->addProperty('extra.foo-bar', true)); + self::assertEquals('{ "repositories": [ { "type": "package", @@ -1867,8 +1868,8 @@ public function testAddConfigWithPackage(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('preferred-install.my-organization/stable-package', 'dist')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('preferred-install.my-organization/stable-package', 'dist')); + self::assertEquals('{ "repositories": [ { "type": "package", @@ -1911,8 +1912,8 @@ public function testAddSuggestWithPackage(): void } }'); - $this->assertTrue($manipulator->addProperty('suggest.new-package', 'new-description')); - $this->assertEquals('{ + self::assertTrue($manipulator->addProperty('suggest.new-package', 'new-description')); + self::assertEquals('{ "repositories": [ { "type": "package", @@ -1939,8 +1940,8 @@ public function testAddRepositoryCanInitializeEmptyRepositories(): void } }'); - $this->assertTrue($manipulator->addRepository('bar', ['type' => 'composer'])); - $this->assertEquals('{ + self::assertTrue($manipulator->addRepository('bar', ['type' => 'composer'])); + self::assertEquals('{ "repositories": { "bar": { "type": "composer" @@ -1956,8 +1957,8 @@ public function testAddRepositoryCanInitializeFromScratch(): void \t\"a\": \"b\" }"); - $this->assertTrue($manipulator->addRepository('bar2', ['type' => 'composer'])); - $this->assertEquals("{ + self::assertTrue($manipulator->addRepository('bar2', ['type' => 'composer'])); + self::assertEquals("{ \t\"a\": \"b\", \t\"repositories\": { \t\t\"bar2\": { @@ -1979,8 +1980,8 @@ public function testAddRepositoryCanAppend(): void } }'); - $this->assertTrue($manipulator->addRepository('bar', ['type' => 'composer'], true)); - $this->assertEquals('{ + self::assertTrue($manipulator->addRepository('bar', ['type' => 'composer'], true)); + self::assertEquals('{ "repositories": { "foo": { "type": "vcs", @@ -2005,8 +2006,8 @@ public function testAddRepositoryCanPrepend(): void } }'); - $this->assertTrue($manipulator->addRepository('bar', ['type' => 'composer'], false)); - $this->assertEquals('{ + self::assertTrue($manipulator->addRepository('bar', ['type' => 'composer'], false)); + self::assertEquals('{ "repositories": { "bar": { "type": "composer" @@ -2031,8 +2032,8 @@ public function testAddRepositoryCanOverrideDeepRepos(): void } }'); - $this->assertTrue($manipulator->addRepository('baz', ['type' => 'composer'])); - $this->assertEquals('{ + self::assertTrue($manipulator->addRepository('baz', ['type' => 'composer'])); + self::assertEquals('{ "repositories": { "baz": { "type": "composer" @@ -2049,9 +2050,9 @@ public function testAddConfigSettingEscapes(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('test', 'a\b')); - $this->assertTrue($manipulator->addConfigSetting('test2', "a\nb\fa")); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('test', 'a\b')); + self::assertTrue($manipulator->addConfigSetting('test2', "a\nb\fa")); + self::assertEquals('{ "config": { "test": "a\\\\b", "test2": "a\nb\fa" @@ -2065,8 +2066,8 @@ public function testAddConfigSettingWorksFromScratch(): void $manipulator = new JsonManipulator('{ }'); - $this->assertTrue($manipulator->addConfigSetting('foo.bar', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('foo.bar', 'baz')); + self::assertEquals('{ "config": { "foo": { "bar": "baz" @@ -2084,8 +2085,8 @@ public function testAddConfigSettingCanAdd(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('bar', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('bar', 'baz')); + self::assertEquals('{ "config": { "foo": "bar", "bar": "baz" @@ -2103,8 +2104,8 @@ public function testAddConfigSettingCanOverwrite(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('foo', 'zomg')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('foo', 'zomg')); + self::assertEquals('{ "config": { "foo": "zomg", "bar": "baz" @@ -2121,8 +2122,8 @@ public function testAddConfigSettingCanOverwriteNumbers(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('foo', 50)); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('foo', 50)); + self::assertEquals('{ "config": { "foo": 50 } @@ -2141,8 +2142,8 @@ public function testAddConfigSettingCanOverwriteArrays(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('github-protocols', ['https', 'http'])); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('github-protocols', ['https', 'http'])); + self::assertEquals('{ "config": { "github-oauth": { "github.com": "foo" @@ -2152,8 +2153,8 @@ public function testAddConfigSettingCanOverwriteArrays(): void } ', $manipulator->getContents()); - $this->assertTrue($manipulator->addConfigSetting('github-oauth', ['github.com' => 'bar', 'alt.example.org' => 'baz'])); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('github-oauth', ['github.com' => 'bar', 'alt.example.org' => 'baz'])); + self::assertEquals('{ "config": { "github-oauth": { "github.com": "bar", @@ -2172,8 +2173,8 @@ public function testAddConfigSettingCanAddSubKeyInEmptyConfig(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('github-oauth.bar', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('github-oauth.bar', 'baz')); + self::assertEquals('{ "config": { "github-oauth": { "bar": "baz" @@ -2193,10 +2194,10 @@ public function testAddConfigSettingCanAddSubKeyInEmptyVal(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('github-oauth.bar', 'baz')); - $this->assertTrue($manipulator->addConfigSetting('github-oauth2.a.bar', 'baz2')); - $this->assertTrue($manipulator->addConfigSetting('github-oauth3.b', 'c')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('github-oauth.bar', 'baz')); + self::assertTrue($manipulator->addConfigSetting('github-oauth2.a.bar', 'baz2')); + self::assertTrue($manipulator->addConfigSetting('github-oauth3.b', 'c')); + self::assertEquals('{ "config": { "github-oauth": { "bar": "baz" @@ -2222,8 +2223,8 @@ public function testAddConfigSettingCanAddSubKeyInHash(): void } }'); - $this->assertTrue($manipulator->addConfigSetting('github-oauth.bar', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addConfigSetting('github-oauth.bar', 'baz')); + self::assertEquals('{ "config": { "github-oauth": { "github.com": "foo", @@ -2242,8 +2243,8 @@ public function testAddRootSettingDoesNotBreakDots(): void } }'); - $this->assertTrue($manipulator->addSubNode('github-oauth', 'bar', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addSubNode('github-oauth', 'bar', 'baz')); + self::assertEquals('{ "github-oauth": { "github.com": "foo", "bar": "baz" @@ -2263,8 +2264,8 @@ public function testRemoveConfigSettingCanRemoveSubKeyInHash(): void } }'); - $this->assertTrue($manipulator->removeConfigSetting('github-oauth.bar')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeConfigSetting('github-oauth.bar')); + self::assertEquals('{ "config": { "github-oauth": { "github.com": "foo" @@ -2286,8 +2287,8 @@ public function testRemoveConfigSettingCanRemoveSubKeyInHashWithSiblings(): void } }'); - $this->assertTrue($manipulator->removeConfigSetting('github-oauth.bar')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeConfigSetting('github-oauth.bar')); + self::assertEquals('{ "config": { "foo": "bar", "github-oauth": { @@ -2304,8 +2305,8 @@ public function testAddMainKey(): void "foo": "bar" }'); - $this->assertTrue($manipulator->addMainKey('bar', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('bar', 'baz')); + self::assertEquals('{ "foo": "bar", "bar": "baz" } @@ -2318,8 +2319,8 @@ public function testAddMainKeyWithContentHavingDollarSignFollowedByDigit(): void "foo": "bar" }'); - $this->assertTrue($manipulator->addMainKey('bar', '$1baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('bar', '$1baz')); + self::assertEquals('{ "foo": "bar", "bar": "$1baz" } @@ -2330,8 +2331,8 @@ public function testAddMainKeyWithContentHavingDollarSignFollowedByDigit2(): voi { $manipulator = new JsonManipulator('{}'); - $this->assertTrue($manipulator->addMainKey('foo', '$1bar')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('foo', '$1bar')); + self::assertEquals('{ "foo": "$1bar" } ', $manipulator->getContents()); @@ -2343,8 +2344,8 @@ public function testUpdateMainKey(): void "foo": "bar" }'); - $this->assertTrue($manipulator->addMainKey('foo', 'baz')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('foo', 'baz')); + self::assertEquals('{ "foo": "baz" } ', $manipulator->getContents()); @@ -2361,9 +2362,9 @@ public function testUpdateMainKey2(): void "baz": "bar" }'); - $this->assertTrue($manipulator->addMainKey('foo', 'baz')); - $this->assertTrue($manipulator->addMainKey('baz', 'quux')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('foo', 'baz')); + self::assertTrue($manipulator->addMainKey('baz', 'quux')); + self::assertEquals('{ "a": { "foo": "bar", "baz": "qux" @@ -2385,8 +2386,8 @@ public function testUpdateMainKey3(): void } }'); - $this->assertTrue($manipulator->addMainKey('require-dev', ['foo' => 'qux'])); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('require-dev', ['foo' => 'qux'])); + self::assertEquals('{ "require": { "php": "5.*" }, @@ -2403,8 +2404,8 @@ public function testUpdateMainKeyWithContentHavingDollarSignFollowedByDigit(): v "foo": "bar" }'); - $this->assertTrue($manipulator->addMainKey('foo', '$1bar')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('foo', '$1bar')); + self::assertEquals('{ "foo": "$1bar" } ', $manipulator->getContents()); @@ -2436,8 +2437,8 @@ public function testRemoveMainKey(): void } }'); - $this->assertTrue($manipulator->removeMainKey('repositories')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeMainKey('repositories')); + self::assertEquals('{ "require": { "package/a": "*", "package/b": "*", @@ -2450,8 +2451,8 @@ public function testRemoveMainKey(): void } ', $manipulator->getContents()); - $this->assertTrue($manipulator->removeMainKey('foo')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeMainKey('foo')); + self::assertEquals('{ "require": { "package/a": "*", "package/b": "*", @@ -2463,9 +2464,9 @@ public function testRemoveMainKey(): void } ', $manipulator->getContents()); - $this->assertTrue($manipulator->removeMainKey('require')); - $this->assertTrue($manipulator->removeMainKey('require-dev')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeMainKey('require')); + self::assertTrue($manipulator->removeMainKey('require-dev')); + self::assertEquals('{ } ', $manipulator->getContents()); } @@ -2485,8 +2486,8 @@ public function testRemoveMainKeyIfEmpty(): void } }'); - $this->assertTrue($manipulator->removeMainKeyIfEmpty('repositories')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeMainKeyIfEmpty('repositories')); + self::assertEquals('{ "require": { "package/a": "*", "package/b": "*", @@ -2498,10 +2499,10 @@ public function testRemoveMainKeyIfEmpty(): void } ', $manipulator->getContents()); - $this->assertTrue($manipulator->removeMainKeyIfEmpty('foo')); - $this->assertTrue($manipulator->removeMainKeyIfEmpty('require')); - $this->assertTrue($manipulator->removeMainKeyIfEmpty('require-dev')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeMainKeyIfEmpty('foo')); + self::assertTrue($manipulator->removeMainKeyIfEmpty('require')); + self::assertTrue($manipulator->removeMainKeyIfEmpty('require-dev')); + self::assertEquals('{ "require": { "package/a": "*", "package/b": "*", @@ -2521,11 +2522,11 @@ public function testRemoveMainKeyRemovesKeyWhereValueIsNull(): void $manipulator->removeMainKey('bar'); - $expected = json_encode([ + $expected = JsonFile::encode([ 'foo' => 9000, ]); - $this->assertJsonStringEqualsJsonString($expected, $manipulator->getContents()); + self::assertJsonStringEqualsJsonString($expected, $manipulator->getContents()); } public function testIndentDetection(): void @@ -2537,8 +2538,8 @@ public function testIndentDetection(): void } }'); - $this->assertTrue($manipulator->addMainKey('require-dev', ['foo' => 'qux'])); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('require-dev', ['foo' => 'qux'])); + self::assertEquals('{ "require": { "php": "5.*" @@ -2558,9 +2559,9 @@ public function testRemoveMainKeyAtEndOfFile(): void } } '); - $this->assertTrue($manipulator->addMainKey('homepage', 'http...')); - $this->assertTrue($manipulator->addMainKey('license', 'mit')); - $this->assertEquals('{ + self::assertTrue($manipulator->addMainKey('homepage', 'http...')); + self::assertTrue($manipulator->addMainKey('license', 'mit')); + self::assertEquals('{ "require": { "package/a": "*" }, @@ -2569,9 +2570,9 @@ public function testRemoveMainKeyAtEndOfFile(): void } ', $manipulator->getContents()); - $this->assertTrue($manipulator->removeMainKey('homepage')); - $this->assertTrue($manipulator->removeMainKey('license')); - $this->assertEquals('{ + self::assertTrue($manipulator->removeMainKey('homepage')); + self::assertTrue($manipulator->removeMainKey('license')); + self::assertEquals('{ "require": { "package/a": "*" } @@ -2588,8 +2589,8 @@ public function testEscapedUnicodeDoesNotCauseBacktrackLimitErrorGithubIssue8131 } }'); - $this->assertTrue($manipulator->addLink('require', 'foo/baz', '^1.0')); - $this->assertEquals('{ + self::assertTrue($manipulator->addLink('require', 'foo/baz', '^1.0')); + self::assertEquals('{ "description": "Some U\u00F1icode", "require": { "foo/bar": "^1.0", @@ -3251,8 +3252,8 @@ public function testLargeFileDoesNotCauseBacktrackLimitErrorGithubIssue9595(): v "prefer-stable": true }'); - $this->assertTrue($manipulator->addSubNode('config', 'platform-check', false)); - $this->assertEquals('{ + self::assertTrue($manipulator->addSubNode('config', 'platform-check', false)); + self::assertEquals('{ "name": "leoloso/pop", "require": { "php": "^7.4|^8.0", diff --git a/tests/Composer/Test/Json/JsonValidationExceptionTest.php b/tests/Composer/Test/Json/JsonValidationExceptionTest.php index 69a446341fff..672f99364b49 100644 --- a/tests/Composer/Test/Json/JsonValidationExceptionTest.php +++ b/tests/Composer/Test/Json/JsonValidationExceptionTest.php @@ -25,14 +25,14 @@ class JsonValidationExceptionTest extends TestCase public function testGetErrors(string $message, array $errors, string $expectedMessage, array $expectedErrors): void { $object = new JsonValidationException($message, $errors); - $this->assertSame($expectedMessage, $object->getMessage()); - $this->assertSame($expectedErrors, $object->getErrors()); + self::assertSame($expectedMessage, $object->getMessage()); + self::assertSame($expectedErrors, $object->getErrors()); } public function testGetErrorsWhenNoErrorsProvided(): void { $object = new JsonValidationException('test message'); - $this->assertEquals([], $object->getErrors()); + self::assertEquals([], $object->getErrors()); } public static function errorProvider(): array diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index ad5cb98af9d5..377a820c292a 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -116,7 +116,7 @@ public function testManualExcludes(): void $this->finder = new ArchivableFilesFinder($this->sources, $excludes); - $this->assertArchivableFiles([ + self::assertArchivableFiles([ '/!important!.txt', '/!important_too!.txt', '/#weirdfile', @@ -191,7 +191,7 @@ public function testGitExcludes(): void $this->finder = new ArchivableFilesFinder($this->sources, []); - $this->assertArchivableFiles($this->getArchivedFiles( + self::assertArchivableFiles($this->getArchivedFiles( 'git init && '. 'git config user.email "you@example.com" && '. 'git config user.name "Your Name" && '. @@ -212,7 +212,7 @@ public function testSkipExcludes(): void $this->finder = new ArchivableFilesFinder($this->sources, $excludes, true); - $this->assertArchivableFiles([ + self::assertArchivableFiles([ '/!important!.txt', '/!important_too!.txt', '/#weirdfile', @@ -306,6 +306,6 @@ protected function assertArchivableFiles(array $expectedFiles): void { $actualFiles = $this->getArchivableFiles(); - $this->assertEquals($expectedFiles, $actualFiles); + self::assertEquals($expectedFiles, $actualFiles); } } diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php index ca07aede41a8..e75e49a70bd1 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -69,10 +69,10 @@ public function testArchiveTar(): void $this->manager->archive($package, 'tar', $this->targetDir); $target = $this->getTargetName($package, 'tar'); - $this->assertFileExists($target); + self::assertFileExists($target); $tmppath = sys_get_temp_dir().'/composer_archiver/'.$this->manager->getPackageFilename($package); - $this->assertFileDoesNotExist($tmppath); + self::assertFileDoesNotExist($tmppath); unlink($target); } @@ -91,10 +91,10 @@ public function testArchiveCustomFileName(): void $target = $this->targetDir . '/' . $fileName . '.tar'; - $this->assertFileExists($target); + self::assertFileExists($target); $tmppath = sys_get_temp_dir().'/composer_archiver/'.$this->manager->getPackageFilename($package); - $this->assertFileDoesNotExist($tmppath); + self::assertFileDoesNotExist($tmppath); unlink($target); } diff --git a/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php b/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php index 0f28bd35de4e..56134e23af07 100644 --- a/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php +++ b/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php @@ -26,7 +26,7 @@ public function testPatternEscape(string $ignore, array $expected): void { $filter = new GitExcludeFilter('/'); - $this->assertEquals($expected, $filter->parseGitAttributesLine($ignore)); + self::assertEquals($expected, $filter->parseGitAttributesLine($ignore)); } public static function providePatterns(): array diff --git a/tests/Composer/Test/Package/Archiver/PharArchiverTest.php b/tests/Composer/Test/Package/Archiver/PharArchiverTest.php index 1d6e68e2ec60..cfe203004082 100644 --- a/tests/Composer/Test/Package/Archiver/PharArchiverTest.php +++ b/tests/Composer/Test/Package/Archiver/PharArchiverTest.php @@ -27,7 +27,7 @@ public function testTarArchive(): void // Test archive $archiver = new PharArchiver(); $archiver->archive($package->getSourceUrl(), $target, 'tar', ['foo/bar', 'baz', '!/foo/bar/baz']); - $this->assertFileExists($target); + self::assertFileExists($target); $this->filesystem->removeDirectory(dirname($target)); } @@ -42,7 +42,7 @@ public function testZipArchive(): void // Test archive $archiver = new PharArchiver(); $archiver->archive($package->getSourceUrl(), $target, 'zip'); - $this->assertFileExists($target); + self::assertFileExists($target); $this->filesystem->removeDirectory(dirname($target)); } diff --git a/tests/Composer/Test/Package/BasePackageTest.php b/tests/Composer/Test/Package/BasePackageTest.php index 324bda08f782..ae40844170d1 100644 --- a/tests/Composer/Test/Package/BasePackageTest.php +++ b/tests/Composer/Test/Package/BasePackageTest.php @@ -54,7 +54,7 @@ public function testFormatVersionForDevPackage(string $sourceReference, bool $tr $package->expects($this->once())->method('getPrettyVersion')->will($this->returnValue('PrettyVersion')); $package->expects($this->any())->method('getSourceReference')->will($this->returnValue($sourceReference)); - $this->assertSame($expected, $package->getFullPrettyVersion($truncate)); + self::assertSame($expected, $package->getFullPrettyVersion($truncate)); } public static function provideFormattedVersions(): array @@ -93,7 +93,7 @@ public function testPackageNamesToRegexp(array $packageNames, $wrap, string $exp { $regexp = BasePackage::packageNamesToRegexp($packageNames, $wrap); - $this->assertSame($expectedRegexp, $regexp); + self::assertSame($expectedRegexp, $regexp); } /** diff --git a/tests/Composer/Test/Package/CompletePackageTest.php b/tests/Composer/Test/Package/CompletePackageTest.php index 17dce9fa13d3..6f7fe5f2b28f 100644 --- a/tests/Composer/Test/Package/CompletePackageTest.php +++ b/tests/Composer/Test/Package/CompletePackageTest.php @@ -42,7 +42,7 @@ public function testPackageHasExpectedNamingSemantics(string $name, string $vers $versionParser = new VersionParser(); $normVersion = $versionParser->normalize($version); $package = new Package($name, $normVersion, $version); - $this->assertEquals(strtolower($name), $package->getName()); + self::assertEquals(strtolower($name), $package->getName()); } /** @@ -53,8 +53,8 @@ public function testPackageHasExpectedVersioningSemantics(string $name, string $ $versionParser = new VersionParser(); $normVersion = $versionParser->normalize($version); $package = new Package($name, $normVersion, $version); - $this->assertEquals($version, $package->getPrettyVersion()); - $this->assertEquals($normVersion, $package->getVersion()); + self::assertEquals($version, $package->getPrettyVersion()); + self::assertEquals($normVersion, $package->getVersion()); } /** @@ -65,34 +65,34 @@ public function testPackageHasExpectedMarshallingSemantics(string $name, string $versionParser = new VersionParser(); $normVersion = $versionParser->normalize($version); $package = new Package($name, $normVersion, $version); - $this->assertEquals(strtolower($name).'-'.$normVersion, (string) $package); + self::assertEquals(strtolower($name).'-'.$normVersion, (string) $package); } public function testGetTargetDir(): void { $package = new Package('a', '1.0.0.0', '1.0'); - $this->assertNull($package->getTargetDir()); + self::assertNull($package->getTargetDir()); $package->setTargetDir('./../foo/'); - $this->assertEquals('foo/', $package->getTargetDir()); + self::assertEquals('foo/', $package->getTargetDir()); $package->setTargetDir('foo/../../../bar/'); - $this->assertEquals('foo/bar/', $package->getTargetDir()); + self::assertEquals('foo/bar/', $package->getTargetDir()); $package->setTargetDir('../..'); - $this->assertEquals('', $package->getTargetDir()); + self::assertEquals('', $package->getTargetDir()); $package->setTargetDir('..'); - $this->assertEquals('', $package->getTargetDir()); + self::assertEquals('', $package->getTargetDir()); $package->setTargetDir('/..'); - $this->assertEquals('', $package->getTargetDir()); + self::assertEquals('', $package->getTargetDir()); $package->setTargetDir('/foo/..'); - $this->assertEquals('foo/', $package->getTargetDir()); + self::assertEquals('foo/', $package->getTargetDir()); $package->setTargetDir('/foo/..//bar'); - $this->assertEquals('foo/bar', $package->getTargetDir()); + self::assertEquals('foo/bar', $package->getTargetDir()); } } diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index ca885243afef..95a638820133 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -32,7 +32,7 @@ public function setUp(): void public function testRequiredInformation(): void { $config = $this->dumper->dump(self::getPackage()); - $this->assertEquals( + self::assertEquals( [ 'name' => 'dummy/pkg', 'version' => '1.0.0', @@ -49,7 +49,7 @@ public function testRootPackage(): void $package->setMinimumStability('dev'); $config = $this->dumper->dump($package); - $this->assertSame('dev', $config['minimum-stability']); + self::assertSame('dev', $config['minimum-stability']); } public function testDumpAbandoned(): void @@ -58,7 +58,7 @@ public function testDumpAbandoned(): void $package->setAbandoned(true); $config = $this->dumper->dump($package); - $this->assertTrue($config['abandoned']); + self::assertTrue($config['abandoned']); } public function testDumpAbandonedReplacement(): void @@ -67,7 +67,7 @@ public function testDumpAbandonedReplacement(): void $package->setAbandoned('foo/bar'); $config = $this->dumper->dump($package); - $this->assertSame('foo/bar', $config['abandoned']); + self::assertSame('foo/bar', $config['abandoned']); } /** @@ -81,12 +81,12 @@ public function testKeys(string $key, $value, ?string $method = null, $expectedV { $package = self::getRootPackage(); - // @phpstan-ignore method.dynamicName, ternary.shortNotAllowed - $package->{'set'.ucfirst($method ?: $key)}($value); + // @phpstan-ignore method.dynamicName + $package->{'set'.ucfirst($method ?? $key)}($value); $config = $this->dumper->dump($package); - $this->assertSame($expectedValue ?: $value, $config[$key]); + self::assertSame($expectedValue ?: $value, $config[$key]); } public static function provideKeys(): array diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index 31f4fdf49b44..09602fca08a5 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -42,7 +42,7 @@ public function testSelfVersion(): void $package = $this->loader->load($config); $replaces = $package->getReplaces(); - $this->assertEquals('== 1.2.3.4', (string) $replaces['foo']->getConstraint()); + self::assertEquals('== 1.2.3.4', (string) $replaces['foo']->getConstraint()); } public function testTypeDefault(): void @@ -53,7 +53,7 @@ public function testTypeDefault(): void ]; $package = $this->loader->load($config); - $this->assertEquals('library', $package->getType()); + self::assertEquals('library', $package->getType()); $config = [ 'name' => 'A', @@ -62,7 +62,7 @@ public function testTypeDefault(): void ]; $package = $this->loader->load($config); - $this->assertEquals('foo', $package->getType()); + self::assertEquals('foo', $package->getType()); } public function testNormalizedVersionOptimization(): void @@ -73,7 +73,7 @@ public function testNormalizedVersionOptimization(): void ]; $package = $this->loader->load($config); - $this->assertEquals('1.2.3.0', $package->getVersion()); + self::assertEquals('1.2.3.0', $package->getVersion()); $config = [ 'name' => 'A', @@ -82,7 +82,7 @@ public function testNormalizedVersionOptimization(): void ]; $package = $this->loader->load($config); - $this->assertEquals('1.2.3.4', $package->getVersion()); + self::assertEquals('1.2.3.4', $package->getVersion()); } public static function parseDumpProvider(): array @@ -161,7 +161,7 @@ public function testParseDumpDefaultLoadConfig(array $config): void $package = $this->loader->load($config); $dumper = new ArrayDumper; $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config); - $this->assertEquals($expectedConfig, $dumper->dump($package)); + self::assertEquals($expectedConfig, $dumper->dump($package)); } /** @@ -175,7 +175,7 @@ public function testParseDumpTrueLoadConfig(array $config): void $package = $loader->load($config); $dumper = new ArrayDumper; $expectedConfig = $config; - $this->assertEquals($expectedConfig, $dumper->dump($package)); + self::assertEquals($expectedConfig, $dumper->dump($package)); } /** @@ -189,7 +189,7 @@ public function testParseDumpFalseLoadConfig(array $config): void $package = $loader->load($config); $dumper = new ArrayDumper; $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config); - $this->assertEquals($expectedConfig, $dumper->dump($package)); + self::assertEquals($expectedConfig, $dumper->dump($package)); } public function testPackageWithBranchAlias(): void @@ -202,8 +202,8 @@ public function testPackageWithBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\AliasPackage', $package); - $this->assertEquals('1.0.x-dev', $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\AliasPackage', $package); + self::assertEquals('1.0.x-dev', $package->getPrettyVersion()); $config = [ 'name' => 'A', @@ -213,8 +213,8 @@ public function testPackageWithBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\AliasPackage', $package); - $this->assertEquals('1.0.x-dev', $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\AliasPackage', $package); + self::assertEquals('1.0.x-dev', $package->getPrettyVersion()); $config = [ 'name' => 'B', @@ -224,8 +224,8 @@ public function testPackageWithBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\AliasPackage', $package); - $this->assertEquals('4.0.x-dev', $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\AliasPackage', $package); + self::assertEquals('4.0.x-dev', $package->getPrettyVersion()); $config = [ 'name' => 'B', @@ -235,8 +235,8 @@ public function testPackageWithBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\AliasPackage', $package); - $this->assertEquals('4.0.x-dev', $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\AliasPackage', $package); + self::assertEquals('4.0.x-dev', $package->getPrettyVersion()); $config = [ 'name' => 'C', @@ -246,8 +246,8 @@ public function testPackageWithBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\CompletePackage', $package); - $this->assertEquals('4.x-dev', $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\CompletePackage', $package); + self::assertEquals('4.x-dev', $package->getPrettyVersion()); } public function testPackageAliasingWithoutBranchAlias(): void @@ -261,8 +261,8 @@ public function testPackageAliasingWithoutBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\AliasPackage', $package); - $this->assertEquals(VersionParser::DEFAULT_BRANCH_ALIAS, $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\AliasPackage', $package); + self::assertEquals(VersionParser::DEFAULT_BRANCH_ALIAS, $package->getPrettyVersion()); // non-default branch gets no alias even if non-numeric $config = [ @@ -273,8 +273,8 @@ public function testPackageAliasingWithoutBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\CompletePackage', $package); - $this->assertEquals('dev-main', $package->getPrettyVersion()); + self::assertInstanceOf('Composer\Package\CompletePackage', $package); + self::assertEquals('dev-main', $package->getPrettyVersion()); // default branch gets no alias if already numeric $config = [ @@ -285,8 +285,8 @@ public function testPackageAliasingWithoutBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\CompletePackage', $package); - $this->assertEquals('2.9999999.9999999.9999999-dev', $package->getVersion()); + self::assertInstanceOf('Composer\Package\CompletePackage', $package); + self::assertEquals('2.9999999.9999999.9999999-dev', $package->getVersion()); // default branch gets no alias if already numeric, with v prefix $config = [ @@ -297,8 +297,8 @@ public function testPackageAliasingWithoutBranchAlias(): void $package = $this->loader->load($config); - $this->assertInstanceOf('Composer\Package\CompletePackage', $package); - $this->assertEquals('2.9999999.9999999.9999999-dev', $package->getVersion()); + self::assertInstanceOf('Composer\Package\CompletePackage', $package); + self::assertEquals('2.9999999.9999999.9999999-dev', $package->getVersion()); } public function testAbandoned(): void @@ -310,8 +310,8 @@ public function testAbandoned(): void ]; $package = $this->loader->load($config); - $this->assertTrue($package->isAbandoned()); - $this->assertEquals('foo/bar', $package->getReplacementPackage()); + self::assertTrue($package->isAbandoned()); + self::assertEquals('foo/bar', $package->getReplacementPackage()); } public function testNotAbandoned(): void @@ -322,7 +322,7 @@ public function testNotAbandoned(): void ]; $package = $this->loader->load($config); - $this->assertFalse($package->isAbandoned()); + self::assertFalse($package->isAbandoned()); } public static function providePluginApiVersions(): array @@ -353,23 +353,23 @@ public function testPluginApiVersionAreKeptAsDeclared(string $apiVersion): void { $links = $this->loader->parseLinks('Plugin', '9.9.9', Link::TYPE_REQUIRE, ['composer-plugin-api' => $apiVersion]); - $this->assertArrayHasKey('composer-plugin-api', $links); - $this->assertSame($apiVersion, $links['composer-plugin-api']->getConstraint()->getPrettyString()); + self::assertArrayHasKey('composer-plugin-api', $links); + self::assertSame($apiVersion, $links['composer-plugin-api']->getConstraint()->getPrettyString()); } public function testPluginApiVersionDoesSupportSelfVersion(): void { $links = $this->loader->parseLinks('Plugin', '6.6.6', Link::TYPE_REQUIRE, ['composer-plugin-api' => 'self.version']); - $this->assertArrayHasKey('composer-plugin-api', $links); - $this->assertSame('6.6.6', $links['composer-plugin-api']->getConstraint()->getPrettyString()); + self::assertArrayHasKey('composer-plugin-api', $links); + self::assertSame('6.6.6', $links['composer-plugin-api']->getConstraint()->getPrettyString()); } public function testParseLinksIntegerTarget(): void { $links = $this->loader->parseLinks('Plugin', '9.9.9', Link::TYPE_REQUIRE, ['1' => 'dev-main']); - $this->assertArrayHasKey('1', $links); + self::assertArrayHasKey('1', $links); } public function testNoneStringVersion(): void @@ -380,7 +380,7 @@ public function testNoneStringVersion(): void ]; $package = $this->loader->load($config); - $this->assertSame('1', $package->getPrettyVersion()); + self::assertSame('1', $package->getPrettyVersion()); } public function testNoneStringSourceDistReference(): void @@ -401,8 +401,8 @@ public function testNoneStringSourceDistReference(): void ]; $package = $this->loader->load($config); - $this->assertSame('2019', $package->getSourceReference()); - $this->assertSame('2019', $package->getDistReference()); + self::assertSame('2019', $package->getSourceReference()); + self::assertSame('2019', $package->getDistReference()); } public function testBranchAliasIntegerIndex(): void @@ -421,7 +421,7 @@ public function testBranchAliasIntegerIndex(): void ], ]; - $this->assertNull($this->loader->getBranchAlias($config)); + self::assertNull($this->loader->getBranchAlias($config)); } public function testPackageLinksRequire(): void @@ -435,8 +435,8 @@ public function testPackageLinksRequire(): void ); $package = $this->loader->load($config); - $this->assertArrayHasKey('foo/bar', $package->getRequires()); - $this->assertSame('1.0', $package->getRequires()['foo/bar']->getConstraint()->getPrettyString()); + self::assertArrayHasKey('foo/bar', $package->getRequires()); + self::assertSame('1.0', $package->getRequires()['foo/bar']->getConstraint()->getPrettyString()); } public function testPackageLinksRequireInvalid(): void @@ -452,7 +452,7 @@ public function testPackageLinksRequireInvalid(): void ); $package = $this->loader->load($config); - $this->assertCount(0, $package->getRequires()); + self::assertCount(0, $package->getRequires()); } public function testPackageLinksReplace(): void @@ -466,8 +466,8 @@ public function testPackageLinksReplace(): void ); $package = $this->loader->load($config); - $this->assertArrayHasKey('coyote/package', $package->getReplaces()); - $this->assertSame('dev-1', $package->getReplaces()['coyote/package']->getConstraint()->getPrettyString()); + self::assertArrayHasKey('coyote/package', $package->getReplaces()); + self::assertSame('dev-1', $package->getReplaces()['coyote/package']->getConstraint()->getPrettyString()); } public function testPackageLinksReplaceInvalid(): void @@ -479,7 +479,7 @@ public function testPackageLinksReplaceInvalid(): void ); $package = $this->loader->load($config); - $this->assertCount(0, $package->getReplaces()); + self::assertCount(0, $package->getReplaces()); } public function testSupportStringValue(): void @@ -491,6 +491,6 @@ public function testSupportStringValue(): void ); $package = $this->loader->load($config); - $this->assertSame([], $package->getSupport()); + self::assertSame([], $package->getSupport()); } } diff --git a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php index 28684e56ea8e..a66377f9e63d 100644 --- a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php @@ -62,8 +62,8 @@ public function testStabilityFlagsParsing(): void 'minimum-stability' => 'alpha', ]); - $this->assertEquals('alpha', $package->getMinimumStability()); - $this->assertEquals([ + self::assertEquals('alpha', $package->getMinimumStability()); + self::assertEquals([ 'bar/baz' => BasePackage::STABILITY_DEV, 'qux/quux' => BasePackage::STABILITY_RC, 'zux/complex' => BasePackage::STABILITY_DEV, @@ -88,8 +88,8 @@ public function testNoVersionIsVisibleInPrettyVersion(): void $package = $loader->load([]); - $this->assertEquals("1.0.0.0", $package->getVersion()); - $this->assertEquals(RootPackage::DEFAULT_PRETTY_VERSION, $package->getPrettyVersion()); + self::assertEquals("1.0.0.0", $package->getVersion()); + self::assertEquals(RootPackage::DEFAULT_PRETTY_VERSION, $package->getPrettyVersion()); } public function testPrettyVersionForRootPackageInVersionBranch(): void @@ -110,7 +110,7 @@ public function testPrettyVersionForRootPackageInVersionBranch(): void $loader = new RootPackageLoader($manager, $config, null, $versionGuesser); $package = $loader->load([]); - $this->assertEquals('3.0-dev', $package->getPrettyVersion()); + self::assertEquals('3.0-dev', $package->getPrettyVersion()); } public function testFeatureBranchPrettyVersion(): void @@ -138,7 +138,7 @@ public function testFeatureBranchPrettyVersion(): void $loader = new RootPackageLoader($manager, $config, null, new VersionGuesser($config, $process, new VersionParser())); $package = $loader->load(['require' => ['foo/bar' => 'self.version']]); - $this->assertEquals("dev-master", $package->getPrettyVersion()); + self::assertEquals("dev-master", $package->getPrettyVersion()); } public function testNonFeatureBranchPrettyVersion(): void @@ -165,6 +165,6 @@ public function testNonFeatureBranchPrettyVersion(): void $loader = new RootPackageLoader($manager, $config, null, new VersionGuesser($config, $process, new VersionParser())); $package = $loader->load(['require' => ['foo/bar' => 'self.version'], "non-feature-branches" => ["latest-.*"]]); - $this->assertEquals("dev-latest-production", $package->getPrettyVersion()); + self::assertEquals("dev-latest-production", $package->getPrettyVersion()); } } diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 40f05894e037..9b70911eee41 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -224,7 +224,7 @@ public function testLoadFailureThrowsException(array $config, array $expectedErr $errors = $e->getErrors(); sort($expectedErrors); sort($errors); - $this->assertEquals($expectedErrors, $errors); + self::assertEquals($expectedErrors, $errors); } } @@ -243,7 +243,7 @@ public function testLoadWarnings(array $config, array $expectedWarnings): void $warnings = $loader->getWarnings(); sort($expectedWarnings); sort($warnings); - $this->assertEquals($expectedWarnings, $warnings); + self::assertEquals($expectedWarnings, $warnings); } /** @@ -255,7 +255,7 @@ public function testLoadWarnings(array $config, array $expectedWarnings): void public function testLoadSkipsWarningDataWhenIgnoringErrors(array $config, array $expectedWarnings, bool $mustCheck = true): void { if (!$mustCheck) { - $this->assertTrue(true); + self::assertTrue(true); // @phpstan-ignore staticMethod.alreadyNarrowedType return; } diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index 7d3233b45ac2..fc0178cf227c 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -39,7 +39,7 @@ public function testIsLocked(): void ->method('read') ->will($this->returnValue(['packages' => []])); - $this->assertTrue($locker->isLocked()); + self::assertTrue($locker->isLocked()); } public function testGetNotLockedPackages(): void @@ -81,8 +81,8 @@ public function testGetLockedPackages(): void ])); $repo = $locker->getLockedRepository(); - $this->assertNotNull($repo->findPackage('pkg1', '1.0.0-beta')); - $this->assertNotNull($repo->findPackage('pkg2', '0.1.10')); + self::assertNotNull($repo->findPackage('pkg1', '1.0.0-beta')); + self::assertNotNull($repo->findPackage('pkg2', '0.1.10')); } public function testSetLockData(): void @@ -156,7 +156,7 @@ public function testIsFresh(): void ->method('read') ->will($this->returnValue(['hash' => md5($jsonContent)])); - $this->assertTrue($locker->isFresh()); + self::assertTrue($locker->isFresh()); } public function testIsFreshFalse(): void @@ -171,7 +171,7 @@ public function testIsFreshFalse(): void ->method('read') ->will($this->returnValue(['hash' => $this->getJsonContent(['name' => 'test2'])])); - $this->assertFalse($locker->isFresh()); + self::assertFalse($locker->isFresh()); } public function testIsFreshWithContentHash(): void @@ -187,7 +187,7 @@ public function testIsFreshWithContentHash(): void ->method('read') ->will($this->returnValue(['hash' => md5($jsonContent . ' '), 'content-hash' => md5($jsonContent)])); - $this->assertTrue($locker->isFresh()); + self::assertTrue($locker->isFresh()); } public function testIsFreshWithContentHashAndNoHash(): void @@ -203,7 +203,7 @@ public function testIsFreshWithContentHashAndNoHash(): void ->method('read') ->will($this->returnValue(['content-hash' => md5($jsonContent)])); - $this->assertTrue($locker->isFresh()); + self::assertTrue($locker->isFresh()); } public function testIsFreshFalseWithContentHash(): void @@ -220,7 +220,7 @@ public function testIsFreshFalseWithContentHash(): void ->method('read') ->will($this->returnValue(['hash' => $differentHash, 'content-hash' => $differentHash])); - $this->assertFalse($locker->isFresh()); + self::assertFalse($locker->isFresh()); } /** diff --git a/tests/Composer/Test/Package/RootAliasPackageTest.php b/tests/Composer/Test/Package/RootAliasPackageTest.php index 8f71a3bd0998..b475719fd231 100644 --- a/tests/Composer/Test/Package/RootAliasPackageTest.php +++ b/tests/Composer/Test/Package/RootAliasPackageTest.php @@ -31,9 +31,9 @@ public function testUpdateRequires(): void ->with($this->equalTo($links)); $alias = new RootAliasPackage($root, '1.0', '1.0.0.0'); - $this->assertEmpty($alias->getRequires()); + self::assertEmpty($alias->getRequires()); $alias->setRequires($links); - $this->assertNotEmpty($alias->getRequires()); + self::assertNotEmpty($alias->getRequires()); } public function testUpdateDevRequires(): void @@ -46,9 +46,9 @@ public function testUpdateDevRequires(): void ->with($this->equalTo($links)); $alias = new RootAliasPackage($root, '1.0', '1.0.0.0'); - $this->assertEmpty($alias->getDevRequires()); + self::assertEmpty($alias->getDevRequires()); $alias->setDevRequires($links); - $this->assertNotEmpty($alias->getDevRequires()); + self::assertNotEmpty($alias->getDevRequires()); } public function testUpdateConflicts(): void @@ -61,9 +61,9 @@ public function testUpdateConflicts(): void ->with($this->equalTo($links)); $alias = new RootAliasPackage($root, '1.0', '1.0.0.0'); - $this->assertEmpty($alias->getConflicts()); + self::assertEmpty($alias->getConflicts()); $alias->setConflicts($links); - $this->assertNotEmpty($alias->getConflicts()); + self::assertNotEmpty($alias->getConflicts()); } public function testUpdateProvides(): void @@ -76,9 +76,9 @@ public function testUpdateProvides(): void ->with($this->equalTo($links)); $alias = new RootAliasPackage($root, '1.0', '1.0.0.0'); - $this->assertEmpty($alias->getProvides()); + self::assertEmpty($alias->getProvides()); $alias->setProvides($links); - $this->assertNotEmpty($alias->getProvides()); + self::assertNotEmpty($alias->getProvides()); } public function testUpdateReplaces(): void @@ -91,9 +91,9 @@ public function testUpdateReplaces(): void ->with($this->equalTo($links)); $alias = new RootAliasPackage($root, '1.0', '1.0.0.0'); - $this->assertEmpty($alias->getReplaces()); + self::assertEmpty($alias->getReplaces()); $alias->setReplaces($links); - $this->assertNotEmpty($alias->getReplaces()); + self::assertNotEmpty($alias->getReplaces()); } /** diff --git a/tests/Composer/Test/Package/Version/VersionBumperTest.php b/tests/Composer/Test/Package/Version/VersionBumperTest.php index a146c2884a37..ba58743788f1 100644 --- a/tests/Composer/Test/Package/Version/VersionBumperTest.php +++ b/tests/Composer/Test/Package/Version/VersionBumperTest.php @@ -37,7 +37,7 @@ public function testBumpRequirement(string $requirement, string $prettyVersion, $newConstraint = $versionBumper->bumpRequirement($versionParser->parseConstraints($requirement), $package); // assert that the recommended version is what we expect - $this->assertSame($expectedRequirement, $newConstraint); + self::assertSame($expectedRequirement, $newConstraint); } public static function provideBumpRequirementTests(): Generator diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php index 2e59afe6ae31..676ef24202c0 100644 --- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -50,9 +50,9 @@ public function testHgGuessVersionReturnsData(): void $versionArray = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionArray); - $this->assertEquals("dev-".$branch, $versionArray['version']); - $this->assertEquals("dev-".$branch, $versionArray['pretty_version']); - $this->assertEmpty($versionArray['commit']); + self::assertEquals("dev-".$branch, $versionArray['version']); + self::assertEquals("dev-".$branch, $versionArray['pretty_version']); + self::assertEmpty($versionArray['commit']); } public function testGuessVersionReturnsData(): void @@ -74,11 +74,11 @@ public function testGuessVersionReturnsData(): void $versionArray = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionArray); - $this->assertEquals("dev-master", $versionArray['version']); - $this->assertEquals("dev-master", $versionArray['pretty_version']); - $this->assertArrayNotHasKey('feature_version', $versionArray); - $this->assertArrayNotHasKey('feature_pretty_version', $versionArray); - $this->assertEquals($commitHash, $versionArray['commit']); + self::assertEquals("dev-master", $versionArray['version']); + self::assertEquals("dev-master", $versionArray['pretty_version']); + self::assertArrayNotHasKey('feature_version', $versionArray); + self::assertArrayNotHasKey('feature_pretty_version', $versionArray); + self::assertEquals($commitHash, $versionArray['commit']); } public function testGuessVersionDoesNotSeeCustomDefaultBranchAsNonFeatureBranch(): void @@ -101,8 +101,8 @@ public function testGuessVersionDoesNotSeeCustomDefaultBranchAsNonFeatureBranch( $versionArray = $guesser->guessVersion(['version' => 'self.version'], 'dummy/path'); self::assertIsArray($versionArray); - $this->assertEquals("dev-current", $versionArray['version']); - $this->assertEquals($anotherCommitHash, $versionArray['commit']); + self::assertEquals("dev-current", $versionArray['version']); + self::assertEquals($anotherCommitHash, $versionArray['commit']); } public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationForArbitraryNaming(): void @@ -128,11 +128,11 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF $versionArray = $guesser->guessVersion(['version' => 'self.version', 'non-feature-branches' => ['arbitrary']], 'dummy/path'); self::assertIsArray($versionArray); - $this->assertEquals("dev-arbitrary", $versionArray['version']); - $this->assertEquals($anotherCommitHash, $versionArray['commit']); + self::assertEquals("dev-arbitrary", $versionArray['version']); + self::assertEquals($anotherCommitHash, $versionArray['commit']); self::assertArrayHasKey('feature_version', $versionArray); - $this->assertEquals("dev-feature", $versionArray['feature_version']); - $this->assertEquals("dev-feature", $versionArray['feature_pretty_version']); + self::assertEquals("dev-feature", $versionArray['feature_version']); + self::assertEquals("dev-feature", $versionArray['feature_pretty_version']); } public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationForArbitraryNamingRegex(): void @@ -158,11 +158,11 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF $versionArray = $guesser->guessVersion(['version' => 'self.version', 'non-feature-branches' => ['latest-.*']], 'dummy/path'); self::assertIsArray($versionArray); - $this->assertEquals("dev-latest-testing", $versionArray['version']); - $this->assertEquals($anotherCommitHash, $versionArray['commit']); + self::assertEquals("dev-latest-testing", $versionArray['version']); + self::assertEquals($anotherCommitHash, $versionArray['commit']); self::assertArrayHasKey('feature_version', $versionArray); - $this->assertEquals("dev-feature", $versionArray['feature_version']); - $this->assertEquals("dev-feature", $versionArray['feature_pretty_version']); + self::assertEquals("dev-feature", $versionArray['feature_version']); + self::assertEquals("dev-feature", $versionArray['feature_pretty_version']); } public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationForArbitraryNamingWhenOnNonFeatureBranch(): void @@ -184,10 +184,10 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF $versionArray = $guesser->guessVersion(['version' => 'self.version', 'non-feature-branches' => ['latest-.*']], 'dummy/path'); self::assertIsArray($versionArray); - $this->assertEquals("dev-latest-testing", $versionArray['version']); - $this->assertEquals($commitHash, $versionArray['commit']); - $this->assertArrayNotHasKey('feature_version', $versionArray); - $this->assertArrayNotHasKey('feature_pretty_version', $versionArray); + self::assertEquals("dev-latest-testing", $versionArray['version']); + self::assertEquals($commitHash, $versionArray['commit']); + self::assertArrayNotHasKey('feature_version', $versionArray); + self::assertArrayNotHasKey('feature_pretty_version', $versionArray); } public function testDetachedHeadBecomesDevHash(): void @@ -209,7 +209,7 @@ public function testDetachedHeadBecomesDevHash(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("dev-$commitHash", $versionData['version']); + self::assertEquals("dev-$commitHash", $versionData['version']); } public function testDetachedFetchHeadBecomesDevHashGit2(): void @@ -231,7 +231,7 @@ public function testDetachedFetchHeadBecomesDevHashGit2(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("dev-$commitHash", $versionData['version']); + self::assertEquals("dev-$commitHash", $versionData['version']); } public function testDetachedCommitHeadBecomesDevHashGit2(): void @@ -253,7 +253,7 @@ public function testDetachedCommitHeadBecomesDevHashGit2(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("dev-$commitHash", $versionData['version']); + self::assertEquals("dev-$commitHash", $versionData['version']); } public function testTagBecomesVersion(): void @@ -276,7 +276,7 @@ public function testTagBecomesVersion(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("2.0.5.0-alpha2", $versionData['version']); + self::assertEquals("2.0.5.0-alpha2", $versionData['version']); } public function testTagBecomesPrettyVersion(): void @@ -299,8 +299,8 @@ public function testTagBecomesPrettyVersion(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals('1.0.0.0', $versionData['version']); - $this->assertEquals('1.0.0', $versionData['pretty_version']); + self::assertEquals('1.0.0.0', $versionData['version']); + self::assertEquals('1.0.0', $versionData['pretty_version']); } public function testInvalidTagBecomesVersion(): void @@ -319,7 +319,7 @@ public function testInvalidTagBecomesVersion(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("dev-foo", $versionData['version']); + self::assertEquals("dev-foo", $versionData['version']); } public function testNumericBranchesShowNicely(): void @@ -338,8 +338,8 @@ public function testNumericBranchesShowNicely(): void $versionData = $guesser->guessVersion([], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("1.5.x-dev", $versionData['pretty_version']); - $this->assertEquals("1.5.9999999.9999999-dev", $versionData['version']); + self::assertEquals("1.5.x-dev", $versionData['pretty_version']); + self::assertEquals("1.5.9999999.9999999-dev", $versionData['version']); } public function testRemoteBranchesAreSelected(): void @@ -362,7 +362,7 @@ public function testRemoteBranchesAreSelected(): void $guesser = new VersionGuesser($config, $process, new VersionParser()); $versionData = $guesser->guessVersion(['version' => 'self.version'], 'dummy/path'); self::assertIsArray($versionData); - $this->assertEquals("1.5.x-dev", $versionData['pretty_version']); - $this->assertEquals("1.5.9999999.9999999-dev", $versionData['version']); + self::assertEquals("1.5.x-dev", $versionData['pretty_version']); + self::assertEquals("1.5.9999999.9999999-dev", $versionData['version']); } } diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index c0db2142af79..845b52dfeeb4 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -27,7 +27,7 @@ public function testParseNameVersionPairs(array $pairs, array $result): void { $versionParser = new VersionParser(); - $this->assertSame($result, $versionParser->parseNameVersionPairs($pairs)); + self::assertSame($result, $versionParser->parseNameVersionPairs($pairs)); } public static function provideParseNameVersionPairsData(): array @@ -46,7 +46,7 @@ public static function provideParseNameVersionPairsData(): array */ public function testIsUpgrade(string $from, string $to, bool $expected): void { - $this->assertSame($expected, VersionParser::isUpgrade($from, $to)); + self::assertSame($expected, VersionParser::isUpgrade($from, $to)); } public static function provideIsUpgradeTests(): array diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index aec5cf86caa7..e4d508cd8f1b 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -48,7 +48,7 @@ public function testLatestVersionIsReturned(): void $best = $versionSelector->findBestCandidate($packageName); // 1.2.2 should be returned because it's the latest of the returned versions - $this->assertSame($package2, $best, 'Latest version should be 1.2.2'); + self::assertSame($package2, $best, 'Latest version should be 1.2.2'); } public function testLatestVersionIsReturnedThatMatchesPhpRequirements(): void @@ -77,12 +77,12 @@ public function testLatestVersionIsReturnedThatMatchesPhpRequirements(): void $io = new BufferIO(); $best = $versionSelector->findBestCandidate($packageName, null, 'stable', null, 0, $io); - $this->assertSame((string) $package1, (string) $best, 'Latest version supporting php 5.5 should be returned (1.0.0)'); + self::assertSame((string) $package1, (string) $best, 'Latest version supporting php 5.5 should be returned (1.0.0)'); self::assertSame("Cannot use foo/bar's latest version 2.1.0 as it requires php >=5.6 which is not satisfied by your platform.".PHP_EOL, $io->getOutput()); $io = new BufferIO('', StreamOutput::VERBOSITY_VERBOSE); $best = $versionSelector->findBestCandidate($packageName, null, 'stable', null, 0, $io); - $this->assertSame((string) $package1, (string) $best, 'Latest version supporting php 5.5 should be returned (1.0.0)'); + self::assertSame((string) $package1, (string) $best, 'Latest version supporting php 5.5 should be returned (1.0.0)'); self::assertSame( "Cannot use foo/bar's latest version 2.1.0 as it requires php >=5.6 which is not satisfied by your platform.".PHP_EOL ."Cannot use foo/bar 2.0.0 as it requires php >=5.6 which is not satisfied by your platform.".PHP_EOL, @@ -90,7 +90,7 @@ public function testLatestVersionIsReturnedThatMatchesPhpRequirements(): void ); $best = $versionSelector->findBestCandidate($packageName, null, 'stable', PlatformRequirementFilterFactory::ignoreAll()); - $this->assertSame((string) $package3, (string) $best, 'Latest version should be returned when ignoring platform reqs (2.1.0)'); + self::assertSame((string) $package3, (string) $best, 'Latest version should be returned when ignoring platform reqs (2.1.0)'); } public function testLatestVersionIsReturnedThatMatchesExtRequirements(): void @@ -114,9 +114,9 @@ public function testLatestVersionIsReturnedThatMatchesExtRequirements(): void ->will($this->returnValue($packages)); $best = $versionSelector->findBestCandidate($packageName); - $this->assertSame($package1, $best, 'Latest version supporting ext-zip 5.3.0 should be returned (1.0.0)'); + self::assertSame($package1, $best, 'Latest version supporting ext-zip 5.3.0 should be returned (1.0.0)'); $best = $versionSelector->findBestCandidate($packageName, null, 'stable', PlatformRequirementFilterFactory::ignoreAll()); - $this->assertSame($package2, $best, 'Latest version should be returned when ignoring platform reqs (2.0.0)'); + self::assertSame($package2, $best, 'Latest version should be returned when ignoring platform reqs (2.0.0)'); } public function testLatestVersionIsReturnedThatMatchesPlatformExt(): void @@ -139,9 +139,9 @@ public function testLatestVersionIsReturnedThatMatchesPlatformExt(): void ->will($this->returnValue($packages)); $best = $versionSelector->findBestCandidate($packageName); - $this->assertSame($package1, $best, 'Latest version not requiring ext-barfoo should be returned (1.0.0)'); + self::assertSame($package1, $best, 'Latest version not requiring ext-barfoo should be returned (1.0.0)'); $best = $versionSelector->findBestCandidate($packageName, null, 'stable', PlatformRequirementFilterFactory::ignoreAll()); - $this->assertSame($package2, $best, 'Latest version should be returned when ignoring platform reqs (2.0.0)'); + self::assertSame($package2, $best, 'Latest version should be returned when ignoring platform reqs (2.0.0)'); } public function testLatestVersionIsReturnedThatMatchesComposerRequirements(): void @@ -165,9 +165,9 @@ public function testLatestVersionIsReturnedThatMatchesComposerRequirements(): vo ->will($this->returnValue($packages)); $best = $versionSelector->findBestCandidate($packageName); - $this->assertSame($package1, $best, 'Latest version supporting composer 1 should be returned (1.0.0)'); + self::assertSame($package1, $best, 'Latest version supporting composer 1 should be returned (1.0.0)'); $best = $versionSelector->findBestCandidate($packageName, null, 'stable', PlatformRequirementFilterFactory::ignoreAll()); - $this->assertSame($package2, $best, 'Latest version should be returned when ignoring platform reqs (1.1.0)'); + self::assertSame($package2, $best, 'Latest version should be returned when ignoring platform reqs (1.1.0)'); } public function testMostStableVersionIsReturned(): void @@ -187,7 +187,7 @@ public function testMostStableVersionIsReturned(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName); - $this->assertSame($package1, $best, 'Latest most stable version should be returned (1.0.0)'); + self::assertSame($package1, $best, 'Latest most stable version should be returned (1.0.0)'); } public function testMostStableVersionIsReturnedRegardlessOfOrder(): void @@ -209,10 +209,10 @@ public function testMostStableVersionIsReturnedRegardlessOfOrder(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName); - $this->assertSame($package2, $best, 'Expecting 2.0.0-beta3, cause beta is more stable than dev'); + self::assertSame($package2, $best, 'Expecting 2.0.0-beta3, cause beta is more stable than dev'); $best = $versionSelector->findBestCandidate($packageName); - $this->assertSame($package2, $best, 'Expecting 2.0.0-beta3, cause beta is more stable than dev'); + self::assertSame($package2, $best, 'Expecting 2.0.0-beta3, cause beta is more stable than dev'); } public function testHighestVersionIsReturned(): void @@ -232,7 +232,7 @@ public function testHighestVersionIsReturned(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, 'dev'); - $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); + self::assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); } public function testHighestVersionMatchingStabilityIsReturned(): void @@ -253,7 +253,7 @@ public function testHighestVersionMatchingStabilityIsReturned(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, 'beta'); - $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); + self::assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); } public function testMostStableUnstableVersionIsReturned(): void @@ -273,7 +273,7 @@ public function testMostStableUnstableVersionIsReturned(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, 'stable'); - $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); + self::assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); } public function testDefaultBranchAliasIsNeverReturned(): void @@ -294,7 +294,7 @@ public function testDefaultBranchAliasIsNeverReturned(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, 'dev'); - $this->assertSame($package2, $best, 'Latest version should be returned (dev-main)'); + self::assertSame($package2, $best, 'Latest version should be returned (dev-main)'); } public function testFalseReturnedOnNoPackages(): void @@ -306,7 +306,7 @@ public function testFalseReturnedOnNoPackages(): void $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate('foobaz'); - $this->assertFalse($best, 'No versions are available returns false'); + self::assertFalse($best, 'No versions are available returns false'); } /** @@ -327,7 +327,7 @@ public function testFindRecommendedRequireVersion(string $prettyVersion, string $recommended = $versionSelector->findRecommendedRequireVersion($package); // assert that the recommended version is what we expect - $this->assertSame($expectedVersion, $recommended); + self::assertSame($expectedVersion, $recommended); } public static function provideRecommendedRequireVersionPackages(): array diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index ecaa615a27c2..ba2402a19c11 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -160,8 +160,8 @@ public function testInstallNewPlugin(): void $installer->install($this->repository, $this->packages[0]); $plugins = $this->pm->getPlugins(); - $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals( + self::assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore property.notFound + self::assertEquals( 'activate v1'.PHP_EOL, $this->io->getOutput() ); @@ -182,11 +182,11 @@ public function testInstallPluginWithRootPackageHavingFilesAutoload(): void $installer->install($this->repository, $this->packages[0]); $plugins = $this->pm->getPlugins(); - $this->assertEquals( + self::assertEquals( 'activate v1'.PHP_EOL, $this->io->getOutput() ); - $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound + self::assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore property.notFound } public function testInstallMultiplePlugins(): void @@ -201,11 +201,11 @@ public function testInstallMultiplePlugins(): void $installer->install($this->repository, $this->packages[3]); $plugins = $this->pm->getPlugins(); - $this->assertEquals('plugin1', $plugins[0]->name); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('installer-v4', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('plugin2', $plugins[1]->name); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('installer-v4', $plugins[1]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput()); + self::assertEquals('plugin1', $plugins[0]->name); // @phpstan-ignore property.notFound + self::assertEquals('installer-v4', $plugins[0]->version); // @phpstan-ignore property.notFound + self::assertEquals('plugin2', $plugins[1]->name); // @phpstan-ignore property.notFound + self::assertEquals('installer-v4', $plugins[1]->version); // @phpstan-ignore property.notFound + self::assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput()); } public function testUpgradeWithNewClassName(): void @@ -224,9 +224,9 @@ public function testUpgradeWithNewClassName(): void $installer->update($this->repository, $this->packages[0], $this->packages[1]); $plugins = $this->pm->getPlugins(); - $this->assertCount(1, $plugins); - $this->assertEquals('installer-v2', $plugins[1]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput()); + self::assertCount(1, $plugins); + self::assertEquals('installer-v2', $plugins[1]->version); // @phpstan-ignore property.notFound + self::assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput()); } public function testUninstall(): void @@ -245,8 +245,8 @@ public function testUninstall(): void $installer->uninstall($this->repository, $this->packages[0]); $plugins = $this->pm->getPlugins(); - $this->assertCount(0, $plugins); - $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput()); + self::assertCount(0, $plugins); + self::assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput()); } public function testUpgradeWithSameClassName(): void @@ -265,8 +265,8 @@ public function testUpgradeWithSameClassName(): void $installer->update($this->repository, $this->packages[1], $this->packages[2]); $plugins = $this->pm->getPlugins(); - $this->assertEquals('installer-v3', $plugins[1]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput()); + self::assertEquals('installer-v3', $plugins[1]->version); // @phpstan-ignore property.notFound + self::assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput()); } public function testRegisterPluginOnlyOneTime(): void @@ -282,9 +282,9 @@ public function testRegisterPluginOnlyOneTime(): void $installer->install($this->repository, clone $this->packages[0]); $plugins = $this->pm->getPlugins(); - $this->assertCount(1, $plugins); - $this->assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore staticMethod.dynamicCall, property.notFound - $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); + self::assertCount(1, $plugins); + self::assertEquals('installer-v1', $plugins[0]->version); // @phpstan-ignore property.notFound + self::assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); } /** @@ -325,16 +325,16 @@ public function testStarPluginVersionWorksWithAnyAPIVersion(): void $starVersionPlugin = [$this->packages[4]]; $this->setPluginApiVersionWithPlugins('1.0.0', $starVersionPlugin); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('1.9.9', $starVersionPlugin); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('2.0.0-dev', $starVersionPlugin); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('100.0.0-stable', $starVersionPlugin); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); } public function testPluginConstraintWorksOnlyWithCertainAPIVersion(): void @@ -342,16 +342,16 @@ public function testPluginConstraintWorksOnlyWithCertainAPIVersion(): void $pluginWithApiConstraint = [$this->packages[5]]; $this->setPluginApiVersionWithPlugins('1.0.0', $pluginWithApiConstraint); - $this->assertCount(0, $this->pm->getPlugins()); + self::assertCount(0, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('1.1.9', $pluginWithApiConstraint); - $this->assertCount(0, $this->pm->getPlugins()); + self::assertCount(0, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('1.2.0', $pluginWithApiConstraint); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('1.9.9', $pluginWithApiConstraint); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); } public function testPluginRangeConstraintsWorkOnlyWithCertainAPIVersion(): void @@ -359,13 +359,13 @@ public function testPluginRangeConstraintsWorkOnlyWithCertainAPIVersion(): void $pluginWithApiConstraint = [$this->packages[6]]; $this->setPluginApiVersionWithPlugins('1.0.0', $pluginWithApiConstraint); - $this->assertCount(0, $this->pm->getPlugins()); + self::assertCount(0, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('3.0.0', $pluginWithApiConstraint); - $this->assertCount(1, $this->pm->getPlugins()); + self::assertCount(1, $this->pm->getPlugins()); $this->setPluginApiVersionWithPlugins('5.5.0', $pluginWithApiConstraint); - $this->assertCount(0, $this->pm->getPlugins()); + self::assertCount(0, $this->pm->getPlugins()); } public function testCommandProviderCapability(): void @@ -379,19 +379,19 @@ public function testCommandProviderCapability(): void /** @var \Composer\Plugin\Capability\CommandProvider[] $caps */ $caps = $this->pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', ['composer' => $this->composer, 'io' => $this->io]); - $this->assertCount(1, $caps); - $this->assertInstanceOf('Composer\Plugin\Capability\CommandProvider', $caps[0]); + self::assertCount(1, $caps); + self::assertInstanceOf('Composer\Plugin\Capability\CommandProvider', $caps[0]); $commands = $caps[0]->getCommands(); - $this->assertCount(1, $commands); - $this->assertInstanceOf('Composer\Command\BaseCommand', $commands[0]); + self::assertCount(1, $commands); + self::assertInstanceOf('Composer\Command\BaseCommand', $commands[0]); } public function testIncapablePluginIsCorrectlyDetected(): void { $plugin = $this->getMockBuilder('Composer\Plugin\PluginInterface') ->getMock(); - $this->assertNull($this->pm->getPluginCapability($plugin, 'Fake\Ability')); + self::assertNull($this->pm->getPluginCapability($plugin, 'Fake\Ability')); } public function testCapabilityImplementsComposerPluginApiClassAndIsConstructedWithArgs(): void @@ -411,9 +411,9 @@ public function testCapabilityImplementsComposerPluginApiClassAndIsConstructedWi /** @var \Composer\Test\Plugin\Mock\Capability $capability */ $capability = $this->pm->getPluginCapability($plugin, $capabilityApi, ['a' => 1, 'b' => 2]); - $this->assertInstanceOf($capabilityApi, $capability); - $this->assertInstanceOf($capabilityImplementation, $capability); - $this->assertSame(['a' => 1, 'b' => 2, 'plugin' => $plugin], $capability->args); + self::assertInstanceOf($capabilityApi, $capability); + self::assertInstanceOf($capabilityImplementation, $capability); + self::assertSame(['a' => 1, 'b' => 2, 'plugin' => $plugin], $capability->args); } /** @return mixed[] */ @@ -467,7 +467,7 @@ public function testQueryingNonProvidedCapabilityReturnsNullSafely(): void return []; })); - $this->assertNull($this->pm->getPluginCapability($plugin, $capabilityApi)); + self::assertNull($this->pm->getPluginCapability($plugin, $capabilityApi)); } /** @return mixed[] */ diff --git a/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php b/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php index 371f4959792e..bfad93083bc0 100644 --- a/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php +++ b/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php @@ -63,7 +63,7 @@ public function testAskConfirmation(string $question, bool $expected, bool $defa [$input, $dialog] = $this->createInput($question."\n"); $question = new StrictConfirmationQuestion('Do you like French fries?', $default); - $this->assertEquals($expected, $dialog->ask($input, $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); + self::assertEquals($expected, $dialog->ask($input, $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); } /** @@ -88,10 +88,10 @@ public function testAskConfirmationWithCustomTrueAndFalseAnswer(): void $question = new StrictConfirmationQuestion('Do you like French fries?', false, '/^ja$/i', '/^nein$/i'); [$input, $dialog] = $this->createInput("ja\n"); - $this->assertTrue($dialog->ask($input, $this->createOutputInterface(), $question)); + self::assertTrue($dialog->ask($input, $this->createOutputInterface(), $question)); [$input, $dialog] = $this->createInput("nein\n"); - $this->assertFalse($dialog->ask($input, $this->createOutputInterface(), $question)); + self::assertFalse($dialog->ask($input, $this->createOutputInterface(), $question)); } /** @@ -100,7 +100,7 @@ public function testAskConfirmationWithCustomTrueAndFalseAnswer(): void protected function getInputStream(string $input) { $stream = fopen('php://memory', 'r+', false); - $this->assertNotFalse($stream); + self::assertNotFalse($stream); fwrite($stream, $input); rewind($stream); diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php index 95d56a867109..a06fb648ce5e 100644 --- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php @@ -23,7 +23,7 @@ public function testAddPackage(): void $repo = new ArrayRepository; $repo->addPackage(self::getPackage('foo', '1')); - $this->assertCount(1, $repo); + self::assertCount(1, $repo); } public function testRemovePackage(): void @@ -34,12 +34,12 @@ public function testRemovePackage(): void $repo->addPackage(self::getPackage('foo', '1')); $repo->addPackage($package); - $this->assertCount(2, $repo); + self::assertCount(2, $repo); $repo->removePackage(self::getPackage('foo', '1')); - $this->assertCount(1, $repo); - $this->assertEquals([$package], $repo->getPackages()); + self::assertCount(1, $repo); + self::assertEquals([$package], $repo->getPackages()); } public function testHasPackage(): void @@ -48,8 +48,8 @@ public function testHasPackage(): void $repo->addPackage(self::getPackage('foo', '1')); $repo->addPackage(self::getPackage('bar', '2')); - $this->assertTrue($repo->hasPackage(self::getPackage('foo', '1'))); - $this->assertFalse($repo->hasPackage(self::getPackage('bar', '1'))); + self::assertTrue($repo->hasPackage(self::getPackage('foo', '1'))); + self::assertFalse($repo->hasPackage(self::getPackage('bar', '1'))); } public function testFindPackages(): void @@ -60,12 +60,12 @@ public function testFindPackages(): void $repo->addPackage(self::getPackage('bar', '3')); $foo = $repo->findPackages('foo'); - $this->assertCount(1, $foo); - $this->assertEquals('foo', $foo[0]->getName()); + self::assertCount(1, $foo); + self::assertEquals('foo', $foo[0]->getName()); $bar = $repo->findPackages('bar'); - $this->assertCount(2, $bar); - $this->assertEquals('bar', $bar[0]->getName()); + self::assertCount(2, $bar); + self::assertEquals('bar', $bar[0]->getName()); } public function testAutomaticallyAddAliasedPackageButNotRemove(): void @@ -77,13 +77,13 @@ public function testAutomaticallyAddAliasedPackageButNotRemove(): void $repo->addPackage($alias); - $this->assertCount(2, $repo); - $this->assertTrue($repo->hasPackage(self::getPackage('foo', '1'))); - $this->assertTrue($repo->hasPackage(self::getPackage('foo', '2'))); + self::assertCount(2, $repo); + self::assertTrue($repo->hasPackage(self::getPackage('foo', '1'))); + self::assertTrue($repo->hasPackage(self::getPackage('foo', '2'))); $repo->removePackage($alias); - $this->assertCount(1, $repo); + self::assertCount(1, $repo); } public function testSearch(): void @@ -93,17 +93,17 @@ public function testSearch(): void $repo->addPackage(self::getPackage('foo', '1')); $repo->addPackage(self::getPackage('bar', '1')); - $this->assertSame( + self::assertSame( [['name' => 'foo', 'description' => null]], $repo->search('foo', RepositoryInterface::SEARCH_FULLTEXT) ); - $this->assertSame( + self::assertSame( [['name' => 'bar', 'description' => null]], $repo->search('bar') ); - $this->assertEmpty( + self::assertEmpty( $repo->search('foobar') ); } @@ -119,14 +119,14 @@ public function testSearchWithPackageType(): void $package->setType('composer-plugin'); $repo->addPackage($package); - $this->assertSame( + self::assertSame( [['name' => 'foo', 'description' => null]], $repo->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'library') ); - $this->assertEmpty($repo->search('bar', RepositoryInterface::SEARCH_FULLTEXT, 'package')); + self::assertEmpty($repo->search('bar', RepositoryInterface::SEARCH_FULLTEXT, 'package')); - $this->assertSame( + self::assertSame( [['name' => 'foobar', 'description' => null]], $repo->search('foo', 0, 'composer-plugin') ); @@ -143,7 +143,7 @@ public function testSearchWithAbandonedPackages(): void $package2->setAbandoned('bar'); $repo->addPackage($package2); - $this->assertSame( + self::assertSame( [ ['name' => 'foo1', 'description' => null, 'abandoned' => true], ['name' => 'foo2', 'description' => null, 'abandoned' => 'bar'], diff --git a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php index 52b2b568c2b9..1db3263a11f7 100644 --- a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php @@ -51,14 +51,14 @@ public function testExtractsConfigsFromZipArchives(): void sort($expectedPackages); sort($foundPackages); - $this->assertSame($expectedPackages, $foundPackages); + self::assertSame($expectedPackages, $foundPackages); $tarPackage = array_filter($repo->getPackages(), static function (BasePackage $package): bool { return $package->getPrettyName() === 'test/jsonInRootTarFile'; }); - $this->assertCount(1, $tarPackage); + self::assertCount(1, $tarPackage); $tarPackage = array_pop($tarPackage); - $this->assertSame('tar', $tarPackage->getDistType()); + self::assertSame('tar', $tarPackage->getDistType()); } public function testAbsoluteRepoUrlCreatesAbsoluteUrlPackages(): void @@ -68,7 +68,7 @@ public function testAbsoluteRepoUrlCreatesAbsoluteUrlPackages(): void $repo = new ArtifactRepository($coordinates, new NullIO()); foreach ($repo->getPackages() as $package) { - $this->assertSame(strpos($package->getDistUrl(), strtr($absolutePath, '\\', '/')), 0); + self::assertSame(strpos($package->getDistUrl(), strtr($absolutePath, '\\', '/')), 0); } } @@ -79,7 +79,7 @@ public function testRelativeRepoUrlCreatesRelativeUrlPackages(): void $repo = new ArtifactRepository($coordinates, new NullIO()); foreach ($repo->getPackages() as $package) { - $this->assertSame(strpos($package->getDistUrl(), $relativePath), 0); + self::assertSame(strpos($package->getDistUrl(), $relativePath), 0); } } } diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 6eba1081dc32..5355c07a66fe 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -55,7 +55,7 @@ public function testLoadData(array $expected, array $repoPackages): void $packages = $repository->getPackages(); // Final sanity check, ensure the correct number of packages were added. - $this->assertCount(count($expected), $packages); + self::assertCount(count($expected), $packages); foreach ($expected as $index => $pkg) { self::assertSame($pkg['name'].' '.$pkg['version'], $packages[$index]->getName().' '.$packages[$index]->getPrettyVersion()); @@ -165,9 +165,9 @@ public function testWhatProvides(): void $reflMethod->setAccessible(true); $packages = $reflMethod->invoke($repo, 'a'); - $this->assertCount(5, $packages); - $this->assertEquals(['1', '1-alias', '2', '2-alias', '3'], array_keys($packages)); - $this->assertSame($packages['2'], $packages['2-alias']->getAliasOf()); + self::assertCount(5, $packages); + self::assertEquals(['1', '1-alias', '2', '2-alias', '3'], array_keys($packages)); + self::assertSame($packages['2'], $packages['2-alias']->getAliasOf()); } public function testSearchWithType(): void @@ -202,12 +202,12 @@ public function testSearchWithType(): void $config->merge(['config' => ['cache-read-only' => true]]); $repository = new ComposerRepository($repoConfig, new NullIO, $config, $httpDownloader, $eventDispatcher); - $this->assertSame( + self::assertSame( [['name' => 'foo', 'description' => null]], $repository->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'composer-plugin') ); - $this->assertEmpty( + self::assertEmpty( $repository->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'library') ); } @@ -234,7 +234,7 @@ public function testSearchWithSpecialChars(): void $config->merge(['config' => ['cache-read-only' => true]]); $repository = new ComposerRepository($repoConfig, new NullIO, $config, $httpDownloader, $eventDispatcher); - $this->assertEmpty( + self::assertEmpty( $repository->search('foo bar', RepositoryInterface::SEARCH_FULLTEXT) ); } @@ -277,7 +277,7 @@ public function testSearchWithAbandonedPackages(): void $config->merge(['config' => ['cache-read-only' => true]]); $repository = new ComposerRepository($repoConfig, new NullIO, $config, $httpDownloader, $eventDispatcher); - $this->assertSame( + self::assertSame( [ ['name' => 'foo1', 'description' => null, 'abandoned' => true], ['name' => 'foo2', 'description' => null, 'abandoned' => 'bar'], @@ -312,7 +312,7 @@ public function testCanonicalizeUrl(string $expected, string $url, string $repos $property->setAccessible(true); $property->setValue($repository, $repositoryUrl); - $this->assertSame($expected, $method->invoke($repository, $url)); + self::assertSame($expected, $method->invoke($repository, $url)); } public static function provideCanonicalizeUrlTestCases(): array @@ -379,7 +379,7 @@ public function testGetProviderNamesWillReturnPartialPackageNames(): void $httpDownloader ); - $this->assertEquals(['foo/bar'], $repository->getPackageNames()); + self::assertEquals(['foo/bar'], $repository->getPackageNames()); } public function testGetSecurityAdvisoriesAssertRepositoryHttpOptionsAreUsed(): void @@ -425,7 +425,7 @@ public function testGetSecurityAdvisoriesAssertRepositoryHttpOptionsAreUsed(): v $httpDownloader ); - $this->assertSame([ + self::assertSame([ 'namesFound' => [], 'advisories' => [], ], $repository->getSecurityAdvisories(['foo/bar' => new Constraint('=', '1.0.0.0')])); diff --git a/tests/Composer/Test/Repository/CompositeRepositoryTest.php b/tests/Composer/Test/Repository/CompositeRepositoryTest.php index 098396c9b392..ea2e7ffda14d 100644 --- a/tests/Composer/Test/Repository/CompositeRepositoryTest.php +++ b/tests/Composer/Test/Repository/CompositeRepositoryTest.php @@ -28,11 +28,11 @@ public function testHasPackage(): void $repo = new CompositeRepository([$arrayRepoOne, $arrayRepoTwo]); - $this->assertTrue($repo->hasPackage(self::getPackage('foo', '1')), "Should have package 'foo/1'"); - $this->assertTrue($repo->hasPackage(self::getPackage('bar', '1')), "Should have package 'bar/1'"); + self::assertTrue($repo->hasPackage(self::getPackage('foo', '1')), "Should have package 'foo/1'"); + self::assertTrue($repo->hasPackage(self::getPackage('bar', '1')), "Should have package 'bar/1'"); - $this->assertFalse($repo->hasPackage(self::getPackage('foo', '2')), "Should not have package 'foo/2'"); - $this->assertFalse($repo->hasPackage(self::getPackage('bar', '2')), "Should not have package 'bar/2'"); + self::assertFalse($repo->hasPackage(self::getPackage('foo', '2')), "Should not have package 'foo/2'"); + self::assertFalse($repo->hasPackage(self::getPackage('bar', '2')), "Should not have package 'bar/2'"); } public function testFindPackage(): void @@ -45,11 +45,11 @@ public function testFindPackage(): void $repo = new CompositeRepository([$arrayRepoOne, $arrayRepoTwo]); - $this->assertEquals('foo', $repo->findPackage('foo', '1')->getName(), "Should find package 'foo/1' and get name of 'foo'"); - $this->assertEquals('1', $repo->findPackage('foo', '1')->getPrettyVersion(), "Should find package 'foo/1' and get pretty version of '1'"); - $this->assertEquals('bar', $repo->findPackage('bar', '1')->getName(), "Should find package 'bar/1' and get name of 'bar'"); - $this->assertEquals('1', $repo->findPackage('bar', '1')->getPrettyVersion(), "Should find package 'bar/1' and get pretty version of '1'"); - $this->assertNull($repo->findPackage('foo', '2'), "Should not find package 'foo/2'"); + self::assertEquals('foo', $repo->findPackage('foo', '1')->getName(), "Should find package 'foo/1' and get name of 'foo'"); + self::assertEquals('1', $repo->findPackage('foo', '1')->getPrettyVersion(), "Should find package 'foo/1' and get pretty version of '1'"); + self::assertEquals('bar', $repo->findPackage('bar', '1')->getName(), "Should find package 'bar/1' and get name of 'bar'"); + self::assertEquals('1', $repo->findPackage('bar', '1')->getPrettyVersion(), "Should find package 'bar/1' and get pretty version of '1'"); + self::assertNull($repo->findPackage('foo', '2'), "Should not find package 'foo/2'"); } public function testFindPackages(): void @@ -67,16 +67,16 @@ public function testFindPackages(): void $repo = new CompositeRepository([$arrayRepoOne, $arrayRepoTwo]); $bats = $repo->findPackages('bat'); - $this->assertCount(1, $bats, "Should find one instance of 'bats' (defined in just one repository)"); - $this->assertEquals('bat', $bats[0]->getName(), "Should find packages named 'bat'"); + self::assertCount(1, $bats, "Should find one instance of 'bats' (defined in just one repository)"); + self::assertEquals('bat', $bats[0]->getName(), "Should find packages named 'bat'"); $bars = $repo->findPackages('bar'); - $this->assertCount(2, $bars, "Should find two instances of 'bar' (both defined in the same repository)"); - $this->assertEquals('bar', $bars[0]->getName(), "Should find packages named 'bar'"); + self::assertCount(2, $bars, "Should find two instances of 'bar' (both defined in the same repository)"); + self::assertEquals('bar', $bars[0]->getName(), "Should find packages named 'bar'"); $foos = $repo->findPackages('foo'); - $this->assertCount(3, $foos, "Should find three instances of 'foo' (two defined in one repository, the third in the other)"); - $this->assertEquals('foo', $foos[0]->getName(), "Should find packages named 'foo'"); + self::assertCount(3, $foos, "Should find three instances of 'foo' (two defined in one repository, the third in the other)"); + self::assertEquals('foo', $foos[0]->getName(), "Should find packages named 'foo'"); } public function testGetPackages(): void @@ -90,11 +90,11 @@ public function testGetPackages(): void $repo = new CompositeRepository([$arrayRepoOne, $arrayRepoTwo]); $packages = $repo->getPackages(); - $this->assertCount(2, $packages, "Should get two packages"); - $this->assertEquals("foo", $packages[0]->getName(), "First package should have name of 'foo'"); - $this->assertEquals("1", $packages[0]->getPrettyVersion(), "First package should have pretty version of '1'"); - $this->assertEquals("bar", $packages[1]->getName(), "Second package should have name of 'bar'"); - $this->assertEquals("1", $packages[1]->getPrettyVersion(), "Second package should have pretty version of '1'"); + self::assertCount(2, $packages, "Should get two packages"); + self::assertEquals("foo", $packages[0]->getName(), "First package should have name of 'foo'"); + self::assertEquals("1", $packages[0]->getPrettyVersion(), "First package should have pretty version of '1'"); + self::assertEquals("bar", $packages[1]->getName(), "Second package should have name of 'bar'"); + self::assertEquals("1", $packages[1]->getPrettyVersion(), "Second package should have pretty version of '1'"); } public function testAddRepository(): void @@ -108,9 +108,9 @@ public function testAddRepository(): void $arrayRepoTwo->addPackage(self::getPackage('bar', '3')); $repo = new CompositeRepository([$arrayRepoOne]); - $this->assertCount(1, $repo, "Composite repository should have just one package before addRepository() is called"); + self::assertCount(1, $repo, "Composite repository should have just one package before addRepository() is called"); $repo->addRepository($arrayRepoTwo); - $this->assertCount(4, $repo, "Composite repository should have four packages after addRepository() is called"); + self::assertCount(4, $repo, "Composite repository should have four packages after addRepository() is called"); } public function testCount(): void @@ -123,7 +123,7 @@ public function testCount(): void $repo = new CompositeRepository([$arrayRepoOne, $arrayRepoTwo]); - $this->assertCount(2, $repo, "Should return '2' for count(\$repo)"); + self::assertCount(2, $repo, "Should return '2' for count(\$repo)"); } /** @@ -134,7 +134,7 @@ public function testCount(): void public function testNoRepositories(string $method, array $args): void { $repo = new CompositeRepository([]); - $this->assertEquals([], call_user_func_array([$repo, $method], $args)); + self::assertEquals([], call_user_func_array([$repo, $method], $args)); } public static function provideMethodCalls(): array diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index e932ef4a85bc..c4f62a6554f2 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -39,10 +39,10 @@ public function testRepositoryRead(): void $packages = $repository->getPackages(); - $this->assertCount(1, $packages); - $this->assertSame('package1', $packages[0]->getName()); - $this->assertSame('1.0.0.0-beta', $packages[0]->getVersion()); - $this->assertSame('vendor', $packages[0]->getType()); + self::assertCount(1, $packages); + self::assertSame('package1', $packages[0]->getName()); + self::assertSame('1.0.0.0-beta', $packages[0]->getVersion()); + self::assertSame('vendor', $packages[0]->getType()); } public function testCorruptedRepositoryFile(): void @@ -75,7 +75,7 @@ public function testUnexistentRepositoryFile(): void ->method('exists') ->will($this->returnValue(false)); - $this->assertEquals([], $repository->getPackages()); + self::assertEquals([], $repository->getPackages()); } public function testRepositoryWrite(): void @@ -195,7 +195,7 @@ public function testRepositoryWritesInstalledPhp(): void })); $repository->write(true, $im); - $this->assertSame(file_get_contents(__DIR__.'/Fixtures/installed.php'), file_get_contents($dir.'/installed.php')); + self::assertSame(file_get_contents(__DIR__.'/Fixtures/installed.php'), file_get_contents($dir.'/installed.php')); } public function testSafelyLoadInstalledVersions(): void diff --git a/tests/Composer/Test/Repository/FilterRepositoryTest.php b/tests/Composer/Test/Repository/FilterRepositoryTest.php index 726cc549c475..8698f97f8a16 100644 --- a/tests/Composer/Test/Repository/FilterRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilterRepositoryTest.php @@ -45,7 +45,7 @@ public function testRepoMatching(array $expected, $config): void $repo = new FilterRepository($this->arrayRepo, $config); $packages = $repo->getPackages(); - $this->assertSame($expected, array_map(static function ($p): string { + self::assertSame($expected, array_map(static function ($p): string { return $p->getName(); }, $packages)); } @@ -87,15 +87,15 @@ public function testCanonicalDefaultTrue(): void { $repo = new FilterRepository($this->arrayRepo, []); $result = $repo->loadPackages(['foo/aaa' => new MatchAllConstraint], BasePackage::$stabilities, []); - $this->assertCount(1, $result['packages']); - $this->assertCount(1, $result['namesFound']); + self::assertCount(1, $result['packages']); + self::assertCount(1, $result['namesFound']); } public function testNonCanonical(): void { $repo = new FilterRepository($this->arrayRepo, ['canonical' => false]); $result = $repo->loadPackages(['foo/aaa' => new MatchAllConstraint], BasePackage::$stabilities, []); - $this->assertCount(1, $result['packages']); - $this->assertCount(0, $result['namesFound']); + self::assertCount(1, $result['packages']); + self::assertCount(0, $result['namesFound']); } } diff --git a/tests/Composer/Test/Repository/InstalledRepositoryTest.php b/tests/Composer/Test/Repository/InstalledRepositoryTest.php index 127c58b8f0a2..47726ecadefa 100644 --- a/tests/Composer/Test/Repository/InstalledRepositoryTest.php +++ b/tests/Composer/Test/Repository/InstalledRepositoryTest.php @@ -36,9 +36,9 @@ public function testFindPackagesWithReplacersAndProviders(): void $repo = new InstalledRepository([$arrayRepoOne, $arrayRepoTwo]); - $this->assertEquals([$foo2], $repo->findPackagesWithReplacersAndProviders('foo', '2')); - $this->assertEquals([$bar], $repo->findPackagesWithReplacersAndProviders('bar', '1')); - $this->assertEquals([$foo, $bar2], $repo->findPackagesWithReplacersAndProviders('provided')); + self::assertEquals([$foo2], $repo->findPackagesWithReplacersAndProviders('foo', '2')); + self::assertEquals([$bar], $repo->findPackagesWithReplacersAndProviders('bar', '1')); + self::assertEquals([$foo, $bar2], $repo->findPackagesWithReplacersAndProviders('provided')); } public function testAddRepository(): void diff --git a/tests/Composer/Test/Repository/PathRepositoryTest.php b/tests/Composer/Test/Repository/PathRepositoryTest.php index 5edd4c423fd5..f30aee38821b 100644 --- a/tests/Composer/Test/Repository/PathRepositoryTest.php +++ b/tests/Composer/Test/Repository/PathRepositoryTest.php @@ -36,8 +36,8 @@ public function testLoadPackageFromFileSystemWithVersion(): void $repository = $this->createPathRepo(['url' => $repositoryUrl]); $repository->getPackages(); - $this->assertSame(1, $repository->count()); - $this->assertTrue($repository->hasPackage(self::getPackage('test/path-versioned', '0.0.2'))); + self::assertSame(1, $repository->count()); + self::assertTrue($repository->hasPackage(self::getPackage('test/path-versioned', '0.0.2'))); } public function testLoadPackageFromFileSystemWithoutVersion(): void @@ -46,13 +46,13 @@ public function testLoadPackageFromFileSystemWithoutVersion(): void $repository = $this->createPathRepo(['url' => $repositoryUrl]); $packages = $repository->getPackages(); - $this->assertGreaterThanOrEqual(1, $repository->count()); + self::assertGreaterThanOrEqual(1, $repository->count()); $package = $packages[0]; - $this->assertSame('test/path-unversioned', $package->getName()); + self::assertSame('test/path-unversioned', $package->getName()); $packageVersion = $package->getVersion(); - $this->assertNotEmpty($packageVersion); + self::assertNotEmpty($packageVersion); } public function testLoadPackageFromFileSystemWithWildcard(): void @@ -62,7 +62,7 @@ public function testLoadPackageFromFileSystemWithWildcard(): void $packages = $repository->getPackages(); $names = []; - $this->assertGreaterThanOrEqual(2, $repository->count()); + self::assertGreaterThanOrEqual(2, $repository->count()); $package = $packages[0]; $names[] = $package->getName(); @@ -71,7 +71,7 @@ public function testLoadPackageFromFileSystemWithWildcard(): void $names[] = $package->getName(); sort($names); - $this->assertEquals(['test/path-unversioned', 'test/path-versioned'], $names); + self::assertEquals(['test/path-unversioned', 'test/path-versioned'], $names); } public function testLoadPackageWithExplicitVersions(): void @@ -88,7 +88,7 @@ public function testLoadPackageWithExplicitVersions(): void $versions = []; - $this->assertEquals(2, $repository->count()); + self::assertEquals(2, $repository->count()); $package = $packages[0]; $versions[$package->getName()] = $package->getVersion(); @@ -97,7 +97,7 @@ public function testLoadPackageWithExplicitVersions(): void $versions[$package->getName()] = $package->getVersion(); ksort($versions); - $this->assertSame(['test/path-unversioned' => '4.3.2.1', 'test/path-versioned' => '3.2.1.0'], $versions); + self::assertSame(['test/path-unversioned' => '4.3.2.1', 'test/path-versioned' => '3.2.1.0'], $versions); } /** @@ -115,14 +115,14 @@ public function testUrlRemainsRelative(): void $repository = $this->createPathRepo(['url' => $relativeUrl]); $packages = $repository->getPackages(); - $this->assertSame(1, $repository->count()); + self::assertSame(1, $repository->count()); $package = $packages[0]; - $this->assertSame('test/path-versioned', $package->getName()); + self::assertSame('test/path-versioned', $package->getName()); // Convert platform specific separators back to generic URL slashes $relativeUrl = str_replace(DIRECTORY_SEPARATOR, '/', $relativeUrl); - $this->assertSame($relativeUrl, $package->getDistUrl()); + self::assertSame($relativeUrl, $package->getDistUrl()); } public function testReferenceNone(): void @@ -134,10 +134,10 @@ public function testReferenceNone(): void $repository = $this->createPathRepo(['url' => $repositoryUrl, 'options' => $options]); $packages = $repository->getPackages(); - $this->assertGreaterThanOrEqual(2, $repository->count()); + self::assertGreaterThanOrEqual(2, $repository->count()); foreach ($packages as $package) { - $this->assertEquals($package->getDistReference(), null); + self::assertEquals($package->getDistReference(), null); } } @@ -151,10 +151,10 @@ public function testReferenceConfig(): void $repository = $this->createPathRepo(['url' => $repositoryUrl, 'options' => $options]); $packages = $repository->getPackages(); - $this->assertGreaterThanOrEqual(2, $repository->count()); + self::assertGreaterThanOrEqual(2, $repository->count()); foreach ($packages as $package) { - $this->assertEquals( + self::assertEquals( $package->getDistReference(), sha1(file_get_contents($package->getDistUrl() . '/composer.json') . serialize($options)) ); diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index aa7ddb8ddc2b..df52653d3cf8 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -1256,8 +1256,8 @@ static function ($package): bool { } else { self::assertNotNull($package, sprintf('Expected to find package "%s"', $packageName)); self::assertSame($expectedVersion, $package->getPrettyVersion(), sprintf('Expected version %s for %s', $expectedVersion, $packageName)); - $this->assertPackageLinks('replaces', $expectedReplaces, $package, $package->getReplaces()); - $this->assertPackageLinks('provides', $expectedProvides, $package, $package->getProvides()); + self::assertPackageLinks('replaces', $expectedReplaces, $package, $package->getReplaces()); + self::assertPackageLinks('provides', $expectedProvides, $package, $package->getProvides()); } } } diff --git a/tests/Composer/Test/Repository/RepositoryFactoryTest.php b/tests/Composer/Test/Repository/RepositoryFactoryTest.php index 8b0a944c0d27..b318e030bb3e 100644 --- a/tests/Composer/Test/Repository/RepositoryFactoryTest.php +++ b/tests/Composer/Test/Repository/RepositoryFactoryTest.php @@ -30,7 +30,7 @@ public function testManagerWithAllRepositoryTypes(): void $ref->setAccessible(true); $repositoryClasses = $ref->getValue($manager); - $this->assertEquals([ + self::assertEquals([ 'composer', 'vcs', 'package', @@ -60,7 +60,7 @@ public function testManagerWithAllRepositoryTypes(): void */ public function testGenerateRepositoryName($index, array $config, array $existingRepos, string $expected): void { - $this->assertSame($expected, RepositoryFactory::generateRepositoryName($index, $config, $existingRepos)); + self::assertSame($expected, RepositoryFactory::generateRepositoryName($index, $config, $existingRepos)); } public static function generateRepositoryNameProvider(): array diff --git a/tests/Composer/Test/Repository/RepositoryManagerTest.php b/tests/Composer/Test/Repository/RepositoryManagerTest.php index d1fa09c8b249..859826e6a841 100644 --- a/tests/Composer/Test/Repository/RepositoryManagerTest.php +++ b/tests/Composer/Test/Repository/RepositoryManagerTest.php @@ -50,21 +50,17 @@ public function testPrepend(): void $rm->addRepository($repository1); $rm->prependRepository($repository2); - $this->assertEquals([$repository2, $repository1], $rm->getRepositories()); + self::assertEquals([$repository2, $repository1], $rm->getRepositories()); } /** * @dataProvider provideRepoCreationTestCases * + * @doesNotPerformAssertions * @param array $options - * @param class-string<\Throwable>|null $exception */ - public function testRepoCreation(string $type, array $options, ?string $exception = null): void + public function testRepoCreation(string $type, array $options): void { - if ($exception !== null) { - self::expectException($exception); - } - $rm = new RepositoryManager( $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config = new Config, @@ -86,7 +82,7 @@ public function testRepoCreation(string $type, array $options, ?string $exceptio $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); $rm->createRepository('composer', ['url' => 'http://example.org']); - $this->assertInstanceOf('Composer\Repository\RepositoryInterface', $rm->createRepository($type, $options)); + $rm->createRepository($type, $options); } public static function provideRepoCreationTestCases(): array @@ -97,9 +93,7 @@ public static function provideRepoCreationTestCases(): array ['git', ['url' => 'http://github.com/foo/bar']], ['git', ['url' => 'git@example.org:foo/bar.git']], ['svn', ['url' => 'svn://example.org/foo/bar']], - ['pear', ['url' => 'http://pear.example.org/foo'], 'InvalidArgumentException'], ['package', ['package' => []]], - ['invalid', [], 'InvalidArgumentException'], ]; if (class_exists('ZipArchive')) { @@ -109,6 +103,36 @@ public static function provideRepoCreationTestCases(): array return $cases; } + /** + * @dataProvider provideInvalidRepoCreationTestCases + * + * @param array $options + */ + public function testInvalidRepoCreationThrows(string $type, array $options): void + { + self::expectException('InvalidArgumentException'); + + $rm = new RepositoryManager( + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $config = new Config, + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() + ); + + $tmpdir = $this->tmpdir; + $config->merge(['config' => ['cache-repo-dir' => $tmpdir]]); + + $rm->createRepository($type, $options); + } + + public static function provideInvalidRepoCreationTestCases(): array + { + return [ + ['pear', ['url' => 'http://pear.example.org/foo']], + ['invalid', []], + ]; + } + public function testFilterRepoWrapping(): void { $rm = new RepositoryManager( @@ -122,7 +146,7 @@ public function testFilterRepoWrapping(): void /** @var \Composer\Repository\FilterRepository $repo */ $repo = $rm->createRepository('path', ['type' => 'path', 'url' => __DIR__, 'only' => ['foo/bar']]); - $this->assertInstanceOf('Composer\Repository\FilterRepository', $repo); - $this->assertInstanceOf('Composer\Repository\PathRepository', $repo->getRepository()); + self::assertInstanceOf('Composer\Repository\FilterRepository', $repo); + self::assertInstanceOf('Composer\Repository\PathRepository', $repo->getRepository()); } } diff --git a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php index 06ec3d177062..854c55e33ba3 100644 --- a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php @@ -62,6 +62,6 @@ public function testSupport(string $url, bool $assertion): void { $config = new Config(); $result = FossilDriver::supports($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config, $url); - $this->assertEquals($assertion, $result); + self::assertEquals($assertion, $result); } } diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index 5050b0fb49da..d1fa2bb232b9 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -111,12 +111,12 @@ public function testDriver(): GitBitbucketDriver ['url' => $urls[4], 'body' => '{"date": "2016-05-17T13:19:52+00:00"}'], ], true); - $this->assertEquals( + self::assertEquals( 'main', $driver->getRootIdentifier() ); - $this->assertEquals( + self::assertEquals( [ '1.0.1' => '9b78a3932143497c519e49b8241083838c8ff8a1', '1.0.0' => 'd3393d514318a9267d2f8ebbf463a9aaa389f8eb', @@ -124,14 +124,14 @@ public function testDriver(): GitBitbucketDriver $driver->getTags() ); - $this->assertEquals( + self::assertEquals( [ 'main' => '937992d19d72b5116c3e8c4a04f960e5fa270b22', ], $driver->getBranches() ); - $this->assertEquals( + self::assertEquals( [ 'name' => 'user/repo', 'description' => 'test repo', @@ -167,9 +167,9 @@ public function testGetParams(\Composer\Repository\Vcs\VcsDriverInterface $drive { $url = 'https://bitbucket.org/user/repo.git'; - $this->assertEquals($url, $driver->getUrl()); + self::assertEquals($url, $driver->getUrl()); - $this->assertEquals( + self::assertEquals( [ 'type' => 'zip', 'url' => 'https://bitbucket.org/user/repo/get/reference.zip', @@ -179,7 +179,7 @@ public function testGetParams(\Composer\Repository\Vcs\VcsDriverInterface $drive $driver->getDist('reference') ); - $this->assertEquals( + self::assertEquals( ['type' => 'git', 'url' => $url, 'reference' => 'reference'], $driver->getSource('reference') ); @@ -217,22 +217,22 @@ public function testInvalidSupportData(): void $driver->getRootIdentifier(); $data = $driver->getComposerInformation('main'); - $this->assertIsArray($data); - $this->assertSame('https://bitbucket.org/user/repo/src/937992d19d72b5116c3e8c4a04f960e5fa270b22/?at=main', $data['support']['source']); + self::assertIsArray($data); + self::assertSame('https://bitbucket.org/user/repo/src/937992d19d72b5116c3e8c4a04f960e5fa270b22/?at=main', $data['support']['source']); } public function testSupports(): void { - $this->assertTrue( + self::assertTrue( GitBitbucketDriver::supports($this->io, $this->config, 'https://bitbucket.org/user/repo.git') ); // should not be changed, see https://github.com/composer/composer/issues/9400 - $this->assertFalse( + self::assertFalse( GitBitbucketDriver::supports($this->io, $this->config, 'git@bitbucket.org:user/repo.git') ); - $this->assertFalse( + self::assertFalse( GitBitbucketDriver::supports($this->io, $this->config, 'https://github.com/user/repo.git') ); } diff --git a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php index b54e29ad113a..daa8b1aea562 100644 --- a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php @@ -76,7 +76,7 @@ public function testGetRootIdentifierFromRemoteLocalRepository(): void 'stdout' => $stdout, ]], true); - $this->assertSame('main', $driver->getRootIdentifier()); + self::assertSame('main', $driver->getRootIdentifier()); } public function testGetRootIdentifierFromRemote(): void @@ -109,7 +109,7 @@ public function testGetRootIdentifierFromRemote(): void 'stdout' => $stdout, ]]); - $this->assertSame('main', $driver->getRootIdentifier()); + self::assertSame('main', $driver->getRootIdentifier()); } public function testGetRootIdentifierFromLocalWithNetworkDisabled(): void @@ -134,7 +134,7 @@ public function testGetRootIdentifierFromLocalWithNetworkDisabled(): void 'stdout' => $stdout, ]]); - $this->assertSame('main', $driver->getRootIdentifier()); + self::assertSame('main', $driver->getRootIdentifier()); } public function testGetBranchesFilterInvalidBranchNames(): void @@ -160,7 +160,7 @@ public function testGetBranchesFilterInvalidBranchNames(): void ]]); $branches = $driver->getBranches(); - $this->assertSame([ + self::assertSame([ 'main' => '089681446ba44d6d9004350192486f2ceb4eaa06', '2.2' => '12681446ba44d6d9004350192486f2ceb4eaa06', ], $branches); @@ -174,7 +174,7 @@ public function testFileGetContentInvalidIdentifier(): void $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); - $this->assertNull($driver->getFileContent('file.txt', 'h')); + self::assertNull($driver->getFileContent('file.txt', 'h')); $driver->getFileContent('file.txt', '-h'); } diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index aa454e8946d5..f7e62ca643b3 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -91,18 +91,18 @@ public function testPrivateRepository(): void $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', [$identifier => $sha]); - $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + self::assertEquals('test_master', $gitHubDriver->getRootIdentifier()); $dist = $gitHubDriver->getDist($sha); self::assertIsArray($dist); - $this->assertEquals('zip', $dist['type']); - $this->assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); - $this->assertEquals('SOMESHA', $dist['reference']); + self::assertEquals('zip', $dist['type']); + self::assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); + self::assertEquals('SOMESHA', $dist['reference']); $source = $gitHubDriver->getSource($sha); - $this->assertEquals('git', $source['type']); - $this->assertEquals($repoSshUrl, $source['url']); - $this->assertEquals('SOMESHA', $source['reference']); + self::assertEquals('git', $source['type']); + self::assertEquals($repoSshUrl, $source['url']); + self::assertEquals('SOMESHA', $source['reference']); } public function testPublicRepository(): void @@ -134,18 +134,18 @@ public function testPublicRepository(): void $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', [$identifier => $sha]); - $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + self::assertEquals('test_master', $gitHubDriver->getRootIdentifier()); $dist = $gitHubDriver->getDist($sha); self::assertIsArray($dist); - $this->assertEquals('zip', $dist['type']); - $this->assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); - $this->assertEquals($sha, $dist['reference']); + self::assertEquals('zip', $dist['type']); + self::assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); + self::assertEquals($sha, $dist['reference']); $source = $gitHubDriver->getSource($sha); - $this->assertEquals('git', $source['type']); - $this->assertEquals($repoUrl, $source['url']); - $this->assertEquals($sha, $source['reference']); + self::assertEquals('git', $source['type']); + self::assertEquals($repoUrl, $source['url']); + self::assertEquals($sha, $source['reference']); } public function testPublicRepository2(): void @@ -180,23 +180,23 @@ public function testPublicRepository2(): void $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', [$identifier => $sha]); - $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + self::assertEquals('test_master', $gitHubDriver->getRootIdentifier()); $dist = $gitHubDriver->getDist($sha); self::assertIsArray($dist); - $this->assertEquals('zip', $dist['type']); - $this->assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); - $this->assertEquals($sha, $dist['reference']); + self::assertEquals('zip', $dist['type']); + self::assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); + self::assertEquals($sha, $dist['reference']); $source = $gitHubDriver->getSource($sha); - $this->assertEquals('git', $source['type']); - $this->assertEquals($repoUrl, $source['url']); - $this->assertEquals($sha, $source['reference']); + self::assertEquals('git', $source['type']); + self::assertEquals($repoUrl, $source['url']); + self::assertEquals($sha, $source['reference']); $data = $gitHubDriver->getComposerInformation($identifier); - $this->assertIsArray($data); - $this->assertArrayNotHasKey('abandoned', $data); + self::assertIsArray($data); + self::assertArrayNotHasKey('abandoned', $data); } public function testInvalidSupportData(): void @@ -233,8 +233,8 @@ public function testInvalidSupportData(): void $data = $gitHubDriver->getComposerInformation($identifier); - $this->assertIsArray($data); - $this->assertSame('https://github.com/composer/packagist/tree/feature/3.2-foo', $data['support']['source']); + self::assertIsArray($data); + self::assertSame('https://github.com/composer/packagist/tree/feature/3.2-foo', $data['support']['source']); } public function testPublicRepositoryArchived(): void @@ -271,8 +271,8 @@ public function testPublicRepositoryArchived(): void $data = $gitHubDriver->getComposerInformation($sha); - $this->assertIsArray($data); - $this->assertTrue($data['abandoned']); + self::assertIsArray($data); + self::assertTrue($data['abandoned']); } public function testPrivateRepositoryNoInteraction(): void @@ -326,23 +326,23 @@ public function testPrivateRepositoryNoInteraction(): void $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $process); $gitHubDriver->initialize(); - $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + self::assertEquals('test_master', $gitHubDriver->getRootIdentifier()); $dist = $gitHubDriver->getDist($sha); self::assertIsArray($dist); - $this->assertEquals('zip', $dist['type']); - $this->assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); - $this->assertEquals($sha, $dist['reference']); + self::assertEquals('zip', $dist['type']); + self::assertEquals('https://api.github.com/repos/composer/packagist/zipball/SOMESHA', $dist['url']); + self::assertEquals($sha, $dist['reference']); $source = $gitHubDriver->getSource($identifier); - $this->assertEquals('git', $source['type']); - $this->assertEquals($repoSshUrl, $source['url']); - $this->assertEquals($identifier, $source['reference']); + self::assertEquals('git', $source['type']); + self::assertEquals($repoSshUrl, $source['url']); + self::assertEquals($identifier, $source['reference']); $source = $gitHubDriver->getSource($sha); - $this->assertEquals('git', $source['type']); - $this->assertEquals($repoSshUrl, $source['url']); - $this->assertEquals($sha, $source['reference']); + self::assertEquals('git', $source['type']); + self::assertEquals($repoSshUrl, $source['url']); + self::assertEquals($sha, $source['reference']); } /** @@ -384,7 +384,7 @@ public function testSupports(bool $expected, string $repoUrl): void { $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $this->assertSame($expected, GitHubDriver::supports($io, $this->config, $repoUrl)); + self::assertSame($expected, GitHubDriver::supports($io, $this->config, $repoUrl)); } /** @@ -426,7 +426,7 @@ public function testGetEmptyFileContent(): void $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $this->getProcessExecutorMock()); $gitHubDriver->initialize(); - $this->assertSame('', $gitHubDriver->getFileContent('composer.json', 'main')); + self::assertSame('', $gitHubDriver->getFileContent('composer.json', 'main')); } /** diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index 9d163f567ff2..acc1df9e35de 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -118,10 +118,10 @@ public function testInitialize(string $url, string $apiUrl): GitLabDriver $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); - $this->assertEquals('mymaster', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); - $this->assertEquals('git@gitlab.com:mygroup/myproject.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); - $this->assertEquals('https://gitlab.com/mygroup/myproject', $driver->getUrl()); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals('mymaster', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); + self::assertEquals('git@gitlab.com:mygroup/myproject.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); + self::assertEquals('https://gitlab.com/mygroup/myproject', $driver->getUrl()); return $driver; } @@ -158,10 +158,10 @@ public function testInitializePublicProject(string $url, string $apiUrl): GitLab $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); - $this->assertEquals('mymaster', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); - $this->assertEquals('https://gitlab.com/mygroup/myproject.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); - $this->assertEquals('https://gitlab.com/mygroup/myproject', $driver->getUrl()); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals('mymaster', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); + self::assertEquals('https://gitlab.com/mygroup/myproject.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); + self::assertEquals('https://gitlab.com/mygroup/myproject', $driver->getUrl()); return $driver; } @@ -197,10 +197,10 @@ public function testInitializePublicProjectAsAnonymous(string $url, string $apiU $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); - $this->assertEquals('mymaster', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); - $this->assertEquals('https://gitlab.com/mygroup/myproject.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); - $this->assertEquals('https://gitlab.com/mygroup/myproject', $driver->getUrl()); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals('mymaster', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); + self::assertEquals('https://gitlab.com/mygroup/myproject.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); + self::assertEquals('https://gitlab.com/mygroup/myproject', $driver->getUrl()); return $driver; } @@ -238,10 +238,10 @@ public function testInitializeWithPortNumber(): void $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); - $this->assertEquals('1.0.x', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); - $this->assertEquals($url.'.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); - $this->assertEquals($url, $driver->getUrl()); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals('1.0.x', $driver->getRootIdentifier(), 'Root identifier is the default branch in GitLab'); + self::assertEquals($url.'.git', $driver->getRepositoryUrl(), 'The repository URL is the SSH one by default'); + self::assertEquals($url, $driver->getUrl()); } public function testInvalidSupportData(): void @@ -256,8 +256,8 @@ public function testInvalidSupportData(): void $data = $driver->getComposerInformation('main'); - $this->assertIsArray($data); - $this->assertSame('https://gitlab.com/mygroup/myproject/-/tree/main', $data['support']['source']); + self::assertIsArray($data); + self::assertSame('https://gitlab.com/mygroup/myproject/-/tree/main', $data['support']['source']); } public function testGetDist(): void @@ -272,7 +272,7 @@ public function testGetDist(): void 'shasum' => '', ]; - $this->assertEquals($expected, $driver->getDist($reference)); + self::assertEquals($expected, $driver->getDist($reference)); } public function testGetSource(): void @@ -286,7 +286,7 @@ public function testGetSource(): void 'reference' => $reference, ]; - $this->assertEquals($expected, $driver->getSource($reference)); + self::assertEquals($expected, $driver->getSource($reference)); } public function testGetSource_GivenPublicProject(): void @@ -300,7 +300,7 @@ public function testGetSource_GivenPublicProject(): void 'reference' => $reference, ]; - $this->assertEquals($expected, $driver->getSource($reference)); + self::assertEquals($expected, $driver->getSource($reference)); } public function testGetTags(): void @@ -340,8 +340,8 @@ public function testGetTags(): void 'v2.0.0' => '8e8f60b3ec86d63733db3bd6371117a758027ec6', ]; - $this->assertEquals($expected, $driver->getTags()); - $this->assertEquals($expected, $driver->getTags(), 'Tags are cached'); + self::assertEquals($expected, $driver->getTags()); + self::assertEquals($expected, $driver->getTags(), 'Tags are cached'); } public function testGetPaginatedRefs(): void @@ -402,8 +402,8 @@ public function testGetPaginatedRefs(): void 'stagingdupe' => '502cffe49f136443f2059803f2e7192d1ac066cd', ]; - $this->assertEquals($expected, $driver->getBranches()); - $this->assertEquals($expected, $driver->getBranches(), 'Branches are cached'); + self::assertEquals($expected, $driver->getBranches()); + self::assertEquals($expected, $driver->getBranches(), 'Branches are cached'); } public function testGetBranches(): void @@ -444,8 +444,8 @@ public function testGetBranches(): void 'staging' => '502cffe49f136443f2059803f2e7192d1ac066cd', ]; - $this->assertEquals($expected, $driver->getBranches()); - $this->assertEquals($expected, $driver->getBranches(), 'Branches are cached'); + self::assertEquals($expected, $driver->getBranches()); + self::assertEquals($expected, $driver->getBranches(), 'Branches are cached'); } /** @@ -454,7 +454,7 @@ public function testGetBranches(): void */ public function testSupports(string $url, bool $expected): void { - $this->assertSame($expected, GitLabDriver::supports($this->io, $this->config, $url)); + self::assertSame($expected, GitLabDriver::supports($this->io, $this->config, $url)); } public static function dataForTestSupports(): array @@ -510,7 +510,7 @@ public function testGitlabSubDirectory(): void $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); } public function testGitlabSubGroup(): void @@ -542,7 +542,7 @@ public function testGitlabSubGroup(): void $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); } public function testGitlabSubDirectorySubGroup(): void @@ -574,7 +574,7 @@ public function testGitlabSubDirectorySubGroup(): void $driver = new GitLabDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + self::assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); } public function testForwardsOptions(): void @@ -645,7 +645,7 @@ public function testProtocolOverrideRepositoryUrlGeneration(): void $config->merge(['config' => ['gitlab-protocol' => 'http']]); $driver = new GitLabDriver(['url' => $url], $this->io, $config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals('https://gitlab.com/mygroup/myproject.git', $driver->getRepositoryUrl(), 'Repository URL matches config request for http not git'); + self::assertEquals('https://gitlab.com/mygroup/myproject.git', $driver->getRepositoryUrl(), 'Repository URL matches config request for http not git'); } /** diff --git a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php index f1f047de5950..42d9f5b4acc8 100644 --- a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php @@ -50,7 +50,7 @@ protected function tearDown(): void */ public function testSupports(string $repositoryUrl): void { - $this->assertTrue( + self::assertTrue( HgDriver::supports($this->io, $this->config, $repositoryUrl) ); } @@ -93,7 +93,7 @@ public function testGetBranchesFilterInvalidBranchNames(): void ]]); $branches = $driver->getBranches(); - $this->assertSame([ + self::assertSame([ 'help' => 'dbf6c8acb641', 'default' => 'dbf6c8acb640', ], $branches); @@ -106,7 +106,7 @@ public function testFileGetContentInvalidIdentifier(): void $process = $this->getProcessExecutorMock(); $driver = new HgDriver(['url' => 'https://example.org/acme.git'], $this->io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); - $this->assertNull($driver->getFileContent('file.txt', 'h')); + self::assertNull($driver->getFileContent('file.txt', 'h')); $driver->getFileContent('file.txt', '-h'); } diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php index b6165ebab155..357a544a4be2 100644 --- a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php @@ -132,9 +132,9 @@ public function testInitializeCapturesVariablesFromRepoConfig(): void { $driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); - $this->assertEquals(self::TEST_URL, $driver->getUrl()); - $this->assertEquals(self::TEST_DEPOT, $driver->getDepot()); - $this->assertEquals(self::TEST_BRANCH, $driver->getBranch()); + self::assertEquals(self::TEST_URL, $driver->getUrl()); + self::assertEquals(self::TEST_DEPOT, $driver->getDepot()); + self::assertEquals(self::TEST_BRANCH, $driver->getBranch()); } public function testInitializeLogsInAndConnectsClient(): void @@ -157,7 +157,7 @@ public function testHasComposerFileReturnsFalseOnNoComposerFile(): void $this->perforce->expects($this->any())->method('getComposerInformation')->with($this->equalTo($formatted_depot_path))->will($this->returnValue([])); $this->driver->initialize(); $result = $this->driver->hasComposerFile($identifier); - $this->assertFalse($result); + self::assertFalse($result); } /** @@ -171,7 +171,7 @@ public function testHasComposerFileReturnsTrueWithOneOrMoreComposerFiles(): void $this->perforce->expects($this->any())->method('getComposerInformation')->with($this->equalTo($formatted_depot_path))->will($this->returnValue([''])); $this->driver->initialize(); $result = $this->driver->hasComposerFile($identifier); - $this->assertTrue($result); + self::assertTrue($result); } /** @@ -182,7 +182,7 @@ public function testHasComposerFileReturnsTrueWithOneOrMoreComposerFiles(): void public function testSupportsReturnsFalseNoDeepCheck(): void { $this->expectOutputString(''); - $this->assertFalse(PerforceDriver::supports($this->io, $this->config, 'existing.url')); + self::assertFalse(PerforceDriver::supports($this->io, $this->config, 'existing.url')); } public function testCleanup(): void diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 3232f2d4e8c1..d8a76f702315 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -101,6 +101,6 @@ public function testSupport(string $url, bool $assertion): void { $config = new Config(); $result = SvnDriver::supports($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config, $url); - $this->assertEquals($assertion, $result); + self::assertEquals($assertion, $result); } } diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php index dc219371c513..692e716b87b9 100644 --- a/tests/Composer/Test/Repository/VcsRepositoryTest.php +++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php @@ -177,6 +177,6 @@ public function testLoadVersions(): void } } - $this->assertEmpty($expected, 'Missing versions: '.implode(', ', array_keys($expected))); + self::assertEmpty($expected, 'Missing versions: '.implode(', ', array_keys($expected))); } } diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php index 1647f7169ebf..f62b96cb4c78 100644 --- a/tests/Composer/Test/Script/EventTest.php +++ b/tests/Composer/Test/Script/EventTest.php @@ -28,15 +28,14 @@ public function testEventSetsOriginatingEvent(): void $scriptEvent = new Event('test', $composer, $io, true); - $this->assertNull( + self::assertNull( $scriptEvent->getOriginatingEvent(), 'originatingEvent is initialized as null' ); $scriptEvent->setOriginatingEvent($originatingEvent); - // @phpstan-ignore staticMethod.dynamicCall - $this->assertSame( + self::assertSame( $originatingEvent, $scriptEvent->getOriginatingEvent(), 'getOriginatingEvent() SHOULD return test event' @@ -55,13 +54,13 @@ public function testEventCalculatesNestedOriginatingEvent(): void $scriptEvent = new Event('test', $composer, $io, true); $scriptEvent->setOriginatingEvent($intermediateEvent); - $this->assertNotSame( + self::assertNotSame( $intermediateEvent, $scriptEvent->getOriginatingEvent(), 'getOriginatingEvent() SHOULD NOT return intermediate events' ); - $this->assertSame( + self::assertSame( $originatingEvent, $scriptEvent->getOriginatingEvent(), 'getOriginatingEvent() SHOULD return upper-most event' diff --git a/tests/Composer/Test/Util/AuthHelperTest.php b/tests/Composer/Test/Util/AuthHelperTest.php index ee5802321c90..0be55944fcc0 100644 --- a/tests/Composer/Test/Util/AuthHelperTest.php +++ b/tests/Composer/Test/Util/AuthHelperTest.php @@ -57,7 +57,7 @@ public function testAddAuthenticationHeaderWithoutAuthCredentials(): void ->with($origin) ->willReturn(false); - $this->assertSame( + self::assertSame( $headers, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -80,7 +80,7 @@ public function testAddAuthenticationHeaderWithBearerPassword(): void $expectedHeaders = array_merge($headers, ['Authorization: Bearer ' . $auth['username']]); - $this->assertSame( + self::assertSame( $expectedHeaders, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -107,7 +107,7 @@ public function testAddAuthenticationHeaderWithGithubToken(): void $expectedHeaders = array_merge($headers, ['Authorization: token ' . $auth['username']]); - $this->assertSame( + self::assertSame( $expectedHeaders, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -139,7 +139,7 @@ public function testAddAuthenticationHeaderWithGitlabOathToken(): void $expectedHeaders = array_merge($headers, ['Authorization: Bearer ' . $auth['username']]); - $this->assertSame( + self::assertSame( $expectedHeaders, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -182,7 +182,7 @@ public function testAddAuthenticationHeaderWithGitlabPrivateToken(string $passwo $expectedHeaders = array_merge($headers, ['PRIVATE-TOKEN: ' . $auth['username']]); - $this->assertSame( + self::assertSame( $expectedHeaders, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -214,7 +214,7 @@ public function testAddAuthenticationHeaderWithBitbucketOathToken(): void $expectedHeaders = array_merge($headers, ['Authorization: Bearer ' . $auth['password']]); - $this->assertSame( + self::assertSame( $expectedHeaders, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -250,7 +250,7 @@ public function testAddAuthenticationHeaderWithBitbucketPublicUrl(string $url): ->with('gitlab-domains') ->willReturn([]); - $this->assertSame( + self::assertSame( $headers, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -320,7 +320,7 @@ public function testAddAuthenticationHeaderWithBasicHttpAuthentication(string $u ['Authorization: Basic ' . base64_encode($auth['username'] . ':' . $auth['password'])] ); - $this->assertSame( + self::assertSame( $expectedHeaders, $this->authHelper->addAuthenticationHeader($headers, $origin, $url) ); @@ -331,12 +331,12 @@ public function testAddAuthenticationHeaderWithBasicHttpAuthentication(string $u */ public function testIsPublicBitBucketDownloadWithBitbucketPublicUrl(string $url): void { - $this->assertTrue($this->authHelper->isPublicBitBucketDownload($url)); + self::assertTrue($this->authHelper->isPublicBitBucketDownload($url)); } public function testIsPublicBitBucketDownloadWithNonBitbucketPublicUrl(): void { - $this->assertFalse( + self::assertFalse( $this->authHelper->isPublicBitBucketDownload( 'https://bitbucket.org/site/oauth2/authorize' ) @@ -592,12 +592,12 @@ function ($repositoryName) use (&$getAuthenticationReturnValues) { $result1 = $this->authHelper->promptAuthIfNeeded('https://bitbucket.org/workspace/repo1/get/hash1.zip', $origin, 401, 'HTTP/2 401 '); $result2 = $this->authHelper->promptAuthIfNeeded('https://bitbucket.org/workspace/repo2/get/hash2.zip', $origin, 401, 'HTTP/2 401 '); - $this->assertSame( + self::assertSame( $expectedResult, $result1 ); - $this->assertSame( + self::assertSame( $expectedResult, $result2 ); diff --git a/tests/Composer/Test/Util/BitbucketTest.php b/tests/Composer/Test/Util/BitbucketTest.php index dfaac651dff9..8b977e56f45f 100644 --- a/tests/Composer/Test/Util/BitbucketTest.php +++ b/tests/Composer/Test/Util/BitbucketTest.php @@ -102,7 +102,7 @@ public function testRequestAccessTokenWithValidOAuthConsumer(): void $this->setExpectationsForStoringAccessToken(); - $this->assertEquals( + self::assertEquals( $this->token, $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret) ); @@ -124,7 +124,7 @@ public function testRequestAccessTokenWithValidOAuthConsumerAndValidStoredAccess ] ); - $this->assertEquals( + self::assertEquals( $this->token, $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret) ); @@ -178,7 +178,7 @@ public function testRequestAccessTokenWithValidOAuthConsumerAndExpiredAccessToke $this->setExpectationsForStoringAccessToken(); - $this->assertEquals( + self::assertEquals( $this->token, $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret) ); @@ -222,7 +222,7 @@ public function testRequestAccessTokenWithUsernameAndPassword(): void ->with('bitbucket-oauth') ->willReturn(null); - $this->assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password)); + self::assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password)); } public function testRequestAccessTokenWithUsernameAndPasswordWithUnauthorizedResponse(): void @@ -252,7 +252,7 @@ public function testRequestAccessTokenWithUsernameAndPasswordWithUnauthorizedRes ) ->willThrowException(new \Composer\Downloader\TransportException('HTTP/1.1 401 UNAUTHORIZED', 401)); - $this->assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password)); + self::assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password)); } public function testRequestAccessTokenWithUsernameAndPasswordWithNotFoundResponse(): void @@ -315,7 +315,7 @@ public function testUsernamePasswordAuthenticationFlow(): void $this->setExpectationsForStoringAccessToken(true); - $this->assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); + self::assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); } public function testAuthorizeOAuthInteractivelyWithEmptyUsername(): void @@ -329,7 +329,7 @@ public function testAuthorizeOAuthInteractivelyWithEmptyUsername(): void ['ask' => 'Consumer Key (hidden): ', 'reply' => ''], ]); - $this->assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); + self::assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); } public function testAuthorizeOAuthInteractivelyWithEmptyPassword(): void @@ -345,7 +345,7 @@ public function testAuthorizeOAuthInteractivelyWithEmptyPassword(): void ['ask' => 'Consumer Secret (hidden): ', 'reply' => ''], ]); - $this->assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); + self::assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); } public function testAuthorizeOAuthInteractivelyWithRequestAccessTokenFailure(): void @@ -378,7 +378,7 @@ public function testAuthorizeOAuthInteractivelyWithRequestAccessTokenFailure(): ) ); - $this->assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); + self::assertFalse($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); } private function setExpectationsForStoringAccessToken(bool $removeBasicAuth = false): void @@ -418,7 +418,7 @@ private function setExpectationsForStoringAccessToken(bool $removeBasicAuth = fa public function testGetTokenWithoutAccessToken(): void { - $this->assertSame('', $this->bitbucket->getToken()); + self::assertSame('', $this->bitbucket->getToken()); } /** @@ -426,12 +426,12 @@ public function testGetTokenWithoutAccessToken(): void */ public function testGetTokenWithAccessToken(Bitbucket $bitbucket): void { - $this->assertSame($this->token, $bitbucket->getToken()); + self::assertSame($this->token, $bitbucket->getToken()); } public function testAuthorizeOAuthWithWrongOriginUrl(): void { - $this->assertFalse($this->bitbucket->authorizeOAuth('non-' . $this->origin)); + self::assertFalse($this->bitbucket->authorizeOAuth('non-' . $this->origin)); } public function testAuthorizeOAuthWithoutAvailableGitConfigToken(): void @@ -441,7 +441,7 @@ public function testAuthorizeOAuthWithoutAvailableGitConfigToken(): void $bitbucket = new Bitbucket($this->io, $this->config, $process, $this->httpDownloader, $this->time); - $this->assertFalse($bitbucket->authorizeOAuth($this->origin)); + self::assertFalse($bitbucket->authorizeOAuth($this->origin)); } public function testAuthorizeOAuthWithAvailableGitConfigToken(): void @@ -450,6 +450,6 @@ public function testAuthorizeOAuthWithAvailableGitConfigToken(): void $bitbucket = new Bitbucket($this->io, $this->config, $process, $this->httpDownloader, $this->time); - $this->assertTrue($bitbucket->authorizeOAuth($this->origin)); + self::assertTrue($bitbucket->authorizeOAuth($this->origin)); } } diff --git a/tests/Composer/Test/Util/ConfigValidatorTest.php b/tests/Composer/Test/Util/ConfigValidatorTest.php index c6d2bbfde3da..885390efc1a4 100644 --- a/tests/Composer/Test/Util/ConfigValidatorTest.php +++ b/tests/Composer/Test/Util/ConfigValidatorTest.php @@ -29,7 +29,7 @@ public function testConfigValidatorCommitRefWarning(): void $configValidator = new ConfigValidator(new NullIO()); [, , $warnings] = $configValidator->validate(__DIR__ . '/Fixtures/composer_commit-ref.json'); - $this->assertContains( + self::assertContains( 'The package "some/package" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', $warnings ); @@ -40,7 +40,7 @@ public function testConfigValidatorWarnsOnScriptDescriptionForNonexistentScript( $configValidator = new ConfigValidator(new NullIO()); [, , $warnings] = $configValidator->validate(__DIR__ . '/Fixtures/composer_scripts-descriptions.json'); - $this->assertContains( + self::assertContains( 'Description for non-existent script "phpcsxxx" found in "scripts-descriptions"', $warnings ); @@ -51,7 +51,7 @@ public function testConfigValidatorWarnsOnScriptAliasForNonexistentScript(): voi $configValidator = new ConfigValidator(new NullIO()); [, , $warnings] = $configValidator->validate(__DIR__ . '/Fixtures/composer_scripts-aliases.json'); - $this->assertContains( + self::assertContains( 'Aliases for non-existent script "phpcsxxx" found in "scripts-aliases"', $warnings ); @@ -62,15 +62,15 @@ public function testConfigValidatorWarnsOnUnnecessaryProvideReplace(): void $configValidator = new ConfigValidator(new NullIO()); [, , $warnings] = $configValidator->validate(__DIR__ . '/Fixtures/composer_provide-replace-requirements.json'); - $this->assertContains( + self::assertContains( 'The package a/a in require is also listed in provide which satisfies the requirement. Remove it from provide if you wish to install it.', $warnings ); - $this->assertContains( + self::assertContains( 'The package b/b in require is also listed in replace which satisfies the requirement. Remove it from replace if you wish to install it.', $warnings ); - $this->assertContains( + self::assertContains( 'The package c/c in require-dev is also listed in provide which satisfies the requirement. Remove it from provide if you wish to install it.', $warnings ); diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index 83a28d75b5a1..cfc0bacaa5fb 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -57,7 +57,7 @@ protected function tearDown(): void public function testFindShortestPathCode(string $a, string $b, bool $directory, string $expected, bool $static = false): void { $fs = new Filesystem; - $this->assertEquals($expected, $fs->findShortestPathCode($a, $b, $directory, $static)); + self::assertEquals($expected, $fs->findShortestPathCode($a, $b, $directory, $static)); } public static function providePathCouplesAsCode(): array @@ -116,7 +116,7 @@ public static function providePathCouplesAsCode(): array public function testFindShortestPath(string $a, string $b, string $expected, bool $directory = false): void { $fs = new Filesystem; - $this->assertEquals($expected, $fs->findShortestPath($a, $b, $directory)); + self::assertEquals($expected, $fs->findShortestPath($a, $b, $directory)); } public static function providePathCouples(): array @@ -171,8 +171,8 @@ public function testRemoveDirectoryPhp(): void file_put_contents($this->workingDir . "/level1/level2/hello.txt", "hello world"); $fs = new Filesystem; - $this->assertTrue($fs->removeDirectoryPhp($this->workingDir)); - $this->assertFileDoesNotExist($this->workingDir . "/level1/level2/hello.txt"); + self::assertTrue($fs->removeDirectoryPhp($this->workingDir)); + self::assertFileDoesNotExist($this->workingDir . "/level1/level2/hello.txt"); } public function testFileSize(): void @@ -180,7 +180,7 @@ public function testFileSize(): void file_put_contents($this->testFile, 'Hello'); $fs = new Filesystem; - $this->assertGreaterThanOrEqual(5, $fs->size($this->testFile)); + self::assertGreaterThanOrEqual(5, $fs->size($this->testFile)); } public function testDirectorySize(): void @@ -190,7 +190,7 @@ public function testDirectorySize(): void file_put_contents($this->workingDir."/file2.txt", 'World'); $fs = new Filesystem; - $this->assertGreaterThanOrEqual(10, $fs->size($this->workingDir)); + self::assertGreaterThanOrEqual(10, $fs->size($this->workingDir)); } /** @@ -199,7 +199,7 @@ public function testDirectorySize(): void public function testNormalizePath(string $expected, string $actual): void { $fs = new Filesystem; - $this->assertEquals($expected, $fs->normalizePath($actual)); + self::assertEquals($expected, $fs->normalizePath($actual)); } public static function provideNormalizedPaths(): array @@ -251,8 +251,8 @@ public function testUnlinkSymlinkedDirectory(): void $fs = new Filesystem(); $result = $fs->unlink($symlinked); - $this->assertTrue($result); - $this->assertFileDoesNotExist($symlinked); + self::assertTrue($result); + self::assertFileDoesNotExist($symlinked); } /** @@ -283,9 +283,9 @@ public function testRemoveSymlinkedDirectoryWithTrailingSlash(): void $fs = new Filesystem(); $result = $fs->removeDirectory($symlinkedTrailingSlash); - $this->assertTrue($result); - $this->assertFileDoesNotExist($symlinkedTrailingSlash); - $this->assertFileDoesNotExist($symlinked); + self::assertTrue($result); + self::assertFileDoesNotExist($symlinkedTrailingSlash); + self::assertFileDoesNotExist($symlinked); } public function testJunctions(): void @@ -295,8 +295,8 @@ public function testJunctions(): void // Non-Windows systems do not support this and will return false on all tests, and an exception on creation if (!defined('PHP_WINDOWS_VERSION_BUILD')) { - $this->assertFalse($fs->isJunction($this->workingDir)); - $this->assertFalse($fs->removeJunction($this->workingDir)); + self::assertFalse($fs->isJunction($this->workingDir)); + self::assertFalse($fs->removeJunction($this->workingDir)); self::expectException('LogicException'); self::expectExceptionMessage('not available on non-Windows platform'); } @@ -306,16 +306,16 @@ public function testJunctions(): void // Create and detect junction $fs->junction($target, $junction); - $this->assertTrue($fs->isJunction($junction), $junction . ': is a junction'); - $this->assertFalse($fs->isJunction($target), $target . ': is not a junction'); - $this->assertTrue($fs->isJunction($target . '/../../junction'), $target . '/../../junction: is a junction'); - $this->assertFalse($fs->isJunction($junction . '/../real'), $junction . '/../real: is not a junction'); - $this->assertTrue($fs->isJunction($junction . '/../junction'), $junction . '/../junction: is a junction'); + self::assertTrue($fs->isJunction($junction), $junction . ': is a junction'); + self::assertFalse($fs->isJunction($target), $target . ': is not a junction'); + self::assertTrue($fs->isJunction($target . '/../../junction'), $target . '/../../junction: is a junction'); + self::assertFalse($fs->isJunction($junction . '/../real'), $junction . '/../real: is not a junction'); + self::assertTrue($fs->isJunction($junction . '/../junction'), $junction . '/../junction: is a junction'); // Remove junction - $this->assertDirectoryExists($junction, $junction . ' is a directory'); - $this->assertTrue($fs->removeJunction($junction), $junction . ' has been removed'); - $this->assertDirectoryDoesNotExist($junction, $junction . ' is not a directory'); + self::assertDirectoryExists($junction, $junction . ' is a directory'); + self::assertTrue($fs->removeJunction($junction), $junction . ' has been removed'); + self::assertDirectoryDoesNotExist($junction, $junction . ' is not a directory'); } public function testOverrideJunctions(): void @@ -335,19 +335,19 @@ public function testOverrideJunctions(): void $fs->junction($old_target, $junction); $fs->junction($target, $junction); - $this->assertTrue($fs->isJunction($junction), $junction.': is a junction'); - $this->assertTrue($fs->isJunction($target.'/../../junction'), $target.'/../../junction: is a junction'); + self::assertTrue($fs->isJunction($junction), $junction.': is a junction'); + self::assertTrue($fs->isJunction($target.'/../../junction'), $target.'/../../junction: is a junction'); //Remove junction - $this->assertTrue($fs->removeJunction($junction), $junction . ' has been removed'); + self::assertTrue($fs->removeJunction($junction), $junction . ' has been removed'); // Override broken junction $fs->junction($old_target, $junction); $fs->removeDirectory($old_target); $fs->junction($target, $junction); - $this->assertTrue($fs->isJunction($junction), $junction.': is a junction'); - $this->assertTrue($fs->isJunction($target.'/../../junction'), $target.'/../../junction: is a junction'); + self::assertTrue($fs->isJunction($junction), $junction.': is a junction'); + self::assertTrue($fs->isJunction($target.'/../../junction'), $target.'/../../junction: is a junction'); } public function testCopy(): void @@ -362,17 +362,17 @@ public function testCopy(): void $fs = new Filesystem(); $result1 = $fs->copy($this->workingDir . '/foo', $this->workingDir . '/foop'); - $this->assertTrue($result1, 'Copying directory failed.'); - $this->assertDirectoryExists($this->workingDir . '/foop', 'Not a directory: ' . $this->workingDir . '/foop'); - $this->assertDirectoryExists($this->workingDir . '/foop/bar', 'Not a directory: ' . $this->workingDir . '/foop/bar'); - $this->assertDirectoryExists($this->workingDir . '/foop/baz', 'Not a directory: ' . $this->workingDir . '/foop/baz'); - $this->assertFileExists($this->workingDir . '/foop/foo.file', 'Not a file: ' . $this->workingDir . '/foop/foo.file'); - $this->assertFileExists($this->workingDir . '/foop/bar/foobar.file', 'Not a file: ' . $this->workingDir . '/foop/bar/foobar.file'); - $this->assertFileExists($this->workingDir . '/foop/baz/foobaz.file', 'Not a file: ' . $this->workingDir . '/foop/baz/foobaz.file'); + self::assertTrue($result1, 'Copying directory failed.'); + self::assertDirectoryExists($this->workingDir . '/foop', 'Not a directory: ' . $this->workingDir . '/foop'); + self::assertDirectoryExists($this->workingDir . '/foop/bar', 'Not a directory: ' . $this->workingDir . '/foop/bar'); + self::assertDirectoryExists($this->workingDir . '/foop/baz', 'Not a directory: ' . $this->workingDir . '/foop/baz'); + self::assertFileExists($this->workingDir . '/foop/foo.file', 'Not a file: ' . $this->workingDir . '/foop/foo.file'); + self::assertFileExists($this->workingDir . '/foop/bar/foobar.file', 'Not a file: ' . $this->workingDir . '/foop/bar/foobar.file'); + self::assertFileExists($this->workingDir . '/foop/baz/foobaz.file', 'Not a file: ' . $this->workingDir . '/foop/baz/foobaz.file'); $result2 = $fs->copy($this->testFile, $this->workingDir . '/testfile.file'); - $this->assertTrue($result2); - $this->assertFileExists($this->workingDir . '/testfile.file'); + self::assertTrue($result2); + self::assertFileExists($this->workingDir . '/testfile.file'); } public function testCopyThenRemove(): void @@ -387,14 +387,14 @@ public function testCopyThenRemove(): void $fs = new Filesystem(); $fs->copyThenRemove($this->testFile, $this->workingDir . '/testfile.file'); - $this->assertFileDoesNotExist($this->testFile, 'Still a file: ' . $this->testFile); + self::assertFileDoesNotExist($this->testFile, 'Still a file: ' . $this->testFile); $fs->copyThenRemove($this->workingDir . '/foo', $this->workingDir . '/foop'); - $this->assertFileDoesNotExist($this->workingDir . '/foo/baz/foobaz.file', 'Still a file: ' . $this->workingDir . '/foo/baz/foobaz.file'); - $this->assertFileDoesNotExist($this->workingDir . '/foo/bar/foobar.file', 'Still a file: ' . $this->workingDir . '/foo/bar/foobar.file'); - $this->assertFileDoesNotExist($this->workingDir . '/foo/foo.file', 'Still a file: ' . $this->workingDir . '/foo/foo.file'); - $this->assertDirectoryDoesNotExist($this->workingDir . '/foo/baz', 'Still a directory: ' . $this->workingDir . '/foo/baz'); - $this->assertDirectoryDoesNotExist($this->workingDir . '/foo/bar', 'Still a directory: ' . $this->workingDir . '/foo/bar'); - $this->assertDirectoryDoesNotExist($this->workingDir . '/foo', 'Still a directory: ' . $this->workingDir . '/foo'); + self::assertFileDoesNotExist($this->workingDir . '/foo/baz/foobaz.file', 'Still a file: ' . $this->workingDir . '/foo/baz/foobaz.file'); + self::assertFileDoesNotExist($this->workingDir . '/foo/bar/foobar.file', 'Still a file: ' . $this->workingDir . '/foo/bar/foobar.file'); + self::assertFileDoesNotExist($this->workingDir . '/foo/foo.file', 'Still a file: ' . $this->workingDir . '/foo/foo.file'); + self::assertDirectoryDoesNotExist($this->workingDir . '/foo/baz', 'Still a directory: ' . $this->workingDir . '/foo/baz'); + self::assertDirectoryDoesNotExist($this->workingDir . '/foo/bar', 'Still a directory: ' . $this->workingDir . '/foo/bar'); + self::assertDirectoryDoesNotExist($this->workingDir . '/foo', 'Still a directory: ' . $this->workingDir . '/foo'); } } diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index d9e0e541209b..7ef74b934199 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -55,7 +55,7 @@ public function testUsernamePasswordAuthenticationFlow(): void $github = new GitHub($io, $config, null, $httpDownloader); - $this->assertTrue($github->authorizeOAuthInteractively($this->origin, $this->message)); + self::assertTrue($github->authorizeOAuthInteractively($this->origin, $this->message)); } public function testUsernamePasswordFailure(): void @@ -80,7 +80,7 @@ public function testUsernamePasswordFailure(): void $github = new GitHub($io, $config, null, $httpDownloader); - $this->assertFalse($github->authorizeOAuthInteractively($this->origin)); + self::assertFalse($github->authorizeOAuthInteractively($this->origin)); } /** diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 5c8cc9443e5d..095cd747a838 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -57,7 +57,7 @@ public function testUsernamePasswordAuthenticationFlow(): void $gitLab = new GitLab($io, $config, null, $httpDownloader); - $this->assertTrue($gitLab->authorizeOAuthInteractively('http', $this->origin, $this->message)); + self::assertTrue($gitLab->authorizeOAuthInteractively('http', $this->origin, $this->message)); } public function testUsernamePasswordFailure(): void diff --git a/tests/Composer/Test/Util/GitTest.php b/tests/Composer/Test/Util/GitTest.php index 9a0ec1e8feb8..71e064544796 100644 --- a/tests/Composer/Test/Util/GitTest.php +++ b/tests/Composer/Test/Util/GitTest.php @@ -47,7 +47,7 @@ protected function setUp(): void public function testRunCommandPublicGitHubRepositoryNotInitialClone(string $protocol, string $expectedUrl): void { $commandCallable = function ($url) use ($expectedUrl): string { - $this->assertSame($expectedUrl, $url); + self::assertSame($expectedUrl, $url); return 'git command'; }; @@ -72,7 +72,7 @@ public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteracti self::expectException('RuntimeException'); $commandCallable = function ($url): string { - $this->assertSame('https://github.com/acme/repo', $url); + self::assertSame('https://github.com/acme/repo', $url); return 'git command'; }; diff --git a/tests/Composer/Test/Util/HttpDownloaderTest.php b/tests/Composer/Test/Util/HttpDownloaderTest.php index 294063902a1e..aa550b500d29 100644 --- a/tests/Composer/Test/Util/HttpDownloaderTest.php +++ b/tests/Composer/Test/Util/HttpDownloaderTest.php @@ -49,7 +49,7 @@ public function testCaptureAuthenticationParamsFromUrl(): void try { $fs->get('https://user:pass@github.com/composer/composer/404'); } catch (\Composer\Downloader\TransportException $e) { - $this->assertNotEquals(200, $e->getCode()); + self::assertNotEquals(200, $e->getCode()); } } @@ -57,7 +57,7 @@ public function testOutputWarnings(): void { $io = new BufferIO(); HttpDownloader::outputWarnings($io, '$URL', []); - $this->assertSame('', $io->getOutput()); + self::assertSame('', $io->getOutput()); HttpDownloader::outputWarnings($io, '$URL', [ 'warning' => 'old warning msg', 'warning-versions' => '>=2.0', @@ -74,7 +74,7 @@ public function testOutputWarnings(): void ]); // the tag are consumed by the OutputFormatter, but not as that is not a default output format - $this->assertSame( + self::assertSame( 'Warning from $URL: old warning msg'.PHP_EOL. 'Info from $URL: old info msg'.PHP_EOL. 'Warning from $URL: visible warning'.PHP_EOL. diff --git a/tests/Composer/Test/Util/IniHelperTest.php b/tests/Composer/Test/Util/IniHelperTest.php index fbfde3a3d46a..9750ab8c092c 100644 --- a/tests/Composer/Test/Util/IniHelperTest.php +++ b/tests/Composer/Test/Util/IniHelperTest.php @@ -33,8 +33,8 @@ public function testWithNoIni(): void ]; $this->setEnv($paths); - $this->assertStringContainsString('does not exist', IniHelper::getMessage()); - $this->assertEquals($paths, IniHelper::getAll()); + self::assertStringContainsString('does not exist', IniHelper::getMessage()); + self::assertEquals($paths, IniHelper::getAll()); } public function testWithLoadedIniOnly(): void @@ -44,7 +44,7 @@ public function testWithLoadedIniOnly(): void ]; $this->setEnv($paths); - $this->assertStringContainsString('loaded.ini', IniHelper::getMessage()); + self::assertStringContainsString('loaded.ini', IniHelper::getMessage()); } public function testWithLoadedIniAndAdditional(): void @@ -56,8 +56,8 @@ public function testWithLoadedIniAndAdditional(): void ]; $this->setEnv($paths); - $this->assertStringContainsString('multiple ini files', IniHelper::getMessage()); - $this->assertEquals($paths, IniHelper::getAll()); + self::assertStringContainsString('multiple ini files', IniHelper::getMessage()); + self::assertEquals($paths, IniHelper::getAll()); } public function testWithoutLoadedIniAndAdditional(): void @@ -69,8 +69,8 @@ public function testWithoutLoadedIniAndAdditional(): void ]; $this->setEnv($paths); - $this->assertStringContainsString('multiple ini files', IniHelper::getMessage()); - $this->assertEquals($paths, IniHelper::getAll()); + self::assertStringContainsString('multiple ini files', IniHelper::getMessage()); + self::assertEquals($paths, IniHelper::getAll()); } public static function setUpBeforeClass(): void diff --git a/tests/Composer/Test/Util/MetadataMinifierTest.php b/tests/Composer/Test/Util/MetadataMinifierTest.php index d0961d5fde50..46e91a991e06 100644 --- a/tests/Composer/Test/Util/MetadataMinifierTest.php +++ b/tests/Composer/Test/Util/MetadataMinifierTest.php @@ -39,7 +39,7 @@ public function testMinifyExpand(): void $source = [$dumper->dump($package1), $dumper->dump($package2), $dumper->dump($package3)]; - $this->assertSame($minified, MetadataMinifier::minify($source)); - $this->assertSame($source, MetadataMinifier::expand($minified)); + self::assertSame($minified, MetadataMinifier::minify($source)); + self::assertSame($source, MetadataMinifier::expand($minified)); } } diff --git a/tests/Composer/Test/Util/NoProxyPatternTest.php b/tests/Composer/Test/Util/NoProxyPatternTest.php index 91c00f3ca2fb..6e639110c8e8 100644 --- a/tests/Composer/Test/Util/NoProxyPatternTest.php +++ b/tests/Composer/Test/Util/NoProxyPatternTest.php @@ -24,7 +24,7 @@ public function testHostName(string $noproxy, string $url, bool $expected): void { $matcher = new NoProxyPattern($noproxy); $url = $this->getUrl($url); - $this->assertEquals($expected, $matcher->test($url)); + self::assertEquals($expected, $matcher->test($url)); } public static function dataHostName(): array @@ -51,7 +51,7 @@ public function testIpAddress(string $noproxy, string $url, bool $expected): voi { $matcher = new NoProxyPattern($noproxy); $url = $this->getUrl($url); - $this->assertEquals($expected, $matcher->test($url)); + self::assertEquals($expected, $matcher->test($url)); } public static function dataIpAddress(): array @@ -76,7 +76,7 @@ public function testIpRange(string $noproxy, string $url, bool $expected): void { $matcher = new NoProxyPattern($noproxy); $url = $this->getUrl($url); - $this->assertEquals($expected, $matcher->test($url)); + self::assertEquals($expected, $matcher->test($url)); } public static function dataIpRange(): array @@ -101,7 +101,7 @@ public function testPort(string $noproxy, string $url, bool $expected): void { $matcher = new NoProxyPattern($noproxy); $url = $this->getUrl($url); - $this->assertEquals($expected, $matcher->test($url)); + self::assertEquals($expected, $matcher->test($url)); } public static function dataPort(): array diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index 414cca112620..1994675e0a3d 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -78,7 +78,7 @@ public function testGetClientWithoutStream(): void $client = $this->perforce->getClient(); $expected = 'composer_perforce_TEST_depot'; - $this->assertEquals($expected, $client); + self::assertEquals($expected, $client); } public function testGetClientFromStream(): void @@ -88,13 +88,13 @@ public function testGetClientFromStream(): void $client = $this->perforce->getClient(); $expected = 'composer_perforce_TEST_depot_branch'; - $this->assertEquals($expected, $client); + self::assertEquals($expected, $client); } public function testGetStreamWithoutStream(): void { $stream = $this->perforce->getStream(); - $this->assertEquals("//depot", $stream); + self::assertEquals("//depot", $stream); } public function testGetStreamWithStream(): void @@ -102,26 +102,26 @@ public function testGetStreamWithStream(): void $this->setPerforceToStream(); $stream = $this->perforce->getStream(); - $this->assertEquals('//depot/branch', $stream); + self::assertEquals('//depot/branch', $stream); } public function testGetStreamWithoutLabelWithStreamWithoutLabel(): void { $stream = $this->perforce->getStreamWithoutLabel('//depot/branch'); - $this->assertEquals('//depot/branch', $stream); + self::assertEquals('//depot/branch', $stream); } public function testGetStreamWithoutLabelWithStreamWithLabel(): void { $stream = $this->perforce->getStreamWithoutLabel('//depot/branching@label'); - $this->assertEquals('//depot/branching', $stream); + self::assertEquals('//depot/branching', $stream); } public function testGetClientSpec(): void { $clientSpec = $this->perforce->getP4ClientSpec(); $expected = 'path/composer_perforce_TEST_depot.p4.spec'; - $this->assertEquals($expected, $clientSpec); + self::assertEquals($expected, $clientSpec); } public function testGenerateP4Command(): void @@ -129,13 +129,13 @@ public function testGenerateP4Command(): void $command = 'do something'; $p4Command = $this->perforce->generateP4Command($command); $expected = 'p4 -u user -c composer_perforce_TEST_depot -p port do something'; - $this->assertEquals($expected, $p4Command); + self::assertEquals($expected, $p4Command); } public function testQueryP4UserWithUserAlreadySet(): void { $this->perforce->queryP4user(); - $this->assertEquals(self::TEST_P4USER, $this->perforce->getUser()); + self::assertEquals(self::TEST_P4USER, $this->perforce->getUser()); } public function testQueryP4UserWithUserSetInP4VariablesWithWindowsOS(): void @@ -148,7 +148,7 @@ public function testQueryP4UserWithUserSetInP4VariablesWithWindowsOS(): void ); $this->perforce->queryP4user(); - $this->assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser()); + self::assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser()); } public function testQueryP4UserWithUserSetInP4VariablesNotWindowsOS(): void @@ -162,7 +162,7 @@ public function testQueryP4UserWithUserSetInP4VariablesNotWindowsOS(): void ); $this->perforce->queryP4user(); - $this->assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser()); + self::assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser()); } public function testQueryP4UserQueriesForUser(): void @@ -173,7 +173,7 @@ public function testQueryP4UserQueriesForUser(): void ->with($this->equalTo($expectedQuestion)) ->willReturn('TEST_QUERY_USER'); $this->perforce->queryP4user(); - $this->assertEquals('TEST_QUERY_USER', $this->perforce->getUser()); + self::assertEquals('TEST_QUERY_USER', $this->perforce->getUser()); } public function testQueryP4UserStoresResponseToQueryForUserWithWindows(): void @@ -228,7 +228,7 @@ public function testQueryP4PasswordWithPasswordAlreadySet(): void ]; $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, $this->getMockIOInterface()); $password = $this->perforce->queryP4Password(); - $this->assertEquals('TEST_PASSWORD', $password); + self::assertEquals('TEST_PASSWORD', $password); } public function testQueryP4PasswordWithPasswordSetInP4VariablesWithWindowsOS(): void @@ -241,7 +241,7 @@ public function testQueryP4PasswordWithPasswordSetInP4VariablesWithWindowsOS(): ); $password = $this->perforce->queryP4Password(); - $this->assertEquals('TEST_P4VARIABLE_PASSWORD', $password); + self::assertEquals('TEST_P4VARIABLE_PASSWORD', $password); } public function testQueryP4PasswordWithPasswordSetInP4VariablesNotWindowsOS(): void @@ -254,7 +254,7 @@ public function testQueryP4PasswordWithPasswordSetInP4VariablesNotWindowsOS(): v ); $password = $this->perforce->queryP4Password(); - $this->assertEquals('TEST_P4VARIABLE_PASSWORD', $password); + self::assertEquals('TEST_P4VARIABLE_PASSWORD', $password); } public function testQueryP4PasswordQueriesForPassword(): void @@ -266,7 +266,7 @@ public function testQueryP4PasswordQueriesForPassword(): void ->willReturn('TEST_QUERY_PASSWORD'); $password = $this->perforce->queryP4Password(); - $this->assertEquals('TEST_QUERY_PASSWORD', $password); + self::assertEquals('TEST_QUERY_PASSWORD', $password); } public function testWriteP4ClientSpecWithoutStream(): void @@ -282,9 +282,9 @@ public function testWriteP4ClientSpecWithoutStream(): void $expectedArray = $this->getExpectedClientSpec(false); try { foreach ($expectedArray as $expected) { - $this->assertStringStartsWith($expected, fgets($stream)); + self::assertStringStartsWith($expected, (string) fgets($stream)); } - $this->assertFalse(fgets($stream)); + self::assertFalse(fgets($stream)); } catch (\Exception $e) { fclose($stream); throw $e; @@ -306,9 +306,9 @@ public function testWriteP4ClientSpecWithStream(): void $expectedArray = $this->getExpectedClientSpec(true); try { foreach ($expectedArray as $expected) { - $this->assertStringStartsWith($expected, fgets($stream)); + self::assertStringStartsWith($expected, (string) fgets($stream)); } - $this->assertFalse(fgets($stream)); + self::assertFalse(fgets($stream)); } catch (\Exception $e) { fclose($stream); throw $e; @@ -354,7 +354,7 @@ public function testGetBranchesWithStream(): void ); $branches = $this->perforce->getBranches(); - $this->assertEquals('//depot/branch@1234', $branches['master']); + self::assertEquals('//depot/branch@1234', $branches['master']); } public function testGetBranchesWithoutStream(): void @@ -370,7 +370,7 @@ public function testGetBranchesWithoutStream(): void ); $branches = $this->perforce->getBranches(); - $this->assertEquals('//depot@5678', $branches['master']); + self::assertEquals('//depot@5678', $branches['master']); } public function testGetTagsWithoutStream(): void @@ -386,8 +386,8 @@ public function testGetTagsWithoutStream(): void ); $tags = $this->perforce->getTags(); - $this->assertEquals('//depot@0.0.1', $tags['0.0.1']); - $this->assertEquals('//depot@0.0.2', $tags['0.0.2']); + self::assertEquals('//depot@0.0.1', $tags['0.0.1']); + self::assertEquals('//depot@0.0.2', $tags['0.0.2']); } public function testGetTagsWithStream(): void @@ -405,15 +405,15 @@ public function testGetTagsWithStream(): void ); $tags = $this->perforce->getTags(); - $this->assertEquals('//depot/branch@0.0.1', $tags['0.0.1']); - $this->assertEquals('//depot/branch@0.0.2', $tags['0.0.2']); + self::assertEquals('//depot/branch@0.0.1', $tags['0.0.1']); + self::assertEquals('//depot/branch@0.0.2', $tags['0.0.2']); } public function testCheckStreamWithoutStream(): void { $result = $this->perforce->checkStream(); - $this->assertFalse($result); - $this->assertFalse($this->perforce->isStream()); + self::assertFalse($result); + self::assertFalse($this->perforce->isStream()); } public function testCheckStreamWithStream(): void @@ -429,8 +429,8 @@ public function testCheckStreamWithStream(): void ); $result = $this->perforce->checkStream(); - $this->assertTrue($result); - $this->assertTrue($this->perforce->isStream()); + self::assertTrue($result); + self::assertTrue($this->perforce->isStream()); } public function testGetComposerInformationWithoutLabelWithoutStream(): void @@ -452,7 +452,7 @@ public function testGetComposerInformationWithoutLabelWithoutStream(): void 'minimum-stability' => 'dev', 'autoload' => ['psr-0' => []], ]; - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } public function testGetComposerInformationWithLabelWithoutStream(): void @@ -479,7 +479,7 @@ public function testGetComposerInformationWithLabelWithoutStream(): void 'minimum-stability' => 'dev', 'autoload' => ['psr-0' => []], ]; - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } public function testGetComposerInformationWithoutLabelWithStream(): void @@ -504,7 +504,7 @@ public function testGetComposerInformationWithoutLabelWithStream(): void 'minimum-stability' => 'dev', 'autoload' => ['psr-0' => []], ]; - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } public function testGetComposerInformationWithLabelWithStream(): void @@ -533,7 +533,7 @@ public function testGetComposerInformationWithLabelWithStream(): void 'minimum-stability' => 'dev', 'autoload' => ['psr-0' => []], ]; - $this->assertEquals($expected, $result); + self::assertEquals($expected, $result); } public function testSyncCodeBaseWithoutStream(): void @@ -566,7 +566,7 @@ public function testCheckServerExists(): void ); $result = $this->perforce->checkServerExists('perforce.does.exist:port', $this->processExecutor); - $this->assertTrue($result); + self::assertTrue($result); } /** @@ -585,7 +585,7 @@ public function testCheckServerClientError(): void ->willReturn(127); $result = $this->perforce->checkServerExists('perforce.does.exist:port', $processExecutor); - $this->assertFalse($result); + self::assertFalse($result); } public static function getComposerJson(): string diff --git a/tests/Composer/Test/Util/PlatformTest.php b/tests/Composer/Test/Util/PlatformTest.php index b6be7b8974eb..bbaa4ec82a9e 100644 --- a/tests/Composer/Test/Util/PlatformTest.php +++ b/tests/Composer/Test/Util/PlatformTest.php @@ -25,15 +25,15 @@ class PlatformTest extends TestCase public function testExpandPath(): void { putenv('TESTENV=/home/test'); - $this->assertEquals('/home/test/myPath', Platform::expandPath('%TESTENV%/myPath')); - $this->assertEquals('/home/test/myPath', Platform::expandPath('$TESTENV/myPath')); - $this->assertEquals((getenv('HOME') ?: getenv('USERPROFILE')) . '/test', Platform::expandPath('~/test')); + self::assertEquals('/home/test/myPath', Platform::expandPath('%TESTENV%/myPath')); + self::assertEquals('/home/test/myPath', Platform::expandPath('$TESTENV/myPath')); + self::assertEquals((getenv('HOME') ?: getenv('USERPROFILE')) . '/test', Platform::expandPath('~/test')); } public function testIsWindows(): void { // Compare 2 common tests for Windows to the built-in Windows test - $this->assertEquals(('\\' === DIRECTORY_SEPARATOR), Platform::isWindows()); - $this->assertEquals(defined('PHP_WINDOWS_VERSION_MAJOR'), Platform::isWindows()); + self::assertEquals(('\\' === DIRECTORY_SEPARATOR), Platform::isWindows()); + self::assertEquals(defined('PHP_WINDOWS_VERSION_MAJOR'), Platform::isWindows()); } } diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index a8455c7ab3af..006f2321fd06 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -29,7 +29,7 @@ public function testExecuteCapturesOutput(): void { $process = new ProcessExecutor; $process->execute('echo foo', $output); - $this->assertEquals("foo".PHP_EOL, $output); + self::assertEquals("foo".PHP_EOL, $output); } public function testExecuteOutputsIfNotCaptured(): void @@ -38,7 +38,7 @@ public function testExecuteOutputsIfNotCaptured(): void ob_start(); $process->execute('echo foo'); $output = ob_get_clean(); - $this->assertEquals("foo".PHP_EOL, $output); + self::assertEquals("foo".PHP_EOL, $output); } public function testUseIOIsNotNullAndIfNotCaptured(): void @@ -56,14 +56,14 @@ public function testExecuteCapturesStderr(): void { $process = new ProcessExecutor; $process->execute('cat foo', $output); - $this->assertNotNull($process->getErrorOutput()); + self::assertStringContainsString('foo: No such file or directory', $process->getErrorOutput()); } public function testTimeout(): void { ProcessExecutor::setTimeout(1); $process = new ProcessExecutor; - $this->assertEquals(1, $process->getTimeout()); + self::assertEquals(1, $process->getTimeout()); ProcessExecutor::setTimeout(60); } @@ -74,7 +74,7 @@ public function testHidePasswords(string $command, string $expectedCommandOutput { $process = new ProcessExecutor($buffer = new BufferIO('', StreamOutput::VERBOSITY_DEBUG)); $process->execute($command, $output); - $this->assertEquals('Executing command (CWD): ' . $expectedCommandOutput, trim($buffer->getOutput())); + self::assertEquals('Executing command (CWD): ' . $expectedCommandOutput, trim($buffer->getOutput())); } public static function hidePasswordProvider(): array @@ -92,18 +92,18 @@ public function testDoesntHidePorts(): void { $process = new ProcessExecutor($buffer = new BufferIO('', StreamOutput::VERBOSITY_DEBUG)); $process->execute('echo https://localhost:1234/', $output); - $this->assertEquals('Executing command (CWD): echo https://localhost:1234/', trim($buffer->getOutput())); + self::assertEquals('Executing command (CWD): echo https://localhost:1234/', trim($buffer->getOutput())); } public function testSplitLines(): void { $process = new ProcessExecutor; - $this->assertEquals([], $process->splitLines('')); - $this->assertEquals([], $process->splitLines(null)); - $this->assertEquals(['foo'], $process->splitLines('foo')); - $this->assertEquals(['foo', 'bar'], $process->splitLines("foo\nbar")); - $this->assertEquals(['foo', 'bar'], $process->splitLines("foo\r\nbar")); - $this->assertEquals(['foo', 'bar'], $process->splitLines("foo\r\nbar\n")); + self::assertEquals([], $process->splitLines('')); + self::assertEquals([], $process->splitLines(null)); + self::assertEquals(['foo'], $process->splitLines('foo')); + self::assertEquals(['foo', 'bar'], $process->splitLines("foo\nbar")); + self::assertEquals(['foo', 'bar'], $process->splitLines("foo\r\nbar")); + self::assertEquals(['foo', 'bar'], $process->splitLines("foo\r\nbar\n")); } public function testConsoleIODoesNotFormatSymfonyConsoleStyle(): void @@ -112,7 +112,7 @@ public function testConsoleIODoesNotFormatSymfonyConsoleStyle(): void $process = new ProcessExecutor(new ConsoleIO(new ArrayInput([]), $output, new HelperSet([]))); $process->execute('php -ddisplay_errors=0 -derror_reporting=0 -r "echo \'foo\'.PHP_EOL;"'); - $this->assertSame('foo'.PHP_EOL, $output->fetch()); + self::assertSame('foo'.PHP_EOL, $output->fetch()); } public function testExecuteAsyncCancel(): void @@ -121,12 +121,12 @@ public function testExecuteAsyncCancel(): void $process->enableAsync(); $start = microtime(true); $promise = $process->executeAsync('sleep 2'); - $this->assertEquals(1, $process->countActiveJobs()); + self::assertEquals(1, $process->countActiveJobs()); $promise->cancel(); - $this->assertEquals(0, $process->countActiveJobs()); + self::assertEquals(0, $process->countActiveJobs()); $process->wait(); $end = microtime(true); - $this->assertTrue($end - $start < 2, 'Canceling took longer than it should, lasted '.($end - $start)); + self::assertTrue($end - $start < 2, 'Canceling took longer than it should, lasted '.($end - $start)); } /** @@ -139,7 +139,7 @@ public function testExecuteAsyncCancel(): void public function testEscapeArgument($argument, string $win, string $unix): void { $expected = defined('PHP_WINDOWS_VERSION_BUILD') ? $win : $unix; - $this->assertSame($expected, ProcessExecutor::escape($argument)); + self::assertSame($expected, ProcessExecutor::escape($argument)); } /** diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index d673cd2b01c8..048ccb0f8358 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -34,7 +34,7 @@ public function testGetOptionsForUrl(): void ; $res = $this->callGetOptionsForUrl($io, ['http://example.org', []]); - $this->assertTrue(isset($res['http']['header']) && is_array($res['http']['header']), 'getOptions must return an array with headers'); + self::assertTrue(isset($res['http']['header']) && is_array($res['http']['header']), 'getOptions must return an array with headers'); } public function testGetOptionsForUrlWithAuthorization(): void @@ -59,7 +59,7 @@ public function testGetOptionsForUrlWithAuthorization(): void $found = true; } } - $this->assertTrue($found, 'getOptions must have an Authorization header'); + self::assertTrue($found, 'getOptions must have an Authorization header'); } public function testGetOptionsForUrlWithStreamOptions(): void @@ -82,7 +82,7 @@ public function testGetOptionsForUrlWithStreamOptions(): void ]]; $res = $this->callGetOptionsForUrl($io, ['https://example.org', []], $streamOptions); - $this->assertTrue( + self::assertTrue( isset($res['ssl'], $res['ssl']['allow_self_signed']) && true === $res['ssl']['allow_self_signed'], 'getOptions must return an array with a allow_self_signed set to true' ); @@ -108,7 +108,7 @@ public function testGetOptionsForUrlWithCallOptionsKeepsHeader(): void ]]; $res = $this->callGetOptionsForUrl($io, ['https://example.org', $streamOptions]); - $this->assertTrue(isset($res['http']['header']), 'getOptions must return an array with a http.header key'); + self::assertTrue(isset($res['http']['header']), 'getOptions must return an array with a http.header key'); $found = false; foreach ($res['http']['header'] as $header) { @@ -117,15 +117,15 @@ public function testGetOptionsForUrlWithCallOptionsKeepsHeader(): void } } - $this->assertTrue($found, 'getOptions must have a Foo: bar header'); - $this->assertGreaterThan(1, count($res['http']['header'])); + self::assertTrue($found, 'getOptions must have a Foo: bar header'); + self::assertGreaterThan(1, count($res['http']['header'])); } public function testCallbackGetFileSize(): void { $fs = new RemoteFilesystem($this->getIOInterfaceMock(), $this->getConfigMock()); $this->callCallbackGet($fs, STREAM_NOTIFY_FILE_SIZE_IS, 0, '', 0, 0, 20); - $this->assertAttributeEqualsCustom(20, 'bytesMax', $fs); + self::assertAttributeEqualsCustom(20, 'bytesMax', $fs); } public function testCallbackGetNotifyProgress(): void @@ -141,22 +141,24 @@ public function testCallbackGetNotifyProgress(): void $this->setAttribute($fs, 'progress', true); $this->callCallbackGet($fs, STREAM_NOTIFY_PROGRESS, 0, '', 0, 10, 20); - $this->assertAttributeEqualsCustom(50, 'lastProgress', $fs); + self::assertAttributeEqualsCustom(50, 'lastProgress', $fs); } + /** + * @doesNotPerformAssertions + */ public function testCallbackGetPassesThrough404(): void { $fs = new RemoteFilesystem($this->getIOInterfaceMock(), $this->getConfigMock()); $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, 'HTTP/1.1 404 Not Found', 404, 0, 0); - $this->assertTrue(true, 'callbackGet must pass through 404'); } public function testGetContents(): void { $fs = new RemoteFilesystem($this->getIOInterfaceMock(), $this->getConfigMock()); - $this->assertStringContainsString('testGetContents', $fs->getContents('http://example.org', 'file://'.__FILE__)); + self::assertStringContainsString('testGetContents', (string) $fs->getContents('http://example.org', 'file://'.__FILE__)); } public function testCopy(): void @@ -164,9 +166,9 @@ public function testCopy(): void $fs = new RemoteFilesystem($this->getIOInterfaceMock(), $this->getConfigMock()); $file = $this->createTempFile(); - $this->assertTrue($fs->copy('http://example.org', 'file://'.__FILE__, $file)); - $this->assertFileExists($file); - $this->assertStringContainsString('testCopy', file_get_contents($file)); + self::assertTrue($fs->copy('http://example.org', 'file://'.__FILE__, $file)); + self::assertFileExists($file); + self::assertStringContainsString('testCopy', (string) file_get_contents($file)); unlink($file); } @@ -231,9 +233,9 @@ public function testCopyWithSuccessOnRetry(): void ['retry-auth-failure' => true] ); - $this->assertTrue($copyResult); - $this->assertFileExists($file); - $this->assertStringContainsString('Copied', file_get_contents($file)); + self::assertTrue($copyResult); + self::assertFileExists($file); + self::assertStringContainsString('Copied', (string) file_get_contents($file)); unlink($file); } @@ -247,16 +249,16 @@ public function testGetOptionsForUrlCreatesSecureTlsDefaults(): void $res = $this->callGetOptionsForUrl($io, ['example.org', ['ssl' => ['cafile' => '/some/path/file.crt']]], [], 'http://www.example.org'); - $this->assertTrue(isset($res['ssl']['ciphers'])); - $this->assertMatchesRegularExpression('|!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA|', $res['ssl']['ciphers']); - $this->assertTrue($res['ssl']['verify_peer']); - $this->assertTrue($res['ssl']['SNI_enabled']); - $this->assertEquals(7, $res['ssl']['verify_depth']); - $this->assertEquals('/some/path/file.crt', $res['ssl']['cafile']); + self::assertTrue(isset($res['ssl']['ciphers'])); + self::assertMatchesRegularExpression('|!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA|', $res['ssl']['ciphers']); + self::assertTrue($res['ssl']['verify_peer']); + self::assertTrue($res['ssl']['SNI_enabled']); + self::assertEquals(7, $res['ssl']['verify_depth']); + self::assertEquals('/some/path/file.crt', $res['ssl']['cafile']); if (version_compare(PHP_VERSION, '5.4.13') >= 0) { - $this->assertTrue($res['ssl']['disable_compression']); + self::assertTrue($res['ssl']['disable_compression']); } else { - $this->assertFalse(isset($res['ssl']['disable_compression'])); + self::assertFalse(isset($res['ssl']['disable_compression'])); } } @@ -291,7 +293,7 @@ public function testBitBucketPublicDownload(string $url, string $contents): void $result = $rfs->getContents($hostname, $url, false); - $this->assertEquals($contents, $result); + self::assertEquals($contents, $result); } /** @@ -331,8 +333,8 @@ public function testBitBucketPublicDownloadWithAuthConfigured(string $url, strin $result = $rfs->getContents($hostname, $url, false); - $this->assertEquals($contents, $result); - $this->assertEquals(['bitbucket.org', 'bbuseruploads.s3.amazonaws.com'], $domains); + self::assertEquals($contents, $result); + self::assertEquals(['bitbucket.org', 'bbuseruploads.s3.amazonaws.com'], $domains); } /** @@ -399,7 +401,7 @@ private function assertAttributeEqualsCustom($value, string $attribute, $object) { $attr = new ReflectionProperty($object, $attribute); $attr->setAccessible(true); - $this->assertSame($value, $attr->getValue($object)); + self::assertSame($value, $attr->getValue($object)); } /** diff --git a/tests/Composer/Test/Util/SilencerTest.php b/tests/Composer/Test/Util/SilencerTest.php index 9161dae60123..270b9b1118e1 100644 --- a/tests/Composer/Test/Util/SilencerTest.php +++ b/tests/Composer/Test/Util/SilencerTest.php @@ -40,10 +40,10 @@ public function testSilencer(): void return $a * $b * $c; }, 2, 3, 4); - $this->assertEquals(24, $result); + self::assertEquals(24, $result); // Check the error reporting setting was restored correctly - $this->assertEquals($before, error_reporting()); + self::assertEquals($before, error_reporting()); } /** diff --git a/tests/Composer/Test/Util/StreamContextFactoryTest.php b/tests/Composer/Test/Util/StreamContextFactoryTest.php index 4d875122d4da..d1fe5db31fe1 100644 --- a/tests/Composer/Test/Util/StreamContextFactoryTest.php +++ b/tests/Composer/Test/Util/StreamContextFactoryTest.php @@ -45,8 +45,8 @@ public function testGetContext(array $expectedOptions, array $defaultOptions, ar $options = stream_context_get_options($context); $params = stream_context_get_params($context); - $this->assertEquals($expectedOptions, $options); - $this->assertEquals($expectedParams, $params); + self::assertEquals($expectedOptions, $options); + self::assertEquals($expectedParams, $params); } public static function dataGetContext(): array @@ -72,7 +72,7 @@ public function testHttpProxy(): void $context = StreamContextFactory::getContext('http://example.org', ['http' => ['method' => 'GET', 'header' => 'User-Agent: foo']]); $options = stream_context_get_options($context); - $this->assertEquals(['http' => [ + self::assertEquals(['http' => [ 'proxy' => 'tcp://proxyserver.net:3128', 'request_fulluri' => true, 'method' => 'GET', @@ -90,7 +90,7 @@ public function testHttpProxyWithNoProxy(): void $context = StreamContextFactory::getContext('http://example.org', ['http' => ['method' => 'GET', 'header' => 'User-Agent: foo']]); $options = stream_context_get_options($context); - $this->assertEquals(['http' => [ + self::assertEquals(['http' => [ 'method' => 'GET', 'max_redirects' => 20, 'follow_location' => 1, @@ -106,7 +106,7 @@ public function testHttpProxyWithNoProxyWildcard(): void $context = StreamContextFactory::getContext('http://example.org', ['http' => ['method' => 'GET', 'header' => 'User-Agent: foo']]); $options = stream_context_get_options($context); - $this->assertEquals(['http' => [ + self::assertEquals(['http' => [ 'method' => 'GET', 'max_redirects' => 20, 'follow_location' => 1, @@ -121,7 +121,7 @@ public function testOptionsArePreserved(): void $context = StreamContextFactory::getContext('http://example.org', ['http' => ['method' => 'GET', 'header' => ['User-Agent: foo', "X-Foo: bar"], 'request_fulluri' => false]]); $options = stream_context_get_options($context); - $this->assertEquals(['http' => [ + self::assertEquals(['http' => [ 'proxy' => 'tcp://proxyserver.net:3128', 'request_fulluri' => false, 'method' => 'GET', @@ -138,7 +138,7 @@ public function testHttpProxyWithoutPort(): void $context = StreamContextFactory::getContext('https://example.org', ['http' => ['method' => 'GET', 'header' => 'User-Agent: foo']]); $options = stream_context_get_options($context); - $this->assertEquals(['http' => [ + self::assertEquals(['http' => [ 'proxy' => 'tcp://proxyserver.net:80', 'method' => 'GET', 'header' => ['User-Agent: foo', "Proxy-Authorization: Basic " . base64_encode('username:password')], @@ -172,7 +172,7 @@ public function testSSLProxy(string $expected, string $proxy): void $context = StreamContextFactory::getContext('http://example.org', ['http' => ['header' => 'User-Agent: foo']]); $options = stream_context_get_options($context); - $this->assertEquals(['http' => [ + self::assertEquals(['http' => [ 'proxy' => $expected, 'request_fulluri' => true, 'max_redirects' => 20, @@ -184,7 +184,7 @@ public function testSSLProxy(string $expected, string $proxy): void StreamContextFactory::getContext('http://example.org'); $this->fail(); } catch (\RuntimeException $e) { - $this->assertInstanceOf('Composer\Downloader\TransportException', $e); + self::assertInstanceOf('Composer\Downloader\TransportException', $e); } } } @@ -216,7 +216,7 @@ public function testEnsureThatfixHttpHeaderFieldMovesContentTypeToEndOfOptions() ]; $context = StreamContextFactory::getContext('http://example.org', $options); $ctxoptions = stream_context_get_options($context); - $this->assertEquals(end($expectedOptions['http']['header']), end($ctxoptions['http']['header'])); + self::assertEquals(end($expectedOptions['http']['header']), end($ctxoptions['http']['header'])); } public function testInitOptionsDoesIncludeProxyAuthHeaders(): void @@ -227,7 +227,7 @@ public function testInitOptionsDoesIncludeProxyAuthHeaders(): void $options = StreamContextFactory::initOptions('https://example.org', $options); $headers = implode(' ', $options['http']['header']); - $this->assertTrue(false !== stripos($headers, 'Proxy-Authorization')); + self::assertTrue(false !== stripos($headers, 'Proxy-Authorization')); } public function testInitOptionsForCurlDoesNotIncludeProxyAuthHeaders(): void @@ -242,6 +242,6 @@ public function testInitOptionsForCurlDoesNotIncludeProxyAuthHeaders(): void $options = StreamContextFactory::initOptions('https://example.org', $options, true); $headers = implode(' ', $options['http']['header']); - $this->assertFalse(stripos($headers, 'Proxy-Authorization')); + self::assertFalse(stripos($headers, 'Proxy-Authorization')); } } diff --git a/tests/Composer/Test/Util/SvnTest.php b/tests/Composer/Test/Util/SvnTest.php index cf9f5f0adf0f..044bf2eed977 100644 --- a/tests/Composer/Test/Util/SvnTest.php +++ b/tests/Composer/Test/Util/SvnTest.php @@ -33,7 +33,7 @@ public function testCredentials(string $url, string $expect): void $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); $reflMethod->setAccessible(true); - $this->assertEquals($expect, $reflMethod->invoke($svn)); + self::assertEquals($expect, $reflMethod->invoke($svn)); } public static function urlProvider(): array @@ -53,7 +53,7 @@ public function testInteractiveString(): void $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCommand'); $reflMethod->setAccessible(true); - $this->assertEquals( + self::assertEquals( self::getCmd("svn ls --non-interactive -- 'http://svn.example.org'"), $reflMethod->invokeArgs($svn, ['svn ls', $url]) ); @@ -76,7 +76,7 @@ public function testCredentialsFromConfig(): void $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); $reflMethod->setAccessible(true); - $this->assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); + self::assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); } public function testCredentialsFromConfigWithCacheCredentialsTrue(): void @@ -99,7 +99,7 @@ public function testCredentialsFromConfigWithCacheCredentialsTrue(): void $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); $reflMethod->setAccessible(true); - $this->assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); + self::assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); } public function testCredentialsFromConfigWithCacheCredentialsFalse(): void @@ -122,6 +122,6 @@ public function testCredentialsFromConfigWithCacheCredentialsFalse(): void $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); $reflMethod->setAccessible(true); - $this->assertEquals(self::getCmd(" --no-auth-cache --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); + self::assertEquals(self::getCmd(" --no-auth-cache --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); } } diff --git a/tests/Composer/Test/Util/TarTest.php b/tests/Composer/Test/Util/TarTest.php index 86c7564cd321..cba1d501b684 100644 --- a/tests/Composer/Test/Util/TarTest.php +++ b/tests/Composer/Test/Util/TarTest.php @@ -24,13 +24,13 @@ public function testReturnsNullifTheTarIsNotFound(): void { $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/invalid.zip'); - $this->assertNull($result); + self::assertNull($result); } public function testReturnsNullIfTheTarIsEmpty(): void { $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/empty.tar.gz'); - $this->assertNull($result); + self::assertNull($result); } public function testThrowsExceptionIfTheTarHasNoComposerJson(): void @@ -48,13 +48,13 @@ public function testThrowsExceptionIfTheComposerJsonIsInASubSubfolder(): void public function testReturnsComposerJsonInTarRoot(): void { $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/root.tar.gz'); - $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + self::assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } public function testReturnsComposerJsonInFirstFolder(): void { $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/folder.tar.gz'); - $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + self::assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } public function testMultipleTopLevelDirsIsInvalid(): void diff --git a/tests/Composer/Test/Util/TlsHelperTest.php b/tests/Composer/Test/Util/TlsHelperTest.php index 58144a425a37..64c0c4cc5e00 100644 --- a/tests/Composer/Test/Util/TlsHelperTest.php +++ b/tests/Composer/Test/Util/TlsHelperTest.php @@ -31,11 +31,11 @@ public function testCheckCertificateHost(bool $expectedResult, string $hostname, $result = TlsHelper::checkCertificateHost($certificate, $hostname, $foundCn); if (true === $expectedResult) { - $this->assertTrue($result); - $this->assertSame($expectedCn, $foundCn); + self::assertTrue($result); + self::assertSame($expectedCn, $foundCn); } else { - $this->assertFalse($result); - $this->assertNull($foundCn); + self::assertFalse($result); + self::assertNull($foundCn); } } @@ -74,8 +74,8 @@ public function testGetCertificateNames(): void $names = TlsHelper::getCertificateNames($certificate); self::assertIsArray($names); - $this->assertSame('example.net', $names['cn']); - $this->assertSame([ + self::assertSame('example.net', $names['cn']); + self::assertSame([ 'example.com', 'getcomposer.org', 'composer.example.org', diff --git a/tests/Composer/Test/Util/UrlTest.php b/tests/Composer/Test/Util/UrlTest.php index 267b1c1bd7ad..04167904f5de 100644 --- a/tests/Composer/Test/Util/UrlTest.php +++ b/tests/Composer/Test/Util/UrlTest.php @@ -29,7 +29,7 @@ public function testUpdateDistReference(string $url, string $expectedUrl, array $config = new Config(); $config->merge(['config' => $conf]); - $this->assertSame($expectedUrl, Url::updateDistReference($config, $url, $ref)); + self::assertSame($expectedUrl, Url::updateDistReference($config, $url, $ref)); } public static function distRefsProvider(): array @@ -67,7 +67,7 @@ public static function distRefsProvider(): array */ public function testSanitize(string $expected, string $url): void { - $this->assertSame($expected, Url::sanitize($url)); + self::assertSame($expected, Url::sanitize($url)); } public static function sanitizeProvider(): array diff --git a/tests/Composer/Test/Util/ZipTest.php b/tests/Composer/Test/Util/ZipTest.php index 0af7ea89acd4..b1981de8679d 100644 --- a/tests/Composer/Test/Util/ZipTest.php +++ b/tests/Composer/Test/Util/ZipTest.php @@ -40,7 +40,7 @@ public function testReturnsNullifTheZipIsNotFound(): void $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/invalid.zip'); - $this->assertNull($result); + self::assertNull($result); } public function testReturnsNullIfTheZipIsEmpty(): void @@ -51,7 +51,7 @@ public function testReturnsNullIfTheZipIsEmpty(): void $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/empty.zip'); - $this->assertNull($result); + self::assertNull($result); } public function testThrowsExceptionIfTheZipHasNoComposerJson(): void @@ -86,7 +86,7 @@ public function testReturnsComposerJsonInZipRoot(): void $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/root.zip'); - $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + self::assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } public function testReturnsComposerJsonInFirstFolder(): void @@ -96,7 +96,7 @@ public function testReturnsComposerJsonInFirstFolder(): void } $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip'); - $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + self::assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } public function testMultipleTopLevelDirsIsInvalid(): void @@ -119,6 +119,6 @@ public function testReturnsComposerJsonFromFirstSubfolder(): void $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/single-sub.zip'); - $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + self::assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } } From c1be804a0c28a23bb52eeac34bcd09e810fb505c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 31 May 2024 10:29:56 +0200 Subject: [PATCH 095/257] Fix UX when a non-required plugin is still present in vendor dir (#12000) Composer now skips it and does not prompt if it is not allowed to run, fixes #11944 --- src/Composer/Plugin/PluginManager.php | 30 ++++++++++++++++--- src/Composer/Repository/RepositoryUtils.php | 11 +++++-- .../Test/Repository/RepositoryUtilsTest.php | 8 +++-- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index f594478a6fe0..c22364ad2814 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -20,11 +20,13 @@ use Composer\Package\CompletePackage; use Composer\Package\Locker; use Composer\Package\Package; +use Composer\Package\RootPackageInterface; use Composer\Package\Version\VersionParser; use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepository; +use Composer\Repository\RepositoryUtils; use Composer\Repository\RootPackageRepository; use Composer\Package\PackageInterface; use Composer\Package\Link; @@ -98,7 +100,7 @@ public function loadInstalledPlugins(): void { if (!$this->arePluginsDisabled('local')) { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); - $this->loadRepository($repo, false); + $this->loadRepository($repo, false, $this->composer->getPackage()); } if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { @@ -445,9 +447,11 @@ public function uninstallPlugin(PluginInterface $plugin): void * * @param RepositoryInterface $repo Repository to scan for plugins to install * + * @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage + * * @throws \RuntimeException */ - private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo): void + private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void { $packages = $repo->getPackages(); @@ -462,10 +466,28 @@ private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo): } $sortedPackages = PackageSorter::sortPackages($packages, $weights); + if (!$isGlobalRepo) { + $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true); + } + foreach ($sortedPackages as $package) { if (!($package instanceof CompletePackage)) { continue; } + + if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) { + continue; + } + + if ( + !$isGlobalRepo + && !in_array($package, $requiredPackages, true) + && !$this->isPluginAllowed($package->getName(), false, true, false) + ) { + $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.'); + continue; + } + if ('composer-plugin' === $package->getType()) { $this->registerPackage($package, false, $isGlobalRepo); // Backward compatibility @@ -668,7 +690,7 @@ public function disablePlugins(): void /** * @internal */ - public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false): bool + public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool { if ($isGlobalPlugin) { $rules = &$this->allowGlobalPluginRules; @@ -703,7 +725,7 @@ public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $opt return false; } - if ($this->io->isInteractive()) { + if ($this->io->isInteractive() && $prompt) { $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; $this->io->writeError(''.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins'); diff --git a/src/Composer/Repository/RepositoryUtils.php b/src/Composer/Repository/RepositoryUtils.php index 62e1c5b0f59f..e6960c63d0c1 100644 --- a/src/Composer/Repository/RepositoryUtils.php +++ b/src/Composer/Repository/RepositoryUtils.php @@ -24,23 +24,28 @@ class RepositoryUtils /** * Find all of $packages which are required by $requirer, either directly or transitively * - * Require-dev is ignored + * Require-dev is ignored by default, you can enable the require-dev of the initial $requirer + * packages by passing $includeRequireDev=true, but require-dev of transitive dependencies + * are always ignored. * * @template T of PackageInterface * @param array $packages * @param list $bucket Do not pass this in, only used to avoid recursion with circular deps * @return list */ - public static function filterRequiredPackages(array $packages, PackageInterface $requirer, array $bucket = []): array + public static function filterRequiredPackages(array $packages, PackageInterface $requirer, bool $includeRequireDev = false, array $bucket = []): array { $requires = $requirer->getRequires(); + if ($includeRequireDev) { + $requires = array_merge($requires, $requirer->getDevRequires()); + } foreach ($packages as $candidate) { foreach ($candidate->getNames() as $name) { if (isset($requires[$name])) { if (!in_array($candidate, $bucket, true)) { $bucket[] = $candidate; - $bucket = self::filterRequiredPackages($packages, $candidate, $bucket); + $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket); } break; } diff --git a/tests/Composer/Test/Repository/RepositoryUtilsTest.php b/tests/Composer/Test/Repository/RepositoryUtilsTest.php index ddc328998037..e85d2f2f543f 100644 --- a/tests/Composer/Test/Repository/RepositoryUtilsTest.php +++ b/tests/Composer/Test/Repository/RepositoryUtilsTest.php @@ -24,13 +24,13 @@ class RepositoryUtilsTest extends TestCase * @param PackageInterface[] $pkgs * @param string[] $expected */ - public function testFilterRequiredPackages(array $pkgs, PackageInterface $requirer, array $expected): void + public function testFilterRequiredPackages(array $pkgs, PackageInterface $requirer, array $expected, bool $includeRequireDev = false): void { $expected = array_map(static function (string $name) use ($pkgs): PackageInterface { return $pkgs[$name]; }, $expected); - self::assertSame($expected, RepositoryUtils::filterRequiredPackages($pkgs, $requirer)); + self::assertSame($expected, RepositoryUtils::filterRequiredPackages($pkgs, $requirer, $includeRequireDev)); } /** @@ -72,6 +72,10 @@ public static function provideFilterRequireTests(): Generator self::configureLinks($requirer, ['require-dev' => ['required/a' => '*']]); yield 'require-dev has no effect' => [$pkgs, $requirer, []]; + $requirer = self::getPackage('requirer/pkg'); + self::configureLinks($requirer, ['require-dev' => ['required/a' => '*']]); + yield 'require-dev works if called with it enabled' => [$pkgs, $requirer, ['a'], true]; + $requirer = self::getPackage('requirer/pkg'); self::configureLinks($requirer, ['require' => ['required/a' => '*']]); yield 'simple require' => [$pkgs, $requirer, ['a']]; From dc857b4f9148def53cb5c182dbb965aaac76d947 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 31 May 2024 15:08:44 +0200 Subject: [PATCH 096/257] Fixed PSR violations for classes not matching the namespace of a rule being hidden, fixes #11957 --- composer.json | 10 +++- composer.lock | 48 +++++++++---------- src/Composer/Autoload/AutoloadGenerator.php | 22 ++++++--- .../Test/Command/DumpAutoloadCommandTest.php | 2 +- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 3513b8c58dbf..1e38b523aafa 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require": { "php": "^7.2.5 || ^8.0", "composer/ca-bundle": "^1.0", - "composer/class-map-generator": "^1.0", + "composer/class-map-generator": "^1.3.1", "composer/metadata-minifier": "^1.0", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", @@ -81,7 +81,13 @@ "autoload-dev": { "psr-4": { "Composer\\Test\\": "tests/Composer/Test/" - } + }, + "exclude-from-classmap": [ + "tests/Composer/Test/Fixtures/", + "tests/Composer/Test/Autoload/Fixtures", + "tests/Composer/Test/Autoload/MinimumVersionSupport", + "tests/Composer/Test/Plugin/Fixtures" + ] }, "bin": [ "bin/composer" diff --git a/composer.lock b/composer.lock index 9dc770cf1454..69a37ac28071 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "9cc98d4fd8dcc30565a6570841b7ec85", + "content-hash": "1322de6eee2357fec92034b7b41e6512", "packages": [ { "name": "composer/ca-bundle", @@ -84,16 +84,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.1.1", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "8286a62d243312ed99b3eee20d5005c961adb311" + "reference": "5bb7f1e135ccc696bdccb7ec945403fcaac144d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8286a62d243312ed99b3eee20d5005c961adb311", - "reference": "8286a62d243312ed99b3eee20d5005c961adb311", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/5bb7f1e135ccc696bdccb7ec945403fcaac144d5", + "reference": "5bb7f1e135ccc696bdccb7ec945403fcaac144d5", "shasum": "" }, "require": { @@ -137,7 +137,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.1.1" + "source": "https://github.com/composer/class-map-generator/tree/1.3.1" }, "funding": [ { @@ -153,7 +153,7 @@ "type": "tidelift" } ], - "time": "2024-03-15T12:53:41+00:00" + "time": "2024-05-31T15:31:05+00:00" }, { "name": "composer/metadata-minifier", @@ -2015,16 +2015,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.2", + "version": "1.11.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0d5d4294a70deb7547db655c47685d680e39cfec" + "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec", - "reference": "0d5d4294a70deb7547db655c47685d680e39cfec", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e64220a05c1209fc856d58e789c3b7a32c0bb9a5", + "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5", "shasum": "" }, "require": { @@ -2069,7 +2069,7 @@ "type": "github" } ], - "time": "2024-05-24T13:23:04+00:00" + "time": "2024-05-31T13:53:37+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2221,16 +2221,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "d530cfebba55763732bc2421f79d2576d9d7942f" + "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/d530cfebba55763732bc2421f79d2576d9d7942f", - "reference": "d530cfebba55763732bc2421f79d2576d9d7942f", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/af6ae0f4b91bc080265e80776af26da3e5befb28", + "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28", "shasum": "" }, "require": { @@ -2287,22 +2287,22 @@ "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.4.1" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.3" }, - "time": "2024-05-24T14:00:29+00:00" + "time": "2024-05-30T15:01:27+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.0.7", + "version": "v7.1.0", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9" + "reference": "1168ef27edb094fe4ba755ec937cf62a6dff84eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9", - "reference": "0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/1168ef27edb094fe4ba755ec937cf62a6dff84eb", + "reference": "1168ef27edb094fe4ba755ec937cf62a6dff84eb", "shasum": "" }, "require": { @@ -2354,7 +2354,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.7" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.0" }, "funding": [ { @@ -2370,7 +2370,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:29:19+00:00" + "time": "2024-05-17T10:55:18+00:00" } ], "aliases": [], diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 844b802073e5..9da1767adf7b 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -319,7 +319,7 @@ public static function autoload(\$class) EOF; } - $excluded = null; + $excluded = []; if (!empty($autoloads['exclude-from-classmap'])) { $excluded = $autoloads['exclude-from-classmap']; } @@ -348,7 +348,14 @@ public static function autoload(\$class) continue; } - $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded), $group['type'], $namespace); + // if the vendor dir is contained within a psr-0/psr-4 dir being scanned we exclude it + if (str_contains($vendorPath, $dir.'/')) { + $exclusionRegex = $this->buildExclusionRegex($dir, array_merge($excluded, [$vendorPath.'/'])); + } else { + $exclusionRegex = $this->buildExclusionRegex($dir, $excluded); + } + + $classMapGenerator->scanPaths($dir, $exclusionRegex, $group['type'], $namespace); } } } @@ -368,6 +375,9 @@ public static function autoload(\$class) ); } } + + // output PSR violations which are not coming from the vendor dir + $classMap->clearPsrViolationsByPath($vendorPath); foreach ($classMap->getPsrViolations() as $msg) { $this->io->writeError("$msg"); } @@ -460,12 +470,12 @@ public static function autoload(\$class) } /** - * @param array|null $excluded + * @param array $excluded * @return non-empty-string|null */ - private function buildExclusionRegex(string $dir, ?array $excluded): ?string + private function buildExclusionRegex(string $dir, array $excluded): ?string { - if (null === $excluded) { + if ([] === $excluded) { return null; } @@ -602,7 +612,7 @@ public function createLoader(array $autoloads, ?string $vendorDir = null) } if (isset($autoloads['classmap'])) { - $excluded = null; + $excluded = []; if (!empty($autoloads['exclude-from-classmap'])) { $excluded = $autoloads['exclude-from-classmap']; } diff --git a/tests/Composer/Test/Command/DumpAutoloadCommandTest.php b/tests/Composer/Test/Command/DumpAutoloadCommandTest.php index fd58156ab9c0..c8c46a0649b7 100644 --- a/tests/Composer/Test/Command/DumpAutoloadCommandTest.php +++ b/tests/Composer/Test/Command/DumpAutoloadCommandTest.php @@ -62,7 +62,7 @@ public function testFailsUsingStrictPsrIfClassMapViolationsAreFound(): void self::assertSame(1, $appTester->run(['command' => 'dump-autoload', '--optimize' => true, '--strict-psr' => true])); $output = $appTester->getDisplay(true); - self::assertMatchesRegularExpression('/Class Application\\\Src\\\Foo located in .*? does not comply with psr-4 autoloading standard. Skipping./', $output); + self::assertMatchesRegularExpression('#Class Application\\\\Src\\\\Foo located in .*? does not comply with psr-4 autoloading standard \(rule: Application\\\\ => \./src\)\. Skipping\.#', $output); } public function testUsingClassmapAuthoritative(): void From 9dfcf62335e2719cfd08019e854f9a7181a9aca2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 31 May 2024 17:53:52 +0200 Subject: [PATCH 097/257] Fix new platform requirements from composer.json not being checked when composer.lock is outdated, fixes #11989 (#12001) --- src/Composer/Installer.php | 15 +++++- .../outdated-lock-file-fails-install.test | 39 ++++++++++++++++ ...ock-file-with-new-platform-reqs-fails.test | 46 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test create mode 100644 tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index c0d2ddb2e114..95eead74c914 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -749,9 +749,22 @@ protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alre $request->fixLockedPackage($package); } + $rootRequires = $this->package->getRequires(); + if ($this->devMode) { + $rootRequires = array_merge($rootRequires, $this->package->getDevRequires()); + } + foreach ($rootRequires as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) { - $request->requireName($link->getTarget(), $link->getConstraint()); + if (!isset($rootRequires[$link->getTarget()])) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } } + unset($rootRequires, $link); $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes); diff --git a/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test new file mode 100644 index 000000000000..73b34f28fee8 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test @@ -0,0 +1,39 @@ +--TEST-- +Test that install checks missing requirements from both composer.json if the lock file is outdated. +--COMPOSER-- +{ + "require": { + "some/dep": "dev-main", + "some/dep2": "dev-main" + } +} +--LOCK-- +{ + "content-hash": "old", + "packages": [ + {"name": "some/dep", "version": "dev-foo"} + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +install +--EXPECT-EXIT-CODE-- +4 +--EXPECT-OUTPUT-- +Installing dependencies from lock file (including require-dev) +Verifying lock file contents can be installed on current platform. +Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update `. +- Required package "some/dep" is in the lock file as "dev-foo" but that does not satisfy your constraint "dev-main". +- Required package "some/dep2" is not present in the lock file. +This usually happens when composer files are incorrectly merged or the composer.json file is manually edited. +Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md +and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r + +--EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test new file mode 100644 index 000000000000..a7ff7b3779b5 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test @@ -0,0 +1,46 @@ +--TEST-- +Test that install checks platform requirements from both composer.json AND composer.lock even if the lock file is outdated. +Platform requires appearing in both lock and composer.json will be checked using the composer.json as source of truth (see ext-foo). +--COMPOSER-- +{ + "require": { + "php-64bit": "^25", + "ext-foo": "^10" + } +} +--LOCK-- +{ + "content-hash": "old", + "packages": [ + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": {"php": "^20", "ext-foo": "^5"}, + "platform-dev": [] +} +--RUN-- +install +--EXPECT-EXIT-CODE-- +2 +--EXPECT-OUTPUT-- +Installing dependencies from lock file (including require-dev) +Verifying lock file contents can be installed on current platform. +Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update `. +Your lock file does not contain a compatible set of packages. Please run composer update. + + Problem 1 + - Root composer.json requires php-64bit ^25 but your php-64bit version (%s) does not satisfy that requirement. + Problem 2 + - Root composer.json requires PHP extension ext-foo ^10 but it is missing from your system. Install or enable PHP's foo extension. + Problem 3 + - Root composer.json requires php ^20 but your php version (%s) does not satisfy that requirement. + +To enable extensions, verify that they are enabled in your .ini files: +__inilist__ +You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode. +Alternatively, you can run Composer with `--ignore-platform-req=ext-foo` to temporarily ignore these required extensions. +--EXPECT-- From a5285d7a76b6ace91d6f4216e93ff11cc2ac1d0b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 31 May 2024 21:47:19 +0200 Subject: [PATCH 098/257] Update deps --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 69a37ac28071..1c9fb0aca9f7 100644 --- a/composer.lock +++ b/composer.lock @@ -84,16 +84,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "5bb7f1e135ccc696bdccb7ec945403fcaac144d5" + "reference": "acd227952154850d0bb7d65caa4f9edf9cd806a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/5bb7f1e135ccc696bdccb7ec945403fcaac144d5", - "reference": "5bb7f1e135ccc696bdccb7ec945403fcaac144d5", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/acd227952154850d0bb7d65caa4f9edf9cd806a7", + "reference": "acd227952154850d0bb7d65caa4f9edf9cd806a7", "shasum": "" }, "require": { @@ -137,7 +137,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.3.1" + "source": "https://github.com/composer/class-map-generator/tree/1.3.2" }, "funding": [ { @@ -153,7 +153,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:31:05+00:00" + "time": "2024-05-31T19:45:56+00:00" }, { "name": "composer/metadata-minifier", From ee2c9afdc86ef3f06a4bd49b1fea7d1d636afc92 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 31 May 2024 21:54:41 +0200 Subject: [PATCH 099/257] Update deps --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1e38b523aafa..d17fac46fab1 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require": { "php": "^7.2.5 || ^8.0", "composer/ca-bundle": "^1.0", - "composer/class-map-generator": "^1.3.1", + "composer/class-map-generator": "^1.3.2", "composer/metadata-minifier": "^1.0", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", diff --git a/composer.lock b/composer.lock index 1c9fb0aca9f7..b31a14b812c0 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "1322de6eee2357fec92034b7b41e6512", + "content-hash": "4b94cd51ff16da359c4a511f30f57aaa", "packages": [ { "name": "composer/ca-bundle", From 137ec17c0a9288bf4bc3bda55af149173db461e0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 11:37:52 +0200 Subject: [PATCH 100/257] Fix empty type support in init command, fixes #11999 --- src/Composer/Command/InitCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 5f6773daf3dd..81233a08481e 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -378,11 +378,14 @@ static function ($value) use ($minimumStability) { ); $input->setOption('stability', $minimumStability); - $type = $input->getOption('type') ?: false; + $type = $input->getOption('type'); $type = $io->ask( 'Package Type (e.g. library, project, metapackage, composer-plugin) ['.$type.']: ', $type ); + if ($type === '' || $type === false) { + $type = null; + } $input->setOption('type', $type); if (null === $license = $input->getOption('license')) { From f3e877a80e42e61448aca52541388e299a23ca17 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 14:02:28 +0200 Subject: [PATCH 101/257] Update deps --- composer.json | 2 +- composer.lock | 115 ++++++++++++++++++++++++++------------------------ 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/composer.json b/composer.json index d17fac46fab1..c1e52868d092 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require": { "php": "^7.2.5 || ^8.0", "composer/ca-bundle": "^1.0", - "composer/class-map-generator": "^1.3.2", + "composer/class-map-generator": "^1.3.3", "composer/metadata-minifier": "^1.0", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", diff --git a/composer.lock b/composer.lock index b31a14b812c0..5fbe54a5507e 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "4b94cd51ff16da359c4a511f30f57aaa", + "content-hash": "4c03575acd7a47b4a22ffd1947c31b91", "packages": [ { "name": "composer/ca-bundle", @@ -84,16 +84,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "acd227952154850d0bb7d65caa4f9edf9cd806a7" + "reference": "61804f9973685ec7bead0fb7fe022825e3cd418e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/acd227952154850d0bb7d65caa4f9edf9cd806a7", - "reference": "acd227952154850d0bb7d65caa4f9edf9cd806a7", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/61804f9973685ec7bead0fb7fe022825e3cd418e", + "reference": "61804f9973685ec7bead0fb7fe022825e3cd418e", "shasum": "" }, "require": { @@ -137,7 +137,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.3.2" + "source": "https://github.com/composer/class-map-generator/tree/1.3.3" }, "funding": [ { @@ -153,7 +153,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T19:45:56+00:00" + "time": "2024-06-10T11:53:54+00:00" }, { "name": "composer/metadata-minifier", @@ -938,16 +938,16 @@ }, { "name": "symfony/console", - "version": "v5.4.39", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1" + "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f3e591c48688a0cfa1a3296205926c05e84b22b1", - "reference": "f3e591c48688a0cfa1a3296205926c05e84b22b1", + "url": "https://api.github.com/repos/symfony/console/zipball/aa73115c0c24220b523625bfcfa655d7d73662dd", + "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd", "shasum": "" }, "require": { @@ -1017,7 +1017,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.39" + "source": "https://github.com/symfony/console/tree/v5.4.40" }, "funding": [ { @@ -1033,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1104,23 +1104,25 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.39", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e6edd875d5d39b03de51f3c3951148cfa79a4d12" + "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e6edd875d5d39b03de51f3c3951148cfa79a4d12", - "reference": "e6edd875d5d39b03de51f3c3951148cfa79a4d12", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/26dd9912df6940810ea00f8f53ad48d6a3424995", + "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { "symfony/process": "^5.4|^6.4" }, "type": "library", @@ -1149,7 +1151,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.39" + "source": "https://github.com/symfony/filesystem/tree/v5.4.40" }, "funding": [ { @@ -1165,20 +1167,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/finder", - "version": "v5.4.39", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f6a96e4fcd468a25fede16ee665f50ced856bd0a" + "reference": "f51cff4687547641c7d8180d74932ab40b2205ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f6a96e4fcd468a25fede16ee665f50ced856bd0a", - "reference": "f6a96e4fcd468a25fede16ee665f50ced856bd0a", + "url": "https://api.github.com/repos/symfony/finder/zipball/f51cff4687547641c7d8180d74932ab40b2205ce", + "reference": "f51cff4687547641c7d8180d74932ab40b2205ce", "shasum": "" }, "require": { @@ -1212,7 +1214,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.39" + "source": "https://github.com/symfony/finder/tree/v5.4.40" }, "funding": [ { @@ -1228,7 +1230,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1782,16 +1784,16 @@ }, { "name": "symfony/process", - "version": "v5.4.39", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "85a554acd7c28522241faf2e97b9541247a0d3d5" + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/85a554acd7c28522241faf2e97b9541247a0d3d5", - "reference": "85a554acd7c28522241faf2e97b9541247a0d3d5", + "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", "shasum": "" }, "require": { @@ -1824,7 +1826,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.39" + "source": "https://github.com/symfony/process/tree/v5.4.40" }, "funding": [ { @@ -1840,7 +1842,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/service-contracts", @@ -1927,16 +1929,16 @@ }, { "name": "symfony/string", - "version": "v5.4.39", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "495e71bae5862308051b9e63cc3e34078eed83ef" + "reference": "142877285aa974a6f7685e292ab5ba9aae86b143" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/495e71bae5862308051b9e63cc3e34078eed83ef", - "reference": "495e71bae5862308051b9e63cc3e34078eed83ef", + "url": "https://api.github.com/repos/symfony/string/zipball/142877285aa974a6f7685e292ab5ba9aae86b143", + "reference": "142877285aa974a6f7685e292ab5ba9aae86b143", "shasum": "" }, "require": { @@ -1993,7 +1995,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.39" + "source": "https://github.com/symfony/string/tree/v5.4.40" }, "funding": [ { @@ -2009,22 +2011,22 @@ "type": "tidelift" } ], - "time": "2024-04-18T08:26:06+00:00" + "time": "2024-05-31T14:33:22+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.3", + "version": "1.11.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5" + "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e64220a05c1209fc856d58e789c3b7a32c0bb9a5", - "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9100a76ce8015b9aa7125b9171ae3a76887b6c82", + "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82", "shasum": "" }, "require": { @@ -2069,7 +2071,7 @@ "type": "github" } ], - "time": "2024-05-31T13:53:37+00:00" + "time": "2024-06-06T12:19:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2221,16 +2223,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28" + "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/af6ae0f4b91bc080265e80776af26da3e5befb28", - "reference": "af6ae0f4b91bc080265e80776af26da3e5befb28", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/bca27f1701fc1a297749e6c2a1e3da4462c1a6af", + "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af", "shasum": "" }, "require": { @@ -2287,22 +2289,22 @@ "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.4.3" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.4" }, - "time": "2024-05-30T15:01:27+00:00" + "time": "2024-06-07T09:43:24+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.1.0", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "1168ef27edb094fe4ba755ec937cf62a6dff84eb" + "reference": "3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/1168ef27edb094fe4ba755ec937cf62a6dff84eb", - "reference": "1168ef27edb094fe4ba755ec937cf62a6dff84eb", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16", + "reference": "3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16", "shasum": "" }, "require": { @@ -2334,7 +2336,8 @@ "Symfony\\Bridge\\PhpUnit\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2354,7 +2357,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.0" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.1" }, "funding": [ { @@ -2370,7 +2373,7 @@ "type": "tidelift" } ], - "time": "2024-05-17T10:55:18+00:00" + "time": "2024-06-04T06:50:37+00:00" } ], "aliases": [], From fa3b9582c38fa2b7ed14989101a5d1e1788d787c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 14:48:02 +0200 Subject: [PATCH 102/257] Fix secure-http check to avoid bypass using emojis --- src/Composer/Config.php | 4 ++-- tests/Composer/Test/ConfigTest.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 8d2885a3cc1c..165761d9da26 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -584,8 +584,8 @@ private function disableRepoByName(string $name): void */ public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []): void { - // Return right away if the URL is malformed or custom (see issue #5173) - if (false === filter_var($url, FILTER_VALIDATE_URL)) { + // Return right away if the URL is malformed or custom (see issue #5173), but only for non-HTTP(S) URLs + if (false === filter_var($url, FILTER_VALIDATE_URL) && !Preg::isMatch('{^https?://}', $url)) { return; } diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index c8677962be5a..1f35fbd6e078 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -294,6 +294,7 @@ public static function prohibitedUrlProvider(): array 'http://packagist.org', 'http://10.1.0.1/satis', 'http://127.0.0.1/satis', + 'http://💛@example.org', 'svn://localhost/trunk', 'svn://will.not.resolve/trunk', 'svn://192.168.0.1/trunk', From 6bd43dff859c597c09bd03a7e7d6443822d0a396 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 14:56:13 +0200 Subject: [PATCH 103/257] Merge pull request from GHSA-v9qv-c7wm-wgmf --- src/Composer/Package/Version/VersionGuesser.php | 15 ++++++++------- .../Test/Package/Version/VersionGuesserTest.php | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 2b2b19706506..72fc799a7049 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -173,7 +173,7 @@ private function guessGitVersion(array $packageConfig, string $path): array $featurePrettyVersion = $prettyVersion; // try to find the best (nearest) version branch to assume this feature's version - $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path); + $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path); $version = $result['version']; $prettyVersion = $result['pretty_version']; } @@ -248,7 +248,7 @@ private function guessHgVersion(array $packageConfig, string $path): ?array $branches = array_map('strval', array_keys($driver->getBranches())); // try to find the best (nearest) version branch to assume this feature's version - $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path); + $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path); $result['commit'] = ''; $result['feature_version'] = $version; $result['feature_pretty_version'] = $version; @@ -261,13 +261,12 @@ private function guessHgVersion(array $packageConfig, string $path): ?array /** * @param array $packageConfig - * @param string[] $branches - * - * @phpstan-param non-empty-string $scmCmdline + * @param list $branches + * @param list $scmCmdline * * @return array{version: string|null, pretty_version: string|null} */ - private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, string $scmCmdline, string $path): array + private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array { $prettyVersion = $version; @@ -309,7 +308,9 @@ private function guessFeatureVersion(array $packageConfig, ?string $version, arr continue; } - $cmdLine = str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $scmCmdline); + $cmdLine = array_map(static function (string $component) use ($candidate, $branch) { + return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component); + }, $scmCmdline); $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void { if (!$process->isSuccessful()) { return; diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php index 676ef24202c0..c6e01251b369 100644 --- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -117,7 +117,7 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF 'stdout' => " arbitrary $commitHash Commit message\n* feature $anotherCommitHash Another message\n", ], [ - 'cmd' => 'git rev-list arbitrary..feature', + 'cmd' => ['git', 'rev-list', 'arbitrary..feature'], 'stdout' => "$anotherCommitHash\n", ], ], true); @@ -147,7 +147,7 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF 'stdout' => " latest-testing $commitHash Commit message\n* feature $anotherCommitHash Another message\n", ], [ - 'cmd' => 'git rev-list latest-testing..feature', + 'cmd' => ['git', 'rev-list', 'latest-testing..feature'], 'stdout' => "$anotherCommitHash\n", ], ], true); @@ -352,7 +352,7 @@ public function testRemoteBranchesAreSelected(): void "remotes/origin/1.5 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n", ], [ - 'cmd' => 'git rev-list remotes/origin/1.5..feature-branch', + 'cmd' => ['git', 'rev-list', 'remotes/origin/1.5..feature-branch'], 'stdout' => "\n", ], ], true); From ee28354ca8d33c15949ad7de2ce6656ba3f68704 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 14:56:42 +0200 Subject: [PATCH 104/257] Merge pull request from GHSA-47f6-5gq3-vx9c --- src/Composer/Downloader/GitDownloader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 0840219d0fe6..b29782d1af17 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -294,9 +294,9 @@ public function getUnpushedChanges(PackageInterface $package, string $path): ?st $unpushedChanges = null; } foreach ($remoteBranches as $remoteBranch) { - $command = sprintf('git diff --name-status %s...%s --', $remoteBranch, $branch); + $command = ['git', 'diff', '--name-status', $remoteBranch.'...'.$branch, '--']; if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $output = trim($output); From 5aa7b03b9d6cd0b20b32733298b6e97a2b11b287 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 15:09:06 +0200 Subject: [PATCH 105/257] Fix test --- tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php index a66377f9e63d..4a12eab82a72 100644 --- a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php @@ -130,7 +130,7 @@ public function testFeatureBranchPrettyVersion(): void 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'stdout' => "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n", ], - 'git rev-list master..latest-production', + ['cmd' => ['git', 'rev-list', 'master..latest-production']], ], true); $config = new Config; From 3130a7455a9fb53e14a08d2c9d6d904810159df1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 21:28:19 +0200 Subject: [PATCH 106/257] Fix windows parameter encoding to prevent abuse of unicode characters with best fit encoding conversion --- src/Composer/Util/ProcessExecutor.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index b546b1529d78..6694a6e874bb 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -495,7 +495,9 @@ private static function escapeArgument($argument): string } // New lines break cmd.exe command parsing - $argument = strtr($argument, "\n", ' '); + // and special chars like the fullwidth quote can be used to break out + // of parameter encoding via "Best Fit" encoding conversion + $argument = strtr($argument, ["\n" => ' ', '"' => '"', ':' => ':', '/' => '/']); // In addition to whitespace, commas need quoting to preserve paths $quote = strpbrk($argument, " \t,") !== false; From ad8985e6b07b54f0fb9c1f1dc50248048406cf5a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 21:44:47 +0200 Subject: [PATCH 107/257] Update changelog --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 189759a5a5c2..257939f8dfaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +### [2.7.7] 2024-06-10 + + * Security: Fixed command injection via malicious git branch name (GHSA-47f6-5gq3-vx9c / CVE-2024-35241) + * Security: Fixed multiple command injections via malicious git/hg branch names (GHSA-v9qv-c7wm-wgmf / CVE-2024-35242) + * Fixed PSR violations for classes not matching the namespace of a rule being hidden, this may lead to new violations being shown (#11957) + * Fixed UX when a plugin is still in vendor dir but is not required nor allowed anymore after changing branches (#12000) + * Fixed new platform requirements from composer.json not being checked if the lock file is outdated (#12001) + * Fixed secure-http checks that could be bypassed by using malformed URL formats (fa3b9582c) + * Fixed Filesystem::isLocalPath including windows-specific checks on linux (3c37a67c) + * Fixed perforce argument escaping (3773f775) + * Fixed handling of zip bombs when extracting archives (de5f7e32) + * Fixed Windows command parameter escaping to prevent abuse of unicode characters with best fit encoding conversion (3130a7455) + * Fixed ability for `config` command to remove autoload keys (#11967) + * Fixed empty `type` support in `init` command (#11999) + * Fixed git clone errors when `safe.bareRepository` is set to `strict` in the git config (#11969) + * Fixed regression showing network errors on PHP <8.1 (#11974) + * Fixed some color bleed from a few warnings (#11972) + ### [2.7.6] 2024-05-04 * Fixed regression when script handlers add an autoloader which uses a private callback (#11960) @@ -1870,6 +1888,7 @@ * Initial release +[2.7.7]: https://github.com/composer/composer/compare/2.7.6...2.7.7 [2.7.6]: https://github.com/composer/composer/compare/2.7.5...2.7.6 [2.7.5]: https://github.com/composer/composer/compare/2.7.4...2.7.5 [2.7.4]: https://github.com/composer/composer/compare/2.7.3...2.7.4 From 04a63b324fef7f019dde83a3153c97219b4c794b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 22:08:29 +0200 Subject: [PATCH 108/257] Add more characters for best fit encoding protection --- src/Composer/Util/ProcessExecutor.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 6694a6e874bb..d6a983ef9be4 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -497,7 +497,21 @@ private static function escapeArgument($argument): string // New lines break cmd.exe command parsing // and special chars like the fullwidth quote can be used to break out // of parameter encoding via "Best Fit" encoding conversion - $argument = strtr($argument, ["\n" => ' ', '"' => '"', ':' => ':', '/' => '/']); + $argument = strtr($argument, [ + "\n" => ' ', + "\u{ff02}" => '"', + "\u{02ba}" => '"', + "\u{301d}" => '"', + "\u{301e}" => '"', + "\u{030e}" => '"', + "\u{ff1a}" => ':', + "\u{0589}" => ':', + "\u{2236}" => ':', + "\u{ff0f}" => '/', + "\u{2044}" => '/', + "\u{2215}" => '/', + "\u{00b4}" => '/', + ]); // In addition to whitespace, commas need quoting to preserve paths $quote = strpbrk($argument, " \t,") !== false; From e354a8d290c67a5c5b152d3f710823b62daaecd2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 22:10:31 +0200 Subject: [PATCH 109/257] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 257939f8dfaa..694cc4744b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ * Fixed Filesystem::isLocalPath including windows-specific checks on linux (3c37a67c) * Fixed perforce argument escaping (3773f775) * Fixed handling of zip bombs when extracting archives (de5f7e32) - * Fixed Windows command parameter escaping to prevent abuse of unicode characters with best fit encoding conversion (3130a7455) + * Fixed Windows command parameter escaping to prevent abuse of unicode characters with best fit encoding conversion (3130a7455, 04a63b324) * Fixed ability for `config` command to remove autoload keys (#11967) * Fixed empty `type` support in `init` command (#11999) * Fixed git clone errors when `safe.bareRepository` is set to `strict` in the git config (#11969) From 291942978f39435cf904d33739f98d7d4eca7b23 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 22:11:12 +0200 Subject: [PATCH 110/257] Release 2.7.7 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..94bbbc1eec44 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.7'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-06-10 22:11:12'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 01ce481f22a2f1652ac11fb71d580682b33acf0c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 22:11:13 +0200 Subject: [PATCH 111/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 94bbbc1eec44..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.7'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-06-10 22:11:12'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From a4a83a341adb17741d6529ff8965c3f67455b70e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Jun 2024 22:14:46 +0200 Subject: [PATCH 112/257] Update changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 694cc4744b83..def2885fbff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,14 @@ * Security: Fixed command injection via malicious git branch name (GHSA-47f6-5gq3-vx9c / CVE-2024-35241) * Security: Fixed multiple command injections via malicious git/hg branch names (GHSA-v9qv-c7wm-wgmf / CVE-2024-35242) + * Security: Fixed secure-http checks that could be bypassed by using malformed URL formats (fa3b9582c) + * Security: Fixed Filesystem::isLocalPath including windows-specific checks on linux (3c37a67c) + * Security: Fixed perforce argument escaping (3773f775) + * Security: Fixed handling of zip bombs when extracting archives (de5f7e32) + * Security: Fixed Windows command parameter escaping to prevent abuse of unicode characters with best fit encoding conversion (3130a7455, 04a63b324) * Fixed PSR violations for classes not matching the namespace of a rule being hidden, this may lead to new violations being shown (#11957) * Fixed UX when a plugin is still in vendor dir but is not required nor allowed anymore after changing branches (#12000) * Fixed new platform requirements from composer.json not being checked if the lock file is outdated (#12001) - * Fixed secure-http checks that could be bypassed by using malformed URL formats (fa3b9582c) - * Fixed Filesystem::isLocalPath including windows-specific checks on linux (3c37a67c) - * Fixed perforce argument escaping (3773f775) - * Fixed handling of zip bombs when extracting archives (de5f7e32) - * Fixed Windows command parameter escaping to prevent abuse of unicode characters with best fit encoding conversion (3130a7455, 04a63b324) * Fixed ability for `config` command to remove autoload keys (#11967) * Fixed empty `type` support in `init` command (#11999) * Fixed git clone errors when `safe.bareRepository` is set to `strict` in the git config (#11969) From 11e5237ad9d9e8f29bdc57d946f87c816320d863 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 12 Jun 2024 16:14:13 +0200 Subject: [PATCH 113/257] Update deps --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 5fbe54a5507e..a9c400dfae6c 100644 --- a/composer.lock +++ b/composer.lock @@ -84,16 +84,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.3.3", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "61804f9973685ec7bead0fb7fe022825e3cd418e" + "reference": "b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/61804f9973685ec7bead0fb7fe022825e3cd418e", - "reference": "61804f9973685ec7bead0fb7fe022825e3cd418e", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3", + "reference": "b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3", "shasum": "" }, "require": { @@ -137,7 +137,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.3.3" + "source": "https://github.com/composer/class-map-generator/tree/1.3.4" }, "funding": [ { @@ -153,7 +153,7 @@ "type": "tidelift" } ], - "time": "2024-06-10T11:53:54+00:00" + "time": "2024-06-12T14:13:04+00:00" }, { "name": "composer/metadata-minifier", From dffa0a73a7f34ee1a11db07cfeed9c2b7a6a3c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BB=B3nh=20Nguy=E1=BB=85n?= Date: Tue, 2 Jul 2024 05:30:29 +0700 Subject: [PATCH 114/257] Fix typo for IO tests (#12027) --- tests/Composer/Test/IO/ConsoleIOTest.php | 2 +- tests/Composer/Test/IO/NullIOTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index ddac19b27c30..26f04e39f50e 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -255,7 +255,7 @@ public function testSelect(): void self::assertEquals(['1'], $result); } - public function testSetAndgetAuthentication(): void + public function testSetAndGetAuthentication(): void { $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); diff --git a/tests/Composer/Test/IO/NullIOTest.php b/tests/Composer/Test/IO/NullIOTest.php index 987ea7071f6a..c33a372bf1a5 100644 --- a/tests/Composer/Test/IO/NullIOTest.php +++ b/tests/Composer/Test/IO/NullIOTest.php @@ -24,7 +24,7 @@ public function testIsInteractive(): void self::assertFalse($io->isInteractive()); } - public function testhasAuthentication(): void + public function testHasAuthentication(): void { $io = new NullIO(); @@ -38,7 +38,7 @@ public function testAskAndHideAnswer(): void self::assertNull($io->askAndHideAnswer('foo')); } - public function testgetAuthentications(): void + public function testGetAuthentications(): void { $io = new NullIO(); @@ -58,7 +58,7 @@ public function testAskConfirmation(): void { $io = new NullIO(); - self::assertEquals(false, $io->askConfirmation('bar', false)); + self::assertFalse($io->askConfirmation('bar', false)); } public function testAskAndValidate(): void From e61d4ad9866570bff39ce0f95a7fc2c950c63cc5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 6 Jul 2024 23:35:00 +0200 Subject: [PATCH 115/257] Update deps --- .github/workflows/phpstan.yml | 2 +- composer.json | 2 +- composer.lock | 175 +++++++++--------- .../{baseline-8.1.neon => baseline-8.3.neon} | 10 + phpstan/baseline.neon | 34 ++-- phpstan/config.neon | 2 + phpstan/ignore-by-php-version.neon.php | 2 +- src/Composer/Command/SelfUpdateCommand.php | 2 +- src/Composer/Console/Input/InputArgument.php | 2 +- src/Composer/Console/Input/InputOption.php | 2 +- src/Composer/DependencyResolver/Rule.php | 4 +- src/Composer/Util/Platform.php | 6 +- 12 files changed, 121 insertions(+), 122 deletions(-) rename phpstan/{baseline-8.1.neon => baseline-8.3.neon} (95%) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index a4c25379a3ba..501b14840954 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -27,7 +27,7 @@ jobs: include: - php-version: "7.2" experimental: false - - php-version: "8.1" + - php-version: "8.3" experimental: true fail-fast: false diff --git a/composer.json b/composer.json index c1e52868d092..6bdfa04ad823 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", - "justinrainbow/json-schema": "^5.2.11", + "justinrainbow/json-schema": "^5.3", "psr/log": "^1.0 || ^2.0 || ^3.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", diff --git a/composer.lock b/composer.lock index a9c400dfae6c..3a0451251acc 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "4c03575acd7a47b4a22ffd1947c31b91", + "content-hash": "6a83e69e5c06f06ecba7db6c83213f9d", "packages": [ { "name": "composer/ca-bundle", @@ -524,20 +524,20 @@ }, { "name": "justinrainbow/json-schema", - "version": "v5.2.13", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", @@ -548,11 +548,6 @@ "bin/validate-json" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -588,9 +583,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" }, - "time": "2023-09-26T02:20:38+00:00" + "time": "2024-07-06T21:00:26+00:00" }, { "name": "psr/container", @@ -938,16 +933,16 @@ }, { "name": "symfony/console", - "version": "v5.4.40", + "version": "v5.4.41", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd" + "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/aa73115c0c24220b523625bfcfa655d7d73662dd", - "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd", + "url": "https://api.github.com/repos/symfony/console/zipball/6473d441a913cb997123b59ff2dbe3d1cf9e11ba", + "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba", "shasum": "" }, "require": { @@ -1017,7 +1012,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.40" + "source": "https://github.com/symfony/console/tree/v5.4.41" }, "funding": [ { @@ -1033,7 +1028,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-06-28T07:48:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1104,16 +1099,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.40", + "version": "v5.4.41", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995" + "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/26dd9912df6940810ea00f8f53ad48d6a3424995", - "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6d29dd9340b372fa603f04e6df4dd76bb808591e", + "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e", "shasum": "" }, "require": { @@ -1151,7 +1146,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.40" + "source": "https://github.com/symfony/filesystem/tree/v5.4.41" }, "funding": [ { @@ -1167,7 +1162,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-06-28T09:36:24+00:00" }, { "name": "symfony/finder", @@ -1234,16 +1229,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -1293,7 +1288,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -1309,20 +1304,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { @@ -1371,7 +1366,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -1387,20 +1382,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { @@ -1452,7 +1447,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -1468,20 +1463,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -1532,7 +1527,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -1548,20 +1543,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", "shasum": "" }, "require": { @@ -1608,7 +1603,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" }, "funding": [ { @@ -1624,20 +1619,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -1688,7 +1683,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -1704,20 +1699,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", - "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", "shasum": "" }, "require": { @@ -1764,7 +1759,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" }, "funding": [ { @@ -1780,7 +1775,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/process", @@ -1929,16 +1924,16 @@ }, { "name": "symfony/string", - "version": "v5.4.40", + "version": "v5.4.41", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "142877285aa974a6f7685e292ab5ba9aae86b143" + "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/142877285aa974a6f7685e292ab5ba9aae86b143", - "reference": "142877285aa974a6f7685e292ab5ba9aae86b143", + "url": "https://api.github.com/repos/symfony/string/zipball/065a9611e0b1fd2197a867e1fb7f2238191b7096", + "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096", "shasum": "" }, "require": { @@ -1995,7 +1990,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.40" + "source": "https://github.com/symfony/string/tree/v5.4.41" }, "funding": [ { @@ -2011,22 +2006,22 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-06-28T09:20:55+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.4", + "version": "1.11.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82" + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9100a76ce8015b9aa7125b9171ae3a76887b6c82", - "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", "shasum": "" }, "require": { @@ -2071,7 +2066,7 @@ "type": "github" } ], - "time": "2024-06-06T12:19:22+00:00" + "time": "2024-07-06T11:17:41+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2223,16 +2218,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.4", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af" + "reference": "1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/bca27f1701fc1a297749e6c2a1e3da4462c1a6af", - "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d", + "reference": "1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d", "shasum": "" }, "require": { @@ -2289,22 +2284,22 @@ "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.4.4" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.5" }, - "time": "2024-06-07T09:43:24+00:00" + "time": "2024-06-26T12:19:42+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.1.1", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16" + "reference": "8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16", - "reference": "3e1cb8c4dee341cfe96ae9fe29b1acda52a6bb16", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8", + "reference": "8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8", "shasum": "" }, "require": { @@ -2357,7 +2352,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.1" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.2" }, "funding": [ { @@ -2373,7 +2368,7 @@ "type": "tidelift" } ], - "time": "2024-06-04T06:50:37+00:00" + "time": "2024-06-25T19:55:06+00:00" } ], "aliases": [], diff --git a/phpstan/baseline-8.1.neon b/phpstan/baseline-8.3.neon similarity index 95% rename from phpstan/baseline-8.1.neon rename to phpstan/baseline-8.3.neon index 1a2a74b812df..c6e16fee647b 100644 --- a/phpstan/baseline-8.1.neon +++ b/phpstan/baseline-8.3.neon @@ -55,6 +55,16 @@ parameters: count: 2 path: ../src/Composer/Console/Application.php + - + message: "#^Parameter \\#2 \\$mode of method Symfony\\\\Component\\\\Console\\\\Input\\\\InputArgument\\:\\:__construct\\(\\) expects int\\<0, 7\\>\\|null, int\\|null given\\.$#" + count: 1 + path: ../src/Composer/Console/Input/InputArgument.php + + - + message: "#^Parameter \\#3 \\$mode of method Symfony\\\\Component\\\\Console\\\\Input\\\\InputOption\\:\\:__construct\\(\\) expects int\\<0, 31\\>\\|null, int\\|null given\\.$#" + count: 1 + path: ../src/Composer/Console/Input/InputOption.php + - message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(string, string\\)\\: int, 'version_compare' given\\.$#" count: 2 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index d3122d737706..7d1258a3462d 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -497,7 +497,7 @@ parameters: - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 7 + count: 6 path: ../src/Composer/Command/InitCommand.php - @@ -1276,7 +1276,7 @@ parameters: path: ../src/Composer/DependencyResolver/Rule.php - - message: "#^Method Composer\\\\DependencyResolver\\\\Rule\\:\\:getReason\\(\\) should return 2\\|3\\|6\\|7\\|10\\|12\\|13\\|14 but returns int\\.$#" + message: "#^Method Composer\\\\DependencyResolver\\\\Rule\\:\\:getReason\\(\\) should return 2\\|3\\|6\\|7\\|10\\|12\\|13\\|14 but returns int\\<0, 255\\>\\.$#" count: 1 path: ../src/Composer/DependencyResolver/Rule.php @@ -1295,11 +1295,6 @@ parameters: count: 1 path: ../src/Composer/DependencyResolver/Rule.php - - - message: "#^Only booleans are allowed in a negated boolean, int given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - message: "#^Only booleans are allowed in an if condition, Composer\\\\Repository\\\\LockArrayRepository\\|null given\\.$#" count: 2 @@ -2485,6 +2480,16 @@ parameters: count: 1 path: ../src/Composer/Package/BasePackage.php + - + message: "#^Method Composer\\\\Package\\\\BasePackage\\:\\:packageNameToRegexp\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: ../src/Composer/Package/BasePackage.php + + - + message: "#^Method Composer\\\\Package\\\\BasePackage\\:\\:packageNamesToRegexp\\(\\) should return non\\-empty\\-string but returns string\\.$#" + count: 1 + path: ../src/Composer/Package/BasePackage.php + - message: "#^Only booleans are allowed in &&, Composer\\\\Repository\\\\RepositoryInterface\\|null given on the left side\\.$#" count: 1 @@ -4403,21 +4408,11 @@ parameters: count: 1 path: ../src/Composer/Util/Perforce.php - - - message: "#^Cannot access offset 'dir' on array\\|false\\.$#" - count: 1 - path: ../src/Composer/Util/Platform.php - - message: "#^Casting to string something that's already string\\.$#" count: 1 path: ../src/Composer/Util/Platform.php - - - message: "#^Only booleans are allowed in &&, array\\|false given on the left side\\.$#" - count: 1 - path: ../src/Composer/Util/Platform.php - - message: "#^Only booleans are allowed in &&, string\\|false given on the right side\\.$#" count: 1 @@ -4578,11 +4573,6 @@ parameters: count: 1 path: ../src/Composer/Util/RemoteFilesystem.php - - - message: "#^Parameter &\\$responseHeaders @param\\-out type of method Composer\\\\Util\\\\RemoteFilesystem\\:\\:getRemoteContents\\(\\) expects list\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Util/RemoteFilesystem.php - - message: "#^Property Composer\\\\Util\\\\RemoteFilesystem\\:\\:\\$scheme \\(string\\) does not accept string\\|false\\|null\\.$#" count: 1 diff --git a/phpstan/config.neon b/phpstan/config.neon index 6790168b916f..8a3062ba8e88 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -5,6 +5,8 @@ includes: - ../vendor/phpstan/phpstan-strict-rules/rules.neon - ../vendor/phpstan/phpstan-symfony/extension.neon - ../vendor/phpstan/phpstan-symfony/rules.neon + # TODO when requiring php 7.4+ we can use this + #- ../vendor/staabm/phpstan-todo-by/extension.neon - ./rules.neon # Composer-specific PHPStan extensions, can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon' in your phpstan config - ./baseline.neon - ./ignore-by-php-version.neon.php diff --git a/phpstan/ignore-by-php-version.neon.php b/phpstan/ignore-by-php-version.neon.php index 21212110c723..14a209ea3739 100644 --- a/phpstan/ignore-by-php-version.neon.php +++ b/phpstan/ignore-by-php-version.neon.php @@ -3,7 +3,7 @@ // more inspiration at https://github.com/phpstan/phpstan-src/blob/master/build/ignore-by-php-version.neon.php $includes = []; if (PHP_VERSION_ID >= 80000) { - $includes[] = __DIR__ . '/baseline-8.1.neon'; + $includes[] = __DIR__ . '/baseline-8.3.neon'; } $config['includes'] = $includes; diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 1a8797a5bc2b..bd006ea88a34 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -146,7 +146,7 @@ class_exists('Composer\Downloader\FilesystemException'); $homeDirOwnerId = fileowner($home); if (is_array($composerUser) && $homeDirOwnerId !== false) { $homeOwner = posix_getpwuid($homeDirOwnerId); - if (is_array($homeOwner) && isset($composerUser['name'], $homeOwner['name']) && $composerUser['name'] !== $homeOwner['name']) { + if (is_array($homeOwner) && $composerUser['name'] !== $homeOwner['name']) { $io->writeError('You are running Composer as "'.$composerUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"'); } } diff --git a/src/Composer/Console/Input/InputArgument.php b/src/Composer/Console/Input/InputArgument.php index a64724d6be46..19aff8c33b5b 100644 --- a/src/Composer/Console/Input/InputArgument.php +++ b/src/Composer/Console/Input/InputArgument.php @@ -26,7 +26,7 @@ * * @internal * - * TODO drop when PHP 8.1 / symfony 6.1+ can be required + * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required */ class InputArgument extends BaseInputArgument { diff --git a/src/Composer/Console/Input/InputOption.php b/src/Composer/Console/Input/InputOption.php index c742daee7bb0..b5ff333cd672 100644 --- a/src/Composer/Console/Input/InputOption.php +++ b/src/Composer/Console/Input/InputOption.php @@ -26,7 +26,7 @@ * * @internal * - * TODO drop when PHP 8.1 / symfony 6.1+ can be required + * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required */ class InputOption extends BaseInputOption { diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 1b2f1aa951e6..d65164154cbf 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -137,12 +137,12 @@ public function enable(): void public function isDisabled(): bool { - return (bool) (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); + return 0 !== (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } public function isEnabled(): bool { - return !(($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); + return 0 === (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } abstract public function isAssertion(): bool; diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 45060c85c4e2..d7735ebc1efe 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -129,7 +129,9 @@ public static function getUserDirectory(): string if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) { $info = posix_getpwuid(posix_getuid()); - return $info['dir']; + if (is_array($info)) { + return $info['dir']; + } } throw new \RuntimeException('Could not determine user directory'); @@ -252,7 +254,7 @@ private static function isVirtualBoxGuest(): bool if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { $processUser = posix_getpwuid(posix_geteuid()); - if ($processUser && $processUser['name'] === 'vagrant') { + if (is_array($processUser) && $processUser['name'] === 'vagrant') { return self::$isVirtualBoxGuest = true; } } From b2832867e6d264a98bd3983e86359e85c0ab9da1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jul 2024 09:47:37 +0200 Subject: [PATCH 116/257] Fix some edge cases of tilde constraints in bump command (#12038) * Fix: Add test case for not dropping patch version for tilde * Fix some edge cases of tilde constraints in bump command, fixes #11218 --------- Co-authored-by: Matthias Vogel --- src/Composer/Package/Version/VersionBumper.php | 11 ++++++++--- .../Test/Package/Version/VersionBumperTest.php | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Composer/Package/Version/VersionBumper.php b/src/Composer/Package/Version/VersionBumper.php index e6dbdebe6b7f..b100d0e00edf 100644 --- a/src/Composer/Package/Version/VersionBumper.php +++ b/src/Composer/Package/Version/VersionBumper.php @@ -39,6 +39,8 @@ class VersionBumper * * ^1.2 || ^2.3 + 2.4.0 -> ^1.2 || ^2.4 * * ^3@dev + 3.2.99999-dev -> ^3.2@dev * * ~2 + 2.0-beta.1 -> ~2 + * * ~2.0.0 + 2.0.3 -> ~2.0.3 + * * ~2.0 + 2.0.3 -> ^2.0.3 * * dev-master + dev-master -> dev-master * * * + 1.2.3 -> >=1.2.3 */ @@ -84,7 +86,7 @@ public function bumpRequirement(ConstraintInterface $constraint, PackageInterfac (?<=,|\ |\||^) # leading separator (?P \^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything - | ~v?'.$major.'(?:\.\d+){0,2} # e.g. ~2 or ~2.2 or ~2.2.2 but no more + | ~v?'.$major.'(?:\.\d+){1,3} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2 | v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc | \* # full wildcard @@ -99,8 +101,11 @@ public function bumpRequirement(ConstraintInterface $constraint, PackageInterfac if (substr_count($match[0], '.') === 2 && substr_count($versionWithoutSuffix, '.') === 1) { $suffix = '.0'; } - if (str_starts_with($match[0], '~') && substr_count($match[0], '.') === 2) { - $replacement = '~'.$versionWithoutSuffix.$suffix; + if (str_starts_with($match[0], '~') && substr_count($match[0], '.') !== 1) { + // take as many version bits from the current version as we have in the constraint to bump it without making it more specific + $versionBits = explode('.', $versionWithoutSuffix); + $versionBits = array_pad($versionBits, substr_count($match[0], '.') + 1, '0'); + $replacement = '~'.implode('.', array_slice($versionBits, 0, substr_count($match[0], '.') + 1)); } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) { $replacement = '>='.$versionWithoutSuffix.$suffix; } else { diff --git a/tests/Composer/Test/Package/Version/VersionBumperTest.php b/tests/Composer/Test/Package/Version/VersionBumperTest.php index ba58743788f1..15b554ae342e 100644 --- a/tests/Composer/Test/Package/Version/VersionBumperTest.php +++ b/tests/Composer/Test/Package/Version/VersionBumperTest.php @@ -64,9 +64,14 @@ public static function provideBumpRequirementTests(): Generator yield 'upgrade major wildcard as x to caret/2' => ['2.x.x', '2.4.0', '^2.4.0']; yield 'leave minor wildcard alone' => ['2.4.*', '2.4.3', '2.4.*']; yield 'leave patch wildcard alone' => ['2.4.3.*', '2.4.3.2', '2.4.3.*']; + yield 'leave single tilde alone' => ['~2', '2.4.3', '~2']; yield 'upgrade tilde to caret when compatible' => ['~2.2', '2.4.3', '^2.4.3']; - yield 'update patch-only-tilde alone' => ['~2.2.3', '2.2.6', '~2.2.6']; - yield 'leave extra-only-tilde alone' => ['~2.2.3.1', '2.2.4.5', '~2.2.3.1']; + yield 'upgrade patch-only-tilde, longer version' => ['~2.2.3', '2.2.6.2', '~2.2.6']; + yield 'upgrade patch-only-tilde' => ['~2.2.3', '2.2.6', '~2.2.6']; + yield 'upgrade patch-only-tilde, also .0s' => ['~2.0.0', '2.0.0', '~2.0.0']; + yield 'upgrade 4 bits tilde' => ['~2.2.3.1', '2.2.4', '~2.2.4.0']; + yield 'upgrade 4 bits tilde/2' => ['~2.2.3.1', '2.2.4.0', '~2.2.4.0']; + yield 'upgrade 4 bits tilde/3' => ['~2.2.3.1', '2.2.4.5', '~2.2.4.5']; yield 'upgrade bigger-or-eq to latest' => ['>=3.0', '3.4.5', '>=3.4.5']; yield 'upgrade bigger-or-eq to latest with v' => ['>=v3.0', '3.4.5', '>=3.4.5']; yield 'leave bigger-than untouched' => ['>2.2.3', '2.2.6', '>2.2.3']; From 03bbfdd8f4d6ce3c0678b6cba26446704b4d594b Mon Sep 17 00:00:00 2001 From: Ilya Urvachev Date: Wed, 10 Jul 2024 10:35:26 +0200 Subject: [PATCH 117/257] fix(Locker): don't store transport-options.ssl within the lock-file (#12019) --- src/Composer/Package/Locker.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index f832e797cf43..6c3dfa604c1e 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -431,6 +431,14 @@ private function lockPackages(array $packages): array $spec = $this->dumper->dump($package); unset($spec['version_normalized']); + // remove `transport-options.ssl` from lock file to prevent storing + // local-filesystem repo config paths in the lock file as that makes it less portable + if (isset($spec['transport-options']['ssl'])) { + unset($spec['transport-options']['ssl']); + if (\count($spec['transport-options']) === 0) { + unset($spec['transport-options']); + } + } // always move time to the end of the package definition $time = $spec['time'] ?? null; From 6ec76db92668cdd2f0ee24fb53ec78746c8e6ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BB=B3nh=20Nguy=E1=BB=85n?= Date: Wed, 10 Jul 2024 16:04:20 +0700 Subject: [PATCH 118/257] Remove redundant boolean type casts (#12033) --- src/Composer/Cache.php | 4 ++-- src/Composer/Config.php | 2 +- .../EventDispatcher/EventDispatcher.php | 2 +- src/Composer/Installer.php | 20 +++++++++---------- .../Question/StrictConfirmationQuestion.php | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 1a216c59231f..b1e008594cee 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -53,7 +53,7 @@ public function __construct(IOInterface $io, string $cacheDir, string $allowlist $this->root = rtrim($cacheDir, '/\\') . '/'; $this->allowlist = $allowlist; $this->filesystem = $filesystem ?: new Filesystem(); - $this->readOnly = (bool) $readOnly; + $this->readOnly = $readOnly; if (!self::isUsable($cacheDir)) { $this->enabled = false; @@ -65,7 +65,7 @@ public function __construct(IOInterface $io, string $cacheDir, string $allowlist */ public function setReadOnly(bool $readOnly) { - $this->readOnly = (bool) $readOnly; + $this->readOnly = $readOnly; } /** diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 165761d9da26..4c87a195dc37 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -125,7 +125,7 @@ public function __construct(bool $useEnvironment = true, ?string $baseDir = null $this->config = static::$defaultConfig; $this->repositories = static::$defaultRepositories; - $this->useEnvironment = (bool) $useEnvironment; + $this->useEnvironment = $useEnvironment; $this->baseDir = is_string($baseDir) && '' !== $baseDir ? $baseDir : null; foreach ($this->config as $configKey => $configValue) { diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index cab8562520ba..347d53b5d582 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -90,7 +90,7 @@ public function __construct(PartialComposer $composer, IOInterface $io, ?Process */ public function setRunScripts(bool $runScripts = true): self { - $this->runScripts = (bool) $runScripts; + $this->runScripts = $runScripts; return $this; } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 95eead74c914..c740f3204986 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1173,7 +1173,7 @@ public function setTemporaryConstraints(array $constraints): self */ public function setDryRun(bool $dryRun = true): self { - $this->dryRun = (bool) $dryRun; + $this->dryRun = $dryRun; return $this; } @@ -1205,7 +1205,7 @@ public function setDownloadOnly(bool $downloadOnly = true): self */ public function setPreferSource(bool $preferSource = true): self { - $this->preferSource = (bool) $preferSource; + $this->preferSource = $preferSource; return $this; } @@ -1217,7 +1217,7 @@ public function setPreferSource(bool $preferSource = true): self */ public function setPreferDist(bool $preferDist = true): self { - $this->preferDist = (bool) $preferDist; + $this->preferDist = $preferDist; return $this; } @@ -1229,7 +1229,7 @@ public function setPreferDist(bool $preferDist = true): self */ public function setOptimizeAutoloader(bool $optimizeAutoloader): self { - $this->optimizeAutoloader = (bool) $optimizeAutoloader; + $this->optimizeAutoloader = $optimizeAutoloader; if (!$this->optimizeAutoloader) { // Force classMapAuthoritative off when not optimizing the // autoloader @@ -1247,7 +1247,7 @@ public function setOptimizeAutoloader(bool $optimizeAutoloader): self */ public function setClassMapAuthoritative(bool $classMapAuthoritative): self { - $this->classMapAuthoritative = (bool) $classMapAuthoritative; + $this->classMapAuthoritative = $classMapAuthoritative; if ($this->classMapAuthoritative) { // Force optimizeAutoloader when classmap is authoritative $this->setOptimizeAutoloader(true); @@ -1276,7 +1276,7 @@ public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderP */ public function setUpdate(bool $update): self { - $this->update = (bool) $update; + $this->update = $update; return $this; } @@ -1288,7 +1288,7 @@ public function setUpdate(bool $update): self */ public function setInstall(bool $install): self { - $this->install = (bool) $install; + $this->install = $install; return $this; } @@ -1300,7 +1300,7 @@ public function setInstall(bool $install): self */ public function setDevMode(bool $devMode = true): self { - $this->devMode = (bool) $devMode; + $this->devMode = $devMode; return $this; } @@ -1314,7 +1314,7 @@ public function setDevMode(bool $devMode = true): self */ public function setDumpAutoloader(bool $dumpAutoloader = true): self { - $this->dumpAutoloader = (bool) $dumpAutoloader; + $this->dumpAutoloader = $dumpAutoloader; return $this; } @@ -1353,7 +1353,7 @@ public function setConfig(Config $config): self */ public function setVerbose(bool $verbose = true): self { - $this->verbose = (bool) $verbose; + $this->verbose = $verbose; return $this; } diff --git a/src/Composer/Question/StrictConfirmationQuestion.php b/src/Composer/Question/StrictConfirmationQuestion.php index c27753b32f27..9cbc74ec5f6c 100644 --- a/src/Composer/Question/StrictConfirmationQuestion.php +++ b/src/Composer/Question/StrictConfirmationQuestion.php @@ -40,7 +40,7 @@ class StrictConfirmationQuestion extends Question */ public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y(?:es)?$/i', string $falseAnswerRegex = '/^no?$/i') { - parent::__construct($question, (bool) $default); + parent::__construct($question, $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->falseAnswerRegex = $falseAnswerRegex; From 68f6498bd3ec2442ec9952072986dce92f5cd9d3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jul 2024 15:07:27 +0200 Subject: [PATCH 119/257] Only read first 500 bytes of a bin file to detect if it is a PHP script, refs #12032 --- src/Composer/Installer/BinaryInstaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 9e3123ff8c27..3e67ef486cc9 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -213,7 +213,7 @@ protected function generateUnixyProxyCode(string $bin, string $link): string $binDir = ProcessExecutor::escape(dirname($binPath)); $binFile = basename($binPath); - $binContents = file_get_contents($bin); + $binContents = file_get_contents($bin, false, null, 0, 500); // For php files, we generate a PHP proxy instead of a shell one, // which allows calling the proxy with a custom php process if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) { From bd03981ea73ee7c73084cc88d0bf9d2f6962355a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 11 Jul 2024 09:29:55 +0200 Subject: [PATCH 120/257] Fix archive command crashing if a path cannot be realpathed on windows, fixes #11544 --- .../Package/Archiver/ArchivableFilesFinder.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Composer/Package/Archiver/ArchivableFilesFinder.php b/src/Composer/Package/Archiver/ArchivableFilesFinder.php index b2b6b87b8a8e..2cf7ffc72e0a 100644 --- a/src/Composer/Package/Archiver/ArchivableFilesFinder.php +++ b/src/Composer/Package/Archiver/ArchivableFilesFinder.php @@ -47,7 +47,11 @@ public function __construct(string $sources, array $excludes, bool $ignoreFilter { $fs = new Filesystem(); - $sources = $fs->normalizePath(realpath($sources)); + $sourcesRealPath = realpath($sources); + if ($sourcesRealPath === false) { + throw new \RuntimeException('Could not realpath() the source directory "'.$sources.'"'); + } + $sources = $fs->normalizePath($sourcesRealPath); if ($ignoreFilters) { $filters = []; @@ -61,14 +65,18 @@ public function __construct(string $sources, array $excludes, bool $ignoreFilter $this->finder = new Finder(); $filter = static function (\SplFileInfo $file) use ($sources, $filters, $fs): bool { - if ($file->isLink() && ($file->getRealPath() === false || strpos($file->getRealPath(), $sources) !== 0)) { + $realpath = $file->getRealPath(); + if ($realpath === false) { + return false; + } + if ($file->isLink() && strpos($realpath, $sources) !== 0) { return false; } $relativePath = Preg::replace( '#^'.preg_quote($sources, '#').'#', '', - $fs->normalizePath($file->getRealPath()) + $fs->normalizePath($realpath) ); $exclude = false; @@ -79,10 +87,6 @@ public function __construct(string $sources, array $excludes, bool $ignoreFilter return !$exclude; }; - if (method_exists($filter, 'bindTo')) { - $filter = $filter->bindTo(null); - } - $this->finder ->in($sources) ->filter($filter) From 07aee7ea8e1ab691c14bfe4258f826a8aae95ed3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 11 Jul 2024 15:57:11 +0200 Subject: [PATCH 121/257] Update ca-bundle to latest --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 3a0451251acc..0ff81628a3a1 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/063d9aa8696582f5a41dffbbaf3c81024f0a604a", + "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a", "shasum": "" }, "require": { @@ -27,7 +27,7 @@ }, "require-dev": { "phpstan/phpstan": "^1.10", - "psr/log": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/phpunit-bridge": "^4.2 || ^5", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + "source": "https://github.com/composer/ca-bundle/tree/1.5.1" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2024-03-15T14:00:32+00:00" + "time": "2024-07-08T15:28:20+00:00" }, { "name": "composer/class-map-generator", From 685add70ec84d18075a202686288304db0d6d265 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 12 Jul 2024 11:27:44 +0200 Subject: [PATCH 122/257] Refactor the BasePackage::$stabilities into a constant --- composer.lock | 26 +++++++++---------- src/Composer/Command/ArchiveCommand.php | 2 +- src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Command/CreateProjectCommand.php | 6 ++--- src/Composer/Command/InitCommand.php | 6 ++--- .../Command/PackageDiscoveryTrait.php | 7 +++++ src/Composer/Command/ShowCommand.php | 2 +- .../DependencyResolver/DefaultPolicy.php | 2 +- .../DependencyResolver/PoolBuilder.php | 4 +-- src/Composer/Installer.php | 2 +- src/Composer/Package/BasePackage.php | 13 +++++++--- .../Package/Loader/RootPackageLoader.php | 4 +-- .../Package/Loader/ValidatingArrayLoader.php | 4 +-- src/Composer/Package/Locker.php | 5 +++- src/Composer/Package/RootPackage.php | 2 +- src/Composer/Package/RootPackageInterface.php | 6 ++++- .../Package/Version/StabilityFilter.php | 6 ++--- .../Package/Version/VersionSelector.php | 4 +-- .../Repository/ComposerRepository.php | 6 ++--- .../Repository/RepositoryInterface.php | 3 ++- src/Composer/Repository/RepositorySet.php | 13 +++++----- .../DependencyResolver/PoolBuilderTest.php | 5 +++- .../Test/Repository/FilterRepositoryTest.php | 4 +-- 23 files changed, 80 insertions(+), 54 deletions(-) diff --git a/composer.lock b/composer.lock index 0ff81628a3a1..d9cf31b93616 100644 --- a/composer.lock +++ b/composer.lock @@ -297,16 +297,16 @@ }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "8536c1b9103405bcbd310c69e7a5739a1c2b1f0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/8536c1b9103405bcbd310c69e7a5739a1c2b1f0b", + "reference": "8536c1b9103405bcbd310c69e7a5739a1c2b1f0b", "shasum": "" }, "require": { @@ -358,7 +358,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.1" }, "funding": [ { @@ -374,7 +374,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-07-12T09:13:09+00:00" }, { "name": "composer/spdx-licenses", @@ -760,23 +760,23 @@ }, { "name": "seld/jsonlint", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.5", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, "bin": [ @@ -808,7 +808,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" }, "funding": [ { @@ -820,7 +820,7 @@ "type": "tidelift" } ], - "time": "2024-02-07T12:57:50+00:00" + "time": "2024-07-11T14:55:45+00:00" }, { "name": "seld/phar-utils", diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index e0189109b652..b71f4e241fad 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -169,7 +169,7 @@ protected function selectPackage(IOInterface $io, string $packageName, ?string $ } if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) { - $minStability = $match[1]; + $minStability = VersionParser::normalizeStability($match[1]); $version = (string) substr($version, 0, -strlen($match[0])); } diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index b625cbf1a522..9bbba55134fa 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -664,7 +664,7 @@ static function ($vals) { }], 'minimum-stability' => [ static function ($val): bool { - return isset(BasePackage::$stabilities[VersionParser::normalizeStability($val)]); + return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]); }, static function ($val): string { return VersionParser::normalizeStability($val); diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index b7e873684b5b..6cc35ebe2398 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -375,7 +375,7 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co if (null === $stability) { if (null === $packageVersion) { $stability = 'stable'; - } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $packageVersion, $match)) { + } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::STABILITIES)).')$}i', $packageVersion, $match)) { $stability = $match[1]; } else { $stability = VersionParser::parseStability($packageVersion); @@ -384,8 +384,8 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co $stability = VersionParser::normalizeStability($stability); - if (!isset(BasePackage::$stabilities[$stability])) { - throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); + if (!isset(BasePackage::STABILITIES[$stability])) { + throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::STABILITIES))); } $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts); diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 81233a08481e..7ddacd3c2a17 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -61,7 +61,7 @@ protected function configure() new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), - new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'), + new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::STABILITIES)).')'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'), @@ -364,10 +364,10 @@ static function ($value) use ($minimumStability) { return $minimumStability; } - if (!isset(BasePackage::$stabilities[$value])) { + if (!isset(BasePackage::STABILITIES[$value])) { throw new \InvalidArgumentException( 'Invalid minimum stability "'.$value.'". Must be empty or one of: '. - implode(', ', array_keys(BasePackage::$stabilities)) + implode(', ', array_keys(BasePackage::STABILITIES)) ); } diff --git a/src/Composer/Command/PackageDiscoveryTrait.php b/src/Composer/Command/PackageDiscoveryTrait.php index f0aafda807ef..0bbd2a48cd28 100644 --- a/src/Composer/Command/PackageDiscoveryTrait.php +++ b/src/Composer/Command/PackageDiscoveryTrait.php @@ -16,6 +16,7 @@ use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\IO\IOInterface; +use Composer\Package\BasePackage; use Composer\Package\CompletePackageInterface; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; @@ -52,6 +53,9 @@ protected function getRepos(): CompositeRepository return $this->repos; } + /** + * @param key-of|null $minimumStability + */ private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet { $key = $minimumStability ?? 'default'; @@ -64,6 +68,9 @@ private function getRepositorySet(InputInterface $input, ?string $minimumStabili return $this->repositorySets[$key]; } + /** + * @return key-of + */ 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 diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index a1e7eb059fdc..7af0348537da 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -1454,7 +1454,7 @@ private function findLatestPackage(PackageInterface $package, Composer $composer $stability = $composer->getPackage()->getMinimumStability(); $flags = $composer->getPackage()->getStabilityFlags(); if (isset($flags[$name])) { - $stability = array_search($flags[$name], BasePackage::$stabilities, true); + $stability = array_search($flags[$name], BasePackage::STABILITIES, true); } $bestStability = $stability; diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index c608534281c7..2635de9ae5be 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -53,7 +53,7 @@ public function __construct(bool $preferStable = false, bool $preferLowest = fal public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool { if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { - return BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB]; + return BasePackage::STABILITIES[$stabA] < BasePackage::STABILITIES[$stabB]; } // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 52aefa251cdb..15bc35885973 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -40,7 +40,7 @@ class PoolBuilder { /** * @var int[] - * @phpstan-var array + * @phpstan-var array, BasePackage::STABILITY_*> */ private $acceptableStabilities; /** @@ -153,7 +153,7 @@ class PoolBuilder /** * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value - * @phpstan-param array $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array $stabilityFlags * @param array[] $rootAliases diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index c740f3204986..8401072edabd 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -910,7 +910,7 @@ private function createRepositorySet(bool $forUpdate, PlatformRepository $platfo $this->fixedRootPackage->setRequires([]); $this->fixedRootPackage->setDevRequires([]); - $stabilityFlags[$this->package->getName()] = BasePackage::$stabilities[VersionParser::parseStability($this->package->getVersion())]; + $stabilityFlags[$this->package->getName()] = BasePackage::STABILITIES[VersionParser::parseStability($this->package->getVersion())]; $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints); $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage)); diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index d6d37d360d91..764fdb424769 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -40,8 +40,7 @@ abstract class BasePackage implements PackageInterface public const STABILITY_ALPHA = 15; public const STABILITY_DEV = 20; - /** @var array */ - public static $stabilities = [ + public const STABILITIES = [ 'stable' => self::STABILITY_STABLE, 'RC' => self::STABILITY_RC, 'beta' => self::STABILITY_BETA, @@ -49,6 +48,14 @@ abstract class BasePackage implements PackageInterface 'dev' => self::STABILITY_DEV, ]; + /** + * @deprecated + * @readonly + * @var array, self::STABILITY_*> + * @phpstan-ignore property.readOnlyByPhpDocDefaultValue + */ + public static $stabilities = self::STABILITIES; + /** * READ-ONLY: The package id, public for fast access in dependency solver * @var int @@ -234,7 +241,7 @@ public function getFullPrettyVersion(bool $truncate = true, int $displayMode = P */ public function getStabilityPriority(): int { - return self::$stabilities[$this->getStability()]; + return self::STABILITIES[$this->getStability()]; } public function __clone() diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 64a169019a07..2b0d016e6c23 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -227,6 +227,7 @@ private function extractAliases(array $requires, array $aliases): array * * @param array $requires * @param array $stabilityFlags + * @param key-of $minimumStability * * @return array * @@ -235,8 +236,7 @@ private function extractAliases(array $requires, array $aliases): array */ public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags): array { - $stabilities = BasePackage::$stabilities; - /** @var int $minimumStability */ + $stabilities = BasePackage::STABILITIES; $minimumStability = $stabilities[$minimumStability]; foreach ($requires as $reqName => $reqVersion) { $constraints = []; diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 6d1388e234b8..a6431d2df25e 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -323,8 +323,8 @@ public function load(array $config, string $class = 'Composer\Package\CompletePa } if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) { - if (!isset(BasePackage::$stabilities[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') { - $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::$stabilities)); + if (!isset(BasePackage::STABILITIES[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') { + $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::STABILITIES)); unset($this->config['minimum-stability']); } } diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 6c3dfa604c1e..da1e4bf5ea7d 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -247,6 +247,9 @@ public function getPlatformRequirements(bool $withDevReqs = false): array return $requirements; } + /** + * @return key-of + */ public function getMinimumStability(): string { $lockData = $this->getLockData(); @@ -431,7 +434,7 @@ private function lockPackages(array $packages): array $spec = $this->dumper->dump($package); unset($spec['version_normalized']); - // remove `transport-options.ssl` from lock file to prevent storing + // remove `transport-options.ssl` from lock file to prevent storing // local-filesystem repo config paths in the lock file as that makes it less portable if (isset($spec['transport-options']['ssl'])) { unset($spec['transport-options']['ssl']); diff --git a/src/Composer/Package/RootPackage.php b/src/Composer/Package/RootPackage.php index e1305426f3db..a0f8e659fb3f 100644 --- a/src/Composer/Package/RootPackage.php +++ b/src/Composer/Package/RootPackage.php @@ -21,7 +21,7 @@ class RootPackage extends CompletePackage implements RootPackageInterface { public const DEFAULT_PRETTY_VERSION = '1.0.0+no-version-set'; - /** @var string */ + /** @var key-of */ protected $minimumStability = 'stable'; /** @var bool */ protected $preferStable = false; diff --git a/src/Composer/Package/RootPackageInterface.php b/src/Composer/Package/RootPackageInterface.php index 4adad6c8b5ab..8a08060f8925 100644 --- a/src/Composer/Package/RootPackageInterface.php +++ b/src/Composer/Package/RootPackageInterface.php @@ -33,6 +33,8 @@ public function getAliases(): array; /** * Returns the minimum stability of the package + * + * @return key-of */ public function getMinimumStability(): string; @@ -120,12 +122,14 @@ public function setDevAutoload(array $devAutoload): void; /** * Set the stabilityFlags * - * @param array $stabilityFlags + * @phpstan-param array $stabilityFlags */ public function setStabilityFlags(array $stabilityFlags): void; /** * Set the minimumStability + * + * @phpstan-param key-of $minimumStability */ public function setMinimumStability(string $minimumStability): void; diff --git a/src/Composer/Package/Version/StabilityFilter.php b/src/Composer/Package/Version/StabilityFilter.php index 172901d44931..7e0182a6e16b 100644 --- a/src/Composer/Package/Version/StabilityFilter.php +++ b/src/Composer/Package/Version/StabilityFilter.php @@ -23,11 +23,11 @@ class StabilityFilter * Checks if any of the provided package names in the given stability match the configured acceptable stability and flags * * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value - * @phpstan-param array $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array $stabilityFlags * @param string[] $names The package name(s) to check for stability flags - * @param string $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' + * @param key-of $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' * @return bool true if any package name is acceptable */ public static function isPackageAcceptable(array $acceptableStabilities, array $stabilityFlags, array $names, string $stability): bool @@ -35,7 +35,7 @@ public static function isPackageAcceptable(array $acceptableStabilities, array $ foreach ($names as $name) { // allow if package matches the package-specific stability flag if (isset($stabilityFlags[$name])) { - if (BasePackage::$stabilities[$stability] <= $stabilityFlags[$name]) { + if (BasePackage::STABILITIES[$stability] <= $stabilityFlags[$name]) { return true; } } elseif (isset($acceptableStabilities[$stability])) { diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index 9ab322084ad1..f1180c8e3f3d 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -71,7 +71,7 @@ public function __construct(RepositorySet $repositorySet, ?PlatformRepository $p */ public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true) { - if (!isset(BasePackage::$stabilities[$preferredStability])) { + if (!isset(BasePackage::STABILITIES[$preferredStability])) { // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability); } @@ -86,7 +86,7 @@ public function findBestCandidate(string $packageName, ?string $targetPackageVer $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags); - $minPriority = BasePackage::$stabilities[$preferredStability]; + $minPriority = BasePackage::STABILITIES[$preferredStability]; usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) { $aPriority = $a->getStabilityPriority(); $bPriority = $b->getStabilityPriority(); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 529279b1f136..fe93fb7d9dc2 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -827,7 +827,7 @@ private function hasProviders(): bool /** * @param string $name package name * @param array|null $acceptableStabilities - * @phpstan-param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array|null $stabilityFlags * @param array> $alreadyLoaded @@ -997,7 +997,7 @@ public function addPackage(PackageInterface $package) * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only * packages matching it will be loaded * @param array|null $acceptableStabilities - * @phpstan-param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array|null $stabilityFlags * @param array> $alreadyLoaded @@ -1129,7 +1129,7 @@ private function startCachedAsyncDownload(string $fileName, ?string $packageName * @param string $name package name (must be lowercased already) * @param array $versionData * @param array|null $acceptableStabilities - * @phpstan-param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array|null $stabilityFlags */ diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index c26248801a26..3bcb1ea97445 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -72,12 +72,13 @@ public function getPackages(); * - The namesFound returned are names which should be considered as canonically found in this repository, that should not be looked up in any further lower priority repositories * * @param ConstraintInterface[] $packageNameMap package names pointing to constraints - * @param array $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @param array $acceptableStabilities array of stability => BasePackage::STABILITY_* value * @param array $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @param array> $alreadyLoaded an array of package name => package version => package * * @return array * + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities * @phpstan-param array $packageNameMap * @phpstan-return array{namesFound: array, packages: array} */ diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index f6e8c7802ec7..96c0c007bf2a 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -65,7 +65,7 @@ class RepositorySet /** * @var int[] array of stability => BasePackage::STABILITY_* value - * @phpstan-var array + * @phpstan-var array, BasePackage::STABILITY_*> */ private $acceptableStabilities; @@ -96,6 +96,7 @@ class RepositorySet * passing minimumStability is all you need to worry about. The rest is for advanced pool creation including * aliases, pinned references and other special cases. * + * @param key-of $minimumStability * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array $stabilityFlags * @param array[] $rootAliases @@ -112,8 +113,8 @@ public function __construct(string $minimumStability = 'stable', array $stabilit $this->rootReferences = $rootReferences; $this->acceptableStabilities = []; - foreach (BasePackage::$stabilities as $stability => $value) { - if ($value <= BasePackage::$stabilities[$minimumStability]) { + foreach (BasePackage::STABILITIES as $stability => $value) { + if ($value <= BasePackage::STABILITIES[$minimumStability]) { $this->acceptableStabilities[$stability] = $value; } } @@ -195,7 +196,7 @@ public function findPackages(string $name, ?ConstraintInterface $constraint = nu } } else { foreach ($this->repositories as $repository) { - $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::$stabilities : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags); + $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::STABILITIES : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags); $packages[] = $result['packages']; foreach ($result['namesFound'] as $nameFound) { @@ -300,8 +301,8 @@ public function getProviders(string $packageName): array /** * Check for each given package name whether it would be accepted by this RepositorySet in the given $stability * - * @param string[] $names - * @param string $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' + * @param string[] $names + * @param key-of $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' */ public function isPackageAcceptable(array $names, string $stability): bool { diff --git a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php index 4feb297ed2e8..fc226259912f 100644 --- a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php @@ -50,7 +50,10 @@ public function testPoolBuilder(string $file, string $message, array $expect, ar $stabilityFlags = !empty($root['stability-flags']) ? $root['stability-flags'] : []; $rootReferences = !empty($root['references']) ? $root['references'] : []; $stabilityFlags = array_map(static function ($stability): int { - return BasePackage::$stabilities[$stability]; + if (!isset(BasePackage::STABILITIES[$stability])) { + throw new \LogicException('Invalid stability given: '.$stability); + } + return BasePackage::STABILITIES[$stability]; }, $stabilityFlags); $parser = new VersionParser(); diff --git a/tests/Composer/Test/Repository/FilterRepositoryTest.php b/tests/Composer/Test/Repository/FilterRepositoryTest.php index 8698f97f8a16..2acb58f0d2a5 100644 --- a/tests/Composer/Test/Repository/FilterRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilterRepositoryTest.php @@ -86,7 +86,7 @@ public function testSecurityAdvisoriesDisabledInChild(): void public function testCanonicalDefaultTrue(): void { $repo = new FilterRepository($this->arrayRepo, []); - $result = $repo->loadPackages(['foo/aaa' => new MatchAllConstraint], BasePackage::$stabilities, []); + $result = $repo->loadPackages(['foo/aaa' => new MatchAllConstraint], BasePackage::STABILITIES, []); self::assertCount(1, $result['packages']); self::assertCount(1, $result['namesFound']); } @@ -94,7 +94,7 @@ public function testCanonicalDefaultTrue(): void public function testNonCanonical(): void { $repo = new FilterRepository($this->arrayRepo, ['canonical' => false]); - $result = $repo->loadPackages(['foo/aaa' => new MatchAllConstraint], BasePackage::$stabilities, []); + $result = $repo->loadPackages(['foo/aaa' => new MatchAllConstraint], BasePackage::STABILITIES, []); self::assertCount(1, $result['packages']); self::assertCount(0, $result['namesFound']); } From ea931df77d03471cb7bc5e532e14cdf0350800f4 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Fri, 19 Jul 2024 20:41:33 +0100 Subject: [PATCH 123/257] Update schema for php-ext section to add needs-value flag (#12050) Reference: https://github.com/ThePHPF/pie-design/pull/18 --- res/composer-schema.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/composer-schema.json b/res/composer-schema.json index 90714875c014..0f06cc542534 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -324,6 +324,12 @@ "example": "without-xdebug-compression", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$" }, + "needs-value": { + "type": "boolean", + "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=), otherwise it is a flag without a value (e.g. --enable-some-feature).", + "example": false, + "default": false + }, "description": { "type": "string", "description": "The description of what the flag does or means.", From db869409e1dc7f40a2eb4c92c5f381c3388493d5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 25 Jul 2024 16:26:13 +0200 Subject: [PATCH 124/257] Bump highest php lint to use nightly (#12058) --- .github/workflows/lint.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 479fe09c2036..5e3fe1d29512 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,7 +21,7 @@ jobs: matrix: php-version: - "7.2" - - "latest" + - "nightly" steps: - name: "Checkout" @@ -36,4 +36,16 @@ jobs: php-version: "${{ matrix.php-version }}" - name: "Lint PHP files" - run: "find src/ -type f -name '*.php' -print0 | xargs -0 -L1 -P4 -- php -l -f" + run: | + hasErrors=0 + for f in $(find src/ tests/ -type f -name '*.php' ! -path '*/vendor/*') + do + { error="$(php -derror_reporting=-1 -ddisplay_errors=1 -l -f $f 2>&1 1>&3 3>&-)"; } 3>&1; + if [ "$error" != "" ]; then + while IFS= read -r line; do echo "::error file=$f::$line"; done <<< "$error" + hasErrors=1 + fi + done + if [ $hasErrors -eq 1 ]; then + exit 1 + fi From 029dda0b43d45a259b41c396ca03deafef348cf8 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 25 Jul 2024 19:56:39 +0530 Subject: [PATCH 125/257] Fix deprecation notice on PHP 8.4 (#12046) PHP 8.4 requires nullable arguments to be explicitly declared as such. --- src/Composer/Downloader/TransportException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Downloader/TransportException.php b/src/Composer/Downloader/TransportException.php index 5ab5ca6f38d2..a30842e1e4b2 100644 --- a/src/Composer/Downloader/TransportException.php +++ b/src/Composer/Downloader/TransportException.php @@ -26,7 +26,7 @@ class TransportException extends \RuntimeException /** @var array */ protected $responseInfo = []; - public function __construct(string $message = "", int $code = 400, \Throwable $previous = null) + public function __construct(string $message = "", int $code = 400, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } From 7504685a2effe5211b830325080701546f8b025c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 25 Jul 2024 16:28:25 +0200 Subject: [PATCH 126/257] Update phpstan and add composer/pcre extensions (#12045) * Update phpstan and add composer/pcre extensions * Update baseline (1516) --- composer.json | 4 +- composer.lock | 62 +++++---- phpstan/baseline-8.3.neon | 7 +- phpstan/baseline.neon | 131 +----------------- phpstan/config.neon | 1 + src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Command/InitCommand.php | 2 - src/Composer/Compiler.php | 1 + src/Composer/Downloader/GitDownloader.php | 2 +- src/Composer/Installer/BinaryInstaller.php | 6 +- src/Composer/Platform/Runtime.php | 5 +- .../Repository/PlatformRepository.php | 14 +- src/Composer/Repository/Vcs/GitDriver.php | 10 +- src/Composer/Repository/Vcs/GitHubDriver.php | 2 - src/Composer/Repository/Vcs/GitLabDriver.php | 6 +- src/Composer/Repository/Vcs/SvnDriver.php | 56 ++++---- src/Composer/Util/ErrorHandler.php | 4 +- src/Composer/Util/Git.php | 39 +++--- src/Composer/Util/Hg.php | 8 +- tests/Composer/Test/AllFunctionalTest.php | 16 ++- 20 files changed, 128 insertions(+), 250 deletions(-) diff --git a/composer.json b/composer.json index 6bdfa04ad823..9cb86861c0d0 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "symfony/finder": "^5.4 || ^6.0 || ^7", "symfony/process": "^5.4 || ^6.0 || ^7", "react/promise": "^2.8 || ^3", - "composer/pcre": "^2.1 || ^3.1", + "composer/pcre": "^2.2 || ^3.2", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", @@ -46,7 +46,7 @@ }, "require-dev": { "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1", - "phpstan/phpstan": "^1.11.0", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-deprecation-rules": "^1.2.0", "phpstan/phpstan-strict-rules": "^1.6.0", diff --git a/composer.lock b/composer.lock index d9cf31b93616..019b129b5f4f 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "6a83e69e5c06f06ecba7db6c83213f9d", + "content-hash": "715b9529f60660d59b08b12c74c828c6", "packages": [ { "name": "composer/ca-bundle", @@ -226,30 +226,38 @@ }, { "name": "composer/pcre", - "version": "2.1.3", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "540af382c97b83c628227d5f87cf56466d476191" + "reference": "0e455b78ac53637929b29d5ab5bf3c978329c1eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/540af382c97b83c628227d5f87cf56466d476191", - "reference": "540af382c97b83c628227d5f87cf56466d476191", + "url": "https://api.github.com/repos/composer/pcre/zipball/0e455b78ac53637929b29d5ab5bf3c978329c1eb", + "reference": "0e455b78ac53637929b29d5ab5bf3c978329c1eb", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.8" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "2.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -277,7 +285,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.1.3" + "source": "https://github.com/composer/pcre/tree/2.2.0" }, "funding": [ { @@ -293,20 +301,20 @@ "type": "tidelift" } ], - "time": "2024-03-19T09:03:05+00:00" + "time": "2024-07-25T09:28:32+00:00" }, { "name": "composer/semver", - "version": "3.4.1", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "8536c1b9103405bcbd310c69e7a5739a1c2b1f0b" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/8536c1b9103405bcbd310c69e7a5739a1c2b1f0b", - "reference": "8536c1b9103405bcbd310c69e7a5739a1c2b1f0b", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -358,7 +366,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.1" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -374,7 +382,7 @@ "type": "tidelift" } ], - "time": "2024-07-12T09:13:09+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/spdx-licenses", @@ -2012,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.7", + "version": "1.11.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", "shasum": "" }, "require": { @@ -2066,7 +2074,7 @@ "type": "github" } ], - "time": "2024-07-06T11:17:41+00:00" + "time": "2024-07-24T07:01:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2218,22 +2226,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.5", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d" + "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d", - "reference": "1bd7c339f622dfb5a1a97dcaf1a862734eabfa1d", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/e909a075d69e0d4db262ac3407350ae2c6b6ab5f", + "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.11.7" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -2284,9 +2292,9 @@ "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.4.5" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.6" }, - "time": "2024-06-26T12:19:42+00:00" + "time": "2024-07-16T11:48:54+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index c6e16fee647b..5e17a28f4aa5 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -182,14 +182,9 @@ parameters: - message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#" - count: 15 + count: 8 path: ../src/Composer/Util/Git.php - - - message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#" - count: 2 - path: ../src/Composer/Util/Hg.php - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_add_handle expects CurlMultiHandle, resource\\|null given\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 7d1258a3462d..5ae3c0b3b964 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -130,11 +130,6 @@ parameters: count: 1 path: ../src/Composer/Autoload/ClassLoader.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 2 - path: ../src/Composer/Cache.php - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, 50\\> given\\.$#" count: 1 @@ -975,11 +970,6 @@ parameters: count: 2 path: ../src/Composer/Config.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/Config.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -1570,11 +1560,6 @@ parameters: count: 1 path: ../src/Composer/Downloader/DownloadManager.php - - - message: "#^Method Composer\\\\Downloader\\\\FileDownloader\\:\\:getDistPath\\(\\) should return string but returns array\\\\|string\\.$#" - count: 1 - path: ../src/Composer/Downloader/FileDownloader.php - - message: "#^Strict comparison using \\=\\=\\= between null and Composer\\\\Util\\\\Http\\\\Response\\|string will always evaluate to false\\.$#" count: 1 @@ -1860,11 +1845,6 @@ parameters: count: 1 path: ../src/Composer/EventDispatcher/EventDispatcher.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/EventDispatcher/EventDispatcher.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -2067,7 +2047,7 @@ parameters: - message: "#^Casting to bool something that's already bool\\.$#" - count: 11 + count: 1 path: ../src/Composer/Installer.php - @@ -2130,11 +2110,6 @@ parameters: count: 2 path: ../src/Composer/Installer/BinaryInstaller.php - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Installer/BinaryInstaller.php - - message: "#^Parameter \\#1 \\$binPath of method Composer\\\\Installer\\\\BinaryInstaller\\:\\:installFullBinaries\\(\\) expects string, string\\|false given\\.$#" count: 1 @@ -2160,11 +2135,6 @@ parameters: count: 1 path: ../src/Composer/Installer/BinaryInstaller.php - - - message: "#^Parameter \\#2 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:isMatch\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Installer/BinaryInstaller.php - - message: "#^Property Composer\\\\Installer\\\\BinaryInstaller\\:\\:\\$binDir \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -2410,16 +2380,6 @@ parameters: count: 1 path: ../src/Composer/Package/AliasPackage.php - - - message: "#^Call to function method_exists\\(\\) with Closure\\(SplFileInfo\\)\\: bool and 'bindTo' will always evaluate to true\\.$#" - count: 1 - path: ../src/Composer/Package/Archiver/ArchivableFilesFinder.php - - - - message: "#^Parameter \\#1 \\$path of method Composer\\\\Util\\\\Filesystem\\:\\:normalizePath\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Package/Archiver/ArchivableFilesFinder.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -2930,11 +2890,6 @@ parameters: count: 1 path: ../src/Composer/Plugin/PostFileDownloadEvent.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/Question/StrictConfirmationQuestion.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -3240,11 +3195,6 @@ parameters: count: 1 path: ../src/Composer/Repository/PearRepository.php - - - message: "#^Call to an undefined method object\\:\\:getVersion\\(\\)\\.$#" - count: 1 - path: ../src/Composer/Repository/PlatformRepository.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -3405,11 +3355,6 @@ parameters: - message: "#^Only booleans are allowed in &&, string given on the left side\\.$#" - count: 3 - path: ../src/Composer/Repository/Vcs/GitDriver.php - - - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" count: 1 path: ../src/Composer/Repository/Vcs/GitDriver.php @@ -3525,7 +3470,7 @@ parameters: - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 5 + count: 4 path: ../src/Composer/Repository/Vcs/GitLabDriver.php - @@ -3648,11 +3593,6 @@ parameters: count: 2 path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - message: "#^Method Composer\\\\Repository\\\\Vcs\\\\SvnDriver\\:\\:getRootIdentifier\\(\\) should return string but returns string\\|false\\.$#" count: 1 @@ -3663,26 +3603,11 @@ parameters: count: 1 path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - message: "#^Only booleans are allowed in &&, string given on the left side\\.$#" - count: 4 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - message: "#^Only booleans are allowed in &&, string\\|false given on the right side\\.$#" count: 1 path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" - count: 3 - path: ../src/Composer/Repository/Vcs/SvnDriver.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 @@ -3903,16 +3828,6 @@ parameters: count: 1 path: ../src/Composer/Util/ConfigValidator.php - - - message: "#^Only booleans are allowed in a negated boolean, int given\\.$#" - count: 1 - path: ../src/Composer/Util/ErrorHandler.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\IO\\\\IOInterface\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/ErrorHandler.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -3923,26 +3838,11 @@ parameters: count: 1 path: ../src/Composer/Util/Git.php - - - message: "#^Only booleans are allowed in &&, string given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Git.php - - - - message: "#^Only booleans are allowed in &&, string\\|false given on the left side\\.$#" - count: 1 - path: ../src/Composer/Util/Git.php - - message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#" count: 1 path: ../src/Composer/Util/Git.php - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\>\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Git.php - - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" count: 2 @@ -3950,7 +3850,7 @@ parameters: - message: "#^Parameter \\#1 \\$str of function rawurlencode expects string, string\\|null given\\.$#" - count: 15 + count: 8 path: ../src/Composer/Util/Git.php - @@ -3998,11 +3898,6 @@ parameters: count: 2 path: ../src/Composer/Util/GitLab.php - - - message: "#^Parameter \\#1 \\$str of function rawurlencode expects string, string\\|null given\\.$#" - count: 2 - path: ../src/Composer/Util/Hg.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -4703,11 +4598,6 @@ parameters: count: 2 path: ../src/bootstrap.php - - - message: "#^Call to method RecursiveDirectoryIterator\\:\\:getSubPathname\\(\\) with incorrect case\\: getSubPathName$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Method Composer\\\\Test\\\\AllFunctionalTest\\:\\:cleanOutput\\(\\) should return string but returns string\\|false\\.$#" count: 1 @@ -4723,26 +4613,11 @@ parameters: count: 1 path: ../tests/Composer/Test/AllFunctionalTest.php - - - message: "#^Only booleans are allowed in \\|\\|, string given on the right side\\.$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - - - message: "#^Only numeric types are allowed in \\-, int\\<0, max\\>\\|false given on the left side\\.$#" - count: 3 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" count: 1 path: ../tests/Composer/Test/AllFunctionalTest.php - - - message: "#^Parameter \\#2 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:split\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Dynamic call to static method Composer\\\\Test\\\\TestCase\\:\\:ensureDirectoryExistsAndClear\\(\\)\\.$#" count: 1 diff --git a/phpstan/config.neon b/phpstan/config.neon index 8a3062ba8e88..3c8e1a20c5f2 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -4,6 +4,7 @@ includes: - ../vendor/phpstan/phpstan-deprecation-rules/rules.neon - ../vendor/phpstan/phpstan-strict-rules/rules.neon - ../vendor/phpstan/phpstan-symfony/extension.neon + - ../vendor/composer/pcre/extension.neon - ../vendor/phpstan/phpstan-symfony/rules.neon # TODO when requiring php 7.4+ we can use this #- ../vendor/staabm/phpstan-todo-by/extension.neon diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 9bbba55134fa..02e77df60a27 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -291,7 +291,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $source = $this->config->getSourceOfValue($settingKey); if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { - if (!isset($matches[1]) || $matches[1] === '') { + if (!isset($matches[1])) { $value = $data['repositories'] ?? []; } else { if (!isset($data['repositories'][$matches[1]])) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 7ddacd3c2a17..4f1398726ca0 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -468,8 +468,6 @@ static function ($value) use ($autoload) { private function parseAuthorString(string $author): array { if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { - assert(is_string($match['name'])); - if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 9a2e27f5b426..def3ff8c4e4e 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -120,6 +120,7 @@ public function compile(string $pharFile = 'composer.phar'): void ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/') ->notPath('justinrainbow/json-schema/demo/') ->notPath('justinrainbow/json-schema/dist/') + ->notPath('composer/pcre/extension.neon') ->notPath('composer/LICENSE') ->exclude('Tests') ->exclude('tests') diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index b29782d1af17..4561328ccb5e 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -255,7 +255,7 @@ public function getUnpushedChanges(PackageInterface $package, string $path): ?st } $headRef = $match[1]; - if (!Preg::isMatchAllStrictGroups('{^'.$headRef.' refs/heads/(.+)$}mi', $refs, $matches)) { + if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) { // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this return null; } diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 3e67ef486cc9..54ecd94cf183 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -213,7 +213,7 @@ protected function generateUnixyProxyCode(string $bin, string $link): string $binDir = ProcessExecutor::escape(dirname($binPath)); $binFile = basename($binPath); - $binContents = file_get_contents($bin, false, null, 0, 500); + $binContents = (string) file_get_contents($bin, false, null, 0, 500); // For php files, we generate a PHP proxy instead of a shell one, // which allows calling the proxy with a custom php process if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) { @@ -224,7 +224,7 @@ protected function generateUnixyProxyCode(string $bin, string $link): string $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n"; $phpunitHack1 = $phpunitHack2 = ''; // Don't expose autoload path when vendor dir was not set in custom installers - if ($this->vendorDir) { + if ($this->vendorDir !== null) { // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already $vendorDirReal = realpath($this->vendorDir); if ($vendorDirReal === false) { @@ -242,7 +242,7 @@ protected function generateUnixyProxyCode(string $bin, string $link): string $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data); $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);'; } - if (trim((string) $match[0]) !== ' $class + * @phpstan-return T + * * @throws \ReflectionException */ public function construct(string $class, array $arguments = []): object diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 61c70cf32d3b..cbc2552d5492 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -350,16 +350,18 @@ protected function initialize(): void break; case 'imagick': + // @phpstan-ignore staticMethod.dynamicCall (called like this for mockability) $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org - Preg::match('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches); - $version = $matches['version']; - if (isset($matches['patch'])) { - $version .= '.'.$matches['patch']; - } + if (Preg::isMatch('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches)) { + $version = $matches['version']; + if (isset($matches['patch'])) { + $version .= '.'.$matches['patch']; + } - $this->addLibrary($name.'-imagemagick', $version, null, ['imagick']); + $this->addLibrary($name.'-imagemagick', $version, null, ['imagick']); + } break; case 'ldap': diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 75ede6910d04..d6f1b8213410 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -153,7 +153,7 @@ public function getFileContent(string $file, string $identifier): ?string $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); - if (!trim($content)) { + if (trim($content) === '') { return null; } @@ -182,9 +182,9 @@ public function getTags(): array $this->tags = []; $this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir); - foreach ($output = $this->process->splitLines($output) as $tag) { - if ($tag && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { - $this->tags[$match[2]] = (string) $match[1]; + foreach ($this->process->splitLines($output) as $tag) { + if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { + $this->tags[$match[2]] = $match[1]; } } } @@ -202,7 +202,7 @@ public function getBranches(): array $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { - if ($branch && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { + if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 83c58887c0c0..09beb7680997 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -63,8 +63,6 @@ public function initialize(): void throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url)); } - assert(is_string($match[3])); - assert(is_string($match[4])); $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = strtolower($match[1] ?? (string) $match[2]); diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 3721419b1788..75304f46bf3e 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -97,8 +97,6 @@ public function initialize(): void throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); } - assert(is_string($match['parts'])); - assert(is_string($match['repo'])); $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $configuredDomains = $this->config->get('gitlab-domains'); $urlParts = explode('/', $match['parts']); @@ -115,7 +113,7 @@ public function initialize(): void if (is_string($protocol = $this->config->get('gitlab-protocol'))) { // https treated as a synonym for http. - if (!in_array($protocol, ['git', 'http', 'https'])) { + if (!in_array($protocol, ['git', 'http', 'https'], true)) { throw new \RuntimeException('gitlab-protocol must be one of git, http.'); } $this->protocol = $protocol === 'git' ? 'ssh' : 'http'; @@ -566,8 +564,6 @@ public static function supports(IOInterface $io, Config $config, string $url, bo return false; } - assert(is_string($match['parts'])); - assert(is_string($match['repo'])); $scheme = $match['scheme']; $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $urlParts = explode('/', $match['parts']); diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index ea8158e34d2a..d4f3180795c9 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -177,8 +177,7 @@ public function getFileContent(string $file, string $identifier): ?string { $identifier = '/' . trim($identifier, '/') . '/'; - Preg::match('{^(.+?)(@\d+)?/$}', $identifier, $match); - if (!empty($match[2])) { + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) { $path = $match[1]; $rev = $match[2]; } else { @@ -189,7 +188,7 @@ public function getFileContent(string $file, string $identifier): ?string try { $resource = $path.$file; $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); - if (!trim($output)) { + if ('' === trim($output)) { return null; } } catch (\RuntimeException $e) { @@ -206,8 +205,7 @@ public function getChangeDate(string $identifier): ?\DateTimeImmutable { $identifier = '/' . trim($identifier, '/') . '/'; - Preg::match('{^(.+?)(@\d+)?/$}', $identifier, $match); - if (null !== $match[2] && null !== $match[1]) { + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) { $path = $match[1]; $rev = $match[2]; } else { @@ -217,7 +215,7 @@ public function getChangeDate(string $identifier): ?\DateTimeImmutable $output = $this->execute('svn info', $this->baseUrl . $path . $rev); foreach ($this->process->splitLines($output) as $line) { - if ($line && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { + if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); } } @@ -235,20 +233,18 @@ public function getTags(): array if ($this->tagsPath !== false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath); - if ($output) { + if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines($output) as $line) { $line = trim($line); - if ($line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { - if (isset($match[1], $match[2])) { - if ($match[2] === './') { - $lastRev = (int) $match[1]; - } else { - $tags[rtrim($match[2], '/')] = $this->buildIdentifier( - '/' . $this->tagsPath . '/' . $match[2], - max($lastRev, (int) $match[1]) - ); - } + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $tags[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->tagsPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); } } } @@ -276,11 +272,11 @@ public function getBranches(): array } $output = $this->execute('svn ls --verbose', $trunkParent); - if ($output) { + if ($output !== '') { foreach ($this->process->splitLines($output) as $line) { $line = trim($line); - if ($line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { - if (isset($match[1], $match[2]) && $match[2] === './') { + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { $branches['trunk'] = $this->buildIdentifier( '/' . $this->trunkPath, (int) $match[1] @@ -295,20 +291,18 @@ public function getBranches(): array if ($this->branchesPath !== false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath); - if ($output) { + if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines(trim($output)) as $line) { $line = trim($line); - if ($line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { - if (isset($match[1], $match[2])) { - if ($match[2] === './') { - $lastRev = (int) $match[1]; - } else { - $branches[rtrim($match[2], '/')] = $this->buildIdentifier( - '/' . $this->branchesPath . '/' . $match[2], - max($lastRev, (int) $match[1]) - ); - } + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $branches[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->branchesPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); } } } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 38cf84e44807..fbd92843d2ab 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -40,7 +40,7 @@ public static function handle(int $level, string $message, string $file, int $li $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED; // error code is not included in error_reporting - if (!$isDeprecationNotice && !(error_reporting() & $level)) { + if (!$isDeprecationNotice && 0 === (error_reporting() & $level)) { return true; } @@ -53,7 +53,7 @@ public static function handle(int $level, string $message, string $file, int $li throw new \ErrorException($message, 0, $level, $file, $line); } - if (self::$io) { + if (self::$io !== null) { self::$io->writeError('Deprecation Notice: '.$message.' in '.$file.':'.$line.''); if (self::$io->isVerbose()) { self::$io->writeError('Stack trace:'); diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 64b64321648a..5a949db70723 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -69,6 +69,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $protocols = $this->config->get('github-protocols'); // public github, autoswitch protocols + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { $messages = []; foreach ($protocols as $protocol) { @@ -104,7 +105,9 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) { $errorMsg = $this->process->getErrorOutput(); // private github repository without ssh key access, try https with auth + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match) ) { if (!$this->io->hasAuthentication($match[1])) { @@ -127,6 +130,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups } elseif (Preg::isMatchStrictGroups('{^https://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match)) { //bitbucket oauth $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process); @@ -172,7 +176,9 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $errorMsg = $this->process->getErrorOutput(); } } elseif ( + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match) ) { if ($match[1] === 'git') { @@ -191,9 +197,9 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') { - $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password + $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password } else { - $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3]; + $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3]; } $command = $commandCallable($authUrl); @@ -201,11 +207,11 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, return; } - $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } - } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate - if (strpos($match[2], '@')) { + } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { // private non-github/gitlab/bitbucket repo that failed to authenticate + if (str_contains($match[2], '@')) { [$authParts, $match[2]] = explode('@', $match[2], 2); } @@ -214,8 +220,8 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $auth = $this->io->getAuthentication($match[2]); } elseif ($this->io->isInteractive()) { $defaultUsername = null; - if (isset($authParts) && $authParts) { - if (false !== strpos($authParts, ':')) { + if (isset($authParts) && $authParts !== '') { + if (str_contains($authParts, ':')) { [$defaultUsername, ] = explode(':', $authParts, 2); } else { $defaultUsername = $authParts; @@ -232,7 +238,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, } if (null !== $auth) { - $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; + $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3]; $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { @@ -243,7 +249,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, return; } - $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } @@ -262,7 +268,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, public function syncMirror(string $url, string $dir): bool { - if (Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { $this->io->writeError('Aborting git mirror sync of '.$url.' as network is disabled'); return false; @@ -357,13 +363,12 @@ private function checkRefIsInMirror(string $dir, string $ref): bool } /** - * @param array $match - * @param-out array $match + * @return array|null */ - private function isAuthenticationFailure(string $url, array &$match): bool + private function getAuthenticationFailure(string $url): ?array { if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { - return false; + return null; } $authFailures = [ @@ -377,11 +382,11 @@ private function isAuthenticationFailure(string $url, array &$match): bool $errorOutput = $this->process->getErrorOutput(); foreach ($authFailures as $authFailure) { if (strpos($errorOutput, $authFailure) !== false) { - return true; + return $match; } } - return false; + return null; } public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string @@ -405,7 +410,7 @@ public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPa $lines = $this->process->splitLines($output); foreach ($lines as $line) { - if (Preg::match('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches) > 0) { + if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) { return $matches[1]; } } diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index c687542e0462..28107584b670 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -60,17 +60,17 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd) // Try with the authentication information available if ( Preg::isMatch('{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi', $url, $matches) - && $this->io->hasAuthentication((string) $matches['host']) + && $this->io->hasAuthentication($matches['host']) ) { if ($matches['proto'] === 'ssh') { $user = ''; - if ($matches['user'] !== '' && $matches['user'] !== null) { + if ($matches['user'] !== null) { $user = rawurlencode($matches['user']) . '@'; } $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path']; } else { - $auth = $this->io->getAuthentication((string) $matches['host']); - $authenticatedUrl = $matches['proto'] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $matches['host'] . $matches['path']; + $auth = $this->io->getAuthentication($matches['host']); + $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path']; } $command = $commandCallable($authenticatedUrl); diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 452862273b38..807fe5ce9cb0 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -78,7 +78,7 @@ public function testBuildPhar(): void $ri = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::SELF_FIRST); foreach ($ri as $file) { - $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName(); + $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname(); if ($file->isDir()) { $fs->ensureDirectoryExists($targetPath); } else { @@ -89,7 +89,7 @@ public function testBuildPhar(): void $proc = new Process([PHP_BINARY, '-dphar.readonly=0', './bin/compile'], $target); $exitcode = $proc->run(); - if ($exitcode !== 0 || trim($proc->getOutput())) { + if ($exitcode !== 0 || trim($proc->getOutput()) !== '') { $this->fail($proc->getOutput()); } @@ -136,7 +136,9 @@ public function testIntegration(string $testFile): void $line++; } if ($expected[$i] === '%') { - Preg::isMatchStrictGroups('{%(.+?)%}', substr($expected, $i), $match); + if (!Preg::isMatchStrictGroups('{%(.+?)%}', substr($expected, $i), $match)) { + throw new \LogicException('Failed to match %...% in '.substr($expected, $i)); + } $regex = $match[1]; if (Preg::isMatch('{'.$regex.'}', substr($output, $j), $match)) { @@ -146,7 +148,7 @@ public function testIntegration(string $testFile): void } else { $this->fail( 'Failed to match pattern '.$regex.' at line '.$line.' / abs offset '.$i.': ' - .substr($output, $j, min(strpos($output, "\n", $j) - $j, 100)).PHP_EOL.PHP_EOL. + .substr($output, $j, min(((int) strpos($output, "\n", $j)) - $j, 100)).PHP_EOL.PHP_EOL. 'Output:'.PHP_EOL.$output ); } @@ -154,8 +156,8 @@ public function testIntegration(string $testFile): void if ($expected[$i] !== $output[$j]) { $this->fail( 'Output does not match expectation at line '.$line.' / abs offset '.$i.': '.PHP_EOL - .'-'.substr($expected, $i, min(strpos($expected, "\n", $i) - $i, 100)).PHP_EOL - .'+'.substr($output, $j, min(strpos($output, "\n", $j) - $j, 100)).PHP_EOL.PHP_EOL + .'-'.substr($expected, $i, min(((int) strpos($expected, "\n", $i)) - $i, 100)).PHP_EOL + .'+'.substr($output, $j, min(((int) strpos($output, "\n", $j)) - $j, 100)).PHP_EOL.PHP_EOL .'Output:'.PHP_EOL.$output ); } @@ -195,7 +197,7 @@ public static function getTestFiles(): array */ private function parseTestFile(string $file): array { - $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE); + $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', (string) file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE); $data = []; $section = null; From eeff1c79bad67d3055afa5ee3180763095f15671 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 25 Jul 2024 16:46:57 +0200 Subject: [PATCH 127/257] Fix addressability of branches containing # characters (#12042) Fixes #12029 --- src/Composer/DependencyResolver/Problem.php | 12 ++++++ src/Composer/Repository/VcsRepository.php | 3 +- .../solver-problem-with-hash-in-branch.test | 40 +++++++++++++++++++ .../Test/Repository/VcsRepositoryTest.php | 4 ++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/solver-problem-with-hash-in-branch.test diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index cf2cb381e327..a49bf5436278 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -25,6 +25,7 @@ use Composer\Semver\Constraint\ConstraintInterface; use Composer\Package\Version\VersionParser; use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\MultiConstraint; /** * Represents a problem detected while solving dependencies @@ -262,6 +263,17 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req } } + if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && Preg::isMatch('{^dev-.*#.*}', $constraint->getPrettyString())) { + $newConstraint = Preg::replace('{ +as +([^,\s|]+)$}', '', $constraint->getPrettyString()); + $packages = $repositorySet->findPackages($packageName, new MultiConstraint([ + new Constraint(Constraint::STR_OP_EQ, $newConstraint), + new Constraint(Constraint::STR_OP_EQ, str_replace('#', '+', $newConstraint)) + ], false)); + if (\count($packages) > 0) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).'. The # character in branch names is replaced by a + character. Make sure to require it as "'.str_replace('#', '+', $constraint->getPrettyString()).'".']; + } + } + // first check if the actual requested package is found in normal conditions // if so it must mean it is rejected by another constraint than the one given here if ($packages = $repositorySet->findPackages($packageName, $constraint)) { diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index d10ad87a0a81..57535295479e 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -341,7 +341,8 @@ protected function initialize() // make sure branch packages have a dev flag if (strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) { - $version = 'dev-' . $branch; + $version = 'dev-' . str_replace('#', '+', $branch); + $parsedBranch = str_replace('#', '+', $parsedBranch); } else { $prefix = strpos($branch, 'v') === 0 ? 'v' : ''; $version = $prefix . Preg::replace('{(\.9{7})+}', '.x', $parsedBranch); diff --git a/tests/Composer/Test/Fixtures/installer/solver-problem-with-hash-in-branch.test b/tests/Composer/Test/Fixtures/installer/solver-problem-with-hash-in-branch.test new file mode 100644 index 000000000000..e45a57720d96 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/solver-problem-with-hash-in-branch.test @@ -0,0 +1,40 @@ +--TEST-- +Test the problem output suggests fixes for branch names where the # was replaced by + +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "package/found", "version": "dev-foo+bar" }, + { "name": "package/found2", "version": "dev-foo+abcd09832478" }, + { "name": "package/works", "version": "dev-foo+abcd09832478" }, + { "name": "package/works2", "version": "dev-+123" } + ] + } + ], + "require": { + "package/found": "dev-foo#bar", + "package/found2": "dev-foo#abcd09832478", + "package/works": "dev-foo+abcd09832478", + "package/works2": "dev-+123" + } +} + +--RUN-- +update + +--EXPECT-EXIT-CODE-- +2 + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires package/found dev-foo#bar, found package/found[dev-foo+bar]. The # character in branch names is replaced by a + character. Make sure to require it as "dev-foo+bar". + Problem 2 + - Root composer.json requires package/found2 dev-foo#abcd09832478, found package/found2[dev-foo+abcd09832478]. The # character in branch names is replaced by a + character. Make sure to require it as "dev-foo+abcd09832478". + +--EXPECT-- diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php index 692e716b87b9..429b12b30764 100644 --- a/tests/Composer/Test/Repository/VcsRepositoryTest.php +++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php @@ -94,6 +94,9 @@ protected function initialize(): void $exec('git add foo'); $exec('git commit -m change-a'); + // add foo#bar branch which should result in dev-foo+bar + $exec('git branch foo#bar'); + // add version to composer.json $exec('git checkout master'); $composer['version'] = '1.0.0'; @@ -154,6 +157,7 @@ public function testLoadVersions(): void '1.1.x-dev' => true, 'dev-feature-b' => true, 'dev-feature/a-1.0-B' => true, + 'dev-foo+bar' => true, 'dev-master' => true, '9999999-dev' => true, // alias of dev-master ]; From 39981a0e2be5cd1b21e66e9a2eef178b96acc1af Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 25 Jul 2024 17:24:49 +0200 Subject: [PATCH 128/257] Sanitize VCS URLs when building cache keys, fixes #11917, closes #11918 (#12043) --- src/Composer/Downloader/GitDownloader.php | 6 +++--- src/Composer/Repository/Vcs/GitDriver.php | 2 +- src/Composer/Repository/Vcs/HgDriver.php | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 4561328ccb5e..f29d74522cab 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -67,7 +67,7 @@ protected function doDownload(PackageInterface $package, string $path, string $u GitUtil::cleanEnv(); - $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', $url).'/'; + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $gitVersion = GitUtil::getVersion($this->process); // --dissociate option is only available since git 2.3.0-rc0 @@ -92,7 +92,7 @@ protected function doInstall(PackageInterface $package, string $path, string $ur { GitUtil::cleanEnv(); $path = $this->normalizePath($path); - $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', $url).'/'; + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $ref = $package->getSourceReference(); $flag = Platform::isWindows() ? '/D ' : ''; @@ -161,7 +161,7 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } - $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', $url).'/'; + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $ref = $target->getSourceReference(); if (!empty($this->cachedPackages[$target->getId()][$ref])) { diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index d6f1b8213410..f3f7a91a6607 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -52,7 +52,7 @@ public function initialize(): void throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); } - $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; + $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)) . '/'; GitUtil::cleanEnv(); diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index e70b9a6ac517..e468ca746812 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -19,6 +19,7 @@ use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; +use Composer\Util\Url; /** * @author Per Bernhardt @@ -47,7 +48,7 @@ public function initialize(): void } $cacheDir = $this->config->get('cache-vcs-dir'); - $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', $this->url) . '/'; + $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', Url::sanitize($this->url)) . '/'; $fs = new Filesystem(); $fs->ensureDirectoryExists($cacheDir); From 17f4984601835c549af73c847794743c960ec51c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 25 Jul 2024 17:43:11 +0200 Subject: [PATCH 129/257] Make use of new PHP 8.4.0 function to replace implicit $http_response_header var (#11995) --- phpstan/baseline.neon | 16 ++++++++++------ src/Composer/Installer.php | 2 +- src/Composer/Util/RemoteFilesystem.php | 7 ++++++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 5ae3c0b3b964..8f455c83ff13 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -2045,11 +2045,6 @@ parameters: count: 1 path: ../src/Composer/Installer.php - - - message: "#^Casting to bool something that's already bool\\.$#" - count: 1 - path: ../src/Composer/Installer.php - - message: "#^Only booleans are allowed in &&, array\\\\> given on the right side\\.$#" count: 1 @@ -4353,6 +4348,16 @@ parameters: count: 8 path: ../src/Composer/Util/RemoteFilesystem.php + - + message: "#^Function http_clear_last_response_headers not found\\.$#" + count: 1 + path: ../src/Composer/Util/RemoteFilesystem.php + + - + message: "#^Function http_get_last_response_headers not found\\.$#" + count: 1 + path: ../src/Composer/Util/RemoteFilesystem.php + - message: "#^Method Composer\\\\Util\\\\RemoteFilesystem\\:\\:copy\\(\\) should return bool but returns bool\\|string\\.$#" count: 1 @@ -5095,4 +5100,3 @@ parameters: message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" count: 1 path: ../tests/Composer/Test/Util/TlsHelperTest.php - diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 8401072edabd..b88906aaedfc 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1329,7 +1329,7 @@ public function setDumpAutoloader(bool $dumpAutoloader = true): self */ public function setRunScripts(bool $runScripts = true): self { - $this->runScripts = (bool) $runScripts; + $this->runScripts = $runScripts; return $this; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 560ef35db6c5..7eeebc423b90 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -533,7 +533,12 @@ protected function getRemoteContents(string $originUrl, string $fileUrl, $contex } // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php - $responseHeaders = $http_response_header ?? []; + if (PHP_VERSION_ID >= 80400) { + $responseHeaders = http_get_last_response_headers(); + http_clear_last_response_headers(); + } else { + $responseHeaders = $http_response_header ?? []; + } if (null !== $e) { throw $e; From d53cf814293aca199a73d59c33af21405f2a0094 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 26 Jul 2024 09:30:57 +0200 Subject: [PATCH 130/257] Fix various phpstan warnings --- src/Composer/Util/Platform.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index d7735ebc1efe..6266b0413d78 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -76,7 +76,6 @@ public static function getEnv(string $name) */ public static function putEnv(string $name, string $value): void { - $value = (string) $value; putenv($name . '=' . $value); $_SERVER[$name] = $_ENV[$name] = $value; } @@ -105,7 +104,10 @@ public static function expandPath(string $path): string // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons if (Platform::isWindows() && $matches['var'] === 'HOME') { - return (Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE')) . $matches['path']; + if ((bool) Platform::getEnv('HOME')) { + return Platform::getEnv('HOME') . $matches['path']; + } + return Platform::getEnv('USERPROFILE') . $matches['path']; } return Platform::getEnv($matches['var']) . $matches['path']; @@ -180,7 +182,7 @@ public static function strlen(string $str): int { static $useMbString = null; if (null === $useMbString) { - $useMbString = \function_exists('mb_strlen') && ini_get('mbstring.func_overload'); + $useMbString = \function_exists('mb_strlen') && (bool) ini_get('mbstring.func_overload'); } if ($useMbString) { @@ -204,7 +206,7 @@ public static function isTty($fd = null): bool // detect msysgit/mingw and assume this is a tty because detection // does not work correctly, see https://github.com/composer/composer/issues/9690 - if (in_array(strtoupper(self::getEnv('MSYSTEM') ?: ''), ['MINGW32', 'MINGW64'], true)) { + if (in_array(strtoupper((string) self::getEnv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { return true; } @@ -220,8 +222,11 @@ public static function isTty($fd = null): bool } $stat = @fstat($fd); + if ($stat === false) { + return false; + } // Check if formatted mode is S_IFCHR - return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + return 0020000 === ($stat['mode'] & 0170000); } /** From d3d378184bf1ae88abb1c3c9f8d0c26de5ea2744 Mon Sep 17 00:00:00 2001 From: Sam L Date: Fri, 26 Jul 2024 10:21:02 -0400 Subject: [PATCH 131/257] Provide release-date/release-age and latest-release-date in composer outdated -A -f json (#12053) --- phpstan/baseline.neon | 11 ++++++++--- src/Composer/Command/ShowCommand.php | 11 ++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 8f455c83ff13..7d3160d1c0a9 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -671,17 +671,17 @@ parameters: path: ../src/Composer/Command/ShowCommand.php - - message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" + message: "#^Only booleans are allowed in &&, array given on the right side\\.$#" count: 1 path: ../src/Composer/Command/ShowCommand.php - - message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" + message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" count: 1 path: ../src/Composer/Command/ShowCommand.php - - message: "#^Only booleans are allowed in &&, array\\\\>\\> given on the right side\\.$#" + message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" count: 1 path: ../src/Composer/Command/ShowCommand.php @@ -760,6 +760,11 @@ parameters: count: 1 path: ../src/Composer/Command/ShowCommand.php + - + message: "#^Parameter \\#1 \\$array \\(array\\<'available'\\|'installed'\\|'locked'\\|'platform', list\\\\>\\>\\) to function array_filter contains falsy values only, the result will always be an empty array\\.$#" + count: 1 + path: ../src/Composer/Command/ShowCommand.php + - message: "#^Parameter \\#1 \\$arrayTree of method Composer\\\\Command\\\\ShowCommand\\:\\:displayPackageTree\\(\\) expects array\\\\>, array\\\\>\\|string\\|null\\>\\> given\\.$#" count: 2 diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 7af0348537da..b7b95705713e 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -43,6 +43,7 @@ use Composer\Semver\Semver; use Composer\Spdx\SpdxLicenses; use Composer\Util\PackageInfo; +use DateTimeInterface; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -493,7 +494,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion; $writeLatest = $writeVersion && $showLatest; $writeDescription = !$input->getOption('name-only') && !$input->getOption('path'); - $writeReleaseDate = $writeLatest && $input->getOption('sort-by-age'); + $writeReleaseDate = $writeLatest && ($input->getOption('sort-by-age') || $format === 'json'); $hasOutdatedPackages = false; @@ -550,8 +551,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $packageViewData['release-age'] = 'from '.$packageViewData['release-age']; } $releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age'])); + $packageViewData['release-date'] = $package->getReleaseDate()->format(DateTimeInterface::ATOM); } else { $packageViewData['release-age'] = ''; + $packageViewData['release-date'] = ''; } } if ($writeLatest && $latestPackage) { @@ -561,6 +564,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); $latestLength = max($latestLength, strlen($packageViewData['latest'])); + + if ($latestPackage->getReleaseDate() !== null) { + $packageViewData['latest-release-date'] = $latestPackage->getReleaseDate()->format(DateTimeInterface::ATOM); + } else { + $packageViewData['latest-release-date'] = ''; + } } elseif ($writeLatest) { $packageViewData['latest'] = '[none matched]'; $packageViewData['latest-status'] = 'up-to-date'; From 9da1948585f11e5af594d1fece682947f1b1fa99 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 19 Aug 2024 11:01:34 +0200 Subject: [PATCH 132/257] Improvements to docker detection (#12062) * Improvements to docker detection, fixes #11073 * Apply suggestions from code review Co-authored-by: Dan Wallis --- ...ow-to-install-untrusted-packages-safely.md | 7 ++++ src/Composer/Console/Application.php | 6 +-- src/Composer/Util/Platform.php | 42 +++++++++++++++++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/doc/faqs/how-to-install-untrusted-packages-safely.md b/doc/faqs/how-to-install-untrusted-packages-safely.md index 27a8b7efd5c8..6048d15237f7 100644 --- a/doc/faqs/how-to-install-untrusted-packages-safely.md +++ b/doc/faqs/how-to-install-untrusted-packages-safely.md @@ -39,3 +39,10 @@ Also note that the `exec` command will always run third party code as the user w See the [COMPOSER_ALLOW_SUPERUSER](../03-cli.md#composer-allow-superuser) environment variable for more info on how to disable the warnings. + +## Running Composer inside Docker/Podman containers + +Composer makes a best effort attempt to detect that it runs inside a container and if so it will +allow running as root without any further issues. If that detection fails however you will +see warnings and plugins will be disabled unless you set the [COMPOSER_ALLOW_SUPERUSER](../03-cli.md#composer-allow-superuser) +environment variable. diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 2f3e2c4b1dfb..217919bc025c 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -218,11 +218,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int $needsSudoCheck = !Platform::isWindows() && function_exists('exec') && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER') - && (ini_get('open_basedir') || ( - !file_exists('/.dockerenv') - && !file_exists('/run/.containerenv') - && !file_exists('/var/run/.containerenv') - )); + && !Platform::isDocker(); $isNonAllowedRoot = false; // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 6266b0413d78..dd41904a0784 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -25,6 +25,8 @@ class Platform private static $isVirtualBoxGuest = null; /** @var ?bool */ private static $isWindowsSubsystemForLinux = null; + /** @var ?bool */ + private static $isDocker = null; /** * getcwd() equivalent which always returns a string @@ -153,12 +155,10 @@ public static function isWindowsSubsystemForLinux(): bool } if ( - !ini_get('open_basedir') + !(bool) ini_get('open_basedir') && is_readable('/proc/version') && false !== stripos((string)Silencer::call('file_get_contents', '/proc/version'), 'microsoft') - && !file_exists('/.dockerenv') // Docker and Podman running inside WSL should not be seen as WSL - && !file_exists('/run/.containerenv') - && !file_exists('/var/run/.containerenv') + && !self::isDocker() // Docker and Podman running inside WSL should not be seen as WSL ) { return self::$isWindowsSubsystemForLinux = true; } @@ -175,6 +175,40 @@ public static function isWindows(): bool return \defined('PHP_WINDOWS_VERSION_BUILD'); } + public static function isDocker(): bool + { + if (null !== self::$isDocker) { + return self::$isDocker; + } + + // cannot check so assume no + if ((bool) ini_get('open_basedir')) { + return self::$isDocker = false; + } + + // .dockerenv and .containerenv are present in some cases but not reliably + if (file_exists('/.dockerenv') || file_exists('/run/.containerenv') || file_exists('/var/run/.containerenv')) { + return self::$isDocker = true; + } + + // see https://www.baeldung.com/linux/is-process-running-inside-container + $cgroups = [ + '/proc/self/mountinfo', // cgroup v2 + '/proc/1/cgroup', // cgroup v1 + ]; + foreach($cgroups as $cgroup) { + if (!is_readable($cgroup)) { + continue; + } + $data = file_get_contents($cgroup); + if (is_string($data) && str_contains($data, '/var/lib/docker/')) { + return self::$isDocker = true; + } + } + + return self::$isDocker = false; + } + /** * @return int return a guaranteed binary length of the string, regardless of silly mbstring configs */ From 3ba58ea3eb651dafa06bb69b2c6cb3a708d9b762 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 19 Aug 2024 13:52:44 +0200 Subject: [PATCH 133/257] Normalize namespaces in psr-0/psr-4 rules to fix edge cases, fixes #12028 (#12063) --- src/Composer/Autoload/AutoloadGenerator.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 9da1767adf7b..2d4bfd265f54 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -1241,6 +1241,10 @@ protected function parseAutoloadsType(array $packageMap, string $type, RootPacka } foreach ($autoload[$type] as $namespace => $paths) { + if (in_array($type, ['psr-4', 'psr-0'], true)) { + // normalize namespaces to ensure "\" becomes "" and others do not have leading separators as they are not needed + $namespace = ltrim($namespace, '\\'); + } foreach ((array) $paths as $path) { if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath.'/'.$path)) { // remove target-dir from file paths of the root package From 48d345ac3e5701347ccb68ced948c727de65ab05 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 14:07:03 +0200 Subject: [PATCH 134/257] Update deps, fix some phpstan issues Update baseline (1484, 84) --- composer.lock | 86 ++++++------ phpstan/baseline-8.3.neon | 10 -- phpstan/baseline.neon | 130 ------------------ phpstan/config.neon | 3 +- src/Composer/Autoload/AutoloadGenerator.php | 8 +- src/Composer/Config.php | 1 - .../EventDispatcher/EventDispatcher.php | 6 +- src/Composer/IO/BufferIO.php | 23 ++-- .../Installer/InstallationManager.php | 63 +++++---- src/Composer/Json/JsonFile.php | 2 +- src/Composer/Json/JsonFormatter.php | 6 +- src/Composer/Util/Filesystem.php | 1 - src/Composer/Util/Platform.php | 3 - src/Composer/Util/ProcessExecutor.php | 2 - src/Composer/Util/Url.php | 1 - tests/Composer/Test/TestCase.php | 1 - 16 files changed, 98 insertions(+), 248 deletions(-) diff --git a/composer.lock b/composer.lock index 019b129b5f4f..7bccf2d7bd94 100644 --- a/composer.lock +++ b/composer.lock @@ -226,26 +226,26 @@ }, { "name": "composer/pcre", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "0e455b78ac53637929b29d5ab5bf3c978329c1eb" + "reference": "06d0e49d6e136e4521c6bad18598bf0f6062ae37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/0e455b78ac53637929b29d5ab5bf3c978329c1eb", - "reference": "0e455b78ac53637929b29d5ab5bf3c978329c1eb", + "url": "https://api.github.com/repos/composer/pcre/zipball/06d0e49d6e136e4521c6bad18598bf0f6062ae37", + "reference": "06d0e49d6e136e4521c6bad18598bf0f6062ae37", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.8" + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^8 || ^9" }, @@ -285,7 +285,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.2.0" + "source": "https://github.com/composer/pcre/tree/2.3.0" }, "funding": [ { @@ -301,7 +301,7 @@ "type": "tidelift" } ], - "time": "2024-07-25T09:28:32+00:00" + "time": "2024-08-19T19:14:31+00:00" }, { "name": "composer/semver", @@ -941,16 +941,16 @@ }, { "name": "symfony/console", - "version": "v5.4.41", + "version": "v5.4.42", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba" + "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6473d441a913cb997123b59ff2dbe3d1cf9e11ba", - "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba", + "url": "https://api.github.com/repos/symfony/console/zipball/cef62396a0477e94fc52e87a17c6e5c32e226b7f", + "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f", "shasum": "" }, "require": { @@ -1020,7 +1020,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.41" + "source": "https://github.com/symfony/console/tree/v5.4.42" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T07:48:55+00:00" + "time": "2024-07-26T12:21:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1174,16 +1174,16 @@ }, { "name": "symfony/finder", - "version": "v5.4.40", + "version": "v5.4.42", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce" + "reference": "0724c51fa067b198e36506d2864e09a52180998a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f51cff4687547641c7d8180d74932ab40b2205ce", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/0724c51fa067b198e36506d2864e09a52180998a", + "reference": "0724c51fa067b198e36506d2864e09a52180998a", "shasum": "" }, "require": { @@ -1217,7 +1217,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.40" + "source": "https://github.com/symfony/finder/tree/v5.4.42" }, "funding": [ { @@ -1233,7 +1233,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-07-22T08:53:29+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1932,16 +1932,16 @@ }, { "name": "symfony/string", - "version": "v5.4.41", + "version": "v5.4.42", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096" + "reference": "909cec913edea162a3b2836788228ad45fcab337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/065a9611e0b1fd2197a867e1fb7f2238191b7096", - "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096", + "url": "https://api.github.com/repos/symfony/string/zipball/909cec913edea162a3b2836788228ad45fcab337", + "reference": "909cec913edea162a3b2836788228ad45fcab337", "shasum": "" }, "require": { @@ -1998,7 +1998,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.41" + "source": "https://github.com/symfony/string/tree/v5.4.42" }, "funding": [ { @@ -2014,22 +2014,22 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:20:55+00:00" + "time": "2024-07-20T18:38:32+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.8", + "version": "1.11.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" + "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", - "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-07-24T07:01:22+00:00" + "time": "2024-08-19T14:37:29+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2226,16 +2226,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.6", + "version": "1.4.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f" + "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/e909a075d69e0d4db262ac3407350ae2c6b6ab5f", - "reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/14eec8c011b856eee4d744a2a3f709db1e1858bd", + "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd", "shasum": "" }, "require": { @@ -2292,22 +2292,22 @@ "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.4.6" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.8" }, - "time": "2024-07-16T11:48:54+00:00" + "time": "2024-08-13T19:43:40+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8" + "reference": "e823122d31935eb711e2767c31f3d71cb0b87fb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8", - "reference": "8eb63f1c0e2001f97b3cd9ed550b18765cdeb1c8", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e823122d31935eb711e2767c31f3d71cb0b87fb1", + "reference": "e823122d31935eb711e2767c31f3d71cb0b87fb1", "shasum": "" }, "require": { @@ -2360,7 +2360,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.2" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.3" }, "funding": [ { @@ -2376,7 +2376,7 @@ "type": "tidelift" } ], - "time": "2024-06-25T19:55:06+00:00" + "time": "2024-07-26T12:41:01+00:00" } ], "aliases": [], diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index 5e17a28f4aa5..4871d6cf6c53 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -105,16 +105,6 @@ parameters: count: 1 path: ../src/Composer/EventDispatcher/EventDispatcher.php - - - message: "#^Parameter \\#1 \\$stream of function fwrite expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^Parameter \\#1 \\$stream of function rewind expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 7d3160d1c0a9..8bf450f15a66 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -1885,11 +1885,6 @@ parameters: count: 1 path: ../src/Composer/EventDispatcher/EventDispatcher.php - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/EventDispatcher/EventDispatcher.php - - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" count: 2 @@ -1985,46 +1980,6 @@ parameters: count: 1 path: ../src/Composer/IO/BaseIO.php - - - message: "#^Only booleans are allowed in a ternary operator condition, Symfony\\\\Component\\\\Console\\\\Formatter\\\\OutputFormatterInterface\\|null given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^PHPDoc type Symfony\\\\Component\\\\Console\\\\Input\\\\StringInput of property Composer\\\\IO\\\\BufferIO\\:\\:\\$input is not the same as PHPDoc type Symfony\\\\Component\\\\Console\\\\Input\\\\InputInterface of overridden property Composer\\\\IO\\\\ConsoleIO\\:\\:\\$input\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^PHPDoc type Symfony\\\\Component\\\\Console\\\\Output\\\\StreamOutput of property Composer\\\\IO\\\\BufferIO\\:\\:\\$output is not the same as PHPDoc type Symfony\\\\Component\\\\Console\\\\Output\\\\OutputInterface of overridden property Composer\\\\IO\\\\ConsoleIO\\:\\:\\$output\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^Parameter \\#1 \\$fp of function fwrite expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^Parameter \\#1 \\$fp of function rewind expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^Parameter \\#1 \\$stream of class Symfony\\\\Component\\\\Console\\\\Output\\\\StreamOutput constructor expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^Parameter \\#1 \\$stream of method Symfony\\\\Component\\\\Console\\\\Input\\\\Input\\:\\:setStream\\(\\) expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - - - message: "#^Parameter \\#3 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:replaceCallback\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/IO/BufferIO.php - - message: "#^Parameter \\#1 \\$attempts of method Symfony\\\\Component\\\\Console\\\\Question\\\\Question\\:\\:setMaxAttempts\\(\\) expects int\\|null, int\\\\|int\\<1, max\\>\\|true\\|null given\\.$#" count: 1 @@ -2145,71 +2100,6 @@ parameters: count: 1 path: ../src/Composer/Installer/BinaryInstaller.php - - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 2 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Foreach overwrites \\$batch with its value variable\\.$#" - count: 2 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\EventDispatcher\\\\EventDispatcher\\|null given on the right side\\.$#" - count: 2 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in &&, array given on the right side\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in a negated boolean, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in an if condition, Symfony\\\\Component\\\\Console\\\\Helper\\\\ProgressBar\\|null given\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 4 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\> given\\.$#" - count: 2 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\>\\|false given\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - - - message: "#^Parameter \\#2 \\$offset of function array_splice expects int, int\\|string given\\.$#" - count: 1 - path: ../src/Composer/Installer/InstallationManager.php - - message: "#^Variable method call on \\$this\\(Composer\\\\Installer\\\\InstallationManager\\)\\.$#" count: 2 @@ -4303,31 +4193,11 @@ parameters: count: 1 path: ../src/Composer/Util/Perforce.php - - - message: "#^Casting to string something that's already string\\.$#" - count: 1 - path: ../src/Composer/Util/Platform.php - - - - message: "#^Only booleans are allowed in &&, string\\|false given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Platform.php - - message: "#^Only booleans are allowed in a negated boolean, string\\|false given\\.$#" count: 1 path: ../src/Composer/Util/Platform.php - - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Platform.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: ../src/Composer/Util/Platform.php - - message: "#^Method Composer\\\\Util\\\\ProcessExecutor\\:\\:doExecute\\(\\) should return int but returns int\\|null\\.$#" count: 1 diff --git a/phpstan/config.neon b/phpstan/config.neon index 3c8e1a20c5f2..5581b24deceb 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -18,9 +18,8 @@ parameters: excludePaths: - '../tests/Composer/Test/Fixtures/*' - '../tests/Composer/Test/Autoload/Fixtures/*' - - '../tests/Composer/Test/Autoload/MinimumVersionSupport/vendor/' + - '../tests/Composer/Test/Autoload/MinimumVersionSupport/vendor/*' - '../tests/Composer/Test/Plugin/Fixtures/*' - - '../tests/Composer/Test/PolyfillTestCase.php' reportUnmatchedIgnoredErrors: false treatPhpDocTypesAsCertain: false diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 2d4bfd265f54..e3dd15646cc6 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -410,7 +410,7 @@ public static function autoload(\$class) // carry over existing autoload.php's suffix if possible and none is configured if (null === $suffix && Filesystem::isReadable($vendorPath.'/autoload.php')) { - $content = file_get_contents($vendorPath.'/autoload.php'); + $content = (string) file_get_contents($vendorPath.'/autoload.php'); if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) { $suffix = $match[1]; } @@ -1269,10 +1269,8 @@ protected function parseAutoloadsType(array $packageMap, string $type, RootPacka $path = Preg::replaceCallback( '{^((?:(?:\\\\\\.){1,2}+/)+)}', static function ($matches) use (&$updir): string { - if (isset($matches[1])) { - // undo preg_quote for the matched string - $updir = str_replace('\\.', '.', $matches[1]); - } + // undo preg_quote for the matched string + $updir = str_replace('\\.', '.', $matches[1]); return ''; }, diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 4c87a195dc37..01d86324944b 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -529,7 +529,6 @@ private function process($value, int $flags) } return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) { - assert(is_string($match[1])); return $this->get($match[1], $flags); }, $value); } diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 347d53b5d582..0af571c39740 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -330,7 +330,7 @@ protected function doDispatch(Event $event) } $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); - if ($possibleLocalBinaries) { + if (count($possibleLocalBinaries) > 0) { foreach ($possibleLocalBinaries as $localExec) { if (Preg::isMatch('{\b'.preg_quote($callable).'$}', $localExec)) { $caller = BinaryInstaller::determineBinaryCaller($localExec); @@ -354,7 +354,7 @@ protected function doDispatch(Event $event) $pathAndArgs = substr($exec, 5); if (Platform::isWindows()) { $pathAndArgs = Preg::replaceCallback('{^\S+}', static function ($path) { - return str_replace('/', '\\', (string) $path[0]); + return str_replace('/', '\\', $path[0]); }, $pathAndArgs); } // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it @@ -384,8 +384,6 @@ protected function doDispatch(Event $event) if (Platform::isWindows()) { $exec = Preg::replaceCallback('{^\S+}', static function ($path) { - assert(is_string($path[0])); - return str_replace('/', '\\', $path[0]); }, $exec); } diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index 0404287a0cbb..6cf962b84b47 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -14,6 +14,8 @@ use Composer\Pcre\Preg; use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Input\StreamableInputInterface; @@ -25,17 +27,16 @@ */ class BufferIO extends ConsoleIO { - /** @var StringInput */ - protected $input; - /** @var StreamOutput */ - protected $output; - public function __construct(string $input = '', int $verbosity = StreamOutput::VERBOSITY_NORMAL, ?OutputFormatterInterface $formatter = null) { $input = new StringInput($input); $input->setInteractive(false); - $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter); + $stream = fopen('php://memory', 'rw'); + if ($stream === false) { + throw new \RuntimeException('Unable to open memory output stream'); + } + $output = new StreamOutput($stream, $verbosity, $formatter !== null ? $formatter->isDecorated() : false, $formatter); parent::__construct($input, $output, new HelperSet([ new QuestionHelper(), @@ -47,13 +48,12 @@ public function __construct(string $input = '', int $verbosity = StreamOutput::V */ public function getOutput(): string { + assert($this->output instanceof StreamOutput); fseek($this->output->getStream(), 0); - $output = stream_get_contents($this->output->getStream()); + $output = (string) stream_get_contents($this->output->getStream()); $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches): string { - assert(is_string($matches[1])); - assert(is_string($matches[2])); $pre = strip_tags($matches[1]); if (strlen($pre) === strlen($matches[2])) { @@ -85,11 +85,14 @@ public function setUserInputs(array $inputs): void /** * @param string[] $inputs * - * @return false|resource stream + * @return resource stream */ private function createStream(array $inputs) { $stream = fopen('php://memory', 'r+'); + if ($stream === false) { + throw new \RuntimeException('Unable to open memory output stream'); + } foreach ($inputs as $input) { fwrite($stream, $input.PHP_EOL); diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 27afa7c3add2..89169a6cb046 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -39,7 +39,7 @@ */ class InstallationManager { - /** @var array */ + /** @var list */ private $installers = []; /** @var array */ private $cache = []; @@ -180,7 +180,7 @@ public function ensureBinariesPresence(PackageInterface $package): void */ public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void { - /** @var array> */ + /** @var array> $cleanupPromises */ $cleanupPromises = []; $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) { @@ -197,25 +197,28 @@ public function execute(InstalledRepositoryInterface $repo, array $operations, b foreach ($operations as $index => $operation) { if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) { $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); - if ($package->getType() === 'composer-plugin' && ($extra = $package->getExtra()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { - if ($batch) { - $batches[] = $batch; + if ($package->getType() === 'composer-plugin') { + $extra = $package->getExtra(); + if (isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { + if (count($batch) > 0) { + $batches[] = $batch; + } + $batches[] = [$index => $operation]; + $batch = []; + + continue; } - $batches[] = [$index => $operation]; - $batch = []; - - continue; } } $batch[$index] = $operation; } - if ($batch) { + if (count($batch) > 0) { $batches[] = $batch; } - foreach ($batches as $batch) { - $this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations); + foreach ($batches as $batchToExecute) { + $this->downloadAndExecuteBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations); } } catch (\Exception $e) { $this->runCleanup($cleanupPromises); @@ -248,7 +251,7 @@ private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, arr $opType = $operation->getOperationType(); // ignoring alias ops as they don't need to execute anything at this stage - if (!in_array($opType, ['update', 'install', 'uninstall'])) { + if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { continue; } @@ -266,7 +269,7 @@ private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, arr $cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface { // avoid calling cleanup if the download was not even initialized for a package // as without installation source configured nothing will work - if (!$package->getInstallationSource()) { + if (null === $package->getInstallationSource()) { return \React\Promise\resolve(null); } @@ -282,7 +285,7 @@ private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, arr } // execute all downloads first - if (count($promises)) { + if (count($promises) > 0) { $this->waitOnPromises($promises); } @@ -299,7 +302,7 @@ private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, arr if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) { $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { - if ($batch) { + if (count($batch) > 0) { $batches[] = $batch; } $batches[] = [$index => $operation]; @@ -311,12 +314,12 @@ private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, arr $batch[$index] = $operation; } - if ($batch) { + if (count($batch) > 0) { $batches[] = $batch; } - foreach ($batches as $batch) { - $this->executeBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $allOperations); + foreach ($batches as $batchToExecute) { + $this->executeBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $allOperations); } } @@ -334,7 +337,7 @@ private function executeBatch(InstalledRepositoryInterface $repo, array $operati $opType = $operation->getOperationType(); // ignoring alias ops as they don't need to execute anything - if (!in_array($opType, ['update', 'install', 'uninstall'])) { + if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { // output alias ops in debug verbosity as they have no output otherwise if ($this->io->isDebug()) { $this->io->writeError(' - ' . $operation->show(false)); @@ -360,9 +363,9 @@ private function executeBatch(InstalledRepositoryInterface $repo, array $operati 'install' => PackageEvents::PRE_PACKAGE_INSTALL, 'update' => PackageEvents::PRE_PACKAGE_UPDATE, 'uninstall' => PackageEvents::PRE_PACKAGE_UNINSTALL, - ][$opType] ?? null; + ][$opType]; - if (null !== $eventName && $runScripts && $this->eventDispatcher) { + if ($runScripts && $this->eventDispatcher !== null) { $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); } @@ -389,9 +392,9 @@ private function executeBatch(InstalledRepositoryInterface $repo, array $operati 'install' => PackageEvents::POST_PACKAGE_INSTALL, 'update' => PackageEvents::POST_PACKAGE_UPDATE, 'uninstall' => PackageEvents::POST_PACKAGE_UNINSTALL, - ][$opType] ?? null; + ][$opType]; - if (null !== $eventName && $runScripts && $dispatcher) { + if ($runScripts && $dispatcher !== null) { $postExecCallbacks[] = static function () use ($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation): void { $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); }; @@ -401,7 +404,7 @@ private function executeBatch(InstalledRepositoryInterface $repo, array $operati } // execute all prepare => installs/updates/removes => cleanup steps - if (count($promises)) { + if (count($promises) > 0) { $this->waitOnPromises($promises); } @@ -421,14 +424,14 @@ private function waitOnPromises(array $promises): void if ( $this->outputProgress && $this->io instanceof ConsoleIO - && !Platform::getEnv('CI') + && !((bool) Platform::getEnv('CI')) && !$this->io->isDebug() && count($promises) > 1 ) { $progress = $this->io->getProgressBar(); } $this->loop->wait($promises, $progress); - if ($progress) { + if ($progress !== null) { $progress->clear(); // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing if (!$this->io->isDecorated()) { @@ -573,7 +576,7 @@ public function notifyInstalls(IOInterface $io): void try { foreach ($this->notifiablePackages as $repoUrl => $packages) { // non-batch API, deprecated - if (strpos($repoUrl, '%package%')) { + if (str_contains($repoUrl, '%package%')) { foreach ($packages as $package) { $url = str_replace('%package%', $package->getPrettyName(), $repoUrl); @@ -635,7 +638,7 @@ public function notifyInstalls(IOInterface $io): void private function markForNotification(PackageInterface $package): void { - if ($package->getNotificationUrl()) { + if ($package->getNotificationUrl() !== null) { $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; } } @@ -663,7 +666,7 @@ private function runCleanup(array $cleanupPromises): void }); } - if (!empty($promises)) { + if (count($promises) > 0) { $this->loop->wait($promises); } } diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 7f9814d61d3e..df4067e265c0 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -284,7 +284,7 @@ public static function encode($data, int $options = 448, string $indent = self:: return Preg::replaceCallback( '#^ {4,}#m', static function ($match) use ($indent): string { - return str_repeat($indent, (int)(strlen($match[0] ?? '') / 4)); + return str_repeat($indent, (int)(strlen($match[0]) / 4)); }, $json ); diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php index 417067241f83..fa1a3c53f2f1 100644 --- a/src/Composer/Json/JsonFormatter.php +++ b/src/Composer/Json/JsonFormatter.php @@ -68,8 +68,6 @@ public static function format(string $json, bool $unescapeUnicode, bool $unescap if ($unescapeUnicode && function_exists('mb_convert_encoding')) { // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match): string { - assert(is_string($match[1])); - assert(is_string($match[2])); $l = strlen($match[1]); if ($l % 2) { @@ -77,7 +75,7 @@ public static function format(string $json, bool $unescapeUnicode, bool $unescap // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped // see https://github.com/composer/composer/issues/7510 if (0xD800 <= $code && 0xDFFF >= $code) { - return (string) $match[0]; + return $match[0]; } return str_repeat('\\', $l - 1) . mb_convert_encoding( @@ -87,7 +85,7 @@ public static function format(string $json, bool $unescapeUnicode, bool $unescap ); } - return (string) $match[0]; + return $match[0]; }, $buffer); } diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index a8b1e8ea7488..0bb0ef86998c 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -608,7 +608,6 @@ public function normalizePath(string $path) // ensure c: is normalized to C: $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) { - assert(is_string($m[0])); return strtoupper($m[0]); }, $prefix); diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index dd41904a0784..c1a38bac1182 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -101,9 +101,6 @@ public static function expandPath(string $path): string } return Preg::replaceCallback('#^(\$|(?P%))(?P\w++)(?(percent)%)(?P.*)#', static function ($matches): string { - assert(is_string($matches['var'])); - assert('' !== $matches['var']); - // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons if (Platform::isWindows() && $matches['var'] === 'HOME') { if ((bool) Platform::getEnv('HOME')) { diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index d6a983ef9be4..bf4b49846bf8 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -455,8 +455,6 @@ private function outputCommandRun($command, ?string $cwd, bool $async): void $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command)); $safeCommand = Preg::replaceCallback('{://(?P[^:/\s]+):(?P[^@\s/]+)@}i', static function ($m): string { - assert(is_string($m['user'])); - // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) { return '://***:***@'; diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 5d703a20def2..040d17db04e6 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -110,7 +110,6 @@ public static function sanitize(string $url): string $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url); $url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@}i', static function ($m): string { - assert(is_string($m['user'])); // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}', $m['user'])) { return $m['prefix'].'***:***@'; diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index b1c4c23d710f..233e1c3d125e 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -355,7 +355,6 @@ protected static function getCmd(string $cmd): string { if (Platform::isWindows()) { $cmd = Preg::replaceCallback("/('[^']*')/", static function ($m) { - assert(is_string($m[1])); // Double-quotes are used only when needed $char = (strpbrk($m[1], " \t^&|<>()") !== false || $m[1] === "''") ? '"' : ''; From 556ca0690674f5e5376b721c2ea4985c7b1d5718 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 16:44:20 +0200 Subject: [PATCH 135/257] Fix phpstan build with latest deps --- src/Composer/IO/BaseIO.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index c4d40ba015b7..55ac166044a6 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -176,46 +176,74 @@ public function loadConfiguration(Config $config) ProcessExecutor::setTimeout($config->get('process-timeout')); } + /** + * @param string|\Stringable $message + */ public function emergency($message, array $context = []): void { $this->log(LogLevel::EMERGENCY, $message, $context); } + /** + * @param string|\Stringable $message + */ public function alert($message, array $context = []): void { $this->log(LogLevel::ALERT, $message, $context); } + /** + * @param string|\Stringable $message + */ public function critical($message, array $context = []): void { $this->log(LogLevel::CRITICAL, $message, $context); } + /** + * @param string|\Stringable $message + */ public function error($message, array $context = []): void { $this->log(LogLevel::ERROR, $message, $context); } + /** + * @param string|\Stringable $message + */ public function warning($message, array $context = []): void { $this->log(LogLevel::WARNING, $message, $context); } + /** + * @param string|\Stringable $message + */ public function notice($message, array $context = []): void { $this->log(LogLevel::NOTICE, $message, $context); } + /** + * @param string|\Stringable $message + */ public function info($message, array $context = []): void { $this->log(LogLevel::INFO, $message, $context); } + /** + * @param string|\Stringable $message + */ public function debug($message, array $context = []): void { $this->log(LogLevel::DEBUG, $message, $context); } + /** + * @param mixed|LogLevel::* $level + * @param string|\Stringable $message + */ public function log($level, $message, array $context = []): void { $message = (string) $message; From 8f3fed674b5ee83e9136a3d173ed236452c7c1af Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 17:06:42 +0200 Subject: [PATCH 136/257] Clean up md5/sha1 usages, upgrade algos where possible (#12088) * Clean up md5/sha1 usages, upgrade algos where possible * Fully qualify PHP_VERSION_ID constant usages * Fix 7.2 build --- phpstan/baseline-8.3.neon | 15 +++++++++++++++ phpstan/baseline.neon | 12 +----------- src/Composer/Autoload/AutoloadGenerator.php | 7 ++++--- src/Composer/Cache.php | 4 ++-- src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Command/SelfUpdateCommand.php | 2 +- src/Composer/Console/Application.php | 4 ++-- src/Composer/DependencyResolver/GenericRule.php | 2 +- .../DependencyResolver/MultiConflictRule.php | 2 +- src/Composer/Downloader/ArchiveDownloader.php | 2 +- src/Composer/Downloader/FileDownloader.php | 4 ++-- src/Composer/EventDispatcher/EventDispatcher.php | 2 +- src/Composer/Package/Archiver/ArchiveManager.php | 6 +++--- src/Composer/Package/Comparer/Comparer.php | 2 +- src/Composer/Package/Locker.php | 4 ++-- src/Composer/Repository/ArtifactRepository.php | 2 +- src/Composer/Repository/PathRepository.php | 2 +- src/Composer/SelfUpdate/Versions.php | 2 +- src/Composer/Util/ComposerMirror.php | 4 ++-- src/Composer/Util/Http/CurlDownloader.php | 4 ++-- src/Composer/Util/RemoteFilesystem.php | 2 +- src/Composer/Util/TlsHelper.php | 2 +- .../Composer/Test/DependencyResolver/RuleTest.php | 2 +- .../Test/Downloader/FileDownloaderTest.php | 4 ++-- .../Test/Downloader/GitDownloaderTest.php | 2 +- .../Test/Installer/BinaryInstallerTest.php | 2 +- .../Test/Installer/MetapackageInstallerTest.php | 2 +- .../Installer/SuggestedPackagesReporterTest.php | 2 +- tests/Composer/Test/InstallerTest.php | 2 +- tests/Composer/Test/Package/LockerTest.php | 10 +++++----- .../Test/Repository/PathRepositoryTest.php | 2 +- tests/Composer/Test/TestCase.php | 2 +- tests/Composer/Test/Util/ErrorHandlerTest.php | 4 ++-- 33 files changed, 64 insertions(+), 58 deletions(-) diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index 4871d6cf6c53..cf5444103339 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -65,6 +65,16 @@ parameters: count: 1 path: ../src/Composer/Console/Input/InputOption.php + - + message: "#^Casting to string something that's already string\\.$#" + count: 1 + path: ../src/Composer/DependencyResolver/GenericRule.php + + - + message: "#^Casting to string something that's already string\\.$#" + count: 1 + path: ../src/Composer/DependencyResolver/MultiConflictRule.php + - message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(string, string\\)\\: int, 'version_compare' given\\.$#" count: 2 @@ -260,6 +270,11 @@ parameters: count: 2 path: ../tests/Composer/Test/ConfigTest.php + - + message: "#^Casting to string something that's already string\\.$#" + count: 1 + path: ../tests/Composer/Test/DependencyResolver/RuleTest.php + - message: "#^Call to function method_exists\\(\\) with Composer\\\\Console\\\\Application and 'setCatchErrors' will always evaluate to true\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 8bf450f15a66..9dd3190277e9 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -85,11 +85,6 @@ parameters: count: 1 path: ../src/Composer/Autoload/AutoloadGenerator.php - - - message: "#^Parameter \\#2 \\$subject of static method Composer\\\\Pcre\\\\Preg\\:\\:isMatch\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Autoload/AutoloadGenerator.php - - message: "#^Parameter \\#2 \\$to of method Composer\\\\Util\\\\Filesystem\\:\\:findShortestPathCode\\(\\) expects string, string\\|false given\\.$#" count: 1 @@ -1052,7 +1047,7 @@ parameters: - message: "#^Only booleans are allowed in \\|\\|, string\\|false given on the left side\\.$#" - count: 2 + count: 1 path: ../src/Composer/Console/Application.php - @@ -4193,11 +4188,6 @@ parameters: count: 1 path: ../src/Composer/Util/Perforce.php - - - message: "#^Only booleans are allowed in a negated boolean, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Platform.php - - message: "#^Method Composer\\\\Util\\\\ProcessExecutor\\:\\:doExecute\\(\\) should return int but returns int\\|null\\.$#" count: 1 diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index e3dd15646cc6..2296a7eb1287 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -417,7 +417,7 @@ public static function autoload(\$class) } if (null === $suffix) { - $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : md5(uniqid('', true)); + $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : bin2hex(random_bytes(16)); } } @@ -1052,7 +1052,7 @@ public static function getLoader() } if ($this->apcu) { - $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : substr(base64_encode(md5(uniqid('', true), true)), 0, -3)), true); + $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : bin2hex(random_bytes(10))), true); $file .= <<setApcuPrefix($apcuPrefix); @@ -1312,7 +1312,8 @@ static function ($matches) use (&$updir): string { */ protected function getFileIdentifier(PackageInterface $package, string $path) { - return md5($package->getName() . ':' . $path); + // TODO composer v3 change this to sha1 or xxh3? Possibly not worth the potential breakage though + return hash('md5', $package->getName() . ':' . $path); } /** diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index b1e008594cee..2e6f2edadfd4 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -144,7 +144,7 @@ public function write(string $file, string $contents) $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); - $tempFileName = $this->root . $file . uniqid('.', true) . '.tmp'; + $tempFileName = $this->root . $file . bin2hex(random_bytes(5)) . '.tmp'; try { return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file); } catch (\ErrorException $e) { @@ -357,7 +357,7 @@ public function sha1(string $file) if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { - return sha1_file($this->root . $file); + return hash_file('sha1', $this->root . $file); } } diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 06a8a84af014..e95ca8eaab67 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -630,7 +630,7 @@ private function checkPlatform() $errors['ioncube'] = ioncube_loader_version(); } - if (PHP_VERSION_ID < 70205) { + if (\PHP_VERSION_ID < 70205) { $errors['php'] = PHP_VERSION; } diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index bd006ea88a34..6ca01120c065 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -328,7 +328,7 @@ class_exists('Composer\Downloader\FilesystemException'); $verified = 1 === openssl_verify((string) file_get_contents($tempFilename), $signatureSha384, $pubkeyid, $algo); // PHP 8 automatically frees the key instance and deprecates the function - if (PHP_VERSION_ID < 80000) { + if (\PHP_VERSION_ID < 80000) { // @phpstan-ignore function.deprecated openssl_free_key($pubkeyid); } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 217919bc025c..64166a98226f 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -321,7 +321,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' ), true, IOInterface::DEBUG); - if (PHP_VERSION_ID < 70205) { + if (\PHP_VERSION_ID < 70205) { $io->writeError('Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.'. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.'); } @@ -348,7 +348,7 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow // Check system temp folder for usability as it can cause weird runtime issues otherwise Silencer::call(static function () use ($io): void { $pid = function_exists('getmypid') ? getmypid() . '-' : ''; - $tempfile = sys_get_temp_dir() . '/temp-' . $pid . md5(microtime()); + $tempfile = sys_get_temp_dir() . '/temp-' . $pid . bin2hex(random_bytes(5)); if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) === __FILE__) && unlink($tempfile) && !file_exists($tempfile))) { $io->writeError(sprintf('PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini', sys_get_temp_dir())); } diff --git a/src/Composer/DependencyResolver/GenericRule.php b/src/Composer/DependencyResolver/GenericRule.php index f7cf7f23c32d..c4b2f981ad9d 100644 --- a/src/Composer/DependencyResolver/GenericRule.php +++ b/src/Composer/DependencyResolver/GenericRule.php @@ -46,7 +46,7 @@ public function getLiterals(): array */ public function getHash() { - $data = unpack('ihash', md5(implode(',', $this->literals), true)); + $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true)); return $data['hash']; } diff --git a/src/Composer/DependencyResolver/MultiConflictRule.php b/src/Composer/DependencyResolver/MultiConflictRule.php index 4826489d258a..05fedc2077e7 100644 --- a/src/Composer/DependencyResolver/MultiConflictRule.php +++ b/src/Composer/DependencyResolver/MultiConflictRule.php @@ -52,7 +52,7 @@ public function getLiterals(): array */ public function getHash() { - $data = unpack('ihash', md5('c:'.implode(',', $this->literals), true)); + $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true)); return $data['hash']; } diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 6de51ee58ba2..ff132e28b419 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -68,7 +68,7 @@ public function install(PackageInterface $package, string $path, bool $output = } do { - $temporaryDir = $vendorDir.'/composer/'.substr(md5(uniqid('', true)), 0, 8); + $temporaryDir = $vendorDir.'/composer/'.bin2hex(random_bytes(4)); } while (is_dir($temporaryDir)); $this->addCleanupPath($package, $temporaryDir); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index adf26785c1bf..2e1207b5519b 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -126,7 +126,7 @@ public function download(PackageInterface $package, string $path, ?PackageInterf } $cacheKeyGenerator = static function (PackageInterface $package, $key): string { - $cacheKey = sha1($key); + $cacheKey = hash('sha1', $key); return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); }; @@ -441,7 +441,7 @@ protected function getFileName(PackageInterface $package, string $path): string $extension = $package->getDistType(); } - return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . md5($package . spl_object_hash($package)) . '.' . $extension, '.'); + return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . hash('md5', $package . spl_object_hash($package)) . '.' . $extension, '.'); } /** diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 0af571c39740..d10ca53b22ae 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -299,7 +299,7 @@ protected function doDispatch(Event $event) // it does not hurt to keep the same stream as the current Application if ($this->io instanceof ConsoleIO) { $reflProp = new \ReflectionProperty($this->io, 'output'); - if (PHP_VERSION_ID < 80100) { + if (\PHP_VERSION_ID < 80100) { $reflProp->setAccessible(true); } $output = $reflProp->getValue($this->io); diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 4b15fa844f3c..77c3ebe3dcc1 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -96,7 +96,7 @@ public function getPackageFilenameParts(CompletePackageInterface $package): arra $sourceReference = $package->getSourceReference(); if (null !== $sourceReference) { - $parts['source_reference'] = substr(sha1($sourceReference), 0, 6); + $parts['source_reference'] = substr(hash('sha1', $sourceReference), 0, 6); } $parts = array_filter($parts, function (?string $part) { @@ -171,7 +171,7 @@ public function archive(CompletePackageInterface $package, string $format, strin $sourcePath = realpath('.'); } else { // Directory used to download the sources - $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid(); + $sourcePath = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)); $filesystem->ensureDirectoryExists($sourcePath); try { @@ -216,7 +216,7 @@ public function archive(CompletePackageInterface $package, string $format, strin } // Create the archive - $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format; + $tempTarget = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)).'.'.$format; $filesystem->ensureDirectoryExists(dirname($tempTarget)); $archivePath = $usableArchiver->archive( diff --git a/src/Composer/Package/Comparer/Comparer.php b/src/Composer/Package/Comparer/Comparer.php index 1fd79b8bbd6d..70a7a28f83dd 100644 --- a/src/Composer/Package/Comparer/Comparer.php +++ b/src/Composer/Package/Comparer/Comparer.php @@ -136,7 +136,7 @@ private function doTree(string $dir, array &$array) return false; } } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) { - $array[$dir][$file] = md5_file($dir.'/'.$file); + $array[$dir][$file] = hash_file(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', $dir.'/'.$file); } } } diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index da1e4bf5ea7d..2c6ae087df4d 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -66,7 +66,7 @@ public function __construct(IOInterface $io, JsonFile $lockFile, InstallationMan { $this->lockFile = $lockFile; $this->installationManager = $installationManager; - $this->hash = md5($composerFileContents); + $this->hash = hash('md5', $composerFileContents); $this->contentHash = self::getContentHash($composerFileContents); $this->loader = new ArrayLoader(null, true); $this->dumper = new ArrayDumper(); @@ -107,7 +107,7 @@ public static function getContentHash(string $composerFileContents): string ksort($relevantContent); - return md5(JsonFile::encode($relevantContent, 0)); + return hash('md5', JsonFile::encode($relevantContent, 0)); } /** diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index f73f4e9e1c5a..78176fad7588 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -129,7 +129,7 @@ private function getComposerInformation(\SplFileInfo $file): ?BasePackage $package['dist'] = [ 'type' => $fileType, 'url' => strtr($file->getPathname(), '\\', '/'), - 'shasum' => sha1_file($file->getRealPath()), + 'shasum' => hash_file('sha1', $file->getRealPath()), ]; try { diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 06676e73e60d..7239b0d2d851 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -181,7 +181,7 @@ protected function initialize(): void if ('none' === $reference) { $package['dist']['reference'] = null; } elseif ('config' === $reference || 'auto' === $reference) { - $package['dist']['reference'] = sha1($json . serialize($this->options)); + $package['dist']['reference'] = hash('sha1', $json . serialize($this->options)); } // copy symlink/relative options to transport options diff --git a/src/Composer/SelfUpdate/Versions.php b/src/Composer/SelfUpdate/Versions.php index 045fb22a7754..8cc7d455c76f 100644 --- a/src/Composer/SelfUpdate/Versions.php +++ b/src/Composer/SelfUpdate/Versions.php @@ -89,7 +89,7 @@ public function getLatest(?string $channel = null): array $versions = $this->getVersionsData(); foreach ($versions[$channel ?: $this->getChannel()] as $version) { - if ($version['min-php'] <= PHP_VERSION_ID) { + if ($version['min-php'] <= \PHP_VERSION_ID) { return $version; } } diff --git a/src/Composer/Util/ComposerMirror.php b/src/Composer/Util/ComposerMirror.php index 106e76c41e99..6be5396932a8 100644 --- a/src/Composer/Util/ComposerMirror.php +++ b/src/Composer/Util/ComposerMirror.php @@ -28,9 +28,9 @@ class ComposerMirror public static function processUrl(string $mirrorUrl, string $packageName, string $version, ?string $reference, ?string $type, ?string $prettyVersion = null): string { if ($reference) { - $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : md5($reference); + $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : hash('md5', $reference); } - $version = strpos($version, '/') === false ? $version : md5($version); + $version = strpos($version, '/') === false ? $version : hash('md5', $version); $from = ['%package%', '%version%', '%reference%', '%type%']; $to = [$packageName, $version, $reference, $type]; diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 8e755551accd..0d14d1e7f211 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -99,7 +99,7 @@ public function __construct(IOInterface $io, Config $config, array $options = [] $this->multiHandle = $mh = curl_multi_init(); if (function_exists('curl_multi_setopt')) { - curl_multi_setopt($mh, CURLMOPT_PIPELINING, PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); + curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) { curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8); } @@ -363,7 +363,7 @@ public function tick(): void continue; } - if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) { + if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && \PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) { $timeoutWarning = true; $this->io->writeError('A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.'); } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 7eeebc423b90..cafdee21384a 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -533,7 +533,7 @@ protected function getRemoteContents(string $originUrl, string $fileUrl, $contex } // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php - if (PHP_VERSION_ID >= 80400) { + if (\PHP_VERSION_ID >= 80400) { $responseHeaders = http_get_last_response_headers(); http_clear_last_response_headers(); } else { diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index 5ab2bf9c96c0..da0801a1a74b 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -150,7 +150,7 @@ public static function getCertificateFingerprint(string $certificate): string $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); $der = base64_decode($pemtrim); - return sha1($der); + return hash('sha1', $der); } /** diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index fe13c2c6c083..3742289b2922 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -26,7 +26,7 @@ public function testGetHash(): void { $rule = new GenericRule([123], Rule::RULE_ROOT_REQUIRE, ['packageName' => '', 'constraint' => new MatchAllConstraint]); - $hash = unpack('ihash', md5('123', true)); + $hash = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', '123', true)); self::assertEquals($hash['hash'], $rule->getHash()); } diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 967c726bb4a5..cbb44b08535c 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -158,7 +158,7 @@ public function testDownloadWithCustomProcessedUrl(): void $composer->setConfig($config); $expectedUrl = 'foobar'; - $expectedCacheKey = 'dummy/pkg/'.sha1($expectedUrl).'.'; + $expectedCacheKey = 'dummy/pkg/'.hash('sha1', $expectedUrl).'.'; $dispatcher = new EventDispatcher( $composer, @@ -242,7 +242,7 @@ public function testDownloadWithCustomCacheKey(): void $expectedUrl = 'url'; $customCacheKey = 'xyzzy'; - $expectedCacheKey = 'dummy/pkg/'.sha1($customCacheKey).'.'; + $expectedCacheKey = 'dummy/pkg/'.hash('sha1', $customCacheKey).'.'; $dispatcher = new EventDispatcher( $composer, diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 0abc0df59a42..50db7c73d792 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -66,7 +66,7 @@ protected function setupConfig($config = null): Config $config = new Config(); } if (!$config->has('home')) { - $tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest-'.md5(uniqid('', true)); + $tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest-'.bin2hex(random_bytes(5)); $config->merge(['config' => ['home' => $tmpDir]]); } diff --git a/tests/Composer/Test/Installer/BinaryInstallerTest.php b/tests/Composer/Test/Installer/BinaryInstallerTest.php index 00f6ce6184d9..e613ca7e1f63 100644 --- a/tests/Composer/Test/Installer/BinaryInstallerTest.php +++ b/tests/Composer/Test/Installer/BinaryInstallerTest.php @@ -121,7 +121,7 @@ public static function executableBinaryProvider(): array protected function createPackageMock() { return $this->getMockBuilder('Composer\Package\Package') - ->setConstructorArgs([md5((string) mt_rand()), '1.0.0.0', '1.0.0']) + ->setConstructorArgs([bin2hex(random_bytes(5)), '1.0.0.0', '1.0.0']) ->getMock(); } } diff --git a/tests/Composer/Test/Installer/MetapackageInstallerTest.php b/tests/Composer/Test/Installer/MetapackageInstallerTest.php index 83bc2704feab..6f083dd4b1c7 100644 --- a/tests/Composer/Test/Installer/MetapackageInstallerTest.php +++ b/tests/Composer/Test/Installer/MetapackageInstallerTest.php @@ -113,7 +113,7 @@ public function testUninstall(): void private function createPackageMock() { return $this->getMockBuilder('Composer\Package\Package') - ->setConstructorArgs([md5((string) mt_rand()), '1.0.0.0', '1.0.0']) + ->setConstructorArgs([bin2hex(random_bytes(5)), '1.0.0.0', '1.0.0']) ->getMock(); } } diff --git a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php index 3f412c3d4f4d..d2d5df994718 100644 --- a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php +++ b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php @@ -272,7 +272,7 @@ private function getSuggestedPackageArray(): array private function createPackageMock() { return $this->getMockBuilder('Composer\Package\Package') - ->setConstructorArgs([md5((string) mt_rand()), '1.0.0.0', '1.0.0']) + ->setConstructorArgs([bin2hex(random_bytes(5)), '1.0.0.0', '1.0.0']) ->getMock(); } } diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index cecbdd942439..c81257c223f5 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -533,7 +533,7 @@ public static function loadIntegrationTests(string $path): array if (!empty($testData['LOCK'])) { $lock = JsonFile::parseJson($testData['LOCK']); if (!isset($lock['hash'])) { - $lock['hash'] = md5(JsonFile::encode($composer, 0)); + $lock['hash'] = hash('md5', JsonFile::encode($composer, 0)); } } if (!empty($testData['INSTALLED'])) { diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index fc0178cf227c..fd52642ffe01 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -96,7 +96,7 @@ public function testSetLockData(): void $package1 = self::getPackage('pkg1', '1.0.0-beta'); $package2 = self::getPackage('pkg2', '0.1.10'); - $contentHash = md5(trim($jsonContent)); + $contentHash = hash('md5', trim($jsonContent)); $json ->expects($this->once()) @@ -154,7 +154,7 @@ public function testIsFresh(): void $json ->expects($this->once()) ->method('read') - ->will($this->returnValue(['hash' => md5($jsonContent)])); + ->will($this->returnValue(['hash' => hash('md5', $jsonContent)])); self::assertTrue($locker->isFresh()); } @@ -185,7 +185,7 @@ public function testIsFreshWithContentHash(): void $json ->expects($this->once()) ->method('read') - ->will($this->returnValue(['hash' => md5($jsonContent . ' '), 'content-hash' => md5($jsonContent)])); + ->will($this->returnValue(['hash' => hash('md5', $jsonContent . ' '), 'content-hash' => hash('md5', $jsonContent)])); self::assertTrue($locker->isFresh()); } @@ -201,7 +201,7 @@ public function testIsFreshWithContentHashAndNoHash(): void $json ->expects($this->once()) ->method('read') - ->will($this->returnValue(['content-hash' => md5($jsonContent)])); + ->will($this->returnValue(['content-hash' => hash('md5', $jsonContent)])); self::assertTrue($locker->isFresh()); } @@ -213,7 +213,7 @@ public function testIsFreshFalseWithContentHash(): void $locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent()); - $differentHash = md5($this->getJsonContent(['name' => 'test2'])); + $differentHash = hash('md5', $this->getJsonContent(['name' => 'test2'])); $json ->expects($this->once()) diff --git a/tests/Composer/Test/Repository/PathRepositoryTest.php b/tests/Composer/Test/Repository/PathRepositoryTest.php index f30aee38821b..d5deb664b8ca 100644 --- a/tests/Composer/Test/Repository/PathRepositoryTest.php +++ b/tests/Composer/Test/Repository/PathRepositoryTest.php @@ -156,7 +156,7 @@ public function testReferenceConfig(): void foreach ($packages as $package) { self::assertEquals( $package->getDistReference(), - sha1(file_get_contents($package->getDistUrl() . '/composer.json') . serialize($options)) + hash('sha1', file_get_contents($package->getDistUrl() . '/composer.json') . serialize($options)) ); } } diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index 233e1c3d125e..0fc9a1acc9a8 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -102,7 +102,7 @@ public static function getUniqueTmpDirectory(): string $root = sys_get_temp_dir(); do { - $unique = $root . DIRECTORY_SEPARATOR . uniqid('composer-test-' . random_int(1000, 9000)); + $unique = $root . DIRECTORY_SEPARATOR . 'composer-test-' . bin2hex(random_bytes(10)); if (!file_exists($unique) && Silencer::call('mkdir', $unique, 0777)) { return realpath($unique); diff --git a/tests/Composer/Test/Util/ErrorHandlerTest.php b/tests/Composer/Test/Util/ErrorHandlerTest.php index 29f10463f546..ae89d83b2867 100644 --- a/tests/Composer/Test/Util/ErrorHandlerTest.php +++ b/tests/Composer/Test/Util/ErrorHandlerTest.php @@ -36,7 +36,7 @@ protected function tearDown(): void */ public function testErrorHandlerCaptureNotice(): void { - if (PHP_VERSION_ID >= 80000) { + if (\PHP_VERSION_ID >= 80000) { self::expectException('\ErrorException'); self::expectExceptionMessage('Undefined array key "baz"'); } else { @@ -54,7 +54,7 @@ public function testErrorHandlerCaptureNotice(): void */ public function testErrorHandlerCaptureWarning(): void { - if (PHP_VERSION_ID >= 80000) { + if (\PHP_VERSION_ID >= 80000) { self::expectException('TypeError'); self::expectExceptionMessage('array_merge'); } else { From 04db9478bf6dbe84d9bcc42cf637d63181d1eba7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 17:10:16 +0200 Subject: [PATCH 137/257] Bump minimum dep versions --- composer.json | 10 +++++----- composer.lock | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 9cb86861c0d0..0548d49c2601 100644 --- a/composer.json +++ b/composer.json @@ -33,10 +33,10 @@ "psr/log": "^1.0 || ^2.0 || ^3.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", - "symfony/console": "^5.4.11 || ^6.0.11 || ^7", - "symfony/filesystem": "^5.4 || ^6.0 || ^7", - "symfony/finder": "^5.4 || ^6.0 || ^7", - "symfony/process": "^5.4 || ^6.0 || ^7", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3", "react/promise": "^2.8 || ^3", "composer/pcre": "^2.2 || ^3.2", "symfony/polyfill-php73": "^1.24", @@ -45,7 +45,7 @@ "seld/signal-handler": "^2.0" }, "require-dev": { - "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1", + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1", "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-deprecation-rules": "^1.2.0", diff --git a/composer.lock b/composer.lock index 7bccf2d7bd94..ab4c4ac9397a 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "715b9529f60660d59b08b12c74c828c6", + "content-hash": "9fd78ab9efb1458712ff3f804c1bc496", "packages": [ { "name": "composer/ca-bundle", From d50cc69ffb37eedade2ed3fa81103b8f8c1d6c55 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 17:16:43 +0200 Subject: [PATCH 138/257] Bump more deps --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0548d49c2601..7ca64f0ba2c7 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ ], "require": { "php": "^7.2.5 || ^8.0", - "composer/ca-bundle": "^1.0", + "composer/ca-bundle": "^1.5", "composer/class-map-generator": "^1.3.3", "composer/metadata-minifier": "^1.0", "composer/semver": "^3.3", @@ -37,7 +37,7 @@ "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3", - "react/promise": "^2.8 || ^3", + "react/promise": "^3.2", "composer/pcre": "^2.2 || ^3.2", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", From 8715c7b8d64e1f4206af9acf84bf24f922980b1c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 17:17:44 +0200 Subject: [PATCH 139/257] Update lock file --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index ab4c4ac9397a..0a20d007e218 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "9fd78ab9efb1458712ff3f804c1bc496", + "content-hash": "1205554eabbe029b41297f4f003f8cc0", "packages": [ { "name": "composer/ca-bundle", From 47b924d27c46a52141ff9a457446e3d64ce4887a Mon Sep 17 00:00:00 2001 From: Mohamed Hubail Date: Wed, 21 Aug 2024 19:14:40 +0300 Subject: [PATCH 140/257] Add Update Interactive tests (#12065) * Add Update Interactive tests * Fix type + remove extra comma * Used `php-cs-fixer` * Normalize for windows + fix use of `Generator` --- .../Test/Command/UpdateCommandTest.php | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index 5b58e52d91c3..7b5c41280147 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -12,6 +12,8 @@ namespace Composer\Test\Command; +use Composer\Package\Link; +use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Test\TestCase; use InvalidArgumentException; @@ -135,4 +137,97 @@ public function testInteractiveModeThrowsIfNoPackageEntered(): void $appTester->setInputs(['']); $appTester->run(['command' => 'update', '--interactive' => true]); } + + /** + * @dataProvider provideInteractiveUpdates + * @param array $packageNames + */ + public function testInteractiveTmp(array $packageNames, string $expected): void + { + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'root/req', 'version' => '1.0.0', 'require' => ['dep/pkg' => '^1']], + ['name' => 'dep/pkg', 'version' => '1.0.0'], + ['name' => 'dep/pkg', 'version' => '1.0.1'], + ['name' => 'dep/pkg', 'version' => '1.0.2'], + ['name' => 'another-dep/pkg', 'version' => '1.0.2'], + ], + ], + ], + 'require' => [ + 'root/req' => '1.*', + ], + ]); + + $rootPackage = self::getPackage('root/req'); + $packages = [$rootPackage]; + + foreach ($packageNames as $pkg => $ver) { + $currentPkg = self::getPackage($pkg, $ver); + array_push($packages, $currentPkg); + } + + $rootPackage->setRequires([ + 'dep/pkg' => new Link( + 'root/req', + 'dep/pkg', + new MatchAllConstraint(), + Link::TYPE_REQUIRE, + '^1' + ), + 'another-dep/pkg' => new Link( + 'root/req', + 'another-dep/pkg', + new MatchAllConstraint(), + Link::TYPE_REQUIRE, + '^1' + ), + ]); + + $this->createComposerLock($packages); + $this->createInstalledJson($packages); + + $appTester = $this->getApplicationTester(); + $appTester->setInputs(array_merge(array_keys($packageNames), ['', 'yes'])); + $appTester->run([ + 'command' => 'update', '--interactive' => true, + '--no-audit' => true, + '--dry-run' => true, + ]); + + self::assertStringEndsWith( + trim($expected), + trim($appTester->getDisplay(true)) + ); + } + + public function provideInteractiveUpdates(): \Generator + { + yield [ + ['dep/pkg' => '1.0.1'], + << 1.0.2) +Installing dependencies from lock file (including require-dev) +Package operations: 1 install, 1 update, 0 removals + - Upgrading dep/pkg (1.0.1 => 1.0.2) + - Installing another-dep/pkg (1.0.2) +OUTPUT + ]; + + yield [ + ['dep/pkg' => '1.0.1', 'another-dep/pkg' => '1.0.2'], + << 1.0.2) +Installing dependencies from lock file (including require-dev) +Package operations: 0 installs, 1 update, 0 removals + - Upgrading dep/pkg (1.0.1 => 1.0.2) +OUTPUT + ]; + } } From e173d204504ab4d372e3c7d3a19e4ac572679783 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 21 Aug 2024 18:38:51 +0200 Subject: [PATCH 141/257] Ensure COMPOSER_AUTH takes precedence over local auth.json, fixes #12084 --- src/Composer/Factory.php | 42 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 0d455b7e3296..8b7c4dbdb3ec 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -216,22 +216,7 @@ public static function createConfig(?IOInterface $io = null, ?string $cwd = null } $config->setAuthConfigSource(new JsonConfigSource($file, true)); - // load COMPOSER_AUTH environment variable if set - if ($composerAuthEnv = Platform::getEnv('COMPOSER_AUTH')) { - $authData = json_decode($composerAuthEnv); - if (null === $authData) { - throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); - } else { - if ($io instanceof IOInterface) { - $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG); - } - self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH'); - $authData = json_decode($composerAuthEnv, true); - if (null !== $authData) { - $config->merge(['config' => $authData], 'COMPOSER_AUTH'); - } - } - } + self::loadComposerAuthEnv($config, $io); return $config; } @@ -341,6 +326,9 @@ public function createComposer(IOInterface $io, $localConfig = null, $disablePlu } } + // make sure we load the auth env again over the local auth.json + composer.json config + self::loadComposerAuthEnv($config, $io); + $vendorDir = $config->get('vendor-dir'); // initialize composer @@ -678,6 +666,28 @@ public static function createHttpDownloader(IOInterface $io, Config $config, arr return $httpDownloader; } + private static function loadComposerAuthEnv(Config $config, ?IOInterface $io): void + { + $composerAuthEnv = Platform::getEnv('COMPOSER_AUTH'); + if (false === $composerAuthEnv || '' === $composerAuthEnv) { + return; + } + + $authData = json_decode($composerAuthEnv); + if (null === $authData) { + throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); + } + + if ($io instanceof IOInterface) { + $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH'); + $authData = json_decode($composerAuthEnv, true); + if (null !== $authData) { + $config->merge(['config' => $authData], 'COMPOSER_AUTH'); + } + } + private static function useXdg(): bool { foreach (array_keys($_SERVER) as $key) { From cbfa2985017e0e3e96854f57b8fbe4e6f27d2cdb Mon Sep 17 00:00:00 2001 From: viktor-kup <83069290+viktor-kup@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:49:52 +0200 Subject: [PATCH 142/257] Add missing semicolon to code example (#12067) --- doc/articles/scripts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 1f31eaeb42e4..26b8113ab848 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -273,7 +273,7 @@ class MyCommand extends Command public function execute(InputInterface $input, OutputInterface $output): int { if ($input->getOption('arbitrary-flag')) { - $output->writeln('The flag was used') + $output->writeln('The flag was used'); } return 0; From dca85cc94034997f83b3b2fe637fd3207cbf23fd Mon Sep 17 00:00:00 2001 From: Mohamed Hubail Date: Thu, 22 Aug 2024 11:27:58 +0300 Subject: [PATCH 143/257] Add "require command with conflicting keys" test (#12072) * Add test case for conflicting both with `--dev` and without * Implement interactive case * Restrcture so that interactive case is actually interactive I don't know why specifying `'--no-interaction' => !$isInteractive,` didn't give the desired behavior of the prompt being interactive. You can verify that by printing the `$appTester->getDisplay()` which doesn't contain a prompt. In fact, it doesn't make any difference whether I set it to true or false. The only difference is if I set it or don't set it. * Fix dreaded trainling comma --- .../Test/Command/RequireCommandTest.php | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/Composer/Test/Command/RequireCommandTest.php b/tests/Composer/Test/Command/RequireCommandTest.php index 0ab7508d069a..d7cc957757b4 100644 --- a/tests/Composer/Test/Command/RequireCommandTest.php +++ b/tests/Composer/Test/Command/RequireCommandTest.php @@ -12,6 +12,7 @@ namespace Composer\Test\Command; +use Composer\Json\JsonFile; use Composer\Test\TestCase; use InvalidArgumentException; @@ -263,4 +264,91 @@ public static function provideRequire(): \Generator OUTPUT ]; } + + /** + * @dataProvider provideInconsistentRequireKeys + * @param bool $isDev + * @param bool $isInteractive + * @param string $expectedWarning + */ + public function testInconsistentRequireKeys(bool $isDev, bool $isInteractive, string $expectedWarning): void + { + $currentKey = $isDev ? "require" : "require-dev"; + $otherKey = $isDev ? "require-dev" : "require"; + + $dir = $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'required/pkg', 'version' => '1.0.0'], + ], + ], + ], + $currentKey => [ + "required/pkg" => "^1.0", + ], + ]); + + $package = self::getPackage('required/pkg'); + if ($isDev) { + $this->createComposerLock([], [$package]); + $this->createInstalledJson([], [$package]); + } else { + $this->createComposerLock([$package], []); + $this->createInstalledJson([$package], []); + } + + $appTester = $this->getApplicationTester(); + $command = [ + 'command' => 'require', + '--no-audit' => true, + '--dev' => $isDev, + '--no-install' => true, + 'packages' => ['required/pkg'] + ]; + + if ($isInteractive) + $appTester->setInputs(['yes']); + else + $command['--no-interaction'] = true; + + $appTester->run($command); + + self::assertStringContainsString( + $expectedWarning, + $appTester->getDisplay(true) + ); + + $composer_content = (new JsonFile($dir . '/composer.json'))->read(); + self::assertArrayHasKey($otherKey, $composer_content); + self::assertArrayNotHasKey($currentKey, $composer_content); + } + + public function provideInconsistentRequireKeys(): \Generator + { + yield [ + true, + false, + 'required/pkg is currently present in the require key and you ran the command with the --dev flag, which will move it to the require-dev key.' + ]; + + yield [ + false, + false, + 'required/pkg is currently present in the require-dev key and you ran the command without the --dev flag, which will move it to the require key.' + ]; + + yield [ + true, + true, + 'required/pkg is currently present in the require key and you ran the command with the --dev flag, which will move it to the require-dev key.' + ]; + + yield [ + false, + true, + 'required/pkg is currently present in the require-dev key and you ran the command without the --dev flag, which will move it to the require key.' + ]; + } } From 8c5f2dbb97c84a404e228159d3faef6b8293e6ee Mon Sep 17 00:00:00 2001 From: Mohamed Hubail Date: Thu, 22 Aug 2024 11:38:16 +0300 Subject: [PATCH 144/257] Add `GlobalCommandTest.php` (#12073) * Add `GlobalCommandTest.php` - `testGlobal` to check `COMPOSER_HOME` is followed correctly + check `COMPOSER` is unset. - `testNotCreateHome` to test handling invalid `COMPOSER_HOME`. * Add error string for non obvious test case * Clean up env vars and minor code style changes --------- Co-authored-by: Jordi Boggiano --- .../Test/Command/GlobalCommandTest.php | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/Composer/Test/Command/GlobalCommandTest.php diff --git a/tests/Composer/Test/Command/GlobalCommandTest.php b/tests/Composer/Test/Command/GlobalCommandTest.php new file mode 100644 index 000000000000..1c3b32a36146 --- /dev/null +++ b/tests/Composer/Test/Command/GlobalCommandTest.php @@ -0,0 +1,80 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Command; + +use Composer\Test\TestCase; +use Composer\Util\Platform; + +class GlobalCommandTest extends TestCase +{ + public function tearDown(): void + { + parent::tearDown(); + Platform::clearEnv('COMPOSER_HOME'); + Platform::clearEnv('COMPOSER'); + } + + public function testGlobal(): void + { + $script = '@php -r \'echo getenv("COMPOSER") . PHP_EOL;\''; + $fakeComposer = 'TMP_COMPOSER.JSON'; + $composerHome = $this->initTempComposer( + [ + "scripts" => [ + "test-script" => $script, + ], + ] + ); + + Platform::putEnv('COMPOSER_HOME', $composerHome); + Platform::putEnv('COMPOSER', $fakeComposer); + + $dir = self::getUniqueTmpDirectory(); + chdir($dir); + + $appTester = $this->getApplicationTester(); + $appTester->run([ + 'command' => 'global', + 'command-name' => 'test-script', + '--no-interaction' => true, + ]); + + $display = $appTester->getDisplay(true); + + self::assertStringContainsString( + 'Changed current directory to ' . $composerHome, + $display + ); + self::assertStringContainsString($script, $display); + self::assertStringNotContainsString($fakeComposer, $display, '$COMPOSER is not unset by global command'); + } + + public function testCannotCreateHome(): void + { + $dir = self::getUniqueTmpDirectory(); + $filename = $dir . '/file'; + file_put_contents($filename, ''); + + Platform::putEnv('COMPOSER_HOME', $filename); + + self::expectException(\RuntimeException::class); + $this->expectExceptionMessage($filename . ' exists and is not a directory.'); + + $appTester = $this->getApplicationTester(); + $appTester->run([ + 'command' => 'global', + 'command-name' => 'test-script', + '--no-interaction' => true, + ]); + } +} From f931887304226e469db441bc506585678e3102f4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 10:48:56 +0200 Subject: [PATCH 145/257] Detect incorrectly configured COMPOSER env when set to a directory, refs #12049 --- src/Composer/Factory.php | 14 +++++++++++++- tests/Composer/Test/FactoryTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 8b7c4dbdb3ec..3d6d58318de0 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -223,7 +223,19 @@ public static function createConfig(?IOInterface $io = null, ?string $cwd = null public static function getComposerFile(): string { - return trim((string) Platform::getEnv('COMPOSER')) ?: './composer.json'; + $env = Platform::getEnv('COMPOSER'); + if (is_string($env)) { + $env = trim($env); + if ('' !== $env) { + if (is_dir($env)) { + throw new \RuntimeException('The COMPOSER environment variable is set to '.$env.' which is a directory, this variable should point to a composer.json or be left unset.'); + } + + return $env; + } + } + + return './composer.json'; } public static function getLockFile(string $composerFile): string diff --git a/tests/Composer/Test/FactoryTest.php b/tests/Composer/Test/FactoryTest.php index 636e58c317d4..a4b5e6df107d 100644 --- a/tests/Composer/Test/FactoryTest.php +++ b/tests/Composer/Test/FactoryTest.php @@ -13,9 +13,16 @@ namespace Composer\Test; use Composer\Factory; +use Composer\Util\Platform; class FactoryTest extends TestCase { + public function tearDown(): void + { + parent::tearDown(); + Platform::clearEnv('COMPOSER'); + } + /** * @group TLS */ @@ -37,4 +44,23 @@ public function testDefaultValuesAreAsExpected(): void Factory::createHttpDownloader($ioMock, $config); } + + public function testGetComposerJsonPath(): void + { + self::assertSame('./composer.json', Factory::getComposerFile()); + } + + public function testGetComposerJsonPathFailsIfDir(): void + { + Platform::putEnv('COMPOSER', __DIR__); + self::expectException('RuntimeException'); + self::expectExceptionMessage('The COMPOSER environment variable is set to '.__DIR__.' which is a directory, this variable should point to a composer.json or be left unset.'); + Factory::getComposerFile(); + } + + public function testGetComposerJsonPathFromEnv(): void + { + Platform::putEnv('COMPOSER', ' foo.json '); + self::assertSame('foo.json', Factory::getComposerFile()); + } } From 1684f82a43e1bec230938ae0c7ce5770d1c79e4f Mon Sep 17 00:00:00 2001 From: Mohamed Hubail Date: Thu, 22 Aug 2024 11:49:55 +0300 Subject: [PATCH 146/257] Add `InitCommand.php` Interactive test case (#12068) * Add single test case for interactive `init` command * Fix spelling + use single quotes * Fix test expectations --------- Co-authored-by: Jordi Boggiano --- .../Composer/Test/Command/InitCommandTest.php | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 18847c1e4ec1..41860f628051 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -696,4 +696,43 @@ public function testRunLicense(): void $file = new JsonFile($dir . '/composer.json'); self::assertEquals($expected, $file->read()); } + + public function testInteractiveRun(): void + { + $dir = $this->initTempComposer(); + unlink($dir . '/composer.json'); + unlink($dir . '/auth.json'); + + $appTester = $this->getApplicationTester(); + + $appTester->setInputs([ + 'vendor/pkg', // Pkg name + 'my desciption', // Description + 'Mr. Test ', // Author + 'stable', // Minimum stability + 'library', // Type + 'Custom License', // License + 'no', // Define dependencies + 'no', // Define dev dependencies + 'n', // Add PSR-4 autoload mapping + '', // Confirm generation + ]); + + $appTester->run(['command' => 'init']); + + self::assertSame(0, $appTester->getStatusCode()); + + $expected = [ + 'name' => 'vendor/pkg', + 'description' => 'my desciption', + 'type' => 'library', + 'license' => 'Custom License', + 'authors' => [['name' => 'Mr. Test', 'email' => 'test@example.org']], + 'minimum-stability' => 'stable', + 'require' => [], + ]; + + $file = new JsonFile($dir . '/composer.json'); + self::assertEquals($expected, $file->read()); + } } From 39d9a5b6c560c01f09947e4d318ccbb6893b87ae Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 11:45:25 +0200 Subject: [PATCH 147/257] Fix relative:true not being respected in path repo installs, fixes #12074 (#12092) --- src/Composer/Downloader/PathDownloader.php | 42 ++++++++++++++++----- src/Composer/Util/Filesystem.php | 10 +++-- tests/Composer/Test/Util/FilesystemTest.php | 10 +++-- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 791521bd62bc..8997f561e134 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -43,6 +43,9 @@ public function download(PackageInterface $package, string $path, ?PackageInterf { $path = Filesystem::trimTrailingSlash($path); $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot download.'); + } $realUrl = realpath($url); if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { throw new \RuntimeException(sprintf( @@ -79,7 +82,13 @@ public function install(PackageInterface $package, string $path, bool $output = { $path = Filesystem::trimTrailingSlash($path); $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); + } $realUrl = realpath($url); + if (false === $realUrl) { + throw new \RuntimeException('Failed to realpath '.$url); + } if (realpath($path) === $realUrl) { if ($output) { @@ -111,16 +120,16 @@ public function install(PackageInterface $package, string $path, bool $output = } $this->filesystem->junction($realUrl, $path); } else { - $absolutePath = $path; - if (!$this->filesystem->isAbsolutePath($absolutePath)) { - $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path; - } - $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl); $path = rtrim($path, "/"); if ($output) { $this->io->writeError(sprintf('Symlinking from %s', $url), false); } - if ($transportOptions['relative']) { + if ($transportOptions['relative'] === true) { + $absolutePath = $path; + if (!$this->filesystem->isAbsolutePath($absolutePath)) { + $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path; + } + $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true); $symfonyFilesystem->symlink($shortestPath.'/', $path); } else { $symfonyFilesystem->symlink($realUrl.'/', $path); @@ -185,13 +194,18 @@ public function remove(PackageInterface $package, string $path, bool $output = t return \React\Promise\resolve(null); } + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.'); + } + // ensure that the source path (dist url) is not the same as the install path, which // can happen when using custom installers, see https://github.com/composer/composer/pull/9116 // not using realpath here as we do not want to resolve the symlink to the original dist url // it points to $fs = new Filesystem; $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path; - $absDistUrl = $fs->isAbsolutePath($package->getDistUrl()) ? $package->getDistUrl() : Platform::getCwd() . '/' . $package->getDistUrl(); + $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url; if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); @@ -214,7 +228,8 @@ public function getVcsReference(PackageInterface $package, string $path): ?strin $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); - if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { + $packageVersion = $guesser->guessVersion($packageConfig, $path); + if ($packageVersion !== null) { return $packageVersion['commit']; } @@ -226,7 +241,14 @@ public function getVcsReference(PackageInterface $package, string $path): ?strin */ protected function getInstallOperationAppendix(PackageInterface $package, string $path): string { - $realUrl = realpath($package->getDistUrl()); + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); + } + $realUrl = realpath($url); + if (false === $realUrl) { + throw new \RuntimeException('Failed to realpath '.$url); + } if (realpath($path) === $realUrl) { return ': Source already present'; @@ -257,7 +279,7 @@ private function computeAllowedStrategies(array $transportOptions): array $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR]; $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS'); - if ($mirrorPathRepos) { + if ((bool) $mirrorPathRepos) { $currentStrategy = self::STRATEGY_MIRROR; } diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 0bb0ef86998c..8aa743c71d92 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -436,10 +436,11 @@ public function rename(string $source, string $target) * Returns the shortest path from $from to $to * * @param bool $directories if true, the source/target are considered to be directories + * @param bool $preferRelative if true, relative paths will be preferred even if longer * @throws \InvalidArgumentException * @return string */ - public function findShortestPath(string $from, string $to, bool $directories = false) + public function findShortestPath(string $from, string $to, bool $directories = false, bool $preferRelative = false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); @@ -471,7 +472,7 @@ public function findShortestPath(string $from, string $to, bool $directories = f $commonPathCode = str_repeat('../', $sourcePathDepth); // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups - if ('/' === $commonPath && $sourcePathDepth > 1) { + if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { return $to; } @@ -487,10 +488,11 @@ public function findShortestPath(string $from, string $to, bool $directories = f * Returns PHP code that, when executed in $from, will return the path to $to * * @param bool $directories if true, the source/target are considered to be directories + * @param bool $preferRelative if true, relative paths will be preferred even if longer * @throws \InvalidArgumentException * @return string */ - public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false) + public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false, bool $preferRelative = false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); @@ -520,7 +522,7 @@ public function findShortestPathCode(string $from, string $to, bool $directories $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories; // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups - if ('/' === $commonPath && $sourcePathDepth > 1) { + if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { return var_export($to, true); } diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index cfc0bacaa5fb..e947e4fe9e03 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -54,10 +54,10 @@ protected function tearDown(): void /** * @dataProvider providePathCouplesAsCode */ - public function testFindShortestPathCode(string $a, string $b, bool $directory, string $expected, bool $static = false): void + public function testFindShortestPathCode(string $a, string $b, bool $directory, string $expected, bool $static = false, bool $preferRelative = false): void { $fs = new Filesystem; - self::assertEquals($expected, $fs->findShortestPathCode($a, $b, $directory, $static)); + self::assertEquals($expected, $fs->findShortestPathCode($a, $b, $directory, $static, $preferRelative)); } public static function providePathCouplesAsCode(): array @@ -77,6 +77,7 @@ public static function providePathCouplesAsCode(): array ['/foo/bar', '/foo/baz', true, "dirname(__DIR__).'/baz'"], ['/foo/bin/run', '/foo/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"], ['/foo/bin/run', '/bar/bin/run', true, "'/bar/bin/run'"], + ['/app/vendor/foo/bar', '/lib', true, "dirname(dirname(dirname(dirname(__DIR__)))).'/lib'", false, true], ['/bin/run', '/bin/run', true, "__DIR__"], ['c:/bin/run', 'C:\\bin/run', true, "__DIR__"], ['c:/bin/run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"], @@ -113,10 +114,10 @@ public static function providePathCouplesAsCode(): array /** * @dataProvider providePathCouples */ - public function testFindShortestPath(string $a, string $b, string $expected, bool $directory = false): void + public function testFindShortestPath(string $a, string $b, string $expected, bool $directory = false, bool $preferRelative = false): void { $fs = new Filesystem; - self::assertEquals($expected, $fs->findShortestPath($a, $b, $directory)); + self::assertEquals($expected, $fs->findShortestPath($a, $b, $directory, $preferRelative)); } public static function providePathCouples(): array @@ -152,6 +153,7 @@ public static function providePathCouples(): array ['C:/Temp', 'c:\Temp\..\..\test', "../test", true], ['C:/Temp/../..', 'c:\Temp\..\..\test', "./test", true], ['C:/Temp/../..', 'D:\Temp\..\..\test', "D:/test", true], + ['/app/vendor/foo/bar', '/lib', '../../../../lib', true, true], ['/tmp', '/tmp/../../test', '../test', true], ['/tmp', '/test', '../test', true], ['/foo/bar', '/foo/bar_vendor', '../bar_vendor', true], From bbb603490b12382af5dd84b773fc8d20f0513cb0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 12:11:39 +0200 Subject: [PATCH 148/257] Fix duplicate libraries causing issues when conflicting extensions from core and pecl are installed concurrently (#12093) Fixes #12082 --- .../Repository/PlatformRepository.php | 117 ++++++++++-------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index cbc2552d5492..f5a2eb5ace07 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -110,6 +110,8 @@ protected function initialize(): void { parent::initialize(); + $libraries = []; + $this->versionParser = new VersionParser(); // Add each of the override versions as options. @@ -207,12 +209,12 @@ protected function initialize(): void // librabbitmq version => 0.9.0 if (Preg::isMatch('/^librabbitmq version => (?.+)$/im', $info, $librabbitmqMatches)) { - $this->addLibrary($name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); + $this->addLibrary($libraries, $name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); } // AMQP protocol version => 0-9-1 if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?.+)$/im', $info, $protocolMatches)) { - $this->addLibrary($name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); + $this->addLibrary($libraries, $name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); } break; @@ -221,13 +223,13 @@ protected function initialize(): void // BZip2 Version => 1.0.6, 6-Sept-2010 if (Preg::isMatch('/^BZip2 Version => (?.*),/im', $info, $matches)) { - $this->addLibrary($name, $matches['version']); + $this->addLibrary($libraries, $name, $matches['version']); } break; case 'curl': $curlVersion = $this->runtime->invoke('curl_version'); - $this->addLibrary($name, $curlVersion['version']); + $this->addLibrary($libraries, $name, $curlVersion['version']); $info = $this->runtime->getExtensionInfo($name); @@ -236,25 +238,25 @@ protected function initialize(): void $library = strtolower($sslMatches['library']); if ($library === 'openssl') { $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); - $this->addLibrary($name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []); + $this->addLibrary($libraries, $name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []); } else { if ($library === '(securetransport) openssl') { $shortlib = 'securetransport'; } else { $shortlib = $library; } - $this->addLibrary($name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', ['curl-openssl']); + $this->addLibrary($libraries, $name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', ['curl-openssl']); } } // libSSH Version => libssh2/1.4.3 if (Preg::isMatchStrictGroups('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}im', $info, $sshMatches)) { - $this->addLibrary($name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); + $this->addLibrary($libraries, $name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); } // ZLib Version => 1.2.8 if (Preg::isMatchStrictGroups('{^ZLib Version => (?.+)$}im', $info, $zlibMatches)) { - $this->addLibrary($name.'-zlib', $zlibMatches['version'], 'curl zlib version'); + $this->addLibrary($libraries, $name.'-zlib', $zlibMatches['version'], 'curl zlib version'); } break; @@ -263,7 +265,7 @@ protected function initialize(): void // timelib version => 2018.03 if (Preg::isMatchStrictGroups('/^timelib version => (?.+)$/im', $info, $timelibMatches)) { - $this->addLibrary($name.'-timelib', $timelibMatches['version'], 'date timelib version'); + $this->addLibrary($libraries, $name.'-timelib', $timelibMatches['version'], 'date timelib version'); } // Timezone Database => internal @@ -272,9 +274,9 @@ protected function initialize(): void if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?.+?)(?:\.system)?$/im', $info, $zoneinfoMatches)) { // If the timezonedb is provided by ext/timezonedb, register that version as a replacement if ($external && in_array('timezonedb', $loadedExtensions, true)) { - $this->addLibrary('timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']); + $this->addLibrary($libraries, 'timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']); } else { - $this->addLibrary($name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); + $this->addLibrary($libraries, $name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); } } } @@ -285,39 +287,39 @@ protected function initialize(): void // libmagic => 537 if (Preg::isMatch('/^libmagic => (?.+)$/im', $info, $magicMatches)) { - $this->addLibrary($name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); + $this->addLibrary($libraries, $name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); } break; case 'gd': - $this->addLibrary($name, $this->runtime->getConstant('GD_VERSION')); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('GD_VERSION')); $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^libJPEG Version => (?.+?)(?: compatible)?$/im', $info, $libjpegMatches)) { - $this->addLibrary($name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); + $this->addLibrary($libraries, $name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); } if (Preg::isMatchStrictGroups('/^libPNG Version => (?.+)$/im', $info, $libpngMatches)) { - $this->addLibrary($name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); + $this->addLibrary($libraries, $name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); } if (Preg::isMatchStrictGroups('/^FreeType Version => (?.+)$/im', $info, $freetypeMatches)) { - $this->addLibrary($name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); + $this->addLibrary($libraries, $name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); } if (Preg::isMatchStrictGroups('/^libXpm Version => (?\d+)$/im', $info, $libxpmMatches)) { - $this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd'); + $this->addLibrary($libraries, $name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd'); } break; case 'gmp': - $this->addLibrary($name, $this->runtime->getConstant('GMP_VERSION')); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('GMP_VERSION')); break; case 'iconv': - $this->addLibrary($name, $this->runtime->getConstant('ICONV_VERSION')); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('ICONV_VERSION')); break; case 'intl': @@ -326,26 +328,26 @@ protected function initialize(): void $description = 'The ICU unicode and globalization support library'; // Truthy check is for testing only so we can make the condition fail if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { - $this->addLibrary('icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); + $this->addLibrary($libraries, 'icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); } elseif (Preg::isMatch('/^ICU version => (?.+)$/im', $info, $matches)) { - $this->addLibrary('icu', $matches['version'], $description); + $this->addLibrary($libraries, 'icu', $matches['version'], $description); } // ICU TZData version => 2019c if (Preg::isMatchStrictGroups('/^ICU TZData version => (?.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) { - $this->addLibrary('icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu'); + $this->addLibrary($libraries, 'icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu'); } // Add a separate version for the CLDR library version if ($this->runtime->hasClass('ResourceBundle')) { $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', false]); if ($resourceBundle !== null) { - $this->addLibrary('icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version'); + $this->addLibrary($libraries, 'icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version'); } } if ($this->runtime->hasClass('IntlChar')) { - $this->addLibrary('icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version'); + $this->addLibrary($libraries, 'icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version'); } break; @@ -360,7 +362,7 @@ protected function initialize(): void $version .= '.'.$matches['patch']; } - $this->addLibrary($name.'-imagemagick', $version, null, ['imagick']); + $this->addLibrary($libraries, $name.'-imagemagick', $version, null, ['imagick']); } break; @@ -368,7 +370,7 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^Vendor Version => (?\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?.+)$/im', $info, $vendorMatches)) { - $this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap'); + $this->addLibrary($libraries, $name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap'); } break; @@ -377,7 +379,7 @@ protected function initialize(): void $libxmlProvides = array_map(static function ($extension): string { return $extension . '-libxml'; }, array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'])); - $this->addLibrary($name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides); break; @@ -386,16 +388,16 @@ protected function initialize(): void // libmbfl version => 1.3.2 if (Preg::isMatch('/^libmbfl version => (?.+)$/im', $info, $libmbflMatches)) { - $this->addLibrary($name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); + $this->addLibrary($libraries, $name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); } if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { - $this->addLibrary($name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); + $this->addLibrary($libraries, $name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); // Multibyte regex (oniguruma) version => 5.9.5 // oniguruma version => 6.9.0 } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/im', $info, $onigurumaMatches)) { - $this->addLibrary($name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); + $this->addLibrary($libraries, $name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); } break; @@ -405,7 +407,7 @@ protected function initialize(): void // libmemcached version => 1.0.18 if (Preg::isMatch('/^libmemcached version => (?.+)$/im', $info, $matches)) { - $this->addLibrary($name.'-libmemcached', $matches['version'], 'libmemcached version'); + $this->addLibrary($libraries, $name.'-libmemcached', $matches['version'], 'libmemcached version'); } break; @@ -413,18 +415,18 @@ protected function initialize(): void // OpenSSL 1.1.1g 21 Apr 2020 if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\s*(?\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); - $this->addLibrary($name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []); + $this->addLibrary($libraries, $name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []); } break; case 'pcre': - $this->addLibrary($name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); + $this->addLibrary($libraries, $name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); $info = $this->runtime->getExtensionInfo($name); // PCRE Unicode Version => 12.1.0 if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?.+)$/im', $info, $pcreUnicodeMatches)) { - $this->addLibrary($name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); + $this->addLibrary($libraries, $name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); } break; @@ -434,7 +436,7 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { - $this->addLibrary($name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); + $this->addLibrary($libraries, $name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); } break; @@ -442,17 +444,17 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?.+)$/im', $info, $libmongocMatches)) { - $this->addLibrary($name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); + $this->addLibrary($libraries, $name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); } if (Preg::isMatchStrictGroups('/^libbson bundled version => (?.+)$/im', $info, $libbsonMatches)) { - $this->addLibrary($name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); + $this->addLibrary($libraries, $name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); } break; case 'pgsql': if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) { - $this->addLibrary('pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql'); + $this->addLibrary($libraries, 'pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql'); break; } // intentional fall-through to next case... @@ -461,7 +463,7 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?.*)$/im', $info, $matches)) { - $this->addLibrary($name.'-libpq', $matches['version'], 'libpq for '.$name); + $this->addLibrary($libraries, $name.'-libpq', $matches['version'], 'libpq for '.$name); } break; @@ -471,7 +473,7 @@ protected function initialize(): void // Used Library => Compiled => Linked // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2 if (Preg::isMatch('/^libpq => (?.+) => (?.+)$/im', $info, $matches)) { - $this->addLibrary($name.'-libpq', $matches['linked'], 'libpq for '.$name); + $this->addLibrary($libraries, $name.'-libpq', $matches['linked'], 'libpq for '.$name); } break; @@ -487,14 +489,15 @@ protected function initialize(): void * pre-release ID in practice is always 0xff even for RCs etc, so we ignore it */ $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION'); - $this->addLibrary($name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0xFF000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name); + $this->addLibrary($libraries, $name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0xFF000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name); } break; case 'libsodium': case 'sodium': if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) { - $this->addLibrary('libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); } break; @@ -503,7 +506,7 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^SQLite Library => (?.+)$/im', $info, $matches)) { - $this->addLibrary($name.'-sqlite', $matches['version']); + $this->addLibrary($libraries, $name.'-sqlite', $matches['version']); } break; @@ -511,16 +514,16 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libssh2 version => (?.+)$/im', $info, $matches)) { - $this->addLibrary($name.'-libssh2', $matches['version']); + $this->addLibrary($libraries, $name.'-libssh2', $matches['version']); } break; case 'xsl': - $this->addLibrary('libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']); + $this->addLibrary($libraries, 'libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']); $info = $this->runtime->getExtensionInfo('xsl'); if (Preg::isMatch('/^libxslt compiled against libxml Version => (?.+)$/im', $info, $matches)) { - $this->addLibrary('libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); + $this->addLibrary($libraries, 'libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); } break; @@ -528,23 +531,23 @@ protected function initialize(): void $info = $this->runtime->getExtensionInfo('yaml'); if (Preg::isMatch('/^LibYAML Version => (?.+)$/im', $info, $matches)) { - $this->addLibrary($name.'-libyaml', $matches['version'], 'libyaml version of yaml'); + $this->addLibrary($libraries, $name.'-libyaml', $matches['version'], 'libyaml version of yaml'); } break; case 'zip': if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { - $this->addLibrary($name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']); + $this->addLibrary($libraries, $name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']); } break; case 'zlib': if ($this->runtime->hasConstant('ZLIB_VERSION')) { - $this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION')); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('ZLIB_VERSION')); // Linked Version => 1.2.8 } elseif (Preg::isMatch('/^Linked Version => (?.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) { - $this->addLibrary($name, $matches['version']); + $this->addLibrary($libraries, $name, $matches['version']); } break; @@ -680,10 +683,11 @@ private function buildPackageName(string $name): string } /** - * @param string[] $replaces - * @param string[] $provides + * @param array $libraries + * @param array $replaces + * @param array $provides */ - private function addLibrary(string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void + private function addLibrary(array &$libraries, string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void { if (null === $prettyVersion) { return; @@ -694,6 +698,13 @@ private function addLibrary(string $name, ?string $prettyVersion, ?string $descr return; } + // avoid adding the same lib twice even if two conflicting extensions provide the same lib + // see https://github.com/composer/composer/issues/12082 + if (isset($libraries['lib-'.$name])) { + return; + } + $libraries['lib-'.$name] = true; + if ($description === null) { $description = 'The '.$name.' library'; } From a17096f5ba3efa6334cf9cfffae53ef1a2242969 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 12:44:48 +0200 Subject: [PATCH 149/257] Fix copy() sometimes failing on virtualbox shared folders, fixes #12057 --- src/Composer/Cache.php | 4 ++-- src/Composer/Util/Filesystem.php | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 2e6f2edadfd4..e18715f478b1 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -198,7 +198,7 @@ public function copyFrom(string $file, string $source) $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source); } - return copy($source, $this->root . $file); + return $this->filesystem->copy($source, $this->root . $file); } return false; @@ -224,7 +224,7 @@ public function copyTo(string $file, string $target) $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); - return copy($this->root . $file, $target); + return $this->filesystem->copy($this->root . $file, $target); } } diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 8aa743c71d92..099747add5b5 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -13,6 +13,7 @@ namespace Composer\Util; use Composer\Pcre\Preg; +use ErrorException; use React\Promise\PromiseInterface; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -367,7 +368,29 @@ public function copy(string $source, string $target) $target = $this->normalizePath($target); if (!is_dir($source)) { - return copy($source, $target); + try { + return copy($source, $target); + } catch (ErrorException $e) { + // if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders + // see https://github.com/composer/composer/issues/12057 + if (str_contains($e->getMessage(), 'Bad address')) { + $sourceHandle = fopen($source, 'r'); + $targetHandle = fopen($target, 'w'); + if (false === $sourceHandle || false === $targetHandle) { + throw $e; + } + while (!feof($sourceHandle)) { + if (false === fwrite($targetHandle, (string) fread($sourceHandle, 1024 * 1024))) { + throw $e; + } + } + fclose($sourceHandle); + fclose($targetHandle); + + return true; + } + throw $e; + } } $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); From b96821c7276804bd63b6dbe7ae7994ea43e8ee47 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 15:04:02 +0200 Subject: [PATCH 150/257] Fix build --- phpstan/baseline-8.3.neon | 2 +- phpstan/baseline.neon | 54 +++++---------------------------------- 2 files changed, 8 insertions(+), 48 deletions(-) diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index cf5444103339..2b6d16ef072c 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -177,7 +177,7 @@ parameters: - message: "#^Casting to string something that's already string\\.$#" - count: 4 + count: 3 path: ../src/Composer/Util/Filesystem.php - diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 9dd3190277e9..a5ca524b6bd2 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -1655,51 +1655,6 @@ parameters: count: 3 path: ../src/Composer/Downloader/HgDownloader.php - - - message: "#^Only booleans are allowed in an if condition, array\\\\|null given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Parameter \\#1 \\$path of function realpath expects string, string\\|null given\\.$#" - count: 3 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Parameter \\#1 \\$path of method Composer\\\\Util\\\\Filesystem\\:\\:isAbsolutePath\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Parameter \\#1 \\$path of method Composer\\\\Util\\\\Filesystem\\:\\:normalizePath\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Parameter \\#1 \\$path of method Composer\\\\Util\\\\Filesystem\\:\\:normalizePath\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Parameter \\#1 \\$target of method Composer\\\\Util\\\\Filesystem\\:\\:junction\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - - - message: "#^Parameter \\#2 \\$to of method Composer\\\\Util\\\\Filesystem\\:\\:findShortestPath\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Downloader/PathDownloader.php - - message: "#^Cannot call method cleanupClientSpec\\(\\) on Composer\\\\Util\\\\Perforce\\|null\\.$#" count: 1 @@ -1947,7 +1902,7 @@ parameters: - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" - count: 6 + count: 5 path: ../src/Composer/Factory.php - @@ -1967,7 +1922,7 @@ parameters: - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 3 + count: 2 path: ../src/Composer/Factory.php - @@ -3713,6 +3668,11 @@ parameters: count: 1 path: ../src/Composer/Util/ConfigValidator.php + - + message: "#^Casting to string something that's already string\\.$#" + count: 1 + path: ../src/Composer/Util/Filesystem.php + - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 From ca98528555afb69f11d925328abf0b5348beb406 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 15:28:29 +0200 Subject: [PATCH 151/257] Update changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index def2885fbff3..c1432845d68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +### [2.7.8] 2024-08-22 + + * Added `release-age`, `release-date` and `latest-release-date` in the JSON output of `outdated` (#12053) + * Fixed PHP 8.4 deprecation warnings + * Fixed addressability of branches containing `#` signs (#12042) + * Fixed `bump` command not handling some `~` constraints correctly (#12038) + * Fixed COMPOSER_AUTH not taking precedence over ./auth.json (#12084) + * Fixed `relative: true` sometimes not being respected in path repo symlinks (#12092) + * Fixed copy from cache sometimes failing on VirtualBox shared folders (#12057) + * Fixed PSR-4 autoloading order regression in some edge case (#12063) + * Fixed duplicate lib-* packages causing issues when having pecl + core versions of the same PHP extension (#12093) + * Fixed transport-options.ssl for local cert authorization being stored in lock file making them less portable (#12019) + * Fixed memory issues when installing large binaries (#12032) + * Fixed `archive` command crashing when a path cannot be realpath'd on windows (#11544) + * API: Deprecated BasePackage::$stabilities in favor of BasePackage::STABILITIES (685add70ec) + * Improved Docker detection (#12062) + ### [2.7.7] 2024-06-10 * Security: Fixed command injection via malicious git branch name (GHSA-47f6-5gq3-vx9c / CVE-2024-35241) @@ -1888,6 +1905,7 @@ * Initial release +[2.7.8]: https://github.com/composer/composer/compare/2.7.7...2.7.8 [2.7.7]: https://github.com/composer/composer/compare/2.7.6...2.7.7 [2.7.6]: https://github.com/composer/composer/compare/2.7.5...2.7.6 [2.7.5]: https://github.com/composer/composer/compare/2.7.4...2.7.5 From a2edd4e4414c17008ab585e0c62574fdb644ebfc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 15:28:36 +0200 Subject: [PATCH 152/257] Release 2.7.8 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..8a9a5042f25d 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.8'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-08-22 15:28:36'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From aca19582b56576ec64108656bd890e872ea97a4c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 22 Aug 2024 15:28:36 +0200 Subject: [PATCH 153/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 8a9a5042f25d..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.8'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-08-22 15:28:36'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From d832d8c6c5a15e1c267d7288eddbcaa634427e0a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 1 Sep 2024 20:53:37 +0200 Subject: [PATCH 154/257] Simplify lint CI --- .github/workflows/lint.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5e3fe1d29512..bff8ead33cb6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,16 +24,16 @@ jobs: - "nightly" steps: - - name: "Checkout" - uses: "actions/checkout@v4" + - uses: actions/checkout@v4 - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" + - uses: shivammathur/setup-php@v2 with: - coverage: "none" - extensions: "intl" - ini-values: "memory_limit=-1, error_reporting=E_ALL, display_errors=On" php-version: "${{ matrix.php-version }}" + coverage: none + + - uses: ramsey/composer-install@v3 + with: + dependency-versions: highest - name: "Lint PHP files" run: | From 4a34725682040d501bff40fa8e4d95967f17110f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 1 Sep 2024 22:17:42 +0200 Subject: [PATCH 155/257] Bump dependencies, fixes #12015 --- composer.lock | 70 +++++++++++------------ phpstan/baseline-8.3.neon | 35 ------------ phpstan/baseline.neon | 35 ++++++++++-- src/Composer/Util/Http/CurlDownloader.php | 4 +- 4 files changed, 67 insertions(+), 77 deletions(-) diff --git a/composer.lock b/composer.lock index 0a20d007e218..3f53818dac07 100644 --- a/composer.lock +++ b/composer.lock @@ -226,16 +226,16 @@ }, { "name": "composer/pcre", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "06d0e49d6e136e4521c6bad18598bf0f6062ae37" + "reference": "26859a860a7f140fc08422c3cc14ad9c2a287d79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/06d0e49d6e136e4521c6bad18598bf0f6062ae37", - "reference": "06d0e49d6e136e4521c6bad18598bf0f6062ae37", + "url": "https://api.github.com/repos/composer/pcre/zipball/26859a860a7f140fc08422c3cc14ad9c2a287d79", + "reference": "26859a860a7f140fc08422c3cc14ad9c2a287d79", "shasum": "" }, "require": { @@ -285,7 +285,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.3.0" + "source": "https://github.com/composer/pcre/tree/2.3.1" }, "funding": [ { @@ -301,7 +301,7 @@ "type": "tidelift" } ], - "time": "2024-08-19T19:14:31+00:00" + "time": "2024-08-27T12:02:26+00:00" }, { "name": "composer/semver", @@ -941,16 +941,16 @@ }, { "name": "symfony/console", - "version": "v5.4.42", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f" + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cef62396a0477e94fc52e87a17c6e5c32e226b7f", - "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f", + "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", "shasum": "" }, "require": { @@ -1020,7 +1020,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.42" + "source": "https://github.com/symfony/console/tree/v5.4.43" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:21:55+00:00" + "time": "2024-08-13T16:31:56+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1174,16 +1174,16 @@ }, { "name": "symfony/finder", - "version": "v5.4.42", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0724c51fa067b198e36506d2864e09a52180998a" + "reference": "ae25a9145a900764158d439653d5630191155ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0724c51fa067b198e36506d2864e09a52180998a", - "reference": "0724c51fa067b198e36506d2864e09a52180998a", + "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", + "reference": "ae25a9145a900764158d439653d5630191155ca0", "shasum": "" }, "require": { @@ -1217,7 +1217,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.42" + "source": "https://github.com/symfony/finder/tree/v5.4.43" }, "funding": [ { @@ -1233,7 +1233,7 @@ "type": "tidelift" } ], - "time": "2024-07-22T08:53:29+00:00" + "time": "2024-08-13T14:03:51+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1932,16 +1932,16 @@ }, { "name": "symfony/string", - "version": "v5.4.42", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "909cec913edea162a3b2836788228ad45fcab337" + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/909cec913edea162a3b2836788228ad45fcab337", - "reference": "909cec913edea162a3b2836788228ad45fcab337", + "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", "shasum": "" }, "require": { @@ -1998,7 +1998,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.42" + "source": "https://github.com/symfony/string/tree/v5.4.43" }, "funding": [ { @@ -2014,22 +2014,22 @@ "type": "tidelift" } ], - "time": "2024-07-20T18:38:32+00:00" + "time": "2024-08-01T10:24:28+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.11.11", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" + "reference": "384af967d35b2162f69526c7276acadce534d0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", + "reference": "384af967d35b2162f69526c7276acadce534d0e1", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-08-19T14:37:29+00:00" + "time": "2024-08-27T09:18:05+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2298,16 +2298,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "e823122d31935eb711e2767c31f3d71cb0b87fb1" + "reference": "e876eb90e32a8fc4c4911d458e09f88d65877d1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e823122d31935eb711e2767c31f3d71cb0b87fb1", - "reference": "e823122d31935eb711e2767c31f3d71cb0b87fb1", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e876eb90e32a8fc4c4911d458e09f88d65877d1c", + "reference": "e876eb90e32a8fc4c4911d458e09f88d65877d1c", "shasum": "" }, "require": { @@ -2360,7 +2360,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.3" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.4" }, "funding": [ { @@ -2376,7 +2376,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-08-13T14:28:19+00:00" } ], "aliases": [], diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index 2b6d16ef072c..68bb1e533bbb 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -185,46 +185,11 @@ parameters: count: 8 path: ../src/Composer/Util/Git.php - - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_add_handle expects CurlMultiHandle, resource\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_exec expects CurlMultiHandle, resource\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_info_read expects CurlMultiHandle, resource\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_remove_handle expects CurlMultiHandle, resource\\|null given\\.$#" - count: 2 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_select expects CurlMultiHandle, resource\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$string of function rtrim expects string, string\\|false given\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - - message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$multiHandle \\(resource\\|null\\) does not accept CurlMultiHandle\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$shareHandle \\(resource\\|null\\) does not accept CurlShareHandle\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index a5ca524b6bd2..4b21d55c68a0 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -3819,27 +3819,27 @@ parameters: path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$mh of function curl_multi_add_handle expects resource, resource\\|null given\\.$#" + message: "#^Parameter \\#1 \\$mh of function curl_multi_add_handle expects resource, CurlMultiHandle given\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$mh of function curl_multi_exec expects resource, resource\\|null given\\.$#" + message: "#^Parameter \\#1 \\$mh of function curl_multi_exec expects resource, CurlMultiHandle given\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$mh of function curl_multi_info_read expects resource, resource\\|null given\\.$#" + message: "#^Parameter \\#1 \\$mh of function curl_multi_info_read expects resource, CurlMultiHandle given\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$mh of function curl_multi_remove_handle expects resource, resource\\|null given\\.$#" + message: "#^Parameter \\#1 \\$mh of function curl_multi_remove_handle expects resource, CurlMultiHandle given\\.$#" count: 2 path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$mh of function curl_multi_select expects resource, resource\\|null given\\.$#" + message: "#^Parameter \\#1 \\$mh of function curl_multi_select expects resource, CurlMultiHandle given\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php @@ -3923,11 +3923,36 @@ parameters: count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php + - + message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$multiHandle \\(CurlMultiHandle\\) does not accept resource\\.$#" + count: 1 + path: ../src/Composer/Util/Http/CurlDownloader.php + + - + message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$multiHandle has unknown class CurlMultiHandle as its type\\.$#" + count: 1 + path: ../src/Composer/Util/Http/CurlDownloader.php + + - + message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$shareHandle \\(CurlShareHandle\\) does not accept resource\\.$#" + count: 1 + path: ../src/Composer/Util/Http/CurlDownloader.php + + - + message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$shareHandle has unknown class CurlShareHandle as its type\\.$#" + count: 1 + path: ../src/Composer/Util/Http/CurlDownloader.php + - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php + - + message: "#^Type alias Job contains unknown class CurlHandle\\.$#" + count: 1 + path: ../src/Composer/Util/Http/CurlDownloader.php + - message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#" count: 1 diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 0d14d1e7f211..577c81b47225 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -34,9 +34,9 @@ */ class CurlDownloader { - /** @var ?resource */ + /** @var \CurlMultiHandle */ private $multiHandle; - /** @var ?resource */ + /** @var \CurlShareHandle */ private $shareHandle; /** @var Job[] */ private $jobs = []; From 1fcb6a5d530b0abe2a2a74131296cb7d0412cbf5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 2 Sep 2024 10:24:23 +0200 Subject: [PATCH 156/257] Fix docker detection breaking on constrained environments, fixes #12095 --- src/Composer/Util/Platform.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index c1a38bac1182..b13fe5bb829a 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -193,11 +193,17 @@ public static function isDocker(): bool '/proc/self/mountinfo', // cgroup v2 '/proc/1/cgroup', // cgroup v1 ]; - foreach($cgroups as $cgroup) { + foreach ($cgroups as $cgroup) { if (!is_readable($cgroup)) { continue; } - $data = file_get_contents($cgroup); + // suppress errors as some environments have these files as readable but system restrictions prevent the read from succeeding + // see https://github.com/composer/composer/issues/12095 + try { + $data = @file_get_contents($cgroup); + } catch (\Throwable $e) { + break; + } if (is_string($data) && str_contains($data, '/var/lib/docker/')) { return self::$isDocker = true; } From 1980f562caa558e7422f37ca448691e30ea4f99c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 4 Sep 2024 14:14:17 +0200 Subject: [PATCH 157/257] Update deps --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 3f53818dac07..3476d503908d 100644 --- a/composer.lock +++ b/composer.lock @@ -2020,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "384af967d35b2162f69526c7276acadce534d0e1" + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", - "reference": "384af967d35b2162f69526c7276acadce534d0e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-08-27T09:18:05+00:00" + "time": "2024-09-03T19:55:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", From 260a25490585974823a82ffcc81d1d40d95b58e1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 4 Sep 2024 14:31:15 +0200 Subject: [PATCH 158/257] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1432845d68b..f46d54f053c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### [2.7.9] 2024-09-04 + + * Fixed Docker detection breaking on constrained environments (#12095) + * Fixed upstream issue in bash completion script, it is recommended to update it using the `completion` command (#12015) + ### [2.7.8] 2024-08-22 * Added `release-age`, `release-date` and `latest-release-date` in the JSON output of `outdated` (#12053) @@ -1905,6 +1910,7 @@ * Initial release +[2.7.9]: https://github.com/composer/composer/compare/2.7.8...2.7.9 [2.7.8]: https://github.com/composer/composer/compare/2.7.7...2.7.8 [2.7.7]: https://github.com/composer/composer/compare/2.7.6...2.7.7 [2.7.6]: https://github.com/composer/composer/compare/2.7.5...2.7.6 From e30ccdd665828ae66eb1be78f056e39e1d5f55ab Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 4 Sep 2024 14:43:28 +0200 Subject: [PATCH 159/257] Release 2.7.9 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..a36dbf545088 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.9'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-09-04 14:43:28'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From e4038e5e3bffe22cb815dc1663ea76be96d4222e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 4 Sep 2024 14:43:28 +0200 Subject: [PATCH 160/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index a36dbf545088..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.7.9'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-09-04 14:43:28'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From 5f2b91aea8d81810133c6d40f170fcbe38150476 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 16 Sep 2024 11:31:52 +0200 Subject: [PATCH 161/257] Revert "fix(Locker): don't store transport-options.ssl within the lock-file (#12019)" This reverts commit 03bbfdd8f4d6ce3c0678b6cba26446704b4d594b. --- src/Composer/Package/Locker.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 2c6ae087df4d..87971cc03b49 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -434,14 +434,6 @@ private function lockPackages(array $packages): array $spec = $this->dumper->dump($package); unset($spec['version_normalized']); - // remove `transport-options.ssl` from lock file to prevent storing - // local-filesystem repo config paths in the lock file as that makes it less portable - if (isset($spec['transport-options']['ssl'])) { - unset($spec['transport-options']['ssl']); - if (\count($spec['transport-options']) === 0) { - unset($spec['transport-options']); - } - } // always move time to the end of the package definition $time = $spec['time'] ?? null; From 1b5b56f234ab52a9dcfc935228d49e2a5e262e39 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 08:38:43 +0200 Subject: [PATCH 162/257] Fix handling of COMPOSER_ROOT_VERSION to normalize according to expectations, fixes #12101 (#12109) --- .../Package/Loader/RootPackageLoader.php | 2 +- .../Package/Version/VersionGuesser.php | 13 ++++++++++ src/Composer/Repository/PathRepository.php | 2 +- .../Package/Version/VersionGuesserTest.php | 26 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 2b0d016e6c23..81cb375fc78d 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -88,7 +88,7 @@ public function load(array $config, string $class = 'Composer\Package\RootPackag // override with env var if available if (Platform::getEnv('COMPOSER_ROOT_VERSION')) { - $config['version'] = Platform::getEnv('COMPOSER_ROOT_VERSION'); + $config['version'] = $this->versionGuesser->getRootVersionFromEnv(); } else { $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(true)); if ($versionData) { diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 72fc799a7049..9a51527e8b22 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -420,4 +420,17 @@ private function guessSvnVersion(array $packageConfig, string $path): ?array return null; } + + public function getRootVersionFromEnv(): string + { + $version = Platform::getEnv('COMPOSER_ROOT_VERSION'); + if (!is_string($version) || $version === '') { + throw new \RuntimeException('COMPOSER_ROOT_VERSION not set or empty'); + } + if (Preg::isMatch('{^(\d+(?:\.\d+)*)-dev$}i', $version, $match)) { + $version = $match[1].'.x-dev'; + } + + return $version; + } } diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 7239b0d2d851..1d9e4e7e0982 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -198,7 +198,7 @@ protected function initialize(): void && 0 === $this->process->execute('git rev-parse HEAD', $ref2) && $ref1 === $ref2 ) { - $package['version'] = $rootVersion; + $package['version'] = $this->versionGuesser->getRootVersionFromEnv(); } } diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php index c6e01251b369..833ea953588e 100644 --- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -17,6 +17,7 @@ use Composer\Semver\VersionParser; use Composer\Test\TestCase; use Composer\Util\Git as GitUtil; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; class VersionGuesserTest extends TestCase @@ -365,4 +366,29 @@ public function testRemoteBranchesAreSelected(): void self::assertEquals("1.5.x-dev", $versionData['pretty_version']); self::assertEquals("1.5.9999999.9999999-dev", $versionData['version']); } + + /** + * @dataProvider rootEnvVersionsProvider + */ + public function testGetRootVersionFromEnv(string $env, string $expectedVersion): void + { + Platform::putEnv('COMPOSER_ROOT_VERSION', $env); + $guesser = new VersionGuesser(new Config, $this->getProcessExecutorMock(), new VersionParser()); + self::assertSame($expectedVersion, $guesser->getRootVersionFromEnv()); + Platform::clearEnv('COMPOSER_ROOT_VERSION'); + } + + /** + * @return array + */ + public function rootEnvVersionsProvider(): array + { + return [ + ['1.0-dev', '1.0.x-dev'], + ['1.0.x-dev', '1.0.x-dev'], + ['1-dev', '1.x-dev'], + ['1.x-dev', '1.x-dev'], + ['1.0.0', '1.0.0'], + ]; + } } From 3e7b826904b28fa534f32a8a1b727a514e24bdc7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 09:47:20 +0200 Subject: [PATCH 163/257] Avoid opening php://stdin multiple times, fixes #12107 --- src/Composer/Console/Application.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 64166a98226f..3a784f8a88bc 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -153,7 +153,10 @@ public function doRun(InputInterface $input, OutputInterface $output): int $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); - $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); + static $stdin = null; + if (null === $stdin) { + $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); + } if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) { $input->setInteractive(false); } From 305b3413cf8dea533c8f051bdda3fc0b013332f3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 09:52:56 +0200 Subject: [PATCH 164/257] Add test covering the fix for #12107, closes #12108 --- tests/Composer/Test/ApplicationTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 8e4bce1b51db..866892a3b7ef 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -74,4 +74,16 @@ public function testDevWarningSuppressedForSelfUpdate(): void self::assertSame('', $output->fetch()); } + + /** + * @runInSeparateProcess + * @see https://github.com/composer/composer/issues/12107 + */ + public function testProcessIsolationWorksMultipleTimes(): void + { + $application = new Application; + $application->add(new \Composer\Command\AboutCommand); + self::assertSame(0, $application->doRun(new ArrayInput(['command' => 'about']), new BufferedOutput())); + self::assertSame(0, $application->doRun(new ArrayInput(['command' => 'about']), new BufferedOutput())); + } } From f9e6214bd950105f705cc6252dc028b7529d9981 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 11:07:27 +0200 Subject: [PATCH 165/257] Fix completion docs, fixes #12099 --- doc/03-cli.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 5b29fd9b2f38..c189f1ebb16c 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1086,8 +1086,8 @@ php composer.phar help install ## Command-line completion -Command-line completion can be enabled by following instructions -[on this page](https://github.com/bamarni/symfony-console-autocomplete). +Command-line completion can be enabled by running the `composer completion --help` command and +following the instructions. ## Environment variables From f17df6d5a15d9f90fe4a21c213fa0691e28f57e4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 13:31:33 +0200 Subject: [PATCH 166/257] Fix handling of platform packages in why-not command and partial updates, fixes #12104 (#12110) --- .../Command/BaseDependencyCommand.php | 21 +++++++++- .../DependencyResolver/PoolBuilder.php | 10 ++++- .../Command/BaseDependencyCommandTest.php | 42 +++++++++++++++++-- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index 2fb363979c10..bb2a64233c7b 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -13,6 +13,7 @@ namespace Composer\Command; use Composer\Package\Link; +use Composer\Package\Package; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\RootPackage; @@ -24,6 +25,8 @@ use Composer\Repository\RepositoryFactory; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Composer\Semver\Constraint\Bound; +use Composer\Util\Platform; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\Package\Version\VersionParser; @@ -102,13 +105,27 @@ protected function doExecute(InputInterface $input, OutputInterface $output, boo // If the version we ask for is not installed then we need to locate it in remote repos and add it. // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. - if (!$installedRepo->findPackage($needle, $textConstraint)) { + $matchedPackage = $installedRepo->findPackage($needle, $textConstraint); + if (!$matchedPackage) { $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { $installedRepo->addRepository(new InstalledArrayRepository([clone $match])); + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($textConstraint); + if ($constraint->getLowerBound() !== Bound::zero()) { + $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion()); + $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg])); + } } else { $this->getIO()->writeError('Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.'); } + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $extraNotice = ''; + if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) { + $extraNotice = ' (version provided by config.platform)'; + } + $this->getIO()->writeError('Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.'); } // Include replaced packages for inverted lookups as they are then the actual starting point to consider @@ -154,7 +171,7 @@ protected function doExecute(InputInterface $input, OutputInterface $output, boo $this->printTable($output, $results); } - if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT)) { + if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) { $composerCommand = 'update'; foreach ($composer->getPackage()->getRequires() as $rootRequirement) { diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 15bc35885973..d3fbc521f8b2 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -616,6 +616,8 @@ private function isUpdateAllowed(BasePackage $package): bool private function warnAboutNonMatchingUpdateAllowList(Request $request): void { foreach ($this->updateAllowList as $pattern) { + $matchedPlatformPackage = false; + $patternRegexp = BasePackage::packageNameToRegexp($pattern); // update pattern matches a locked package? => all good foreach ($request->getLockedRepository()->getPackages() as $package) { @@ -626,10 +628,16 @@ private function warnAboutNonMatchingUpdateAllowList(Request $request): void // update pattern matches a root require? => all good, probably a new package foreach ($request->getRequires() as $packageName => $constraint) { if (Preg::isMatch($patternRegexp, $packageName)) { + if (PlatformRepository::isPlatformPackage($packageName)) { + $matchedPlatformPackage = true; + continue; + } continue 2; } } - if (strpos($pattern, '*') !== false) { + if ($matchedPlatformPackage) { + $this->io->writeError('Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.'); + } elseif (strpos($pattern, '*') !== false) { $this->io->writeError('Pattern "' . $pattern . '" listed for update does not match any locked packages.'); } else { $this->io->writeError('Package "' . $pattern . '" listed for update is not locked.'); diff --git a/tests/Composer/Test/Command/BaseDependencyCommandTest.php b/tests/Composer/Test/Command/BaseDependencyCommandTest.php index c663b70f75d3..0fad665aef7f 100644 --- a/tests/Composer/Test/Command/BaseDependencyCommandTest.php +++ b/tests/Composer/Test/Command/BaseDependencyCommandTest.php @@ -15,6 +15,7 @@ use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; use Symfony\Component\Console\Command\Command; use UnexpectedValueException; use InvalidArgumentException; @@ -374,19 +375,25 @@ public function testWhyNotCommandOutputs(array $parameters, string $expectedOutp 'package' => [ ['name' => 'vendor1/package1', 'version' => '1.3.0'], ['name' => 'vendor2/package1', 'version' => '2.0.0'], - ['name' => 'vendor2/package2', 'version' => '1.0.0', 'require' => ['vendor2/package3' => '1.4.*']], + ['name' => 'vendor2/package2', 'version' => '1.0.0', 'require' => ['vendor2/package3' => '1.4.*', 'php' => '^8.2']], ['name' => 'vendor2/package3', 'version' => '1.4.0'], ['name' => 'vendor2/package3', 'version' => '1.5.0'] ], ], ], 'require' => [ - 'vendor1/package1' => '1.*' + 'vendor1/package1' => '1.*', + 'php' => '^8', ], 'require-dev' => [ 'vendor2/package1' => '2.*', 'vendor2/package2' => '^1' - ] + ], + 'config' => [ + 'platform' => [ + 'php' => '8.3.2', + ], + ], ]); $someRequiredPackage = self::getPackage('vendor1/package1', '1.3.0'); @@ -399,7 +406,14 @@ public function testWhyNotCommandOutputs(array $parameters, string $expectedOutp new MatchAllConstraint(), Link::TYPE_REQUIRE, '1.4.*' - ) + ), + 'php' => new Link( + 'vendor2/package2', + 'php', + new MultiConstraint([self::getVersionConstraint('>=', '8.2.0.0'), self::getVersionConstraint('<', '9.0.0.0-dev')]), + Link::TYPE_REQUIRE, + '^8.2' + ), ]); $secondDevNestedRequiredPackage = self::getPackage('vendor2/package3', '1.4.0'); @@ -466,6 +480,26 @@ public function caseWhyNotProvider(): Generator vendor2/package2 1.0.0 requires vendor2/package3 (1.4.*) Not finding what you were looking for? Try calling `composer update "vendor2/package3:1.5.0" --dry-run` to get another view on the problem. OUTPUT +, + 1 + ]; + + yield 'all compatible with the inspected platform package (range matching installed)' => [ + ['package' => 'php', 'version' => '^8'], + << [ + ['package' => 'php', 'version' => '9.1.0'], + << Date: Tue, 17 Sep 2024 14:08:38 +0200 Subject: [PATCH 167/257] Bump branch-alias to 2.8 --- composer.json | 2 +- src/Composer/Composer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7ca64f0ba2c7..060b41d9df32 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" }, "phpstan": { "includes": [ diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..07d6973334d4 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -54,7 +54,7 @@ class Composer extends PartialComposer public const VERSION = '@package_version@'; public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const SOURCE_VERSION = '2.8.999-dev+source'; /** * Version number of the internal composer-runtime-api package From fde6a87f649534187c927f74d6b59b66c304db02 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 14:13:58 +0200 Subject: [PATCH 168/257] Sort problem rules (#12111) --- phpstan/baseline.neon | 10 + src/Composer/DependencyResolver/Problem.php | 51 ++ .../Test/DependencyResolver/SolverTest.php | 8 +- .../installer-slow/github-issues-7665.test | 446 +++++++++--------- .../installer/alias-solver-problems.test | 5 +- .../installer/alias-solver-problems2.test | 3 +- .../installer/broken-deps-do-not-replace.test | 4 +- ...ainst-replaced-by-dep-package-problem.test | 3 +- ...lict-against-replaced-package-problem.test | 3 +- .../conflict-between-dependents.test | 3 +- ...alias-prevents-update-if-not-required.test | 4 +- ...h-alias-in-lock-does-prevents-install.test | 2 +- ...alias-prevents-update-if-not-required.test | 2 +- ...ncies-option-dont-recommend-to-use-it.test | 3 +- .../deduplicate-solver-problems.test | 5 +- .../installer/github-issues-7051.test | 21 +- .../installer/github-issues-8902.test | 6 +- .../installer/provider-conflicts.test | 3 +- .../installer/provider-conflicts3.test | 11 +- .../Fixtures/installer/solver-problems.test | 4 +- ...unded-conflict-matches-default-branch.test | 2 +- 21 files changed, 325 insertions(+), 274 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 4b21d55c68a0..421690f904d2 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -1185,6 +1185,11 @@ parameters: count: 4 path: ../src/Composer/DependencyResolver/PoolOptimizer.php + - + message: "#^Cannot call method getPrettyString\\(\\) on array\\\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|string\\.$#" + count: 1 + path: ../src/Composer/DependencyResolver/Problem.php + - message: "#^Cannot call method getRepoName\\(\\) on Composer\\\\Repository\\\\RepositoryInterface\\|null\\.$#" count: 3 @@ -1195,6 +1200,11 @@ parameters: count: 1 path: ../src/Composer/DependencyResolver/Problem.php + - + message: "#^Cannot cast array\\{package\\: Composer\\\\Package\\\\BasePackage\\}\\|array\\{packageName\\: string, constraint\\: Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\}\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|non\\-empty\\-string to string\\.$#" + count: 1 + path: ../src/Composer/DependencyResolver/Problem.php + - message: "#^Only booleans are allowed in &&, Composer\\\\DependencyResolver\\\\Pool\\|null given on the left side\\.$#" count: 2 diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index a49bf5436278..652144b23fe3 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -98,9 +98,60 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, } } + usort($reasons, function (Rule $rule1, Rule $rule2) use ($pool) { + $rule1Prio = $this->getRulePriority($rule1); + $rule2Prio = $this->getRulePriority($rule2); + if ($rule1Prio !== $rule2Prio) { + return $rule2Prio - $rule1Prio; + } + + return $this->getSortableString($pool, $rule1) <=> $this->getSortableString($pool, $rule2); + }); + return self::formatDeduplicatedRules($reasons, ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); } + private function getSortableString(Pool $pool, Rule $rule): string + { + switch ($rule->getReason()) { + case Rule::RULE_ROOT_REQUIRE: + return $rule->getReasonData()['packageName']; + case Rule::RULE_FIXED: + return (string) $rule->getReasonData()['package']; + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_REQUIRES: + return $rule->getSourcePackage($pool) . '//' . $rule->getReasonData()->getPrettyString($rule->getSourcePackage($pool)); + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_PACKAGE_ALIAS: + case Rule::RULE_PACKAGE_INVERSE_ALIAS: + return (string) $rule->getReasonData(); + case Rule::RULE_LEARNED: + return implode('-', $rule->getLiterals()); + } + + throw new \LogicException('Unknown rule type: '.$rule->getReason()); + } + + private function getRulePriority(Rule $rule): int + { + switch ($rule->getReason()) { + case Rule::RULE_FIXED: + return 3; + case Rule::RULE_ROOT_REQUIRE: + return 2; + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_REQUIRES: + return 1; + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_LEARNED: + case Rule::RULE_PACKAGE_ALIAS: + case Rule::RULE_PACKAGE_INVERSE_ALIAS: + return 0; + } + + throw new \LogicException('Unknown rule type: '.$rule->getReason()); + } + /** * @param Rule[] $rules * @param array $installedMap A map of all present packages diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index c04f3f978040..2e7d3eb10b49 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -777,8 +777,8 @@ public function testConflictResultEmpty(): void $msg = "\n"; $msg .= " Problem 1\n"; $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; - $msg .= " - A 1.0 conflicts with B 1.0.\n"; $msg .= " - Root composer.json requires b * -> satisfiable by B[1.0].\n"; + $msg .= " - A 1.0 conflicts with B 1.0.\n"; self::assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } @@ -851,12 +851,12 @@ public function testRequireMismatchException(): void $msg = "\n"; $msg .= " Problem 1\n"; + $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; + $msg .= " - A 1.0 requires b >= 1.0 -> satisfiable by B[1.0].\n"; + $msg .= " - B 1.0 requires c >= 1.0 -> satisfiable by C[1.0].\n"; $msg .= " - C 1.0 requires d >= 1.0 -> satisfiable by D[1.0].\n"; $msg .= " - D 1.0 requires b < 1.0 -> satisfiable by B[0.9].\n"; - $msg .= " - B 1.0 requires c >= 1.0 -> satisfiable by C[1.0].\n"; $msg .= " - You can only install one version of a package, so only one of these can be installed: B[0.9, 1.0].\n"; - $msg .= " - A 1.0 requires b >= 1.0 -> satisfiable by B[1.0].\n"; - $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; self::assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } diff --git a/tests/Composer/Test/Fixtures/installer-slow/github-issues-7665.test b/tests/Composer/Test/Fixtures/installer-slow/github-issues-7665.test index 33e29deecfb2..a971ad45bdf9 100644 --- a/tests/Composer/Test/Fixtures/installer-slow/github-issues-7665.test +++ b/tests/Composer/Test/Fixtures/installer-slow/github-issues-7665.test @@ -2799,237 +2799,237 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.1 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.2 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.3 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.4 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.5 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.6 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.7 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.8 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.0 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.1 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.2 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.3 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.14.0 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.14.1 (conflict analysis result) - - Conclusion: don't install friendsofphp/php-cs-fixer v2.14.2 (conflict analysis result) + - Root composer.json requires behat/behat ~2.5 -> satisfiable by behat/behat[v2.5.5]. - Root composer.json requires friendsofphp/php-cs-fixer ^2.12 -> satisfiable by friendsofphp/php-cs-fixer[v2.12.0, ..., v2.14.2]. - - Conclusion: don't install symfony/finder v2.7.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.0.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.11 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.12 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.1 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.13 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.14 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.15 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.16 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.17 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.18 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.19 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.20 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.21 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.1.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.22 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.23 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.1 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.24 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.25 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.26 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.27 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.28 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.29 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.30 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.31 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.32 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.33 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.11 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.34 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.12 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.35 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.13 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.36 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.2.14 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.37 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.38 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.1 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.39 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.40 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.41 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.42 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.43 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.44 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.45 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.46 (conflict analysis result) + - behat/behat v2.5.5 requires symfony/finder ~2.0 -> satisfiable by symfony/finder[v2.6.0, ..., v2.8.49]. + - friendsofphp/php-cs-fixer v2.12.0 requires symfony/finder ^3.0 || ^4.0 -> satisfiable by symfony/finder[v3.0.0, ..., v3.4.24, v4.0.0, ..., v4.2.5]. + - Conclusion: don't install symfony/finder v4.1.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.1.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.1.0 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.9 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.15 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.14 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.13 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.12 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.11 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.10 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v4.0.0 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.9 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.24 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.23 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.22 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.21 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.20 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.19 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.18 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.17 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.16 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.15 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.14 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.13 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.12 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.11 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.10 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.4.0 (conflict analysis result) - Conclusion: don't install symfony/finder v3.3.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.47 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.48 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.11 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.49 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.12 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.7.50 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.13 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.14 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.1 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.15 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.16 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.3.17 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.2 (conflict analysis result) - Conclusion: don't install symfony/finder v3.3.18 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.1 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.17 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.16 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.15 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.14 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.13 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.12 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.11 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.10 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.3.0 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.9 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.14 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.13 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.12 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.11 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.10 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.2.0 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.9 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.10 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.1.0 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.9 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v3.0.1 (conflict analysis result) - Conclusion: don't install symfony/finder v2.8.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.11 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.12 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.13 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.14 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.15 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.16 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.11 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.17 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.12 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.18 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.13 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.19 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.14 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.20 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.15 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.21 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.16 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.22 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.17 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.23 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.18 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.24 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.19 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.25 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.20 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.26 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.21 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.27 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.22 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.28 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.23 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.29 (conflict analysis result) - - Conclusion: don't install symfony/finder v3.4.24 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.30 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.31 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.1 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.32 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.2 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.33 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.3 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.34 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.4 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.35 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.5 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.36 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.6 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.37 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.7 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.38 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.8 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.39 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.9 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.40 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.10 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.41 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.11 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.42 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.12 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.43 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.13 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.44 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.14 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.45 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.0.15 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.46 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.1.0 (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.47 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.1.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.49 (conflict analysis result) - Conclusion: don't install symfony/finder v2.8.48 (conflict analysis result) - - Conclusion: don't install symfony/finder v4.1.2 (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.2.5] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.2.4] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.2.3] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.2.2] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.2.1] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.2.0] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.11] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.47 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.46 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.45 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.44 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.43 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.42 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.41 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.40 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.39 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.38 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.37 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.36 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.35 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.34 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.33 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.32 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.31 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.30 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.29 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.28 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.27 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.26 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.25 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.24 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.23 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.22 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.21 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.20 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.2 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.19 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.18 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.17 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.16 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.15 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.14 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.13 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.12 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.11 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.10 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.1 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.8.0 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.9 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.8 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.7 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.6 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.50 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.5 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.49 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.48 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.47 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.46 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.45 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.44 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.43 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.42 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.41 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.40 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.4 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.39 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.38 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.37 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.36 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.35 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.34 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.33 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.32 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.31 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.30 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.3 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.29 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.28 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.27 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.26 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.25 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.24 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.23 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.22 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.21 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.20 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.19 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.18 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.17 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.16 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.15 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.14 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.13 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.12 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.11 (conflict analysis result) + - Conclusion: don't install symfony/finder v2.7.10 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.14.2 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.14.1 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.14.0 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.3 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.2 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.1 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.13.0 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.8 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.7 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.6 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.5 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.4 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.3 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.2 (conflict analysis result) + - Conclusion: don't install friendsofphp/php-cs-fixer v2.12.1 (conflict analysis result) - Conclusion: don't install symfony/finder[v4.1.10] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.9] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.8] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.7] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.6] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.5] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder[v4.1.4] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.1.11] | install symfony/finder[v2.8.49] (conflict analysis result) - Conclusion: don't install symfony/finder[v4.1.3] | install symfony/finder[v2.8.49] (conflict analysis result) - - Conclusion: don't install symfony/finder v2.8.49 (conflict analysis result) - - Root composer.json requires behat/behat ~2.5 -> satisfiable by behat/behat[v2.5.5]. - - behat/behat v2.5.5 requires symfony/finder ~2.0 -> satisfiable by symfony/finder[v2.6.0, ..., v2.8.49]. + - Conclusion: don't install symfony/finder[v4.1.4] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.1.5] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.1.6] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.1.7] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.1.8] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.1.9] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.2.0] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.2.1] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.2.2] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.2.3] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.2.4] | install symfony/finder[v2.8.49] (conflict analysis result) + - Conclusion: don't install symfony/finder[v4.2.5] | install symfony/finder[v2.8.49] (conflict analysis result) - You can only install one version of a package, so only one of these can be installed: symfony/finder[v2.6.0, ..., v2.8.49, v3.0.0, ..., v3.4.24, v4.0.0, ..., v4.2.5]. - - friendsofphp/php-cs-fixer v2.12.0 requires symfony/finder ^3.0 || ^4.0 -> satisfiable by symfony/finder[v3.0.0, ..., v3.4.24, v4.0.0, ..., v4.2.5]. - - Conclusion: don't install symfony/finder v3.0.1 (conflict analysis result) --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test index 1729826ec082..16e93b0fb33f 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test @@ -45,12 +45,11 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 + - Root composer.json requires a/a *@dev -> satisfiable by a/a[dev-master]. - Root composer.json requires b/b *@dev -> satisfiable by b/b[dev-master]. - a/a dev-master requires d/d 1.0.0 -> satisfiable by d/d[1.0.0]. - - You can only install one version of a package, so only one of these can be installed: d/d[1.0.0, 2.0.0]. - b/b dev-master requires d/d 2.0.0 -> satisfiable by d/d[2.0.0]. - Conclusion: install b/b dev-master (conflict analysis result) - - Root composer.json requires a/a *@dev -> satisfiable by a/a[dev-master]. + - You can only install one version of a package, so only one of these can be installed: d/d[1.0.0, 2.0.0]. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test index 3e37004d5f61..f32aa31fda71 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test @@ -43,9 +43,8 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. - locked/pkg is locked to version dev-master and an update of this package was not requested. + - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test index c9a9dba6e6a3..baccf01ff3e5 100644 --- a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test +++ b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test @@ -26,9 +26,9 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - c/c 1.0.0 requires x/x 1.0 -> could not be found in any version, there may be a typo in the package name. - - b/b 1.0.0 requires c/c 1.* -> satisfiable by c/c[1.0.0]. - Root composer.json requires b/b 1.* -> satisfiable by b/b[1.0.0]. + - b/b 1.0.0 requires c/c 1.* -> satisfiable by c/c[1.0.0]. + - c/c 1.0.0 requires x/x 1.0 -> could not be found in any version, there may be a typo in the package name. Potential causes: - A typo in the package name diff --git a/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test index 81035601a06d..f316ccc68c82 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test @@ -41,8 +41,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires conflicting/pkg * -> satisfiable by conflicting/pkg[1.0.0]. - - conflicting/pkg 1.0.0 conflicts with replaced/pkg2 >=2.1 (provider/pkg 1.0.0 replaces replaced/pkg2 2.5). - Root composer.json requires provider/pkg * -> satisfiable by provider/pkg[1.0.0]. + - conflicting/pkg 1.0.0 conflicts with replaced/pkg2 >=2.1 (provider/pkg 1.0.0 replaces replaced/pkg2 2.5). --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test index b6cca7270d11..2fe44ae4d8b3 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test @@ -38,8 +38,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - __root__ is present at version 1.2.3 and cannot be modified by Composer - - conflicting/pkg 1.0.0 conflicts with replaced/pkg 2.* (__root__ 1.2.3 replaces replaced/pkg 2.*). - Root composer.json requires conflicting/pkg * -> satisfiable by conflicting/pkg[1.0.0]. + - conflicting/pkg 1.0.0 conflicts with replaced/pkg 2.* (__root__ 1.2.3 replaces replaced/pkg 2.*). --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test b/tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test index a59b2ef98a24..71bf55709dab 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test @@ -31,8 +31,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires conflicter/pkg 1.0.0 -> satisfiable by conflicter/pkg[1.0.0]. - - conflicter/pkg 1.0.0 conflicts with victim/pkg 1.0.0. - Root composer.json requires victim/pkg 1.0.0 -> satisfiable by victim/pkg[1.0.0]. + - conflicter/pkg 1.0.0 conflicts with victim/pkg 1.0.0. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/conflict-on-root-with-alias-prevents-update-if-not-required.test b/tests/Composer/Test/Fixtures/installer/conflict-on-root-with-alias-prevents-update-if-not-required.test index 3a457b22c3dd..d562163c64e4 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-on-root-with-alias-prevents-update-if-not-required.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-on-root-with-alias-prevents-update-if-not-required.test @@ -32,7 +32,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - __root__ is present at version 1.0.0+no-version-set and cannot be modified by Composer - - some/dep 1.3.x-dev is an alias of some/dep dev-main and must be installed with it. - - __root__ 1.0.0+no-version-set conflicts with some/dep 1.3.x-dev. - Root composer.json requires some/dep dev-main -> satisfiable by some/dep[dev-main]. + - __root__ 1.0.0+no-version-set conflicts with some/dep 1.3.x-dev. + - some/dep 1.3.x-dev is an alias of some/dep dev-main and must be installed with it. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test index c19ca396ca1c..6009d64cfa16 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test @@ -48,7 +48,7 @@ Your lock file does not contain a compatible set of packages. Please run compose Problem 1 - conflictor/foo is locked to version 1.0.0 and an update of this package was not requested. - - conflictor/foo 1.0.0 conflicts with some/dep 1.3.x-dev. - some/dep is locked to version 1.3.x-dev and an update of this package was not requested. + - conflictor/foo 1.0.0 conflicts with some/dep 1.3.x-dev. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update-if-not-required.test b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update-if-not-required.test index 78cfd0362a1e..8475f41fb0f4 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update-if-not-required.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update-if-not-required.test @@ -30,9 +30,9 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 + - Root composer.json requires conflictor/foo 1.0.0 -> satisfiable by conflictor/foo[1.0.0]. - Root composer.json requires some/dep dev-main -> satisfiable by some/dep[dev-main]. - conflictor/foo 1.0.0 conflicts with some/dep 1.3.x-dev. - some/dep 1.3.x-dev is an alias of some/dep dev-main and must be installed with it. - - Root composer.json requires conflictor/foo 1.0.0 -> satisfiable by conflictor/foo[1.0.0]. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test b/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test index 1f5120880d40..f1c5714a570a 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test @@ -43,8 +43,7 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. - locked/pkg is locked to version dev-master and an update of this package was not requested. + - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test b/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test index 02155ef52b00..fc26296ddf64 100644 --- a/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test @@ -41,9 +41,8 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - package/a[2.2.0, ..., 2.6.0] require missing/dep ^1.1 -> found missing/dep[2.0.0] but it does not match the constraint. - - package/a[2.0.0, ..., 2.1.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint. - Root composer.json requires package/a * -> satisfiable by package/a[2.0.0, ..., 2.6.0]. + - package/a[2.0.0, ..., 2.1.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint. + - package/a[2.2.0, ..., 2.6.0] require missing/dep ^1.1 -> found missing/dep[2.0.0] but it does not match the constraint. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-7051.test b/tests/Composer/Test/Fixtures/installer/github-issues-7051.test index 190689db90e0..ada6386759f4 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-7051.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-7051.test @@ -115,19 +115,19 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - Conclusion: don't install friendsofphp/php-cs-fixer v2.10.5 (conflict analysis result) - Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.4, v2.10.5]. - - illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26]. + - Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0]. + - friendsofphp/php-cs-fixer v2.10.4 requires symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.2.13, ..., v3.4.29]. + - illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10]. - illuminate/console v5.2.26 requires symfony/console 2.8.* -> satisfiable by symfony/console[v2.8.7, v2.8.8]. + - illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26]. - Conclusion: don't install symfony/console v2.8.7 (conflict analysis result) - - Conclusion: don't install symfony/console v3.4.28 (conflict analysis result) - Conclusion: don't install symfony/console v2.8.8 (conflict analysis result) + - Conclusion: don't install symfony/console v3.1.10 (conflict analysis result) + - Conclusion: don't install symfony/console v3.4.28 (conflict analysis result) - Conclusion: don't install symfony/console v3.4.29 (conflict analysis result) - - Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0]. - - friendsofphp/php-cs-fixer v2.10.4 requires symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.2.13, ..., v3.4.29]. + - Conclusion: don't install friendsofphp/php-cs-fixer v2.10.5 (conflict analysis result) - You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.7, v2.8.8, v3.1.9, ..., v3.4.29]. - - illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10]. - - Conclusion: don't install symfony/console v3.1.10 (conflict analysis result) --EXPECT-OUTPUT-OPTIMIZED-- Loading composer repositories with package information @@ -135,16 +135,15 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 + - Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.4, v2.10.5]. - Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0]. - - illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26]. + - friendsofphp/php-cs-fixer[v2.10.4, ..., v2.10.5] require symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.2.13, ..., v3.4.29]. - illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10]. - illuminate/console v5.2.26 requires symfony/console 2.8.* -> satisfiable by symfony/console[v2.8.7, v2.8.8]. + - illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26]. - You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.7, v2.8.8, v3.1.9, ..., v3.4.29]. - - friendsofphp/php-cs-fixer[v2.10.4, ..., v2.10.5] require symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.2.13, ..., v3.4.29]. - - Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.4, v2.10.5]. --EXPECT-- --EXPECT-EXIT-CODE-- 2 - diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-8902.test b/tests/Composer/Test/Fixtures/installer/github-issues-8902.test index 53fc1275bcf3..bc25155255ff 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-8902.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-8902.test @@ -34,11 +34,11 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires beyondcode/laravel-dump-server ^1.3 -> satisfiable by beyondcode/laravel-dump-server[1.4.0]. + - Root composer.json requires laravel/framework ^6.8 -> satisfiable by laravel/framework[6.8.14]. + - beyondcode/laravel-dump-server 1.4.0 requires symfony/var-dumper ^5.0 -> satisfiable by symfony/var-dumper[5.2.x-dev (alias of dev-master)]. + - laravel/framework 6.8.14 requires symfony/var-dumper ^4.3.4 -> satisfiable by symfony/var-dumper[4.4.0]. - You can only install one version of a package, so only one of these can be installed: symfony/var-dumper[dev-master, 4.4.0]. - symfony/var-dumper 5.2.x-dev is an alias of symfony/var-dumper dev-master and thus requires it to be installed too. - - laravel/framework 6.8.14 requires symfony/var-dumper ^4.3.4 -> satisfiable by symfony/var-dumper[4.4.0]. - - beyondcode/laravel-dump-server 1.4.0 requires symfony/var-dumper ^5.0 -> satisfiable by symfony/var-dumper[5.2.x-dev (alias of dev-master)]. - - Root composer.json requires laravel/framework ^6.8 -> satisfiable by laravel/framework[6.8.14]. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/provider-conflicts.test b/tests/Composer/Test/Fixtures/installer/provider-conflicts.test index a73dd3dc9f56..572440468479 100644 --- a/tests/Composer/Test/Fixtures/installer/provider-conflicts.test +++ b/tests/Composer/Test/Fixtures/installer/provider-conflicts.test @@ -42,8 +42,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - __root__ is present at version 1.2.3 and cannot be modified by Composer - - provider/pkg[1.0.0] cannot be installed as that would require removing __root__[1.2.3]. They both replace root-replaced/transitive-replaced and thus cannot coexist. - Root composer.json requires provider/pkg * -> satisfiable by provider/pkg[1.0.0]. + - provider/pkg[1.0.0] cannot be installed as that would require removing __root__[1.2.3]. They both replace root-replaced/transitive-replaced and thus cannot coexist. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test b/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test index 29f0ebe86d83..eb54bb93df05 100644 --- a/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test +++ b/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test @@ -37,12 +37,12 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - Conclusion: don't install regular/pkg 1.0.1 (conflict analysis result) - - Conclusion: don't install regular/pkg 1.0.2 (conflict analysis result) - - Conclusion: don't install regular/pkg 1.0.3 (conflict analysis result) - - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it. - Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3]. - Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. + - Conclusion: don't install regular/pkg 1.0.3 (conflict analysis result) + - Conclusion: don't install regular/pkg 1.0.2 (conflict analysis result) + - Conclusion: don't install regular/pkg 1.0.1 (conflict analysis result) + - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it. --EXPECT-OUTPUT-OPTIMIZED-- Loading composer repositories with package information @@ -50,9 +50,8 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it. - Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3]. - Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. + - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/solver-problems.test b/tests/Composer/Test/Fixtures/installer/solver-problems.test index 610911bb98ed..95f7f6e2bff3 100644 --- a/tests/Composer/Test/Fixtures/installer/solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/solver-problems.test @@ -141,9 +141,9 @@ Your requirements could not be resolved to an installable set of packages. - Root composer.json requires requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0]. - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> found dependency/pkg[1.0.0] but it conflicts with your root composer.json require (2.*). Problem 15 - - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> found dependency/pkg[1.0.0] but it conflicts with your root composer.json require (2.*). - - package/found5 2.0.0 requires requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0]. - Root composer.json requires package/found5 2.* -> satisfiable by package/found5[2.0.0]. + - package/found5 2.0.0 requires requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0]. + - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> found dependency/pkg[1.0.0] but it conflicts with your root composer.json require (2.*). Potential causes: - A typo in the package name diff --git a/tests/Composer/Test/Fixtures/installer/unbounded-conflict-matches-default-branch.test b/tests/Composer/Test/Fixtures/installer/unbounded-conflict-matches-default-branch.test index d4a3b4d85aa8..ae1bfdb5cd87 100644 --- a/tests/Composer/Test/Fixtures/installer/unbounded-conflict-matches-default-branch.test +++ b/tests/Composer/Test/Fixtures/installer/unbounded-conflict-matches-default-branch.test @@ -32,8 +32,8 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - conflicter/pkg 1.0.0 conflicts with victim/pkg dev-master. - Root composer.json requires conflicter/pkg 1.0.0 -> satisfiable by conflicter/pkg[1.0.0]. - Root composer.json requires victim/pkg * -> satisfiable by victim/pkg[dev-master]. + - conflicter/pkg 1.0.0 conflicts with victim/pkg dev-master. --EXPECT-- From 39110978231e3e1fcdefd2d684e845ef8ef0b51d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 14:20:28 +0200 Subject: [PATCH 169/257] Update deps --- composer.lock | 138 +++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/composer.lock b/composer.lock index 3476d503908d..8ed996808ef6 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "1205554eabbe029b41297f4f003f8cc0", + "content-hash": "d6685068b4f2660ec25263c8ecf9a367", "packages": [ { "name": "composer/ca-bundle", @@ -1237,20 +1237,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1296,7 +1296,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -1312,24 +1312,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -1374,7 +1374,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -1390,24 +1390,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -1455,7 +1455,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -1471,24 +1471,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1535,7 +1535,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1551,24 +1551,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1611,7 +1611,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" }, "funding": [ { @@ -1627,24 +1627,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1691,7 +1691,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -1707,24 +1707,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1767,7 +1767,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -1783,7 +1783,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", @@ -2020,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.1", + "version": "1.12.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", "shasum": "" }, "require": { @@ -2074,25 +2074,25 @@ "type": "github" } ], - "time": "2024-09-03T19:55:22+00:00" + "time": "2024-09-09T08:10:35+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -2119,9 +2119,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1" }, - "time": "2024-04-20T06:39:48+00:00" + "time": "2024-09-11T15:52:35+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2226,22 +2226,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.8", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd" + "reference": "51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/14eec8c011b856eee4d744a2a3f709db1e1858bd", - "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9", + "reference": "51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11.7" + "phpstan/phpstan": "^1.12" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -2292,9 +2292,9 @@ "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.4.8" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.9" }, - "time": "2024-08-13T19:43:40+00:00" + "time": "2024-09-05T16:15:09+00:00" }, { "name": "symfony/phpunit-bridge", From a8b43b4b7755779bf865a4a977075c00d9bd1543 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 14:56:58 +0200 Subject: [PATCH 170/257] Fix phpstan baseline --- phpstan/baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 421690f904d2..7f26fa595473 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -3924,7 +3924,7 @@ parameters: path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#" + message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource\\|false, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php From c2b1667cacd5331683d7abcdd4adc992e28c206f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 14:57:46 +0200 Subject: [PATCH 171/257] Update deps --- composer.lock | 136 +++++++++++++++++++++--------------------- phpstan/baseline.neon | 2 +- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/composer.lock b/composer.lock index 3476d503908d..a43a3278f11e 100644 --- a/composer.lock +++ b/composer.lock @@ -1237,20 +1237,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1296,7 +1296,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -1312,24 +1312,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -1374,7 +1374,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -1390,24 +1390,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -1455,7 +1455,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -1471,24 +1471,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1535,7 +1535,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1551,24 +1551,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1611,7 +1611,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" }, "funding": [ { @@ -1627,24 +1627,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1691,7 +1691,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -1707,24 +1707,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1767,7 +1767,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -1783,7 +1783,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", @@ -2020,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.1", + "version": "1.12.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", "shasum": "" }, "require": { @@ -2074,25 +2074,25 @@ "type": "github" } ], - "time": "2024-09-03T19:55:22+00:00" + "time": "2024-09-09T08:10:35+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -2119,9 +2119,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1" }, - "time": "2024-04-20T06:39:48+00:00" + "time": "2024-09-11T15:52:35+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2226,22 +2226,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.8", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd" + "reference": "51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/14eec8c011b856eee4d744a2a3f709db1e1858bd", - "reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9", + "reference": "51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11.7" + "phpstan/phpstan": "^1.12" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -2292,9 +2292,9 @@ "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.4.8" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.9" }, - "time": "2024-08-13T19:43:40+00:00" + "time": "2024-09-05T16:15:09+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 4b21d55c68a0..dccdeb15d168 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -3914,7 +3914,7 @@ parameters: path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#" + message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource\\|false, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php From bd4fd2cf94b9e7c72417bba963afec25130c7ce0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 15:35:53 +0200 Subject: [PATCH 172/257] Alias clarifications, fixes #11301 --- doc/articles/aliases.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md index ce5a5b71d3d4..e7a4b3189ecd 100644 --- a/doc/articles/aliases.md +++ b/doc/articles/aliases.md @@ -7,32 +7,32 @@ ## Why aliases? When you are using a VCS repository, you will only get comparable versions for -branches that look like versions, such as `2.0` or `2.0.x`. For your `master` branch, you -will get a `dev-master` version. For your `bugfix` branch, you will get a +branches that look like versions, such as `2.0` or `2.0.x`. For your `main` branch, you +will get a `dev-main` version. For your `bugfix` branch, you will get a `dev-bugfix` version. -If your `master` branch is used to tag releases of the `1.0` development line, +If your `main` branch is used to tag releases of the `1.0` development line, i.e. `1.0.1`, `1.0.2`, `1.0.3`, etc., any package depending on it will probably require version `1.0.*`. -If anyone wants to require the latest `dev-master`, they have a problem: Other +If anyone wants to require the latest `dev-main`, they have a problem: Other packages may require `1.0.*`, so requiring that dev version will lead to -conflicts, since `dev-master` does not match the `1.0.*` constraint. +conflicts, since `dev-main` does not match the `1.0.*` constraint. Enter aliases. ## Branch alias -The `dev-master` branch is one in your main VCS repo. It is rather common that -someone will want the latest master dev version. Thus, Composer allows you to -alias your `dev-master` branch to a `1.0.x-dev` version. It is done by +The `dev-main` branch is one in your main VCS repo. It is rather common that +someone will want the latest main dev version. Thus, Composer allows you to +alias your `dev-main` branch to a `1.0.x-dev` version. It is done by specifying a `branch-alias` field under `extra` in `composer.json`: ```json { "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "1.0.x-dev" } } } @@ -41,14 +41,16 @@ specifying a `branch-alias` field under `extra` in `composer.json`: If you alias a non-comparable version (such as dev-develop) `dev-` must prefix the branch name. You may also alias a comparable version (i.e. start with numbers, and end with `.x-dev`), but only as a more specific version. -For example, 1.x-dev could be aliased as 1.2.x-dev. +For example, a `1.x` or `1.x-dev` branch could be aliased from `1.x-dev` to +`1.2.x-dev` as that is more specific. -The alias must be a comparable dev version, and the `branch-alias` must be present on -the branch that it references. For `dev-master`, you need to commit it on the -`master` branch. +The alias must be a comparable dev version (you cannot alias `dev-main` +to `dev-master` for example), and the `branch-alias` must be present on +the branch that it references. To alias `dev-main`, you need to define and +commit it on the `main` branch. As a result, anyone can now require `1.0.*` and it will happily install -`dev-master`. +`dev-main`. In order to use branch aliasing, you must own the repository of the package being aliased. If you want to alias a third party package without maintaining From 21bf74d2c7a4dc3432018701ee8159334fab121e Mon Sep 17 00:00:00 2001 From: Mohamed Hubail Date: Tue, 17 Sep 2024 16:44:55 +0300 Subject: [PATCH 173/257] Add `--abandoned` option (#12091) * Add `--abandoned` option * Refactoring - Use `Auditor::ABANDONEDS` in `Config.php` - Drop `getAuditAbandoned()` from `BaseCommand.php` * Modify cli docs --- doc/03-cli.md | 4 ++++ doc/06-config.md | 7 ++++++- src/Composer/Advisory/Auditor.php | 7 +++++++ src/Composer/Command/AuditCommand.php | 18 +++++++++++++++++- src/Composer/Config.php | 4 ++-- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index c189f1ebb16c..396c24f63550 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1075,6 +1075,10 @@ php composer.phar audit * **--no-dev:** Disables auditing of require-dev packages. * **--format (-f):** Audit output format. Must be "table" (default), "plain", "json", or "summary". * **--locked:** Audit packages from the lock file, regardless of what is currently in vendor dir. +* **--abandoned:** Behavior on abandoned packages. Must be "ignore", "report", + or "fail". See also [audit.abandoned](06-config.md#abandoned). Passing this + flag will override the config value and the environment variable. + ## help diff --git a/doc/06-config.md b/doc/06-config.md index f2c914f2e202..273a1e37997c 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -153,7 +153,12 @@ Defaults to `report` in Composer 2.6, and defaults to `fail` from Composer 2.7 o } ``` -Since Composer 2.7 the option can be overridden via the [`COMPOSER_AUDIT_ABANDONED`](03-cli.md#composer-audit-abandoned) environment variable. +Since Composer 2.7, the option can be overridden via the [`COMPOSER_AUDIT_ABANDONED`](03-cli.md#composer-audit-abandoned) environment variable. + +Since Composer 2.8, the option can be overridden via the +[`--abandoned`](03-cli.md#audit) command line option, which overrides both the +config value and the environment variable. + ## use-parent-dir diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index 38d827dfe190..bfd62a0877f6 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -47,6 +47,13 @@ class Auditor public const ABANDONED_REPORT = 'report'; public const ABANDONED_FAIL = 'fail'; + /** @internal */ + public const ABANDONEDS = [ + self::ABANDONED_IGNORE, + self::ABANDONED_REPORT, + self::ABANDONED_FAIL, + ]; + /** * @param PackageInterface[] $packages * @param self::FORMAT_* $format The format that will be used to output audit results. diff --git a/src/Composer/Command/AuditCommand.php b/src/Composer/Command/AuditCommand.php index 1097bb7af3f8..c0b0dcfadec5 100644 --- a/src/Composer/Command/AuditCommand.php +++ b/src/Composer/Command/AuditCommand.php @@ -33,6 +33,7 @@ protected function configure(): void new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS), new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'), + new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, Auditor::ABANDONEDS), ]) ->setHelp( <<getConfig()->get('audit'); - return min(255, $auditor->audit($this->getIO(), $repoSet, $packages, $this->getAuditFormat($input, 'format'), false, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL)); + $abandoned = $input->getOption('abandoned'); + if ($abandoned !== null && !in_array($abandoned, Auditor::ABANDONEDS, true)) { + throw new \InvalidArgumentException('--audit must be one of '.implode(', ', Auditor::ABANDONEDS).'.'); + } + + $abandoned = $abandoned ?? $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL; + + return min(255, $auditor->audit( + $this->getIO(), + $repoSet, + $packages, + $this->getAuditFormat($input, 'format'), + false, + $auditConfig['ignore'] ?? [], + $abandoned + )); } /** diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 01d86324944b..3cf84a73fe12 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -440,9 +440,9 @@ public function get(string $key, int $flags = 0) $result = $this->config[$key]; $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED'); if (false !== $abandonedEnv) { - if (!in_array($abandonedEnv, $validChoices = [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], true)) { + if (!in_array($abandonedEnv, $validChoices = Auditor::ABANDONEDS, true)) { throw new \RuntimeException( - "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected ".Auditor::ABANDONED_IGNORE.", ".Auditor::ABANDONED_REPORT." or ".Auditor::ABANDONED_FAIL + "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected one of ".implode(', ', Auditor::ABANDONEDS)."." ); } $result['abandoned'] = $abandonedEnv; From bb8387e5a0680769be7a1e4a37f5057dbe135b28 Mon Sep 17 00:00:00 2001 From: John Stevenson Date: Tue, 17 Sep 2024 15:14:47 +0100 Subject: [PATCH 174/257] Remove proxy transition fallback (#11938) --- .../how-to-use-composer-behind-a-proxy.md | 12 ------- src/Composer/Console/Application.php | 11 ------ src/Composer/Util/Http/ProxyManager.php | 36 ------------------- .../Test/Util/Http/ProxyManagerTest.php | 26 +------------- .../Test/Util/StreamContextFactoryTest.php | 4 +-- 5 files changed, 3 insertions(+), 86 deletions(-) diff --git a/doc/faqs/how-to-use-composer-behind-a-proxy.md b/doc/faqs/how-to-use-composer-behind-a-proxy.md index 58f82ebcb690..ebefaced0878 100644 --- a/doc/faqs/how-to-use-composer-behind-a-proxy.md +++ b/doc/faqs/how-to-use-composer-behind-a-proxy.md @@ -104,15 +104,3 @@ Setting the value to `*` will bypass the proxy for all requests. Composer originally provided `HTTP_PROXY_REQUEST_FULLURI` and `HTTPS_PROXY_REQUEST_FULLURI` to help mitigate issues with misbehaving proxies. These are no longer required or used. - -## Requirement changes - -Composer <2.8 used `http_proxy` for both HTTP and HTTPS requests if `https_proxy` was not set, -but as of Composer 2.8.0 it requires [scheme-specific](#usage) environment variables. - -The reason for this change is to align Composer with current practice across other popular tools. To help -with the transition, as of Composer 2.7.3 the original behaviour remains but a warning message is -shown instructing the user to add an `https_proxy` environment variable. - -To prevent the original behaviour during the transition period, set an empty environment variable -(`https_proxy=`). diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 3a784f8a88bc..7c511f994de4 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -43,7 +43,6 @@ use Composer\Exception\NoSslException; use Composer\XdebugHandler\XdebugHandler; use Symfony\Component\Process\Exception\ProcessTimedOutException; -use Composer\Util\Http\ProxyManager; /** * The console application that handles the commands @@ -383,8 +382,6 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow } try { - $proxyManager = ProxyManager::getInstance(); - if ($input->hasParameterOption('--profile')) { $startTime = microtime(true); $this->io->enableDebugging($startTime); @@ -406,14 +403,6 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } - if ($proxyManager->needsTransitionWarning()) { - $io->writeError(''); - $io->writeError('Composer now requires separate proxy environment variables for HTTP and HTTPS requests.'); - $io->writeError('Please set `https_proxy` in addition to your existing proxy environment variables.'); - $io->writeError('This fallback (and warning) will be removed in Composer 2.8.0.'); - $io->writeError('https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md'); - } - return $result; } catch (ScriptExecutionException $e) { if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { diff --git a/src/Composer/Util/Http/ProxyManager.php b/src/Composer/Util/Http/ProxyManager.php index 0571780fe053..247105fa5e5f 100644 --- a/src/Composer/Util/Http/ProxyManager.php +++ b/src/Composer/Util/Http/ProxyManager.php @@ -33,20 +33,8 @@ class ProxyManager /** @var ?self */ private static $instance = null; - /** The following 3 properties can be removed after the transition period */ - - /** @var bool */ - private $ignoreHttpsProxy = false; - /** @var bool */ - private $isTransitional = false; - /** @var bool */ - private $needsTransitionWarning = false; - private function __construct() { - // this can be removed after the transition period - $this->isTransitional = true; - try { $this->getProxyData(); } catch (\RuntimeException $e) { @@ -96,16 +84,6 @@ public function getProxyForRequest(string $requestUrl): RequestProxy return $proxy->toRequestProxy($scheme); } - /** - * Returns true if the user needs to set an https_proxy environment variable - * - * This method can be removed after the transition period - */ - public function needsTransitionWarning(): bool - { - return $this->needsTransitionWarning; - } - /** * Returns a ProxyItem if one is set for the scheme, otherwise null */ @@ -116,15 +94,6 @@ private function getProxyForScheme(string $scheme): ?ProxyItem } if ($scheme === 'https') { - // this can be removed after the transition period - if ($this->isTransitional && $this->httpsProxy === null) { - if ($this->httpProxy !== null && !$this->ignoreHttpsProxy) { - $this->needsTransitionWarning = true; - - return $this->httpProxy; - } - } - return $this->httpsProxy; } @@ -179,11 +148,6 @@ private function getProxyEnv(string $envName): array if ($_SERVER[$name] !== '') { return [$_SERVER[$name], $name]; } - // this can be removed after the transition period - if ($this->isTransitional && strtolower($name) === 'https_proxy') { - $this->ignoreHttpsProxy = true; - break; - } } } diff --git a/tests/Composer/Test/Util/Http/ProxyManagerTest.php b/tests/Composer/Test/Util/Http/ProxyManagerTest.php index 80c88a84455b..04c756b48ae7 100644 --- a/tests/Composer/Test/Util/Http/ProxyManagerTest.php +++ b/tests/Composer/Test/Util/Http/ProxyManagerTest.php @@ -20,11 +20,6 @@ */ class ProxyManagerTest extends TestCase { - // isTransitional can be removed after the transition period - - /** @var bool */ - private $isTransitional = true; - protected function setUp(): void { unset( @@ -140,31 +135,12 @@ public function testNoHttpProxyDoesNotUseHttpsProxy(): void } public function testNoHttpsProxyDoesNotUseHttpProxy(): void - { - $_SERVER['http_proxy'] = 'http://proxy.com:80'; - - // This can be removed after the transition period. - // An empty https_proxy value prevents using any http_proxy - if ($this->isTransitional) { - $_SERVER['https_proxy'] = ''; - } - - $proxyManager = ProxyManager::getInstance(); - $proxy = $proxyManager->getProxyForRequest('https://repo.org'); - self::assertSame('', $proxy->getStatus()); - } - - /** - * This test can be removed after the transition period - */ - public function testTransitional(): void { $_SERVER['http_proxy'] = 'http://proxy.com:80'; $proxyManager = ProxyManager::getInstance(); $proxy = $proxyManager->getProxyForRequest('https://repo.org'); - self::assertSame('http://proxy.com:80', $proxy->getStatus()); - self::assertTrue($proxyManager->needsTransitionWarning()); + self::assertSame('', $proxy->getStatus()); } /** diff --git a/tests/Composer/Test/Util/StreamContextFactoryTest.php b/tests/Composer/Test/Util/StreamContextFactoryTest.php index d1fe5db31fe1..bb89cefd597d 100644 --- a/tests/Composer/Test/Util/StreamContextFactoryTest.php +++ b/tests/Composer/Test/Util/StreamContextFactoryTest.php @@ -133,7 +133,7 @@ public function testOptionsArePreserved(): void public function testHttpProxyWithoutPort(): void { - $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net'; + $_SERVER['https_proxy'] = 'http://username:password@proxyserver.net'; $context = StreamContextFactory::getContext('https://example.org', ['http' => ['method' => 'GET', 'header' => 'User-Agent: foo']]); $options = stream_context_get_options($context); @@ -221,7 +221,7 @@ public function testEnsureThatfixHttpHeaderFieldMovesContentTypeToEndOfOptions() public function testInitOptionsDoesIncludeProxyAuthHeaders(): void { - $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:3128/'; + $_SERVER['https_proxy'] = 'http://username:password@proxyserver.net:3128/'; $options = []; $options = StreamContextFactory::initOptions('https://example.org', $options); From 12031542ba8e529925d1ffdc1714acf28a43758a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 18 Sep 2024 08:49:59 +0200 Subject: [PATCH 175/257] Add suggestions of provider packages for ext- and lib- packages (#12113) Fixes #11669 --- src/Composer/DependencyResolver/Problem.php | 48 +++++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 652144b23fe3..c54fab1afd26 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -278,14 +278,19 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) ?: '0'); if (null === $version) { + $providersStr = self::getProvidersList($repositorySet, $packageName, 5); + if ($providersStr !== null) { + $providersStr = "\n\n Alternatively you can require one of these packages that provide the extension (or parts of it):\n$providersStr"; + } + if (extension_loaded($ext)) { return [ $msg, - 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".', + 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".' . $providersStr, ]; } - return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.']; + return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.' . $providersStr]; } return [$msg, 'it has the wrong version installed ('.$version.').']; @@ -299,7 +304,12 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error]; } - return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.']; + $providersStr = self::getProvidersList($repositorySet, $packageName, 5); + if ($providersStr !== null) { + $providersStr = "\n\n Alternatively you can require one of these packages that provide the library (or parts of it):\n$providersStr"; + } + + return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.'.$providersStr]; } } @@ -412,17 +422,8 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req return ["- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.']; } - if ($providers = $repositorySet->getProviders($packageName)) { - $maxProviders = 20; - $providersStr = implode(array_map(static function ($p): string { - $description = $p['description'] ? ' '.substr($p['description'], 0, 100) : ''; - - return ' - '.$p['name'].$description."\n"; - }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); - if (count($providers) > $maxProviders + 1) { - $providersStr .= ' ... and '.(count($providers) - $maxProviders).' more.'."\n"; - } - + $providersStr = self::getProvidersList($repositorySet, $packageName, 15); + if ($providersStr !== null) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr." Consider requiring one of these to satisfy the $packageName requirement."]; } @@ -635,4 +636,23 @@ protected static function constraintToText(?ConstraintInterface $constraint = nu return $constraint ? ' '.$constraint->getPrettyString() : ''; } + + private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string + { + $providers = $repositorySet->getProviders($packageName); + if (\count($providers) > 0) { + $providersStr = implode(array_map(static function ($p): string { + $description = $p['description'] ? ' '.substr($p['description'], 0, 100) : ''; + + return ' - '.$p['name'].$description."\n"; + }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); + if (count($providers) > $maxProviders + 1) { + $providersStr .= ' ... and '.(count($providers) - $maxProviders).' more.'."\n"; + } + + return $providersStr; + } + + return null; + } } From 07248f4323934859a66a50d84f018c82d6f5347e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 18 Sep 2024 09:16:31 +0200 Subject: [PATCH 176/257] Fix require command skipping new stability-flags from the lock file, fixes #11698 (#12112) --- src/Composer/Command/RequireCommand.php | 6 ++++++ src/Composer/Package/Locker.php | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 1b6f5e7b8d7f..6f1b9ea7c8b8 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -561,10 +561,16 @@ private function updateRequirementsAfterResolution(array $requirementsToUpdate, } $lockFile = Factory::getLockFile($this->json->getPath()); if (file_exists($lockFile)) { + $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); + $lockMtime = filemtime($lockFile); $lock = new JsonFile($lockFile); $lockData = $lock->read(); $lockData['content-hash'] = Locker::getContentHash($contents); + foreach ($stabilityFlags as $packageName => $flag) { + $lockData['stability-flags'][$packageName] = $flag; + } + ksort($lockData['stability-flags']); $lock->write($lockData); if (is_int($lockMtime)) { @touch($lockFile, $lockMtime); diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 87971cc03b49..366af3f3fc12 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -373,6 +373,8 @@ public function setLockData(array $packages, ?array $devPackages, array $platfor 'prefer-lowest' => $preferLowest, ]; + ksort($lock['stability-flags']); + $lock['packages'] = $this->lockPackages($packages); if (null !== $devPackages) { $lock['packages-dev'] = $this->lockPackages($devPackages); From be7d9abc666e81e7d077fe0399faf048effbe655 Mon Sep 17 00:00:00 2001 From: Job Vink Date: Wed, 18 Sep 2024 10:43:42 +0200 Subject: [PATCH 177/257] Improve interactive package updates (#11990) * Improve interactive package updates * Exclude platform packages and up to date packages, follow stability flags, ignore-platform-reqs etc * Add tests and support for lock file + empty lock/vendor --------- Co-authored-by: Jordi Boggiano --- src/Composer/Command/UpdateCommand.php | 86 ++++++++++++------- .../Test/Command/UpdateCommandTest.php | 41 ++++++++- 2 files changed, 93 insertions(+), 34 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index b4478cd04f26..1dee17972db0 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -16,20 +16,27 @@ use Composer\DependencyResolver\Request; use Composer\Installer; use Composer\IO\IOInterface; +use Composer\Package\BasePackage; use Composer\Package\Loader\RootPackageLoader; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionSelector; use Composer\Pcre\Preg; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\Version\VersionParser; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; use Composer\Semver\Intervals; use Composer\Util\HttpDownloader; use Composer\Advisory\Auditor; +use Composer\Util\Platform; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\Question; /** * @author Jordi Boggiano @@ -261,48 +268,53 @@ private function getPackagesInteractively(IOInterface $io, InputInterface $input throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); } + $platformReqFilter = $this->getPlatformRequirementFilter($input); + $stabilityFlags = $composer->getPackage()->getStabilityFlags(); $requires = array_merge( $composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires() ); - $autocompleterValues = []; - foreach ($requires as $require) { - $target = $require->getTarget(); - $autocompleterValues[strtolower($target)] = $target; - } - - $installedPackages = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); - foreach ($installedPackages as $package) { - $autocompleterValues[$package->getName()] = $package->getPrettyName(); - } - $helper = $this->getHelper('question'); - $question = new Question('Enter package name: ', null); + $filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null; - $io->writeError('Press enter without value to end submission'); - - do { - $autocompleterValues = array_diff($autocompleterValues, $packages); - $question->setAutocompleterValues($autocompleterValues); - $addedPackage = $helper->ask($input, $output, $question); - - if (!is_string($addedPackage) || empty($addedPackage)) { - break; + $io->writeError('Loading packages that can be updated...'); + $autocompleterValues = []; + $installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages(); + $versionSelector = $this->createVersionSelector($composer); + foreach ($installedPackages as $package) { + if ($filter !== null && !Preg::isMatch($filter, $package->getName())) { + continue; } - - $addedPackage = strtolower($addedPackage); - if (!in_array($addedPackage, $packages)) { - $packages[] = $addedPackage; + $currentVersion = $package->getPrettyVersion(); + $constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null; + $stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability(); + $latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter); + if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) { + $autocompleterValues[$package->getName()] = '' . $currentVersion . ' => ' . $latestVersion->getPrettyVersion() . ''; } - } while (true); + } + if (0 === \count($installedPackages)) { + foreach ($requires as $req => $constraint) { + if (PlatformRepository::isPlatformPackage($req)) { + continue; + } + $autocompleterValues[$req] = ''; + } + } - $packages = array_filter($packages, function (string $pkg) { - return $pkg !== ''; - }); - if (!$packages) { - throw new \InvalidArgumentException('You must enter minimum one package.'); + if (0 === \count($autocompleterValues)) { + throw new \RuntimeException('Could not find any package with new versions available'); } + $packages = $io->select( + 'Select packages: (Select more than one value separated by comma) ', + $autocompleterValues, + false, + 1, + 'No package named "%s" is installed.', + true + ); + $table = new Table($output); $table->setHeaders(['Selected packages']); foreach ($packages as $package) { @@ -319,4 +331,14 @@ private function getPackagesInteractively(IOInterface $io, InputInterface $input throw new \RuntimeException('Installation aborted.'); } + + private function createVersionSelector(Composer $composer): VersionSelector + { + $repositorySet = new RepositorySet(); + $repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), function (RepositoryInterface $repository) { + return !$repository instanceof PlatformRepository; + }))); + + return new VersionSelector($repositorySet); + } } diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index 7b5c41280147..a4d34626e2cc 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -128,10 +128,47 @@ public static function provideUpdates(): \Generator ]; } + public function testInteractiveModeThrowsIfNoPackageToUpdate(): void + { + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'root/req', 'version' => '1.0.0'], + ], + ], + ], + 'require' => [ + 'root/req' => '1.*', + ], + ]); + $this->createComposerLock([self::getPackage('root/req', '1.0.0')]); + self::expectExceptionMessage('Could not find any package with new versions available'); + + $appTester = $this->getApplicationTester(); + $appTester->setInputs(['']); + $appTester->run(['command' => 'update', '--interactive' => true]); + } + public function testInteractiveModeThrowsIfNoPackageEntered(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('You must enter minimum one package.'); + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'root/req', 'version' => '1.0.0'], + ['name' => 'root/req', 'version' => '1.0.1'], + ], + ], + ], + 'require' => [ + 'root/req' => '1.*', + ], + ]); + $this->createComposerLock([self::getPackage('root/req', '1.0.0')]); + self::expectExceptionMessage('No package named "" is installed.'); $appTester = $this->getApplicationTester(); $appTester->setInputs(['']); From 8f455d7c0c4be1dddc111e1277bff9b3a4d49a0b Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 18 Sep 2024 11:00:09 +0200 Subject: [PATCH 178/257] Add allow-missing-requirements config setting to ignore missing requirements (#11966) * Add allow-missing-requirements configuration to ignore error during install if there are any missing requirements * Add test for allow-missing-requirements config --------- Co-authored-by: Joe --- doc/06-config.md | 6 +++ res/composer-schema.json | 4 ++ src/Composer/Config.php | 1 + src/Composer/Installer.php | 4 +- ...tall-from-incomplete-lock-with-ignore.test | 47 +++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test diff --git a/doc/06-config.md b/doc/06-config.md index 273a1e37997c..19a0df714974 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -481,4 +481,10 @@ throw, but you can set this config option to `["example.org"]` to allow using sv URLs on that hostname. This is a better/safer alternative to disabling `secure-http` altogether. +## allow-missing-requirements + +Defaults to `false`. Ignores error during `install` if there are any missing +requirements - the lock file is not up to date with the latest changes in +`composer.json`. + ← [Repositories](05-repositories.md) | [Runtime](07-runtime.md) → diff --git a/res/composer-schema.json b/res/composer-schema.json index 0f06cc542534..8a3d82b1b5cf 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -662,6 +662,10 @@ "platform-check": { "type": ["boolean", "string"], "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap." + }, + "allow-missing-requirements": { + "type": ["boolean"], + "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json." } } }, diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 3cf84a73fe12..34737c09cfa9 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -84,6 +84,7 @@ class Config 'gitlab-token' => [], 'http-basic' => [], 'bearer' => [], + 'allow-missing-requirements' => false, ]; /** @var array */ diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index b88906aaedfc..2502a7d0234a 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -742,7 +742,9 @@ protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alre if ($missingRequirementInfo !== []) { $this->io->writeError($missingRequirementInfo); - return self::ERROR_LOCK_FILE_INVALID; + if (!$this->config->get('allow-missing-requirements')) { + return self::ERROR_LOCK_FILE_INVALID; + } } foreach ($lockedRepository->getPackages() as $package) { diff --git a/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test b/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test new file mode 100644 index 000000000000..856a4257b8d5 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test @@ -0,0 +1,47 @@ +--TEST-- +Requirements from the composer file are not installed if the lock file is present and missing requirements warning +is issued when allow-missing-requirements if enabled +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "required/pkg", "version": "1.0.0" }, + { "name": "newly-required/pkg", "version": "1.0.0" } + ] + } + ], + "require": { + "required/pkg": "1.0.0", + "newly-required/pkg": "1.0.0" + }, + "config": { + "allow-missing-requirements": true + } +} +--LOCK-- +{ + "packages": [ + { "name": "required/pkg", "version": "1.0.0" } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false +} +--RUN-- +install +--EXPECT-OUTPUT-- +Installing dependencies from lock file (including require-dev) +Verifying lock file contents can be installed on current platform. +- Required package "newly-required/pkg" is not present in the lock file. +This usually happens when composer files are incorrectly merged or the composer.json file is manually edited. +Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md +and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r +Package operations: 1 install, 0 updates, 0 removals +Generating autoload files +--EXPECT-- +Installing required/pkg (1.0.0) From 17930441a1a31f053532c3f7da0b7989ae941ce0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 18 Sep 2024 14:44:55 +0200 Subject: [PATCH 179/257] Add a way to control which scripts get args and where (#12086) Add support for `@no_additional_args` and `@additional_args` tags inside script handlers. --- doc/articles/scripts.md | 32 +++++++++++++ .../EventDispatcher/EventDispatcher.php | 24 +++++++--- .../EventDispatcher/EventDispatcherTest.php | 45 +++++++++++++++++++ 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 26b8113ab848..5ed3bf6c3e19 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -410,6 +410,38 @@ JSON array of commands. You can also call a shell/bash script, which will have the path to the PHP executable available in it as a `PHP_BINARY` env var. +## Controlling additional arguments + +When running scripts like `composer script-name arg arg2` or `composer script-name -- --option`, +Composer will by default append `arg`, `arg2` and `--option` to the script's command. + +If you do not want these args in a given command, you can put `@no_additional_args` +anywhere in it, that will remove the default behavior and that flag will be removed +as well before running the command. + +If you want the args to be added somewhere else than at the very end, then you can put +`@additional_args` to be able to choose exactly where they go. + +For example running `composer run-commands ARG` with the below config: + +```json +{ + "scripts": { + "run-commands": [ + "echo hello @no_additional_args", + "command-with-args @additional_args && do-something-without-args --here" + ] + } +} +``` + +Would end up executing these commands: + +``` +echo hello +command-with-args ARG && do-something-without-args --here +``` + ## Setting environment variables To set an environment variable in a cross-platform way, you can use `@putenv`: diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index d10ca53b22ae..2d8af9e0707a 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -202,7 +202,12 @@ protected function doDispatch(Event $event) $return = 0; $this->ensureBinDirIsInPath(); - $formattedEventNameWithArgs = $event->getName() . ($event->getArguments() !== [] ? ' (' . implode(', ', $event->getArguments()) . ')' : ''); + $additionalArgs = $event->getArguments(); + if (is_string($callable) && str_contains($callable, '@no_additional_args')) { + $callable = Preg::replace('{ ?@no_additional_args}', '', $callable); + $additionalArgs = []; + } + $formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : ''); if (!is_string($callable)) { if (!is_callable($callable)) { $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; @@ -220,7 +225,12 @@ protected function doDispatch(Event $event) $scriptName = $script[0]; unset($script[0]); - $args = array_merge($script, $event->getArguments()); + $index = array_search('@additional_args', $script, true); + if ($index !== false) { + $args = array_splice($script, $index, 0, $additionalArgs); + } else { + $args = array_merge($script, $additionalArgs); + } $flags = $event->getFlags(); if (isset($flags['script-alias-input'])) { $argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script)); @@ -294,7 +304,7 @@ protected function doDispatch(Event $event) $app->add($cmd); $app->setDefaultCommand((string) $cmd->getName(), true); try { - $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $event->getArguments())); + $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs)); // reusing the output from $this->io is mostly needed for tests, but generally speaking // it does not hurt to keep the same stream as the current Application if ($this->io instanceof ConsoleIO) { @@ -313,13 +323,17 @@ protected function doDispatch(Event $event) throw $e; } } else { - $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $event->getArguments())); + $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs)); // @putenv does not receive arguments if (strpos($callable, '@putenv ') === 0) { $exec = $callable; } else { - $exec = $callable . ($args === '' ? '' : ' '.$args); + if (str_contains($callable, '@additional_args')) { + $exec = str_replace('@additional_args', $args, $callable); + } else { + $exec = $callable . ($args === '' ? '' : ' '.$args); + } } if ($this->io->isVerbose()) { diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 233317c03c29..1439c9f87515 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -311,6 +311,51 @@ public function testDispatcherAppendsDirBinOnPathForEveryListener(): void } } + public function testDispatcherSupportForAdditionalArgs(): void + { + $process = $this->getProcessExecutorMock(); + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') + ->setConstructorArgs([ + $this->createComposerInstance(), + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE), + $process, + ]) + ->onlyMethods([ + 'getListeners', + ]) + ->getMock(); + + $reflMethod = new \ReflectionMethod($dispatcher, 'getPhpExecCommand'); + if (PHP_VERSION_ID < 80100) { + $reflMethod->setAccessible(true); + } + $phpCmd = $reflMethod->invoke($dispatcher); + + $args = ProcessExecutor::escape('ARG').' '.ProcessExecutor::escape('ARG2').' '.ProcessExecutor::escape('--arg'); + $process->expects([ + 'echo -n foo', + $phpCmd.' foo.php '.$args.' then the rest', + 'echo -n bar '.$args, + ], true); + + $listeners = [ + 'echo -n foo @no_additional_args', + '@php foo.php @additional_args then the rest', + 'echo -n bar', + ]; + + $dispatcher->expects($this->atLeastOnce()) + ->method('getListeners') + ->will($this->returnValue($listeners)); + + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false, ['ARG', 'ARG2', '--arg']); + + $expected = '> post-install-cmd: echo -n foo'.PHP_EOL. + '> post-install-cmd: @php foo.php '.$args.' then the rest'.PHP_EOL. + '> post-install-cmd: echo -n bar '.$args.PHP_EOL; + self::assertEquals($expected, $io->getOutput()); + } + public static function createsVendorBinFolderChecksEnvDoesNotContainsBin(): void { mkdir(__DIR__ . '/vendor/bin', 0700, true); From c8838f198eb91dab72b06c9386a7ff3387a1cef6 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Wed, 18 Sep 2024 15:34:25 +0200 Subject: [PATCH 180/257] Add option to run bump after update (#11942) * Add option to run bump after update * Convert the option into a bool | string parameter and change a couple of texts * Apply suggestions from code review * Fix tests --------- Co-authored-by: Jordi Boggiano --- doc/03-cli.md | 1 + doc/06-config.md | 6 ++ res/composer-schema.json | 4 ++ src/Composer/Command/BumpCommand.php | 31 +++++++--- src/Composer/Command/ConfigCommand.php | 12 ++++ src/Composer/Command/UpdateCommand.php | 24 ++++++- src/Composer/Config.php | 1 + .../Test/Command/UpdateCommandTest.php | 62 ++++++++++++++++++- 8 files changed, 132 insertions(+), 9 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 396c24f63550..8c1c56e16aec 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -234,6 +234,7 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.* changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--interactive:** Interactive interface with autocompletion to select the packages to update. * **--root-reqs:** Restricts the update to your first degree dependencies. +* **--bump-after-update:** Runs `bump` after performing the update. Set to `dev` or `no-dev` to only bump those dependencies. Specifying one of the words `mirrors`, `lock`, or `nothing` as an argument has the same effect as specifying the option `--lock`, for example `composer update mirrors` is exactly the same as `composer update --lock`. diff --git a/doc/06-config.md b/doc/06-config.md index 19a0df714974..bff70d03d353 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -481,6 +481,12 @@ throw, but you can set this config option to `["example.org"]` to allow using sv URLs on that hostname. This is a better/safer alternative to disabling `secure-http` altogether. +## bump-after-update + +Defaults to `false` and can be any of `true`, `false`, `"dev"` or `"no-dev"`. If +set to true, Composer will run the `bump` command after running the `update` command. +If set to `"dev"` or `"no-dev"` then only the corresponding dependencies will be bumped. + ## allow-missing-requirements Defaults to `false`. Ignores error during `install` if there are any missing diff --git a/res/composer-schema.json b/res/composer-schema.json index 8a3d82b1b5cf..e6f626dcb281 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -663,6 +663,10 @@ "type": ["boolean", "string"], "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap." }, + "bump-after-update": { + "type": ["string", "boolean"], + "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped." + }, "allow-missing-requirements": { "type": ["boolean"], "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json." diff --git a/src/Composer/Command/BumpCommand.php b/src/Composer/Command/BumpCommand.php index db5b9464a0dc..5cd7a0f20093 100644 --- a/src/Composer/Command/BumpCommand.php +++ b/src/Composer/Command/BumpCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\IO\IOInterface; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Locker; @@ -72,9 +73,28 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { + return $this->doBump( + $this->getIO(), + $input->getOption('dev-only'), + $input->getOption('no-dev-only'), + $input->getOption('dry-run'), + $input->getArgument('packages') + ); + } + + /** + * @param string[] $packagesFilter + * @throws \Seld\JsonLint\ParsingException + */ + public function doBump( + IOInterface $io, + bool $devOnly, + bool $noDevOnly, + bool $dryRun, + array $packagesFilter + ): int { /** @readonly */ $composerJsonPath = Factory::getComposerFile(); - $io = $this->getIO(); if (!Filesystem::isReadable($composerJsonPath)) { $io->writeError(''.$composerJsonPath.' is not readable.'); @@ -112,7 +132,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $repo = $composer->getRepositoryManager()->getLocalRepository(); } - if ($composer->getPackage()->getType() !== 'project' && !$input->getOption('dev-only')) { + if ($composer->getPackage()->getType() !== 'project' && !$devOnly) { $io->writeError('Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.'); $contents = $composerJson->read(); @@ -125,14 +145,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $bumper = new VersionBumper(); $tasks = []; - if (!$input->getOption('dev-only')) { + if (!$devOnly) { $tasks['require'] = $composer->getPackage()->getRequires(); } - if (!$input->getOption('no-dev-only')) { + if (!$noDevOnly) { $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); } - $packagesFilter = $input->getArgument('packages'); if (count($packagesFilter) > 0) { $pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter))); foreach ($tasks as $key => $reqs) { @@ -171,8 +190,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - $dryRun = $input->getOption('dry-run'); - if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) { $composerDefinition = $composerJson->read(); foreach ($updates as $key => $packages) { diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 02e77df60a27..6dc48757481e 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -469,6 +469,18 @@ static function ($val) { 'prepend-autoloader' => [$booleanValidator, $booleanNormalizer], 'disable-tls' => [$booleanValidator, $booleanNormalizer], 'secure-http' => [$booleanValidator, $booleanNormalizer], + 'bump-after-update' => [ + static function ($val): bool { + return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('dev' === $val || 'no-dev' === $val) { + return $val; + } + + return $val !== 'false' && (bool) $val; + }, + ], 'cafile' => [ static function ($val): bool { return file_exists($val) && Filesystem::isReadable($val); diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 1dee17972db0..756aa00d9d0f 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -85,6 +85,7 @@ protected function configure() new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), + new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']), ]) ->setHelp( <<disablePlugins(); } - return $install->run(); + $result = $install->run(); + + if ($result === 0) { + $bumpAfterUpdate = $input->getOption('bump-after-update'); + if (false === $bumpAfterUpdate) { + $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update'); + } + + if (false !== $bumpAfterUpdate) { + $io->writeError('Bumping dependencies'); + $bumpCommand = new BumpCommand(); + $bumpCommand->setComposer($composer); + $result = $bumpCommand->doBump( + $io, + $bumpAfterUpdate === 'dev', + $bumpAfterUpdate === 'no-dev', + $input->getOption('dry-run'), + $input->getArgument('packages') + ); + } + } + return $result; } /** diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 34737c09cfa9..6844eb1e23df 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -84,6 +84,7 @@ class Config 'gitlab-token' => [], 'http-basic' => [], 'bearer' => [], + 'bump-after-update' => false, 'allow-missing-requirements' => false, ]; diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index a4d34626e2cc..dd8427d29628 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -24,10 +24,14 @@ class UpdateCommandTest extends TestCase * @param array $composerJson * @param array $command */ - public function testUpdate(array $composerJson, array $command, string $expected): void + public function testUpdate(array $composerJson, array $command, string $expected, bool $createLock = false): void { $this->initTempComposer($composerJson); + if ($createLock) { + $this->createComposerLock(); + } + $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command)); @@ -126,6 +130,62 @@ public static function provideUpdates(): \Generator Run `composer require root/req` or `composer require root/req:^2` instead to replace the constraint OUTPUT ]; + + yield 'update & bump' => [ + $rootDepAndTransitiveDep, + ['--bump-after-update' => true], + <<Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users. +If your package is not a library, you can explicitly specify the "type" by using "composer config type project". +Alternatively you can use --dev-only to only bump dependencies within "require-dev". +No requirements to update in ./composer.json. +OUTPUT + , true + ]; + + yield 'update & bump dev only' => [ + $rootDepAndTransitiveDep, + ['--bump-after-update' => 'dev'], + << [ + $rootDepAndTransitiveDep, + ['--with' => ['dep/pkg:^2'], '--bump-after-update' => true], + << satisfiable by root/req[1.0.0]. + - root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2). +OUTPUT + ]; + } public function testInteractiveModeThrowsIfNoPackageToUpdate(): void From ad6198ad2afadd4606f70c11f475695ebf46a055 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 18 Sep 2024 15:45:26 +0200 Subject: [PATCH 181/257] Ensure stability-flags, platform and platform-dev keys are objects in composer.lock --- composer.lock | 4 ++-- src/Composer/Package/Locker.php | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 8ed996808ef6..55d1cf38e32f 100644 --- a/composer.lock +++ b/composer.lock @@ -2381,13 +2381,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": "^7.2.5 || ^8.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "7.2.5" }, diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 366af3f3fc12..dc33a1123379 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -368,20 +368,22 @@ public function setLockData(array $packages, ?array $devPackages, array $platfor 'packages-dev' => null, 'aliases' => $aliases, 'minimum-stability' => $minimumStability, - 'stability-flags' => $stabilityFlags, + 'stability-flags' => \count($stabilityFlags) > 0 ? $stabilityFlags : new \stdClass, 'prefer-stable' => $preferStable, 'prefer-lowest' => $preferLowest, ]; - ksort($lock['stability-flags']); + if (is_array($lock['stability-flags'])) { + ksort($lock['stability-flags']); + } $lock['packages'] = $this->lockPackages($packages); if (null !== $devPackages) { $lock['packages-dev'] = $this->lockPackages($devPackages); } - $lock['platform'] = $platformReqs; - $lock['platform-dev'] = $platformDevReqs; + $lock['platform'] = \count($platformReqs) > 0 ? $platformReqs : new \stdClass; + $lock['platform-dev'] = \count($platformDevReqs) > 0 ? $platformDevReqs : new \stdClass; if (\count($platformOverrides) > 0) { $lock['platform-overrides'] = $platformOverrides; } From e5af569bfcc6c7e8ab0345f9d83c9bb4d1b1f3f5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 18 Sep 2024 16:32:40 +0200 Subject: [PATCH 182/257] Fix tests --- .../functional/installed-versions2/composer.lock | 6 +++--- .../composer.lock | 6 +++--- .../Test/Fixtures/installer/alias-in-lock.test | 6 +++--- .../Test/Fixtures/installer/alias-in-lock2.test | 12 ++++++------ .../Fixtures/installer/alias-solver-problems.test | 6 +++--- .../Fixtures/installer/alias-solver-problems2.test | 6 +++--- .../Fixtures/installer/alias-with-reference.test | 4 ++-- .../installer/aliased-priority-conflicting.test | 4 ++-- .../Fixtures/installer/aliases-with-require-dev.test | 4 ++-- ...ict-with-alias-in-lock-does-prevents-install.test | 4 ++-- .../conflict-with-alias-prevents-update.test | 4 ++-- ...dependencies-option-dont-recommend-to-use-it.test | 6 +++--- .../Fixtures/installer/github-issues-4795-2.test | 4 ++-- .../Test/Fixtures/installer/github-issues-4795.test | 6 +++--- .../Fixtures/installer/install-dev-using-dist.test | 4 ++-- .../install-from-incomplete-lock-with-ignore.test | 2 +- .../installer/install-from-incomplete-lock.test | 2 +- .../installer/install-from-lock-removes-package.test | 2 +- .../installer/install-missing-alias-from-lock.test | 2 +- .../load-replaced-package-if-replacer-dropped.test | 7 +++---- .../installer/outdated-lock-file-fails-install.test | 6 +++--- ...dated-lock-file-with-new-platform-reqs-fails.test | 4 ++-- ...l-update-always-updates-symlinked-path-repos.test | 4 ++-- ...-update-downgrades-non-allow-listed-unstable.test | 4 ++-- ...reference-from-lock-for-non-updated-packages.test | 12 ++++++------ .../partial-update-from-lock-with-root-alias.test | 12 ++++++------ .../Fixtures/installer/partial-update-from-lock.test | 10 +++++----- ...rtial-update-installs-from-lock-even-missing.test | 12 ++++++------ ...eps-older-dep-if-still-required-with-provide.test | 10 +++++----- ...ial-update-keeps-older-dep-if-still-required.test | 10 +++++----- ...ial-update-loads-root-aliases-for-path-repos.test | 4 ++-- .../partial-update-with-dependencies-provide.test | 10 +++++----- .../partial-update-with-dependencies-replace.test | 10 +++++----- .../partial-update-with-deps-warns-root.test | 10 +++++----- .../partial-update-with-symlinked-path-repos.test | 4 ++-- .../installer/platform-ext-solver-problems.test | 6 +++--- .../Fixtures/installer/problems-reduce-versions.test | 7 +++---- .../provider-dev-require-can-satisfy-require.test | 6 +++--- .../installer/remove-deletes-unused-deps.test | 2 +- ...es-nothing-if-removal-requires-update-of-dep.test | 2 +- ...d-not-be-installed-when-installing-from-lock.test | 4 ++-- .../root-alias-change-with-circular-dep.test | 6 +++--- .../root-alias-gets-loaded-for-locked-pkgs.test | 6 +++--- ...t-requirements-do-not-affect-locked-versions.test | 2 +- .../Test/Fixtures/installer/solver-problems.test | 6 +++--- .../Test/Fixtures/installer/suggest-prod.test | 6 +++--- .../Test/Fixtures/installer/update-alias-lock.test | 6 +++--- .../Test/Fixtures/installer/update-alias-lock2.test | 8 ++++---- .../installer/update-allow-list-locked-require.test | 6 +++--- .../installer/update-allow-list-minimal-changes.test | 6 +++--- ...te-allow-list-patterns-with-all-dependencies.test | 6 +++--- ...e-allow-list-patterns-with-root-dependencies.test | 6 +++--- .../installer/update-allow-list-patterns.test | 6 +++--- .../installer/update-allow-list-reads-lock.test | 2 +- .../installer/update-allow-list-removes-unused.test | 6 +++--- .../update-allow-list-require-new-replace.test | 6 +++--- ...pdate-allow-list-warns-non-existing-patterns.test | 6 +++--- .../update-allow-list-with-dependencies-alias.test | 10 +++++----- ...allow-list-with-dependencies-new-requirement.test | 6 +++--- ...with-dependencies-require-new-replace-mutual.test | 6 +++--- ...w-list-with-dependencies-require-new-replace.test | 6 +++--- ...ate-allow-list-with-dependencies-require-new.test | 6 +++--- .../Test/Fixtures/installer/update-allow-list.test | 6 +++--- .../Test/Fixtures/installer/update-changes-url.test | 8 ++++---- .../update-dev-packages-updates-repo-url.test | 12 ++++++------ .../installer/update-mirrors-changes-url.test | 8 ++++---- .../installer/update-mirrors-fails-with-new-req.test | 12 ++++++------ .../installer/update-no-dev-still-resolves-dev.test | 4 ++-- .../Test/Fixtures/installer/update-no-install.test | 6 +++--- ...age-present-in-lock-but-not-at-all-in-remote.test | 6 +++--- ...-lock-but-not-in-remote-due-to-min-stability.test | 6 +++--- ...te-package-present-in-lock-but-not-in-remote.test | 6 +++--- ...-repo-prio-but-not-main-due-to-min-stability.test | 6 +++--- .../update-picks-up-change-of-vcs-type.test | 12 ++++++------ .../installer/update-removes-unused-locked-dep.test | 12 ++++++------ .../Fixtures/installer/update-syncs-outdated.test | 6 +++--- .../installer/update-to-empty-from-blank.test | 6 +++--- .../installer/update-to-empty-from-locked.test | 12 ++++++------ .../installer/update-with-all-dependencies.test | 6 +++--- .../updating-dev-from-lock-removes-old-deps.test | 2 +- .../updating-dev-updates-url-and-reference.test | 4 ++-- tests/Composer/Test/InstallerTest.php | 5 +++++ tests/Composer/Test/Package/LockerTest.php | 6 +++--- 83 files changed, 262 insertions(+), 259 deletions(-) diff --git a/tests/Composer/Test/Fixtures/functional/installed-versions2/composer.lock b/tests/Composer/Test/Fixtures/functional/installed-versions2/composer.lock index 825f8a49642a..f78da55917ca 100644 --- a/tests/Composer/Test/Fixtures/functional/installed-versions2/composer.lock +++ b/tests/Composer/Test/Fixtures/functional/installed-versions2/composer.lock @@ -231,10 +231,10 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "plugin-api-version": "2.0.0" } diff --git a/tests/Composer/Test/Fixtures/functional/plugin-autoloading-only-loads-dependencies/composer.lock b/tests/Composer/Test/Fixtures/functional/plugin-autoloading-only-loads-dependencies/composer.lock index 0beb2f5a7efc..6499f4b924e5 100644 --- a/tests/Composer/Test/Fixtures/functional/plugin-autoloading-only-loads-dependencies/composer.lock +++ b/tests/Composer/Test/Fixtures/functional/plugin-autoloading-only-loads-dependencies/composer.lock @@ -29,10 +29,10 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "plugin-api-version": "2.2.0" } diff --git a/tests/Composer/Test/Fixtures/installer/alias-in-lock.test b/tests/Composer/Test/Fixtures/installer/alias-in-lock.test index 49cd1519faf2..deb9cb2f17c1 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-in-lock.test +++ b/tests/Composer/Test/Fixtures/installer/alias-in-lock.test @@ -57,11 +57,11 @@ update "alias_normalized": "3.0.3.0" }], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing a/aliased2 (3.0.2) diff --git a/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test b/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test index c781ae32f450..92b1323d6eff 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test +++ b/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test @@ -35,11 +35,11 @@ Newly defined root aliases end up in lock file only if the package is updated "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update a/aliased @@ -63,11 +63,11 @@ update a/aliased "alias_normalized": "3.0.3.0" }], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing a/aliased (3.0.2) diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test index 16e93b0fb33f..2b1a46e78554 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test @@ -26,11 +26,11 @@ Test the error output of solver problems with dev-master aliases. "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test index f32aa31fda71..c5e3e3b5ebda 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test @@ -24,11 +24,11 @@ Test the error output of solver problems with dev-master aliases. "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- diff --git a/tests/Composer/Test/Fixtures/installer/alias-with-reference.test b/tests/Composer/Test/Fixtures/installer/alias-with-reference.test index f7e51e5f9a6d..f6b35ebe6849 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-with-reference.test +++ b/tests/Composer/Test/Fixtures/installer/alias-with-reference.test @@ -55,8 +55,8 @@ update }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing a/aliased (dev-master abcd) diff --git a/tests/Composer/Test/Fixtures/installer/aliased-priority-conflicting.test b/tests/Composer/Test/Fixtures/installer/aliased-priority-conflicting.test index b409a5cfc08e..3c8b613d55ca 100644 --- a/tests/Composer/Test/Fixtures/installer/aliased-priority-conflicting.test +++ b/tests/Composer/Test/Fixtures/installer/aliased-priority-conflicting.test @@ -80,8 +80,8 @@ Aliases take precedence over default package even if default is selected }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test b/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test index 1500d156b8ed..805428165e8a 100644 --- a/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test +++ b/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test @@ -78,8 +78,8 @@ update }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing a/aliased (dev-next) diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test index 6009d64cfa16..ec77808d23f3 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-in-lock-does-prevents-install.test @@ -34,8 +34,8 @@ Test that conflict with a branch alias in the lock file leads to an error on ins }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update.test b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update.test index 74b61304e96a..efe40a9fd80e 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-alias-prevents-update.test @@ -36,8 +36,8 @@ update }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing some/dep (1.2.x-dev) diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test b/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test index f1c5714a570a..b24c04334df2 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test @@ -24,11 +24,11 @@ Verify that a conflict with all dependencies option enabled don't recommend to u "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test index 62bda3d7dc7b..a0c8452b9e80 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test @@ -45,8 +45,8 @@ that are also a root package, when that root package is also explicitly allowed. "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update a/a b/b --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test index b619785f0fb0..4e97d804b8f3 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test @@ -39,11 +39,11 @@ dependency of one the requirements that is allowed for update. "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update b/b --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test b/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test index 3c171a4b03a3..5bdf07c14c07 100644 --- a/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test +++ b/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test @@ -50,8 +50,8 @@ install }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing a/a (dev-master) diff --git a/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test b/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test index 856a4257b8d5..54ace3491279 100644 --- a/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test +++ b/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock-with-ignore.test @@ -28,7 +28,7 @@ is issued when allow-missing-requirements if enabled "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock.test b/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock.test index 0bbef6ace4c9..e068f5ae9acc 100644 --- a/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock.test +++ b/tests/Composer/Test/Fixtures/installer/install-from-incomplete-lock.test @@ -24,7 +24,7 @@ Requirements from the composer file are not installed if the lock file is presen "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/install-from-lock-removes-package.test b/tests/Composer/Test/Fixtures/installer/install-from-lock-removes-package.test index 4160fac55b64..65650cd41702 100644 --- a/tests/Composer/Test/Fixtures/installer/install-from-lock-removes-package.test +++ b/tests/Composer/Test/Fixtures/installer/install-from-lock-removes-package.test @@ -28,7 +28,7 @@ Install from a lock file that deleted a package "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false } --INSTALLED-- diff --git a/tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test b/tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test index 5f6459799be7..c318aca76a4d 100644 --- a/tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test +++ b/tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test @@ -32,7 +32,7 @@ Installing an old alias that doesn't exist anymore from a lock is possible "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/load-replaced-package-if-replacer-dropped.test b/tests/Composer/Test/Fixtures/installer/load-replaced-package-if-replacer-dropped.test index 851e03a97e00..3c249f678985 100644 --- a/tests/Composer/Test/Fixtures/installer/load-replaced-package-if-replacer-dropped.test +++ b/tests/Composer/Test/Fixtures/installer/load-replaced-package-if-replacer-dropped.test @@ -29,11 +29,11 @@ Ensure that a package gets loaded which was previously skipped due to replacemen "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update root/dep --with-all-dependencies @@ -42,4 +42,3 @@ Installing replacer/pkg (1.1.0) Installing root/dep (1.2.0) Installing replaced/pkg (1.0.0) Installing root/no-update (1.0.0) - diff --git a/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test index 73b34f28fee8..e4891a3e8779 100644 --- a/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test +++ b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-fails-install.test @@ -16,11 +16,11 @@ Test that install checks missing requirements from both composer.json if the loc "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test index a7ff7b3779b5..c859cb4f1f50 100644 --- a/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test +++ b/tests/Composer/Test/Fixtures/installer/outdated-lock-file-with-new-platform-reqs-fails.test @@ -16,11 +16,11 @@ Platform requires appearing in both lock and composer.json will be checked using "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": {"php": "^20", "ext-foo": "^5"}, - "platform-dev": [] + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-always-updates-symlinked-path-repos.test b/tests/Composer/Test/Fixtures/installer/partial-update-always-updates-symlinked-path-repos.test index e30d3a4052d9..ff0b1c6d353a 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-always-updates-symlinked-path-repos.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-always-updates-symlinked-path-repos.test @@ -44,8 +44,8 @@ Partial updates always update path repos which are symlinked, but not those whic }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-allow-listed-unstable.test b/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-allow-listed-unstable.test index 2ad9ea3f6607..1a14aab73e65 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-allow-listed-unstable.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-allow-listed-unstable.test @@ -37,8 +37,8 @@ Partial update from lock file should apply lock file and if an unstable package }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- [ diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test b/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test index 614c8751565b..7b76991a14dd 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test @@ -59,11 +59,11 @@ Partial update forces updates dev reference from lock file for non allowed packa "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update b/b @@ -86,11 +86,11 @@ update b/b "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Upgrading a/a (dev-master oldmaster-a => dev-master newmaster-a) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test index b5d195a46148..7aafbfc3f2f4 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test @@ -36,11 +36,11 @@ Partial update from lock file with root aliases should work } ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- [ @@ -67,11 +67,11 @@ update --lock } ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Marking c/aliased (2.0.0) as installed, alias of c/aliased (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test index d176b02d300e..febca0ba80f8 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test @@ -43,8 +43,8 @@ Partial update from lock file should update everything to the state of the lock, }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- [ @@ -67,11 +67,11 @@ update b/unstable "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Upgrading a/old (0.9.0 => 1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test b/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test index 9d971b3847e5..4164590977f8 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test @@ -62,11 +62,11 @@ Partial update installs from lock even if package don't exist in public repo any "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update b/b @@ -90,11 +90,11 @@ update b/b "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Marking a/a (2.1.x-dev oldmaster-a) as uninstalled, alias of a/a (dev-master oldmaster-a) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required-with-provide.test b/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required-with-provide.test index 4cae3c87ce73..0f80a42525d4 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required-with-provide.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required-with-provide.test @@ -36,8 +36,8 @@ Ensure that a partial update of a dependency does not conflict if the only way t "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update dep/pkg2 --with-dependencies @@ -52,11 +52,11 @@ update dep/pkg2 --with-dependencies "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing dep/pkg1 (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required.test b/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required.test index b2a6c67cf2f4..ee20c9d7720c 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-keeps-older-dep-if-still-required.test @@ -36,8 +36,8 @@ Ensure that a partial update of a dependency does not conflict if the only way t "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update dep/pkg2 --with-dependencies @@ -52,11 +52,11 @@ update dep/pkg2 --with-dependencies "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing dep/pkg1 (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-loads-root-aliases-for-path-repos.test b/tests/Composer/Test/Fixtures/installer/partial-update-loads-root-aliases-for-path-repos.test index 71d961e09c8e..a3f9921f0e77 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-loads-root-aliases-for-path-repos.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-loads-root-aliases-for-path-repos.test @@ -34,8 +34,8 @@ Partial updates always load root aliases even for path repos which are symlinked }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-provide.test b/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-provide.test index c332ca045a7a..e7935fb29ad0 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-provide.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-provide.test @@ -36,8 +36,8 @@ Ensure a partial update of a dependency does NOT update dependencies which provi "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update root/req1 --with-dependencies @@ -52,11 +52,11 @@ update root/req1 --with-dependencies "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing dep/pkg1 (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-replace.test b/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-replace.test index 39994422d81f..24d717d7dc38 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-replace.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-with-dependencies-replace.test @@ -35,8 +35,8 @@ Ensure a partial update of a dependency updates dependencies which replace one o "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update root/req1 --with-dependencies @@ -50,11 +50,11 @@ update root/req1 --with-dependencies "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing dep/pkg1 (1.2.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-with-deps-warns-root.test b/tests/Composer/Test/Fixtures/installer/partial-update-with-deps-warns-root.test index d8d49a246dbc..86b3faf13da3 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-with-deps-warns-root.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-with-deps-warns-root.test @@ -74,8 +74,8 @@ Ensure that a partial update of a dependency does not conflict if the only way t "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update update/pkg1 --with-dependencies @@ -101,11 +101,11 @@ update update/pkg1 --with-dependencies "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-OUTPUT-- Loading composer repositories with package information diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-with-symlinked-path-repos.test b/tests/Composer/Test/Fixtures/installer/partial-update-with-symlinked-path-repos.test index 2ea842613956..a6e0aa52782c 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-with-symlinked-path-repos.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-with-symlinked-path-repos.test @@ -64,8 +64,8 @@ Partially updating one root requirement with transitive deps fully updates trans }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- diff --git a/tests/Composer/Test/Fixtures/installer/platform-ext-solver-problems.test b/tests/Composer/Test/Fixtures/installer/platform-ext-solver-problems.test index be8313f55820..34d07c456754 100644 --- a/tests/Composer/Test/Fixtures/installer/platform-ext-solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/platform-ext-solver-problems.test @@ -27,11 +27,11 @@ Test the error output of solver problems with ext/platform packages which have p "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- diff --git a/tests/Composer/Test/Fixtures/installer/problems-reduce-versions.test b/tests/Composer/Test/Fixtures/installer/problems-reduce-versions.test index 377c6a742d41..4832906d537f 100644 --- a/tests/Composer/Test/Fixtures/installer/problems-reduce-versions.test +++ b/tests/Composer/Test/Fixtures/installer/problems-reduce-versions.test @@ -91,11 +91,11 @@ Test the error output minifies version lists "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- @@ -115,4 +115,3 @@ Your requirements could not be resolved to an installable set of packages. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. --EXPECT-- - diff --git a/tests/Composer/Test/Fixtures/installer/provider-dev-require-can-satisfy-require.test b/tests/Composer/Test/Fixtures/installer/provider-dev-require-can-satisfy-require.test index b5d3fefe7477..a4c2b66789e1 100644 --- a/tests/Composer/Test/Fixtures/installer/provider-dev-require-can-satisfy-require.test +++ b/tests/Composer/Test/Fixtures/installer/provider-dev-require-can-satisfy-require.test @@ -42,11 +42,11 @@ update --no-dev ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Installing b/b (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/remove-deletes-unused-deps.test b/tests/Composer/Test/Fixtures/installer/remove-deletes-unused-deps.test index 8a81d02e9648..0e5467e593b1 100644 --- a/tests/Composer/Test/Fixtures/installer/remove-deletes-unused-deps.test +++ b/tests/Composer/Test/Fixtures/installer/remove-deletes-unused-deps.test @@ -34,7 +34,7 @@ Removing a package deletes unused dependencies and does not update other depende "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/remove-does-nothing-if-removal-requires-update-of-dep.test b/tests/Composer/Test/Fixtures/installer/remove-does-nothing-if-removal-requires-update-of-dep.test index 5df46f1188b6..5a703492c4ae 100644 --- a/tests/Composer/Test/Fixtures/installer/remove-does-nothing-if-removal-requires-update-of-dep.test +++ b/tests/Composer/Test/Fixtures/installer/remove-does-nothing-if-removal-requires-update-of-dep.test @@ -27,7 +27,7 @@ Removing a package has no effect if another package would require an update in o "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test b/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test index 947d96b28129..ea06b8e9a167 100644 --- a/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test +++ b/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test @@ -29,8 +29,8 @@ Requiring a replaced package in a version, that is not provided by the replacing "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/root-alias-change-with-circular-dep.test b/tests/Composer/Test/Fixtures/installer/root-alias-change-with-circular-dep.test index bcbbcc81f29a..009f1c55da7d 100644 --- a/tests/Composer/Test/Fixtures/installer/root-alias-change-with-circular-dep.test +++ b/tests/Composer/Test/Fixtures/installer/root-alias-change-with-circular-dep.test @@ -41,11 +41,11 @@ This also checks that an implicit stabilityFlag is added for the root package, i "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test b/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test index 172288f75756..03945bd476eb 100644 --- a/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test +++ b/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test @@ -24,11 +24,11 @@ Newly defined root alias does not get loaded if package is loaded from lock file "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- [ diff --git a/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test b/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test index 32127b612835..a3a75e79f974 100644 --- a/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test +++ b/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test @@ -45,7 +45,7 @@ The locked version will not get overwritten by an install but fails on invalid p "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/solver-problems.test b/tests/Composer/Test/Fixtures/installer/solver-problems.test index 95f7f6e2bff3..65a28cc028da 100644 --- a/tests/Composer/Test/Fixtures/installer/solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/solver-problems.test @@ -86,11 +86,11 @@ Test the error output of solver problems. "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- diff --git a/tests/Composer/Test/Fixtures/installer/suggest-prod.test b/tests/Composer/Test/Fixtures/installer/suggest-prod.test index ed202350486f..b0e22b17754f 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-prod.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-prod.test @@ -22,11 +22,11 @@ Suggestions are not displayed for when not updating the lock file "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- install diff --git a/tests/Composer/Test/Fixtures/installer/update-alias-lock.test b/tests/Composer/Test/Fixtures/installer/update-alias-lock.test index 5aceda8ea942..96001fa15ccd 100644 --- a/tests/Composer/Test/Fixtures/installer/update-alias-lock.test +++ b/tests/Composer/Test/Fixtures/installer/update-alias-lock.test @@ -52,11 +52,11 @@ update "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Upgrading a/a (dev-master 1234 => dev-master master) diff --git a/tests/Composer/Test/Fixtures/installer/update-alias-lock2.test b/tests/Composer/Test/Fixtures/installer/update-alias-lock2.test index b06ce3119225..bd38a6668a96 100644 --- a/tests/Composer/Test/Fixtures/installer/update-alias-lock2.test +++ b/tests/Composer/Test/Fixtures/installer/update-alias-lock2.test @@ -37,7 +37,7 @@ Updating an aliased package where the old alias matches the new package should n "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } @@ -64,11 +64,11 @@ update "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-INSTALLED-- [ diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-locked-require.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-locked-require.test index 317f2396ddea..a4c40e3afcf9 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-locked-require.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-locked-require.test @@ -40,11 +40,11 @@ Update with a package allow list only updates those packages if they are not pre "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update allowed/pkg dependency/pkg diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test index 889555ed0acb..fc3aa0ecf396 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test @@ -49,11 +49,11 @@ Updating transitive dependencies only updates what is really required when a min "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update allowed/pkg --with-all-dependencies --minimal-changes diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-all-dependencies.test index 5c796181a685..9dabdd134e1b 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-all-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-all-dependencies.test @@ -51,11 +51,11 @@ Update with a package allow list pattern and all-dependencies flag updates packa "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update allowed/pkg-* --with-all-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-root-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-root-dependencies.test index 5cabcbb4a910..221769085dc4 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-root-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns-with-root-dependencies.test @@ -63,11 +63,11 @@ Update with a package allow list only updates those packages and their dependenc "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update allowed/pkg-* foobar --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns.test index fde1c9eab9f1..a4314d6cd2ff 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-patterns.test @@ -54,11 +54,11 @@ Update with a package allow list only updates those corresponding to the pattern "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update vendor/Test* exact/test-package notexact/Test all/* no/reg.?xp diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-reads-lock.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-reads-lock.test index ef7ca56c0c1e..68ae0a53b86c 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-reads-lock.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-reads-lock.test @@ -31,7 +31,7 @@ Limited update takes rules from lock if available, and not from the installed re "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-removes-unused.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-removes-unused.test index 7f2299566ea0..f59c149962a2 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-removes-unused.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-removes-unused.test @@ -35,11 +35,11 @@ Update with a package allow list removes unused packages "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update --with-dependencies allowed/pkg diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-require-new-replace.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-require-new-replace.test index 163471fa6d66..13e99ad58e60 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-require-new-replace.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-require-new-replace.test @@ -31,11 +31,11 @@ A partial update for a new package yet to be installed should remove another dep "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update new/pkg diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-warns-non-existing-patterns.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-warns-non-existing-patterns.test index 4d07bc245c31..9450c29ae57c 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-warns-non-existing-patterns.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-warns-non-existing-patterns.test @@ -33,11 +33,11 @@ Verify that partial updates warn about using patterns in the argument which have "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update b/b foo/bar baz/* --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-alias.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-alias.test index 5e5f047779f2..182207786be9 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-alias.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-alias.test @@ -49,11 +49,11 @@ Verify that a partial update with deps correctly keeps track of all aliases. } ], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update new/pkg --with-all-dependencies @@ -87,8 +87,8 @@ update new/pkg --with-all-dependencies }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Marking current/dep2 (1.0.x-dev) as uninstalled, alias of current/dep2 (dev-foo) diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-new-requirement.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-new-requirement.test index ff1257498fed..0b4f53f57f8b 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-new-requirement.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-new-requirement.test @@ -36,11 +36,11 @@ When partially updating a package to a newer version and the new version has a n "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update root/pkg2 --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace-mutual.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace-mutual.test index a850748c8d2c..b110a178c399 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace-mutual.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace-mutual.test @@ -36,11 +36,11 @@ Require a new package in the composer.json and updating with its name as an argu "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update new/pkg --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace.test index 8bb676e760ae..0f544953796b 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new-replace.test @@ -31,11 +31,11 @@ Require a new package in the composer.json and updating with its name as an argu "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update new/pkg --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new.test index 24eb95538e68..bd0146596db6 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-with-dependencies-require-new.test @@ -35,11 +35,11 @@ Require a new package in the composer.json and updating with its name as an argu "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update new/pkg --with-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list.test b/tests/Composer/Test/Fixtures/installer/update-allow-list.test index 59337544234b..6f7f29dc2505 100644 --- a/tests/Composer/Test/Fixtures/installer/update-allow-list.test +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list.test @@ -45,11 +45,11 @@ Update with a package allow list only updates those packages listed as command a "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update allowed/pkg diff --git a/tests/Composer/Test/Fixtures/installer/update-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-changes-url.test index 15600aefeaa7..c1fe0363a689 100644 --- a/tests/Composer/Test/Fixtures/installer/update-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-changes-url.test @@ -159,8 +159,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-LOCK-- { @@ -226,8 +226,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update a/a b/b d/d g/g diff --git a/tests/Composer/Test/Fixtures/installer/update-dev-packages-updates-repo-url.test b/tests/Composer/Test/Fixtures/installer/update-dev-packages-updates-repo-url.test index 0a918b183c95..77e95a66a216 100644 --- a/tests/Composer/Test/Fixtures/installer/update-dev-packages-updates-repo-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-dev-packages-updates-repo-url.test @@ -59,11 +59,11 @@ Updating dev packages where no reference change happened triggers a repo url cha "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update @@ -86,11 +86,11 @@ update "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Upgrading a/a (dev-master oldmaster => dev-master newmaster) diff --git a/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test index d002d2c94b1d..78a2d60d30af 100644 --- a/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test @@ -147,8 +147,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-LOCK-- { @@ -204,8 +204,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update mirrors diff --git a/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test b/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test index 0b47f4b5aa12..d057b32e08de 100644 --- a/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test +++ b/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test @@ -38,11 +38,11 @@ Update mirrors with a new root require which is not yet in lock should simply ig ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update mirrors @@ -56,10 +56,10 @@ update mirrors ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-no-dev-still-resolves-dev.test b/tests/Composer/Test/Fixtures/installer/update-no-dev-still-resolves-dev.test index 9844616f412b..16a40e13be6b 100644 --- a/tests/Composer/Test/Fixtures/installer/update-no-dev-still-resolves-dev.test +++ b/tests/Composer/Test/Fixtures/installer/update-no-dev-still-resolves-dev.test @@ -54,8 +54,8 @@ Updates with --no-dev but we still end up with a complete lock file including de }, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update --no-dev diff --git a/tests/Composer/Test/Fixtures/installer/update-no-install.test b/tests/Composer/Test/Fixtures/installer/update-no-install.test index e1b6f4954938..1fe7c0af09c8 100644 --- a/tests/Composer/Test/Fixtures/installer/update-no-install.test +++ b/tests/Composer/Test/Fixtures/installer/update-no-install.test @@ -47,11 +47,11 @@ update --no-install ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-INSTALLED-- diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test index c98d34dc6efe..5853fd782622 100644 --- a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test @@ -28,11 +28,11 @@ Update package which is in lock file but not in remote repo at all should show t "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update main/dep --with-all-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test index c5582a0e3a55..3816ff647e37 100644 --- a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test @@ -30,11 +30,11 @@ Update package which is in lock file but not remote repo due to min-stability sh "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update main/dep --with-all-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test index 6ff3f6f5b7d8..9bafe3a7074a 100644 --- a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test @@ -29,11 +29,11 @@ Update package which is in lock file but not in remote repo in the correct versi "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update main/dep --with-all-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test index 263282e78a57..be20fd2000ce 100644 --- a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test @@ -33,11 +33,11 @@ Update package which is in lower prio repo but not main repo due to min-stabilit "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update main/dep --with-all-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test b/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test index c1e74635630f..3558a785e750 100644 --- a/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test +++ b/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test @@ -35,11 +35,11 @@ Converting from one VCS type to another (including an URL change) should update "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update mirrors @@ -55,11 +55,11 @@ update mirrors "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Upgrading a/a (1.0.0 old-hg-ref => 1.0.0 new-git-ref) diff --git a/tests/Composer/Test/Fixtures/installer/update-removes-unused-locked-dep.test b/tests/Composer/Test/Fixtures/installer/update-removes-unused-locked-dep.test index a47afb7fff54..f769413570fa 100644 --- a/tests/Composer/Test/Fixtures/installer/update-removes-unused-locked-dep.test +++ b/tests/Composer/Test/Fixtures/installer/update-removes-unused-locked-dep.test @@ -24,11 +24,11 @@ A composer update should remove unused locked dependencies from the lock file an "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --INSTALLED-- [ @@ -46,11 +46,11 @@ update "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-OUTPUT-- Loading composer repositories with package information diff --git a/tests/Composer/Test/Fixtures/installer/update-syncs-outdated.test b/tests/Composer/Test/Fixtures/installer/update-syncs-outdated.test index acef95924122..17120249270a 100644 --- a/tests/Composer/Test/Fixtures/installer/update-syncs-outdated.test +++ b/tests/Composer/Test/Fixtures/installer/update-syncs-outdated.test @@ -34,11 +34,11 @@ Update updates the outdated state of packages "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update diff --git a/tests/Composer/Test/Fixtures/installer/update-to-empty-from-blank.test b/tests/Composer/Test/Fixtures/installer/update-to-empty-from-blank.test index e9892c3b6329..5f0d0aed1046 100644 --- a/tests/Composer/Test/Fixtures/installer/update-to-empty-from-blank.test +++ b/tests/Composer/Test/Fixtures/installer/update-to-empty-from-blank.test @@ -11,10 +11,10 @@ update "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-to-empty-from-locked.test b/tests/Composer/Test/Fixtures/installer/update-to-empty-from-locked.test index 1a2227cec603..29e035ba0ead 100644 --- a/tests/Composer/Test/Fixtures/installer/update-to-empty-from-locked.test +++ b/tests/Composer/Test/Fixtures/installer/update-to-empty-from-locked.test @@ -25,11 +25,11 @@ Update to a state without dependency works well from locked with dependency "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update @@ -39,11 +39,11 @@ update "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Removing a/a (dev-master 1234) diff --git a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test index e9fc88ef9777..e63d3daab139 100644 --- a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test @@ -37,11 +37,11 @@ When `--with-all-dependencies` is used, Composer should update the dependencies "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --RUN-- update b/b --with-all-dependencies diff --git a/tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test b/tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test index 545f92fe9a0a..952c9534a9d9 100644 --- a/tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test +++ b/tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test @@ -20,7 +20,7 @@ Installing locked dev packages should remove old dependencies "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false } diff --git a/tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test b/tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test index c84ab3525f4a..a915cd63fb13 100644 --- a/tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test +++ b/tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test @@ -61,8 +61,8 @@ update "stability-flags": {"a/a":20}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {} } --EXPECT-- Upgrading a/a (dev-master oldref => dev-master newref) diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index c81257c223f5..64a11cd4988f 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -444,6 +444,11 @@ private function doTestIntegration(string $file, string $message, ?string $condi self::assertEquals($expectResult, $result, $output . stream_get_contents($appOutput)); if ($expectLock && isset($actualLock)) { unset($actualLock['hash'], $actualLock['content-hash'], $actualLock['_readme'], $actualLock['plugin-api-version']); + foreach (['stability-flags', 'platform', 'platform-dev'] as $key) { + if ($expectLock[$key] === []) { + $expectLock[$key] = new \stdClass; + } + } self::assertEquals($expectLock, $actualLock); } diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index fd52642ffe01..8bc4c598b2d5 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -113,9 +113,9 @@ public function testSetLockData(): void 'packages-dev' => [], 'aliases' => [], 'minimum-stability' => 'dev', - 'stability-flags' => [], - 'platform' => [], - 'platform-dev' => [], + 'stability-flags' => new \stdClass, + 'platform' => new \stdClass, + 'platform-dev' => new \stdClass, 'platform-overrides' => ['foo/bar' => '1.0'], 'prefer-stable' => false, 'prefer-lowest' => false, From dea55ec139a5e6748ba55f68ac7c61ebfd637bcf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 18 Sep 2024 16:57:22 +0200 Subject: [PATCH 183/257] Respect sort-packages option when adding plugins to the allow-plugins list, fixes #11348 --- src/Composer/Plugin/PluginManager.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index c22364ad2814..7cd08060e593 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -750,7 +750,15 @@ public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $opt // persist answer in composer.json if it wasn't simply discarded if ($answer === 'y' || $answer === 'n') { - $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins.'.$package, $allow); + $allowPlugins = $composer->getConfig()->get('allow-plugins'); + if (is_array($allowPlugins)) { + $allowPlugins[$package] = $allow; + if ($composer->getConfig()->get('sort-packages')) { + ksort($allowPlugins); + } + $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins', $allowPlugins); + $composer->getConfig()->merge(['config' => ['allow-plugins' => $allowPlugins]]); + } } return $allow; From 8ae6fa1205897dcfc57867542c94874cc4fca8bd Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:34:35 +0200 Subject: [PATCH 184/257] PHP 8.4 | Remove use of `E_STRICT` (#12116) The `E_STRICT` constant is deprecated as of PHP 8.4 and will be removed in PHP 9.0 (commit finally went in today). The error level hasn't been in use since PHP 8.0 anyway and was only barely still used in PHP 7.x, so removing the exclusion from the `error_reporting()` setting in these script shouldn't really make any difference in practice. Ref: * https://wiki.php.net/rfc/deprecations_php_8_4#remove_e_strict_error_level_and_deprecate_e_strict_constant Co-authored-by: jrfnl --- src/Composer/Util/ErrorHandler.php | 2 +- src/Composer/Util/Silencer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index fbd92843d2ab..d06b57718987 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -78,7 +78,7 @@ public static function handle(int $level, string $message, string $file, int $li public static function register(?IOInterface $io = null): void { set_error_handler([__CLASS__, 'handle']); - error_reporting(E_ALL | E_STRICT); + error_reporting(E_ALL); self::$io = $io; } } diff --git a/src/Composer/Util/Silencer.php b/src/Composer/Util/Silencer.php index f2b9f73fc9ad..6dd0efb34987 100644 --- a/src/Composer/Util/Silencer.php +++ b/src/Composer/Util/Silencer.php @@ -33,7 +33,7 @@ class Silencer public static function suppress(?int $mask = null): int { if (!isset($mask)) { - $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT; + $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED; } $old = error_reporting(); self::$stack[] = $old; From 3e7b3b26df56f6217a0dfb24004d5352c079cf84 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 19 Sep 2024 10:23:54 +0200 Subject: [PATCH 185/257] Check connectivity to custom composer repos in diagnose command --- src/Composer/Command/DiagnoseCommand.php | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index e95ca8eaab67..cbd4a7c600f1 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -117,6 +117,25 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->write('Checking https connectivity to packagist: ', false); $this->outputResult($this->checkHttp('https', $config)); + foreach ($config->getRepositories() as $repo) { + if (($repo['type'] ?? null) === 'composer' && isset($repo['url'])) { + $composerRepo = new ComposerRepository($repo, $this->getIO(), $config, $this->httpDownloader); + $reflMethod = new \ReflectionMethod($composerRepo, 'getPackagesJsonUrl'); + if (PHP_VERSION_ID < 80100) { + $reflMethod->setAccessible(true); + } + $url = $reflMethod->invoke($composerRepo); + if (!str_starts_with($url, 'http')) { + continue; + } + if (str_starts_with($url, 'https://repo.packagist.org')) { + continue; + } + $io->write('Checking connectivity to ' . $repo['url'].': ', false); + $this->outputResult($this->checkComposerRepo($url, $config)); + } + } + $proxyManager = ProxyManager::getInstance(); $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https']; try { @@ -310,6 +329,45 @@ private function checkHttp(string $proto, Config $config) return true; } + /** + * @return string|string[]|true + */ + private function checkComposerRepo(string $url, Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $result = []; + if (str_starts_with($url, 'https://') && $config->get('disable-tls') === true) { + $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; + } + + try { + $this->httpDownloader->get($url); + } catch (TransportException $e) { + $hints = HttpDownloader::getExceptionHints($e); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $result[] = $hint; + } + } + + $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; + } + + if (isset($tlsWarning)) { + $result[] = $tlsWarning; + } + + if (count($result) > 0) { + return $result; + } + + return true; + } + /** * @return string|\Exception */ From a03331bd219223ce15ddcaae480370d1163ec538 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 19 Sep 2024 11:40:57 +0200 Subject: [PATCH 186/257] Add --strict-ambiguous to dump-autoload command (#12119) Fixes #6221 --- doc/03-cli.md | 2 ++ src/Composer/Command/DumpAutoloadCommand.php | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 8c1c56e16aec..c99624db3450 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -990,6 +990,8 @@ performance. Multiple requirements can be ignored via wildcard. * **--strict-psr:** Return a failed exit code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work. +* **--strict-ambiguous:** Return a failed exit code (2) if the same class is found + in multiple files. Requires --optimize to work. ## clear-cache / clearcache / cc diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index c30fae7a82b2..6e6b56a5ac61 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -43,6 +43,7 @@ protected function configure() new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.'), + new InputOption('strict-ambiguous', null, InputOption::VALUE_NONE, 'Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.'), ]) ->setHelp( <<getOption('strict-psr') && !$optimize && !$authoritative) { throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); } + if ($input->getOption('strict-ambiguous') && !$optimize && !$authoritative) { + throw new \InvalidArgumentException('--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); + } if ($authoritative) { $this->getIO()->write('Generating optimized autoload files (authoritative)'); @@ -124,6 +128,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } + if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses()) > 0) { + return 2; + } + return 0; } } From a5d0d73e00ede9f0f46644123c8cf6881d81c7ec Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 19 Sep 2024 17:06:49 +0200 Subject: [PATCH 187/257] Show warnings for each missing platform package in create-project, fixes #10736 (#12120) --- src/Composer/Package/Version/VersionSelector.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index f1180c8e3f3d..7c0c61a3116d 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -121,6 +121,7 @@ public function findBestCandidate(string $packageName, ?string $targetPackageVer foreach ($candidates as $pkg) { $reqs = $pkg->getRequires(); + $skip = false; foreach ($reqs as $name => $link) { if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) { continue; @@ -151,8 +152,8 @@ public function findBestCandidate(string $packageName, ?string $targetPackageVer $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]); $alreadySeenNames[$pkg->getName()] = true; if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) { - $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName()]); - $alreadyWarnedNames[$pkg->getName()] = true; + $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()]); + $alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()] = true; $latest = $isLatestVersion ? "'s latest version" : ''; $io->writeError( 'Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.', @@ -162,7 +163,11 @@ public function findBestCandidate(string $packageName, ?string $targetPackageVer } // skip candidate - continue 2; + $skip = true; + } + + if ($skip) { + continue; } $package = $pkg; From 58905ffe4efb5704f463a56f7339ec8f4e82cdf9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 19 Sep 2024 17:07:28 +0200 Subject: [PATCH 188/257] Validate licenses passed into init command (#12115) Fixes #10838 --- src/Composer/Command/InitCommand.php | 5 +++++ tests/Composer/Test/Command/InitCommandTest.php | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 4f1398726ca0..0829eb09d090 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -21,6 +21,7 @@ use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; +use Composer\Spdx\SpdxLicenses; use Composer\Util\Filesystem; use Composer\Util\Silencer; use Symfony\Component\Console\Input\ArrayInput; @@ -398,6 +399,10 @@ static function ($value) use ($minimumStability) { 'License ['.$license.']: ', $license ); + $spdx = new SpdxLicenses(); + if (!$spdx->validate($license)) { + throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.'); + } $input->setOption('license', $license); $io->writeError(['', 'Define your dependencies.', '']); diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 41860f628051..67b1fd169627 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -711,7 +711,7 @@ public function testInteractiveRun(): void 'Mr. Test ', // Author 'stable', // Minimum stability 'library', // Type - 'Custom License', // License + 'AGPL-3.0-only', // License 'no', // Define dependencies 'no', // Define dev dependencies 'n', // Add PSR-4 autoload mapping @@ -726,7 +726,7 @@ public function testInteractiveRun(): void 'name' => 'vendor/pkg', 'description' => 'my desciption', 'type' => 'library', - 'license' => 'Custom License', + 'license' => 'AGPL-3.0-only', 'authors' => [['name' => 'Mr. Test', 'email' => 'test@example.org']], 'minimum-stability' => 'stable', 'require' => [], From 3a2a18175da72bd4af128e8fbd174961b39da019 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 21 Sep 2024 13:37:55 +0200 Subject: [PATCH 189/257] Add ability to reinstall packages by type (#12114) Fixes #11364 --- src/Composer/Command/CompletionTrait.php | 30 +++++++++++++ src/Composer/Command/ReinstallCommand.php | 34 +++++++++++---- .../Test/Command/ReinstallCommandTest.php | 43 ++++++++++++++----- 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/Composer/Command/CompletionTrait.php b/src/Composer/Command/CompletionTrait.php index 89c6b68e55b0..444d69554f77 100644 --- a/src/Composer/Command/CompletionTrait.php +++ b/src/Composer/Command/CompletionTrait.php @@ -117,6 +117,36 @@ private function suggestInstalledPackage(bool $includeRootPackage = true, bool $ }; } + /** + * Suggest package names from installed. + */ + private function suggestInstalledPackageTypes(bool $includeRootPackage = true): \Closure + { + return function (CompletionInput $input) use ($includeRootPackage): array { + $composer = $this->requireComposer(); + $installedRepos = []; + + if ($includeRootPackage) { + $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); + } + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = $locker->getLockedRepository(true); + } else { + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $installedRepo = new InstalledRepository($installedRepos); + + return array_values(array_unique( + array_map(static function (PackageInterface $package) { + return $package->getType(); + }, $installedRepo->getPackages()) + )); + }; + } + /** * Suggest package names available on all configured repositories. */ diff --git a/src/Composer/Command/ReinstallCommand.php b/src/Composer/Command/ReinstallCommand.php index 446d9eec14b3..cb7882a9cf49 100644 --- a/src/Composer/Command/ReinstallCommand.php +++ b/src/Composer/Command/ReinstallCommand.php @@ -51,7 +51,8 @@ protected function configure(): void new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)), + new InputOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter packages to reinstall by type(s)', null, $this->suggestInstalledPackageTypes(false)), + new InputArgument('packages', InputArgument::IS_ARRAY, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)), ]) ->setHelp( <<getRepositoryManager()->getLocalRepository(); $packagesToReinstall = []; $packageNamesToReinstall = []; - foreach ($input->getArgument('packages') as $pattern) { - $patternRegexp = BasePackage::packageNameToRegexp($pattern); - $matched = false; + if (\count($input->getOption('type')) > 0) { + if (\count($input->getArgument('packages')) > 0) { + throw new \InvalidArgumentException('You cannot specify package names and filter by type at the same time.'); + } foreach ($localRepo->getCanonicalPackages() as $package) { - if (Preg::isMatch($patternRegexp, $package->getName())) { - $matched = true; + if (in_array($package->getType(), $input->getOption('type'), true)) { $packagesToReinstall[] = $package; $packageNamesToReinstall[] = $package->getName(); } } + } else { + if (\count($input->getArgument('packages')) === 0) { + throw new \InvalidArgumentException('You must pass one or more package names to be reinstalled.'); + } + foreach ($input->getArgument('packages') as $pattern) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + $matched = false; + foreach ($localRepo->getCanonicalPackages() as $package) { + if (Preg::isMatch($patternRegexp, $package->getName())) { + $matched = true; + $packagesToReinstall[] = $package; + $packageNamesToReinstall[] = $package->getName(); + } + } - if (!$matched) { - $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); + if (!$matched) { + $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); + } } } - if (!$packagesToReinstall) { + if (0 === \count($packagesToReinstall)) { $io->writeError('Found no packages to reinstall, aborting.'); return 1; diff --git a/tests/Composer/Test/Command/ReinstallCommandTest.php b/tests/Composer/Test/Command/ReinstallCommandTest.php index 1e81a7790d4d..cc21ce54590e 100644 --- a/tests/Composer/Test/Command/ReinstallCommandTest.php +++ b/tests/Composer/Test/Command/ReinstallCommandTest.php @@ -19,49 +19,70 @@ class ReinstallCommandTest extends TestCase { /** * @dataProvider caseProvider - * @param array $packages + * @param array $options * @param string $expected */ - public function testReinstallCommand(array $packages, string $expected): void + public function testReinstallCommand(array $options, string $expected): void { $this->initTempComposer([ 'require' => [ 'root/req' => '1.*', - 'root/anotherreq' => '2.*' + ], + 'require-dev' => [ + 'root/anotherreq' => '2.*', + 'root/anotherreq2' => '2.*', + 'root/lala' => '2.*', ] ]); $rootReqPackage = self::getPackage('root/req'); $anotherReqPackage = self::getPackage('root/anotherreq'); + $anotherReqPackage2 = self::getPackage('root/anotherreq2'); + $anotherReqPackage3 = self::getPackage('root/lala'); $rootReqPackage->setType('metapackage'); $anotherReqPackage->setType('metapackage'); + $anotherReqPackage2->setType('metapackage'); + $anotherReqPackage3->setType('metapackage'); - $this->createComposerLock([$rootReqPackage], [$anotherReqPackage]); - $this->createInstalledJson([$rootReqPackage], [$anotherReqPackage]); + $this->createComposerLock([$rootReqPackage], [$anotherReqPackage, $anotherReqPackage2, $anotherReqPackage3]); + $this->createInstalledJson([$rootReqPackage], [$anotherReqPackage, $anotherReqPackage2, $anotherReqPackage3]); $appTester = $this->getApplicationTester(); - $appTester->run([ + $appTester->run(array_merge([ 'command' => 'reinstall', '--no-progress' => true, '--no-plugins' => true, - 'packages' => $packages - ]); + ], $options)); self::assertSame($expected, trim($appTester->getDisplay(true))); } public function caseProvider(): Generator { - yield 'reinstall a package' => [ - ['root/req', 'root/anotherreq'], + yield 'reinstall a package by name' => [ + ['packages' => ['root/req', 'root/anotherreq*']], +'- Removing root/req (1.0.0) + - Removing root/anotherreq2 (1.0.0) + - Removing root/anotherreq (1.0.0) + - Installing root/anotherreq (1.0.0) + - Installing root/anotherreq2 (1.0.0) + - Installing root/req (1.0.0)' + ]; + + yield 'reinstall packages by type' => [ + ['--type' => ['metapackage']], '- Removing root/req (1.0.0) + - Removing root/lala (1.0.0) + - Removing root/anotherreq2 (1.0.0) - Removing root/anotherreq (1.0.0) - Installing root/anotherreq (1.0.0) + - Installing root/anotherreq2 (1.0.0) + - Installing root/lala (1.0.0) - Installing root/req (1.0.0)' ]; yield 'reinstall a package that is not installed' => [ - ['root/unknownreq'], + ['packages' => ['root/unknownreq']], 'Pattern "root/unknownreq" does not match any currently installed packages. Found no packages to reinstall, aborting.' ]; From 6b81140f81a4483b814b62fe37c5a81a3fbcfdec Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 21 Sep 2024 13:53:33 +0200 Subject: [PATCH 190/257] Fix many PHPStan errors in DependencyResolver namespace (#12121) --- composer.lock | 26 +- phpstan/baseline.neon | 419 +----------------- src/Composer/Command/ShowCommand.php | 8 +- src/Composer/DependencyResolver/Decisions.php | 12 +- .../DependencyResolver/DefaultPolicy.php | 21 +- .../DependencyResolver/GenericRule.php | 3 + .../DependencyResolver/MultiConflictRule.php | 9 +- .../DependencyResolver/PolicyInterface.php | 4 +- .../DependencyResolver/PoolBuilder.php | 22 +- .../DependencyResolver/PoolOptimizer.php | 6 +- src/Composer/DependencyResolver/Problem.php | 47 +- src/Composer/DependencyResolver/Request.php | 6 +- src/Composer/DependencyResolver/Rule.php | 39 +- .../DependencyResolver/Rule2Literals.php | 2 +- src/Composer/DependencyResolver/RuleSet.php | 2 +- .../DependencyResolver/RuleSetGenerator.php | 14 +- .../DependencyResolver/RuleSetIterator.php | 2 +- src/Composer/DependencyResolver/Solver.php | 86 ++-- .../SolverProblemsException.php | 12 +- .../DependencyResolver/Transaction.php | 13 +- .../DependencyResolver/DefaultPolicyTest.php | 1 + 21 files changed, 179 insertions(+), 575 deletions(-) diff --git a/composer.lock b/composer.lock index 55d1cf38e32f..4595f9519280 100644 --- a/composer.lock +++ b/composer.lock @@ -305,24 +305,24 @@ }, { "name": "composer/semver", - "version": "3.4.2", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -366,7 +366,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.2" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -382,7 +382,7 @@ "type": "tidelift" } ], - "time": "2024-07-12T11:35:52+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/spdx-licenses", @@ -2020,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.3", + "version": "1.12.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" + "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa517cb918591b93acc9b95c0bebdcd0e4538bd", + "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-09-09T08:10:35+00:00" + "time": "2024-09-19T07:58:01+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 7f26fa595473..dc754784e023 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -670,16 +670,6 @@ parameters: count: 1 path: ../src/Composer/Command/ShowCommand.php - - - message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" - count: 1 - path: ../src/Composer/Command/ShowCommand.php - - - - message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" - count: 1 - path: ../src/Composer/Command/ShowCommand.php - - message: "#^Only booleans are allowed in &&, string given on the left side\\.$#" count: 1 @@ -800,11 +790,6 @@ parameters: count: 2 path: ../src/Composer/Command/ShowCommand.php - - - message: "#^Parameter \\#2 \\$literals of method Composer\\\\DependencyResolver\\\\DefaultPolicy\\:\\:selectPreferredPackages\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Command/ShowCommand.php - - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, array\\|string given\\.$#" count: 1 @@ -905,19 +890,9 @@ parameters: count: 1 path: ../src/Composer/Command/UpdateCommand.php - - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 1 - path: ../src/Composer/Command/UpdateCommand.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 3 - path: ../src/Composer/Command/UpdateCommand.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 + count: 2 path: ../src/Composer/Command/UpdateCommand.php - @@ -1095,96 +1070,11 @@ parameters: count: 1 path: ../src/Composer/Console/GithubActionError.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Decisions.php - - - - message: "#^Method Composer\\\\DependencyResolver\\\\Decisions\\:\\:key\\(\\) should return int\\|null but returns int\\|string\\|null\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Decisions.php - - - - message: "#^Only booleans are allowed in a ternary operator condition, Composer\\\\DependencyResolver\\\\Pool\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Decisions.php - - message: "#^Return type \\(array\\{int, Composer\\\\DependencyResolver\\\\Rule\\}\\|false\\) of method Composer\\\\DependencyResolver\\\\Decisions\\:\\:current\\(\\) should be covariant with return type \\(array\\{int, Composer\\\\DependencyResolver\\\\Rule\\}\\) of method Iterator\\\\>\\:\\:current\\(\\)$#" count: 1 path: ../src/Composer/DependencyResolver/Decisions.php - - - message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/DefaultPolicy.php - - - - message: "#^Cannot access offset 'hash' on array\\|false\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/GenericRule.php - - - - message: "#^Cannot access offset 'hash' on array\\|false\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/MultiConflictRule.php - - - - message: "#^Only booleans are allowed in \\|\\|, mixed given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Pool.php - - - - message: "#^Cannot call method getPackages\\(\\) on Composer\\\\Repository\\\\LockArrayRepository\\|null\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\EventDispatcher\\\\EventDispatcher\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Parameter \\#4 \\$index of method Composer\\\\DependencyResolver\\\\PoolBuilder\\:\\:removeLoadedPackage\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/PoolBuilder.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/PoolOptimizer.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/PoolOptimizer.php - - - - message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" - count: 4 - path: ../src/Composer/DependencyResolver/PoolOptimizer.php - - message: "#^Cannot call method getPrettyString\\(\\) on array\\\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|string\\.$#" count: 1 @@ -1192,11 +1082,6 @@ parameters: - message: "#^Cannot call method getRepoName\\(\\) on Composer\\\\Repository\\\\RepositoryInterface\\|null\\.$#" - count: 3 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Cannot call method getRepository\\(\\) on Composer\\\\Package\\\\PackageInterface\\|false\\.$#" count: 1 path: ../src/Composer/DependencyResolver/Problem.php @@ -1205,311 +1090,11 @@ parameters: count: 1 path: ../src/Composer/DependencyResolver/Problem.php - - - message: "#^Only booleans are allowed in &&, Composer\\\\DependencyResolver\\\\Pool\\|null given on the left side\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\|null given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in a ternary operator condition, Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\Package\\\\BasePackage\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 5 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in an if condition, array\\\\> given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\> given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Problem.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\Repository\\\\LockArrayRepository\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Request.php - - - - message: "#^Foreach overwrites \\$literal with its value variable\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - message: "#^Method Composer\\\\DependencyResolver\\\\Rule\\:\\:getReason\\(\\) should return 2\\|3\\|6\\|7\\|10\\|12\\|13\\|14 but returns int\\<0, 255\\>\\.$#" count: 1 path: ../src/Composer/DependencyResolver/Rule.php - - - message: "#^Only booleans are allowed in &&, array\\ given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\Repository\\\\LockArrayRepository\\|null given\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Parameter \\#1 \\$literal of method Composer\\\\DependencyResolver\\\\Pool\\:\\:literalToPackage\\(\\) expects int, int\\|null given\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Parameter \\#1 \\$packages of static method Composer\\\\DependencyResolver\\\\Problem\\:\\:getPackageList\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Rule.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\DependencyResolver\\\\Pool\\|null given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSet.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\DependencyResolver\\\\Request\\|null given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSet.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\Repository\\\\RepositorySet\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSet.php - - - - message: "#^Only booleans are allowed in a negated boolean, Composer\\\\DependencyResolver\\\\Rule\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSetGenerator.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSetGenerator.php - - - - message: "#^Parameter \\#3 \\$reasonData of class Composer\\\\DependencyResolver\\\\GenericRule constructor expects array\\{package\\: Composer\\\\Package\\\\BasePackage\\}\\|array\\{packageName\\: string, constraint\\: Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\}\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|string, array\\{package\\: Composer\\\\Package\\\\BasePackage\\}\\|array\\{packageName\\: string, constraint\\: Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\}\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|string\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSetGenerator.php - - - - message: "#^Parameter \\#4 \\$reasonData of class Composer\\\\DependencyResolver\\\\Rule2Literals constructor expects array\\{package\\: Composer\\\\Package\\\\BasePackage\\}\\|array\\{packageName\\: string, constraint\\: Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\}\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|string, array\\{package\\: Composer\\\\Package\\\\BasePackage\\}\\|array\\{packageName\\: string, constraint\\: Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\}\\|Composer\\\\Package\\\\BasePackage\\|Composer\\\\Package\\\\Link\\|int\\|string\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSetGenerator.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSetGenerator.php - - - - message: "#^Return type \\(\\-1\\|0\\|1\\|4\\) of method Composer\\\\DependencyResolver\\\\RuleSetIterator\\:\\:key\\(\\) should be covariant with return type \\(0\\|1\\|4\\) of method Iterator\\\\:\\:key\\(\\)$#" - count: 1 - path: ../src/Composer/DependencyResolver/RuleSetIterator.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Foreach overwrites \\$literal with its value variable\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in &&, Composer\\\\DependencyResolver\\\\Rule\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in &&, int\\<0, max\\> given on the right side\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in &&, mixed given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in a negated boolean, int given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in an if condition, Composer\\\\DependencyResolver\\\\Rule\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in an if condition, int given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\> given\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Parameter \\#1 \\$literal of method Composer\\\\DependencyResolver\\\\Decisions\\:\\:decide\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Parameter \\#1 \\$literals of class Composer\\\\DependencyResolver\\\\GenericRule constructor expects list\\, non\\-empty\\-list\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Parameter \\#1 \\$number of function abs expects int, int\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Parameter \\#1 \\$rule of method Composer\\\\DependencyResolver\\\\Problem\\:\\:addRule\\(\\) expects Composer\\\\DependencyResolver\\\\Rule, Composer\\\\DependencyResolver\\\\Rule\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Parameter \\#2 \\$literal of method Composer\\\\DependencyResolver\\\\Solver\\:\\:setPropagateLearn\\(\\) expects int\\|string, int\\|null given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Parameter \\#2 \\$presentMap of class Composer\\\\DependencyResolver\\\\LockTransaction constructor expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Solver.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Only booleans are allowed in &&, int\\<0, max\\>\\|false given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Only booleans are allowed in &&, int\\<0, max\\>\\|false given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Only booleans are allowed in an if condition, int\\<0, max\\>\\|false given\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Only booleans are allowed in \\|\\|, int\\<0, max\\>\\|false given on the left side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Only booleans are allowed in \\|\\|, int\\<0, max\\>\\|false given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/SolverProblemsException.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Transaction.php - - - - message: "#^Only booleans are allowed in &&, array given on the right side\\.$#" - count: 1 - path: ../src/Composer/DependencyResolver/Transaction.php - - - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, max\\> given\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Transaction.php - - - - message: "#^Only booleans are allowed in \\|\\|, int\\<0, max\\> given on the right side\\.$#" - count: 2 - path: ../src/Composer/DependencyResolver/Transaction.php - - message: "#^Call to function array_search\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -2857,7 +2442,7 @@ parameters: - message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#" - count: 2 + count: 1 path: ../src/Composer/Repository/ComposerRepository.php - diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index b7b95705713e..14c9a4c32165 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -817,7 +817,8 @@ protected function getPackage(InstalledRepository $installedRepo, RepositoryInte $pool = $repositorySet->createPoolForPackage($name); } $matches = $pool->whatProvides($name, $constraint); - foreach ($matches as $index => $package) { + $literals = []; + foreach ($matches as $package) { // avoid showing the 9999999-dev alias if the default branch has no branch-alias set if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); @@ -829,11 +830,12 @@ protected function getPackage(InstalledRepository $installedRepo, RepositoryInte } $versions[$package->getPrettyVersion()] = $package->getVersion(); - $matches[$index] = $package->getId(); + $literals[] = $package->getId(); } // select preferred package according to policy rules - if (null === $matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, $matches)) { + if (null === $matchedPackage && \count($literals) > 0) { + $preferred = $policy->selectPreferredPackages($pool, $literals); $matchedPackage = $pool->literalToPackage($preferred[0]); } diff --git a/src/Composer/DependencyResolver/Decisions.php b/src/Composer/DependencyResolver/Decisions.php index 9a5c9c2357f9..599110750fff 100644 --- a/src/Composer/DependencyResolver/Decisions.php +++ b/src/Composer/DependencyResolver/Decisions.php @@ -28,7 +28,7 @@ class Decisions implements \Iterator, \Countable /** @var array */ protected $decisionMap; /** - * @var array + * @var array */ protected $decisionQueue = []; @@ -69,12 +69,12 @@ public function conflict(int $literal): bool public function decided(int $literalOrPackageId): bool { - return !empty($this->decisionMap[abs($literalOrPackageId)]); + return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) !== 0; } public function undecided(int $literalOrPackageId): bool { - return empty($this->decisionMap[abs($literalOrPackageId)]); + return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) === 0; } public function decidedInstall(int $literalOrPackageId): bool @@ -94,7 +94,7 @@ public function decisionLevel(int $literalOrPackageId): int return 0; } - public function decisionRule(int $literalOrPackageId): ?Rule + public function decisionRule(int $literalOrPackageId): Rule { $packageId = abs($literalOrPackageId); @@ -104,7 +104,7 @@ public function decisionRule(int $literalOrPackageId): ?Rule } } - return null; + throw new \LogicException('Did not find a decision rule using '.$literalOrPackageId); } /** @@ -219,7 +219,7 @@ public function toString(?Pool $pool = null): string ksort($decisionMap); $str = '['; foreach ($decisionMap as $packageId => $level) { - $str .= (($pool) ? $pool->literalToPackage($packageId) : $packageId).':'.$level.','; + $str .= ($pool !== null ? $pool->literalToPackage($packageId) : $packageId).':'.$level.','; } $str .= ']'; diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 2635de9ae5be..590187026181 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -30,7 +30,7 @@ class DefaultPolicy implements PolicyInterface private $preferLowest; /** @var array|null */ private $preferredVersions; - /** @var array>> */ + /** @var array>> */ private $preferredPackageResultCachePerPool; /** @var array> */ private $sortingCachePerPool; @@ -68,9 +68,8 @@ public function versionCompare(PackageInterface $a, PackageInterface $b, string } /** - * @param int[] $literals - * @param string $requiredPackage - * @return int[] + * @param non-empty-list $literals + * @return non-empty-list */ public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array { @@ -118,8 +117,8 @@ public function selectPreferredPackages(Pool $pool, array $literals, ?string $re } /** - * @param int[] $literals - * @return array + * @param non-empty-list $literals + * @return non-empty-array> */ protected function groupLiteralsByName(Pool $pool, array $literals): array { @@ -164,7 +163,7 @@ public function compareByPriority(Pool $pool, BasePackage $a, BasePackage $b, ?s // for replacers not replacing each other, put a higher prio on replacing // packages with the same vendor as the required package - if ($requiredPackage && false !== ($pos = strpos($requiredPackage, '/'))) { + if ($requiredPackage !== null && false !== ($pos = strpos($requiredPackage, '/'))) { $requiredVendor = substr($requiredPackage, 0, $pos); $aIsSameVendor = strpos($a->getName(), $requiredVendor) === 0; @@ -205,8 +204,8 @@ protected function replaces(BasePackage $source, BasePackage $target): bool } /** - * @param int[] $literals - * @return int[] + * @param list $literals + * @return list */ protected function pruneToBestVersion(Pool $pool, array $literals): array { @@ -252,8 +251,8 @@ protected function pruneToBestVersion(Pool $pool, array $literals): array * * If no package is a local alias, nothing happens * - * @param int[] $literals - * @return int[] + * @param list $literals + * @return list */ protected function pruneRemoteAliases(Pool $pool, array $literals): array { diff --git a/src/Composer/DependencyResolver/GenericRule.php b/src/Composer/DependencyResolver/GenericRule.php index c4b2f981ad9d..64dd7a21507e 100644 --- a/src/Composer/DependencyResolver/GenericRule.php +++ b/src/Composer/DependencyResolver/GenericRule.php @@ -47,6 +47,9 @@ public function getLiterals(): array public function getHash() { $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true)); + if (false === $data) { + throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); + } return $data['hash']; } diff --git a/src/Composer/DependencyResolver/MultiConflictRule.php b/src/Composer/DependencyResolver/MultiConflictRule.php index 05fedc2077e7..a6109475503d 100644 --- a/src/Composer/DependencyResolver/MultiConflictRule.php +++ b/src/Composer/DependencyResolver/MultiConflictRule.php @@ -19,11 +19,11 @@ */ class MultiConflictRule extends Rule { - /** @var list */ + /** @var non-empty-list */ protected $literals; /** - * @param list $literals + * @param non-empty-list $literals */ public function __construct(array $literals, $reason, $reasonData) { @@ -40,7 +40,7 @@ public function __construct(array $literals, $reason, $reasonData) } /** - * @return list + * @return non-empty-list */ public function getLiterals(): array { @@ -53,6 +53,9 @@ public function getLiterals(): array public function getHash() { $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true)); + if (false === $data) { + throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); + } return $data['hash']; } diff --git a/src/Composer/DependencyResolver/PolicyInterface.php b/src/Composer/DependencyResolver/PolicyInterface.php index 928e7e0a9733..b4511d091c74 100644 --- a/src/Composer/DependencyResolver/PolicyInterface.php +++ b/src/Composer/DependencyResolver/PolicyInterface.php @@ -26,8 +26,8 @@ interface PolicyInterface public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool; /** - * @param int[] $literals - * @return int[] + * @param non-empty-list $literals + * @return non-empty-list */ public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array; } diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index d3fbc521f8b2..241acf97b8ce 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -95,7 +95,7 @@ class PoolBuilder */ private $loadedPerRepo = []; /** - * @var BasePackage[] + * @var array */ private $packages = []; /** @@ -201,10 +201,14 @@ public function buildPool(array $repositories, Request $request): Pool { $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null; - if ($request->getUpdateAllowList()) { + if (\count($request->getUpdateAllowList()) > 0) { $this->updateAllowList = $request->getUpdateAllowList(); $this->warnAboutNonMatchingUpdateAllowList($request); + if (null === $request->getLockedRepository()) { + throw new \LogicException('No lock repo present and yet a partial update was requested.'); + } + foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) { if (!$this->isUpdateAllowed($lockedPackage)) { // remember which packages we skipped loading remote content for in this partial update @@ -271,7 +275,7 @@ public function buildPool(array $repositories, Request $request): Pool } } - while (!empty($this->packagesToLoad)) { + while (\count($this->packagesToLoad) > 0) { $this->loadPackagesMarkedForLoading($request, $repositories); } @@ -303,7 +307,7 @@ public function buildPool(array $repositories, Request $request): Pool } } - if ($this->eventDispatcher) { + if ($this->eventDispatcher !== null) { $prePoolCreateEvent = new PrePoolCreateEvent( PluginEvents::PRE_POOL_CREATE, $repositories, @@ -413,7 +417,7 @@ private function loadPackagesMarkedForLoading(Request $request, array $repositor $this->packagesToLoad = []; foreach ($repositories as $repoIndex => $repository) { - if (empty($packageBatch)) { + if (0 === \count($packageBatch)) { break; } @@ -499,7 +503,7 @@ private function loadPackage(Request $request, array $repositories, BasePackage if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { $skippedRootRequires = $this->getSkippedRootRequires($request, $require); - if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) { + if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { $this->unlockPackage($request, $repositories, $require); $this->markPackageNameForLoading($request, $require, $linkConstraint); } else { @@ -528,7 +532,7 @@ private function loadPackage(Request $request, array $repositories, BasePackage if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) { $skippedRootRequires = $this->getSkippedRootRequires($request, $replace); - if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) { + if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { $this->unlockPackage($request, $repositories, $replace); // the replaced package only needs to be loaded if something else requires it $this->markPackageNameForLoadingIfRequired($request, $replace); @@ -615,6 +619,10 @@ private function isUpdateAllowed(BasePackage $package): bool private function warnAboutNonMatchingUpdateAllowList(Request $request): void { + if (null === $request->getLockedRepository()) { + throw new \LogicException('No lock repo present and yet a partial update was requested.'); + } + foreach ($this->updateAllowList as $pattern) { $matchedPlatformPackage = false; diff --git a/src/Composer/DependencyResolver/PoolOptimizer.php b/src/Composer/DependencyResolver/PoolOptimizer.php index c53891fb63e8..3de9e037b409 100644 --- a/src/Composer/DependencyResolver/PoolOptimizer.php +++ b/src/Composer/DependencyResolver/PoolOptimizer.php @@ -207,7 +207,7 @@ private function optimizeByIdenticalDependencies(Request $request, Pool $pool): $groupHashParts[] = 'require:' . (string) $requireConstraint; } - if ($package->getReplaces()) { + if (\count($package->getReplaces()) > 0) { foreach ($package->getReplaces() as $link) { if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) { // Use the same hash part as the regular require hash because that's what the replacement does @@ -224,7 +224,7 @@ private function optimizeByIdenticalDependencies(Request $request, Pool $pool): } } - if (!$groupHashParts) { + if (0 === \count($groupHashParts)) { continue; } @@ -371,7 +371,7 @@ private function keepPackage(BasePackage $package, array $identicalDefinitionsPe */ private function optimizeImpossiblePackagesAway(Request $request, Pool $pool): void { - if (count($request->getLockedPackages()) === 0) { + if (\count($request->getLockedPackages()) === 0) { return; } diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index c54fab1afd26..5576845e907a 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -80,7 +80,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections? $reasons = array_merge(...array_reverse($this->reasons)); - if (count($reasons) === 1) { + if (\count($reasons) === 1) { reset($reasons); $rule = current($reasons); @@ -93,7 +93,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, $constraint = $reasonData['constraint']; $packages = $pool->whatProvides($packageName, $constraint); - if (count($packages) === 0) { + if (\count($packages) === 0) { return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint)); } } @@ -188,7 +188,7 @@ public static function formatDeduplicatedRules(array $rules, string $indent, Rep if (!$isVerbose) { $versions = self::condenseVersionList($versions, 1); } - if (count($versions) > 1) { + if (\count($versions) > 1) { // remove the s from requires/conflicts to correct grammar $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message); $result[] = sprintf($message, $package, '['.implode(', ', $versions).']'); @@ -276,7 +276,7 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req $ext = substr($packageName, 4); $msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but '; - $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) ?: '0'); + $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) === false ? '0' : phpversion($ext)); if (null === $version) { $providersStr = self::getProvidersList($repositorySet, $packageName, 5); if ($providersStr !== null) { @@ -337,7 +337,8 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req // first check if the actual requested package is found in normal conditions // if so it must mean it is rejected by another constraint than the one given here - if ($packages = $repositorySet->findPackages($packageName, $constraint)) { + $packages = $repositorySet->findPackages($packageName, $constraint); + if (\count($packages) > 0) { $rootReqs = $repositorySet->getRootRequires(); if (isset($rootReqs[$packageName])) { $filtered = array_filter($packages, static function ($p) use ($rootReqs, $packageName): bool { @@ -358,7 +359,7 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req } } - if ($lockedPackage) { + if ($lockedPackage !== null) { $fixedConstraint = new Constraint('==', $lockedPackage->getVersion()); $filtered = array_filter($packages, static function ($p) use ($fixedConstraint): bool { return $fixedConstraint->matches(new Constraint('==', $p->getVersion())); @@ -372,7 +373,7 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req return !$p->getRepository() instanceof LockArrayRepository; }); - if (!$nonLockedPackages) { + if (0 === \count($nonLockedPackages)) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.']; } @@ -380,9 +381,11 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req } // check if the package is found when bypassing stability checks - if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { + $packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); + if (\count($packages) > 0) { // we must first verify if a valid package would be found in a lower priority repository - if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { + $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); + if (\count($allReposPackages) > 0) { return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); } @@ -390,9 +393,11 @@ public static function getMissingPackageReason(RepositorySet $repositorySet, Req } // check if the package is found when bypassing the constraint and stability checks - if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { + $packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); + if (\count($packages) > 0) { // we must first verify if a valid package would be found in a lower priority repository - if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { + $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); + if (\count($allReposPackages) > 0) { return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); } @@ -441,12 +446,12 @@ public static function getPackageList(array $packages, bool $isVerbose, ?Pool $p foreach ($packages as $package) { $prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); - if ($pool && $constraint) { + if ($pool !== null && $constraint !== null) { foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) { $prepared[$package->getName()]['versions'][$version] = $prettyVersion; } } - if ($pool && $useRemovedVersionGroup) { + if ($pool !== null && $useRemovedVersionGroup) { foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) { $prepared[$package->getName()]['versions'][$version] = $prettyVersion; } @@ -482,7 +487,7 @@ private static function getPlatformPackageVersion(Pool $pool, string $packageNam { $available = $pool->whatProvides($packageName); - if (count($available)) { + if (\count($available) > 0) { $selected = null; foreach ($available as $pkg) { if ($pkg->getRepository() instanceof PlatformRepository) { @@ -507,7 +512,7 @@ private static function getPlatformPackageVersion(Pool $pool, string $packageNam $version = $selected->getPrettyVersion(); $extra = $selected->getExtra(); if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) { - $version .= '; ' . str_replace('Package ', '', $selected->getDescription()); + $version .= '; ' . str_replace('Package ', '', (string) $selected->getDescription()); } } else { return null; @@ -568,8 +573,8 @@ private static function hasMultipleNames(array $packages): bool } /** - * @param PackageInterface[] $higherRepoPackages - * @param PackageInterface[] $allReposPackages + * @param non-empty-array $higherRepoPackages + * @param non-empty-array $allReposPackages * @return array{0: string, 1: string} */ private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null): array @@ -586,7 +591,9 @@ private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose } } - if ($higherRepoPackages) { + assert(null !== $nextRepo); + + if (\count($higherRepoPackages) > 0) { $topPackage = reset($higherRepoPackages); if ($topPackage instanceof RootPackageInterface) { return [ @@ -634,7 +641,7 @@ protected static function constraintToText(?ConstraintInterface $constraint = nu return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')'; } - return $constraint ? ' '.$constraint->getPrettyString() : ''; + return $constraint !== null ? ' '.$constraint->getPrettyString() : ''; } private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string @@ -642,7 +649,7 @@ private static function getProvidersList(RepositorySet $repositorySet, string $p $providers = $repositorySet->getProviders($packageName); if (\count($providers) > 0) { $providersStr = implode(array_map(static function ($p): string { - $description = $p['description'] ? ' '.substr($p['description'], 0, 100) : ''; + $description = $p['description'] !== '' ? ' '.substr($p['description'], 0, 100) : ''; return ' - '.$p['name'].$description."\n"; }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index 300093f043fc..b11f4e1f2eb0 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -190,7 +190,7 @@ public function getFixedOrLockedPackages(): array } /** - * @return array + * @return ($packageIds is true ? array : array) * * @TODO look into removing the packageIds option, the only place true is used * is for the installed map in the solver problems. @@ -201,7 +201,7 @@ public function getPresentMap(bool $packageIds = false): array { $presentMap = []; - if ($this->lockedRepository) { + if ($this->lockedRepository !== null) { foreach ($this->lockedRepository->getPackages() as $package) { $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; } @@ -215,7 +215,7 @@ public function getPresentMap(bool $packageIds = false): array } /** - * @return BasePackage[] + * @return array */ public function getFixedPackagesMap(): array { diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index d65164154cbf..8dde02b37af0 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -153,7 +153,7 @@ public function isCausedByLock(RepositorySet $repositorySet, Request $request, P if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) { return false; } - if ($request->getLockedRepository()) { + if ($request->getLockedRepository() !== null) { foreach ($request->getLockedRepository()->getPackages() as $package) { if ($package->getName() === $this->getReasonData()->getTarget()) { if ($pool->isUnacceptableFixedOrLockedPackage($package)) { @@ -176,7 +176,7 @@ public function isCausedByLock(RepositorySet $repositorySet, Request $request, P if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) { return false; } - if ($request->getLockedRepository()) { + if ($request->getLockedRepository() !== null) { foreach ($request->getLockedRepository()->getPackages() as $package) { if ($package->getName() === $this->getReasonData()['packageName']) { if ($pool->isUnacceptableFixedOrLockedPackage($package)) { @@ -215,7 +215,7 @@ public function getSourcePackage(Pool $pool): BasePackage return $package2; case self::RULE_PACKAGE_REQUIRES: - $sourceLiteral = array_shift($literals); + $sourceLiteral = $literals[0]; $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); return $sourcePackage; @@ -240,14 +240,14 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, $constraint = $reasonData['constraint']; $packages = $pool->whatProvides($packageName, $constraint); - if (!$packages) { + if (0 === \count($packages)) { return 'No package found to satisfy root composer.json require '.$packageName.' '.$constraint->getPrettyString(); } $packagesNonAlias = array_values(array_filter($packages, static function ($p): bool { return !($p instanceof AliasPackage); })); - if (count($packagesNonAlias) === 1) { + if (\count($packagesNonAlias) === 1) { $package = $packagesNonAlias[0]; if ($request->isLockedPackage($package)) { return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion()." and an update of this package was not requested."; @@ -305,6 +305,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.'; case self::RULE_PACKAGE_REQUIRES: + assert(\count($literals) > 0); $sourceLiteral = array_shift($literals); $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); $reasonData = $this->getReasonData(); @@ -315,7 +316,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, } $text = $reasonData->getPrettyString($sourcePackage); - if ($requires) { + if (\count($requires) > 0) { $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.'; } else { $targetName = $reasonData->getTarget(); @@ -333,19 +334,18 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, $package = $pool->literalToPackage($literal); $packageNames[$package->getName()] = true; } + unset($literal); $replacedName = $this->getReasonData(); - if (count($packageNames) > 1) { - $reason = null; - + if (\count($packageNames) > 1) { if (!isset($packageNames[$replacedName])) { - $reason = 'They '.(count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.'; + $reason = 'They '.(\count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.'; } else { $replacerNames = $packageNames; unset($replacerNames[$replacedName]); $replacerNames = array_keys($replacerNames); - if (count($replacerNames) === 1) { + if (\count($replacerNames) === 1) { $reason = $replacerNames[0] . ' replaces '; } else { $reason = '['.implode(', ', $replacerNames).'] replace '; @@ -363,7 +363,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, } } - if ($installedPackages && $removablePackages) { + if (\count($installedPackages) > 0 && \count($removablePackages) > 0) { return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason; } @@ -381,7 +381,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, // } $learnedString = ' (conflict analysis result)'; - if (count($literals) === 1) { + if (\count($literals) === 1) { $ruleText = $pool->literalToPrettyString($literals[0], $installedMap); } else { $groups = []; @@ -397,7 +397,7 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, } $ruleTexts = []; foreach ($groups as $group => $packages) { - $ruleTexts[] = $group . (count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose); + $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose); } $ruleText = implode(' | ', $ruleTexts); @@ -439,14 +439,13 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, } /** - * @param array $packages An array containing packages or literals + * @param array $literalsOrPackages An array containing packages or literals */ - protected function formatPackagesUnique(Pool $pool, array $packages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string + protected function formatPackagesUnique(Pool $pool, array $literalsOrPackages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string { - foreach ($packages as $index => $package) { - if (!\is_object($package)) { - $packages[$index] = $pool->literalToPackage($package); - } + $packages = []; + foreach ($literalsOrPackages as $package) { + $packages[] = \is_object($package) ? $package : $pool->literalToPackage($package); } return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup); diff --git a/src/Composer/DependencyResolver/Rule2Literals.php b/src/Composer/DependencyResolver/Rule2Literals.php index 17bfaf8ce158..33d0ed0be026 100644 --- a/src/Composer/DependencyResolver/Rule2Literals.php +++ b/src/Composer/DependencyResolver/Rule2Literals.php @@ -43,7 +43,7 @@ public function __construct(int $literal1, int $literal2, $reason, $reasonData) } /** - * @return list + * @return non-empty-list */ public function getLiterals(): array { diff --git a/src/Composer/DependencyResolver/RuleSet.php b/src/Composer/DependencyResolver/RuleSet.php index 4a406238a26f..cca9eb12383b 100644 --- a/src/Composer/DependencyResolver/RuleSet.php +++ b/src/Composer/DependencyResolver/RuleSet.php @@ -179,7 +179,7 @@ public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $ foreach ($this->rules as $type => $rules) { $string .= str_pad(self::TYPES[$type], 8, ' ') . ": "; foreach ($rules as $rule) { - $string .= ($repositorySet && $request && $pool ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n"; + $string .= ($repositorySet !== null && $request !== null && $pool !== null ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n"; } $string .= "\n\n"; } diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 69d1191349aa..08c874ff7afd 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -56,7 +56,7 @@ public function __construct(PolicyInterface $policy, Pool $pool) * * @phpstan-param ReasonData $reasonData */ - protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData = null): ?Rule + protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData): ?Rule { $literals = [-$package->id]; @@ -77,7 +77,7 @@ protected function createRequireRule(BasePackage $package, array $providers, $re * The rule is (A|B|C) with A, B and C different packages. If the given * set of packages is empty an impossible rule is generated. * - * @param BasePackage[] $packages The set of packages to choose from + * @param non-empty-array $packages The set of packages to choose from * @param Rule::RULE_* $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Additional data like the root require or fix request info @@ -109,7 +109,7 @@ protected function createInstallOneOfRule(array $packages, $reason, $reasonData) * * @phpstan-param ReasonData $reasonData */ - protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData = null): ?Rule + protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData): ?Rule { // ignore self conflict if ($issuer === $provider) { @@ -120,7 +120,7 @@ protected function createRule2Literals(BasePackage $issuer, BasePackage $provide } /** - * @param BasePackage[] $packages + * @param non-empty-array $packages * @param Rule::RULE_* $reason A RULE_* constant * @param mixed $reasonData * @@ -151,7 +151,7 @@ protected function createMultiConflictRule(array $packages, $reason, $reasonData */ private function addRule($type, ?Rule $newRule = null): void { - if (!$newRule) { + if (null === $newRule) { return; } @@ -276,7 +276,7 @@ protected function addRulesForRequest(Request $request, PlatformRequirementFilte } $packages = $this->pool->whatProvides($packageName, $constraint); - if ($packages) { + if (\count($packages) > 0) { foreach ($packages as $package) { $this->addRulesForPackage($package, $platformRequirementFilter); } @@ -307,7 +307,7 @@ protected function addRulesForRootAliases(PlatformRequirementFilterInterface $pl public function getRulesFor(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): RuleSet { - $platformRequirementFilter = $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing(); + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); $this->addRulesForRequest($request, $platformRequirementFilter); diff --git a/src/Composer/DependencyResolver/RuleSetIterator.php b/src/Composer/DependencyResolver/RuleSetIterator.php index 2bf67f55bf11..3b8383d47d47 100644 --- a/src/Composer/DependencyResolver/RuleSetIterator.php +++ b/src/Composer/DependencyResolver/RuleSetIterator.php @@ -14,7 +14,7 @@ /** * @author Nils Adermann - * @implements \Iterator + * @implements \Iterator */ class RuleSetIterator implements \Iterator { diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 373e8325d526..b8aa847d1ce8 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -43,7 +43,7 @@ class Solver /** @var int */ protected $propagateIndex; - /** @var mixed[] */ + /** @var array, int}> */ protected $branches = []; /** @var Problem[] */ protected $problems = []; @@ -109,7 +109,7 @@ private function makeAssertionRuleDecisions(): void $conflict = $this->decisions->decisionRule($literal); - if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) { + if (RuleSet::TYPE_PACKAGE === $conflict->getType()) { $problem = new Problem(); $problem->addRule($rule); @@ -164,7 +164,7 @@ protected function checkForRootRequireProblems(Request $request, PlatformRequire $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); } - if (!$this->pool->whatProvides($packageName, $constraint)) { + if (0 === \count($this->pool->whatProvides($packageName, $constraint))) { $problem = new Problem(); $problem->addRule(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint])); $this->problems[] = $problem; @@ -174,7 +174,7 @@ protected function checkForRootRequireProblems(Request $request, PlatformRequire public function solve(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): LockTransaction { - $platformRequirementFilter = $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing(); + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); $this->setupFixedMap($request); @@ -199,7 +199,7 @@ public function solve(Request $request, ?PlatformRequirementFilterInterface $pla $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); - if ($this->problems) { + if (\count($this->problems) > 0) { throw new SolverProblemsException($this->problems, $this->learnedPool); } @@ -227,7 +227,7 @@ protected function propagate(int $level): ?Rule $this->propagateIndex++; - if ($conflict) { + if ($conflict !== null) { return $conflict; } } @@ -257,7 +257,7 @@ private function revert(int $level): void $this->propagateIndex = \count($this->decisions); } - while (!empty($this->branches) && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { + while (\count($this->branches) > 0 && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { array_pop($this->branches); } } @@ -274,10 +274,8 @@ private function revert(int $level): void * rule (always unit) and re-propagate. * * returns the new solver level or 0 if unsolvable - * - * @param string|int $literal */ - private function setPropagateLearn(int $level, $literal, Rule $rule): int + private function setPropagateLearn(int $level, int $literal, Rule $rule): int { $level++; @@ -291,7 +289,9 @@ private function setPropagateLearn(int $level, $literal, Rule $rule): int } if ($level === 1) { - return $this->analyzeUnsolvable($rule); + $this->analyzeUnsolvable($rule); + + return 0; } // conflict @@ -322,7 +322,7 @@ private function setPropagateLearn(int $level, $literal, Rule $rule): int } /** - * @param int[] $decisionQueue + * @param non-empty-list $decisionQueue */ private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): int { @@ -332,7 +332,7 @@ private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): $selectedLiteral = array_shift($literals); // if there are multiple candidates, then branch - if (\count($literals)) { + if (\count($literals) > 0) { $this->branches[] = [$literals, $level]; } @@ -349,7 +349,8 @@ protected function analyze(int $level, Rule $rule): array $num = 0; $l1num = 0; $seen = []; - $learnedLiterals = [null]; + $learnedLiteral = null; + $otherLearnedLiterals = []; $decisionId = \count($this->decisions); @@ -382,7 +383,7 @@ protected function analyze(int $level, Rule $rule): array $num++; } else { // not level1 or conflict level, add to new rule - $learnedLiterals[] = $literal; + $otherLearnedLiterals[] = $literal; if ($l > $ruleLevel) { $ruleLevel = $l; @@ -423,16 +424,14 @@ protected function analyze(int $level, Rule $rule): array if ($literal < 0) { $this->testFlagLearnedPositiveLiteral = true; } - $learnedLiterals[0] = -$literal; + $learnedLiteral = -$literal; - if (!$l1num) { + if (0 === $l1num) { break 2; } - foreach ($learnedLiterals as $i => $learnedLiteral) { - if ($i !== 0) { - unset($seen[abs($learnedLiteral)]); - } + foreach ($otherLearnedLiterals as $otherLiteral) { + unset($seen[abs($otherLiteral)]); } // only level 1 marks left $l1num++; @@ -442,24 +441,24 @@ protected function analyze(int $level, Rule $rule): array $rule = $decision[Decisions::DECISION_REASON]; if ($rule instanceof MultiConflictRule) { - // there is only ever exactly one positive decision in a multiconflict rule - foreach ($rule->getLiterals() as $literal) { - if (!isset($seen[abs($literal)]) && $this->decisions->satisfy(-$literal)) { + // there is only ever exactly one positive decision in a MultiConflictRule + foreach ($rule->getLiterals() as $ruleLiteral) { + if (!isset($seen[abs($ruleLiteral)]) && $this->decisions->satisfy(-$ruleLiteral)) { $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; - $l = $this->decisions->decisionLevel($literal); + $l = $this->decisions->decisionLevel($ruleLiteral); if (1 === $l) { $l1num++; } elseif ($level === $l) { $num++; } else { // not level1 or conflict level, add to new rule - $learnedLiterals[] = $literal; + $otherLearnedLiterals[] = $ruleLiteral; if ($l > $ruleLevel) { $ruleLevel = $l; } } - $seen[abs($literal)] = true; + $seen[abs($ruleLiteral)] = true; break; } } @@ -475,15 +474,16 @@ protected function analyze(int $level, Rule $rule): array $why = \count($this->learnedPool) - 1; - if (null === $learnedLiterals[0]) { + if (null === $learnedLiteral) { throw new SolverBugException( "Did not find a learnable literal in analyzed rule $analyzedRule." ); } - $newRule = new GenericRule($learnedLiterals, Rule::RULE_LEARNED, $why); + array_unshift($otherLearnedLiterals, $learnedLiteral); + $newRule = new GenericRule($otherLearnedLiterals, Rule::RULE_LEARNED, $why); - return [$learnedLiterals[0], $ruleLevel, $newRule, $why]; + return [$learnedLiteral, $ruleLevel, $newRule, $why]; } /** @@ -516,7 +516,7 @@ private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule, arr $problem->addRule($conflictRule); } - private function analyzeUnsolvable(Rule $conflictRule): int + private function analyzeUnsolvable(Rule $conflictRule): void { $problem = new Problem(); $problem->addRule($conflictRule); @@ -539,10 +539,10 @@ private function analyzeUnsolvable(Rule $conflictRule): int } foreach ($this->decisions as $decision) { - $literal = $decision[Decisions::DECISION_LITERAL]; + $decisionLiteral = $decision[Decisions::DECISION_LITERAL]; // skip literals that are not in this rule - if (!isset($seen[abs($literal)])) { + if (!isset($seen[abs($decisionLiteral)])) { continue; } @@ -552,7 +552,6 @@ private function analyzeUnsolvable(Rule $conflictRule): int $this->analyzeUnsolvableRule($problem, $why, $ruleSeen); $literals = $why->getLiterals(); - foreach ($literals as $literal) { // skip the one true literal if ($this->decisions->satisfy($literal)) { @@ -561,8 +560,6 @@ private function analyzeUnsolvable(Rule $conflictRule): int $seen[abs($literal)] = true; } } - - return 0; } private function runSat(): void @@ -586,9 +583,7 @@ private function runSat(): void if (1 === $level) { $conflictRule = $this->propagate($level); if (null !== $conflictRule) { - if ($this->analyzeUnsolvable($conflictRule)) { - continue; - } + $this->analyzeUnsolvable($conflictRule); return; } @@ -612,7 +607,7 @@ private function runSat(): void } } - if ($noneSatisfied && \count($decisionQueue)) { + if ($noneSatisfied && \count($decisionQueue) > 0) { // if any of the options in the decision queue are fixed, only use those $prunedQueue = []; foreach ($decisionQueue as $literal) { @@ -620,12 +615,12 @@ private function runSat(): void $prunedQueue[] = $literal; } } - if (!empty($prunedQueue)) { + if (\count($prunedQueue) > 0) { $decisionQueue = $prunedQueue; } } - if ($noneSatisfied && \count($decisionQueue)) { + if ($noneSatisfied && \count($decisionQueue) > 0) { $oLevel = $level; $level = $this->selectAndInstall($level, $decisionQueue, $rule); @@ -719,7 +714,7 @@ private function runSat(): void } // minimization step - if (\count($this->branches)) { + if (\count($this->branches) > 0) { $lastLiteral = null; $lastLevel = null; $lastBranchIndex = 0; @@ -729,7 +724,7 @@ private function runSat(): void [$literals, $l] = $this->branches[$i]; foreach ($literals as $offset => $literal) { - if ($literal && $literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { + if ($literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { $lastLiteral = $literal; $lastBranchIndex = $i; $lastBranchOffset = $offset; @@ -738,7 +733,8 @@ private function runSat(): void } } - if ($lastLiteral) { + if ($lastLiteral !== null) { + assert($lastLevel !== null); unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]); $level = $lastLevel; diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index 5870c0cae0f3..bd76e4fa3fc0 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -38,7 +38,7 @@ public function __construct(array $problems, array $learnedPool) $this->problems = $problems; $this->learnedPool = $learnedPool; - parent::__construct('Failed resolving dependencies with '.count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED); + parent::__construct('Failed resolving dependencies with '.\count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED); } public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, bool $isDevExtraction = false): string @@ -63,11 +63,11 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, } $hints = []; - if (!$isDevExtraction && (strpos($text, 'could not be found') || strpos($text, 'no matching package found'))) { + if (!$isDevExtraction && (str_contains($text, 'could not be found') || str_contains($text, 'no matching package found'))) { $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems."; } - if (!empty($missingExtensions)) { + if (\count($missingExtensions) > 0) { $hints[] = $this->createExtensionHint($missingExtensions); } @@ -75,17 +75,17 @@ public function getPrettyString(RepositorySet $repositorySet, Request $request, $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions."; } - if (strpos($text, 'found composer-plugin-api[2.0.0] but it does not match') && strpos($text, '- ocramius/package-versions')) { + if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match') && str_contains($text, '- ocramius/package-versions')) { $hints[] = "ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.\nIf you can not upgrade PHP you can require composer/package-versions-deprecated to resolve this with PHP 7.0+."; } if (!class_exists('PHPUnit\Framework\TestCase', false)) { - if (strpos($text, 'found composer-plugin-api[2.0.0] but it does not match')) { + if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match')) { $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2."; } } - if ($hints) { + if (\count($hints) > 0) { $text .= "\n" . implode("\n\n", $hints); } diff --git a/src/Composer/DependencyResolver/Transaction.php b/src/Composer/DependencyResolver/Transaction.php index ef6860c11712..3443dd768f13 100644 --- a/src/Composer/DependencyResolver/Transaction.php +++ b/src/Composer/DependencyResolver/Transaction.php @@ -123,7 +123,7 @@ protected function calculateOperations(): array $visited = []; $processed = []; - while (!empty($stack)) { + while (\count($stack) > 0) { $package = array_pop($stack); if (isset($processed[spl_object_hash($package)])) { @@ -283,17 +283,18 @@ private function movePluginsToFront(array $operations): array continue; } - $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && ($extra = $package->getExtra()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true; + $extra = $package->getExtra(); + $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true; // is this a downloads modifying plugin or a dependency of one? - if ($isDownloadsModifyingPlugin || count(array_intersect($package->getNames(), $dlModifyingPluginRequires))) { + if ($isDownloadsModifyingPlugin || \count(array_intersect($package->getNames(), $dlModifyingPluginRequires)) > 0) { // get the package's requires, but filter out any platform requirements $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { return !PlatformRepository::isPlatformPackage($req); }); // is this a plugin with no meaningful dependencies? - if ($isDownloadsModifyingPlugin && !count($requires)) { + if ($isDownloadsModifyingPlugin && 0 === \count($requires)) { // plugins with no dependencies go to the very front array_unshift($dlModifyingPluginsNoDeps, $op); } else { @@ -311,14 +312,14 @@ private function movePluginsToFront(array $operations): array $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; // is this a plugin or a dependency of a plugin? - if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) { + if ($isPlugin || \count(array_intersect($package->getNames(), $pluginRequires)) > 0) { // get the package's requires, but filter out any platform requirements $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { return !PlatformRepository::isPlatformPackage($req); }); // is this a plugin with no meaningful dependencies? - if ($isPlugin && !count($requires)) { + if ($isPlugin && 0 === \count($requires)) { // plugins with no dependencies go to the very front array_unshift($pluginsNoDeps, $op); } else { diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 8cb3662474a0..cfa86acf91c1 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -226,6 +226,7 @@ public function testSelectLocalReposFirst(): void $pool = $this->repositorySet->createPoolForPackage('A', $this->repoLocked); $packages = $pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev')); + self::assertNotEmpty($packages); $literals = []; foreach ($packages as $package) { $literals[] = $package->getId(); From c8bd0e6278040e96bb240673038082b0d37a9e9e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 21 Sep 2024 13:54:03 +0200 Subject: [PATCH 191/257] Add --patch-only flag to update command to restrict updates to patch versions and make them safer (#12122) Fixes #11446 --- doc/03-cli.md | 1 + src/Composer/Command/UpdateCommand.php | 22 +++++++ .../Test/Command/UpdateCommandTest.php | 62 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index c99624db3450..88db063c366c 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -232,6 +232,7 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.* COMPOSER_PREFER_LOWEST=1 env var. * **--minimal-changes (-m):** During a partial update with `-w`/`-W`, only perform absolutely necessary changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. +* **--patch-only:** Only allow patch version updates for currently installed dependencies. * **--interactive:** Interactive interface with autocompletion to select the packages to update. * **--root-reqs:** Restricts the update to your first degree dependencies. * **--bump-after-update:** Runs `bump` after performing the update. Set to `dev` or `no-dev` to only bump those dependencies. diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 756aa00d9d0f..6747242da1e0 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -28,6 +28,7 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositorySet; +use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Intervals; use Composer\Util\HttpDownloader; use Composer\Advisory\Auditor; @@ -83,6 +84,7 @@ protected function configure() new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']), @@ -175,6 +177,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + if ($input->getOption('patch-only')) { + if (!$composer->getLocker()->isLocked()) { + throw new \InvalidArgumentException('patch-only can only be used with a lock file present'); + } + foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) { + if ($package->isDev()) { + continue; + } + if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) { + continue; + } + $constraint = $parser->parseConstraints('~'.$match[1]); + if (isset($temporaryConstraints[$package->getName()])) { + $temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true); + } else { + $temporaryConstraints[$package->getName()] = $constraint; + } + } + } + if ($input->getOption('interactive')) { $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); } diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index dd8427d29628..0a5938805944 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -185,7 +185,69 @@ public static function provideUpdates(): \Generator - root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2). OUTPUT ]; + } + + public function testUpdateWithPatchOnly(): void + { + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'root/req', 'version' => '1.0.0'], + ['name' => 'root/req', 'version' => '1.0.1'], + ['name' => 'root/req', 'version' => '1.1.0'], + ['name' => 'root/req2', 'version' => '1.0.0'], + ['name' => 'root/req2', 'version' => '1.0.1'], + ['name' => 'root/req2', 'version' => '1.1.0'], + ['name' => 'root/req3', 'version' => '1.0.0'], + ['name' => 'root/req3', 'version' => '1.0.1'], + ['name' => 'root/req3', 'version' => '1.1.0'], + ], + ], + ], + 'require' => [ + 'root/req' => '1.*', + 'root/req2' => '1.*', + 'root/req3' => '1.*', + ], + ]); + $package = self::getPackage('root/req', '1.0.0'); + $package2 = self::getPackage('root/req2', '1.0.0'); + $package3 = self::getPackage('root/req3', '1.0.0'); + $this->createComposerLock([$package, $package2, $package3]); + + $appTester = $this->getApplicationTester(); + // root/req fails because of incompatible --with requirement + $appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true, '--no-install' => true, '--patch-only' => true, '--with' => ['root/req:^1.1']])); + + $expected = <<= 1.1.0.0-dev < 2.0.0.0-dev] [>= 1.0.0.0-dev < 1.1.0.0-dev]]). +OUTPUT; + + self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true))); + + $appTester = $this->getApplicationTester(); + // root/req upgrades to 1.0.1 as that is compatible with the --with requirement now + // root/req2 upgrades to 1.0.1 only due to --patch-only + // root/req3 does not update as it is not in the allowlist + $appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true, '--no-install' => true, '--patch-only' => true, '--with' => ['root/req:^1.0.1'], 'packages' => ['root/req', 'root/req2']])); + + $expected = << 1.0.1) + - Upgrading root/req2 (1.0.0 => 1.0.1) +OUTPUT; + + self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true))); } public function testInteractiveModeThrowsIfNoPackageToUpdate(): void From dfee3dec57fe7459e89abb75a1f855109cec2bdd Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 25 Sep 2024 09:12:58 +0100 Subject: [PATCH 192/257] Add support-nts flag for php-ext section (#12127) * Add support-nts flag for php-ext section Ref: https://github.com/ThePHPF/pie-design/pull/25 * Update php-ext getter/setter definition with support-nts --- res/composer-schema.json | 6 ++++++ src/Composer/Package/Package.php | 2 +- src/Composer/Package/PackageInterface.php | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/res/composer-schema.json b/res/composer-schema.json index e6f626dcb281..ba62d21a48c3 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -311,6 +311,12 @@ "example": false, "default": true }, + "support-nts": { + "type": "boolean", + "description": "Does this package support non-Thread Safe mode", + "example": false, + "default": true + }, "configure-options": { "type": "array", "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index 9d1b0ffc1a79..ad17538d662b 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -595,7 +595,7 @@ public function getIncludePaths(): array /** * Sets the list of paths added to PHP's include path. * - * @param array{extension-name?: string, priority?: int, support-zts?: bool, configure-options?: list}|null $phpExt List of directories. + * @param array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, configure-options?: list}|null $phpExt List of directories. */ public function setPhpExt(?array $phpExt): void { diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index b7c9ecd45de7..6e723d5bbcb2 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -326,7 +326,7 @@ public function getIncludePaths(): array; /** * Returns the settings for php extension packages * - * @return array{extension-name?: string, priority?: int, support-zts?: bool, configure-options?: list}|null + * @return array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, configure-options?: list}|null */ public function getPhpExt(): ?array; From 1add25fcb5b5b7117b2afadf65396a659f1830b6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 26 Sep 2024 13:14:04 +0200 Subject: [PATCH 193/257] Add SSL certificate problem hint in troubleshooting page --- doc/articles/troubleshooting.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index eda335453780..ff0393002c41 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -247,6 +247,18 @@ please report this [issue](https://github.com/composer/composer/issues). 3. Check if it contains any path to a non-existent file, if it's the case, remove them. +## SSL certificate problem: unable to get local issuer certificate + +1. Check that your root certificate store / CA bundle is up to date. Run `composer diagnose -vvv` + and look for `Checked CA file ...` or `Checked directory ...` lines in the first lines of output. + This will show you where Composer is looking for a CA bundle. You can get a + [new cacert.pem from cURL](https://curl.se/docs/caextract.html) and store it there. +2. If this did not help despite Composer finding a valid CA bundle, try disabling your antivirus + software to see if that helps. We have seen issues where Avast on Windows for example would + prevent Composer from functioning correctly. If this helps you should report it to your + antivirus vendor so they can hopefully improve things. + + ## API rate limit and OAuth tokens Because of GitHub's rate limits on their API it can happen that Composer prompts From 5742df97c259b8e21bce6c662e171460dc739316 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 26 Sep 2024 13:15:13 +0200 Subject: [PATCH 194/257] Tweak wording --- doc/articles/troubleshooting.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index ff0393002c41..92ec6bbc7f3c 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -253,10 +253,10 @@ please report this [issue](https://github.com/composer/composer/issues). and look for `Checked CA file ...` or `Checked directory ...` lines in the first lines of output. This will show you where Composer is looking for a CA bundle. You can get a [new cacert.pem from cURL](https://curl.se/docs/caextract.html) and store it there. -2. If this did not help despite Composer finding a valid CA bundle, try disabling your antivirus - software to see if that helps. We have seen issues where Avast on Windows for example would - prevent Composer from functioning correctly. If this helps you should report it to your - antivirus vendor so they can hopefully improve things. +2. If this did not help despite Composer finding a valid CA bundle, try disabling your antivirus and + firewall software to see if that helps. We have seen issues where Avast on Windows for example would + prevent Composer from functioning correctly. If this helps you should report it to the + software vendor so they can hopefully improve things. ## API rate limit and OAuth tokens From d37dd5fff1aa944abc3be5f5793a05edb9a438ee Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 26 Sep 2024 13:35:58 +0200 Subject: [PATCH 195/257] Make version guessing more deterministic if two branches appear to be the base of a feature branch (#12129) --- src/Composer/Package/Version/VersionGuesser.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 9a51527e8b22..bfc16d524135 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -284,7 +284,7 @@ private function guessFeatureVersion(array $packageConfig, ?string $version, arr } // sort local branches first then remote ones - // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, main is picked + // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, 1.9 is picked // and sort using natural sort so that 1.10 will appear before 1.9 usort($branches, static function ($a, $b): int { $aRemote = 0 === strpos($a, 'remotes/'); @@ -300,7 +300,8 @@ private function guessFeatureVersion(array $packageConfig, ?string $version, arr $promises = []; $this->process->setMaxJobs(30); try { - foreach ($branches as $candidate) { + $lastIndex = -1; + foreach ($branches as $index => $candidate) { $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate); // do not compare against itself or other feature branches @@ -311,13 +312,17 @@ private function guessFeatureVersion(array $packageConfig, ?string $version, arr $cmdLine = array_map(static function (string $component) use ($candidate, $branch) { return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component); }, $scmCmdline); - $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void { + $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$lastIndex, $index, &$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void { if (!$process->isSuccessful()) { return; } $output = $process->getOutput(); - if (strlen($output) < $length) { + // overwrite existing if we have a shorter diff, or we have an equal diff and an index that comes later in the array (i.e. older version) + // as newer versions typically have more commits, if the feature branch is based on a newer branch it should have a longer diff to the old version + // but if it doesn't and they have equal diffs, then it probably is based on the old version + if (strlen($output) < $length || (strlen($output) === $length && $lastIndex < $index)) { + $lastIndex = $index; $length = strlen($output); $version = $this->versionParser->normalizeBranch($candidateVersion); $prettyVersion = 'dev-' . $candidateVersion; From 95b9b54f0cbe1cd3327dc420721160c9c4cfc385 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 26 Sep 2024 13:36:25 +0200 Subject: [PATCH 196/257] JSON schema updates (#12123) * Add composer-lock-schema, update composer-repository-schema with new properties, add lock schema validation in diagnose Fixes #7823 * Add ref to composer.json schema in the lock one --- res/composer-lock-schema.json | 101 ++++++++++++++++++++ res/composer-repository-schema.json | 116 ++++++++++++++++++++--- res/composer-schema.json | 2 +- src/Composer/Command/DiagnoseCommand.php | 29 ++++++ src/Composer/Json/JsonFile.php | 10 +- src/Composer/Package/Locker.php | 8 ++ 6 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 res/composer-lock-schema.json diff --git a/res/composer-lock-schema.json b/res/composer-lock-schema.json new file mode 100644 index 000000000000..b1ef31c2bfce --- /dev/null +++ b/res/composer-lock-schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Lock File", + "type": "object", + "required": [ "content-hash", "packages", "packages-dev" ], + "additionalProperties": true, + "properties": { + "_readme": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational text for humans reading the file" + }, + "content-hash": { + "type": "string", + "description": "Hash of all relevant properties of the composer.json that was used to create this lock file." + }, + "packages": { + "type": "array", + "description": "An array of packages that are required.", + "items": { + "$ref": "./composer-schema.json", + "required": ["name", "version"] + } + }, + "packages-dev": { + "type": "array", + "description": "An array of packages that are required in require-dev.", + "items": { + "$ref": "./composer-schema.json" + } + }, + "aliases": { + "type": "array", + "description": "Inline aliases defined in the root package.", + "items": { + "type": "object", + "required": [ "package", "version", "alias", "alias_normalized" ], + "properties": { + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "alias_normalized": { + "type": "string" + } + } + } + }, + "minimum-stability": { + "type": "string", + "description": "The minimum-stability used to generate this lock file." + }, + "stability-flags": { + "type": "object", + "description": "Root package stability flags changing the minimum-stability for specific packages.", + "additionalProperties": { + "type": "integer" + } + }, + "prefer-stable": { + "type": "boolean", + "description": "Whether the --prefer-stable flag was used when building this lock file." + }, + "prefer-lowest": { + "type": "boolean", + "description": "Whether the --prefer-lowest flag was used when building this lock file." + }, + "platform": { + "type": "object", + "description": "Platform requirements of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "platform-dev": { + "type": "object", + "description": "Platform dev-requirements of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "platform-overrides": { + "type": "object", + "description": "Platform config overrides of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "plugin-api-version": { + "type": "string", + "description": "The composer-plugin-api version that was used to generate this lock file." + } + } +} diff --git a/res/composer-repository-schema.json b/res/composer-repository-schema.json index adcc299d662a..223f63abf52a 100644 --- a/res/composer-repository-schema.json +++ b/res/composer-repository-schema.json @@ -1,11 +1,12 @@ { "$schema": "https://json-schema.org/draft-04/schema#", - "description": "A representation of packages metadata.", + "title": "Composer Package Repository", "type": "object", "oneOf": [ { "required": [ "packages" ] }, { "required": [ "providers" ] }, - { "required": [ "provider-includes", "providers-url" ] } + { "required": [ "provider-includes", "providers-url" ] }, + { "required": [ "metadata-url" ] } ], "properties": { "packages": { @@ -13,31 +14,124 @@ "description": "A hashmap of package names in the form of /.", "additionalProperties": { "$ref": "#/definitions/versions" } }, + "metadata-url": { + "type": "string", + "description": "Endpoint to retrieve package metadata data from, in Composer v2 format, e.g. '/p2/%package%.json'." + }, + "available-packages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify all the package names that your repository contains here." + }, + "available-package-patterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify package name patterns containing wildcards (*) that your repository contains here." + }, + "security-advisories": { + "type": "array", + "items": { + "type": "object", + "required": ["metadata", "api-url"], + "properties": { + "metadata": { + "type": "boolean", + "description": "Whether metadata files contain security advisory data or whether it should always be queried using the API URL." + }, + "api-url": { + "type": "string", + "description": "Endpoint to call to retrieve security advisories data." + } + } + } + }, + "metadata-changes-url": { + "type": "string", + "description": "Endpoint to retrieve package metadata updates from. This should receive a timestamp since last call to be able to return new changes. e.g. '/metadata/changes.json'." + }, + "providers-api": { + "type": "string", + "description": "Endpoint to retrieve package names providing a given name from, e.g. '/providers/%package%.json'." + }, + "notify-batch": { + "type": "string", + "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'." + }, + "search": { + "type": "string", + "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'." + }, + "list": { + "type": "string", + "description": "Endpoint that provides a full list of packages present in the repository. It should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. e.g. '/list.json'." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "required": ["message", "versions"], + "properties": { + "message": { + "type": "string", + "description": "A message that will be output by Composer as a warning when this source is consulted." + }, + "versions": { + "type": "string", + "description": "A version constraint to limit to which Composer versions the warning should be shown." + } + } + } + }, + "infos": { + "type": "array", + "items": { + "type": "object", + "required": ["message", "versions"], + "properties": { + "message": { + "type": "string", + "description": "A message that will be output by Composer as info when this source is consulted." + }, + "versions": { + "type": "string", + "description": "A version constraint to limit to which Composer versions the info should be shown." + } + } + } + }, "providers-url": { "type": "string", - "description": "Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'." + "description": "DEPRECATED: Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'." }, "provider-includes": { "type": "object", - "description": "A hashmap of provider listings.", + "description": "DEPRECATED: A hashmap of provider listings.", "additionalProperties": { "$ref": "#/definitions/provider" } }, "providers": { "type": "object", - "description": "A hashmap of package names in the form of /.", + "description": "DEPRECATED: A hashmap of package names in the form of /.", "additionalProperties": { "$ref": "#/definitions/provider" } }, - "notify-batch": { + "warning": { "type": "string", - "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'." + "description": "DEPRECATED: A message that will be output by Composer as a warning when this source is consulted." }, - "search": { + "warning-versions": { "type": "string", - "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'." + "description": "DEPRECATED: A version constraint to limit to which Composer versions the warning should be shown." }, - "warning": { + "info": { + "type": "string", + "description": "DEPRECATED: A message that will be output by Composer as a info when this source is consulted." + }, + "info-versions": { "type": "string", - "description": "A message that will be output by Composer as a warning when this source is consulted." + "description": "DEPRECATED: A version constraint to limit to which Composer versions the info should be shown." } }, "definitions": { diff --git a/res/composer-schema.json b/res/composer-schema.json index ba62d21a48c3..9f76bc4b7d54 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft-04/schema#", - "title": "Package", + "title": "Composer Package", "type": "object", "properties": { "name": { diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index cbd4a7c600f1..d06ad4497e64 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -19,6 +19,8 @@ use Composer\Downloader\TransportException; use Composer\IO\BufferIO; use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; +use Composer\Package\Locker; use Composer\Package\RootPackage; use Composer\Package\Version\VersionParser; use Composer\Pcre\Preg; @@ -89,6 +91,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->write('Checking composer.json: ', false); $this->outputResult($this->checkComposerSchema()); + + if ($composer->getLocker()->isLocked()) { + $io->write('Checking composer.lock: ', false); + $this->outputResult($this->checkComposerLockSchema($composer->getLocker())); + } + $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io); } else { $this->process = new ProcessExecutor($io); @@ -267,6 +275,27 @@ private function checkComposerSchema() return true; } + /** + * @return string|true + */ + private function checkComposerLockSchema(Locker $locker) + { + $json = $locker->getJsonFile(); + + try { + $json->validateSchema(JsonFile::LOCK_SCHEMA); + } catch (JsonValidationException $e) { + $output = ''; + foreach ($e->getErrors() as $error) { + $output .= ''.$error.''.PHP_EOL; + } + + return trim($output); + } + + return true; + } + private function checkGit(): string { if (!function_exists('proc_open')) { diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index df4067e265c0..02ce5196c810 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -32,6 +32,7 @@ class JsonFile public const LAX_SCHEMA = 1; public const STRICT_SCHEMA = 2; public const AUTH_SCHEMA = 3; + public const LOCK_SCHEMA = 4; /** @deprecated Use \JSON_UNESCAPED_SLASHES */ public const JSON_UNESCAPED_SLASHES = 64; @@ -41,6 +42,7 @@ class JsonFile public const JSON_UNESCAPED_UNICODE = 256; public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; + public const LOCK_SCHEMA_PATH = __DIR__ . '/../../../res/composer-lock-schema.json'; public const INDENT_DEFAULT = ' '; @@ -228,8 +230,12 @@ public static function validateJsonSchema(string $source, $data, int $schema, ?s { $isComposerSchemaFile = false; if (null === $schemaFile) { - $isComposerSchemaFile = true; - $schemaFile = self::COMPOSER_SCHEMA_PATH; + if ($schema === self::LOCK_SCHEMA) { + $schemaFile = self::LOCK_SCHEMA_PATH; + } else { + $isComposerSchemaFile = true; + $schemaFile = self::COMPOSER_SCHEMA_PATH; + } } // Prepend with file:// only when not using a special schema already (e.g. in the phar) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index dc33a1123379..5b5ec466c1fd 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -73,6 +73,14 @@ public function __construct(IOInterface $io, JsonFile $lockFile, InstallationMan $this->process = $process ?? new ProcessExecutor($io); } + /** + * @internal + */ + public function getJsonFile(): JsonFile + { + return $this->lockFile; + } + /** * Returns the md5 hash of the sorted content of the composer file. * From 3a503d180532cd93db8941d52b8f72bd549b6b1e Mon Sep 17 00:00:00 2001 From: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:09:23 +0200 Subject: [PATCH 197/257] Update documentation for validate command (#12137) --- doc/03-cli.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 88db063c366c..1d4fc2933154 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -729,8 +729,10 @@ packages depending on the packages that cause the conflict. ## validate You should always run the `validate` command before you commit your -`composer.json` file, and before you tag a release. It will check if your -`composer.json` is valid. +`composer.json` file (and `composer.lock` [if applicable](01-basic-usage.md#commit-your-composer-lock-file-to-version-control)), and before you tag a release. + +It will check if your +`composer.json` is valid. If a `composer.lock` exists, it will also check if it is up to date with the `composer.json`. ```shell php composer.phar validate @@ -740,7 +742,9 @@ php composer.phar validate * **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound or overly strict version constraints. * **--no-check-lock:** Do not emit an error if `composer.lock` exists and is not up to date. +* **--check-lock** Check if lock file is up to date (even when [config.lock](06-config.md#lock) is false) * **--no-check-publish:** Do not emit an error if `composer.json` is unsuitable for publishing as a package on Packagist but is otherwise valid. +* **--no-check-version:** Do not emit an error if the version field is present. * **--with-dependencies:** Also validate the composer.json of all installed dependencies. * **--strict:** Return a non-zero exit code for warnings as well as errors. From 8728a75930805b56b623f9d4dab6ce3c66bf5a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Wed, 2 Oct 2024 12:28:32 +0200 Subject: [PATCH 198/257] Fix typos (#12133) --- CHANGELOG.md | 4 ++-- doc/articles/authentication-for-private-packages.md | 2 +- src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Repository/Vcs/GitLabDriver.php | 2 +- tests/Composer/Test/Command/InitCommandTest.php | 6 +++--- tests/Composer/Test/Console/HtmlOutputFormatterTest.php | 4 ++-- .../Test/Fixtures/installer/github-issues-4319.test | 2 +- .../Test/Fixtures/installer/github-issues-4795-2.test | 2 +- .../Test/Fixtures/installer/github-issues-4795.test | 2 +- .../Test/Fixtures/installer/github-issues-8902.test | 2 +- .../Test/Fixtures/installer/github-issues-8903.test | 2 +- .../Test/Fixtures/installer/github-issues-9012.test | 2 +- .../Test/Fixtures/installer/github-issues-9290.test | 2 +- .../Fixtures/installer/update-with-all-dependencies.test | 2 +- 15 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f46d54f053c5..0e9899be73b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -721,7 +721,7 @@ * UX Change: Packages from `path` repositories which are symlinked in the vendor dir will always be updated in partial updates to avoid mistakes when the original composer.json changes but the symlinked package is not explicitly updated (#9765) * Added `reinstall` command that takes one or more package names, including wildcard (`*`) support, and removes then reinstalls them in the exact same version they had (#9915) * Added support for parallel package installs on Windows via [7-Zip](https://www.7-zip.org/) if it is installed (#9875) - * Added detection of invalid composer.lock files that do not fullfil the composer.json requirements to `validate` command (#9899) + * Added detection of invalid composer.lock files that do not fulfill the composer.json requirements to `validate` command (#9899) * Added `InstalledVersions::getInstalledPackagesByType(string $type)` to retrieve installed plugins for example, [read more](https://getcomposer.org/doc/07-runtime.md#knowing-which-packages-of-a-given-type-are-installed) (#9699) * Added `InstalledVersions::getInstalledPath(string $packageName)` to retrieve the install path of a given package, [read more](https://getcomposer.org/doc/07-runtime.md#knowing-the-path-in-which-a-package-is-installed) (#9699) * Added flag to `InstalledVersions::isInstalled()` to allow excluding dev requirements from that check (#9682) @@ -1150,7 +1150,7 @@ * Added a `--no-cache` flag available on all commands to run with the cache disabled * Added PHP_BINARY as env var pointing to the PHP process when executing Composer scripts as shell scripts * Added a `use-github-api` config option which can set the `no-api` flag on all GitHub VCS repositories declared - * Added a static helper you can preprend to a script to avoid process timeouts, `"Composer\\Config::disableProcessTimeout"` + * Added a static helper you can prepend to a script to avoid process timeouts, `"Composer\\Config::disableProcessTimeout"` * Added Event::getOriginatingEvent to retrieve an event's original event when a script handler forwards to another one * Added support for autoloading directly from a phar file * Fixed loading order of plugins to always initialize them in order of dependencies diff --git a/doc/articles/authentication-for-private-packages.md b/doc/articles/authentication-for-private-packages.md index c1c186b3c013..b81cd636e961 100644 --- a/doc/articles/authentication-for-private-packages.md +++ b/doc/articles/authentication-for-private-packages.md @@ -296,7 +296,7 @@ php composer.phar config [--global] --editor --auth ## github-oauth -To create a new access token, head to your [token settings section on Github](https://github.com/settings/tokens) and [generate a new token](https://github.com/settings/tokens/new). +To create a new access token, head to your [token settings section on GitHub](https://github.com/settings/tokens) and [generate a new token](https://github.com/settings/tokens/new). For public repositories when rate limited, a token *without* any particular scope is sufficient (see `(no scope)` in the [scopes documentation](https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps)). Such tokens grant read-only access to public information. diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 6dc48757481e..de3bd367ef77 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -1025,7 +1025,7 @@ private function getAuthConfigFile(InputInterface $input, Config $config): strin } /** - * Suggest setting-keys, while taking given options in acount. + * Suggest setting-keys, while taking given options in account. */ private function suggestSettingKeys(): \Closure { diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index d06ad4497e64..263ee0644f4e 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -175,7 +175,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } elseif (10 > $rate['remaining']) { $io->write('WARNING'); $io->write(sprintf( - 'Github has a rate limit on their API. ' + 'GitHub has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 75304f46bf3e..09fb4258c3ca 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -107,7 +107,7 @@ public function initialize(): void ; $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); if (false === $origin) { - throw new \LogicException('It should not be possible to create a gitlab driver with an unparseable origin URL ('.$this->url.')'); + throw new \LogicException('It should not be possible to create a gitlab driver with an unparsable origin URL ('.$this->url.')'); } $this->originUrl = $origin; diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 67b1fd169627..3e50fbdb41eb 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -707,11 +707,11 @@ public function testInteractiveRun(): void $appTester->setInputs([ 'vendor/pkg', // Pkg name - 'my desciption', // Description + 'my description', // Description 'Mr. Test ', // Author 'stable', // Minimum stability 'library', // Type - 'AGPL-3.0-only', // License + 'AGPL-3.0-only', // License 'no', // Define dependencies 'no', // Define dev dependencies 'n', // Add PSR-4 autoload mapping @@ -724,7 +724,7 @@ public function testInteractiveRun(): void $expected = [ 'name' => 'vendor/pkg', - 'description' => 'my desciption', + 'description' => 'my description', 'type' => 'library', 'license' => 'AGPL-3.0-only', 'authors' => [['name' => 'Mr. Test', 'email' => 'test@example.org']], diff --git a/tests/Composer/Test/Console/HtmlOutputFormatterTest.php b/tests/Composer/Test/Console/HtmlOutputFormatterTest.php index 4337fcf60bb9..0d8032410aeb 100644 --- a/tests/Composer/Test/Console/HtmlOutputFormatterTest.php +++ b/tests/Composer/Test/Console/HtmlOutputFormatterTest.php @@ -25,8 +25,8 @@ public function testFormatting(): void ]); self::assertEquals( - 'text green yellow black w/ yello bg', - $formatter->format('text green yellow black w/ yello bg') + 'text green yellow black w/ yellow bg', + $formatter->format('text green yellow black w/ yellow bg') ); } } diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test index dcc94dff237b..513e800678f0 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #4319 ( github.com/composer/composer/issues/4319 ). +See GitHub issue #4319 ( github.com/composer/composer/issues/4319 ). Present a clear error message when config.platform.php version results in a conflict rule. diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test index a0c8452b9e80..fa8e4529fa64 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #4795 ( github.com/composer/composer/issues/4795 ). +See GitHub issue #4795 ( github.com/composer/composer/issues/4795 ). Composer\Installer::allowListUpdateDependencies should not output a warning for dependencies that need to be updated that are also a root package, when that root package is also explicitly allowed. diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test index 4e97d804b8f3..9044481a3da7 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #4795 ( github.com/composer/composer/issues/4795 ). +See GitHub issue #4795 ( github.com/composer/composer/issues/4795 ). Composer\Installer::allowListUpdateDependencies intentionally ignores root requirements even if said package is also a dependency of one the requirements that is allowed for update. diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-8902.test b/tests/Composer/Test/Fixtures/installer/github-issues-8902.test index bc25155255ff..56b4299d0765 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-8902.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-8902.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #8902 ( https://github.com/composer/composer/issues/8902 ). +See GitHub issue #8902 ( https://github.com/composer/composer/issues/8902 ). Avoid installing packages twice if they are required in different versions and one is matched by a dev package. diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-8903.test b/tests/Composer/Test/Fixtures/installer/github-issues-8903.test index f2c8c9f202bb..90ef85ef4507 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-8903.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-8903.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #8903 ( https://github.com/composer/composer/issues/8903 ). +See GitHub issue #8903 ( https://github.com/composer/composer/issues/8903 ). Recursive output of learnt rules can lead to infinite loop. diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-9012.test b/tests/Composer/Test/Fixtures/installer/github-issues-9012.test index f118584d1733..69d1f51e9ac8 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-9012.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-9012.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #9012 ( https://github.com/composer/composer/issues/9012 and https://gist.github.com/Seldaek/4e2dbc2cea4b4fd7a8207bb310ec8e34). +See GitHub issue #9012 ( https://github.com/composer/composer/issues/9012 and https://gist.github.com/Seldaek/4e2dbc2cea4b4fd7a8207bb310ec8e34). Recursive analysis of the same learnt rules can lead to infinite recursion in solver. diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-9290.test b/tests/Composer/Test/Fixtures/installer/github-issues-9290.test index 8c1122b14b87..af9ea4596eff 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-9290.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-9290.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #9290 ( https://github.com/composer/composer/issues/9290 ). +See GitHub issue #9290 ( https://github.com/composer/composer/issues/9290 ). MultiConflictRule with a level 1 decision needs to exit correctly. --COMPOSER-- diff --git a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test index e63d3daab139..2a0cc5222967 100644 --- a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test @@ -1,6 +1,6 @@ --TEST-- -See Github issue #6661 ( github.com/composer/composer/issues/6661 ). +See GitHub issue #6661 ( github.com/composer/composer/issues/6661 ). When `--with-all-dependencies` is used, Composer should update the dependencies of all allowed packages, even if the dependency is a root requirement. From 469a156b5951781f4a6fb2c088051420fde8110b Mon Sep 17 00:00:00 2001 From: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:52:57 +0200 Subject: [PATCH 199/257] Add dry-run argument to dump-autoload documentation (#12138) --- doc/03-cli.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 1d4fc2933154..bdf95c854cf1 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -983,6 +983,7 @@ performance. * **--apcu:** Use APCu to cache found/not-found classes. * **--apcu-prefix:** Use a custom prefix for the APCu autoloader cache. Implicitly enables `--apcu`. +* **--dry-run:** Outputs the operations but will not execute anything. * **--no-dev:** Disables autoload-dev rules. Composer will by default infer this automatically according to the last `install` or `update` `--no-dev` state. * **--dev:** Enables autoload-dev rules. Composer will by default infer this From 8949f91117df8670b6f96ba8b12b05d48ceaf9dd Mon Sep 17 00:00:00 2001 From: Johnson Page Date: Wed, 2 Oct 2024 21:22:24 +1000 Subject: [PATCH 200/257] Fix deprecated ParametersAcceptorSelector::selectSingle (#12136) * Fix deprecated ParametersAcceptorSelector::selectSingle * Simplify extension code --------- Co-authored-by: Jordi Boggiano --- src/Composer/PHPStan/ConfigReturnTypeExtension.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Composer/PHPStan/ConfigReturnTypeExtension.php b/src/Composer/PHPStan/ConfigReturnTypeExtension.php index 756ad24b56e5..88cd635c58fc 100644 --- a/src/Composer/PHPStan/ConfigReturnTypeExtension.php +++ b/src/Composer/PHPStan/ConfigReturnTypeExtension.php @@ -61,14 +61,12 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return strtolower($methodReflection->getName()) === 'get'; } - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { $args = $methodCall->getArgs(); - $defaultReturn = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - if (count($args) < 1) { - return $defaultReturn; + return null; } $keyType = $scope->getType($args[0]->value); @@ -82,7 +80,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $types = []; foreach($strings as $string) { if (!isset($this->properties[$string->getValue()])) { - return $defaultReturn; + return null; } $types[] = $this->properties[$string->getValue()]; } @@ -90,7 +88,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return TypeCombinator::union(...$types); } - return $defaultReturn; + return null; } /** From edb1588f68cfad4e95f0c8c03e56acda2dc0ac16 Mon Sep 17 00:00:00 2001 From: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:25:46 +0200 Subject: [PATCH 201/257] Handle dump-autoload where vendor folder is not installed or not complete (#12139) * Handle dump-autoload where vendor folder is not installed or not complete * Fix implementation issues The package could theoretically be a single file not a directory --------- Co-authored-by: Jordi Boggiano --- src/Composer/Command/DumpAutoloadCommand.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 6e6b56a5ac61..2e3181a27b0f 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\Package\AliasPackage; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; @@ -67,6 +68,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $package = $composer->getPackage(); $config = $composer->getConfig(); + $missingDependencies = false; + foreach ($localRepo->getCanonicalPackages() as $localPkg) { + $installPath = $installationManager->getInstallPath($localPkg); + if ($installPath !== null && file_exists($installPath) === false) { + $missingDependencies = true; + $this->getIO()->write('Not all dependencies are installed. Make sure to run a "composer install" to install missing dependencies'); + + break; + } + } + $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-prefix'); @@ -124,7 +136,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->getIO()->write('Generated autoload files'); } - if ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0) { + if ($missingDependencies || ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0)) { return 1; } From 58e8da795a472286de2dd256400520993dd49725 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 11:41:09 +0200 Subject: [PATCH 202/257] Update deps --- composer.lock | 98 +++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/composer.lock b/composer.lock index 4595f9519280..aa106a018af9 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a" + "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/063d9aa8696582f5a41dffbbaf3c81024f0a604a", - "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/48a792895a2b7a6ee65dd5442c299d7b835b6137", + "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137", "shasum": "" }, "require": { @@ -27,8 +27,8 @@ }, "require-dev": { "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/phpunit-bridge": "^4.2 || ^5", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.2" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2024-07-08T15:28:20+00:00" + "time": "2024-09-25T07:49:53+00:00" }, { "name": "composer/class-map-generator", @@ -941,16 +941,16 @@ }, { "name": "symfony/console", - "version": "v5.4.43", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" + "reference": "5b5a0aa66e3296e303e22490f90f521551835a83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", - "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", + "url": "https://api.github.com/repos/symfony/console/zipball/5b5a0aa66e3296e303e22490f90f521551835a83", + "reference": "5b5a0aa66e3296e303e22490f90f521551835a83", "shasum": "" }, "require": { @@ -1020,7 +1020,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.43" + "source": "https://github.com/symfony/console/tree/v5.4.44" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T16:31:56+00:00" + "time": "2024-09-20T07:56:40+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1107,16 +1107,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.41", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e" + "reference": "76c3818964e9d32be3862c9318ae3ba9aa280ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6d29dd9340b372fa603f04e6df4dd76bb808591e", - "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/76c3818964e9d32be3862c9318ae3ba9aa280ddc", + "reference": "76c3818964e9d32be3862c9318ae3ba9aa280ddc", "shasum": "" }, "require": { @@ -1154,7 +1154,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.41" + "source": "https://github.com/symfony/filesystem/tree/v5.4.44" }, "funding": [ { @@ -1170,7 +1170,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:36:24+00:00" + "time": "2024-09-16T14:52:48+00:00" }, { "name": "symfony/finder", @@ -1787,16 +1787,16 @@ }, { "name": "symfony/process", - "version": "v5.4.40", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" + "reference": "1b9fa82b5c62cd49da8c9e3952dd8531ada65096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", - "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "url": "https://api.github.com/repos/symfony/process/zipball/1b9fa82b5c62cd49da8c9e3952dd8531ada65096", + "reference": "1b9fa82b5c62cd49da8c9e3952dd8531ada65096", "shasum": "" }, "require": { @@ -1829,7 +1829,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.40" + "source": "https://github.com/symfony/process/tree/v5.4.44" }, "funding": [ { @@ -1845,7 +1845,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-09-17T12:46:43+00:00" }, { "name": "symfony/service-contracts", @@ -1932,16 +1932,16 @@ }, { "name": "symfony/string", - "version": "v5.4.43", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" + "reference": "832caa16b6d9aac6bf11747315225f5aba384c24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", - "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "url": "https://api.github.com/repos/symfony/string/zipball/832caa16b6d9aac6bf11747315225f5aba384c24", + "reference": "832caa16b6d9aac6bf11747315225f5aba384c24", "shasum": "" }, "require": { @@ -1998,7 +1998,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.43" + "source": "https://github.com/symfony/string/tree/v5.4.44" }, "funding": [ { @@ -2014,22 +2014,22 @@ "type": "tidelift" } ], - "time": "2024-08-01T10:24:28+00:00" + "time": "2024-09-20T07:56:40+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.4", + "version": "1.12.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd" + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa517cb918591b93acc9b95c0bebdcd0e4538bd", - "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-09-19T07:58:01+00:00" + "time": "2024-09-26T12:45:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2177,21 +2177,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "daeec748b53de80a97498462513066834ec28f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/daeec748b53de80a97498462513066834ec28f8b", + "reference": "daeec748b53de80a97498462513066834ec28f8b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12.4" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -2220,22 +2220,22 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.1" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2024-09-20T14:04:44+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.9", + "version": "1.4.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9" + "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9", - "reference": "51ab2438fb2695467cf96b58d2f8f28d4dd1e3e9", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f7d5782044bedf93aeb3f38e09c91148ee90e5a1", + "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1", "shasum": "" }, "require": { @@ -2292,9 +2292,9 @@ "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.4.9" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.10" }, - "time": "2024-09-05T16:15:09+00:00" + "time": "2024-09-26T18:14:50+00:00" }, { "name": "symfony/phpunit-bridge", From 5b256070b7c494f5d9a2554128b51739220a7de8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 13:26:08 +0200 Subject: [PATCH 203/257] Tweak output of VcsRepo to be less verbose --- src/Composer/Repository/VcsRepository.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 57535295479e..a5b17b517807 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -217,11 +217,6 @@ protected function initialize() foreach ($driver->getTags() as $tag => $identifier) { $tag = (string) $tag; $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; - if ($isVeryVerbose) { - $this->io->writeError($msg); - } elseif ($isVerbose) { - $this->io->overwriteError($msg, false); - } // strip the release- prefix from tags if present $tag = str_replace('release-', '', $tag); @@ -245,6 +240,12 @@ protected function initialize() continue; } + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + try { $data = $driver->getComposerInformation($identifier); if (null === $data) { From 31d83b2c0f19e878eaa3bd057e4c1972f90a482d Mon Sep 17 00:00:00 2001 From: Johnson Page Date: Wed, 2 Oct 2024 22:14:53 +1000 Subject: [PATCH 204/257] Add `composer audit --ignore-severity` option (#12132) Co-authored-by: Jordi Boggiano --- doc/03-cli.md | 3 +- src/Composer/Advisory/Auditor.php | 20 +++--- src/Composer/Command/AuditCommand.php | 7 ++- tests/Composer/Test/Advisory/AuditorTest.php | 66 +++++++++++++++++--- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index bdf95c854cf1..7041617ad683 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1087,7 +1087,8 @@ php composer.phar audit * **--abandoned:** Behavior on abandoned packages. Must be "ignore", "report", or "fail". See also [audit.abandoned](06-config.md#abandoned). Passing this flag will override the config value and the environment variable. - +* **--ignore-severity:** Ignore advisories of a certain severity level. Can be passed one or more + time to ignore multiple severities. ## help diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index bfd62a0877f6..de8034bad007 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -19,7 +19,6 @@ use Composer\Package\PackageInterface; use Composer\Repository\RepositorySet; use Composer\Util\PackageInfo; -use Composer\Util\Platform; use InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; @@ -60,11 +59,12 @@ class Auditor * @param bool $warningOnly If true, outputs a warning. If false, outputs an error. * @param string[] $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities. * @param self::ABANDONED_* $abandoned + * @param array $ignoredSeverities List of ignored severity levels * * @return int Amount of packages with vulnerabilities found * @throws InvalidArgumentException If no packages are passed in */ - public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL): int + public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL, array $ignoredSeverities = []): int { $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY); // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above @@ -72,7 +72,7 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, if (count($allAdvisories) > 0 && $ignoreList !== [] && $format === self::FORMAT_SUMMARY) { $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, false); } - ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList); + ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities); $abandonedCount = 0; $affectedPackagesCount = 0; @@ -90,8 +90,9 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, if ($ignoredAdvisories !== []) { $json['ignored-advisories'] = $ignoredAdvisories; } - $json['abandoned'] = array_reduce($abandonedPackages, static function(array $carry, CompletePackageInterface $package): array { + $json['abandoned'] = array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package): array { $carry[$package->getPrettyName()] = $package->getReplacementPackage(); + return $carry; }, []); @@ -146,11 +147,12 @@ private function filterAbandonedPackages(array $packages): array /** * @phpstan-param array> $allAdvisories * @param array|array $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities. + * @param array $ignoredSeverities List of ignored severity levels * @phpstan-return array{advisories: array>, ignoredAdvisories: array>} */ - private function processAdvisories(array $allAdvisories, array $ignoreList): array + private function processAdvisories(array $allAdvisories, array $ignoreList, array $ignoredSeverities): array { - if ($ignoreList === []) { + if ($ignoreList === [] && $ignoredSeverities === []) { return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []]; } @@ -174,6 +176,11 @@ private function processAdvisories(array $allAdvisories, array $ignoreList): arr } if ($advisory instanceof SecurityAdvisory) { + if (in_array($advisory->severity, $ignoredSeverities, true)) { + $isActive = false; + $ignoreReason = "Ignored via --ignore-severity={$advisory->severity}"; + } + if (in_array($advisory->cve, $ignoredIds, true)) { $isActive = false; $ignoreReason = $ignoreList[$advisory->cve] ?? null; @@ -394,5 +401,4 @@ private function getURL(SecurityAdvisory $advisory): string return 'link).'>'.OutputFormatter::escape($advisory->link).''; } - } diff --git a/src/Composer/Command/AuditCommand.php b/src/Composer/Command/AuditCommand.php index c0b0dcfadec5..e4a2094b8b50 100644 --- a/src/Composer/Command/AuditCommand.php +++ b/src/Composer/Command/AuditCommand.php @@ -34,6 +34,7 @@ protected function configure(): void new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS), new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'), new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, Auditor::ABANDONEDS), + new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']), ]) ->setHelp( <<getOption('ignore-severity') ?? []; + return min(255, $auditor->audit( $this->getIO(), $repoSet, @@ -80,8 +83,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->getAuditFormat($input, 'format'), false, $auditConfig['ignore'] ?? [], - $abandoned + $abandoned, + $ignoreSeverities )); + } /** diff --git a/tests/Composer/Test/Advisory/AuditorTest.php b/tests/Composer/Test/Advisory/AuditorTest.php index 5e66951a0a37..cc9efb985559 100644 --- a/tests/Composer/Test/Advisory/AuditorTest.php +++ b/tests/Composer/Test/Advisory/AuditorTest.php @@ -15,7 +15,6 @@ use Composer\Advisory\PartialSecurityAdvisory; use Composer\Advisory\SecurityAdvisory; use Composer\IO\BufferIO; -use Composer\IO\NullIO; use Composer\Package\CompletePackage; use Composer\Package\Package; use Composer\Package\Version\VersionParser; @@ -23,7 +22,6 @@ use Composer\Repository\RepositorySet; use Composer\Test\TestCase; use Composer\Advisory\Auditor; -use Composer\Util\Platform; use InvalidArgumentException; class AuditorTest extends TestCase @@ -162,7 +160,8 @@ public function testAudit(array $data, int $expected, string $output): void self::assertSame($output, trim(str_replace("\r", '', $io->getOutput()))); } - public function ignoredIdsProvider(): \Generator { + public function ignoredIdsProvider(): \Generator + { yield 'ignore by CVE' => [ [ new Package('vendor1/package1', '3.0.0.0', '3.0.0'), @@ -178,7 +177,7 @@ public function ignoredIdsProvider(): \Generator { ['text' => 'URL: https://advisory.example.com/advisory1'], ['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'], ['text' => 'Reported at: 2022-05-25T13:21:00+00:00'], - ] + ], ]; yield 'ignore by CVE with reasoning' => [ [ @@ -196,7 +195,7 @@ public function ignoredIdsProvider(): \Generator { ['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'], ['text' => 'Reported at: 2022-05-25T13:21:00+00:00'], ['text' => 'Ignore reason: A good reason'], - ] + ], ]; yield 'ignore by advisory id' => [ [ @@ -213,7 +212,7 @@ public function ignoredIdsProvider(): \Generator { ['text' => 'URL: https://advisory.example.com/advisory2'], ['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'], ['text' => 'Reported at: 2022-05-25T13:21:00+00:00'], - ] + ], ]; yield 'ignore by remote id' => [ [ @@ -230,7 +229,7 @@ public function ignoredIdsProvider(): \Generator { ['text' => 'URL: https://advisory.example.com/advisory17'], ['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'], ['text' => 'Reported at: 2015-05-25T13:21:00+00:00'], - ] + ], ]; yield '1 vulnerability, 0 ignored' => [ [ @@ -247,7 +246,7 @@ public function ignoredIdsProvider(): \Generator { ['text' => 'URL: https://advisory.example.com/advisory1'], ['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'], ['text' => 'Reported at: 2022-05-25T13:21:00+00:00'], - ] + ], ]; yield '1 vulnerability, 3 ignored affecting 2 packages' => [ [ @@ -295,7 +294,7 @@ public function ignoredIdsProvider(): \Generator { ['text' => 'URL: https://advisory.example.com/advisory7'], ['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'], ['text' => 'Reported at: 2015-05-25T13:21:00+00:00'], - ] + ], ]; } @@ -314,6 +313,55 @@ public function testAuditWithIgnore($packages, $ignoredIds, $exitCode, $expected self::assertSame($exitCode, $result); } + public function ignoreSeverityProvider(): \Generator + { + yield 'ignore medium' => [ + [ + new Package('vendor1/package1', '2.0.0.0', '2.0.0'), + ], + ['medium'], + 1, + [ + ['text' => 'Found 2 ignored security vulnerability advisories affecting 1 package:'], + ], + ]; + yield 'ignore high' => [ + [ + new Package('vendor1/package1', '2.0.0.0', '2.0.0'), + ], + ['high'], + 1, + [ + ['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'], + ], + ]; + yield 'ignore high and medium' => [ + [ + new Package('vendor1/package1', '2.0.0.0', '2.0.0'), + ], + ['high', 'medium'], + 0, + [ + ['text' => 'Found 3 ignored security vulnerability advisories affecting 1 package:'], + ], + ]; + } + + /** + * @dataProvider ignoreSeverityProvider + * @phpstan-param array<\Composer\Package\Package> $packages + * @phpstan-param array $ignoredSeverities + * @phpstan-param 0|positive-int $exitCode + * @phpstan-param list $expectedOutput + */ + public function testAuditWithIgnoreSeverity($packages, $ignoredSeverities, $exitCode, $expectedOutput): void + { + $auditor = new Auditor(); + $result = $auditor->audit($io = $this->getIOMock(), $this->getRepoSet(), $packages, Auditor::FORMAT_PLAIN, false, [], Auditor::ABANDONED_IGNORE, $ignoredSeverities); + $io->expects($expectedOutput, true); + self::assertSame($exitCode, $result); + } + private function getRepoSet(): RepositorySet { $repo = $this From a01ab9bbca5c72b16e2a58bc324e9e5d4fb8f972 Mon Sep 17 00:00:00 2001 From: Eirik Stanghelle Morland Date: Wed, 2 Oct 2024 14:36:30 +0200 Subject: [PATCH 205/257] Better app password support for bitbucket (#12103) * Fix support for app passwords better, plus better handling of bitbucket repositories stored with ssh --- phpstan/baseline-8.3.neon | 2 +- phpstan/baseline.neon | 4 +- src/Composer/Util/Git.php | 74 +++++++++---- tests/Composer/Test/Util/GitTest.php | 158 +++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 27 deletions(-) diff --git a/phpstan/baseline-8.3.neon b/phpstan/baseline-8.3.neon index 68bb1e533bbb..9a3a36933f70 100644 --- a/phpstan/baseline-8.3.neon +++ b/phpstan/baseline-8.3.neon @@ -182,7 +182,7 @@ parameters: - message: "#^Parameter \\#1 \\$string of function rawurlencode expects string, string\\|null given\\.$#" - count: 8 + count: 10 path: ../src/Composer/Util/Git.php - diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index dc754784e023..865d93b72d98 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -3290,7 +3290,7 @@ parameters: - message: "#^Parameter \\#1 \\$str of function rawurlencode expects string, string\\|null given\\.$#" - count: 8 + count: 10 path: ../src/Composer/Util/Git.php - @@ -4488,7 +4488,7 @@ parameters: - message: "#^Cannot access an offset on array\\\\>\\|false\\.$#" - count: 1 + count: 3 path: ../tests/Composer/Test/Util/GitTest.php - diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 5a949db70723..58df0ad5711f 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -32,6 +32,8 @@ class Git protected $process; /** @var Filesystem */ protected $filesystem; + /** @var HttpDownloader */ + protected $httpDownloader; public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs) { @@ -41,6 +43,11 @@ public function __construct(IOInterface $io, Config $config, ProcessExecutor $pr $this->filesystem = $fs; } + public function setHttpDownloader(HttpDownloader $httpDownloader): void + { + $this->httpDownloader = $httpDownloader; + } + /** * @param mixed $commandOutput the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler @@ -131,50 +138,69 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $errorMsg = $this->process->getErrorOutput(); } // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups - } elseif (Preg::isMatchStrictGroups('{^https://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match)) { //bitbucket oauth - $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process); - - if (!$this->io->hasAuthentication($match[1])) { + } elseif ( + Preg::isMatchStrictGroups('{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match) + || Preg::isMatchStrictGroups('{^(git)@(bitbucket\.org):(.+?\.git)$}i', $url, $match) + ) { //bitbucket either through oauth or app password, with fallback to ssh. + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); + + $domain = $match[2]; + $repo_with_git_part = $match[3]; + if (!str_ends_with($repo_with_git_part, '.git')) { + $repo_with_git_part .= '.git'; + } + if (!$this->io->hasAuthentication($domain)) { $message = 'Enter your Bitbucket credentials to access private repos'; - if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { + if (!$bitbucketUtil->authorizeOAuth($domain) && $this->io->isInteractive()) { $bitbucketUtil->authorizeOAuthInteractively($match[1], $message); $accessToken = $bitbucketUtil->getToken(); - $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); + $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); + } + } + + // First we try to authenticate with whatever we have stored. + // This will be successful if there is for example an app + // password in there. + if ($this->io->hasAuthentication($domain)) { + $auth = $this->io->getAuthentication($domain); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; + + $command = $commandCallable($authUrl); + if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + // Well if that succeeded on our first try, let's just + // take the win. + return; } - } else { //We're authenticating with a locally stored consumer. - $auth = $this->io->getAuthentication($match[1]); //We already have an access_token from a previous request. if ($auth['username'] !== 'x-token-auth') { - $accessToken = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); + $accessToken = $bitbucketUtil->requestToken($domain, $auth['username'], $auth['password']); if (!empty($accessToken)) { - $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); + $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); } } } - if ($this->io->hasAuthentication($match[1])) { - $auth = $this->io->getAuthentication($match[1]); - $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; - + if ($this->io->hasAuthentication($domain)) { + $auth = $this->io->getAuthentication($domain); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { return; } $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; - $errorMsg = $this->process->getErrorOutput(); - } else { // Falling back to ssh - $sshUrl = 'git@bitbucket.org:' . $match[2] . '.git'; - $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); - $command = $commandCallable($sshUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { - return; - } - - $errorMsg = $this->process->getErrorOutput(); } + //Falling back to ssh + $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part; + $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); + $command = $commandCallable($sshUrl); + if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + return; + } + + $errorMsg = $this->process->getErrorOutput(); } elseif ( // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) diff --git a/tests/Composer/Test/Util/GitTest.php b/tests/Composer/Test/Util/GitTest.php index 71e064544796..04e804d25438 100644 --- a/tests/Composer/Test/Util/GitTest.php +++ b/tests/Composer/Test/Util/GitTest.php @@ -14,6 +14,7 @@ use Composer\Config; use Composer\IO\IOInterface; +use Composer\Test\Mock\HttpDownloaderMock; use Composer\Util\Filesystem; use Composer\Util\Git; use Composer\Test\Mock\ProcessExecutorMock; @@ -126,6 +127,163 @@ public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteracti $this->git->runCommand($commandCallable, $gitUrl, null, true); } + /** + * @dataProvider privateBitbucketWithCredentialsProvider + */ + public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneNotInteractiveWithAuthentication(string $gitUrl, ?string $bitbucketToken, string $expectedUrl, int $expectedFailuresBeforeSuccess, int $bitbucket_git_auth_calls = 0): void + { + $commandCallable = static function ($url) use ($expectedUrl): string { + if ($url !== $expectedUrl) { + return 'git command failing'; + } + + return 'git command ok'; + }; + + $this->config + ->method('get') + ->willReturnMap([ + ['gitlab-domains', 0, ['gitlab.com']], + ['github-domains', 0, ['github.com']], + ]); + + $expectedCalls = array_fill(0, $expectedFailuresBeforeSuccess, ['cmd' => 'git command failing', 'return' => 1]); + if ($bitbucket_git_auth_calls > 0) { + // When we are testing what happens without auth saved, and URLs + // with https, there will also be an attempt to find the token in + // the git config for the folder and repo, locally. + $additional_calls = array_fill(0, $bitbucket_git_auth_calls, ['cmd' => 'git config bitbucket.accesstoken', 'return' => 1]); + foreach ($additional_calls as $call) { + $expectedCalls[] = $call; + } + } + $expectedCalls[] = ['cmd' => 'git command ok', 'return' => 0]; + + $this->process->expects($expectedCalls, true); + + $this->io + ->method('isInteractive') + ->willReturn(false); + + if (null !== $bitbucketToken) { + $this->io + ->expects($this->atLeastOnce()) + ->method('hasAuthentication') + ->with($this->equalTo('bitbucket.org')) + ->willReturn(true); + $this->io + ->expects($this->atLeastOnce()) + ->method('getAuthentication') + ->with($this->equalTo('bitbucket.org')) + ->willReturn(['username' => 'token', 'password' => $bitbucketToken]); + } + $this->git->runCommand($commandCallable, $gitUrl, null, true); + } + + /** + * @dataProvider privateBitbucketWithOauthProvider + * + * @param string $gitUrl + * @param string $expectedUrl + * @param array{'username': string, 'password': string}[] $initial_config + */ + public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneInteractiveWithOauth(string $gitUrl, string $expectedUrl, array $initial_config = []): void + { + $commandCallable = static function ($url) use ($expectedUrl): string { + if ($url !== $expectedUrl) { + return 'git command failing'; + } + + return 'git command ok'; + }; + + $expectedCalls = []; + $expectedCalls[] = ['cmd' => 'git command failing', 'return' => 1]; + if (count($initial_config) > 0) { + $expectedCalls[] = ['cmd' => 'git command failing', 'return' => 1]; + } else { + $expectedCalls[] = ['cmd' => 'git config bitbucket.accesstoken', 'return' => 1]; + } + $expectedCalls[] = ['cmd' => 'git command ok', 'return' => 0]; + $this->process->expects($expectedCalls, true); + + $this->config + ->method('get') + ->willReturnMap([ + ['gitlab-domains', 0, ['gitlab.com']], + ['github-domains', 0, ['github.com']], + ]); + + $this->io + ->method('isInteractive') + ->willReturn(true); + + $this->io + ->method('askConfirmation') + ->willReturnCallback(function () { + return true; + }); + $this->io->method('askAndHideAnswer') + ->willReturnCallback(function ($question) { + switch ($question) { + case 'Consumer Key (hidden): ': + return 'my-consumer-key'; + case 'Consumer Secret (hidden): ': + return 'my-consumer-secret'; + } + return ''; + }); + + $this->io + ->method('hasAuthentication') + ->with($this->equalTo('bitbucket.org')) + ->willReturnCallback(function ($repositoryName) use (&$initial_config) { + return isset($initial_config[$repositoryName]); + }); + $this->io + ->method('setAuthentication') + ->willReturnCallback(function (string $repositoryName, string $username, ?string $password = null) use (&$initial_config) { + $initial_config[$repositoryName] = ['username' => $username, 'password' => $password]; + }); + $this->io + ->method('getAuthentication') + ->willReturnCallback(function (string $repositoryName) use (&$initial_config) { + if (isset($initial_config[$repositoryName])) { + return $initial_config[$repositoryName]; + } + + return ['username' => null, 'password' => null]; + }); + $downloader_mock = $this->getHttpDownloaderMock(); + $downloader_mock->expects([ + ['url' => 'https://bitbucket.org/site/oauth2/access_token', 'status' => 200, 'body' => '{"expires_in": 600, "access_token": "my-access-token"}'] + ]); + $this->git->setHttpDownloader($downloader_mock); + $this->git->runCommand($commandCallable, $gitUrl, null, true); + } + + public static function privateBitbucketWithOauthProvider(): array + { + return [ + ['git@bitbucket.org:acme/repo.git', 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git'], + ['https://bitbucket.org/acme/repo.git', 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git'], + ['https://bitbucket.org/acme/repo', 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git'], + ['git@bitbucket.org:acme/repo.git', 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git', ['bitbucket.org' => ['username' => 'someuseralsoswappedfortoken', 'password' => 'little green men']]], + ]; + } + + public static function privateBitbucketWithCredentialsProvider(): array + { + return [ + ['git@bitbucket.org:acme/repo.git', 'MY_BITBUCKET_TOKEN', 'https://token:MY_BITBUCKET_TOKEN@bitbucket.org/acme/repo.git', 1], + ['https://bitbucket.org/acme/repo', 'MY_BITBUCKET_TOKEN', 'https://token:MY_BITBUCKET_TOKEN@bitbucket.org/acme/repo.git', 1], + ['https://bitbucket.org/acme/repo.git', 'MY_BITBUCKET_TOKEN', 'https://token:MY_BITBUCKET_TOKEN@bitbucket.org/acme/repo.git', 1], + ['git@bitbucket.org:acme/repo.git', null, 'git@bitbucket.org:acme/repo.git', 0], + ['https://bitbucket.org/acme/repo', null, 'git@bitbucket.org:acme/repo.git', 1, 1], + ['https://bitbucket.org/acme/repo.git', null, 'git@bitbucket.org:acme/repo.git', 1, 1], + ]; + } + public static function privateGithubWithCredentialsProvider(): array { return [ From 57e9795455428fa7d79190675d96869142fdef41 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 16:13:01 +0200 Subject: [PATCH 206/257] Fix issue downloading from codeload.github.com when using basic-auth for github, fixes #12126 --- src/Composer/Util/Url.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 040d17db04e6..32f61347b451 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -75,7 +75,7 @@ public static function getOrigin(Config $config, string $url): string $origin .= ':'.$port; } - if (strpos($origin, '.github.com') === (strlen($origin) - 11)) { + if (str_ends_with($origin, '.github.com') && $origin !== 'codeload.github.com') { return 'github.com'; } From c6271f1f7f12d908641327752a506445024c7824 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 16:21:16 +0200 Subject: [PATCH 207/257] Fix init command to accept proprietary as license --- src/Composer/Command/InitCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 0829eb09d090..71dd83f535a5 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -400,7 +400,7 @@ static function ($value) use ($minimumStability) { $license ); $spdx = new SpdxLicenses(); - if (!$spdx->validate($license)) { + if (!$spdx->validate($license) && $license !== 'proprietary') { throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.'); } $input->setOption('license', $license); From 71aa35ba31bc3655caf83728073d719ab141f914 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 16:29:44 +0200 Subject: [PATCH 208/257] Update changelog --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9899be73b7..130d926155dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +### [2.8.0] 2024-10-02 + + * BC Warning: Fixed `https_proxy` env var falling back to `http_proxy`'s value. The fallback and warning have now been removed per the 2.7.3 release notes (#11938, #11915) + * Added `--patch-only` flag to the `update` command to restrict updates to patch versions and make an update of all deps safer (#12122) + * Added `--abandoned` flag to the `audit` command to configure how abandoned packages should be treated, overriding the `audit.abandoned` config setting (#12091) + * Added `--ignore-severity` flag to the `audit` command to ignore one or more advisory severities (#12132) + * Added `--bump-after-update` flag to the `update` command to run bump after the update is done (#11942) + * Added a way to control which `scripts` receive additional CLI arguments and where they appear in the command, see [the docs](https://getcomposer.org/doc/articles/scripts.md#controlling-additional-arguments) (#12086) + * Added `allow-missing-requirements` config setting to skip the error when the lock file is not fulfilling the composer.json's dependencies (#11966) + * Added a JSON schema for the composer.lock file (#12123) + * Added better support for Bitbucket app passwords when cloning repos / installing from source (#12103) + * Added `--type` flag to filter packages by type(s) in the `reinstall` command (#12114) + * Added `--strict-ambiguous` flag to the `dump-autoload` command to make it return with an error code if duplicate classes are found (#12119) + * Added warning in `dump-autoload` when vendor files have been deleted (#12139) + * Added warnings for each missing platform package when running `create-project` to avoid having to run it again and again (#12120) + * Added sorting of packages in allow-plugins when `sort-packages` is enabled (#11348) + * Added suggestion of provider packages / polyfills when an ext or lib package is missing (#12113) + * Improved interactive package update selection by first outputting all packages and their possible updates (#11990) + * Improved dependency resolution failure output by sorting the output in a deterministic and (often) more logical way (#12111) + * Fixed PHP 8.4 deprecation warnings about `E_STRICT` (#12116) + * Fixed `init` command to validate the given license identifier (#12115) + * Fixed version guessing to be more deterministic on feature branches if it appears that it could come from either of two mainline branches (#12129) + * Fixed COMPOSER_ROOT_VERSION env var handling to treat 1.2 the same as 1.2.x-dev and not 1.2.0 (#12109) + * Fixed require command skipping new stability flags from the lock file, causing invalid lock file diffs (#12112) + * Fixed php://stdin potentially being open several times when running Composer programmatically (#12107) + * Fixed handling of platform packages in why-not command and partial updates (#12110) + * Reverted "Fixed transport-options.ssl for local cert authorization being stored in lock file making them less portable (#12019)" from 2.7.8 as it was broken + ### [2.7.9] 2024-09-04 * Fixed Docker detection breaking on constrained environments (#12095) @@ -1910,6 +1938,7 @@ * Initial release +[2.8.0]: https://github.com/composer/composer/compare/2.7.9...2.8.0 [2.7.9]: https://github.com/composer/composer/compare/2.7.8...2.7.9 [2.7.8]: https://github.com/composer/composer/compare/2.7.7...2.7.8 [2.7.7]: https://github.com/composer/composer/compare/2.7.6...2.7.7 From d5e75c21ceeb6829918a75ed63b56c576b4a6261 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 16:40:29 +0200 Subject: [PATCH 209/257] Release 2.8.0 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 07d6973334d4..4d1d6cd2a6b6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.8.999-dev+source'; + public const VERSION = '2.8.0'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-10-02 16:40:29'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From b3d20319e93a69a2cb20e83922e44fc9c00664d0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Oct 2024 16:40:29 +0200 Subject: [PATCH 210/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4d1d6cd2a6b6..07d6973334d4 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.8.0'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-10-02 16:40:29'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.8.999-dev+source'; /** * Version number of the internal composer-runtime-api package From d3f40ca52d289fec850216816606575020282ca1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 3 Oct 2024 14:05:32 +0200 Subject: [PATCH 211/257] Fix outdated command not forwarding some global options correctly --- src/Composer/Command/OutdatedCommand.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index 139fe45a85ec..0fea6dc092f1 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -71,6 +71,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'command' => 'show', '--latest' => true, ]; + if ($input->getOption('no-interaction')) { + $args['--no-interaction'] = true; + } + if ($input->getOption('no-plugins')) { + $args['--no-plugins'] = true; + } + if ($input->getOption('no-scripts')) { + $args['--no-scripts'] = true; + } + if ($input->getOption('no-cache')) { + $args['--no-cache'] = true; + } if (!$input->getOption('all')) { $args['--outdated'] = true; } From 4e3496e1bf0b398396947484de3945e5eeb95129 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 3 Oct 2024 14:15:23 +0200 Subject: [PATCH 212/257] Fix a few cases where the prompt for using a parent dir's composer.json fails to work correctly, fixes #8023 --- src/Composer/Console/Application.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 7c511f994de4..dcf777f83b7f 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -195,13 +195,29 @@ public function doRun(InputInterface $input, OutputInterface $output): int } // prompt user for dir change if no composer.json is present in current dir - if ($io->isInteractive() && null === $newWorkDir && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true) && !file_exists(Factory::getComposerFile()) && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false) { + if ( + null === $newWorkDir + // do not prompt for commands that can function without composer.json + && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true) + && !file_exists(Factory::getComposerFile()) + // if use-parent-dir is disabled we should not prompt + && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false + // config --file ... should not prompt + && ($commandName !== 'config' || ($input->hasParameterOption('--file', true) === false && $input->hasParameterOption('-f', true) === false)) + // calling a command's help should not prompt + && $input->hasParameterOption('--help', true) === false + && $input->hasParameterOption('-h', true) === false + ) { $dir = dirname(Platform::getCwd(true)); $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); // abort when we reach the home dir or top of the filesystem while (dirname($dir) !== $dir && $dir !== $home) { if (file_exists($dir.'/'.Factory::getComposerFile())) { + if ($useParentDirIfNoJsonAvailable !== true && !$io->isInteractive()) { + $io->writeError('No composer.json in current directory, to use the one at '.$dir.' run interactively or set config.use-parent-dir to true'); + break; + } if ($useParentDirIfNoJsonAvailable === true || $io->askConfirmation('No composer.json in current directory, do you want to use the one at '.$dir.'? [Y,n]? ')) { if ($useParentDirIfNoJsonAvailable === true) { $io->writeError('No composer.json in current directory, changing working directory to '.$dir.''); @@ -215,6 +231,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int } $dir = dirname($dir); } + unset($dir, $home); } $needsSudoCheck = !Platform::isWindows() From ad3bfb87fdd647d65627fbe24a5d90d8e4f3f5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dezs=C5=91=20BICZ=C3=93?= Date: Thu, 3 Oct 2024 16:17:30 +0200 Subject: [PATCH 213/257] Add version information to Controlling additional arguments doc (#12143) --- doc/articles/scripts.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 5ed3bf6c3e19..90dfa3672ce7 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -412,6 +412,8 @@ the PHP executable available in it as a `PHP_BINARY` env var. ## Controlling additional arguments +As of Composer 2.8, you can control how additional arguments are passed to script commands. + When running scripts like `composer script-name arg arg2` or `composer script-name -- --option`, Composer will by default append `arg`, `arg2` and `--option` to the script's command. From 66bf6d31c36c06919523fb2b5d5fe3088c809010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antal=20=C3=81ron?= Date: Thu, 3 Oct 2024 16:19:38 +0200 Subject: [PATCH 214/257] Allow init without license (#12145) --- src/Composer/Command/InitCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 71dd83f535a5..6d7311dd451e 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -400,7 +400,7 @@ static function ($value) use ($minimumStability) { $license ); $spdx = new SpdxLicenses(); - if (!$spdx->validate($license) && $license !== 'proprietary') { + if (null !== $license && !$spdx->validate($license) && $license !== 'proprietary') { throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.'); } $input->setOption('license', $license); From 33ffd5abc319053d174565b85261388124685676 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 11:13:17 +0200 Subject: [PATCH 215/257] Fix --strict-ambiguous to really report all issues, fixes #12140 (#12148) --- composer.json | 2 +- composer.lock | 18 +++++++++--------- src/Composer/Autoload/AutoloadGenerator.php | 9 +++++++-- src/Composer/Command/DumpAutoloadCommand.php | 5 +++-- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 060b41d9df32..a943cd7f9ae9 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require": { "php": "^7.2.5 || ^8.0", "composer/ca-bundle": "^1.5", - "composer/class-map-generator": "^1.3.3", + "composer/class-map-generator": "^1.4.0", "composer/metadata-minifier": "^1.0", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", diff --git a/composer.lock b/composer.lock index aa106a018af9..770ebda51bd3 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "d6685068b4f2660ec25263c8ecf9a367", + "content-hash": "7c2f8d4d04b8146b2fcd203cb81fa8d5", "packages": [ { "name": "composer/ca-bundle", @@ -84,16 +84,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.3.4", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3" + "reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3", - "reference": "b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/98bbf6780e56e0fd2404fe4b82eb665a0f93b783", + "reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783", "shasum": "" }, "require": { @@ -106,8 +106,8 @@ "phpstan/phpstan-deprecation-rules": "^1", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/filesystem": "^5.4 || ^6", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6" }, "type": "library", "extra": { @@ -137,7 +137,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.3.4" + "source": "https://github.com/composer/class-map-generator/tree/1.4.0" }, "funding": [ { @@ -153,7 +153,7 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:13:04+00:00" + "time": "2024-10-03T18:14:00+00:00" }, { "name": "composer/metadata-minifier", diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 2296a7eb1287..86c9f184d614 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -173,7 +173,7 @@ public function setPlatformRequirementFilter(PlatformRequirementFilterInterface * @throws \Seld\JsonLint\ParsingException * @throws \RuntimeException */ - public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null) + public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null, bool $strictAmbiguous = false) { if ($this->classMapAuthoritative) { // Force scanPsrPackages when classmap is authoritative @@ -362,7 +362,12 @@ public static function autoload(\$class) } $classMap = $classMapGenerator->getClassMap(); - foreach ($classMap->getAmbiguousClasses() as $className => $ambiguousPaths) { + if ($strictAmbiguous) { + $ambiguousClasses = $classMap->getAmbiguousClasses(false); + } else { + $ambiguousClasses = $classMap->getAmbiguousClasses(); + } + foreach ($ambiguousClasses as $className => $ambiguousPaths) { if (count($ambiguousPaths) > 1) { $this->io->writeError( 'Warning: Ambiguous class resolution, "'.$className.'"'. diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 2e3181a27b0f..cc0d7bf8079d 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -124,7 +124,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'composer', $optimize, null, - $composer->getLocker() + $composer->getLocker(), + $input->getOption('strict-ambiguous') ); $numberOfClasses = count($classMap); @@ -140,7 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } - if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses()) > 0) { + if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses(false)) > 0) { return 2; } From ac2f89a05bfbadba3bf29262a57d17e1fd9912f1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 11:19:36 +0200 Subject: [PATCH 216/257] Fix create-project to reuse the target folders permissions for files created (#12146) * Fix create-project to reuse the target folders permissions for files created Fixes #9039 * Fix issue when COMPOSER_VENDOR_DIR env is set * Allow null in setBaseDir --- src/Composer/Command/CreateProjectCommand.php | 32 +++++++++++-------- src/Composer/Config.php | 16 ++++++++-- src/Composer/Repository/RepositoryFactory.php | 2 +- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 6cc35ebe2398..0454efa59ca9 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -319,14 +319,6 @@ public function installProject(IOInterface $io, Config $config, InputInterface $ $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); chdir($oldCwd); - $vendorComposerDir = $config->get('vendor-dir').'/composer'; - if (is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) { - Silencer::call('rmdir', $vendorComposerDir); - $vendorDir = $config->get('vendor-dir'); - if (is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) { - Silencer::call('rmdir', $vendorDir); - } - } return 0; } @@ -338,10 +330,6 @@ public function installProject(IOInterface $io, Config $config, InputInterface $ */ protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool { - if (!$secureHttp) { - $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); - } - $parser = new VersionParser(); $requirements = $parser->parseNameVersionPairs([$packageName]); $name = strtolower($requirements[0]['name']); @@ -354,12 +342,22 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co $parts = explode("/", $name, 2); $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . array_pop($parts); } + $directory = rtrim($directory, '/\\'); $process = new ProcessExecutor($io); $fs = new Filesystem($process); if (!$fs->isAbsolutePath($directory)) { $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . $directory; } + if ('' === $directory) { + throw new \UnexpectedValueException('Got an empty target directory, something went wrong'); + } + + // set the base dir to ensure $config->all() below resolves the correct absolute paths to vendor-dir etc + $config->setBaseDir($directory); + if (!$secureHttp) { + $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); + } $io->writeError('Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, true) . '"'); @@ -390,6 +388,8 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts); $config = $composer->getConfig(); + // set the base dir here again on the new config instance, as otherwise in case the vendor dir is defined in an env var for example it would still override the value set above by $config->all() + $config->setBaseDir($directory); $rm = $composer->getRepositoryManager(); $repositorySet = new RepositorySet($stability); @@ -424,16 +424,21 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co throw new \InvalidArgumentException($errorMessage .'.'); } + $oldCwd = Platform::getCwd(); // handler Ctrl+C aborts gracefully @mkdir($directory, 0777, true); if (false !== ($realDir = realpath($directory))) { - $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) { + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir, $oldCwd) { + chdir($oldCwd); $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); $fs = new Filesystem(); $fs->removeDirectory($realDir); $handler->exitWithLastSignal(); }); } + if (!chdir($directory)) { + throw new \RuntimeException('Failed to chdir into the new project dir at '.$directory); + } // avoid displaying 9999999-dev as version if default-branch was selected if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { @@ -467,7 +472,6 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co $installedFromVcs = 'source' === $package->getInstallationSource(); $io->writeError('Created project in ' . $directory . ''); - chdir($directory); // ensure that the env var being set does not interfere with create-project // as it is probably not meant to be used here, so we do not use it if a composer.json can be found diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 6844eb1e23df..f39579e06b7a 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -98,7 +98,7 @@ class Config /** @var array */ private $config; - /** @var ?string */ + /** @var ?non-empty-string */ private $baseDir; /** @var array */ private $repositories; @@ -139,6 +139,18 @@ public function __construct(bool $useEnvironment = true, ?string $baseDir = null } } + /** + * Changing this can break path resolution for relative config paths so do not call this without knowing what you are doing + * + * The $baseDir should be an absolute path and without trailing slash + * + * @param non-empty-string|null $baseDir + */ + public function setBaseDir(?string $baseDir): void + { + $this->baseDir = $baseDir; + } + public function setConfigSource(ConfigSourceInterface $source): void { $this->configSource = $source; @@ -546,7 +558,7 @@ private function realpath(string $path): string return $path; } - return $this->baseDir ? $this->baseDir . '/' . $path : $path; + return $this->baseDir !== null ? $this->baseDir . '/' . $path : $path; } /** diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index 62e9183246ff..52da0d604e76 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -178,7 +178,7 @@ private static function createRepos(RepositoryManager $rm, array $repoConfigs): /** * @param int|string $index * @param array{url?: string} $repo - * @param array $existingRepos + * @param array $existingRepos */ public static function generateRepositoryName($index, array $repo, array $existingRepos): string { From 5c71c340c8245fd38a6df26e740c120c27e039b3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 11:30:56 +0200 Subject: [PATCH 217/257] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130d926155dc..a3640f42d64a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### [2.8.1] 2024-10-04 + + * Fixed `init` command regression when no license is provided (#12145) + * Fixed `--strict-ambiguous` flag handling whereas it sometimes did not report all issues (#12148) + * Fixed `create-project` to inherit the target folder's permissions for installed project files (#12146) + * Fixed a few cases where the prompt for using a parent dir's composer.json fails to work correctly (#8023) + ### [2.8.0] 2024-10-02 * BC Warning: Fixed `https_proxy` env var falling back to `http_proxy`'s value. The fallback and warning have now been removed per the 2.7.3 release notes (#11938, #11915) @@ -1938,6 +1945,7 @@ * Initial release +[2.8.1]: https://github.com/composer/composer/compare/2.8.0...2.8.1 [2.8.0]: https://github.com/composer/composer/compare/2.7.9...2.8.0 [2.7.9]: https://github.com/composer/composer/compare/2.7.8...2.7.9 [2.7.8]: https://github.com/composer/composer/compare/2.7.7...2.7.8 From e52b8672276cf436670cdd6bd5de4353740e83b2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 11:31:01 +0200 Subject: [PATCH 218/257] Release 2.8.1 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 07d6973334d4..4d5aefb0e651 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.8.999-dev+source'; + public const VERSION = '2.8.1'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-10-04 11:31:01'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 4cb54792114ecb2349f21840187eea456b54bf06 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 11:31:01 +0200 Subject: [PATCH 219/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4d5aefb0e651..07d6973334d4 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.8.1'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-10-04 11:31:01'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.8.999-dev+source'; /** * Version number of the internal composer-runtime-api package From b840a0620784fae0db0f55badf9fe4b702c8b479 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 11:47:01 +0200 Subject: [PATCH 220/257] Clean up lock update code to remove redundancy and avoid errors (#12149) --- src/Composer/Command/BumpCommand.php | 9 +--- src/Composer/Command/RequireCommand.php | 23 ++------- src/Composer/Package/Locker.php | 69 +++++++++++++++++++++---- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/Composer/Command/BumpCommand.php b/src/Composer/Command/BumpCommand.php index 5cd7a0f20093..dc3df1e123cd 100644 --- a/src/Composer/Command/BumpCommand.php +++ b/src/Composer/Command/BumpCommand.php @@ -217,14 +217,7 @@ public function doBump( } if (!$dryRun && $composer->getLocker()->isLocked() && $changeCount > 0) { - $contents = file_get_contents($composerJson->getPath()); - if (false === $contents) { - throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.'); - } - $lock = new JsonFile(Factory::getLockFile($composerJsonPath)); - $lockData = $lock->read(); - $lockData['content-hash'] = Locker::getContentHash($contents); - $lock->write($lockData); + $composer->getLocker()->updateHash($composerJson); } if ($dryRun && $changeCount > 0) { diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 6f1b9ea7c8b8..92d0a77b5588 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -555,27 +555,14 @@ private function updateRequirementsAfterResolution(array $requirementsToUpdate, if (!$dryRun) { $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); if ($locker->isLocked()) { - $contents = file_get_contents($this->json->getPath()); - if (false === $contents) { - throw new \RuntimeException('Unable to read '.$this->json->getPath().' contents to update the lock file hash.'); - } - $lockFile = Factory::getLockFile($this->json->getPath()); - if (file_exists($lockFile)) { - $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); - - $lockMtime = filemtime($lockFile); - $lock = new JsonFile($lockFile); - $lockData = $lock->read(); - $lockData['content-hash'] = Locker::getContentHash($contents); + $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); + $locker->updateHash($this->json, function (array $lockData) use ($stabilityFlags) { foreach ($stabilityFlags as $packageName => $flag) { $lockData['stability-flags'][$packageName] = $flag; } - ksort($lockData['stability-flags']); - $lock->write($lockData); - if (is_int($lockMtime)) { - @touch($lockFile, $lockMtime); - } - } + + return $lockData; + }); } } diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 5b5ec466c1fd..03f598491045 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -372,31 +372,28 @@ public function setLockData(array $packages, ?array $devPackages, array $platfor 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', 'This file is @gener'.'ated automatically', ], 'content-hash' => $this->contentHash, - 'packages' => null, + 'packages' => $this->lockPackages($packages), 'packages-dev' => null, 'aliases' => $aliases, 'minimum-stability' => $minimumStability, - 'stability-flags' => \count($stabilityFlags) > 0 ? $stabilityFlags : new \stdClass, + 'stability-flags' => $stabilityFlags, 'prefer-stable' => $preferStable, 'prefer-lowest' => $preferLowest, ]; - if (is_array($lock['stability-flags'])) { - ksort($lock['stability-flags']); - } - - $lock['packages'] = $this->lockPackages($packages); if (null !== $devPackages) { $lock['packages-dev'] = $this->lockPackages($devPackages); } - $lock['platform'] = \count($platformReqs) > 0 ? $platformReqs : new \stdClass; - $lock['platform-dev'] = \count($platformDevReqs) > 0 ? $platformDevReqs : new \stdClass; + $lock['platform'] = $platformReqs; + $lock['platform-dev'] = $platformDevReqs; if (\count($platformOverrides) > 0) { $lock['platform-overrides'] = $platformOverrides; } $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION; + $lock = $this->fixupJsonDataType($lock); + try { $isLocked = $this->isLocked(); } catch (ParsingException $e) { @@ -418,6 +415,60 @@ public function setLockData(array $packages, ?array $devPackages, array $platfor return false; } + /** + * Updates the lock file's hash in-place from a given composer.json's JsonFile + * + * This does not reload or require any packages, and retains the filemtime of the lock file. + * + * Use this only to update the lock file hash after updating a composer.json in ways that are guaranteed NOT to impact the dependency resolution. + * + * This is a risky method, use carefully. + * + * @param (callable(array): array)|null $dataProcessor Receives the lock data and can process it before it gets written to disk + */ + public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void + { + $contents = file_get_contents($composerJson->getPath()); + if (false === $contents) { + throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.'); + } + + $lockMtime = filemtime($this->lockFile->getPath()); + $lockData = $this->lockFile->read(); + $lockData['content-hash'] = Locker::getContentHash($contents); + if ($dataProcessor !== null) { + $lockData = $dataProcessor($lockData); + } + + $this->lockFile->write($this->fixupJsonDataType($lockData)); + $this->lockDataCache = null; + $this->virtualFileWritten = false; + if (is_int($lockMtime)) { + @touch($this->lockFile->getPath(), $lockMtime); + } + } + + /** + * Ensures correct data types and ordering for the JSON lock format + * + * @param array $lockData + * @return array + */ + private function fixupJsonDataType(array $lockData): array + { + foreach (['stability-flags', 'platform', 'platform-dev'] as $key) { + if (isset($lockData[$key]) && is_array($lockData[$key]) && \count($lockData[$key]) === 0) { + $lockData[$key] = new \stdClass(); + } + } + + if (is_array($lockData['stability-flags'])) { + ksort($lockData['stability-flags']); + } + + return $lockData; + } + /** * @param PackageInterface[] $packages * From a67ce984e9d833d791a8fb4657b49315bfe61158 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 Oct 2024 16:39:31 +0200 Subject: [PATCH 221/257] Fix type --- src/Composer/Package/Locker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 03f598491045..160031a30400 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -424,7 +424,7 @@ public function setLockData(array $packages, ?array $devPackages, array $platfor * * This is a risky method, use carefully. * - * @param (callable(array): array)|null $dataProcessor Receives the lock data and can process it before it gets written to disk + * @param (callable(array): array)|null $dataProcessor Receives the lock data and can process it before it gets written to disk */ public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void { From 787f5036163e94839e721312072d6c0319aa19ba Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 15 Oct 2024 11:22:49 +0200 Subject: [PATCH 222/257] do not stumble over missing descriptions (#12152) --- phpstan/baseline.neon | 6 +++--- src/Composer/DependencyResolver/Problem.php | 2 +- src/Composer/Repository/RepositoryInterface.php | 2 +- src/Composer/Repository/RepositorySet.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 865d93b72d98..78f5ce38cd21 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -2381,7 +2381,7 @@ parameters: path: ../src/Composer/Repository/ComposerRepository.php - - message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\ but returns array\\\\.$#" + message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\ but returns array\\\\.$#" count: 1 path: ../src/Composer/Repository/ComposerRepository.php @@ -2496,7 +2496,7 @@ parameters: path: ../src/Composer/Repository/CompositeRepository.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\>\\> given\\.$#" + message: "#^Only booleans are allowed in a ternary operator condition, array\\\\>\\> given\\.$#" count: 1 path: ../src/Composer/Repository/CompositeRepository.php @@ -2714,7 +2714,7 @@ parameters: path: ../src/Composer/Repository/RepositorySet.php - - message: "#^Only booleans are allowed in an if condition, array\\\\> given\\.$#" + message: "#^Only booleans are allowed in an if condition, array\\\\> given\\.$#" count: 1 path: ../src/Composer/Repository/RepositorySet.php diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 5576845e907a..6910a39faccd 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -649,7 +649,7 @@ private static function getProvidersList(RepositorySet $repositorySet, string $p $providers = $repositorySet->getProviders($packageName); if (\count($providers) > 0) { $providersStr = implode(array_map(static function ($p): string { - $description = $p['description'] !== '' ? ' '.substr($p['description'], 0, 100) : ''; + $description = $p['description'] !== '' ? ' '.substr($p['description'] ?? '', 0, 100) : ''; return ' - '.$p['name'].$description."\n"; }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 3bcb1ea97445..f90c96d50797 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -104,7 +104,7 @@ public function search(string $query, int $mode = 0, ?string $type = null); * @param string $packageName package name which must be provided * * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') - * @phpstan-return array + * @phpstan-return array */ public function getProviders(string $packageName); diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 96c0c007bf2a..dcde3632746e 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -284,7 +284,7 @@ private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap /** * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') - * @phpstan-return array + * @phpstan-return array */ public function getProviders(string $packageName): array { From 0191d0c7c51df2f847f702d4a8fc0783d0fe096f Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 19 Oct 2024 22:15:13 +0200 Subject: [PATCH 223/257] don't return a single space for packages without descriptions (#12162) --- src/Composer/DependencyResolver/Problem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 6910a39faccd..c58d8601b116 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -649,7 +649,7 @@ private static function getProvidersList(RepositorySet $repositorySet, string $p $providers = $repositorySet->getProviders($packageName); if (\count($providers) > 0) { $providersStr = implode(array_map(static function ($p): string { - $description = $p['description'] !== '' ? ' '.substr($p['description'] ?? '', 0, 100) : ''; + $description = $p['description'] !== '' && $p['description'] !== null ? ' '.substr($p['description'], 0, 100) : ''; return ' - '.$p['name'].$description."\n"; }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); From 13101181018964a765380e3dcc10617c33dcc039 Mon Sep 17 00:00:00 2001 From: Alexis Urien Date: Sat, 19 Oct 2024 13:28:13 -0700 Subject: [PATCH 224/257] Update authentication-for-private-packages.md (#12159) Fix Command line inline http-basic command --- doc/articles/authentication-for-private-packages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/authentication-for-private-packages.md b/doc/articles/authentication-for-private-packages.md index b81cd636e961..d2cc3fd5200b 100644 --- a/doc/articles/authentication-for-private-packages.md +++ b/doc/articles/authentication-for-private-packages.md @@ -155,7 +155,7 @@ If the username e.g. is an email address it needs to be passed as `name%40exampl ### Command line inline http-basic ```shell -php composer.phar config [--global] repositories composer.unique-name https://username:password@repo.example.org +php composer.phar config [--global] repositories.unique-name composer https://username:password@repo.example.org ``` ### Manual inline http-basic From 186d78cde397a3f4adbd600d61e5d4b85dbf7832 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 24 Oct 2024 11:38:38 +0200 Subject: [PATCH 225/257] Add php-ext to array dumper --- src/Composer/Package/Dumper/ArrayDumper.php | 1 + tests/Composer/Test/Package/Dumper/ArrayDumperTest.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index 046ba6674fa3..9333bd9a010c 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -37,6 +37,7 @@ public function dump(PackageInterface $package): array 'devAutoload' => 'autoload-dev', 'notificationUrl' => 'notification-url', 'includePaths' => 'include-path', + 'phpExt' => 'php-ext', ]; $data = []; diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index 95a638820133..253b3253bd6c 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -234,6 +234,11 @@ public static function provideKeys(): array ['ssl' => ['local_cert' => '/opt/certs/test.pem']], 'transportOptions', ], + [ + 'php-ext', + ['extension-name' => 'test'], + 'phpExt', + ], ]; } } From 0a4c2a92aa3d7743b21da570aa964ddf66595d50 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 11:07:18 +0100 Subject: [PATCH 226/257] Update deps --- composer.lock | 82 +++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/composer.lock b/composer.lock index 770ebda51bd3..b07c8411abad 100644 --- a/composer.lock +++ b/composer.lock @@ -941,16 +941,16 @@ }, { "name": "symfony/console", - "version": "v5.4.44", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5b5a0aa66e3296e303e22490f90f521551835a83" + "reference": "108d436c2af470858bdaba3257baab3a74172017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5b5a0aa66e3296e303e22490f90f521551835a83", - "reference": "5b5a0aa66e3296e303e22490f90f521551835a83", + "url": "https://api.github.com/repos/symfony/console/zipball/108d436c2af470858bdaba3257baab3a74172017", + "reference": "108d436c2af470858bdaba3257baab3a74172017", "shasum": "" }, "require": { @@ -1020,7 +1020,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.44" + "source": "https://github.com/symfony/console/tree/v5.4.45" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T07:56:40+00:00" + "time": "2024-10-08T07:27:17+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1107,16 +1107,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.44", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "76c3818964e9d32be3862c9318ae3ba9aa280ddc" + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/76c3818964e9d32be3862c9318ae3ba9aa280ddc", - "reference": "76c3818964e9d32be3862c9318ae3ba9aa280ddc", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { @@ -1154,7 +1154,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.44" + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { @@ -1170,20 +1170,20 @@ "type": "tidelift" } ], - "time": "2024-09-16T14:52:48+00:00" + "time": "2024-10-22T13:05:35+00:00" }, { "name": "symfony/finder", - "version": "v5.4.43", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ae25a9145a900764158d439653d5630191155ca0" + "reference": "63741784cd7b9967975eec610b256eed3ede022b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", - "reference": "ae25a9145a900764158d439653d5630191155ca0", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", "shasum": "" }, "require": { @@ -1217,7 +1217,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.43" + "source": "https://github.com/symfony/finder/tree/v5.4.45" }, "funding": [ { @@ -1233,7 +1233,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:03:51+00:00" + "time": "2024-09-28T13:32:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1787,16 +1787,16 @@ }, { "name": "symfony/process", - "version": "v5.4.44", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "1b9fa82b5c62cd49da8c9e3952dd8531ada65096" + "reference": "95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/1b9fa82b5c62cd49da8c9e3952dd8531ada65096", - "reference": "1b9fa82b5c62cd49da8c9e3952dd8531ada65096", + "url": "https://api.github.com/repos/symfony/process/zipball/95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4", + "reference": "95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4", "shasum": "" }, "require": { @@ -1829,7 +1829,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.44" + "source": "https://github.com/symfony/process/tree/v5.4.45" }, "funding": [ { @@ -1845,7 +1845,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T12:46:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/service-contracts", @@ -1932,16 +1932,16 @@ }, { "name": "symfony/string", - "version": "v5.4.44", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "832caa16b6d9aac6bf11747315225f5aba384c24" + "reference": "7f6807add88b1e2635f3c6de5e1ace631ed7cad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/832caa16b6d9aac6bf11747315225f5aba384c24", - "reference": "832caa16b6d9aac6bf11747315225f5aba384c24", + "url": "https://api.github.com/repos/symfony/string/zipball/7f6807add88b1e2635f3c6de5e1ace631ed7cad2", + "reference": "7f6807add88b1e2635f3c6de5e1ace631ed7cad2", "shasum": "" }, "require": { @@ -1998,7 +1998,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.44" + "source": "https://github.com/symfony/string/tree/v5.4.45" }, "funding": [ { @@ -2014,22 +2014,22 @@ "type": "tidelift" } ], - "time": "2024-09-20T07:56:40+00:00" + "time": "2024-09-25T14:11:13+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.5", + "version": "1.12.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17" + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", - "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-09-26T12:45:22+00:00" + "time": "2024-10-18T11:12:07+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2298,16 +2298,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "e876eb90e32a8fc4c4911d458e09f88d65877d1c" + "reference": "c6b9d8f52d3e276bedb49612aa4a2a046171287f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e876eb90e32a8fc4c4911d458e09f88d65877d1c", - "reference": "e876eb90e32a8fc4c4911d458e09f88d65877d1c", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c6b9d8f52d3e276bedb49612aa4a2a046171287f", + "reference": "c6b9d8f52d3e276bedb49612aa4a2a046171287f", "shasum": "" }, "require": { @@ -2360,7 +2360,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.4" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.1.6" }, "funding": [ { @@ -2376,7 +2376,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-09-25T14:20:29+00:00" } ], "aliases": [], From fa5b361f34d9841d724772a4d67ce86b62c7fa26 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 13:32:20 +0100 Subject: [PATCH 227/257] Fix handling of signals in non-PHP binaries run via proxies (#12176) Fixes #12164 --- src/Composer/Installer/BinaryInstaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 54ecd94cf183..921132552792 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -406,7 +406,7 @@ public function url_stat(\$path, \$flags) fi fi -"\${dir}/$binFile" "\$@" +exec "\${dir}/$binFile" "\$@" PROXY; } From e12cfa0c403651b4b422c0269420e7f543f00104 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 15:37:13 +0100 Subject: [PATCH 228/257] Fix create-project regression when using path repos with relative paths, fixes #12150 --- src/Composer/Command/CreateProjectCommand.php | 8 ++------ src/Composer/Downloader/FileDownloader.php | 9 ++++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 0454efa59ca9..d14e2f12324d 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -424,21 +424,16 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co throw new \InvalidArgumentException($errorMessage .'.'); } - $oldCwd = Platform::getCwd(); // handler Ctrl+C aborts gracefully @mkdir($directory, 0777, true); if (false !== ($realDir = realpath($directory))) { - $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir, $oldCwd) { - chdir($oldCwd); + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) { $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); $fs = new Filesystem(); $fs->removeDirectory($realDir); $handler->exitWithLastSignal(); }); } - if (!chdir($directory)) { - throw new \RuntimeException('Failed to chdir into the new project dir at '.$directory); - } // avoid displaying 9999999-dev as version if default-branch was selected if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { @@ -472,6 +467,7 @@ protected function installRootPackage(InputInterface $input, IOInterface $io, Co $installedFromVcs = 'source' === $package->getInstallationSource(); $io->writeError('Created project in ' . $directory . ''); + chdir($directory); // ensure that the env var being set does not interfere with create-project // as it is probably not meant to be used here, so we do not use it if a composer.json can be found diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 2e1207b5519b..5f3b24279612 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -350,7 +350,14 @@ public function install(PackageInterface $package, string $path, bool $output = $this->io->writeError(" - " . InstallOperation::format($package)); } - $this->filesystem->emptyDirectory($path); + $vendorDir = $this->config->get('vendor-dir'); + + // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains + // the file to be installed. This is the case when installing with create-project in the current directory + // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. + if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { + $this->filesystem->emptyDirectory($path); + } $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME)); From 5c3f6e070d9a28bc980c64e43ccd025a8735e725 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 16:30:35 +0100 Subject: [PATCH 229/257] Remove SignalHandler from Application to fix issues handling ctrl-C inside prompts Fixes #12106 --- src/Composer/Console/Application.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index dcf777f83b7f..2b9922337070 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -19,7 +19,6 @@ use Composer\Util\Silencer; use LogicException; use RuntimeException; -use Seld\Signal\SignalHandler; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Helper\HelperSet; @@ -84,9 +83,6 @@ class Application extends BaseApplication */ private $initialWorkingDirectory; - /** @var SignalHandler */ - private $signalHandler; - public function __construct(string $name = 'Composer', string $version = '') { if (method_exists($this, 'setCatchErrors')) { @@ -108,12 +104,6 @@ public function __construct(string $name = 'Composer', string $version = '') $this->io = new NullIO(); - $this->signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { - $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); - - $handler->exitWithLastSignal(); - }); - if (!$shutdownRegistered) { $shutdownRegistered = true; @@ -135,7 +125,6 @@ public function __construct(string $name = 'Composer', string $version = '') public function __destruct() { - $this->signalHandler->unregister(); } public function run(?InputInterface $input = null, ?OutputInterface $output = null): int From 1f0d0128451a5c1aed79da9607c49c80125cdc34 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 21:37:23 +0100 Subject: [PATCH 230/257] Add hint how ambiguous class issues can be resolved, refs #6221 (#12179) --- src/Composer/Autoload/AutoloadGenerator.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 86c9f184d614..44431128f99d 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -34,6 +34,7 @@ use Composer\Util\PackageSorter; use Composer\Json\JsonFile; use Composer\Package\Locker; +use Symfony\Component\Console\Formatter\OutputFormatter; /** * @author Igor Wiedler @@ -380,6 +381,9 @@ public static function autoload(\$class) ); } } + if (\count($ambiguousClasses) > 0) { + $this->io->writeError('To resolve ambiguity in classes not under your control you can ignore them by path using exclude-files-from-classmap'); + } // output PSR violations which are not coming from the vendor dir $classMap->clearPsrViolationsByPath($vendorPath); From e0ed22bbd0d88e4ddaaf3e5d764a4467f33eb04e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 21:37:38 +0100 Subject: [PATCH 231/257] Warn/throw when we detect git safe.directory errors (#12178) Fixes #12158 Fixes #12160 --- src/Composer/Command/StatusCommand.php | 2 +- src/Composer/Downloader/PathDownloader.php | 2 +- src/Composer/Downloader/VcsDownloader.php | 2 +- src/Composer/Factory.php | 2 +- .../Package/Loader/RootPackageLoader.php | 2 +- src/Composer/Package/Version/VersionGuesser.php | 10 +++++++++- src/Composer/Repository/PathRepository.php | 2 +- src/Composer/Repository/Vcs/GitDriver.php | 1 + src/Composer/Util/Git.php | 16 ++++++++++++++++ 9 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 780f70031a64..5d90b310506b 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -92,7 +92,7 @@ private function doExecute(InputInterface $input): int $vcsVersionChanges = []; $parser = new VersionParser; - $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser); + $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser, $io); $dumper = new ArrayDumper; // list packages diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 8997f561e134..f71ea2568a1a 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -224,7 +224,7 @@ public function getVcsReference(PackageInterface $package, string $path): ?strin { $path = Filesystem::trimTrailingSlash($path); $parser = new VersionParser; - $guesser = new VersionGuesser($this->config, $this->process, $parser); + $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index a1c5979d5731..626bcb5c5594 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -241,7 +241,7 @@ public function remove(PackageInterface $package, string $path): PromiseInterfac public function getVcsReference(PackageInterface $package, string $path): ?string { $parser = new VersionParser; - $guesser = new VersionGuesser($this->config, $this->process, $parser); + $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 3d6d58318de0..4389c487e9a1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -384,7 +384,7 @@ public function createComposer(IOInterface $io, $localConfig = null, $disablePlu // load package $parser = new VersionParser; - $guesser = new VersionGuesser($config, $process, $parser); + $guesser = new VersionGuesser($config, $process, $parser, $io); $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io); $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); $composer->setPackage($package); diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 81cb375fc78d..50807e50628b 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -60,7 +60,7 @@ public function __construct(RepositoryManager $manager, Config $config, ?Version $this->manager = $manager; $this->config = $config; - $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser); + $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser, $io); $this->io = $io; } diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index bfc16d524135..8a0fe9c14983 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -13,6 +13,7 @@ namespace Composer\Package\Version; use Composer\Config; +use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Repository\Vcs\HgDriver; use Composer\IO\NullIO; @@ -50,11 +51,17 @@ class VersionGuesser */ private $versionParser; - public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser) + /** + * @var IOInterface|null + */ + private $io; + + public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser, ?IOInterface $io = null) { $this->config = $config; $this->process = $process; $this->versionParser = $versionParser; + $this->io = $io; } /** @@ -178,6 +185,7 @@ private function guessGitVersion(array $packageConfig, string $path): array $prettyVersion = $result['pretty_version']; } } + GitUtil::checkForRepoOwnershipError($this->process->getErrorOutput(), $path, $this->io); if (!$version || $isDetached) { $result = $this->versionFromGitTags($path); diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 1d9e4e7e0982..4e99bd415ca6 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -116,7 +116,7 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, $this->loader = new ArrayLoader(null, true); $this->url = Platform::expandPath($repoConfig['url']); $this->process = $process ?? new ProcessExecutor($io); - $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser()); + $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser(), $io); $this->repoConfig = $repoConfig; $this->options = $repoConfig['options'] ?? []; if (!isset($this->options['relative'])) { diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index f3f7a91a6607..57bc9b2ef0c0 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -236,6 +236,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo if ($process->execute('git tag', $output, $url) === 0) { return true; } + GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url); } if (!$deep) { diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 58df0ad5711f..f3369c8aa56d 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -43,6 +43,20 @@ public function __construct(IOInterface $io, Config $config, ProcessExecutor $pr $this->filesystem = $fs; } + /** + * @param IOInterface|null $io If present, a warning is output there instead of throwing, so pass this in only for cases where this is a soft failure + */ + public static function checkForRepoOwnershipError(string $output, string $path, ?IOInterface $io = null): void + { + if (str_contains($output, 'fatal: detected dubious ownership')) { + $msg = 'The repository at "' . $path . '" does not have the correct ownership and git refuses to use it:' . PHP_EOL . PHP_EOL . $output; + if ($io === null) { + throw new \RuntimeException($msg); + } + $io->writeError(''.$msg.''); + } + } + public function setHttpDownloader(HttpDownloader $httpDownloader): void { $this->httpDownloader = $httpDownloader; @@ -317,6 +331,7 @@ public function syncMirror(string $url, string $dir): bool return true; } + self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); // clean up directory and do a fresh clone into it $this->filesystem->removeDirectory($dir); @@ -384,6 +399,7 @@ private function checkRefIsInMirror(string $dir, string $ref): bool return true; } } + self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); return false; } From e02f7ba58b3f6428d146f0d6eec51468d22da9b7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Oct 2024 21:58:03 +0100 Subject: [PATCH 232/257] Fix parsing of comments in arrays of sponsor info, fixes composer/packagist#1473 --- src/Composer/Repository/Vcs/GitHubDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 09beb7680997..97a334f94e5e 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -235,7 +235,7 @@ private function getFundingInfo() $key = $match[1]; continue; } - if (Preg::isMatchStrictGroups('{^\[(.*)\](?:\s*#.*)?$}', $match[2], $match2)) { + if (Preg::isMatchStrictGroups('{^\[(.*?)\](?:\s*#.*)?$}', $match[2], $match2)) { foreach (array_map('trim', Preg::split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) { $result[] = ['type' => $match[1], 'url' => trim($item, '"\' ')]; } From f95668341890e5cb7f2dfdc9f1203b8cdc3d2ba0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Oct 2024 16:12:00 +0100 Subject: [PATCH 233/257] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3640f42d64a..ada64852b395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### [2.8.2] 2024-10-29 + + * Fixed crash while suggesting providers if they have no description (#12152) + * Fixed issues creating lock files violating the schema in some circumstances (#12149) + * Fixed `create-project` regression in 2.8.1 when using path repos with relative paths (#12150) + * Fixed ctrl-C aborts not working inside text prompts (#12106) + * Fixed git failing silently when git cannot read a repo due to ownership violations (#12178) + * Fixed handling of signals in non-PHP binaries run via proxies (#12176) + ### [2.8.1] 2024-10-04 * Fixed `init` command regression when no license is provided (#12145) @@ -1945,6 +1954,7 @@ * Initial release +[2.8.2]: https://github.com/composer/composer/compare/2.8.1...2.8.2 [2.8.1]: https://github.com/composer/composer/compare/2.8.0...2.8.1 [2.8.0]: https://github.com/composer/composer/compare/2.7.9...2.8.0 [2.7.9]: https://github.com/composer/composer/compare/2.7.8...2.7.9 From 6e543d03187c882ea1c6ba43add2467754427803 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Oct 2024 16:12:11 +0100 Subject: [PATCH 234/257] Release 2.8.2 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 07d6973334d4..abb571359059 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.8.999-dev+source'; + public const VERSION = '2.8.2'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-10-29 16:12:11'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 5a75d3241418c47128647c9d55e599dec492d406 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Oct 2024 16:12:11 +0100 Subject: [PATCH 235/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index abb571359059..07d6973334d4 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.8.2'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-10-29 16:12:11'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.8.999-dev+source'; /** * Version number of the internal composer-runtime-api package From 3dc279cf66c9329bf84bb31086e90a32cb2c8628 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 6 Nov 2024 13:49:06 +0100 Subject: [PATCH 236/257] Stop relying on OS to find executables on Windows, and migrate most Process calls to array syntax (#12180) Co-authored-by: Jordi Boggiano --- composer.lock | 48 ++-- phpstan/baseline.neon | 65 +---- src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Command/HomeCommand.php | 12 +- src/Composer/Command/InitCommand.php | 11 +- src/Composer/Compiler.php | 22 +- src/Composer/Downloader/FossilDownloader.php | 48 ++-- src/Composer/Downloader/GitDownloader.php | 140 ++++----- src/Composer/Downloader/GzipDownloader.php | 4 +- src/Composer/Downloader/HgDownloader.php | 23 +- src/Composer/Downloader/RarDownloader.php | 4 +- src/Composer/Downloader/SvnDownloader.php | 24 +- src/Composer/Downloader/XzDownloader.php | 4 +- src/Composer/Downloader/ZipDownloader.php | 34 ++- src/Composer/Package/Locker.php | 5 +- .../Package/Version/VersionGuesser.php | 12 +- src/Composer/Platform/HhvmDetector.php | 6 +- src/Composer/Repository/PathRepository.php | 6 +- src/Composer/Repository/Vcs/FossilDriver.php | 25 +- src/Composer/Repository/Vcs/GitDriver.php | 20 +- src/Composer/Repository/Vcs/HgDriver.php | 27 +- src/Composer/Repository/Vcs/SvnDriver.php | 19 +- src/Composer/Util/Bitbucket.php | 2 +- src/Composer/Util/Filesystem.php | 20 +- src/Composer/Util/Git.php | 161 ++++++++--- src/Composer/Util/GitHub.php | 4 +- src/Composer/Util/GitLab.php | 4 +- src/Composer/Util/Hg.php | 2 +- src/Composer/Util/Perforce.php | 22 +- src/Composer/Util/Platform.php | 15 +- src/Composer/Util/ProcessExecutor.php | 52 +++- src/Composer/Util/Svn.php | 54 ++-- tests/Composer/Test/AllFunctionalTest.php | 1 + .../Test/Downloader/FossilDownloaderTest.php | 13 +- .../Test/Downloader/GitDownloaderTest.php | 268 ++++++++++-------- .../Test/Downloader/HgDownloaderTest.php | 11 +- .../Test/Mock/ProcessExecutorMock.php | 6 +- .../Package/Version/VersionGuesserTest.php | 20 +- .../Test/Repository/Vcs/GitDriverTest.php | 16 +- .../Test/Repository/Vcs/GitHubDriverTest.php | 10 +- .../Test/Repository/Vcs/HgDriverTest.php | 4 +- .../Test/Repository/Vcs/SvnDriverTest.php | 9 +- tests/Composer/Test/Util/GitTest.php | 11 +- tests/Composer/Test/Util/PerforceTest.php | 6 +- tests/Composer/Test/Util/SvnTest.php | 28 +- 45 files changed, 715 insertions(+), 585 deletions(-) diff --git a/composer.lock b/composer.lock index b07c8411abad..10d57e9f2ee4 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.2", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137" + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/48a792895a2b7a6ee65dd5442c299d7b835b6137", - "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2", + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.2" + "source": "https://github.com/composer/ca-bundle/tree/1.5.3" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T07:49:53+00:00" + "time": "2024-11-04T10:15:26+00:00" }, { "name": "composer/class-map-generator", @@ -941,16 +941,16 @@ }, { "name": "symfony/console", - "version": "v5.4.45", + "version": "v5.4.46", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "108d436c2af470858bdaba3257baab3a74172017" + "reference": "fb0d4760e7147d81ab4d9e2d57d56268261b4e4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/108d436c2af470858bdaba3257baab3a74172017", - "reference": "108d436c2af470858bdaba3257baab3a74172017", + "url": "https://api.github.com/repos/symfony/console/zipball/fb0d4760e7147d81ab4d9e2d57d56268261b4e4e", + "reference": "fb0d4760e7147d81ab4d9e2d57d56268261b4e4e", "shasum": "" }, "require": { @@ -1020,7 +1020,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.45" + "source": "https://github.com/symfony/console/tree/v5.4.46" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2024-10-08T07:27:17+00:00" + "time": "2024-11-05T14:17:06+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1787,16 +1787,16 @@ }, { "name": "symfony/process", - "version": "v5.4.45", + "version": "v5.4.46", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4" + "reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4", - "reference": "95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4", + "url": "https://api.github.com/repos/symfony/process/zipball/01906871cb9b5e3cf872863b91aba4ec9767daf4", + "reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4", "shasum": "" }, "require": { @@ -1829,7 +1829,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.45" + "source": "https://github.com/symfony/process/tree/v5.4.46" }, "funding": [ { @@ -1845,7 +1845,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2024-11-06T09:18:28+00:00" }, { "name": "symfony/service-contracts", @@ -2226,16 +2226,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.10", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1" + "reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f7d5782044bedf93aeb3f38e09c91148ee90e5a1", - "reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/270c2ee1478d1f8dc5121f539e890017bd64b04c", + "reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c", "shasum": "" }, "require": { @@ -2292,9 +2292,9 @@ "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.4.10" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.11" }, - "time": "2024-09-26T18:14:50+00:00" + "time": "2024-10-30T12:07:21+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 78f5ce38cd21..7a89f1d8301e 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -300,11 +300,6 @@ parameters: count: 1 path: ../src/Composer/Command/CreateProjectCommand.php - - - message: "#^Parameter \\#3 \\$existingRepos of static method Composer\\\\Repository\\\\RepositoryFactory\\:\\:generateRepositoryName\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Command/CreateProjectCommand.php - - message: "#^Variable method call on Composer\\\\Package\\\\RootPackageInterface\\.$#" count: 1 @@ -495,11 +490,6 @@ parameters: count: 2 path: ../src/Composer/Command/LicensesCommand.php - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: ../src/Composer/Command/ReinstallCommand.php - - message: "#^Foreach overwrites \\$type with its key variable\\.$#" count: 1 @@ -955,11 +945,6 @@ parameters: count: 1 path: ../src/Composer/Config.php - - - message: "#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Config.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 @@ -1160,11 +1145,6 @@ parameters: count: 1 path: ../src/Composer/Downloader/FileDownloader.php - - - message: "#^Parameter \\#3 \\$cwd of method Composer\\\\Util\\\\ProcessExecutor\\:\\:execute\\(\\) expects string\\|null, string\\|false given\\.$#" - count: 5 - path: ../src/Composer/Downloader/FossilDownloader.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 5 @@ -1367,7 +1347,7 @@ parameters: - message: "#^Only booleans are allowed in a negated boolean, array\\\\> given\\.$#" - count: 3 + count: 2 path: ../src/Composer/Downloader/ZipDownloader.php - @@ -2330,11 +2310,6 @@ parameters: count: 2 path: ../src/Composer/Question/StrictConfirmationQuestion.php - - - message: "#^Method Composer\\\\Repository\\\\ArrayRepository\\:\\:getProviders\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: ../src/Composer/Repository/ArrayRepository.php - - message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\|null given\\.$#" count: 1 @@ -2381,7 +2356,7 @@ parameters: path: ../src/Composer/Repository/ComposerRepository.php - - message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\ but returns array\\\\.$#" + message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\ but returns array\\\\.$#" count: 1 path: ../src/Composer/Repository/ComposerRepository.php @@ -2496,7 +2471,7 @@ parameters: path: ../src/Composer/Repository/CompositeRepository.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\>\\> given\\.$#" + message: "#^Only booleans are allowed in a ternary operator condition, array\\\\>\\> given\\.$#" count: 1 path: ../src/Composer/Repository/CompositeRepository.php @@ -2714,7 +2689,7 @@ parameters: path: ../src/Composer/Repository/RepositorySet.php - - message: "#^Only booleans are allowed in an if condition, array\\\\> given\\.$#" + message: "#^Only booleans are allowed in an if condition, array\\\\> given\\.$#" count: 1 path: ../src/Composer/Repository/RepositorySet.php @@ -2723,21 +2698,6 @@ parameters: count: 1 path: ../src/Composer/Repository/RepositorySet.php - - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/FossilDriver.php - - - - message: "#^Parameter \\#1 \\$file of method Composer\\\\Util\\\\Filesystem\\:\\:remove\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/FossilDriver.php - - - - message: "#^Parameter \\#1 \\$filename of function is_file expects string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/FossilDriver.php - - message: "#^Call to function array_search\\(\\) requires parameter \\#3 to be set\\.$#" count: 2 @@ -3968,11 +3928,6 @@ parameters: count: 1 path: ../src/Composer/Util/Svn.php - - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Svn.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 @@ -4341,6 +4296,11 @@ parameters: count: 2 path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php + - + message: "#^Property Composer\\\\Test\\\\Mock\\\\ProcessExecutorMock\\:\\:\\$expectations \\(array\\\\|string, return\\: int, stdout\\: string, stderr\\: string, callback\\: \\(callable\\(\\)\\: mixed\\)\\|null\\}\\>\\|null\\) does not accept array\\, non\\-empty\\-list\\\\|\\(callable\\(\\)\\: mixed\\)\\|int\\|string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|int\\|string\\|null\\>\\>\\.$#" + count: 1 + path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php + - message: "#^Composer\\\\Test\\\\Mock\\\\VersionGuesserMock\\:\\:__construct\\(\\) does not call parent constructor from Composer\\\\Package\\\\Version\\\\VersionGuesser\\.$#" count: 1 @@ -4486,9 +4446,14 @@ parameters: count: 1 path: ../tests/Composer/Test/TestCase.php + - + message: "#^Cannot access an offset on array\\\\|int\\|string\\>\\>\\|false\\.$#" + count: 2 + path: ../tests/Composer/Test/Util/GitTest.php + - message: "#^Cannot access an offset on array\\\\>\\|false\\.$#" - count: 3 + count: 1 path: ../tests/Composer/Test/Util/GitTest.php - diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 263ee0644f4e..7dade55ef4da 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -302,7 +302,7 @@ private function checkGit(): string return 'proc_open is not available, git cannot be used'; } - $this->process->execute('git config color.ui', $output); + $this->process->execute(['git', 'config', 'color.ui'], $output); if (strtolower(trim($output)) === 'always') { return 'Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.'; } diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index 5aec6f4c181f..3547faec7799 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -122,22 +122,20 @@ private function handlePackage(CompletePackageInterface $package, bool $showHome */ private function openBrowser(string $url): void { - $url = ProcessExecutor::escape($url); - $process = new ProcessExecutor($this->getIO()); if (Platform::isWindows()) { - $process->execute('start "web" explorer ' . $url, $output); + $process->execute(['start', '"web"', 'explorer', $url], $output); return; } - $linux = $process->execute('which xdg-open', $output); - $osx = $process->execute('which open', $output); + $linux = $process->execute(['which', 'xdg-open'], $output); + $osx = $process->execute(['which', 'open'], $output); if (0 === $linux) { - $process->execute('xdg-open ' . $url, $output); + $process->execute(['xdg-open', $url], $output); } elseif (0 === $osx) { - $process->execute('open ' . $url, $output); + $process->execute(['open', $url], $output); } else { $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url); } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 6d7311dd451e..8b01dda6fcde 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -27,6 +27,7 @@ use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; +use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; @@ -535,15 +536,11 @@ protected function getGitConfig(): array return $this->gitConfig; } - $finder = new ExecutableFinder(); - $gitBin = $finder->find('git'); + $process = new ProcessExecutor($this->getIO()); - $cmd = new Process([$gitBin, 'config', '-l']); - $cmd->run(); - - if ($cmd->isSuccessful()) { + if (0 === $process->execute(['git', 'config', '-l'], $output)) { $this->gitConfig = []; - Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches); + Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches); foreach ($matches[1] as $key => $match) { $this->gitConfig[$match] = $matches[2][$key]; } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index def3ff8c4e4e..e85329c6d19f 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -15,6 +15,7 @@ use Composer\Json\JsonFile; use Composer\CaBundle\CaBundle; use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; use Symfony\Component\Finder\Finder; use Symfony\Component\Process\Process; use Seld\PharUtils\Timestamps; @@ -48,23 +49,22 @@ public function compile(string $pharFile = 'composer.phar'): void unlink($pharFile); } - $process = new Process(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], __DIR__); - if ($process->run() !== 0) { + $process = new ProcessExecutor(); + + if (0 !== $process->execute(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], $output, __DIR__)) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } - $this->version = trim($process->getOutput()); + $this->version = trim($output); - $process = new Process(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], __DIR__); - if ($process->run() !== 0) { + if (0 !== $process->execute(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], $output, __DIR__)) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } - $this->versionDate = new \DateTime(trim($process->getOutput())); + $this->versionDate = new \DateTime(trim($output)); $this->versionDate->setTimezone(new \DateTimeZone('UTC')); - $process = new Process(['git', 'describe', '--tags', '--exact-match', 'HEAD'], __DIR__); - if ($process->run() === 0) { - $this->version = trim($process->getOutput()); + if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, __DIR__)) { + $this->version = trim($output); } else { // get branch-alias defined in composer.json for dev-main (if any) $localConfig = __DIR__.'/../../composer.json'; @@ -75,6 +75,10 @@ public function compile(string $pharFile = 'composer.phar'): void } } + if ('' === $this->version) { + throw new \UnexpectedValueException('Version detection failed'); + } + $phar = new \Phar($pharFile, 0, 'composer.phar'); $phar->setSignatureAlgorithm(\Phar::SHA512); diff --git a/src/Composer/Downloader/FossilDownloader.php b/src/Composer/Downloader/FossilDownloader.php index 6634771dc0e1..60c6ed49da32 100644 --- a/src/Composer/Downloader/FossilDownloader.php +++ b/src/Composer/Downloader/FossilDownloader.php @@ -12,10 +12,12 @@ namespace Composer\Downloader; +use Composer\Util\Platform; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; +use RuntimeException; /** * @author BohwaZ @@ -38,22 +40,13 @@ protected function doInstall(PackageInterface $package, string $path, string $ur // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); - $url = ProcessExecutor::escape($url); - $ref = ProcessExecutor::escape($package->getSourceReference()); $repoFile = $path . '.fossil'; + $realPath = Platform::realpath($path); + $this->io->writeError("Cloning ".$package->getSourceReference()); - $command = sprintf('fossil clone -- %s %s', $url, ProcessExecutor::escape($repoFile)); - if (0 !== $this->process->execute($command, $ignoredOutput)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - $command = sprintf('fossil open --nested -- %s', ProcessExecutor::escape($repoFile)); - if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - $command = sprintf('fossil update -- %s', $ref); - if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } + $this->execute(['fossil', 'clone', '--', $url, $repoFile]); + $this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath); + $this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath); return \React\Promise\resolve(null); } @@ -66,17 +59,15 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); - $ref = ProcessExecutor::escape($target->getSourceReference()); $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } - $command = sprintf('fossil pull && fossil up %s', $ref); - if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } + $realPath = Platform::realpath($path); + $this->execute(['fossil', 'pull'], $realPath); + $this->execute(['fossil', 'up', (string) $target->getSourceReference()], $realPath); return \React\Promise\resolve(null); } @@ -90,7 +81,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin return null; } - $this->process->execute('fossil changes', $output, realpath($path)); + $this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path)); $output = trim($output); @@ -102,11 +93,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin */ protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { - $command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', ProcessExecutor::escape($toReference)); - - if (0 !== $this->process->execute($command, $output, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } + $this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output); $log = ''; $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/'; @@ -121,6 +108,17 @@ protected function getCommitLogs(string $fromReference, string $toReference, str return $log; } + /** + * @param non-empty-list $command + * @throws \RuntimeException + */ + private function execute(array $command, ?string $cwd = null, ?string &$output = null): void + { + if (0 !== $this->process->execute($command, $output, $cwd)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + } + /** * @inheritDoc */ diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index f29d74522cab..e54d95473f66 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -73,7 +73,7 @@ protected function doDownload(PackageInterface $package, string $path, string $u // --dissociate option is only available since git 2.3.0-rc0 if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { $this->io->writeError(" - Syncing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") into cache"); - $this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG); + $this->io->writeError(sprintf(' Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG); $ref = $package->getSourceReference(); if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) { $this->cachedPackages[$package->getId()][$ref] = true; @@ -94,24 +94,30 @@ protected function doInstall(PackageInterface $package, string $path, string $ur $path = $this->normalizePath($path); $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $ref = $package->getSourceReference(); - $flag = Platform::isWindows() ? '/D ' : ''; if (!empty($this->cachedPackages[$package->getId()][$ref])) { $msg = "Cloning ".$this->getShortHash($ref).' from cache'; - $cloneFlags = '--dissociate --reference %cachePath% '; + $cloneFlags = ['--dissociate', '--reference', $cachePath]; $transportOptions = $package->getTransportOptions(); if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) { - $cloneFlags = ''; + $cloneFlags = []; } - $command = - 'git clone --no-checkout %cachePath% %path% ' . $cloneFlags - . '&& cd '.$flag.'%path% ' - . '&& git remote set-url origin -- %sanitizedUrl% && git remote add composer -- %sanitizedUrl%'; + $commands = [ + array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags), + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'], + ]; } else { $msg = "Cloning ".$this->getShortHash($ref); - $command = 'git clone --no-checkout -- %url% %path% && cd '.$flag.'%path% && git remote add composer -- %url% && git fetch composer && git remote set-url origin -- %sanitizedUrl% && git remote set-url composer -- %sanitizedUrl%'; + $commands = [ + array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]), + ['git', 'remote', 'add', 'composer', '--', '%url%'], + ['git', 'fetch', 'composer'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'], + ]; if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); } @@ -119,20 +125,8 @@ protected function doInstall(PackageInterface $package, string $path, string $ur $this->io->writeError($msg); - $commandCallable = static function (string $url) use ($path, $command, $cachePath): string { - return str_replace( - ['%url%', '%path%', '%cachePath%', '%sanitizedUrl%'], - [ - ProcessExecutor::escape($url), - ProcessExecutor::escape($path), - ProcessExecutor::escape($cachePath), - ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}', '://', $url)), - ], - $command - ); - }; + $this->gitUtil->runCommands($commands, $url, $path, true); - $this->gitUtil->runCommand($commandCallable, $url, $path, true); $sourceUrl = $package->getSourceUrl(); if ($url !== $sourceUrl && $sourceUrl !== null) { $this->updateOriginUrl($path, $sourceUrl); @@ -166,10 +160,10 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, if (!empty($this->cachedPackages[$target->getId()][$ref])) { $msg = "Checking out ".$this->getShortHash($ref).' from cache'; - $command = '(git rev-parse --quiet --verify %ref% || (git remote set-url composer -- %cachePath% && git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%'; + $remoteUrl = $cachePath; } else { $msg = "Checking out ".$this->getShortHash($ref); - $command = '(git remote set-url composer -- %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%'; + $remoteUrl = '%url%'; if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting'); } @@ -177,20 +171,19 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, $this->io->writeError($msg); - $commandCallable = static function ($url) use ($ref, $command, $cachePath): string { - return str_replace( - ['%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'], - [ - ProcessExecutor::escape($url), - ProcessExecutor::escape($ref.'^{commit}'), - ProcessExecutor::escape($cachePath), - ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}', '://', $url)), - ], - $command - ); - }; + if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) { + $commands = [ + ['git', 'remote', 'set-url', 'composer', '--', $remoteUrl], + ['git', 'fetch', 'composer'], + ['git', 'fetch', '--tags', 'composer'], + ]; + + $this->gitUtil->runCommands($commands, $url, $path); + } + + $command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%']; + $this->gitUtil->runCommands([$command], $url, $path); - $this->gitUtil->runCommand($commandCallable, $url, $path); if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) { if ($target->getDistReference() === $target->getSourceReference()) { $target->setDistReference($newRef); @@ -200,7 +193,7 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, $updateOriginUrl = false; if ( - 0 === $this->process->execute('git remote -v', $output, $path) + 0 === $this->process->execute(['git', 'remote', '-v'], $output, $path) && Preg::isMatch('{^origin\s+(?P\S+)}m', $output, $originMatch) && Preg::isMatch('{^composer\s+(?P\S+)}m', $output, $composerMatch) ) { @@ -225,9 +218,9 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin return null; } - $command = 'git status --porcelain --untracked-files=no'; + $command = ['git', 'status', '--porcelain', '--untracked-files=no']; if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $output = trim($output); @@ -243,9 +236,9 @@ public function getUnpushedChanges(PackageInterface $package, string $path): ?st return null; } - $command = 'git show-ref --head -d'; + $command = ['git', 'show-ref', '--head', '-d']; if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $refs = trim($output); @@ -310,12 +303,12 @@ public function getUnpushedChanges(PackageInterface $package, string $path): ?st // first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date // remotes and then try again as outdated remotes can sometimes cause false-positives if ($unpushedChanges && $i === 0) { - $this->process->execute('git fetch --all', $output, $path); + $this->process->execute(['git', 'fetch', '--all'], $output, $path); // update list of refs after fetching - $command = 'git show-ref --head -d'; + $command = ['git', 'show-ref', '--head', '-d']; if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $refs = trim($output); } @@ -425,7 +418,7 @@ protected function reapplyChanges(string $path): void if (!empty($this->hasStashedChanges[$path])) { unset($this->hasStashedChanges[$path]); $this->io->writeError(' Re-applying stashed changes'); - if (0 !== $this->process->execute('git stash pop', $output, $path)) { + if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) { throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); } } @@ -441,18 +434,29 @@ protected function reapplyChanges(string $path): void */ protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string { - $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? '-f ' : ''; + $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : []; // This uses the "--" sequence to separate branch from file parameters. // // Otherwise git tries the branch name as well as file name. // If the non-existent branch is actually the name of a file, the file // is checked out. - $template = 'git checkout '.$force.'%s -- && git reset --hard %1$s --'; + $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); + /** + * @var \Closure(non-empty-list): bool $execute + * @phpstan-ignore varTag.nativeType + */ + $execute = function (array $command) use (&$output, $path) { + /** @var non-empty-list $command */ + $output = ''; + + return 0 === $this->process->execute($command, $output, $path); + }; + $branches = null; - if (0 === $this->process->execute('git branch -r', $output, $path)) { + if ($execute(['git', 'branch', '-r'])) { $branches = $output; } @@ -462,8 +466,10 @@ protected function updateToCommit(PackageInterface $package, string $path, strin && null !== $branches && Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches) ) { - $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference)); - if (0 === $this->process->execute($command, $output, $path)) { + $command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']); + $command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--']; + + if ($execute($command1) && $execute($command2)) { return null; } } @@ -475,17 +481,18 @@ protected function updateToCommit(PackageInterface $package, string $path, strin $branch = 'v' . $branch; } - $command = sprintf('git checkout %s --', ProcessExecutor::escape($branch)); - $fallbackCommand = sprintf('git checkout '.$force.'-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch)); - $resetCommand = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference)); + $command = ['git', 'checkout', $branch, '--']; + $fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']); + $resetCommand = ['git', 'reset', '--hard', $reference, '--']; - if (0 === $this->process->execute("($command || $fallbackCommand) && $resetCommand", $output, $path)) { + if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) { return null; } } - $command = sprintf($template, ProcessExecutor::escape($gitRef)); - if (0 === $this->process->execute($command, $output, $path)) { + $command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']); + $command2 = ['git', 'reset', '--hard', $gitRef, '--']; + if ($execute($command1) && $execute($command2)) { return null; } @@ -497,12 +504,14 @@ protected function updateToCommit(PackageInterface $package, string $path, strin $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.'; } + $command = implode(' ', $command1). ' && '.implode(' ', $command2); + throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra)); } protected function updateOriginUrl(string $path, string $url): void { - $this->process->execute(sprintf('git remote set-url origin -- %s', ProcessExecutor::escape($url)), $output, $path); + $this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path); $this->setPushUrl($path, $url); } @@ -515,7 +524,7 @@ protected function setPushUrl(string $path, string $url): void if (!in_array('ssh', $protocols, true)) { $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git'; } - $cmd = sprintf('git remote set-url --push origin -- %s', ProcessExecutor::escape($pushUrl)); + $cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl]; $this->process->execute($cmd, $ignoredOutput, $path); } } @@ -526,10 +535,10 @@ protected function setPushUrl(string $path, string $url): void protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { $path = $this->normalizePath($path); - $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"'.GitUtil::getNoShowSignatureFlag($this->process), ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference)); + $command = array_merge(['git', 'log', $fromReference.'..'.$toReference, '--pretty=format:%h - %an: %s'], GitUtil::getNoShowSignatureFlags($this->process)); if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } return $output; @@ -542,7 +551,10 @@ protected function getCommitLogs(string $fromReference, string $toReference, str protected function discardChanges(string $path): PromiseInterface { $path = $this->normalizePath($path); - if (0 !== $this->process->execute('git clean -df && git reset --hard', $output, $path)) { + if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$output); + } + if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$output); } @@ -558,7 +570,7 @@ protected function discardChanges(string $path): PromiseInterface protected function stashChanges(string $path): PromiseInterface { $path = $this->normalizePath($path); - if (0 !== $this->process->execute('git stash --include-untracked', $output, $path)) { + if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) { throw new \RuntimeException("Could not stash changes\n\n:".$output); } @@ -573,7 +585,7 @@ protected function stashChanges(string $path): PromiseInterface protected function viewDiff(string $path): void { $path = $this->normalizePath($path); - if (0 !== $this->process->execute('git diff HEAD', $output, $path)) { + if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) { throw new \RuntimeException("Could not view diff\n\n:".$output); } diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index 9037f5226d14..010219822dfd 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -31,7 +31,7 @@ protected function extract(PackageInterface $package, string $file, string $path // Try to use gunzip on *nix if (!Platform::isWindows()) { - $command = 'gzip -cd -- ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath); + $command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath]; if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); @@ -44,7 +44,7 @@ protected function extract(PackageInterface $package, string $file, string $path return \React\Promise\resolve(null); } - $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index b0cc9cd7da2d..4709ae35ef38 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -41,16 +41,15 @@ protected function doInstall(PackageInterface $package, string $path, string $ur { $hgUtils = new HgUtils($this->io, $this->config, $this->process); - $cloneCommand = static function (string $url) use ($path): string { - return sprintf('hg clone -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path)); + $cloneCommand = static function (string $url) use ($path): array { + return ['hg', 'clone', '--', $url, $path]; }; $hgUtils->runCommand($cloneCommand, $url, $path); - $ref = ProcessExecutor::escape($package->getSourceReference()); - $command = sprintf('hg up -- %s', $ref); + $command = ['hg', 'up', '--', (string) $package->getSourceReference()]; if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } return \React\Promise\resolve(null); @@ -70,10 +69,14 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } - $command = static function ($url) use ($ref): string { - return sprintf('hg pull -- %s && hg up -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref)); + $command = static function ($url): array { + return ['hg', 'pull', '--', $url]; }; + $hgUtils->runCommand($command, $url, $path); + $command = static function () use ($ref): array { + return ['hg', 'up', '--', $ref]; + }; $hgUtils->runCommand($command, $url, $path); return \React\Promise\resolve(null); @@ -88,7 +91,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin return null; } - $this->process->execute('hg st', $output, realpath($path)); + $this->process->execute(['hg', 'st'], $output, realpath($path)); $output = trim($output); @@ -100,10 +103,10 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin */ protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { - $command = sprintf('hg log -r %s:%s --style compact', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference)); + $command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact']; if (0 !== $this->process->execute($command, $output, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } return $output; diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index a4142c65599b..04e16eaffbee 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -34,13 +34,13 @@ protected function extract(PackageInterface $package, string $file, string $path // Try to use unrar on *nix if (!Platform::isWindows()) { - $command = 'unrar x -- ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' . ProcessExecutor::escape($path); + $command = ['sh', '-c', 'unrar x -- "$0" "$1" >/dev/null && chmod -R u+w "$1"', $file, $path]; if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } - $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); } if (!class_exists('RarArchive')) { diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index be180d63d7f6..ca88ea839467 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -59,7 +59,7 @@ protected function doInstall(PackageInterface $package, string $path, string $ur } $this->io->writeError(" Checking out ".$package->getSourceReference()); - $this->execute($package, $url, "svn co", sprintf("%s/%s", $url, $ref), null, $path); + $this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path); return \React\Promise\resolve(null); } @@ -77,13 +77,13 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target, } $util = new SvnUtil($url, $this->io, $this->config, $this->process); - $flags = ""; + $flags = []; if (version_compare($util->binaryVersion(), '1.7.0', '>=')) { - $flags .= ' --ignore-ancestry'; + $flags[] = '--ignore-ancestry'; } $this->io->writeError(" Checking out " . $ref); - $this->execute($target, $url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path); + $this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path); return \React\Promise\resolve(null); } @@ -97,7 +97,7 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin return null; } - $this->process->execute('svn status --ignore-externals', $output, $path); + $this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path); return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null; } @@ -107,13 +107,13 @@ public function getLocalChanges(PackageInterface $package, string $path): ?strin * if necessary. * * @param string $baseUrl Base URL of the repository - * @param string $command SVN command to run + * @param non-empty-list $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @throws \RuntimeException */ - protected function execute(PackageInterface $package, string $baseUrl, string $command, string $url, ?string $cwd = null, ?string $path = null): string + protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string { $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); @@ -194,10 +194,10 @@ protected function getCommitLogs(string $fromReference, string $toReference, str { if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) { // retrieve the svn base url from the checkout folder - $command = sprintf('svn info --non-interactive --xml -- %s', ProcessExecutor::escape($path)); + $command = ['svn', 'info', '--non-interactive', '--xml', '--', $path]; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException( - 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() + 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput() ); } @@ -214,7 +214,7 @@ protected function getCommitLogs(string $fromReference, string $toReference, str $fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference); $toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference); - $command = sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision)); + $command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental']; $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); @@ -222,7 +222,7 @@ protected function getCommitLogs(string $fromReference, string $toReference, str return $util->executeLocal($command, $path, null, $this->io->isVerbose()); } catch (\RuntimeException $e) { throw new \RuntimeException( - 'Failed to execute ' . $command . "\n\n".$e->getMessage() + 'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage() ); } } @@ -235,7 +235,7 @@ protected function getCommitLogs(string $fromReference, string $toReference, str */ protected function discardChanges(string $path): PromiseInterface { - if (0 !== $this->process->execute('svn revert -R .', $output, $path)) { + if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); } diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index 8c44cd1997ec..286d32cff5ef 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -26,13 +26,13 @@ class XzDownloader extends ArchiveDownloader { protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface { - $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path); + $command = ['tar', '-xJf', $file, '-C', $path]; if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } - $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 1904668bea9e..54d23a5c6791 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -27,7 +27,7 @@ */ class ZipDownloader extends ArchiveDownloader { - /** @var array */ + /** @var array> */ private static $unzipCommands; /** @var bool */ private static $hasZipArchive; @@ -46,16 +46,16 @@ public function download(PackageInterface $package, string $path, ?PackageInterf self::$unzipCommands = []; $finder = new ExecutableFinder; if (Platform::isWindows() && ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) { - self::$unzipCommands[] = ['7z', ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s']; + self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } if ($cmd = $finder->find('unzip')) { - self::$unzipCommands[] = ['unzip', ProcessExecutor::escape($cmd).' -qq %s -d %s']; + self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%']; } if (!Platform::isWindows() && ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present - self::$unzipCommands[] = ['7z', ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s']; + self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } if (!Platform::isWindows() && ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present - self::$unzipCommands[] = ['7zz', ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s']; + self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } } @@ -114,24 +114,28 @@ private function extractWithSystemUnzip(PackageInterface $package, string $file, // Force Exception throwing if the other alternative extraction method is not available $isLastChance = !self::$hasZipArchive; - if (!self::$unzipCommands) { + if (0 === \count(self::$unzipCommands)) { // This was call as the favorite extract way, but is not available // We switch to the alternative return $this->extractWithZipArchive($package, $file, $path); } $commandSpec = reset(self::$unzipCommands); - $command = sprintf($commandSpec[1], ProcessExecutor::escape($file), ProcessExecutor::escape($path)); - // normalize separators to backslashes to avoid problems with 7-zip on windows - // see https://github.com/composer/composer/issues/10058 - if (Platform::isWindows()) { - $command = sprintf($commandSpec[1], ProcessExecutor::escape(strtr($file, '/', '\\')), ProcessExecutor::escape(strtr($path, '/', '\\'))); - } - $executable = $commandSpec[0]; + $command = array_slice($commandSpec, 1); + $map = [ + // normalize separators to backslashes to avoid problems with 7-zip on windows + // see https://github.com/composer/composer/issues/10058 + '%file%' => strtr($file, '/', DIRECTORY_SEPARATOR), + '%path%' => strtr($path, '/', DIRECTORY_SEPARATOR), + ]; + $command = array_map(static function ($value) use ($map) { + return strtr($value, $map); + }, $command); + if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz'], true)) { $warned7ZipLinux = true; - if (0 === $this->process->execute($executable, $output)) { + if (0 === $this->process->execute([$commandSpec[1]], $output)) { if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) { $this->io->writeError(' Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.'); } @@ -186,7 +190,7 @@ private function extractWithSystemUnzip(PackageInterface $package, string $file, $output = $process->getErrorOutput(); $output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output); - return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.$command."\n\n".$output)); + return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output)); } }); } catch (\Throwable $e) { diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 160031a30400..38cd8ef3a92d 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -554,13 +554,14 @@ private function getPackageTime(PackageInterface $package): ?string case 'git': GitUtil::cleanEnv(); - if (0 === $this->process->execute('git log -n1 --pretty=%ct '.ProcessExecutor::escape($sourceRef).GitUtil::getNoShowSignatureFlag($this->process), $output, $path) && Preg::isMatch('{^\s*\d+\s*$}', $output)) { + $command = array_merge(['git', 'log', '-n1', '--pretty=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process)); + if (0 === $this->process->execute($command, $output, $path) && Preg::isMatch('{^\s*\d+\s*$}', $output)) { $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); } break; case 'hg': - if (0 === $this->process->execute('hg log --template "{date|hgdate}" -r '.ProcessExecutor::escape($sourceRef), $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) { + if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) { $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC')); } break; diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 8a0fe9c14983..7e0f8278e443 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -198,7 +198,7 @@ private function guessGitVersion(array $packageConfig, string $path): array } if (null === $commit) { - $command = 'git log --pretty="%H" -n1 HEAD'.GitUtil::getNoShowSignatureFlag($this->process); + $command = array_merge(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process)); if (0 === $this->process->execute($command, $output, $path)) { $commit = trim($output) ?: null; } @@ -217,7 +217,7 @@ private function guessGitVersion(array $packageConfig, string $path): array private function versionFromGitTags(string $path): ?array { // try to fetch current version from git tags - if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) { + if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) { try { $version = $this->versionParser->normalize(trim($output)); @@ -237,7 +237,7 @@ private function versionFromGitTags(string $path): ?array private function guessHgVersion(array $packageConfig, string $path): ?array { // try to fetch current version from hg branch - if (0 === $this->process->execute('hg branch', $output, $path)) { + if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) { $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $isFeatureBranch = 0 === strpos($version, 'dev-'); @@ -375,14 +375,14 @@ private function guessFossilVersion(string $path): array $prettyVersion = null; // try to fetch current version from fossil - if (0 === $this->process->execute('fossil branch list', $output, $path)) { + if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) { $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $prettyVersion = 'dev-' . $branch; } // try to fetch current version from fossil tags - if (0 === $this->process->execute('fossil tag list', $output, $path)) { + if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) { try { $version = $this->versionParser->normalize(trim($output)); $prettyVersion = trim($output); @@ -403,7 +403,7 @@ private function guessSvnVersion(array $packageConfig, string $path): ?array SvnUtil::cleanEnv(); // try to fetch current version from svn - if (0 === $this->process->execute('svn info --xml', $output, $path)) { + if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) { $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches'; $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags'; diff --git a/src/Composer/Platform/HhvmDetector.php b/src/Composer/Platform/HhvmDetector.php index 46886c353418..284b0ba8ad25 100644 --- a/src/Composer/Platform/HhvmDetector.php +++ b/src/Composer/Platform/HhvmDetector.php @@ -49,11 +49,7 @@ public function getVersion(): ?string $hhvmPath = $this->executableFinder->find('hhvm'); if ($hhvmPath !== null) { $this->processExecutor = $this->processExecutor ?? new ProcessExecutor(); - $exitCode = $this->processExecutor->execute( - ProcessExecutor::escape($hhvmPath). - ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', - self::$hhvmVersion - ); + $exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion); if ($exitCode !== 0) { self::$hhvmVersion = false; } diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 4e99bd415ca6..0b8d992360fb 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -194,8 +194,8 @@ protected function initialize(): void // carry over the root package version if this path repo is in the same git repository as root package if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) { if ( - 0 === $this->process->execute('git rev-parse HEAD', $ref1, $path) - && 0 === $this->process->execute('git rev-parse HEAD', $ref2) + 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path) + && 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2) && $ref1 === $ref2 ) { $package['version'] = $this->versionGuesser->getRootVersionFromEnv(); @@ -203,7 +203,7 @@ protected function initialize(): void } $output = ''; - if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H'.GitUtil::getNoShowSignatureFlag($this->process), $output, $path)) { + if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute(array_merge(['git', 'log', '-n1', '--pretty=%H'], GitUtil::getNoShowSignatureFlags($this->process)), $output, $path)) { $package['dist']['reference'] = trim($output); } diff --git a/src/Composer/Repository/Vcs/FossilDriver.php b/src/Composer/Repository/Vcs/FossilDriver.php index e55b0d3acd74..8a305216c58c 100644 --- a/src/Composer/Repository/Vcs/FossilDriver.php +++ b/src/Composer/Repository/Vcs/FossilDriver.php @@ -71,7 +71,7 @@ public function initialize(): void */ protected function checkFossil(): void { - if (0 !== $this->process->execute('fossil version', $ignoredOutput)) { + if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) { throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput()); } } @@ -81,6 +81,8 @@ protected function checkFossil(): void */ protected function updateLocalRepo(): void { + assert($this->repoFile !== null); + $fs = new Filesystem(); $fs->ensureDirectoryExists($this->checkoutDir); @@ -89,8 +91,8 @@ protected function updateLocalRepo(): void } // update the repo if it is a valid fossil repository - if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute('fossil info', $output, $this->checkoutDir)) { - if (0 !== $this->process->execute('fossil pull', $output, $this->checkoutDir)) { + if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) { + if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) { $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { @@ -100,13 +102,13 @@ protected function updateLocalRepo(): void $fs->ensureDirectoryExists($this->checkoutDir); - if (0 !== $this->process->execute(sprintf('fossil clone -- %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoFile)), $output)) { + if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output); } - if (0 !== $this->process->execute(sprintf('fossil open --nested -- %s', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) { + if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output); @@ -155,10 +157,9 @@ public function getDist(string $identifier): ?array */ public function getFileContent(string $file, string $identifier): ?string { - $command = sprintf('fossil cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); - $this->process->execute($command, $content, $this->checkoutDir); + $this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir); - if (!trim($content)) { + if ('' === trim($content)) { return null; } @@ -170,7 +171,7 @@ public function getFileContent(string $file, string $identifier): ?string */ public function getChangeDate(string $identifier): ?\DateTimeImmutable { - $this->process->execute('fossil finfo -b -n 1 composer.json', $output, $this->checkoutDir); + $this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir); [, $date] = explode(' ', trim($output), 3); return new \DateTimeImmutable($date, new \DateTimeZone('UTC')); @@ -184,7 +185,7 @@ public function getTags(): array if (null === $this->tags) { $tags = []; - $this->process->execute('fossil tag list', $output, $this->checkoutDir); + $this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $tag) { $tags[$tag] = $tag; } @@ -203,7 +204,7 @@ public function getBranches(): array if (null === $this->branches) { $branches = []; - $this->process->execute('fossil branch list', $output, $this->checkoutDir); + $this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $branch) { $branch = trim(Preg::replace('/^\*/', '', trim($branch))); $branches[$branch] = $branch; @@ -237,7 +238,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo $process = new ProcessExecutor($io); // check whether there is a fossil repo in that path - if ($process->execute('fossil info', $output, $url) === 0) { + if ($process->execute(['fossil', 'info'], $output, $url) === 0) { return true; } } diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 57bc9b2ef0c0..7be1faba058d 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -102,7 +102,7 @@ public function getRootIdentifier(): string } // select currently checked out branch as default branch - $this->process->execute('git branch --no-color', $output, $this->repoDir); + $this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir); $branches = $this->process->splitLines($output); if (!in_array('* master', $branches)) { foreach ($branches as $branch) { @@ -150,8 +150,7 @@ public function getFileContent(string $file, string $identifier): ?string throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); } - $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); - $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); + $this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir); if (trim($content) === '') { return null; @@ -165,10 +164,7 @@ public function getFileContent(string $file, string $identifier): ?string */ public function getChangeDate(string $identifier): ?\DateTimeImmutable { - $this->process->execute(sprintf( - 'git -c log.showSignature=false log -1 --format=%%at %s', - ProcessExecutor::escape($identifier) - ), $output, $this->repoDir); + $this->process->execute(['git', '-c', 'log.showSignature=false', 'log', '-1', '--format=%at', $identifier], $output, $this->repoDir); return new \DateTimeImmutable('@'.trim($output), new \DateTimeZone('UTC')); } @@ -181,7 +177,7 @@ public function getTags(): array if (null === $this->tags) { $this->tags = []; - $this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir); + $this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { $this->tags[$match[2]] = $match[1]; @@ -200,7 +196,7 @@ public function getBranches(): array if (null === $this->branches) { $branches = []; - $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); + $this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { @@ -233,7 +229,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo $process = new ProcessExecutor($io); // check whether there is a git repo in that path - if ($process->execute('git tag', $output, $url) === 0) { + if ($process->execute(['git', 'tag'], $output, $url) === 0) { return true; } GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url); @@ -247,9 +243,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo GitUtil::cleanEnv(); try { - $gitUtil->runCommand(static function ($url): string { - return 'git ls-remote --heads -- ' . ProcessExecutor::escape($url); - }, $url, sys_get_temp_dir()); + $gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir()); } catch (\RuntimeException $e) { return false; } diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index e468ca746812..625a2a1ebded 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -63,8 +63,8 @@ public function initialize(): void $hgUtils = new HgUtils($this->io, $this->config, $this->process); // update the repo if it is a valid hg repository - if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) { - if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) { + if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) { + if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) { $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { @@ -72,8 +72,8 @@ public function initialize(): void $fs->removeDirectory($this->repoDir); $repoDir = $this->repoDir; - $command = static function ($url) use ($repoDir): string { - return sprintf('hg clone --noupdate -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir)); + $command = static function ($url) use ($repoDir): array { + return ['hg', 'clone', '--noupdate', '--', $url, $repoDir]; }; $hgUtils->runCommand($command, $this->url, null); @@ -90,7 +90,7 @@ public function initialize(): void public function getRootIdentifier(): string { if (null === $this->rootIdentifier) { - $this->process->execute('hg tip --template "{node}"', $output, $this->repoDir); + $this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir); $output = $this->process->splitLines($output); $this->rootIdentifier = $output[0]; } @@ -131,7 +131,7 @@ public function getFileContent(string $file, string $identifier): ?string throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); } - $resource = sprintf('hg cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $resource = ['hg', 'cat', '-r', $identifier, '--', $file]; $this->process->execute($resource, $content, $this->repoDir); if (!trim($content)) { @@ -147,10 +147,7 @@ public function getFileContent(string $file, string $identifier): ?string public function getChangeDate(string $identifier): ?\DateTimeImmutable { $this->process->execute( - sprintf( - 'hg log --template "{date|rfc3339date}" -r %s', - ProcessExecutor::escape($identifier) - ), + ['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier], $output, $this->repoDir ); @@ -166,7 +163,7 @@ public function getTags(): array if (null === $this->tags) { $tags = []; - $this->process->execute('hg tags', $output, $this->repoDir); + $this->process->execute(['hg', 'tags'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) { $tags[$match[1]] = $match[2]; @@ -189,14 +186,14 @@ public function getBranches(): array $branches = []; $bookmarks = []; - $this->process->execute('hg branches', $output, $this->repoDir); + $this->process->execute(['hg', 'branches'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } } - $this->process->execute('hg bookmarks', $output, $this->repoDir); + $this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { $bookmarks[$match[1]] = $match[2]; @@ -228,7 +225,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo $process = new ProcessExecutor($io); // check whether there is a hg repo in that path - if ($process->execute('hg summary', $output, $url) === 0) { + if ($process->execute(['hg', 'summary'], $output, $url) === 0) { return true; } } @@ -238,7 +235,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo } $process = new ProcessExecutor($io); - $exit = $process->execute(sprintf('hg identify -- %s', ProcessExecutor::escape($url)), $ignored); + $exit = $process->execute(['hg', 'identify', '--', $url], $ignored); return $exit === 0; } diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index d4f3180795c9..9a303ee14997 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -187,7 +187,7 @@ public function getFileContent(string $file, string $identifier): ?string try { $resource = $path.$file; - $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); + $output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev); if ('' === trim($output)) { return null; } @@ -213,7 +213,7 @@ public function getChangeDate(string $identifier): ?\DateTimeImmutable $rev = ''; } - $output = $this->execute('svn info', $this->baseUrl . $path . $rev); + $output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev); foreach ($this->process->splitLines($output) as $line) { if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); @@ -232,7 +232,7 @@ public function getTags(): array $tags = []; if ($this->tagsPath !== false) { - $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath); + $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath); if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines($output) as $line) { @@ -271,7 +271,7 @@ public function getBranches(): array $trunkParent = $this->baseUrl . '/' . $this->trunkPath; } - $output = $this->execute('svn ls --verbose', $trunkParent); + $output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent); if ($output !== '') { foreach ($this->process->splitLines($output) as $line) { $line = trim($line); @@ -290,7 +290,7 @@ public function getBranches(): array unset($output); if ($this->branchesPath !== false) { - $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath); + $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath); if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines(trim($output)) as $line) { @@ -331,10 +331,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo } $process = new ProcessExecutor($io); - $exit = $process->execute( - "svn info --non-interactive -- ".ProcessExecutor::escape($url), - $ignoredOutput - ); + $exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput); if ($exit === 0) { // This is definitely a Subversion repository. @@ -375,11 +372,11 @@ protected static function normalizeUrl(string $url): string * Execute an SVN command and try to fix up the process with credentials * if necessary. * - * @param string $command The svn command to run. + * @param non-empty-list $command The svn command to run. * @param string $url The SVN URL. * @throws \RuntimeException */ - protected function execute(string $command, string $url): string + protected function execute(array $command, string $url): string { if (null === $this->util) { $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process); diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index 2be4a815cb05..15743a7e3225 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -77,7 +77,7 @@ public function authorizeOAuth(string $originUrl): bool } // if available use token from git config - if (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) { + if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) { $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output)); return true; diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 099747add5b5..57e4fd6f3532 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -109,9 +109,9 @@ public function removeDirectory(string $directory) } if (Platform::isWindows()) { - $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory))); + $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; } else { - $cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory)); + $cmd = ['rm', '-rf', $directory]; } $result = $this->getProcess()->execute($cmd, $output) === 0; @@ -144,9 +144,9 @@ public function removeDirectoryAsync(string $directory) } if (Platform::isWindows()) { - $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory))); + $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; } else { - $cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory)); + $cmd = ['rm', '-rf', $directory]; } $promise = $this->getProcess()->executeAsync($cmd); @@ -427,8 +427,7 @@ public function rename(string $source, string $target) if (Platform::isWindows()) { // Try to copy & delete - this is a workaround for random "Access denied" errors. - $command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); - $result = $this->getProcess()->execute($command, $output); + $result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output); // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); @@ -441,8 +440,7 @@ public function rename(string $source, string $target) } else { // We do not use PHP's "rename" function here since it does not support // the case where $source, and $target are located on different partitions. - $command = sprintf('mv %s %s', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); - $result = $this->getProcess()->execute($command, $output); + $result = $this->getProcess()->execute(['mv', $source, $target], $output); // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); @@ -841,11 +839,7 @@ public function junction(string $target, string $junction) @rmdir($junction); } - $cmd = sprintf( - 'mklink /J %s %s', - ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)), - ProcessExecutor::escape(realpath($target)) - ); + $cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)]; if ($this->getProcess()->execute($cmd, $output) !== 0) { throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); } diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index f3369c8aa56d..e340c1b46d17 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -63,26 +63,90 @@ public function setHttpDownloader(HttpDownloader $httpDownloader): void } /** + * Runs a set of commands using the $url or a variation of it (with auth, ssh, ..) + * + * Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job + * %sanitizedUrl% is also automatically replaced by the url without user/pass + * + * As soon as a single command fails it will halt, so assume the commands are run as && in bash + * + * @param non-empty-array> $commands + * @param mixed $commandOutput the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + */ + public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void + { + $callables = []; + foreach ($commands as $cmd) { + $callables[] = static function (string $url) use ($cmd): array { + $map = [ + '%url%' => $url, + '%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url), + ]; + + return array_map(static function ($value) use ($map): string { + return $map[$value] ?? $value; + }, $cmd); + }; + } + + // @phpstan-ignore method.deprecated + $this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput); + } + + /** + * @param callable|array $commandCallable * @param mixed $commandOutput the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler + * @deprecated Use runCommands with placeholders instead of callbacks for simplicity */ - public function runCommand(callable $commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void + public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void { + $commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable; + $lastCommand = ''; + // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); if ($initialClone) { $origCwd = $cwd; - $cwd = null; } + $runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) { + $collectOutputs = !is_callable($commandOutput); + $outputs = []; + + $status = 0; + $counter = 0; + foreach ($commandCallables as $callable) { + $lastCommand = $callable($url); + if ($collectOutputs) { + $outputs[] = ''; + $output = &$outputs[count($outputs) - 1]; + } else { + $output = &$commandOutput; + } + $status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd); + if ($status !== 0) { + break; + } + $counter++; + } + + if ($collectOutputs) { + $commandOutput = implode('', $outputs); + } + + return $status; + }; + if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } if (!$initialClone) { // capture username/password from URL if there is one and we have no auth configured yet - $this->process->execute('git remote -v', $output, $cwd); + $this->process->execute(['git', 'remote', '-v'], $output, $cwd); if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) { $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2])); } @@ -100,7 +164,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; } - if (0 === $this->process->execute($commandCallable($protoUrl), $commandOutput, $cwd)) { + if (0 === $runCommands($protoUrl)) { return; } $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput()); @@ -119,11 +183,9 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); - $command = $commandCallable($url); - $auth = null; $credentials = []; - if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) { + if ($bypassSshForGitHub || 0 !== $runCommands($url)) { $errorMsg = $this->process->getErrorOutput(); // private github repository without ssh key access, try https with auth // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups @@ -143,8 +205,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; - $command = $commandCallable($authUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + if (0 === $runCommands($authUrl)) { return; } @@ -180,8 +241,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $auth = $this->io->getAuthentication($domain); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; - $command = $commandCallable($authUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + if (0 === $runCommands($authUrl)) { // Well if that succeeded on our first try, let's just // take the win. return; @@ -199,8 +259,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, if ($this->io->hasAuthentication($domain)) { $auth = $this->io->getAuthentication($domain); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; - $command = $commandCallable($authUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + if (0 === $runCommands($authUrl)) { return; } @@ -209,8 +268,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, //Falling back to ssh $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part; $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); - $command = $commandCallable($sshUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + if (0 === $runCommands($sshUrl)) { return; } @@ -242,8 +300,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3]; } - $command = $commandCallable($authUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + if (0 === $runCommands($authUrl)) { return; } @@ -280,8 +337,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, if (null !== $auth) { $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3]; - $command = $commandCallable($authUrl); - if (0 === $this->process->execute($command, $commandOutput, $cwd)) { + if (0 === $runCommands($authUrl)) { $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); $authHelper = new AuthHelper($this->io, $this->config); $authHelper->storeAuth($match[2], $storeAuth); @@ -298,11 +354,12 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd, $this->filesystem->removeDirectory($origCwd); } + $lastCommand = implode(' ', $lastCommand); if (count($credentials) > 0) { - $command = $this->maskCredentials($command, $credentials); + $lastCommand = $this->maskCredentials($lastCommand, $credentials); $errorMsg = $this->maskCredentials($errorMsg, $credentials); } - $this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url); + $this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url); } } @@ -315,14 +372,16 @@ public function syncMirror(string $url, string $dir): bool } // update the repo if it is a valid git repository - if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') { + if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { try { - $commandCallable = static function ($url): string { - $sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url); - - return sprintf('git remote set-url origin -- %s && git remote update --prune origin && git remote set-url origin -- %s && git gc --auto', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl)); - }; - $this->runCommand($commandCallable, $url, $dir); + $commands = [ + ['git', 'remote', 'set-url', 'origin', '--', '%url%'], + ['git', 'remote', 'update', '--prune', 'origin'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'gc', '--auto'], + ]; + + $this->runCommands($commands, $url, $dir); } catch (\Exception $e) { $this->io->writeError('Sync mirror failed: ' . $e->getMessage() . '', true, IOInterface::DEBUG); @@ -336,11 +395,7 @@ public function syncMirror(string $url, string $dir): bool // clean up directory and do a fresh clone into it $this->filesystem->removeDirectory($dir); - $commandCallable = static function ($url) use ($dir): string { - return sprintf('git clone --mirror -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($dir)); - }; - - $this->runCommand($commandCallable, $url, $dir, true); + $this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true); return true; } @@ -352,10 +407,10 @@ public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?str $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); $branches = null; $tags = null; - if (0 === $this->process->execute('git branch', $output, $dir)) { + if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) { $branches = $output; } - if (0 === $this->process->execute('git tag', $output, $dir)) { + if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) { $tags = $output; } @@ -390,11 +445,23 @@ public static function getNoShowSignatureFlag(ProcessExecutor $process): string return ''; } + /** + * @return list + */ + public static function getNoShowSignatureFlags(ProcessExecutor $process): array + { + $flags = static::getNoShowSignatureFlag($process); + if ('' === $flags) { + return []; + } + + return explode(' ', substr($flags, 1)); + } + private function checkRefIsInMirror(string $dir, string $ref): bool { - if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') { - $escapedRef = ProcessExecutor::escape($ref.'^{commit}'); - $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir); + if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { + $exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir); if ($exitCode === 0) { return true; } @@ -439,15 +506,15 @@ public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPa try { if ($isLocalPathRepository) { - $this->process->execute('git remote show origin', $output, $dir); + $this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir); } else { - $commandCallable = static function ($url): string { - $sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url); - - return sprintf('git remote set-url origin -- %s && git remote show origin && git remote set-url origin -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl)); - }; + $commands = [ + ['git', 'remote', 'set-url', 'origin', '--', '%url%'], + ['git', 'remote', 'show', 'origin'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ]; - $this->runCommand($commandCallable, $url, $dir, false, $output); + $this->runCommands($commands, $url, $dir, false, $output); } $lines = $this->process->splitLines($output); @@ -513,7 +580,7 @@ private function throwException($message, string $url): void // git might delete a directory when it fails and php will not know clearstatcache(); - if (0 !== $this->process->execute('git --version', $ignoredOutput)) { + if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) { throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); } @@ -529,7 +596,7 @@ public static function getVersion(ProcessExecutor $process): ?string { if (false === self::$version) { self::$version = null; - if (0 === $process->execute('git --version', $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { + if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { self::$version = $matches[1]; } } diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 3574e5183215..64ee4f559f59 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -61,7 +61,7 @@ public function authorizeOAuth(string $originUrl): bool } // if available use token from git config - if (0 === $this->process->execute('git config github.accesstoken', $output)) { + if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) { $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic'); return true; @@ -86,7 +86,7 @@ public function authorizeOAuthInteractively(string $originUrl, ?string $message } $note = 'Composer'; - if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute('hostname', $output)) { + if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) { $note .= ' on ' . trim($output); } $note .= ' ' . date('Y-m-d Hi'); diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index e5985c2db7fe..35d8e0dedd9f 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -65,14 +65,14 @@ public function authorizeOAuth(string $originUrl): bool } // if available use token from git config - if (0 === $this->process->execute('git config gitlab.accesstoken', $output)) { + if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) { $this->io->setAuthentication($originUrl, trim($output), 'oauth2'); return true; } // if available use deploy token from git config - if (0 === $this->process->execute('git config gitlab.deploytoken.user', $tokenUser) && 0 === $this->process->execute('git config gitlab.deploytoken.token', $tokenPassword)) { + if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) { $this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword)); return true; diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 28107584b670..34b4796fa49b 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -111,7 +111,7 @@ public static function getVersion(ProcessExecutor $process): ?string { if (false === self::$version) { self::$version = null; - if (0 === $process->execute('hg --version', $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) { + if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) { self::$version = $matches[1]; } } diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index ff931158c178..bfed834b16cd 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -14,6 +14,7 @@ use Composer\IO\IOInterface; use Composer\Pcre\Preg; +use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; /** @@ -81,7 +82,7 @@ public static function create($repoConfig, string $port, string $path, ProcessEx public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool { - return 0 === $processExecutor->execute('p4 -p ' . ProcessExecutor::escape($url) . ' info -s', $ignoredOutput); + return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput); } /** @@ -248,7 +249,7 @@ public function queryP4User(): void } $this->p4User = $this->io->ask('Enter P4 User:'); if ($this->windowsFlag) { - $command = 'p4 set P4USER=' . $this->p4User; + $command = $this->getP4Executable().' set P4USER=' . $this->p4User; } else { $command = 'export P4USER=' . $this->p4User; } @@ -261,7 +262,7 @@ public function queryP4User(): void protected function getP4variable(string $name): ?string { if ($this->windowsFlag) { - $command = 'p4 set'; + $command = $this->getP4Executable().' set'; $this->executeCommand($command); $result = trim($this->commandResult); $resArray = explode(PHP_EOL, $result); @@ -309,7 +310,7 @@ public function queryP4Password(): ?string */ public function generateP4Command(string $command, bool $useClient = true): string { - $p4Command = 'p4 '; + $p4Command = $this->getP4Executable().' '; $p4Command .= '-u ' . $this->getUser() . ' '; if ($useClient) { $p4Command .= '-c ' . $this->getClient() . ' '; @@ -620,4 +621,17 @@ public function setFilesystem(Filesystem $fs): void { $this->filesystem = $fs; } + + private function getP4Executable(): string + { + static $p4Executable; + + if ($p4Executable) { + return $p4Executable; + } + + $finder = new ExecutableFinder(); + + return $p4Executable = $finder->find('p4') ?? 'p4'; + } } diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index b13fe5bb829a..dcbfaa1c5104 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -54,6 +54,19 @@ public static function getCwd(bool $allowEmpty = false): string return $cwd; } + /** + * Infallible realpath version that falls back on the given $path if realpath is not working + */ + public static function realpath(string $path): string + { + $realPath = realpath($path); + if ($realPath === false) { + return $path; + } + + return $realPath; + } + /** * getenv() equivalent but reads from the runtime global variables first * @@ -308,7 +321,7 @@ private static function isVirtualBoxGuest(): bool if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') { $process = new ProcessExecutor(); try { - if (0 === $process->execute('lsmod | grep vboxguest', $ignoredOutput)) { + if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) { return self::$isVirtualBoxGuest = true; } } catch (\Exception $e) { diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index bf4b49846bf8..dc773beb91ee 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -20,6 +20,7 @@ use Symfony\Component\Process\Exception\RuntimeException; use React\Promise\Promise; use React\Promise\PromiseInterface; +use Symfony\Component\Process\ExecutableFinder; /** * @author Robert Schönthal @@ -33,6 +34,14 @@ class ProcessExecutor private const STATUS_FAILED = 4; private const STATUS_ABORTED = 5; + private const BUILTIN_CMD_COMMANDS = [ + 'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date', + 'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto', + 'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause', + 'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set', + 'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol', + ]; + private const GIT_CMDS_NEED_GIT_DIR = [ ['show'], ['log'], @@ -63,6 +72,9 @@ class ProcessExecutor /** @var bool */ private $allowAsync = false; + /** @var array */ + private static $executables = []; + public function __construct(?IOInterface $io = null) { $this->io = $io; @@ -71,7 +83,7 @@ public function __construct(?IOInterface $io = null) /** * runs a process on the commandline * - * @param string|list $command the command to execute + * @param string|non-empty-list $command the command to execute * @param mixed $output the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler * @param null|string $cwd the working directory @@ -89,7 +101,7 @@ public function execute($command, &$output = null, ?string $cwd = null): int /** * runs a process on the commandline in TTY mode * - * @param string|list $command the command to execute + * @param string|non-empty-list $command the command to execute * @param null|string $cwd the working directory * @return int statuscode */ @@ -103,15 +115,26 @@ public function executeTty($command, ?string $cwd = null): int } /** - * @param string|list $command + * @param string|non-empty-list $command * @param array|null $env * @param mixed $output */ private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int { + // On Windows, we don't rely on the OS to find the executable if possible to avoid lookups + // in the current directory which could be untrusted. Instead we use the ExecutableFinder. + if (is_string($command)) { + if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) { + $command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1])); + } + $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout()); } else { + if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) { + $command[0] = self::getExecutable($command[0]); + } + $process = new Process($command, $cwd, $env, null, static::getTimeout()); } @@ -161,7 +184,7 @@ function (string $signal) { } /** - * @param string|list $command + * @param string|non-empty-list $command * @param mixed $output */ private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int @@ -178,7 +201,7 @@ private function doExecute($command, ?string $cwd, bool $tty, &$output = null): $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/'))); if ($isBareRepository) { $configValue = ''; - $this->runProcess('git config safe.bareRepository', $cwd, ['GIT_DIR' => $cwd], $tty, $configValue); + $this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue); $configValue = trim($configValue); if ($configValue === 'explicit') { $env = ['GIT_DIR' => $cwd]; @@ -550,4 +573,23 @@ public function requiresGitDirEnv($command): bool return false; } + + /** + * Resolves executable paths on Windows + */ + private static function getExecutable(string $name): string + { + if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) { + return $name; + } + + if (!isset(self::$executables[$name])) { + $path = (new ExecutableFinder())->find($name, $name); + if ($path !== null) { + self::$executables[$name] = $path; + } + } + + return self::$executables[$name] ?? $name; + } } diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index ea7d5dbeb7a1..506a14ec752b 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -90,7 +90,7 @@ public static function cleanEnv(): void * Execute an SVN remote command and try to fix up the process with credentials * if necessary. * - * @param string $command SVN command to run + * @param non-empty-list $command SVN command to run * @param string $url SVN url * @param ?string $cwd Working directory * @param ?string $path Target for a checkout @@ -98,7 +98,7 @@ public static function cleanEnv(): void * * @throws \RuntimeException */ - public function execute(string $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string + public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); @@ -110,20 +110,23 @@ public function execute(string $command, string $url, ?string $cwd = null, ?stri * Execute an SVN local command and try to fix up the process with credentials * if necessary. * - * @param string $command SVN command to run + * @param non-empty-list $command SVN command to run * @param string $path Path argument passed thru to the command * @param string $cwd Working directory * @param bool $verbose Output all output to the user * * @throws \RuntimeException */ - public function executeLocal(string $command, string $path, ?string $cwd = null, bool $verbose = false): string + public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string { // A local command has no remote url return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); } - private function executeWithAuthRetry(string $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string + /** + * @param non-empty-list $svnCommand + */ + private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string { // Regenerate the command at each try, to use the newly user-provided credentials $command = $this->getCommand($svnCommand, $url, $path); @@ -209,22 +212,23 @@ protected function doAuthDance(): Svn /** * A method to create the svn commands run. * - * @param string $cmd Usually 'svn ls' or something like that. + * @param non-empty-list $cmd Usually 'svn ls' or something like that. * @param string $url Repo URL. * @param string $path Target for a checkout + * + * @return non-empty-list */ - protected function getCommand(string $cmd, string $url, ?string $path = null): string + protected function getCommand(array $cmd, string $url, ?string $path = null): array { - $cmd = sprintf( - '%s %s%s -- %s', + $cmd = array_merge( $cmd, - '--non-interactive ', - $this->getCredentialString(), - ProcessExecutor::escape($url) + ['--non-interactive'], + $this->getCredentialArgs(), + ['--', $url] ); - if ($path) { - $cmd .= ' ' . ProcessExecutor::escape($path); + if ($path !== null) { + $cmd[] = $path; } return $cmd; @@ -234,18 +238,18 @@ protected function getCommand(string $cmd, string $url, ?string $path = null): s * Return the credential string for the svn command. * * Adds --no-auth-cache when credentials are present. + * + * @return list */ - protected function getCredentialString(): string + protected function getCredentialArgs(): array { if (!$this->hasAuth()) { - return ''; + return []; } - return sprintf( - ' %s--username %s --password %s ', - $this->getAuthCache(), - ProcessExecutor::escape($this->getUsername()), - ProcessExecutor::escape($this->getPassword()) + return array_merge( + $this->getAuthCacheArgs(), + ['--username', $this->getUsername(), '--password', $this->getPassword()] ); } @@ -295,10 +299,12 @@ protected function hasAuth(): bool /** * Return the no-auth-cache switch. + * + * @return list */ - protected function getAuthCache(): string + protected function getAuthCacheArgs(): array { - return $this->cacheCredentials ? '' : '--no-auth-cache '; + return $this->cacheCredentials ? [] : ['--no-auth-cache']; } /** @@ -349,7 +355,7 @@ private function createAuthFromUrl(): bool public function binaryVersion(): ?string { if (!self::$version) { - if (0 === $this->process->execute('svn --version', $output)) { + if (0 === $this->process->execute(['svn', '--version'], $output)) { if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) { self::$version = $match[1]; } diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 807fe5ce9cb0..1ef22001d43c 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -87,6 +87,7 @@ public function testBuildPhar(): void } $proc = new Process([PHP_BINARY, '-dphar.readonly=0', './bin/compile'], $target); + $proc->setTimeout(300); $exitcode = $proc->run(); if ($exitcode !== 0 || trim($proc->getOutput()) !== '') { diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 7ab784357871..0f8103e5e570 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -76,9 +76,9 @@ public function testInstall(): void $process = $this->getProcessExecutorMock(); $process->expects([ - self::getCmd('fossil clone -- \'http://fossil.kd2.org/kd2fw/\' \''.$this->workingDir.'.fossil\''), - self::getCmd('fossil open --nested -- \''.$this->workingDir.'.fossil\''), - self::getCmd('fossil update -- \'trunk\''), + ['fossil', 'clone', '--', 'http://fossil.kd2.org/kd2fw/', $this->workingDir.'.fossil'], + ['fossil', 'open', '--nested', '--', $this->workingDir.'.fossil'], + ['fossil', 'update', '--', 'trunk'], ], true); $downloader = $this->getDownloaderMock(null, null, $process); @@ -123,8 +123,9 @@ public function testUpdate(): void $process = $this->getProcessExecutorMock(); $process->expects([ - self::getCmd("fossil changes"), - self::getCmd("fossil pull && fossil up 'trunk'"), + ['fossil', 'changes'], + ['fossil', 'pull'], + ['fossil', 'up', 'trunk'], ], true); $downloader = $this->getDownloaderMock(null, null, $process); @@ -143,7 +144,7 @@ public function testRemove(): void $process = $this->getProcessExecutorMock(); $process->expects([ - self::getCmd('fossil changes'), + ['fossil', 'changes'], ], true); $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 50db7c73d792..a46054b87e0e 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -122,10 +122,16 @@ public function testDownload(): void ->will($this->returnValue('dev-master')); $process = $this->getProcessExecutorMock(); + $expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath'; $process->expects([ - $this->winCompat("git clone --no-checkout -- 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin -- 'https://example.com/composer/composer' && git remote set-url composer -- 'https://example.com/composer/composer'"), - $this->winCompat("git branch -r"), - $this->winCompat("(git checkout 'master' -- || git checkout -B 'master' 'composer/master' --) && git reset --hard '1234567890123456789012345678901234567890' --"), + ['git', 'clone', '--no-checkout', '--', 'https://example.com/composer/composer', $expectedPath], + ['git', 'remote', 'add', 'composer', '--', 'https://example.com/composer/composer'], + ['git', 'fetch', 'composer'], + ['git', 'remote', 'set-url', 'origin', '--', 'https://example.com/composer/composer'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://example.com/composer/composer'], + ['git', 'branch', '-r'], + ['git', 'checkout', 'master', '--'], + ['git', 'reset', '--hard', '1234567890123456789012345678901234567890', '--'], ], true); $downloader = $this->getDownloaderMock(null, null, $process); @@ -160,16 +166,24 @@ public function testDownloadWithCache(): void $filesystem = new \Composer\Util\Filesystem; $filesystem->removeDirectory($cachePath); + $expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath'; $process = $this->getProcessExecutorMock(); $process->expects([ - ['cmd' => $this->winCompat(sprintf("git clone --mirror -- 'https://example.com/composer/composer' '%s'", $cachePath)), 'callback' => static function () use ($cachePath): void { - @mkdir($cachePath, 0777, true); - }], - ['cmd' => 'git rev-parse --git-dir', 'stdout' => '.'], - $this->winCompat('git rev-parse --quiet --verify \'1234567890123456789012345678901234567890^{commit}\''), - $this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin -- 'https://example.com/composer/composer' && git remote add composer -- 'https://example.com/composer/composer'", $cachePath)), - 'git branch -r', - $this->winCompat("(git checkout 'master' -- || git checkout -B 'master' 'composer/master' --) && git reset --hard '1234567890123456789012345678901234567890' --"), + [ + 'cmd' => ['git', 'clone', '--mirror', '--', 'https://example.com/composer/composer', $cachePath], + 'callback' => static function () use ($cachePath): void { + @mkdir($cachePath, 0777, true); + } + ], + ['cmd' => ['git', 'rev-parse', '--git-dir'], 'stdout' => '.'], + ['git', 'rev-parse', '--quiet', '--verify', '1234567890123456789012345678901234567890^{commit}'], + ['git', 'clone', '--no-checkout', $cachePath, $expectedPath, '--dissociate', '--reference', $cachePath], + ['git', 'remote', 'set-url', 'origin', '--', 'https://example.com/composer/composer'], + ['git', 'remote', 'add', 'composer', '--', 'https://example.com/composer/composer'], + ['git', 'branch', '-r'], + ['cmd' => ['git', 'checkout', 'master', '--'], 'return' => 1], + ['git', 'checkout', '-B', 'master', 'composer/master', '--'], + ['git', 'reset', '--hard', '1234567890123456789012345678901234567890', '--'], ], true); $downloader = $this->getDownloaderMock(null, $config, $process); @@ -197,17 +211,21 @@ public function testDownloadUsesVariousProtocolsAndSetsPushUrlForGithub(): void ->will($this->returnValue('1.0.0')); $process = $this->getProcessExecutorMock(); + $expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath'; $process->expects([ - [ - 'cmd' => $this->winCompat("git clone --no-checkout -- 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'https://github.com/mirrors/composer' && git fetch composer && git remote set-url origin -- 'https://github.com/mirrors/composer' && git remote set-url composer -- 'https://github.com/mirrors/composer'"), - 'return' => 1, - 'stderr' => 'Error1', - ], - $this->winCompat("git clone --no-checkout -- 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'git@github.com:mirrors/composer' && git fetch composer && git remote set-url origin -- 'git@github.com:mirrors/composer' && git remote set-url composer -- 'git@github.com:mirrors/composer'"), - $this->winCompat("git remote set-url origin -- 'https://github.com/composer/composer'"), - $this->winCompat("git remote set-url --push origin -- 'git@github.com:composer/composer.git'"), - 'git branch -r', - $this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), + ['cmd' => ['git', 'clone', '--no-checkout', '--', 'https://github.com/mirrors/composer', $expectedPath], 'return' => 1, 'stderr' => 'Error1'], + + ['git', 'clone', '--no-checkout', '--', 'git@github.com:mirrors/composer', $expectedPath], + ['git', 'remote', 'add', 'composer', '--', 'git@github.com:mirrors/composer'], + ['git', 'fetch', 'composer'], + ['git', 'remote', 'set-url', 'origin', '--', 'git@github.com:mirrors/composer'], + ['git', 'remote', 'set-url', 'composer', '--', 'git@github.com:mirrors/composer'], + + ['git', 'remote', 'set-url', 'origin', '--', 'https://github.com/composer/composer'], + ['git', 'remote', 'set-url', '--push', 'origin', '--', 'git@github.com:composer/composer.git'], + ['git', 'branch', '-r'], + ['git', 'checkout', 'ref', '--'], + ['git', 'reset', '--hard', 'ref', '--'], ], true); $downloader = $this->getDownloaderMock(null, new Config(), $process); @@ -250,11 +268,18 @@ public function testDownloadAndSetPushUrlUseCustomVariousProtocolsForGithub(arra ->will($this->returnValue('1.0.0')); $process = $this->getProcessExecutorMock(); + $expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath'; $process->expects([ - $this->winCompat("git clone --no-checkout -- '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer -- '{$url}' && git fetch composer && git remote set-url origin -- '{$url}' && git remote set-url composer -- '{$url}'"), - $this->winCompat("git remote set-url --push origin -- '{$pushUrl}'"), - 'git branch -r', - $this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), + ['git', 'clone', '--no-checkout', '--', $url, $expectedPath], + ['git', 'remote', 'add', 'composer', '--', $url], + ['git', 'fetch', 'composer'], + ['git', 'remote', 'set-url', 'origin', '--', $url], + ['git', 'remote', 'set-url', 'composer', '--', $url], + + ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl], + ['git', 'branch', '-r'], + ['git', 'checkout', 'ref', '--'], + ['git', 'reset', '--hard', 'ref', '--'], ], true); $config = new Config(); @@ -284,28 +309,21 @@ public function testDownloadThrowsRuntimeExceptionIfGitCommandFails(): void ->will($this->returnValue('1.0.0')); $process = $this->getProcessExecutorMock(); + $expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath'; $process->expects([ [ - 'cmd' => $this->winCompat("git clone --no-checkout -- 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin -- 'https://example.com/composer/composer' && git remote set-url composer -- 'https://example.com/composer/composer'"), + 'cmd' => ['git', 'clone', '--no-checkout', '--', 'https://example.com/composer/composer', $expectedPath], 'return' => 1, ], ]); - // not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe - try { - $downloader = $this->getDownloaderMock(null, null, $process); - $downloader->download($packageMock, 'composerPath'); - $downloader->prepare('install', $packageMock, 'composerPath'); - $downloader->install($packageMock, 'composerPath'); - $downloader->cleanup('install', $packageMock, 'composerPath'); - - $this->fail('This test should throw'); - } catch (\RuntimeException $e) { - if ('RuntimeException' !== get_class($e)) { - throw $e; - } - self::assertEquals('RuntimeException', get_class($e)); - } + self::expectException('RuntimeException'); + self::expectExceptionMessage('Failed to execute git clone --no-checkout -- https://example.com/composer/composer '.$expectedPath); + $downloader = $this->getDownloaderMock(null, null, $process); + $downloader->download($packageMock, 'composerPath'); + $downloader->prepare('install', $packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); + $downloader->cleanup('install', $packageMock, 'composerPath'); } public function testUpdateforPackageWithoutSourceReference(): void @@ -327,8 +345,6 @@ public function testUpdateforPackageWithoutSourceReference(): void public function testUpdate(): void { - $expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'"); - $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -345,13 +361,23 @@ public function testUpdate(): void $process = $this->getProcessExecutorMock(); $process->expects([ - $this->winCompat('git show-ref --head -d'), - $this->winCompat('git status --porcelain --untracked-files=no'), - $this->winCompat('git remote -v'), - $expectedGitUpdateCommand, - $this->winCompat('git branch -r'), - $this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), - $this->winCompat('git remote -v'), + ['git', 'show-ref', '--head', '-d'], + ['git', 'status', '--porcelain', '--untracked-files=no'], + ['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1], + + // fallback commands for the above failing + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'], + ['git', 'fetch', 'composer'], + ['git', 'fetch', '--tags', 'composer'], + + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'], + + ['git', 'branch', '-r'], + ['git', 'checkout', 'ref', '--'], + ['git', 'reset', '--hard', 'ref', '--'], + ['git', 'remote', '-v'], ], true); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); @@ -364,8 +390,6 @@ public function testUpdate(): void public function testUpdateWithNewRepoUrl(): void { - $expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'"); - $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -385,22 +409,26 @@ public function testUpdateWithNewRepoUrl(): void $process = $this->getProcessExecutorMock(); $process->expects([ - $this->winCompat("git show-ref --head -d"), - $this->winCompat("git status --porcelain --untracked-files=no"), - $this->winCompat("git remote -v"), - $this->winCompat($expectedGitUpdateCommand), - 'git branch -r', - $this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), + ['git', 'show-ref', '--head', '-d'], + ['git', 'status', '--porcelain', '--untracked-files=no'], + ['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 0], + + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'], + + ['git', 'branch', '-r'], + ['git', 'checkout', 'ref', '--'], + ['git', 'reset', '--hard', 'ref', '--'], [ - 'cmd' => $this->winCompat("git remote -v"), + 'cmd' => ['git', 'remote', '-v'], 'stdout' => 'origin https://github.com/old/url (fetch) origin https://github.com/old/url (push) composer https://github.com/old/url (fetch) composer https://github.com/old/url (push) ', ], - $this->winCompat("git remote set-url origin -- 'https://github.com/composer/composer'"), - $this->winCompat("git remote set-url --push origin -- 'git@github.com:composer/composer.git'"), + ['git', 'remote', 'set-url', 'origin', '--', 'https://github.com/composer/composer'], + ['git', 'remote', 'set-url', '--push', 'origin', '--', 'git@github.com:composer/composer.git'], ], true); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); @@ -416,9 +444,6 @@ public function testUpdateWithNewRepoUrl(): void */ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails(): void { - $expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'"); - $expectedGitUpdateCommand2 = $this->winCompat("(git remote set-url composer -- 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'git@github.com:composer/composer'"); - $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -432,44 +457,38 @@ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails(): void $process = $this->getProcessExecutorMock(); $process->expects([ - $this->winCompat('git show-ref --head -d'), - $this->winCompat('git status --porcelain --untracked-files=no'), - $this->winCompat('git remote -v'), - [ - 'cmd' => $expectedGitUpdateCommand, - 'return' => 1, - ], - [ - 'cmd' => $expectedGitUpdateCommand2, - 'return' => 1, - ], - $this->winCompat('git --version'), + ['git', 'show-ref', '--head', '-d'], + ['git', 'status', '--porcelain', '--untracked-files=no'], + + // commit not yet in so we try to fetch + ['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1], + + // fail first fetch + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'], + ['cmd' => ['git', 'fetch', 'composer'], 'return' => 1], + + // fail second fetch + ['git', 'remote', 'set-url', 'composer', '--', 'git@github.com:composer/composer'], + ['cmd' => ['git', 'fetch', 'composer'], 'return' => 1], + + ['git', '--version'], ], true); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); - // not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe - try { - $downloader = $this->getDownloaderMock(null, new Config(), $process); - $downloader->download($packageMock, $this->workingDir, $packageMock); - $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); - $downloader->update($packageMock, $packageMock, $this->workingDir); - $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); - - $this->fail('This test should throw'); - } catch (\RuntimeException $e) { - if ('RuntimeException' !== get_class($e)) { - throw $e; - } - self::assertEquals('RuntimeException', get_class($e)); - } + self::expectException('RuntimeException'); + self::expectExceptionMessage('Failed to clone https://github.com/composer/composer via https, ssh protocols, aborting.'); + self::expectExceptionMessageMatches('{git@github\.com:composer/composer}'); + $downloader = $this->getDownloaderMock(null, new Config(), $process); + $downloader->download($packageMock, $this->workingDir, $packageMock); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); + $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); } public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover(): void { - $expectedFirstGitUpdateCommand = $this->winCompat("(git remote set-url composer -- '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- '".(Platform::isWindows() ? 'C:\\' : '/')."'"); - $expectedSecondGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'"); - $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -486,22 +505,33 @@ public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstBu $process = $this->getProcessExecutorMock(); $process->expects([ - $this->winCompat('git show-ref --head -d'), - $this->winCompat('git status --porcelain --untracked-files=no'), - $this->winCompat('git remote -v'), - [ - 'cmd' => $expectedFirstGitUpdateCommand, - 'return' => 1, - ], - $this->winCompat('git --version'), - $this->winCompat('git remote -v'), - [ - 'cmd' => $expectedSecondGitUpdateCommand, - 'return' => 0, - ], - $this->winCompat('git branch -r'), - $this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), - $this->winCompat('git remote -v'), + ['git', 'show-ref', '--head', '-d'], + ['git', 'status', '--porcelain', '--untracked-files=no'], + + // commit not yet in so we try to fetch + ['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1], + + // fail first source URL + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', Platform::isWindows() ? 'C:\\' : '/'], + ['cmd' => ['git', 'fetch', 'composer'], 'return' => 1], + ['git', '--version'], + + // commit not yet in so we try to fetch + ['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1], + + // pass second source URL + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'], + ['cmd' => ['git', 'fetch', 'composer'], 'return' => 0], + ['git', 'fetch', '--tags', 'composer'], + ['git', 'remote', '-v'], + ['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'], + + ['git', 'branch', '-r'], + ['git', 'checkout', 'ref', '--'], + ['git', 'reset', '--hard', 'ref', '--'], + ['git', 'remote', '-v'], ], true); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); @@ -604,13 +634,11 @@ public function testNotUsingDowngradingWithReferences(): void public function testRemove(): void { - $expectedGitResetCommand = $this->winCompat("git status --porcelain --untracked-files=no"); - $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $process = $this->getProcessExecutorMock(); $process->expects([ - 'git show-ref --head -d', - $expectedGitResetCommand, + ['git', 'show-ref', '--head', '-d'], + ['git', 'status', '--porcelain', '--untracked-files=no'], ], true); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); @@ -633,16 +661,4 @@ public function testGetInstallationSource(): void self::assertEquals('source', $downloader->getInstallationSource()); } - - private function winCompat(string $cmd): string - { - if (Platform::isWindows()) { - $cmd = str_replace('cd ', 'cd /D ', $cmd); - $cmd = str_replace('composerPath', Platform::getCwd().'/composerPath', $cmd); - - return self::getCmd($cmd); - } - - return $cmd; - } } diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 544d529941db..3788864630d5 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -76,8 +76,8 @@ public function testDownload(): void $process = $this->getProcessExecutorMock(); $process->expects([ - self::getCmd('hg clone -- \'https://mercurial.dev/l3l0/composer\' \''.$this->workingDir.'\''), - self::getCmd('hg up -- \'ref\''), + ['hg', 'clone', '--', 'https://mercurial.dev/l3l0/composer', $this->workingDir], + ['hg', 'up', '--', 'ref'], ], true); $downloader = $this->getDownloaderMock(null, null, $process); @@ -117,8 +117,9 @@ public function testUpdate(): void $process = $this->getProcessExecutorMock(); $process->expects([ - self::getCmd('hg st'), - self::getCmd("hg pull -- 'https://github.com/l3l0/composer' && hg up -- 'ref'"), + ['hg', 'st'], + ['hg', 'pull', '--', 'https://github.com/l3l0/composer'], + ['hg', 'up', '--', 'ref'], ], true); $downloader = $this->getDownloaderMock(null, null, $process); @@ -135,7 +136,7 @@ public function testRemove(): void $process = $this->getProcessExecutorMock(); $process->expects([ - self::getCmd('hg st'), + ['hg', 'st'], ], true); $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); diff --git a/tests/Composer/Test/Mock/ProcessExecutorMock.php b/tests/Composer/Test/Mock/ProcessExecutorMock.php index c90b6bc1dd81..00f094990ca8 100644 --- a/tests/Composer/Test/Mock/ProcessExecutorMock.php +++ b/tests/Composer/Test/Mock/ProcessExecutorMock.php @@ -57,16 +57,16 @@ public function __construct(MockBuilder $processMockBuilder) } /** - * @param array, return?: int, stdout?: string, stderr?: string, callback?: callable}> $expectations + * @param array|array{cmd: string|non-empty-list, return?: int, stdout?: string, stderr?: string, callback?: callable}> $expectations * @param bool $strict set to true if you want to provide *all* expected commands, and not just a subset you are interested in testing * @param array{return: int, stdout?: string, stderr?: string} $defaultHandler default command handler for undefined commands if not in strict mode */ public function expects(array $expectations, bool $strict = false, array $defaultHandler = ['return' => 0, 'stdout' => '', 'stderr' => '']): void { - /** @var array{cmd: string|list, return: int, stdout: string, stderr: string, callback: callable|null} $default */ + /** @var array{cmd: string|non-empty-list, return: int, stdout: string, stderr: string, callback: callable|null} $default */ $default = ['cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null]; $this->expectations = array_map(static function ($expect) use ($default): array { - if (is_string($expect)) { + if (is_string($expect) || array_is_list($expect)) { $command = $expect; $expect = $default; $expect['cmd'] = $command; diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php index 833ea953588e..9dc29a299a52 100644 --- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -36,11 +36,11 @@ public function testHgGuessVersionReturnsData(): void $process = $this->getProcessExecutorMock(); $process->expects([ ['cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'return' => 128], - ['cmd' => 'git describe --exact-match --tags', 'return' => 128], - ['cmd' => 'git log --pretty="%H" -n1 HEAD'.GitUtil::getNoShowSignatureFlag($process), 'return' => 128], - ['cmd' => 'hg branch', 'return' => 0, 'stdout' => $branch], - ['cmd' => 'hg branches', 'return' => 0], - ['cmd' => 'hg bookmarks', 'return' => 0], + ['cmd' => ['git', 'describe', '--exact-match', '--tags'], 'return' => 128], + ['cmd' => array_merge(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($process)), 'return' => 128], + ['cmd' => ['hg', 'branch'], 'return' => 0, 'stdout' => $branch], + ['cmd' => ['hg', 'branches'], 'return' => 0], + ['cmd' => ['hg', 'bookmarks'], 'return' => 0], ], true); GitUtil::getVersion(new ProcessExecutor); @@ -201,7 +201,7 @@ public function testDetachedHeadBecomesDevHash(): void 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'stdout' => "* (no branch) $commitHash Commit message\n", ], - 'git describe --exact-match --tags', + ['git', 'describe', '--exact-match', '--tags'], ], true); $config = new Config; @@ -223,7 +223,7 @@ public function testDetachedFetchHeadBecomesDevHashGit2(): void 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'stdout' => "* (HEAD detached at FETCH_HEAD) $commitHash Commit message\n", ], - 'git describe --exact-match --tags', + ['git', 'describe', '--exact-match', '--tags'], ], true); $config = new Config; @@ -245,7 +245,7 @@ public function testDetachedCommitHeadBecomesDevHashGit2(): void 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'stdout' => "* (HEAD detached at 03a15d220) $commitHash Commit message\n", ], - 'git describe --exact-match --tags', + ['git', 'describe', '--exact-match', '--tags'], ], true); $config = new Config; @@ -266,7 +266,7 @@ public function testTagBecomesVersion(): void 'stdout' => "* (HEAD detached at v2.0.5-alpha2) 433b98d4218c181bae01865901aac045585e8a1a Commit message\n", ], [ - 'cmd' => 'git describe --exact-match --tags', + 'cmd' => ['git', 'describe', '--exact-match', '--tags'], 'stdout' => "v2.0.5-alpha2", ], ], true); @@ -289,7 +289,7 @@ public function testTagBecomesPrettyVersion(): void 'stdout' => "* (HEAD detached at 1.0.0) c006f0c12bbbf197b5c071ffb1c0e9812bb14a4d Commit message\n", ], [ - 'cmd' => 'git describe --exact-match --tags', + 'cmd' => ['git', 'describe', '--exact-match', '--tags'], 'stdout' => '1.0.0', ], ], true); diff --git a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php index daa8b1aea562..decf6cf87953 100644 --- a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php @@ -72,7 +72,7 @@ public function testGetRootIdentifierFromRemoteLocalRepository(): void $process ->expects([[ - 'cmd' => 'git branch --no-color', + 'cmd' => ['git', 'branch', '--no-color'], 'stdout' => $stdout, ]], true); @@ -102,11 +102,17 @@ public function testGetRootIdentifierFromRemote(): void $process ->expects([[ - 'cmd' => 'git remote -v', + 'cmd' => ['git', 'remote', '-v'], 'stdout' => '', ], [ - 'cmd' => Platform::isWindows() ? "git remote set-url origin -- https://example.org/acme.git && git remote show origin && git remote set-url origin -- https://example.org/acme.git" : "git remote set-url origin -- 'https://example.org/acme.git' && git remote show origin && git remote set-url origin -- 'https://example.org/acme.git'", + 'cmd' => ['git', 'remote', 'set-url', 'origin', '--', 'https://example.org/acme.git'], + 'stdout' => '', + ], [ + 'cmd' => ['git', 'remote', 'show', 'origin'], 'stdout' => $stdout, + ], [ + 'cmd' => ['git', 'remote', 'set-url', 'origin', '--', 'https://example.org/acme.git'], + 'stdout' => '', ]]); self::assertSame('main', $driver->getRootIdentifier()); @@ -130,7 +136,7 @@ public function testGetRootIdentifierFromLocalWithNetworkDisabled(): void $process ->expects([[ - 'cmd' => 'git branch --no-color', + 'cmd' => ['git', 'branch', '--no-color'], 'stdout' => $stdout, ]]); @@ -155,7 +161,7 @@ public function testGetBranchesFilterInvalidBranchNames(): void $process ->expects([[ - 'cmd' => 'git branch --no-color --no-abbrev -v', + 'cmd' => ['git', 'branch', '--no-color', '--no-abbrev', '-v'], 'stdout' => $stdout, ]]); diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index f7e62ca643b3..9902af644a33 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -303,18 +303,18 @@ public function testPrivateRepositoryNoInteraction(): void $process = $this->getProcessExecutorMock(); $process->expects([ - ['cmd' => 'git config github.accesstoken', 'return' => 1], - 'git clone --mirror -- '.ProcessExecutor::escape($repoSshUrl).' '.ProcessExecutor::escape($this->config->get('cache-vcs-dir').'/git-github.com-composer-packagist.git/'), + ['cmd' => ['git', 'config', 'github.accesstoken'], 'return' => 1], + ['git', 'clone', '--mirror', '--', $repoSshUrl, $this->config->get('cache-vcs-dir').'/git-github.com-composer-packagist.git/'], [ - 'cmd' => 'git show-ref --tags --dereference', + 'cmd' => ['git', 'show-ref', '--tags', '--dereference'], 'stdout' => $sha.' refs/tags/'.$identifier, ], [ - 'cmd' => 'git branch --no-color --no-abbrev -v', + 'cmd' => ['git', 'branch', '--no-color', '--no-abbrev', '-v'], 'stdout' => ' test_master edf93f1fccaebd8764383dc12016d0a1a9672d89 Fix test & behavior', ], [ - 'cmd' => 'git branch --no-color', + 'cmd' => ['git', 'branch', '--no-color'], 'stdout' => '* test_master', ], ], true); diff --git a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php index 42d9f5b4acc8..b4e912af17bb 100644 --- a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php @@ -85,10 +85,10 @@ public function testGetBranchesFilterInvalidBranchNames(): void $process ->expects([[ - 'cmd' => 'hg branches', + 'cmd' => ['hg', 'branches'], 'stdout' => $stdout, ], [ - 'cmd' => 'hg bookmarks', + 'cmd' => ['hg', 'bookmarks'], 'stdout' => $stdout1, ]]); diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index d8a76f702315..7e0b72ba95ba 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -60,12 +60,7 @@ public function testWrongCredentialsInUrl(): void $output .= " rejected Basic challenge (https://corp.svn.local/)"; $process = $this->getProcessExecutorMock(); - $authedCommand = sprintf( - 'svn ls --verbose --non-interactive --username %s --password %s -- %s', - ProcessExecutor::escape('till'), - ProcessExecutor::escape('secret'), - ProcessExecutor::escape('https://till:secret@corp.svn.local/repo/trunk') - ); + $authedCommand = ['svn', 'ls', '--verbose', '--non-interactive', '--username', 'till', '--password', 'secret', '--', 'https://till:secret@corp.svn.local/repo/trunk']; $process->expects([ ['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output], ['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output], @@ -73,7 +68,7 @@ public function testWrongCredentialsInUrl(): void ['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output], ['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output], ['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output], - ['cmd' => 'svn --version', 'return' => 0, 'stdout' => '1.2.3'], + ['cmd' => ['svn', '--version'], 'return' => 0, 'stdout' => '1.2.3'], ], true); $repoConfig = [ diff --git a/tests/Composer/Test/Util/GitTest.php b/tests/Composer/Test/Util/GitTest.php index 04e804d25438..98936459d8f6 100644 --- a/tests/Composer/Test/Util/GitTest.php +++ b/tests/Composer/Test/Util/GitTest.php @@ -57,6 +57,7 @@ public function testRunCommandPublicGitHubRepositoryNotInitialClone(string $prot $this->process->expects(['git command'], true); + // @phpstan-ignore method.deprecated $this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true); } @@ -82,9 +83,10 @@ public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteracti $this->process->expects([ ['cmd' => 'git command', 'return' => 1], - ['cmd' => 'git --version', 'return' => 0], + ['cmd' => ['git', '--version'], 'return' => 0], ], true); + // @phpstan-ignore method.deprecated $this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true); } @@ -124,6 +126,7 @@ public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteracti ->with($this->equalTo('github.com')) ->willReturn(['username' => 'token', 'password' => $gitHubToken]); + // @phpstan-ignore method.deprecated $this->git->runCommand($commandCallable, $gitUrl, null, true); } @@ -152,7 +155,7 @@ public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneNotIntera // When we are testing what happens without auth saved, and URLs // with https, there will also be an attempt to find the token in // the git config for the folder and repo, locally. - $additional_calls = array_fill(0, $bitbucket_git_auth_calls, ['cmd' => 'git config bitbucket.accesstoken', 'return' => 1]); + $additional_calls = array_fill(0, $bitbucket_git_auth_calls, ['cmd' => ['git', 'config', 'bitbucket.accesstoken'], 'return' => 1]); foreach ($additional_calls as $call) { $expectedCalls[] = $call; } @@ -177,6 +180,7 @@ public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneNotIntera ->with($this->equalTo('bitbucket.org')) ->willReturn(['username' => 'token', 'password' => $bitbucketToken]); } + // @phpstan-ignore method.deprecated $this->git->runCommand($commandCallable, $gitUrl, null, true); } @@ -202,7 +206,7 @@ public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneInteracti if (count($initial_config) > 0) { $expectedCalls[] = ['cmd' => 'git command failing', 'return' => 1]; } else { - $expectedCalls[] = ['cmd' => 'git config bitbucket.accesstoken', 'return' => 1]; + $expectedCalls[] = ['cmd' => ['git', 'config', 'bitbucket.accesstoken'], 'return' => 1]; } $expectedCalls[] = ['cmd' => 'git command ok', 'return' => 0]; $this->process->expects($expectedCalls, true); @@ -259,6 +263,7 @@ public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneInteracti ['url' => 'https://bitbucket.org/site/oauth2/access_token', 'status' => 200, 'body' => '{"expires_in": 600, "access_token": "my-access-token"}'] ]); $this->git->setHttpDownloader($downloader_mock); + // @phpstan-ignore method.deprecated $this->git->runCommand($commandCallable, $gitUrl, null, true); } diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index 1994675e0a3d..99a908c57775 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -561,7 +561,9 @@ public function testSyncCodeBaseWithStream(): void public function testCheckServerExists(): void { $this->processExecutor->expects( - ['p4 -p '.ProcessExecutor::escape('perforce.does.exist:port').' info -s'], + [ + ['p4', '-p', 'perforce.does.exist:port', 'info', '-s'] + ], true ); @@ -578,7 +580,7 @@ public function testCheckServerClientError(): void { $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $expectedCommand = 'p4 -p '.ProcessExecutor::escape('perforce.does.exist:port').' info -s'; + $expectedCommand = ['p4', '-p', 'perforce.does.exist:port', 'info', '-s']; $processExecutor->expects($this->once()) ->method('execute') ->with($this->equalTo($expectedCommand), $this->equalTo(null)) diff --git a/tests/Composer/Test/Util/SvnTest.php b/tests/Composer/Test/Util/SvnTest.php index 044bf2eed977..b17f722d3e92 100644 --- a/tests/Composer/Test/Util/SvnTest.php +++ b/tests/Composer/Test/Util/SvnTest.php @@ -23,14 +23,14 @@ class SvnTest extends TestCase * Test the credential string. * * @param string $url The SVN url. - * @param string $expect The expectation for the test. + * @param non-empty-list $expect The expectation for the test. * * @dataProvider urlProvider */ - public function testCredentials(string $url, string $expect): void + public function testCredentials(string $url, array $expect): void { $svn = new Svn($url, new NullIO, new Config()); - $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); + $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs'); $reflMethod->setAccessible(true); self::assertEquals($expect, $reflMethod->invoke($svn)); @@ -39,9 +39,9 @@ public function testCredentials(string $url, string $expect): void public static function urlProvider(): array { return [ - ['http://till:test@svn.example.org/', self::getCmd(" --username 'till' --password 'test' ")], - ['http://svn.apache.org/', ''], - ['svn://johndoe@example.org', self::getCmd(" --username 'johndoe' --password '' ")], + ['http://till:test@svn.example.org/', ['--username', 'till', '--password', 'test']], + ['http://svn.apache.org/', []], + ['svn://johndoe@example.org', ['--username', 'johndoe', '--password', '']], ]; } @@ -54,8 +54,8 @@ public function testInteractiveString(): void $reflMethod->setAccessible(true); self::assertEquals( - self::getCmd("svn ls --non-interactive -- 'http://svn.example.org'"), - $reflMethod->invokeArgs($svn, ['svn ls', $url]) + ['svn', 'ls', '--non-interactive', '--', 'http://svn.example.org'], + $reflMethod->invokeArgs($svn, [['svn', 'ls'], $url]) ); } @@ -73,10 +73,10 @@ public function testCredentialsFromConfig(): void ]); $svn = new Svn($url, new NullIO, $config); - $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); + $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs'); $reflMethod->setAccessible(true); - self::assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); + self::assertEquals(['--username', 'foo', '--password', 'bar'], $reflMethod->invoke($svn)); } public function testCredentialsFromConfigWithCacheCredentialsTrue(): void @@ -96,10 +96,10 @@ public function testCredentialsFromConfigWithCacheCredentialsTrue(): void $svn = new Svn($url, new NullIO, $config); $svn->setCacheCredentials(true); - $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); + $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs'); $reflMethod->setAccessible(true); - self::assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); + self::assertEquals(['--username', 'foo', '--password', 'bar'], $reflMethod->invoke($svn)); } public function testCredentialsFromConfigWithCacheCredentialsFalse(): void @@ -119,9 +119,9 @@ public function testCredentialsFromConfigWithCacheCredentialsFalse(): void $svn = new Svn($url, new NullIO, $config); $svn->setCacheCredentials(false); - $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString'); + $reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs'); $reflMethod->setAccessible(true); - self::assertEquals(self::getCmd(" --no-auth-cache --username 'foo' --password 'bar' "), $reflMethod->invoke($svn)); + self::assertEquals(['--no-auth-cache', '--username', 'foo', '--password', 'bar'], $reflMethod->invoke($svn)); } } From 1e7857d682d3d88f9e108623b89f529eb4bf4ac7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 14 Nov 2024 10:50:54 +0100 Subject: [PATCH 237/257] Update docs with hint for avast disabling --- composer.lock | 52 ++++++++++++++++----------------- doc/articles/troubleshooting.md | 5 ++-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/composer.lock b/composer.lock index 10d57e9f2ee4..75f8d1e4ab12 100644 --- a/composer.lock +++ b/composer.lock @@ -226,16 +226,16 @@ }, { "name": "composer/pcre", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "26859a860a7f140fc08422c3cc14ad9c2a287d79" + "reference": "ebb81df8f52b40172d14062ae96a06939d80a069" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/26859a860a7f140fc08422c3cc14ad9c2a287d79", - "reference": "26859a860a7f140fc08422c3cc14ad9c2a287d79", + "url": "https://api.github.com/repos/composer/pcre/zipball/ebb81df8f52b40172d14062ae96a06939d80a069", + "reference": "ebb81df8f52b40172d14062ae96a06939d80a069", "shasum": "" }, "require": { @@ -245,8 +245,8 @@ "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.10", - "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", "phpunit/phpunit": "^8 || ^9" }, "type": "library", @@ -285,7 +285,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/2.3.1" + "source": "https://github.com/composer/pcre/tree/2.3.2" }, "funding": [ { @@ -301,7 +301,7 @@ "type": "tidelift" } ], - "time": "2024-08-27T12:02:26+00:00" + "time": "2024-11-12T16:24:47+00:00" }, { "name": "composer/semver", @@ -2020,16 +2020,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.12.7", + "version": "1.12.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0" + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", - "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", "shasum": "" }, "require": { @@ -2074,7 +2074,7 @@ "type": "github" } ], - "time": "2024-10-18T11:12:07+00:00" + "time": "2024-11-11T15:37:09+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2125,21 +2125,21 @@ }, { "name": "phpstan/phpstan-phpunit", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + "reference": "11d4235fbc6313ecbf93708606edfd3222e44949" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/11d4235fbc6313ecbf93708606edfd3222e44949", + "reference": "11d4235fbc6313ecbf93708606edfd3222e44949", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -2171,9 +2171,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.1" }, - "time": "2024-04-20T06:39:00+00:00" + "time": "2024-11-12T12:43:59+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -2226,16 +2226,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.11", + "version": "1.4.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c" + "reference": "c7b7e7f520893621558bfbfdb2694d4364565c1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/270c2ee1478d1f8dc5121f539e890017bd64b04c", - "reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c7b7e7f520893621558bfbfdb2694d4364565c1d", + "reference": "c7b7e7f520893621558bfbfdb2694d4364565c1d", "shasum": "" }, "require": { @@ -2292,9 +2292,9 @@ "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.4.11" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.12" }, - "time": "2024-10-30T12:07:21+00:00" + "time": "2024-11-06T10:13:18+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 92ec6bbc7f3c..fbb6356d6e44 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -255,8 +255,9 @@ please report this [issue](https://github.com/composer/composer/issues). [new cacert.pem from cURL](https://curl.se/docs/caextract.html) and store it there. 2. If this did not help despite Composer finding a valid CA bundle, try disabling your antivirus and firewall software to see if that helps. We have seen issues where Avast on Windows for example would - prevent Composer from functioning correctly. If this helps you should report it to the - software vendor so they can hopefully improve things. + prevent Composer from functioning correctly. To disable the HTTPS scanning in Avast you can go in + "Protection > Core Shields > Web Shield > **uncheck** Enable HTTPS scanning". If this helps you + should report it to the software vendor so they can hopefully improve things. ## API rate limit and OAuth tokens From a39f57bcd783d88a16e064eb8a2db09e4c8d9156 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 14 Nov 2024 11:05:18 +0100 Subject: [PATCH 238/257] Update deps --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 75f8d1e4ab12..5470d1af1a43 100644 --- a/composer.lock +++ b/composer.lock @@ -941,16 +941,16 @@ }, { "name": "symfony/console", - "version": "v5.4.46", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fb0d4760e7147d81ab4d9e2d57d56268261b4e4e" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fb0d4760e7147d81ab4d9e2d57d56268261b4e4e", - "reference": "fb0d4760e7147d81ab4d9e2d57d56268261b4e4e", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { @@ -1020,7 +1020,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.46" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T14:17:06+00:00" + "time": "2024-11-06T11:30:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1787,16 +1787,16 @@ }, { "name": "symfony/process", - "version": "v5.4.46", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4" + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/01906871cb9b5e3cf872863b91aba4ec9767daf4", - "reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", "shasum": "" }, "require": { @@ -1829,7 +1829,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.46" + "source": "https://github.com/symfony/process/tree/v5.4.47" }, "funding": [ { @@ -1845,7 +1845,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:18:28+00:00" + "time": "2024-11-06T11:36:42+00:00" }, { "name": "symfony/service-contracts", @@ -1932,16 +1932,16 @@ }, { "name": "symfony/string", - "version": "v5.4.45", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7f6807add88b1e2635f3c6de5e1ace631ed7cad2" + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7f6807add88b1e2635f3c6de5e1ace631ed7cad2", - "reference": "7f6807add88b1e2635f3c6de5e1ace631ed7cad2", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", "shasum": "" }, "require": { @@ -1998,7 +1998,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.45" + "source": "https://github.com/symfony/string/tree/v5.4.47" }, "funding": [ { @@ -2014,7 +2014,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2024-11-10T20:33:58+00:00" } ], "packages-dev": [ From f1163bdbd4684953cc7f08fe7467ea2a35cb5950 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 14 Nov 2024 11:05:32 +0100 Subject: [PATCH 239/257] Avoid updating the lock hash if there is no lock --- src/Composer/Command/BumpCommand.php | 2 +- src/Composer/Command/RequireCommand.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/BumpCommand.php b/src/Composer/Command/BumpCommand.php index dc3df1e123cd..a87a65e1c8e7 100644 --- a/src/Composer/Command/BumpCommand.php +++ b/src/Composer/Command/BumpCommand.php @@ -216,7 +216,7 @@ public function doBump( $io->write('No requirements to update in '.$composerJsonPath.'.'); } - if (!$dryRun && $composer->getLocker()->isLocked() && $changeCount > 0) { + if (!$dryRun && $composer->getLocker()->isLocked() && $composer->getConfig()->get('lock') && $changeCount > 0) { $composer->getLocker()->updateHash($composerJson); } diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 92d0a77b5588..59d1e0585a6c 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -554,7 +554,7 @@ private function updateRequirementsAfterResolution(array $requirementsToUpdate, if (!$dryRun) { $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); - if ($locker->isLocked()) { + if ($locker->isLocked() && $composer->getConfig()->get('lock')) { $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); $locker->updateHash($this->json, function (array $lockData) use ($stabilityFlags) { foreach ($stabilityFlags as $packageName => $flag) { From a7a14ea860ccf5bac9524367c37fdcb15a4ec317 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 14 Nov 2024 11:26:58 +0100 Subject: [PATCH 240/257] Show root package version in error output for circular dependencies for added clarity --- src/Composer/DependencyResolver/Problem.php | 2 +- .../Test/Fixtures/installer/circular-dependency-errors.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index c58d8601b116..fa84ae0c015e 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -598,7 +598,7 @@ private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose if ($topPackage instanceof RootPackageInterface) { return [ "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ', - 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', + 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' '.$topPackage->getPrettyVersion().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', ]; } } diff --git a/tests/Composer/Test/Fixtures/installer/circular-dependency-errors.test b/tests/Composer/Test/Fixtures/installer/circular-dependency-errors.test index 1bd4518f7a71..0a9f42544ce2 100644 --- a/tests/Composer/Test/Fixtures/installer/circular-dependency-errors.test +++ b/tests/Composer/Test/Fixtures/installer/circular-dependency-errors.test @@ -49,7 +49,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires requires/root 1.0.0 -> satisfiable by requires/root[1.0.0]. - - requires/root 1.0.0 requires root/pkg ^1.0 -> satisfiable by root/pkg[1.0.0] from package repo (defining 3 packages) but root/pkg is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance. + - requires/root 1.0.0 requires root/pkg ^1.0 -> satisfiable by root/pkg[1.0.0] from package repo (defining 3 packages) but root/pkg dev-master is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance. Problem 2 - Root composer.json requires requires/root2 1.0.0 -> satisfiable by requires/root2[1.0.0]. - requires/root2 1.0.0 requires root/pkg ^2.0 -> found root/pkg[dev-master] but it does not match the constraint. See https://getcomposer.org/dep-on-root for details and assistance. From 8f24b67c3c811fc8968004b0d3e0fa43794c7c96 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 14 Nov 2024 11:47:19 +0100 Subject: [PATCH 241/257] Try to fix lowest deps tests --- .github/workflows/continuous-integration.yml | 4 +++- tests/Composer/Test/Command/DiagnoseCommandTest.php | 11 +++++++++-- tests/Composer/Test/Downloader/FileDownloaderTest.php | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 0fddd07d6931..e6cbb0695ccb 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -80,7 +80,9 @@ jobs: - name: "Handle lowest dependencies update" if: "contains(matrix.dependencies, 'lowest')" - run: "echo \"COMPOSER_UPDATE_FLAGS=$COMPOSER_UPDATE_FLAGS --prefer-lowest\" >> $GITHUB_ENV" + run: | + echo "COMPOSER_UPDATE_FLAGS=$COMPOSER_UPDATE_FLAGS --prefer-lowest" >> $GITHUB_ENV + echo "COMPOSER_LOWEST_DEPS_TEST=1" >> $GITHUB_ENV - name: "Handle ignore-platform-reqs dependencies update" if: "contains(matrix.dependencies, 'ignore')" diff --git a/tests/Composer/Test/Command/DiagnoseCommandTest.php b/tests/Composer/Test/Command/DiagnoseCommandTest.php index 28ef18f50ce0..ef241c9c9dd0 100644 --- a/tests/Composer/Test/Command/DiagnoseCommandTest.php +++ b/tests/Composer/Test/Command/DiagnoseCommandTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Command; use Composer\Test\TestCase; +use Composer\Util\Platform; class DiagnoseCommandTest extends TestCase { @@ -23,7 +24,11 @@ public function testCmdFail(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'diagnose']); - self::assertSame(1, $appTester->getStatusCode()); + if (Platform::getEnv('COMPOSER_LOWEST_DEPS_TEST') === '1') { + self::assertGreaterThanOrEqual(1, $appTester->getStatusCode()); + } else { + self::assertSame(1, $appTester->getStatusCode()); + } $output = $appTester->getDisplay(true); self::assertStringContainsString('Checking composer.json: WARNING @@ -41,7 +46,9 @@ public function testCmdSuccess(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'diagnose']); - $appTester->assertCommandIsSuccessful(); + if (Platform::getEnv('COMPOSER_LOWEST_DEPS_TEST') !== '1') { + $appTester->assertCommandIsSuccessful(); + } $output = $appTester->getDisplay(true); self::assertStringContainsString('Checking composer.json: OK', $output); diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index cbb44b08535c..a03dd9fc4105 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -388,6 +388,9 @@ public function testDowngradeShowsAppropriateMessage(): void $filesystem->expects($this->once()) ->method('removeDirectoryAsync') ->will($this->returnValue(\React\Promise\resolve(true))); + $filesystem->expects($this->any()) + ->method('normalizePath') + ->will(self::returnArgument(0)); $downloader = $this->getDownloader($ioMock, $config, null, null, null, $filesystem); From 23d1030c7338aaef41bf62db9f8a3c887b3c2229 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 14 Nov 2024 11:54:11 +0100 Subject: [PATCH 242/257] phpstan type fixes --- src/Composer/Autoload/AutoloadGenerator.php | 4 ++-- src/Composer/Command/BaseDependencyCommand.php | 12 ++++++------ src/Composer/Repository/InstalledRepository.php | 9 +++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 44431128f99d..6db30b8fdf4a 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -665,11 +665,11 @@ protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem foreach ($package->getIncludePaths() as $includePath) { $includePath = trim($includePath, '/'); - $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath; + $includePaths[] = $installPath === '' ? $includePath : $installPath.'/'.$includePath; } } - if (!$includePaths) { + if (\count($includePaths) === 0) { return null; } diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index bb2a64233c7b..55b502e03cc8 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -197,9 +197,9 @@ protected function doExecute(InputInterface $input, OutputInterface $output, boo /** * Assembles and prints a bottom-up table of the dependencies. * - * @param array{PackageInterface, Link, mixed}[] $results + * @param array{PackageInterface, Link, array|false}[] $results */ - protected function printTable(OutputInterface $output, $results): void + protected function printTable(OutputInterface $output, array $results): void { $table = []; $doubles = []; @@ -221,13 +221,13 @@ protected function printTable(OutputInterface $output, $results): void $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); $rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())]; - if ($children) { + if (is_array($children)) { $queue = array_merge($queue, $children); } } $results = $queue; $table = array_merge($rows, $table); - } while (!empty($results)); + } while (\count($results) > 0); $this->renderTable($table, $output); } @@ -254,7 +254,7 @@ protected function initStyles(OutputInterface $output): void /** * Recursively prints a tree of the selected results. * - * @param array{PackageInterface, Link, mixed[]|bool}[] $results Results to be printed at this level. + * @param array{PackageInterface, Link, array|false}[] $results Results to be printed at this level. * @param string $prefix Prefix of the current tree level. * @param int $level Current level of recursion. */ @@ -275,7 +275,7 @@ protected function printTree(array $results, string $prefix = '', int $level = 1 $linkText = sprintf('%s <%s>%s %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint()); $circularWarn = $children === false ? '(circular dependency aborted here)' : ''; $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn))); - if ($children) { + if (is_array($children)) { $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1); } } diff --git a/src/Composer/Repository/InstalledRepository.php b/src/Composer/Repository/InstalledRepository.php index e3b52079e351..3520fdec67fb 100644 --- a/src/Composer/Repository/InstalledRepository.php +++ b/src/Composer/Repository/InstalledRepository.php @@ -84,7 +84,7 @@ public function findPackagesWithReplacersAndProviders(string $name, $constraint * @param string[] $packagesFound Used internally when recurring * * @return array[] An associative array of arrays as described above. - * @phpstan-return array + * @phpstan-return array|false}> */ public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = false, bool $recurse = true, ?array $packagesFound = null): array { @@ -137,6 +137,7 @@ public function getDependents($needle, ?ConstraintInterface $constraint = null, } } } + unset($needle); } // Require-dev is only relevant for the root package @@ -163,7 +164,7 @@ public function getDependents($needle, ?ConstraintInterface $constraint = null, } // When inverting, we need to check for conflicts of the needles against installed packages - if ($invert && in_array($package->getName(), $needles)) { + if ($invert && in_array($package->getName(), $needles, true)) { foreach ($package->getConflicts() as $link) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); @@ -176,7 +177,7 @@ public function getDependents($needle, ?ConstraintInterface $constraint = null, // List conflicts against X as they may explain why the current version was selected, or explain why it is rejected if the conflict matched when inverting foreach ($package->getConflicts() as $link) { - if (in_array($link->getTarget(), $needles)) { + if (in_array($link->getTarget(), $needles, true)) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); if ($link->getConstraint()->matches($version) === $invert) { @@ -187,7 +188,7 @@ public function getDependents($needle, ?ConstraintInterface $constraint = null, } // When inverting, we need to check for conflicts of the needles' requirements against installed packages - if ($invert && $constraint && in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) { + if ($invert && $constraint && in_array($package->getName(), $needles, true) && $constraint->matches(new Constraint('=', $package->getVersion()))) { foreach ($package->getRequires() as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { if ($this->findPackage($link->getTarget(), $link->getConstraint())) { From 2e83ead40c396a8b0be85b6359442f51a902b6ea Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 15 Nov 2024 13:53:30 +0100 Subject: [PATCH 243/257] Allow react/promise 2.x again, fixes #12188 --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a943cd7f9ae9..743ca0bba96d 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3", - "react/promise": "^3.2", + "react/promise": "^2.11 || ^3.2", "composer/pcre": "^2.2 || ^3.2", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", diff --git a/composer.lock b/composer.lock index 5470d1af1a43..b49b1e1c2332 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "7c2f8d4d04b8146b2fcd203cb81fa8d5", + "content-hash": "d6c1c91b79d7140594e249343184ce6f", "packages": [ { "name": "composer/ca-bundle", From 580f0006d6506008b8d0b55b1bae326b994a43b5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 15 Nov 2024 14:08:32 +0100 Subject: [PATCH 244/257] Ensure we run git commands for bin/compile inside the root of the git repo, refs #12194 --- src/Composer/Compiler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index e85329c6d19f..591132c7f104 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -51,19 +51,19 @@ public function compile(string $pharFile = 'composer.phar'): void $process = new ProcessExecutor(); - if (0 !== $process->execute(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], $output, __DIR__)) { + if (0 !== $process->execute(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], $output, dirname(dirname(__DIR__)))) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->version = trim($output); - if (0 !== $process->execute(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], $output, __DIR__)) { + if (0 !== $process->execute(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], $output, dirname(dirname(__DIR__)))) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->versionDate = new \DateTime(trim($output)); $this->versionDate->setTimezone(new \DateTimeZone('UTC')); - if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, __DIR__)) { + if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, dirname(dirname(__DIR__)))) { $this->version = trim($output); } else { // get branch-alias defined in composer.json for dev-main (if any) From 8f87ab3ea0f55ef21fe6a6346a06a0485b0e9c60 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 17 Nov 2024 13:12:53 +0100 Subject: [PATCH 245/257] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ada64852b395..ebd97e8ce8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### [2.8.3] 2024-11-17 + + * Fixed windows handling of process discovery (#12180) + * Fixed react/promise requirement to allow 2.x installs again (#12188) + * Fixed some issues when lock:false is set in require and bump commands + ### [2.8.2] 2024-10-29 * Fixed crash while suggesting providers if they have no description (#12152) @@ -1954,6 +1960,7 @@ * Initial release +[2.8.3]: https://github.com/composer/composer/compare/2.8.2...2.8.3 [2.8.2]: https://github.com/composer/composer/compare/2.8.1...2.8.2 [2.8.1]: https://github.com/composer/composer/compare/2.8.0...2.8.1 [2.8.0]: https://github.com/composer/composer/compare/2.7.9...2.8.0 From 2a7c71266b2545a3bed9f4860734081963f6e688 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 17 Nov 2024 13:13:04 +0100 Subject: [PATCH 246/257] Release 2.8.3 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 07d6973334d4..234174462e07 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.8.999-dev+source'; + public const VERSION = '2.8.3'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-11-17 13:13:04'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package From 9fb833f97e59b6af886fd5d5c3f0a81012a61bf5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 17 Nov 2024 13:13:04 +0100 Subject: [PATCH 247/257] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 234174462e07..07d6973334d4 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.8.3'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2024-11-17 13:13:04'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.8.999-dev+source'; /** * Version number of the internal composer-runtime-api package From aee3bd14dbce189b912bdbf1034aee70895bfdab Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 21 Nov 2024 07:52:30 +0000 Subject: [PATCH 248/257] Add build-path to php-ext config options for PIE (#12206) * Add build-path to php-ext config options * Use phpstan- prefix for shape definitions --- res/composer-schema.json | 6 ++++++ src/Composer/Package/Package.php | 12 +++++++++--- src/Composer/Package/PackageInterface.php | 5 ++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/res/composer-schema.json b/res/composer-schema.json index 9f76bc4b7d54..9df390e06e2b 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -317,6 +317,12 @@ "example": false, "default": true }, + "build-path": { + "type": ["string", "null"], + "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.", + "example": "my-extension-source", + "default": null + }, "configure-options": { "type": "array", "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index ad17538d662b..faa2a07a06ee 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -23,6 +23,7 @@ * * @phpstan-import-type AutoloadRules from PackageInterface * @phpstan-import-type DevAutoloadRules from PackageInterface + * @phpstan-import-type PhpExtConfig from PackageInterface */ class Package extends BasePackage { @@ -98,7 +99,10 @@ class Package extends BasePackage protected $isDefaultBranch = false; /** @var mixed[] */ protected $transportOptions = []; - /** @var array{priority?: int, configure-options?: list}|null */ + /** + * @var array|null + * @phpstan-var PhpExtConfig|null + */ protected $phpExt = null; /** @@ -593,9 +597,11 @@ public function getIncludePaths(): array } /** - * Sets the list of paths added to PHP's include path. + * Sets the settings for php extension packages + * + * @param array|null $phpExt * - * @param array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, configure-options?: list}|null $phpExt List of directories. + * @phpstan-param PhpExtConfig|null $phpExt */ public function setPhpExt(?array $phpExt): void { diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 6e723d5bbcb2..dec040334962 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -23,6 +23,7 @@ * * @phpstan-type AutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list, exclude-from-classmap?: list} * @phpstan-type DevAutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list} + * @phpstan-type PhpExtConfig array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, build-path?: string|null, configure-options?: list} */ interface PackageInterface { @@ -326,7 +327,9 @@ public function getIncludePaths(): array; /** * Returns the settings for php extension packages * - * @return array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, configure-options?: list}|null + * @return array|null + * + * @phpstan-return PhpExtConfig|null */ public function getPhpExt(): ?array; From 38cb4bfe71dc3a9093cb8b4ecebfd39cb1a5699c Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 21 Nov 2024 08:16:24 +0000 Subject: [PATCH 249/257] GitLab: adjust links to profile/user-settings (#12205) --- doc/articles/authentication-for-private-packages.md | 2 +- src/Composer/Util/GitLab.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/articles/authentication-for-private-packages.md b/doc/articles/authentication-for-private-packages.md index d2cc3fd5200b..fb86c5c23391 100644 --- a/doc/articles/authentication-for-private-packages.md +++ b/doc/articles/authentication-for-private-packages.md @@ -264,7 +264,7 @@ php composer.phar config [--global] --editor --auth > **Note:** For the gitlab authentication to work on private gitlab instances, the > [`gitlab-domains`](../06-config.md#gitlab-domains) section should also contain the URL. -To create a new access token, go to your [access tokens section on GitLab](https://gitlab.com/-/profile/personal_access_tokens) +To create a new access token, go to your [access tokens section on GitLab](https://gitlab.com/-/user_settings/personal_access_tokens) (or the equivalent URL on your private instance) and create a new token. See also [the GitLab access token documentation](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token) for more information. When creating a gitlab token manually, make sure it has either the `read_api` or `api` scope. diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 35d8e0dedd9f..2f108bc5a97e 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -126,8 +126,8 @@ public function authorizeOAuthInteractively(string $scheme, string $originUrl, ? } $localAuthConfig = $this->config->getLocalAuthConfigSource(); - $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/profile/personal_access_tokens'; - $revokeLink = $scheme.'://'.$originUrl.'/-/profile/applications'; + $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/user_settings/personal_access_tokens'; + $revokeLink = $scheme.'://'.$originUrl.'/-/user_settings/applications'; $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('To revoke access to this token you can visit:'); $this->io->writeError($revokeLink); @@ -312,7 +312,7 @@ private function refreshToken(string $scheme, string $originUrl): array $token = $this->httpDownloader->get($scheme.'://'.$originUrl.'/oauth/token', $options)->decodeJson(); $this->io->writeError('GitLab token successfully refreshed', true, IOInterface::VERY_VERBOSE); - $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/profile/applications', true, IOInterface::VERY_VERBOSE); + $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/user_settings/applications', true, IOInterface::VERY_VERBOSE); return $token; } From e468b73cb225dde1d28fa805502ffa752f1eb7b3 Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Mon, 25 Nov 2024 10:30:31 -0300 Subject: [PATCH 250/257] Use a bitmask to produce deterministic exit codes for the "audit" command (#12203) * Use a bitmask to produce deterministic exit codes for the "audit" command * Rename consts, small cleanups --------- Co-authored-by: Jordi Boggiano --- src/Composer/Advisory/Auditor.php | 46 ++++++++++++----- tests/Composer/Test/Advisory/AuditorTest.php | 52 +++++++++++++++++--- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index de8034bad007..485b3326787f 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -53,6 +53,11 @@ class Auditor self::ABANDONED_FAIL, ]; + /** Values to determine the audit result. */ + public const STATUS_OK = 0; + public const STATUS_VULNERABLE = 1; + public const STATUS_ABANDONED = 2; + /** * @param PackageInterface[] $packages * @param self::FORMAT_* $format The format that will be used to output audit results. @@ -61,7 +66,7 @@ class Auditor * @param self::ABANDONED_* $abandoned * @param array $ignoredSeverities List of ignored severity levels * - * @return int Amount of packages with vulnerabilities found + * @return int-mask A bitmask of STATUS_* constants or 0 on success * @throws InvalidArgumentException If no packages are passed in */ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL, array $ignoredSeverities = []): int @@ -75,7 +80,7 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities); $abandonedCount = 0; - $affectedPackagesCount = 0; + $affectedPackagesCount = count($advisories); if ($abandoned === self::ABANDONED_IGNORE) { $abandonedPackages = []; } else { @@ -85,6 +90,8 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, } } + $auditBitmask = $this->calculateBitmask(0 < $affectedPackagesCount, 0 < $abandonedCount); + if (self::FORMAT_JSON === $format) { $json = ['advisories' => $advisories]; if ($ignoredAdvisories !== []) { @@ -98,23 +105,22 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, $io->write(JsonFile::encode($json)); - return count($advisories) + $abandonedCount; + return $auditBitmask; } $errorOrWarn = $warningOnly ? 'warning' : 'error'; - if (count($advisories) > 0 || count($ignoredAdvisories) > 0) { + if ($affectedPackagesCount > 0 || count($ignoredAdvisories) > 0) { $passes = [ [$ignoredAdvisories, "Found %d ignored security vulnerability advisor%s affecting %d package%s%s"], - // this has to run last to allow $affectedPackagesCount in the return statement to be correct [$advisories, "<$errorOrWarn>Found %d security vulnerability advisor%s affecting %d package%s%s"], ]; foreach ($passes as [$advisoriesToOutput, $message]) { - [$affectedPackagesCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput); - if ($affectedPackagesCount > 0) { + [$pkgCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput); + if ($pkgCount > 0) { $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies'; - $pkgPlurality = $affectedPackagesCount === 1 ? '' : 's'; + $pkgPlurality = $pkgCount === 1 ? '' : 's'; $punctuation = $format === 'summary' ? '.' : ':'; - $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $affectedPackagesCount, $pkgPlurality, $punctuation)); + $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $pkgCount, $pkgPlurality, $punctuation)); $this->outputAdvisories($io, $advisoriesToOutput, $format); } } @@ -130,7 +136,7 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, $this->outputAbandonedPackages($io, $abandonedPackages, $format); } - return $affectedPackagesCount + $abandonedCount; + return $auditBitmask; } /** @@ -139,7 +145,7 @@ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, */ private function filterAbandonedPackages(array $packages): array { - return array_filter($packages, static function (PackageInterface $pkg) { + return array_filter($packages, static function (PackageInterface $pkg): bool { return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned(); }); } @@ -401,4 +407,22 @@ private function getURL(SecurityAdvisory $advisory): string return 'link).'>'.OutputFormatter::escape($advisory->link).''; } + + /** + * @return int-mask + */ + private function calculateBitmask(bool $hasVulnerablePackages, bool $hasAbandonedPackages): int + { + $bitmask = self::STATUS_OK; + + if ($hasVulnerablePackages) { + $bitmask |= self::STATUS_VULNERABLE; + } + + if ($hasAbandonedPackages) { + $bitmask |= self::STATUS_ABANDONED; + } + + return $bitmask; + } } diff --git a/tests/Composer/Test/Advisory/AuditorTest.php b/tests/Composer/Test/Advisory/AuditorTest.php index cc9efb985559..a5472f0d804f 100644 --- a/tests/Composer/Test/Advisory/AuditorTest.php +++ b/tests/Composer/Test/Advisory/AuditorTest.php @@ -37,7 +37,7 @@ public static function auditProvider() ], 'warningOnly' => true, ], - 'expected' => 0, + 'expected' => Auditor::STATUS_OK, 'output' => 'No security vulnerability advisories found.', ]; @@ -50,7 +50,7 @@ public static function auditProvider() ], 'warningOnly' => true, ], - 'expected' => 1, + 'expected' => Auditor::STATUS_VULNERABLE, 'output' => 'Found 2 security vulnerability advisories affecting 1 package: Package: vendor1/package1 Severity: high @@ -83,7 +83,7 @@ public static function auditProvider() 'warningOnly' => false, 'abandoned' => Auditor::ABANDONED_IGNORE, ], - 'expected' => 0, + 'expected' => Auditor::STATUS_OK, 'output' => 'No security vulnerability advisories found.', ]; @@ -96,7 +96,7 @@ public static function auditProvider() 'warningOnly' => true, 'abandoned' => Auditor::ABANDONED_REPORT, ], - 'expected' => 0, + 'expected' => Auditor::STATUS_OK, 'output' => 'No security vulnerability advisories found. Found 2 abandoned packages: vendor/abandoned is abandoned. Use foo/bar instead. @@ -113,7 +113,7 @@ public static function auditProvider() 'abandoned' => Auditor::ABANDONED_FAIL, 'format' => Auditor::FORMAT_TABLE, ], - 'expected' => 2, + 'expected' => Auditor::STATUS_ABANDONED, 'output' => 'No security vulnerability advisories found. Found 2 abandoned packages: +-------------------+----------------------------------------------------------------------------------+ @@ -124,6 +124,46 @@ public static function auditProvider() +-------------------+----------------------------------------------------------------------------------+', ]; + yield 'vulnerable and abandoned packages fails' => [ + 'data' => [ + 'packages' => [ + new Package('vendor1/package1', '8.2.1', '8.2.1'), + $abandonedWithReplacement, + $abandonedNoReplacement, + ], + 'warningOnly' => false, + 'abandoned' => Auditor::ABANDONED_FAIL, + 'format' => Auditor::FORMAT_TABLE, + ], + 'expected' => Auditor::STATUS_VULNERABLE | Auditor::STATUS_ABANDONED, + 'output' => 'Found 2 security vulnerability advisories affecting 1 package: ++-------------------+----------------------------------------------------------------------------------+ +| Package | vendor1/package1 | +| Severity | high | +| CVE | CVE3 | +| Title | advisory4 | +| URL | https://advisory.example.com/advisory4 | +| Affected versions | >=8,<8.2.2|>=1,<2.5.6 | +| Reported at | 2022-05-25T13:21:00+00:00 | ++-------------------+----------------------------------------------------------------------------------+ ++-------------------+----------------------------------------------------------------------------------+ +| Package | vendor1/package1 | +| Severity | medium | +| CVE | | +| Title | advisory5 | +| URL | https://advisory.example.com/advisory5 | +| Affected versions | >=8,<8.2.2|>=1,<2.5.6 | +| Reported at | 2022-05-25T13:21:00+00:00 | ++-------------------+----------------------------------------------------------------------------------+ +Found 2 abandoned packages: ++-------------------+----------------------------------------------------------------------------------+ +| Abandoned Package | Suggested Replacement | ++-------------------+----------------------------------------------------------------------------------+ +| vendor/abandoned | foo/bar | +| vendor/abandoned2 | none | ++-------------------+----------------------------------------------------------------------------------+', + ]; + yield 'abandoned packages fails with json format' => [ 'data' => [ 'packages' => [ @@ -134,7 +174,7 @@ public static function auditProvider() 'abandoned' => Auditor::ABANDONED_FAIL, 'format' => Auditor::FORMAT_JSON, ], - 'expected' => 2, + 'expected' => Auditor::STATUS_ABANDONED, 'output' => '{ "advisories": [], "abandoned": { From dc2844cc728a47762c28f9fe7ba24e05e064e0c3 Mon Sep 17 00:00:00 2001 From: Lctrs Date: Mon, 25 Nov 2024 15:03:36 +0100 Subject: [PATCH 251/257] disable multiplexing for some versions of curl (#12207) * disable multiplexing for some versions of curl I'm behind a corporate proxy and was hitting a `Curl 2 (...) [CONN-1-0] send: no filter connected` error when trying to download some packages. Some google research led me to https://github.com/rust-lang/cargo/issues/12202 and its fix https://github.com/rust-lang/cargo/pull/12234. This PR backports this fix to composer. > In certain versions of libcurl when proxy is in use with HTTP/2 multiplexing, connections will continue stacking up. This was fixed in libcurl 8.0.0 in curl/curl@821f6e2 * fix has proxy condition --- src/Composer/Util/Http/CurlDownloader.php | 21 ++++++++++++++++++++- src/Composer/Util/Http/ProxyManager.php | 5 +++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 577c81b47225..1cdae6df9b38 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -34,6 +34,14 @@ */ class CurlDownloader { + /** + * Known libcurl's broken versions when proxy is in use with HTTP/2 + * multiplexing. + * + * @var list + */ + private const BAD_MULTIPLEXING_CURL_VERSIONS = ['7.87.0', '7.88.0', '7.88.1']; + /** @var \CurlMultiHandle */ private $multiHandle; /** @var \CurlShareHandle */ @@ -99,7 +107,18 @@ public function __construct(IOInterface $io, Config $config, array $options = [] $this->multiHandle = $mh = curl_multi_init(); if (function_exists('curl_multi_setopt')) { - curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); + if (ProxyManager::getInstance()->hasProxy() && ($version = curl_version()) !== false && in_array($version['version'], self::BAD_MULTIPLEXING_CURL_VERSIONS, true)) { + /** + * Disable HTTP/2 multiplexing for some broken versions of libcurl. + * + * In certain versions of libcurl when proxy is in use with HTTP/2 + * multiplexing, connections will continue stacking up. This was + * fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc + */ + curl_multi_setopt($mh, CURLMOPT_PIPELINING, /* CURLPIPE_NOTHING */ 0); + } else { + curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); + } if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) { curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8); } diff --git a/src/Composer/Util/Http/ProxyManager.php b/src/Composer/Util/Http/ProxyManager.php index 247105fa5e5f..3747cedaa722 100644 --- a/src/Composer/Util/Http/ProxyManager.php +++ b/src/Composer/Util/Http/ProxyManager.php @@ -59,6 +59,11 @@ public static function reset(): void self::$instance = null; } + public function hasProxy(): bool + { + return $this->httpProxy !== null || $this->httpsProxy !== null; + } + /** * Returns a RequestProxy instance for the request url * From cc820306eb5d7503d26f41daa39dde2d431ec5f9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 25 Nov 2024 16:23:10 +0100 Subject: [PATCH 252/257] Ensure installed.php data is sorted deterministically, fixes #12197 --- src/Composer/Repository/FilesystemRepository.php | 8 ++++++++ tests/Composer/Test/Repository/Fixtures/installed.php | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index ec37573f2f97..abb9f125e704 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -329,6 +329,14 @@ private function generateInstalledVersions(InstallationManager $installationMana ksort($versions['versions']); ksort($versions); + foreach ($versions['versions'] as $name => $version) { + foreach (['aliases', 'replaced', 'provided'] as $key) { + if (isset($versions['versions'][$name][$key])) { + sort($versions['versions'][$name][$key], SORT_NATURAL); + } + } + } + return $versions; } diff --git a/tests/Composer/Test/Repository/Fixtures/installed.php b/tests/Composer/Test/Repository/Fixtures/installed.php index dbdda5e3c67b..3bfbc15ab494 100644 --- a/tests/Composer/Test/Repository/Fixtures/installed.php +++ b/tests/Composer/Test/Repository/Fixtures/installed.php @@ -65,10 +65,10 @@ 'foo/impl' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '^1.1', - 1 => '1.2', - 2 => '1.4', - 3 => '2.0', + 0 => '1.2', + 1 => '1.4', + 2 => '2.0', + 3 => '^1.1', ), ), 'foo/impl2' => array( From 59b63bc23156bbabe81692ccb89a4e94f49d9ef4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 26 Nov 2024 14:49:36 +0100 Subject: [PATCH 253/257] Validate license data more thoroughly --- .../Package/Loader/ValidatingArrayLoader.php | 61 +++++++++++++------ .../Loader/ValidatingArrayLoaderTest.php | 41 ++++++++++--- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index a6431d2df25e..e3600d6e1bef 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -132,33 +132,54 @@ public function load(array $config, string $class = 'Composer\Package\CompletePa } } - // check for license validity on newly updated branches - if (isset($this->config['license']) && (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days'))) { + if (isset($this->config['license'])) { + // validate main data types if (is_array($this->config['license']) || is_string($this->config['license'])) { $licenses = (array) $this->config['license']; - $licenseValidator = new SpdxLicenses(); - foreach ($licenses as $license) { - // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer - if ('proprietary' === $license) { - continue; + foreach ($licenses as $index => $license) { + if (!is_string($license)) { + $this->warnings[] = sprintf( + 'License %s should be a string.', + json_encode($license) + ); + unset($licenses[$index]); } - $licenseToValidate = str_replace('proprietary', 'MIT', $license); - if (!$licenseValidator->validate($licenseToValidate)) { - if ($licenseValidator->validate(trim($licenseToValidate))) { - $this->warnings[] = sprintf( - 'License %s must not contain extra spaces, make sure to trim it.', - json_encode($license) - ); - } else { - $this->warnings[] = sprintf( - 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . - 'If the software is closed-source, you may use "proprietary" as license.', - json_encode($license) - ); + } + + // check for license validity on newly updated branches/tags + if (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days')) { + $licenseValidator = new SpdxLicenses(); + foreach ($licenses as $license) { + // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer + if ('proprietary' === $license) { + continue; + } + $licenseToValidate = str_replace('proprietary', 'MIT', $license); + if (!$licenseValidator->validate($licenseToValidate)) { + if ($licenseValidator->validate(trim($licenseToValidate))) { + $this->warnings[] = sprintf( + 'License %s must not contain extra spaces, make sure to trim it.', + json_encode($license) + ); + } else { + $this->warnings[] = sprintf( + 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . + 'If the software is closed-source, you may use "proprietary" as license.', + json_encode($license) + ); + } } } } + + $this->config['license'] = array_values($licenses); + } else { + $this->warnings[] = sprintf( + 'License must be a string or array of strings, got %s.', + json_encode($this->config['license']) + ); + unset($this->config['license']); } } diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 9b70911eee41..24f8b687a5db 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -52,7 +52,7 @@ public static function successProvider(): array 'keywords' => ['a', 'b_c', 'D E', 'éîüø', '微信'], 'homepage' => 'https://foo.com', 'time' => '2010-10-10T10:10:10+00:00', - 'license' => 'MIT', + 'license' => ['MIT', 'WTFPL'], 'authors' => [ [ 'name' => 'Alice', @@ -165,12 +165,6 @@ public static function successProvider(): array 'transport-options' => ['ssl' => ['local_cert' => '/opt/certs/test.pem']], ], ], - [ // test licenses as array - [ - 'name' => 'foo/bar', - 'license' => ['MIT', 'WTFPL'], - ], - ], [ // test bin as string [ 'name' => 'foo/bar', @@ -252,7 +246,7 @@ public function testLoadWarnings(array $config, array $expectedWarnings): void * @param array $config * @param string[] $expectedWarnings */ - public function testLoadSkipsWarningDataWhenIgnoringErrors(array $config, array $expectedWarnings, bool $mustCheck = true): void + public function testLoadSkipsWarningDataWhenIgnoringErrors(array $config, array $expectedWarnings, bool $mustCheck = true, ?array $expectedArray = null): void { if (!$mustCheck) { self::assertTrue(true); // @phpstan-ignore staticMethod.alreadyNarrowedType @@ -263,7 +257,7 @@ public function testLoadSkipsWarningDataWhenIgnoringErrors(array $config, array $internalLoader ->expects($this->once()) ->method('load') - ->with(['name' => 'a/b']); + ->with($expectedArray ?? ['name' => 'a/b']); $loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL); $config['name'] = 'a/b'; @@ -552,6 +546,35 @@ public static function warningProvider(): array ], false, ], + [ + [ + 'name' => 'a/b', + 'license' => 'XXXXX', + ], + [ + 'License "XXXXX" is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.'.PHP_EOL. + 'If the software is closed-source, you may use "proprietary" as license.', + ], + true, + [ + 'name' => 'a/b', + 'license' => ['XXXXX'], + ] + ], + [ + [ + 'name' => 'a/b', + 'license' => [['author'=>'bar'], 'MIT'], + ], + [ + 'License {"author":"bar"} should be a string.', + ], + true, + [ + 'name' => 'a/b', + 'license' => ['MIT'], + ] + ], ]; } } From 2e7b006134ebafe996085e661929e81c7a58b175 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 26 Nov 2024 14:52:33 +0100 Subject: [PATCH 254/257] Add missing type annotation --- tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 24f8b687a5db..7f39a8789e2a 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -245,6 +245,7 @@ public function testLoadWarnings(array $config, array $expectedWarnings): void * * @param array $config * @param string[] $expectedWarnings + * @param array|null $expectedArray */ public function testLoadSkipsWarningDataWhenIgnoringErrors(array $config, array $expectedWarnings, bool $mustCheck = true, ?array $expectedArray = null): void { From 5eeba719d3f1388f68773c28e3971a7f441ee919 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 26 Nov 2024 17:10:11 +0100 Subject: [PATCH 255/257] Fix type --- src/Composer/Repository/VcsRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index a5b17b517807..6ca321e58e7e 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -66,7 +66,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt private $driver; /** @var ?VersionCacheInterface */ private $versionCache; - /** @var string[] */ + /** @var list */ private $emptyReferences = []; /** @var array<'tags'|'branches', array> */ private $versionTransportExceptions = []; @@ -165,7 +165,7 @@ public function hadInvalidBranches(): bool } /** - * @return string[] + * @return list */ public function getEmptyReferences(): array { From 302ecf824c961665cf0aead98f65099eacec04e8 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Wed, 27 Nov 2024 07:30:21 -0800 Subject: [PATCH 256/257] Update wording of process-timeout description (#12211) Cleans up the description of process-timeout to better separate the config setting from the static helper for script commands. --- doc/06-config.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/06-config.md b/doc/06-config.md index bff70d03d353..85a138b6a445 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -6,10 +6,22 @@ This chapter will describe the `config` section of the `composer.json` ## process-timeout The timeout in seconds for process executions, defaults to 300 (5mins). -The duration processes like git clones can run before +The duration processes like `git clone`s can run before Composer assumes they died out. You may need to make this higher if you have a slow connection or huge vendors. +Example: + +```json +{ + "config": { + "process-timeout": 900 + } +} +``` + +### Disabling timeouts for an individual script command + To disable the process timeout on a custom command under `scripts`, a static helper is available: From 74f68adeb1d6b7e3b402cb96350092ed3f66aa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Mleczko?= Date: Wed, 27 Nov 2024 16:31:32 +0100 Subject: [PATCH 257/257] fix(docs): Audit command dependency from custom repositories (#12212) --- doc/03-cli.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 7041617ad683..cc193e1620e1 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1069,9 +1069,10 @@ php composer.phar archive vendor/package 2.0.21 --format=zip ## audit This command is used to audit the packages you have installed -for possible security issues. It checks for and -lists security vulnerability advisories according to the -[Packagist.org api](https://packagist.org/apidoc#list-security-advisories). +for potential security issues. It checks for and +lists security vulnerability advisories using the +[Packagist.org api](https://packagist.org/apidoc#list-security-advisories) +by default or other repositories if specified in the `repositories` section of `composer.json`. The audit command returns the amount of vulnerabilities found. `0` if successful, and up to `255` otherwise.