Skip to content

Commit 226ba7d

Browse files
authored
feat: Allow to configure the parser PHP version (humbug#1044)
This PR introduces a `php-version` to the configuration file and as command options for the `add-prefix` and `inspect` command (e.g. have `--php-version=7.2`). The PHP version provided is used to configure the underlying PHP-Parser Parser and Printer. This will not affect the PHP internal symbols used by PHP-Scoper (i.e. `mb_str_pad` will be understood as an internal function, even if the PHP version configured is <8.3). However, this will affect what code can be parsed and how the code will be printed. If no PHP version is used, the host version will be used, i.e. executing it with PHP 8.4 will result in PHP 8.4 being used as the PHP version. This should allow to forcefully fix sebastianbergmann/phpunit#5855.
1 parent 5478f21 commit 226ba7d

13 files changed

+165
-24
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver
3636
- [Usage](#usage)
3737
- [Configuration](docs/configuration.md#configuration)
3838
- [Prefix](docs/configuration.md#prefix)
39+
- [PHP Version](docs/configuration.md#php-version)
3940
- [Output directory](docs/configuration.md#output-directory)
4041
- [Finders and paths](docs/configuration.md#finders-and-paths)
4142
- [Patchers](docs/configuration.md#patchers)

docs/configuration.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## Configuration
22

33
- [Prefix](#prefix)
4+
- [PHP-Version](#php-version)
45
- [Output directory](#output-directory)
56
- [Finders and paths](#finders-and-paths)
67
- [Patchers](#patchers)
@@ -28,6 +29,7 @@ use Isolated\Symfony\Component\Finder\Finder;
2829

2930
return [
3031
'prefix' => null, // string|null
32+
'php-version' => null, // string|null
3133
'output-dir' => null, // string|null
3234
'finders' => [], // list<Finder>
3335
'patchers' => [], // list<callable(string $filePath, string $prefix, string $contents): string>
@@ -56,6 +58,15 @@ The prefix to be used to isolate the code. If `null` or `''` (empty string) is g
5658
then a random prefix will be automatically generated.
5759

5860

61+
### PHP Version
62+
63+
The PHP version provided is used to configure the underlying [PHP-Parser] Parser and Printer. This will not affect
64+
the PHP internal symbols used by PHP-Scoper but may affect what code can be parsed and how the code will be printed.
65+
66+
If `null` or `''` (empty string) is given, then the host version will be used, i.e. executing it with PHP 8.4 will
67+
result in PHP 8.4 being used as the PHP version.
68+
69+
5970
### Output directory
6071

6172
The base output directory where the prefixed files will be generated. If `null`
@@ -476,5 +487,6 @@ namespace Humbug\Acme;
476487

477488
[box]: https://github.com/box-project/box
478489
[php-scoper-integration]: https://github.com/humbug/box#isolating-the-phar
490+
[PHP-Parser]: https://github.com/nikic/PHP-Parser
479491
[phpstorm-stubs]: https://github.com/JetBrains/phpstorm-stubs
480492
[symfony_finder]: https://symfony.com/doc/current/components/finder.html

src/Configuration/Configuration.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue;
1818
use Humbug\PhpScoper\Patcher\Patcher;
19+
use PhpParser\PhpVersion;
1920

2021
final class Configuration
2122
{
@@ -38,6 +39,7 @@ public function __construct(
3839
private ?string $path,
3940
private ?string $outputDir,
4041
string|Prefix $prefix,
42+
private ?PhpVersion $phpVersion,
4143
private array $filesWithContents,
4244
private array $excludedFilesWithContents,
4345
private Patcher $patcher,
@@ -75,6 +77,7 @@ public function withPrefix(string $prefix): self
7577
$this->path,
7678
$this->outputDir,
7779
$prefix,
80+
$this->phpVersion,
7881
$this->filesWithContents,
7982
$this->excludedFilesWithContents,
8083
$this->patcher,
@@ -99,6 +102,7 @@ public function withFilesWithContents(array $filesWithContents): self
99102
$this->path,
100103
$this->outputDir,
101104
$this->prefix,
105+
$this->phpVersion,
102106
$filesWithContents,
103107
$this->excludedFilesWithContents,
104108
$this->patcher,
@@ -128,6 +132,7 @@ public function withPatcher(Patcher $patcher): self
128132
$this->path,
129133
$this->outputDir,
130134
$this->prefix,
135+
$this->phpVersion,
131136
$this->filesWithContents,
132137
$this->excludedFilesWithContents,
133138
$patcher,
@@ -144,4 +149,9 @@ public function getSymbolsConfiguration(): SymbolsConfiguration
144149
{
145150
return $this->symbolsConfiguration;
146151
}
152+
153+
public function getPhpVersion(): ?PhpVersion
154+
{
155+
return $this->phpVersion;
156+
}
147157
}

src/Configuration/ConfigurationFactory.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
namespace Humbug\PhpScoper\Configuration;
1616

17+
use Exception;
1718
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfiguration;
1819
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationFile;
1920
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue;
@@ -22,6 +23,7 @@
2223
use Humbug\PhpScoper\Patcher\PatcherChain;
2324
use Humbug\PhpScoper\Patcher\SymfonyParentTraitPatcher;
2425
use Humbug\PhpScoper\Patcher\SymfonyPatcher;
26+
use PhpParser\PhpVersion;
2527
use SplFileInfo;
2628
use Symfony\Component\Filesystem\Filesystem;
2729
use Symfony\Component\Finder\Finder;
@@ -97,6 +99,7 @@ public function create(?string $path = null, array $paths = []): Configuration
9799
$path,
98100
$outputDir,
99101
$prefix,
102+
self::retrievePhpVersion($config),
100103
$filesWithContents,
101104
self::retrieveFilesWithContents($excludedFiles),
102105
new PatcherChain($patchers),
@@ -178,6 +181,28 @@ private static function retrievePrefix(array $config): string
178181
return '' === $prefix ? self::generateRandomPrefix() : $prefix;
179182
}
180183

184+
/**
185+
* @throws InvalidConfigurationValue
186+
*/
187+
private static function retrievePhpVersion(array $config): ?PhpVersion
188+
{
189+
$stringVersion = $config[ConfigurationKeys::PHP_VERSION_KEYWORD] ?? null;
190+
191+
if (null === $stringVersion || '' === $stringVersion) {
192+
return null;
193+
}
194+
195+
if (!is_string($stringVersion)) {
196+
throw InvalidConfigurationValue::forInvalidPhpVersionType($stringVersion);
197+
}
198+
199+
try {
200+
return PhpVersion::fromString($stringVersion);
201+
} catch (Exception $exception) {
202+
throw InvalidConfigurationValue::forInvalidPhpVersion($stringVersion, $exception);
203+
}
204+
}
205+
181206
/**
182207
* @return non-empty-string|null
183208
*/

src/Configuration/ConfigurationKeys.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ final class ConfigurationKeys
2222
use NotInstantiable;
2323

2424
public const PREFIX_KEYWORD = 'prefix';
25+
public const PHP_VERSION_KEYWORD = 'php-version';
2526
public const OUTPUT_DIR_KEYWORD = 'output-dir';
2627
public const EXCLUDED_FILES_KEYWORD = 'exclude-files';
2728
public const FINDER_KEYWORD = 'finders';
@@ -43,6 +44,7 @@ final class ConfigurationKeys
4344

4445
public const KEYWORDS = [
4546
self::PREFIX_KEYWORD,
47+
self::PHP_VERSION_KEYWORD,
4648
self::OUTPUT_DIR_KEYWORD,
4749
self::EXCLUDED_FILES_KEYWORD,
4850
self::FINDER_KEYWORD,

src/Configuration/Throwable/InvalidConfigurationValue.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
namespace Humbug\PhpScoper\Configuration\Throwable;
1616

17+
use Exception;
1718
use Symfony\Component\Finder\Finder;
1819
use UnexpectedValueException;
1920
use function gettype;
21+
use function sprintf;
2022

2123
final class InvalidConfigurationValue extends UnexpectedValueException implements InvalidConfiguration
2224
{
@@ -115,6 +117,27 @@ public static function forInvalidPrefixPattern(string $prefix): self
115117
);
116118
}
117119

120+
public static function forInvalidPhpVersionType(mixed $phpVersion): self
121+
{
122+
return new self(
123+
sprintf(
124+
'Expected the PHP version to be a string, got "%s" instead.',
125+
gettype($phpVersion),
126+
),
127+
);
128+
}
129+
130+
public static function forInvalidPhpVersion(string $stringVersion, Exception $previous): self
131+
{
132+
return new self(
133+
sprintf(
134+
'Expected the PHP version to of the format "<major>.<minor>", e.g. "7.2", got "%s".',
135+
$stringVersion,
136+
),
137+
previous: $previous,
138+
);
139+
}
140+
118141
public static function forInvalidNamespaceSeparator(string $prefix): self
119142
{
120143
return new self(

src/Console/Command/AddPrefixCommand.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Humbug\PhpScoper\Console\ConsoleScoper;
3030
use Humbug\PhpScoper\Scoper\Factory\ScoperFactory;
3131
use InvalidArgumentException;
32+
use PhpParser\PhpVersion;
3233
use Symfony\Component\Console\Exception\RuntimeException;
3334
use Symfony\Component\Console\Input\InputArgument;
3435
use Symfony\Component\Console\Input\InputOption;
@@ -56,6 +57,7 @@ final class AddPrefixCommand implements Command, CommandAware
5657
private const CONTINUE_ON_FAILURE_OPT = 'continue-on-failure';
5758
private const CONFIG_FILE_OPT = 'config';
5859
private const NO_CONFIG_OPT = 'no-config';
60+
private const PHP_VERSION_OPT = 'php-version';
5961

6062
private const DEFAULT_OUTPUT_DIR = 'build';
6163

@@ -96,7 +98,7 @@ public function getConfiguration(): CommandConfiguration
9698
'o',
9799
InputOption::VALUE_REQUIRED,
98100
'The output directory in which the prefixed code will be dumped.',
99-
''
101+
'',
100102
),
101103
new InputOption(
102104
self::FORCE_OPT,
@@ -131,6 +133,12 @@ public function getConfiguration(): CommandConfiguration
131133
InputOption::VALUE_NONE,
132134
'Do not look for a configuration file.',
133135
),
136+
new InputOption(
137+
self::PHP_VERSION_OPT,
138+
null,
139+
InputOption::VALUE_REQUIRED,
140+
'PHP version in which the PHP parser and printer will be configured, e.g. "7.2".',
141+
),
134142
],
135143
);
136144
}
@@ -147,6 +155,7 @@ public function execute(IO $io): int
147155

148156
$paths = $this->getPathArguments($io, $cwd);
149157
$config = $this->retrieveConfig($io, $paths, $cwd);
158+
$phpVersion = self::getPhpVersion($io);
150159

151160
$outputDir = $this->canonicalizePath(
152161
$this->getOutputDir($io, $config),
@@ -157,6 +166,7 @@ public function execute(IO $io): int
157166
$this->getScoper()->scope(
158167
$io,
159168
$config,
169+
$phpVersion,
160170
$paths,
161171
$outputDir,
162172
self::getStopOnFailure($io),
@@ -165,6 +175,17 @@ public function execute(IO $io): int
165175
return ExitCode::SUCCESS;
166176
}
167177

178+
private static function getPhpVersion(IO $io): ?PhpVersion
179+
{
180+
$version = $io
181+
->getTypedOption(self::PHP_VERSION_OPT)
182+
->asNullableString();
183+
184+
return null === $version
185+
? $version
186+
: PhpVersion::fromString($version);
187+
}
188+
168189
private static function getStopOnFailure(IO $io): bool
169190
{
170191
$stopOnFailure = $io->getTypedOption(self::STOP_ON_FAILURE_OPT)->asBoolean();

src/Console/Command/InspectCommand.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Humbug\PhpScoper\Scoper\Factory\ScoperFactory;
2727
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
2828
use InvalidArgumentException;
29+
use PhpParser\PhpVersion;
2930
use Symfony\Component\Console\Input\InputArgument;
3031
use Symfony\Component\Console\Input\InputOption;
3132
use Symfony\Component\Console\Output\OutputInterface;
@@ -49,6 +50,7 @@ final class InspectCommand implements Command, CommandAware
4950
private const PREFIX_OPT = 'prefix';
5051
private const CONFIG_FILE_OPT = 'config';
5152
private const NO_CONFIG_OPT = 'no-config';
53+
private const PHP_VERSION_OPT = 'php-version';
5254

5355
public function __construct(
5456
private readonly Filesystem $fileSystem,
@@ -94,6 +96,12 @@ public function getConfiguration(): CommandConfiguration
9496
InputOption::VALUE_NONE,
9597
'Do not look for a configuration file.',
9698
),
99+
new InputOption(
100+
self::PHP_VERSION_OPT,
101+
null,
102+
InputOption::VALUE_REQUIRED,
103+
'PHP version in which the PHP parser and printer will be configured, e.g. "7.2".',
104+
),
97105
],
98106
);
99107
}
@@ -108,6 +116,7 @@ public function execute(IO $io): int
108116
// working directory
109117
$cwd = getcwd();
110118

119+
$phpversion = self::getPhpVersion($io);
111120
$filePath = $this->getFilePath($io, $cwd);
112121
$config = $this->retrieveConfig($io, [$filePath], $cwd);
113122

@@ -120,7 +129,13 @@ public function execute(IO $io): int
120129
$symbolsRegistry = new SymbolsRegistry();
121130
$fileContents = $config->getFilesWithContents()[$filePath][1];
122131

123-
$scopedContents = $this->scopeFile($config, $symbolsRegistry, $filePath, $fileContents);
132+
$scopedContents = $this->scopeFile(
133+
$config,
134+
$symbolsRegistry,
135+
$phpversion,
136+
$filePath,
137+
$fileContents,
138+
);
124139

125140
$this->printScopedContents($io, $scopedContents, $symbolsRegistry);
126141

@@ -192,12 +207,14 @@ private function canonicalizePath(string $path, string $cwd): string
192207
private function scopeFile(
193208
Configuration $config,
194209
SymbolsRegistry $symbolsRegistry,
210+
?PhpVersion $phpversion,
195211
string $filePath,
196212
string $fileContents,
197213
): string {
198214
$scoper = $this->scoperFactory->createScoper(
199215
$config,
200216
$symbolsRegistry,
217+
$phpversion,
201218
);
202219

203220
return $scoper->scope(
@@ -249,4 +266,15 @@ private static function exportSymbolsRegistry(SymbolsRegistry $symbolsRegistry,
249266
true,
250267
);
251268
}
269+
270+
private static function getPhpVersion(IO $io): ?PhpVersion
271+
{
272+
$version = $io
273+
->getTypedOption(self::PHP_VERSION_OPT)
274+
->asNullableString();
275+
276+
return null === $version
277+
? $version
278+
: PhpVersion::fromString($version);
279+
}
252280
}

0 commit comments

Comments
 (0)