From c692193978aa11e589424e22870107050082f155 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 1 Jun 2026 11:00:54 +0100 Subject: [PATCH 1/2] 622: use providers API to find matching exts in pie install for PHP projects --- phpstan-baseline.neon | 6 +-- src/Command/CommandHelper.php | 5 +- .../InstallExtensionsForProjectCommand.php | 2 +- .../FindMatchingPackages.php | 46 +++++++++++++++---- ...InstallExtensionsForProjectCommandTest.php | 4 +- .../FindMatchingPackagesTest.php | 2 +- 6 files changed, 48 insertions(+), 17 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 19da6192..3cd4a1ca 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -169,9 +169,9 @@ parameters: path: src/File/WindowsDelete.php - - message: '#^Call to function array_key_exists\(\) with ''downloads'' and array\{name\: string, description\: string\|null, abandoned\?\: string\|true, url\?\: string\} will always evaluate to false\.$#' - identifier: function.impossibleType - count: 2 + message: '#^Call to static method Php\\Pie\\ExtensionName\:\:isValidExtensionName\(\) with non\-empty\-string will always evaluate to true\.$#' + identifier: staticMethod.alreadyNarrowedType + count: 1 path: src/Installing/InstallForPhpProject/FindMatchingPackages.php - diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 5fb3757f..27e976ca 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -36,6 +36,7 @@ use function array_key_exists; use function array_map; +use function assert; use function count; use function is_array; use function is_string; @@ -430,6 +431,8 @@ public static function handlePackageNotFound( $requestedPackageName = substr($requestedPackageName, 4); } + assert($requestedPackageName !== ''); + $io->writeError(''); $io->writeError(sprintf('Could not install package: %s', $requestedPackageName)); $io->writeError($exception->getMessage()); @@ -460,7 +463,7 @@ static function (array $match) use ($io, $pieComposer): array { return $match; }, - $findMatchingPackages->for($pieComposer, $requestedPackageName), + $findMatchingPackages->bySearching($pieComposer, $requestedPackageName), ); if (count($matches)) { diff --git a/src/Command/InstallExtensionsForProjectCommand.php b/src/Command/InstallExtensionsForProjectCommand.php index 008bc5b8..0e999619 100644 --- a/src/Command/InstallExtensionsForProjectCommand.php +++ b/src/Command/InstallExtensionsForProjectCommand.php @@ -225,7 +225,7 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac )); try { - $matches = $this->findMatchingPackages->for($pieComposer, $extension->name()); + $matches = $this->findMatchingPackages->byProvider($pieComposer, $extension); } catch (OutOfRangeException) { $anyErrorsHappened = true; diff --git a/src/Installing/InstallForPhpProject/FindMatchingPackages.php b/src/Installing/InstallForPhpProject/FindMatchingPackages.php index 5111f659..ca5ecbe6 100644 --- a/src/Installing/InstallForPhpProject/FindMatchingPackages.php +++ b/src/Installing/InstallForPhpProject/FindMatchingPackages.php @@ -10,10 +10,12 @@ use OutOfRangeException; use Php\Pie\DependencyResolver\Package; use Php\Pie\ExtensionName; +use Php\Pie\ExtensionType; use function array_filter; use function array_key_exists; use function array_merge; +use function array_values; use function count; use function usort; @@ -24,15 +26,14 @@ */ class FindMatchingPackages { - /** @return MatchingPackages */ - public function for(Composer $pieComposer, string $searchTerm): array + /** + * @param list $matches + * @param non-empty-string $searchTerm + * + * @return MatchingPackages + */ + private function filterToCompatible(Composer $pieComposer, array $matches, string $searchTerm): array { - $matches = []; - foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) { - $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext')); - $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext-zend')); - } - if (ExtensionName::isValidExtensionName($searchTerm)) { $extensionName = ExtensionName::normaliseFromString($searchTerm); @@ -41,7 +42,7 @@ public function for(Composer $pieComposer, string $searchTerm): array static function (array $match) use ($pieComposer, $extensionName): bool { $package = $pieComposer->getRepositoryManager()->findPackage($match['name'], '*'); - if (! $package instanceof CompletePackageInterface) { + if (! $package instanceof CompletePackageInterface || ! ExtensionType::isValid($package->getType())) { return false; } @@ -61,4 +62,31 @@ static function (array $match) use ($pieComposer, $extensionName): bool { return $matches; } + + /** @return MatchingPackages */ + public function byProvider(Composer $pieComposer, ExtensionName $extensionName): array + { + $matches = []; + foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) { + $matches = array_merge($matches, $repo->getProviders($extensionName->nameWithExtPrefix())); + } + + return $this->filterToCompatible($pieComposer, array_values($matches), $extensionName->name()); + } + + /** + * @param non-empty-string $searchTerm + * + * @return MatchingPackages + */ + public function bySearching(Composer $pieComposer, string $searchTerm): array + { + $matches = []; + foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) { + $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext')); + $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext-zend')); + } + + return $this->filterToCompatible($pieComposer, $matches, $searchTerm); + } } diff --git a/test/integration/Command/InstallExtensionsForProjectCommandTest.php b/test/integration/Command/InstallExtensionsForProjectCommandTest.php index ce55f0b5..bad81fb6 100644 --- a/test/integration/Command/InstallExtensionsForProjectCommandTest.php +++ b/test/integration/Command/InstallExtensionsForProjectCommandTest.php @@ -119,7 +119,7 @@ public function testInstallingExtensionsForPhpProject(): void $this->composerFactoryForProject->method('composer')->willReturn($composer); - $this->findMatchingPackages->method('for')->willReturn([ + $this->findMatchingPackages->method('byProvider')->willReturn([ ['name' => 'vendor1/foobar', 'description' => 'The official foobar implementation'], ]); @@ -175,7 +175,7 @@ public function testInstallingExtensionsForPhpProjectWithMultipleMatches(): void $this->composerFactoryForProject->method('composer')->willReturn($composer); - $this->findMatchingPackages->method('for')->willReturn([ + $this->findMatchingPackages->method('byProvider')->willReturn([ ['name' => 'vendor1/foobar', 'description' => 'The official foobar implementation'], ['name' => 'vendor2/afoobar', 'description' => 'An improved async foobar extension'], ]); diff --git a/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php b/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php index d3032d86..1f5926e6 100644 --- a/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php +++ b/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php @@ -66,7 +66,7 @@ public function testSearchResultsAreFilteredByExtensionName(): void 'description' => 'The best extension there is', ], ], - (new FindMatchingPackages())->for($composer, 'bar'), + (new FindMatchingPackages())->bySearching($composer, 'bar'), ); } } From bee44c40ce2d564eb929a01f596b67db48380e3e Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Tue, 2 Jun 2026 07:58:25 +0100 Subject: [PATCH 2/2] 622: add test for FindMatchingPackages->byProviders --- .../FindMatchingPackagesTest.php | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php b/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php index 1f5926e6..41e97ba0 100644 --- a/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php +++ b/test/unit/Installing/InstallForPhpProject/FindMatchingPackagesTest.php @@ -12,6 +12,7 @@ use Composer\Repository\ArrayRepository; use Composer\Repository\RepositoryManager; use Composer\Util\HttpDownloader; +use Php\Pie\ExtensionName; use Php\Pie\ExtensionType; use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use PHPUnit\Framework\Attributes\CoversClass; @@ -69,4 +70,52 @@ public function testSearchResultsAreFilteredByExtensionName(): void (new FindMatchingPackages())->bySearching($composer, 'bar'), ); } + + public function testByProvider(): void + { + $package = new CompletePackage('foo/bar', '2.0.0.0', '2.0.0'); + $package->setDescription('The best extension there is'); + $package->setType(ExtensionType::PhpModule->value); + + $repository = $this->createPartialMock(ArrayRepository::class, ['getProviders']); + $repository->addPackage($package); + $repository->expects(self::once()) + ->method('getProviders') + ->with('ext-bar') + ->willReturn([ + 'foo1/bar' => [ + 'name' => 'foo1/bar', + 'description' => 'This is foo1/bar, not quite what you are looking for', + 'type' => 'library', + ], + 'foo/bar' => [ + 'name' => 'foo/bar', + 'description' => 'The best extension there is', + 'type' => 'php-ext', + ], + ]); + + $repoManager = new RepositoryManager( + $this->createMock(IOInterface::class), + $this->createMock(Config::class), + $this->createMock(HttpDownloader::class), + null, + null, + ); + $repoManager->addRepository($repository); + + $composer = $this->createMock(Composer::class); + $composer->method('getRepositoryManager')->willReturn($repoManager); + + self::assertSame( + [ + [ + 'name' => 'foo/bar', + 'description' => 'The best extension there is', + 'type' => 'php-ext', + ], + ], + (new FindMatchingPackages())->byProvider($composer, ExtensionName::normaliseFromString('bar')), + ); + } }