Skip to content

Commit 36fbea6

Browse files
committed
Merge branch '1.3.x' into 1.4.x
2 parents 10c4971 + 9f845c2 commit 36fbea6

File tree

12 files changed

+249
-127
lines changed

12 files changed

+249
-127
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,6 @@ parameters:
264264
count: 2
265265
path: src/Installing/InstallForPhpProject/FindMatchingPackages.php
266266

267-
-
268-
message: '#^Parameter \#2 \$callback of function array_walk expects callable\(array\|float\|int\|string\|true, int\|string\)\: mixed, Closure\(string, string\)\: void given\.$#'
269-
identifier: argument.type
270-
count: 1
271-
path: src/Installing/InstallForPhpProject/InstallSelectedPackage.php
272-
273-
-
274-
message: '#^Parameter \#2 \$workingDirectory of static method Php\\Pie\\Util\\Process\:\:run\(\) expects string\|null, string\|false given\.$#'
275-
identifier: argument.type
276-
count: 1
277-
path: src/Installing/InstallForPhpProject/InstallSelectedPackage.php
278-
279267
-
280268
message: '#^Negated boolean expression is always false\.$#'
281269
identifier: booleanNot.alwaysFalse
@@ -468,12 +456,6 @@ parameters:
468456
count: 1
469457
path: test/unit/Installing/Ini/StandardSinglePhpIniTest.php
470458

471-
-
472-
message: '#^Parameter \#1 \$originalCwd of class Php\\Pie\\File\\FullPathToSelf constructor expects string, string\|false given\.$#'
473-
identifier: argument.type
474-
count: 1
475-
path: test/unit/Installing/InstallForPhpProject/InstallSelectedPackageTest.php
476-
477459
-
478460
message: '#^Access to an undefined property Php\\PieUnitTest\\SelfManage\\BuildTools\\PhpizeBuildToolFinderTest\:\:\$phpBinaryPath\.$#'
479461
identifier: property.notFound

src/Command/InstallExtensionsForProjectCommand.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Php\Pie\ComposerIntegration\PieComposerFactory;
1313
use Php\Pie\ComposerIntegration\PieComposerRequest;
1414
use Php\Pie\ComposerIntegration\PieJsonEditor;
15+
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
1516
use Php\Pie\ExtensionName;
1617
use Php\Pie\ExtensionType;
1718
use Php\Pie\Installing\InstallForPhpProject\ComposerFactoryForProject;
@@ -28,6 +29,7 @@
2829
use Symfony\Component\Console\Input\InputInterface;
2930
use Symfony\Component\Console\Output\OutputInterface;
3031
use Throwable;
32+
use Webmozart\Assert\Assert;
3133

3234
use function array_column;
3335
use function array_key_exists;
@@ -268,20 +270,26 @@ static function (array $match): string {
268270
$selectedPackageName = $matches[0]['name'];
269271
}
270272

271-
$requestInstallConstraint = '';
272-
if ($linkRequiresConstraint !== '*') {
273-
$requestInstallConstraint = ':' . $linkRequiresConstraint;
274-
}
273+
assert($selectedPackageName !== '');
274+
$requestedPackageAndVersion = new RequestedPackageAndVersion(
275+
$selectedPackageName,
276+
$linkRequiresConstraint === '*' || $linkRequiresConstraint === '' ? null : $linkRequiresConstraint,
277+
);
275278

276279
try {
277280
$this->io->write(
278-
sprintf('Invoking pie install of %s%s', $selectedPackageName, $requestInstallConstraint),
281+
sprintf('Invoking pie install of %s', $requestedPackageAndVersion->prettyNameAndVersion()),
279282
verbosity: IOInterface::VERBOSE,
280283
);
281-
$this->installSelectedPackage->withPieCli(
282-
$selectedPackageName . $requestInstallConstraint,
283-
$input,
284-
$this->io,
284+
Assert::same(
285+
0,
286+
$this->installSelectedPackage->withSubCommand(
287+
ExtensionName::normaliseFromString($link->getTarget()),
288+
$requestedPackageAndVersion,
289+
$this,
290+
$input,
291+
),
292+
'Non-zero exit code %s whilst installing ' . $requestedPackageAndVersion->package,
285293
);
286294
} catch (Throwable $t) {
287295
$anyErrorsHappened = true;

src/Command/InvokeSubCommand.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Php\Pie\Command;
66

77
use Symfony\Component\Console\Command\Command;
8+
use Symfony\Component\Console\Formatter\OutputFormatter;
89
use Symfony\Component\Console\Input\ArrayInput;
910
use Symfony\Component\Console\Input\InputInterface;
1011
use Symfony\Component\Console\Output\OutputInterface;
@@ -29,6 +30,7 @@ public function __invoke(
2930
Command $command,
3031
array $subCommandInput,
3132
InputInterface $originalCommandInput,
33+
OutputFormatter|null $formatter = null,
3234
): int {
3335
$originalSuppliedOptions = array_filter($originalCommandInput->getOptions());
3436
$installForProjectInput = new ArrayInput(array_merge(
@@ -42,6 +44,19 @@ public function __invoke(
4244
$application = $command->getApplication();
4345
Assert::notNull($application);
4446

45-
return $application->doRun($installForProjectInput, $this->output);
47+
if ($formatter instanceof OutputFormatter) {
48+
$oldFormatter = $this->output->getFormatter();
49+
$this->output->setFormatter($formatter);
50+
}
51+
52+
try {
53+
$result = $application->doRun($installForProjectInput, $this->output);
54+
} finally {
55+
if ($formatter instanceof OutputFormatter) {
56+
$this->output->setFormatter($oldFormatter);
57+
}
58+
}
59+
60+
return $result;
4661
}
4762
}

src/DependencyResolver/RequestedPackageAndVersion.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,13 @@ public function __construct(
2727
throw InvalidPackageName::fromMissingForwardSlash($this);
2828
}
2929
}
30+
31+
public function prettyNameAndVersion(): string
32+
{
33+
if ($this->version === null) {
34+
return $this->package;
35+
}
36+
37+
return $this->package . ':' . $this->version;
38+
}
3039
}

src/Installing/InstallForPhpProject/InstallSelectedPackage.php

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,37 @@
44

55
namespace Php\Pie\Installing\InstallForPhpProject;
66

7-
use Composer\IO\IOInterface;
8-
use Php\Pie\Command\CommandHelper;
9-
use Php\Pie\File\FullPathToSelf;
10-
use Php\Pie\Util\Process;
7+
use Php\Pie\Command\InvokeSubCommand;
8+
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
9+
use Php\Pie\ExtensionName;
10+
use Php\Pie\Util\OutputFormatterWithPrefix;
11+
use Symfony\Component\Console\Command\Command;
1112
use Symfony\Component\Console\Input\InputInterface;
1213

13-
use function array_filter;
14-
use function array_walk;
15-
use function getcwd;
16-
use function in_array;
17-
18-
use const ARRAY_FILTER_USE_BOTH;
19-
2014
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
2115
class InstallSelectedPackage
2216
{
23-
public function __construct(private readonly FullPathToSelf $fullPathToSelf)
24-
{
17+
public function __construct(
18+
private readonly InvokeSubCommand $invokeSubCommand,
19+
) {
2520
}
2621

27-
public function withPieCli(string $selectedPackage, InputInterface $input, IOInterface $io): void
28-
{
29-
$process = [
30-
($this->fullPathToSelf)(),
31-
'install',
32-
$selectedPackage,
22+
public function withSubCommand(
23+
ExtensionName $ext,
24+
RequestedPackageAndVersion $selectedPackage,
25+
Command $command,
26+
InputInterface $input,
27+
): int {
28+
$params = [
29+
'command' => 'install',
30+
'requested-package-and-version' => $selectedPackage->prettyNameAndVersion(),
3331
];
3432

35-
$phpPathOptions = array_filter(
36-
$input->getOptions(),
37-
static function (mixed $value, string|int $key): bool {
38-
return $value !== null
39-
&& $value !== false
40-
&& in_array(
41-
$key,
42-
[
43-
CommandHelper::OPTION_WITH_PHP_CONFIG,
44-
CommandHelper::OPTION_WITH_PHP_PATH,
45-
CommandHelper::OPTION_WITH_PHPIZE_PATH,
46-
],
47-
);
48-
},
49-
ARRAY_FILTER_USE_BOTH,
50-
);
51-
52-
array_walk(
53-
$phpPathOptions,
54-
static function (string $value, string $key) use (&$process): void {
55-
$process[] = '--' . $key;
56-
$process[] = $value;
57-
},
58-
);
59-
60-
Process::run(
61-
$process,
62-
getcwd(),
63-
outputCallback: static function (string $outOrErr, string $message) use ($io): void {
64-
if ($outOrErr === \Symfony\Component\Process\Process::ERR) {
65-
$io->writeError(' > ' . $message);
66-
67-
return;
68-
}
69-
70-
$io->write(' > ' . $message);
71-
},
33+
return ($this->invokeSubCommand)(
34+
$command,
35+
$params,
36+
$input,
37+
OutputFormatterWithPrefix::newWithPrefix(' ' . $ext->name() . '> '),
7238
);
7339
}
7440
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\Pie\Util;
6+
7+
use Composer\Factory;
8+
use Symfony\Component\Console\Formatter\OutputFormatter;
9+
10+
class OutputFormatterWithPrefix extends OutputFormatter
11+
{
12+
/**
13+
* @param non-empty-string $linePrefix
14+
*
15+
* @inheritDoc
16+
*/
17+
public function __construct(private readonly string $linePrefix, bool $decorated = false, array $styles = [])
18+
{
19+
parent::__construct($decorated, $styles);
20+
}
21+
22+
/** @param non-empty-string $linePrefix */
23+
public static function newWithPrefix(string $linePrefix): self
24+
{
25+
return new self($linePrefix, false, Factory::createAdditionalStyles());
26+
}
27+
28+
public function format(string|null $message): string|null
29+
{
30+
$formatted = parent::format($message);
31+
32+
if ($formatted === null) {
33+
return null;
34+
}
35+
36+
return $this->linePrefix . $formatted;
37+
}
38+
}

test/assets/fake-pie-cli/happy.bat

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/assets/fake-pie-cli/happy.sh

Lines changed: 0 additions & 4 deletions
This file was deleted.

test/integration/Command/InstallExtensionsForProjectCommandTest.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Php\Pie\ComposerIntegration\PieJsonEditor;
1818
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
1919
use Php\Pie\Container;
20+
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
21+
use Php\Pie\ExtensionName;
2022
use Php\Pie\ExtensionType;
2123
use Php\Pie\Installing\InstallForPhpProject\ComposerFactoryForProject;
2224
use Php\Pie\Installing\InstallForPhpProject\DetermineExtensionsRequired;
@@ -125,8 +127,15 @@ public function testInstallingExtensionsForPhpProject(): void
125127
$this->questionHelper->method('ask')->willReturn('vendor1/foobar: The official foobar implementation');
126128

127129
$this->installSelectedPackage->expects(self::once())
128-
->method('withPieCli')
129-
->with('vendor1/foobar:^1.2');
130+
->method('withSubCommand')
131+
->with(
132+
ExtensionName::normaliseFromString('foobar'),
133+
new RequestedPackageAndVersion(
134+
'vendor1/foobar',
135+
'^1.2',
136+
),
137+
)
138+
->willReturn(0);
130139

131140
$this->commandTester->execute(
132141
['--allow-non-interactive-project-install' => true],
@@ -173,7 +182,7 @@ public function testInstallingExtensionsForPhpProjectWithMultipleMatches(): void
173182
$this->questionHelper->method('ask')->willReturn('vendor1/foobar: The official foobar implementation');
174183

175184
$this->installSelectedPackage->expects(self::never())
176-
->method('withPieCli');
185+
->method('withSubCommand');
177186

178187
$this->commandTester->execute(
179188
['--allow-non-interactive-project-install' => true],
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\PieUnitTest\Command;
6+
7+
use Php\Pie\Command\InvokeSubCommand;
8+
use Php\Pie\Util\OutputFormatterWithPrefix;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\TestCase;
11+
use Symfony\Component\Console\Application;
12+
use Symfony\Component\Console\Command\Command;
13+
use Symfony\Component\Console\Input\ArrayInput;
14+
use Symfony\Component\Console\Input\InputDefinition;
15+
use Symfony\Component\Console\Input\InputOption;
16+
use Symfony\Component\Console\Output\BufferedOutput;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
19+
use function trim;
20+
21+
#[CoversClass(InvokeSubCommand::class)]
22+
final class InvokeSubCommandTest extends TestCase
23+
{
24+
public function testInvokeWithNoOutputFormatterRunsSubCommand(): void
25+
{
26+
$inputDefinition = new InputDefinition();
27+
$inputDefinition->addOption(new InputOption('verbose', 'v', InputOption::VALUE_NONE, 'Verbose option'));
28+
$input = new ArrayInput(['--verbose' => true], $inputDefinition);
29+
30+
$output = new BufferedOutput();
31+
32+
$application = $this->createMock(Application::class);
33+
$application->expects(self::once())
34+
->method('doRun')
35+
->willReturnCallback(static function (ArrayInput $newInput, OutputInterface $output) {
36+
self::assertSame('foo --verbose=1', (string) $newInput);
37+
$output->writeln('command output here');
38+
39+
return 0;
40+
});
41+
42+
$command = $this->createMock(Command::class);
43+
$command->method('getApplication')->willReturn($application);
44+
45+
$invoker = new InvokeSubCommand($output);
46+
self::assertSame(0, ($invoker)($command, ['command' => 'foo'], $input));
47+
self::assertSame('command output here', trim($output->fetch()));
48+
}
49+
50+
public function testInvokeWithPrefixOutputFormatterRunsSubCommand(): void
51+
{
52+
$inputDefinition = new InputDefinition();
53+
$inputDefinition->addOption(new InputOption('verbose', 'v', InputOption::VALUE_NONE, 'Verbose option'));
54+
$input = new ArrayInput(['--verbose' => true], $inputDefinition);
55+
56+
$output = new BufferedOutput();
57+
58+
$application = $this->createMock(Application::class);
59+
$application->expects(self::once())
60+
->method('doRun')
61+
->willReturnCallback(static function (ArrayInput $newInput, OutputInterface $output) {
62+
self::assertSame('foo --verbose=1', (string) $newInput);
63+
$output->writeln('command output here');
64+
65+
return 0;
66+
});
67+
68+
$command = $this->createMock(Command::class);
69+
$command->method('getApplication')->willReturn($application);
70+
71+
$invoker = new InvokeSubCommand($output);
72+
self::assertSame(0, ($invoker)($command, ['command' => 'foo'], $input, new OutputFormatterWithPrefix('prefix> ')));
73+
self::assertSame('prefix> command output here', trim($output->fetch()));
74+
}
75+
}

0 commit comments

Comments
 (0)