Skip to content

Commit 892c536

Browse files
committed
Ensure target PHP platform is used to resolve package version
1 parent 52d6b65 commit 892c536

11 files changed

+106
-32
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"require": {
2929
"php": "8.1.*||8.2.*||8.3.*",
3030
"ext-zip": "*",
31-
"composer/composer": "^2.7",
31+
"composer/composer": "dev-main@dev",
3232
"guzzlehttp/guzzle": "^7.8",
3333
"guzzlehttp/psr7": "^2.6",
3434
"illuminate/container": "^10.47",

composer.lock

+11-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Command/DownloadCommand.php

+21-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
use InvalidArgumentException;
99
use Php\Pie\DependencyResolver\DependencyResolver;
1010
use Php\Pie\Downloading\DownloadAndExtract;
11+
use Php\Pie\TargetPhp\PhpBinaryPath;
1112
use Symfony\Component\Console\Attribute\AsCommand;
1213
use Symfony\Component\Console\Command\Command;
1314
use Symfony\Component\Console\Input\InputArgument;
1415
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Input\InputOption;
1517
use Symfony\Component\Console\Output\OutputInterface;
1618
use Webmozart\Assert\Assert;
1719

@@ -30,6 +32,7 @@
3032
final class DownloadCommand extends Command
3133
{
3234
private const ARG_REQUESTED_PACKAGE_AND_VERSION = 'requested-package-and-version';
35+
private const OPTION_WITH_PHP_CONFIG = 'with-php-config';
3336

3437
public function __construct(
3538
private readonly DependencyResolver $dependencyResolver,
@@ -47,18 +50,35 @@ public function configure(): void
4750
InputArgument::REQUIRED,
4851
'The extension name and version constraint to use, in the format {ext-name}{?:version-constraint}{?@dev-branch-name}, for example `ext-debug:^1.0`',
4952
);
53+
$this->addOption(
54+
self::OPTION_WITH_PHP_CONFIG,
55+
null,
56+
InputOption::VALUE_OPTIONAL,
57+
'The path to `php-config` to use',
58+
);
5059
}
5160

5261
public function execute(InputInterface $input, OutputInterface $output): int
5362
{
63+
$phpBinaryPath = PhpBinaryPath::fromCurrentProcess();
64+
65+
/** @var mixed $withPhpConfig */
66+
$withPhpConfig = $input->getOption(self::OPTION_WITH_PHP_CONFIG);
67+
if (is_string($withPhpConfig) && $withPhpConfig !== '') {
68+
$phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($withPhpConfig);
69+
}
70+
71+
$output->writeln(sprintf('<info>You are running PHP %s</info>', PHP_VERSION));
72+
$output->writeln(sprintf('<info>Target PHP installation: %s (from %s)</info>', $phpBinaryPath->version(), $phpBinaryPath->phpBinaryPath));
73+
5474
$requestedNameAndVersionPair = $this->requestedNameAndVersionPair($input);
5575

5676
$package = ($this->dependencyResolver)(
77+
$phpBinaryPath,
5778
$requestedNameAndVersionPair['name'],
5879
$requestedNameAndVersionPair['version'],
5980
);
6081

61-
$output->writeln(sprintf('<info>You are running PHP %s</info>', PHP_VERSION));
6282
$output->writeln(sprintf('<info>Found package:</info> %s (version: %s)', $package->name, $package->version));
6383
$output->writeln(sprintf('<info>Dist download URL:</info> %s', $package->downloadUrl ?? '(none)'));
6484

src/Container.php

+6-7
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
use Composer\Factory as ComposerFactory;
99
use Composer\IO\ConsoleIO;
1010
use Composer\IO\IOInterface;
11-
use Composer\IO\NullIO;
1211
use Composer\Repository\CompositeRepository;
13-
use Composer\Repository\PlatformRepository;
14-
use Composer\Repository\RepositoryFactory;
1512
use Composer\Repository\RepositorySet;
1613
use Composer\Util\AuthHelper;
1714
use Composer\Util\Platform;
@@ -24,6 +21,7 @@
2421
use Php\Pie\Downloading\DownloadZip;
2522
use Php\Pie\Downloading\ExtractZip;
2623
use Php\Pie\Downloading\UnixDownloadAndExtract;
24+
use Php\Pie\TargetPhp\ResolveTargetPhpToPlatformRepository;
2725
use Psr\Container\ContainerInterface;
2826
use RuntimeException;
2927
use Symfony\Component\Console\Helper\HelperSet;
@@ -51,21 +49,22 @@ public static function factory(): ContainerInterface
5149
});
5250
$container->singleton(Composer::class, static function (ContainerInterface $container): Composer {
5351
$io = $container->get(IOInterface::class);
54-
$composer = ComposerFactory::create($io);
52+
$composer = (new ComposerFactory())->createComposer($io, [], true);
5553
$io->loadConfiguration($composer->getConfig());
5654

5755
return $composer;
5856
});
5957

6058
$container->singleton(
6159
DependencyResolver::class,
62-
static function (): DependencyResolver {
60+
static function (ContainerInterface $container): DependencyResolver {
61+
$composer = $container->get(Composer::class);
6362
$repositorySet = new RepositorySet();
64-
$repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultReposWithDefaultManager(new NullIO())));
63+
$repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
6564

6665
return new ResolveDependencyWithComposer(
67-
new PlatformRepository(),
6866
$repositorySet,
67+
new ResolveTargetPhpToPlatformRepository(),
6968
);
7069
},
7170
);

src/DependencyResolver/DependencyResolver.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
namespace Php\Pie\DependencyResolver;
66

7+
use Php\Pie\TargetPhp\PhpBinaryPath;
8+
79
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
810
interface DependencyResolver
911
{
1012
/** @throws UnableToResolveRequirement */
11-
public function __invoke(string $packageName, string|null $requestedVersion): Package;
13+
public function __invoke(PhpBinaryPath $phpBinaryPath, string $packageName, string|null $requestedVersion): Package;
1214
}

src/DependencyResolver/ResolveDependencyWithComposer.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,29 @@
66

77
use Composer\Package\CompletePackageInterface;
88
use Composer\Package\Version\VersionSelector;
9-
use Composer\Repository\PlatformRepository;
109
use Composer\Repository\RepositorySet;
10+
use Php\Pie\TargetPhp\PhpBinaryPath;
11+
use Php\Pie\TargetPhp\ResolveTargetPhpToPlatformRepository;
1112

1213
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
1314
final class ResolveDependencyWithComposer implements DependencyResolver
1415
{
1516
public function __construct(
16-
private readonly PlatformRepository $platformRepository,
1717
private readonly RepositorySet $repositorySet,
18+
private readonly ResolveTargetPhpToPlatformRepository $resolveTargetPhpToPlatformRepository,
1819
) {
1920
}
2021

21-
public function __invoke(string $packageName, string|null $requestedVersion): Package
22+
public function __invoke(PhpBinaryPath $phpBinaryPath, string $packageName, string|null $requestedVersion): Package
2223
{
23-
$package = (new VersionSelector($this->repositorySet, $this->platformRepository))
24+
$package = (new VersionSelector(
25+
$this->repositorySet,
26+
($this->resolveTargetPhpToPlatformRepository)($phpBinaryPath),
27+
))
2428
->findBestCandidate($packageName, $requestedVersion);
2529

30+
// @todo check it is a `php-ext` or `php-ext-zend`
31+
2632
if (! $package instanceof CompletePackageInterface) {
2733
throw UnableToResolveRequirement::fromRequirement($packageName, $requestedVersion);
2834
}

src/TargetPhp/PhpBinaryPath.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
use Symfony\Component\Process\Process;
99
use Webmozart\Assert\Assert;
1010

11+
use function trim;
12+
1113
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
12-
final class PhpBinaryPath
14+
class PhpBinaryPath
1315
{
1416
/** @param non-empty-string $phpBinaryPath */
1517
private function __construct(readonly string $phpBinaryPath)
@@ -22,6 +24,7 @@ public function version(): string
2224
->mustRun()
2325
->getOutput());
2426
Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');
27+
2528
return $phpVersion;
2629
}
2730

@@ -32,13 +35,15 @@ public static function fromPhpConfigExecutable(string $phpConfig): self
3235
->mustRun()
3336
->getOutput());
3437
Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');
38+
3539
return new self($phpExecutable);
3640
}
3741

3842
public static function fromCurrentProcess(): self
3943
{
40-
$phpExecutable = trim((new PhpExecutableFinder())->find());
44+
$phpExecutable = trim((string) (new PhpExecutableFinder())->find());
4145
Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');
46+
4247
return new self($phpExecutable);
4348
}
4449
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\Pie\TargetPhp;
6+
7+
use Composer\Repository\PlatformRepository;
8+
9+
class ResolveTargetPhpToPlatformRepository
10+
{
11+
public function __invoke(PhpBinaryPath $phpBinaryPath): PlatformRepository
12+
{
13+
// @todo I expect we also need to map the extensions for the given PHP binary, somehow?
14+
return new PlatformRepository([], ['php' => $phpBinaryPath->version()]);
15+
}
16+
}

test/integration/Command/DownloadCommandTest.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@ public function setUp(): void
2626

2727
public function testDownloadCommand(): void
2828
{
29-
$this->commandTester->execute(['requested-package-and-version' => 'ramsey/uuid']);
29+
if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
30+
self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
31+
}
32+
33+
// 1.0.0 is only compatible with PHP 8.3.0
34+
$this->commandTester->execute(['requested-package-and-version' => 'asgrim/example-pie-extension:1.0.0']);
3035

3136
$this->commandTester->assertCommandIsSuccessful();
3237

3338
$outputString = $this->commandTester->getDisplay();
34-
self::assertStringContainsString('Found package: ramsey/uuid (version: ', $outputString);
35-
self::assertStringContainsString('Dist download URL: https://api.github.com/repos/ramsey/uuid/zipball/', $outputString);
39+
self::assertStringContainsString('Found package: asgrim/example-pie-extension (version: 1.0.0)', $outputString);
40+
self::assertStringContainsString('Dist download URL: https://api.github.com/repos/asgrim/example-pie-extension/zipball/', $outputString);
3641
}
3742

3843
public function testDownloadCommandFailsWhenUsingIncompatiblePhpVersion(): void
@@ -42,6 +47,7 @@ public function testDownloadCommandFailsWhenUsingIncompatiblePhpVersion(): void
4247
}
4348

4449
$this->expectException(UnableToResolveRequirement::class);
45-
$this->commandTester->execute(['requested-package-and-version' => 'phpunit/phpunit:^11.0']);
50+
// 1.0.0 is only compatible with PHP 8.3.0
51+
$this->commandTester->execute(['requested-package-and-version' => 'asgrim/example-pie-extension:1.0.0']);
4652
}
4753
}

test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php

+19-4
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
use Composer\IO\NullIO;
88
use Composer\Repository\CompositeRepository;
9-
use Composer\Repository\PlatformRepository;
109
use Composer\Repository\RepositoryFactory;
1110
use Composer\Repository\RepositorySet;
1211
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
1312
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
13+
use Php\Pie\TargetPhp\PhpBinaryPath;
14+
use Php\Pie\TargetPhp\ResolveTargetPhpToPlatformRepository;
1415
use PHPUnit\Framework\Attributes\CoversClass;
1516
use PHPUnit\Framework\Attributes\DataProvider;
1617
use PHPUnit\Framework\TestCase;
@@ -19,21 +20,29 @@
1920
final class ResolveDependencyWithComposerTest extends TestCase
2021
{
2122
private RepositorySet $repositorySet;
23+
private ResolveTargetPhpToPlatformRepository $resolveTargetPhpToPlatformRepository;
2224

2325
public function setUp(): void
2426
{
2527
parent::setUp();
2628

2729
$this->repositorySet = new RepositorySet();
2830
$this->repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultReposWithDefaultManager(new NullIO())));
31+
32+
$this->resolveTargetPhpToPlatformRepository = new ResolveTargetPhpToPlatformRepository();
2933
}
3034

3135
public function testPackageThatCanBeResolved(): void
3236
{
37+
$phpBinaryPath = $this->createMock(PhpBinaryPath::class);
38+
$phpBinaryPath->expects(self::once())
39+
->method('version')
40+
->willReturn('8.2.0');
41+
3342
$package = (new ResolveDependencyWithComposer(
34-
new PlatformRepository([], ['php' => '8.2.0']),
3543
$this->repositorySet,
36-
))('phpunit/phpunit', '^11.0');
44+
$this->resolveTargetPhpToPlatformRepository,
45+
))($phpBinaryPath, 'phpunit/phpunit', '^11.0');
3746

3847
self::assertSame('phpunit/phpunit', $package->name);
3948
}
@@ -55,12 +64,18 @@ public static function unresolvableDependencies(): array
5564
#[DataProvider('unresolvableDependencies')]
5665
public function testPackageThatCannotBeResolvedThrowsException(array $platformOverrides, string $package, string $version): void
5766
{
67+
$phpBinaryPath = $this->createMock(PhpBinaryPath::class);
68+
$phpBinaryPath->expects(self::once())
69+
->method('version')
70+
->willReturn($platformOverrides['php']);
71+
5872
$this->expectException(UnableToResolveRequirement::class);
5973

6074
(new ResolveDependencyWithComposer(
61-
new PlatformRepository([], $platformOverrides),
6275
$this->repositorySet,
76+
$this->resolveTargetPhpToPlatformRepository,
6377
))(
78+
$phpBinaryPath,
6479
$package,
6580
$version,
6681
);

test/unit/TargetPhp/PhpBinaryPathTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use PHPUnit\Framework\Attributes\CoversClass;
99
use PHPUnit\Framework\TestCase;
1010

11+
use const PHP_VERSION;
12+
1113
#[CoversClass(PhpBinaryPath::class)]
1214
final class PhpBinaryPathTest extends TestCase
1315
{

0 commit comments

Comments
 (0)