Skip to content

Commit

Permalink
feat: Allow to configure the parser PHP version (#1044)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
theofidry authored Jun 16, 2024
1 parent 5478f21 commit 226ba7d
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver
- [Usage](#usage)
- [Configuration](docs/configuration.md#configuration)
- [Prefix](docs/configuration.md#prefix)
- [PHP Version](docs/configuration.md#php-version)
- [Output directory](docs/configuration.md#output-directory)
- [Finders and paths](docs/configuration.md#finders-and-paths)
- [Patchers](docs/configuration.md#patchers)
Expand Down
12 changes: 12 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Configuration

- [Prefix](#prefix)
- [PHP-Version](#php-version)
- [Output directory](#output-directory)
- [Finders and paths](#finders-and-paths)
- [Patchers](#patchers)
Expand Down Expand Up @@ -28,6 +29,7 @@ use Isolated\Symfony\Component\Finder\Finder;

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


### PHP Version

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 but may affect what code can be parsed and how the code will be printed.

If `null` or `''` (empty string) is given, then 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.


### Output directory

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

[box]: https://github.com/box-project/box
[php-scoper-integration]: https://github.com/humbug/box#isolating-the-phar
[PHP-Parser]: https://github.com/nikic/PHP-Parser
[phpstorm-stubs]: https://github.com/JetBrains/phpstorm-stubs
[symfony_finder]: https://symfony.com/doc/current/components/finder.html
10 changes: 10 additions & 0 deletions src/Configuration/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue;
use Humbug\PhpScoper\Patcher\Patcher;
use PhpParser\PhpVersion;

final class Configuration
{
Expand All @@ -38,6 +39,7 @@ public function __construct(
private ?string $path,
private ?string $outputDir,
string|Prefix $prefix,
private ?PhpVersion $phpVersion,
private array $filesWithContents,
private array $excludedFilesWithContents,
private Patcher $patcher,
Expand Down Expand Up @@ -75,6 +77,7 @@ public function withPrefix(string $prefix): self
$this->path,
$this->outputDir,
$prefix,
$this->phpVersion,
$this->filesWithContents,
$this->excludedFilesWithContents,
$this->patcher,
Expand All @@ -99,6 +102,7 @@ public function withFilesWithContents(array $filesWithContents): self
$this->path,
$this->outputDir,
$this->prefix,
$this->phpVersion,
$filesWithContents,
$this->excludedFilesWithContents,
$this->patcher,
Expand Down Expand Up @@ -128,6 +132,7 @@ public function withPatcher(Patcher $patcher): self
$this->path,
$this->outputDir,
$this->prefix,
$this->phpVersion,
$this->filesWithContents,
$this->excludedFilesWithContents,
$patcher,
Expand All @@ -144,4 +149,9 @@ public function getSymbolsConfiguration(): SymbolsConfiguration
{
return $this->symbolsConfiguration;
}

public function getPhpVersion(): ?PhpVersion
{
return $this->phpVersion;
}
}
25 changes: 25 additions & 0 deletions src/Configuration/ConfigurationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace Humbug\PhpScoper\Configuration;

use Exception;
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfiguration;
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationFile;
use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue;
Expand All @@ -22,6 +23,7 @@
use Humbug\PhpScoper\Patcher\PatcherChain;
use Humbug\PhpScoper\Patcher\SymfonyParentTraitPatcher;
use Humbug\PhpScoper\Patcher\SymfonyPatcher;
use PhpParser\PhpVersion;
use SplFileInfo;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
Expand Down Expand Up @@ -97,6 +99,7 @@ public function create(?string $path = null, array $paths = []): Configuration
$path,
$outputDir,
$prefix,
self::retrievePhpVersion($config),
$filesWithContents,
self::retrieveFilesWithContents($excludedFiles),
new PatcherChain($patchers),
Expand Down Expand Up @@ -178,6 +181,28 @@ private static function retrievePrefix(array $config): string
return '' === $prefix ? self::generateRandomPrefix() : $prefix;
}

/**
* @throws InvalidConfigurationValue
*/
private static function retrievePhpVersion(array $config): ?PhpVersion
{
$stringVersion = $config[ConfigurationKeys::PHP_VERSION_KEYWORD] ?? null;

if (null === $stringVersion || '' === $stringVersion) {
return null;
}

if (!is_string($stringVersion)) {
throw InvalidConfigurationValue::forInvalidPhpVersionType($stringVersion);
}

try {
return PhpVersion::fromString($stringVersion);
} catch (Exception $exception) {
throw InvalidConfigurationValue::forInvalidPhpVersion($stringVersion, $exception);
}
}

/**
* @return non-empty-string|null
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration/ConfigurationKeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ final class ConfigurationKeys
use NotInstantiable;

public const PREFIX_KEYWORD = 'prefix';
public const PHP_VERSION_KEYWORD = 'php-version';
public const OUTPUT_DIR_KEYWORD = 'output-dir';
public const EXCLUDED_FILES_KEYWORD = 'exclude-files';
public const FINDER_KEYWORD = 'finders';
Expand All @@ -43,6 +44,7 @@ final class ConfigurationKeys

public const KEYWORDS = [
self::PREFIX_KEYWORD,
self::PHP_VERSION_KEYWORD,
self::OUTPUT_DIR_KEYWORD,
self::EXCLUDED_FILES_KEYWORD,
self::FINDER_KEYWORD,
Expand Down
23 changes: 23 additions & 0 deletions src/Configuration/Throwable/InvalidConfigurationValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@

namespace Humbug\PhpScoper\Configuration\Throwable;

use Exception;
use Symfony\Component\Finder\Finder;
use UnexpectedValueException;
use function gettype;
use function sprintf;

final class InvalidConfigurationValue extends UnexpectedValueException implements InvalidConfiguration
{
Expand Down Expand Up @@ -115,6 +117,27 @@ public static function forInvalidPrefixPattern(string $prefix): self
);
}

public static function forInvalidPhpVersionType(mixed $phpVersion): self
{
return new self(
sprintf(
'Expected the PHP version to be a string, got "%s" instead.',
gettype($phpVersion),
),
);
}

public static function forInvalidPhpVersion(string $stringVersion, Exception $previous): self
{
return new self(
sprintf(
'Expected the PHP version to of the format "<major>.<minor>", e.g. "7.2", got "%s".',
$stringVersion,
),
previous: $previous,
);
}

public static function forInvalidNamespaceSeparator(string $prefix): self
{
return new self(
Expand Down
23 changes: 22 additions & 1 deletion src/Console/Command/AddPrefixCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Humbug\PhpScoper\Console\ConsoleScoper;
use Humbug\PhpScoper\Scoper\Factory\ScoperFactory;
use InvalidArgumentException;
use PhpParser\PhpVersion;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
Expand Down Expand Up @@ -56,6 +57,7 @@ final class AddPrefixCommand implements Command, CommandAware
private const CONTINUE_ON_FAILURE_OPT = 'continue-on-failure';
private const CONFIG_FILE_OPT = 'config';
private const NO_CONFIG_OPT = 'no-config';
private const PHP_VERSION_OPT = 'php-version';

private const DEFAULT_OUTPUT_DIR = 'build';

Expand Down Expand Up @@ -96,7 +98,7 @@ public function getConfiguration(): CommandConfiguration
'o',
InputOption::VALUE_REQUIRED,
'The output directory in which the prefixed code will be dumped.',
''
'',
),
new InputOption(
self::FORCE_OPT,
Expand Down Expand Up @@ -131,6 +133,12 @@ public function getConfiguration(): CommandConfiguration
InputOption::VALUE_NONE,
'Do not look for a configuration file.',
),
new InputOption(
self::PHP_VERSION_OPT,
null,
InputOption::VALUE_REQUIRED,
'PHP version in which the PHP parser and printer will be configured, e.g. "7.2".',
),
],
);
}
Expand All @@ -147,6 +155,7 @@ public function execute(IO $io): int

$paths = $this->getPathArguments($io, $cwd);
$config = $this->retrieveConfig($io, $paths, $cwd);
$phpVersion = self::getPhpVersion($io);

$outputDir = $this->canonicalizePath(
$this->getOutputDir($io, $config),
Expand All @@ -157,6 +166,7 @@ public function execute(IO $io): int
$this->getScoper()->scope(
$io,
$config,
$phpVersion,
$paths,
$outputDir,
self::getStopOnFailure($io),
Expand All @@ -165,6 +175,17 @@ public function execute(IO $io): int
return ExitCode::SUCCESS;
}

private static function getPhpVersion(IO $io): ?PhpVersion
{
$version = $io
->getTypedOption(self::PHP_VERSION_OPT)
->asNullableString();

return null === $version
? $version
: PhpVersion::fromString($version);
}

private static function getStopOnFailure(IO $io): bool
{
$stopOnFailure = $io->getTypedOption(self::STOP_ON_FAILURE_OPT)->asBoolean();
Expand Down
30 changes: 29 additions & 1 deletion src/Console/Command/InspectCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Humbug\PhpScoper\Scoper\Factory\ScoperFactory;
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
use InvalidArgumentException;
use PhpParser\PhpVersion;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -49,6 +50,7 @@ final class InspectCommand implements Command, CommandAware
private const PREFIX_OPT = 'prefix';
private const CONFIG_FILE_OPT = 'config';
private const NO_CONFIG_OPT = 'no-config';
private const PHP_VERSION_OPT = 'php-version';

public function __construct(
private readonly Filesystem $fileSystem,
Expand Down Expand Up @@ -94,6 +96,12 @@ public function getConfiguration(): CommandConfiguration
InputOption::VALUE_NONE,
'Do not look for a configuration file.',
),
new InputOption(
self::PHP_VERSION_OPT,
null,
InputOption::VALUE_REQUIRED,
'PHP version in which the PHP parser and printer will be configured, e.g. "7.2".',
),
],
);
}
Expand All @@ -108,6 +116,7 @@ public function execute(IO $io): int
// working directory
$cwd = getcwd();

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

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

$scopedContents = $this->scopeFile($config, $symbolsRegistry, $filePath, $fileContents);
$scopedContents = $this->scopeFile(
$config,
$symbolsRegistry,
$phpversion,
$filePath,
$fileContents,
);

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

Expand Down Expand Up @@ -192,12 +207,14 @@ private function canonicalizePath(string $path, string $cwd): string
private function scopeFile(
Configuration $config,
SymbolsRegistry $symbolsRegistry,
?PhpVersion $phpversion,
string $filePath,
string $fileContents,
): string {
$scoper = $this->scoperFactory->createScoper(
$config,
$symbolsRegistry,
$phpversion,
);

return $scoper->scope(
Expand Down Expand Up @@ -249,4 +266,15 @@ private static function exportSymbolsRegistry(SymbolsRegistry $symbolsRegistry,
true,
);
}

private static function getPhpVersion(IO $io): ?PhpVersion
{
$version = $io
->getTypedOption(self::PHP_VERSION_OPT)
->asNullableString();

return null === $version
? $version
: PhpVersion::fromString($version);
}
}
Loading

0 comments on commit 226ba7d

Please sign in to comment.