From 29efc473a1e947638d8323cb5aa0419d4bdf3f6b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jan 2020 09:23:20 +0100 Subject: [PATCH] Suggest which providers could be required to fulfill a virtual package requirement, fixes #2811 --- src/Composer/DependencyResolver/Problem.php | 11 +++++++++++ src/Composer/Repository/ComposerRepository.php | 17 +++++++++++++++++ src/Composer/Repository/RepositorySet.php | 13 +++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index d91b48dd3..1070dc4f3 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -250,6 +250,17 @@ class Problem return array("- 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(function ($p) { + return " - ${p['name']} ".substr($p['description'], 0, 100)."\n"; + }, count($providers) > $maxProviders+1 ? array_slice($providers, 0, $maxProviders) : $providers)); + if (count($providers) > $maxProviders+1) { + $providersStr .= ' ... and '.(count($providers)-$maxProviders).' more.'."\n"; + } + return array("- 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."); + } + return array("- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name."); } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index dd1588a9a..a03974c9a 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -52,6 +52,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected $cache; protected $notifyUrl; protected $searchUrl; + /** @var string|null a URL containing %package% which can be queried to get providers of a given name */ + protected $providersApiUrl; protected $hasProviders = false; protected $providersUrl; protected $availablePackages; @@ -416,6 +418,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return parent::search($query, $mode); } + public function getProviders($packageName) + { + if (!$this->providersApiUrl) { + return array(); + } + + $result = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson(); + + return $result['providers']; + } + private function getProviderNames() { $this->loadRootServerFile(); @@ -810,6 +823,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->hasProviders = true; } + if (!empty($data['providers-api'])) { + $this->providersApiUrl = $data['providers-api']; + } + return $this->rootData = $data; } diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 21c7efe0e..96fe94df9 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -158,6 +158,19 @@ class RepositorySet return $candidates; } + public function getProviders($packageName) + { + foreach ($this->repositories as $repository) { + if ($repository instanceof ComposerRepository) { + if ($providers = $repository->getProviders($packageName)) { + return $providers; + } + } + } + + return array(); + } + public function isPackageAcceptable($names, $stability) { return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability);