diff --git a/docs/limitations.md b/docs/limitations.md index 633b9a4f..0555fedc 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -9,7 +9,6 @@ - [Composer Autoloader](#composer-autoloader) - [Composer Plugins](#composer-plugins) - [PSR-0 Partial support](#psr-0-partial-support) -- [Files autoloading](#files-autoloading) - [Exposing/Excluding traits](#exposingexcluding-traits) - [Exposing/Excluding enums](#exposingexcluding-enums) - [Declaring a custom namespaced function `function_exists()`](#declaring-a-custom-namespaced-function-function_exists) @@ -238,19 +237,6 @@ transforming it to PSR-4, i.e. in the case above: If this works for the classes under `src/JsonMapper/`, it will not for `JsonMapper.php`. -### Files autoloading - -Currently, scoping autoloaded files, i.e. files registered to Composer via the -[`autoload.files`][autoload-files] setting only half work. Indeed, the scoping -itself works, but if your scoped code happen to try to load another Composer -based project with the same file, it will not. The problem identified is that -the Composer autoloader uses hash to know if a file has been loaded or not already. -Unfortunately, this hash is defined by the package and file name, which means -the scoped file and non-scoped file will have the same hash resulting in errors. - -This is a limitation that should be fixable, check [#298] for the progress. - - ### Exposing/Excluding traits There is currently no way to expose or exclude a trait. Since there is no diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9a763720..7e50016c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -22,8 +22,6 @@ parameters: path: 'src/PhpParser/NodeVisitor/Resolver/OriginalNameResolver.php' - message: '#NamespaceManipulator::getOriginalName\(\) should return#' path: 'src/PhpParser/NodeVisitor/NamespaceStmt/NamespaceManipulator.php' - - message: '#DummyScoperFactory extends @final#' - path: 'tests/Console/Command/DummyScoperFactory.php' - message: '#Anonymous function should return string but returns array#' path: 'tests/Console/Command/AddPrefixCommandIntegrationTest.php' - message: '#AddPrefixCommandIntegrationTest\:\:getNormalizeDisplay\(\) should return string but returns array#' @@ -63,3 +61,5 @@ parameters: # Fixed in https://github.com/nikic/PHP-Parser/pull/1003 - message: '#Standard constructor expects array#' path: 'src/Container.php' + - message: '#Standard constructor expects array#' + path: 'src/PhpParser/Printer/StandardPrinterFactory.php' diff --git a/src/Configuration/Configuration.php b/src/Configuration/Configuration.php index 57dfe3b7..f1973c81 100644 --- a/src/Configuration/Configuration.php +++ b/src/Configuration/Configuration.php @@ -14,11 +14,10 @@ namespace Humbug\PhpScoper\Configuration; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue; use Humbug\PhpScoper\Patcher\Patcher; -use InvalidArgumentException; use PhpParser\PhpVersion; use function Safe\preg_match; -use function sprintf; final class Configuration { @@ -39,6 +38,8 @@ final class Configuration * @param array $excludedFilesWithContents Array of tuple * with the first argument being the file path and * the second its contents + * + * @throws InvalidConfigurationValue */ public function __construct( private ?string $path, @@ -73,6 +74,8 @@ public function getOutputDir(): ?string /** * @param non-empty-string $prefix + * + * @throws InvalidConfigurationValue */ public function withPrefix(string $prefix): self { @@ -161,21 +164,11 @@ public function getPhpVersion(): ?PhpVersion private static function validatePrefix(string $prefix): void { if (1 !== preg_match(self::PREFIX_PATTERN, $prefix)) { - throw new InvalidArgumentException( - sprintf( - 'The prefix needs to be composed solely of letters, digits and backslashes (as namespace separators). Got "%s"', - $prefix, - ), - ); + throw InvalidConfigurationValue::forInvalidPrefixPattern($prefix); } if (preg_match('/\\\{2,}/', $prefix)) { - throw new InvalidArgumentException( - sprintf( - 'Invalid namespace separator sequence. Got "%s"', - $prefix, - ), - ); + throw InvalidConfigurationValue::forInvalidNamespaceSeparator($prefix); } } } diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index fa4216d4..3db58fcf 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -14,6 +14,9 @@ namespace Humbug\PhpScoper\Configuration; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfiguration; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationFile; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue; use Humbug\PhpScoper\Patcher\ComposerPatcher; use Humbug\PhpScoper\Patcher\Patcher; use Humbug\PhpScoper\Patcher\PatcherChain; @@ -21,7 +24,6 @@ use Humbug\PhpScoper\Patcher\SymfonyPatcher; use InvalidArgumentException; use PhpParser\PhpVersion; -use RuntimeException; use SplFileInfo; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; @@ -34,9 +36,7 @@ use function bin2hex; use function dirname; use function file_exists; -use function gettype; use function Humbug\PhpScoper\chain; -use function in_array; use function is_array; use function is_callable; use function is_dir; @@ -48,7 +48,6 @@ use function readlink as native_readlink; use function realpath; use function Safe\file_get_contents; -use function sprintf; use function trim; use const DIRECTORY_SEPARATOR; @@ -65,14 +64,12 @@ public function __construct( /** * @param non-empty-string|null $path Absolute canonical path to the configuration file. * @param list $paths List of absolute canonical paths to append besides the one configured + * + * @throws InvalidConfiguration */ public function create(?string $path = null, array $paths = []): Configuration { - if (null === $path) { - $config = []; - } else { - $config = $this->loadConfigFile($path); - } + $config = null === $path ? [] : $this->loadConfigFile($path); self::validateConfigKeys($config); @@ -135,24 +132,17 @@ public function createWithPrefix(Configuration $config, string $prefix): Configu return $config->withPrefix($prefix); } + /** + * @throws InvalidConfigurationValue + */ private function loadConfigFile(string $path): array { if (!$this->fileSystem->isAbsolutePath($path)) { - throw new InvalidArgumentException( - sprintf( - 'Expected the path of the configuration file to load to be an absolute path, got "%s" instead', - $path, - ), - ); + throw InvalidConfigurationFile::forNonAbsolutePath($path); } if (!file_exists($path)) { - throw new InvalidArgumentException( - sprintf( - 'Expected the path of the configuration file to exists but the file "%s" could not be found', - $path, - ), - ); + throw InvalidConfigurationFile::forFileNotFound($path); } $isADirectoryLink = is_link($path) @@ -160,23 +150,13 @@ private function loadConfigFile(string $path): array && is_file(native_readlink($path)); if (!$isADirectoryLink && !is_file($path)) { - throw new InvalidArgumentException( - sprintf( - 'Expected the path of the configuration file to be a file but "%s" appears to be a directory.', - $path, - ), - ); + throw InvalidConfigurationFile::forNotAFile($path); } $config = include $path; if (!is_array($config)) { - throw new InvalidArgumentException( - sprintf( - 'Expected configuration to be an array, found "%s" instead.', - gettype($config), - ), - ); + throw InvalidConfigurationFile::forInvalidValue($path); } return $config; @@ -185,26 +165,11 @@ private function loadConfigFile(string $path): array private static function validateConfigKeys(array $config): void { array_map( - static fn (string $key) => self::validateConfigKey($key), + ConfigurationKeys::assertIsValidKey(...), array_keys($config), ); } - // TODO: move to ConfigKeys - private static function validateConfigKey(string $key): void - { - if (in_array($key, ConfigurationKeys::KEYWORDS, true)) { - return; - } - - throw new InvalidArgumentException( - sprintf( - 'Invalid configuration key value "%s" found.', - $key, - ), - ); - } - /** * @return non-empty-string */ @@ -248,6 +213,8 @@ private static function retrieveOutputDir(array $config): ?string } /** + * @throws InvalidConfigurationValue + * * @return array<(callable(string,string,string): string)|Patcher> */ private static function retrievePatchers(array $config): array @@ -259,31 +226,21 @@ private static function retrievePatchers(array $config): array $patchers = $config[ConfigurationKeys::PATCHERS_KEYWORD]; if (!is_array($patchers)) { - throw new InvalidArgumentException( - sprintf( - 'Expected patchers to be an array of callables, found "%s" instead.', - gettype($patchers), - ), - ); + throw InvalidConfigurationValue::forInvalidPatchersType($patchers); } foreach ($patchers as $index => $patcher) { - if (is_callable($patcher)) { - continue; + if (!is_callable($patcher)) { + throw InvalidConfigurationValue::forInvalidPatcherType($index, $patcher); } - - throw new InvalidArgumentException( - sprintf( - 'Expected patchers to be an array of callables, the "%d" element is not.', - $index, - ), - ); } return $patchers; } /** + * @throws InvalidConfigurationValue + * * @return string[] Absolute paths */ private function retrieveExcludedFiles(string $dirPath, array $config): array @@ -295,22 +252,12 @@ private function retrieveExcludedFiles(string $dirPath, array $config): array $excludedFiles = $config[ConfigurationKeys::EXCLUDED_FILES_KEYWORD]; if (!is_array($excludedFiles)) { - throw new InvalidArgumentException( - sprintf( - 'Expected excluded files to be an array of strings, found "%s" instead.', - gettype($excludedFiles), - ), - ); + throw InvalidConfigurationValue::forInvalidExcludedFilesTypes($excludedFiles); } foreach ($excludedFiles as $index => $file) { if (!is_string($file)) { - throw new InvalidArgumentException( - sprintf( - 'Expected excluded files to be an array of string, the "%d" element is not.', - $index, - ), - ); + throw InvalidConfigurationValue::forInvalidExcludedFilePath($index, $excludedFiles); } if (!$this->fileSystem->isAbsolutePath($file)) { @@ -320,10 +267,14 @@ private function retrieveExcludedFiles(string $dirPath, array $config): array $excludedFiles[$index] = realpath($file); } + // We ignore files not found excluded file as we do not want to bail out just because a file we do not want to + // include does not exist. return array_filter($excludedFiles); } /** + * @throws InvalidConfigurationValue + * * @return Finder[] */ private static function retrieveFinders(array $config): array @@ -335,13 +286,7 @@ private static function retrieveFinders(array $config): array $finders = $config[ConfigurationKeys::FINDER_KEYWORD]; if (!is_array($finders)) { - throw new InvalidArgumentException( - sprintf( - 'Expected finders to be an array of "%s", found "%s" instead.', - Finder::class, - gettype($finders), - ), - ); + throw InvalidConfigurationValue::forInvalidFinderTypes($finders); } foreach ($finders as $index => $finder) { @@ -349,13 +294,7 @@ private static function retrieveFinders(array $config): array continue; } - throw new InvalidArgumentException( - sprintf( - 'Expected finders to be an array of "%s", the "%d" element is not.', - Finder::class, - $index, - ), - ); + throw InvalidConfigurationValue::forInvalidFinderType($index, $finder); } return $finders; @@ -364,6 +303,8 @@ private static function retrieveFinders(array $config): array /** * @param string[] $paths * + * @throws InvalidConfigurationValue + * * @return iterable */ private static function retrieveFilesFromPaths(array $paths): iterable @@ -377,12 +318,7 @@ private static function retrieveFilesFromPaths(array $paths): iterable foreach ($paths as $path) { if (!file_exists($path)) { - throw new RuntimeException( - sprintf( - 'Could not find the file "%s".', - $path, - ), - ); + throw InvalidConfigurationValue::forFileNotFound($path); } if (is_dir($path)) { @@ -408,6 +344,8 @@ private static function retrieveFilesFromPaths(array $paths): iterable /** * @param iterable $files * + * @throws InvalidConfigurationValue + * * @return array Array of tuple with the first argument being the file path and the second its contents */ private static function retrieveFilesWithContents(iterable $files): array @@ -420,21 +358,11 @@ private static function retrieveFilesWithContents(iterable $files): array : realpath($filePathOrFileInfo); if (!$filePath) { - throw new RuntimeException( - sprintf( - 'Could not find the file "%s".', - (string) $filePathOrFileInfo, - ), - ); + throw InvalidConfigurationValue::forFileNotFound((string) $filePathOrFileInfo); } if (!is_readable($filePath)) { - throw new RuntimeException( - sprintf( - 'Could not read the file "%s".', - $filePath, - ), - ); + throw InvalidConfigurationValue::forUnreadableFile($filePath); } $filesWithContents[$filePath] = [$filePath, file_get_contents($filePath)]; diff --git a/src/Configuration/ConfigurationKeys.php b/src/Configuration/ConfigurationKeys.php index 8a8c3316..ee604520 100644 --- a/src/Configuration/ConfigurationKeys.php +++ b/src/Configuration/ConfigurationKeys.php @@ -14,6 +14,7 @@ namespace Humbug\PhpScoper\Configuration; +use Humbug\PhpScoper\Configuration\Throwable\UnknownConfigurationKey; use Humbug\PhpScoper\NotInstantiable; final class ConfigurationKeys @@ -60,4 +61,19 @@ final class ConfigurationKeys self::FUNCTIONS_INTERNAL_SYMBOLS_KEYWORD, self::CONSTANTS_INTERNAL_SYMBOLS_KEYWORD, ]; + + /** + * @throws UnknownConfigurationKey + */ + public static function assertIsValidKey(string $key): void + { + if (!self::isValidateKey($key)) { + throw UnknownConfigurationKey::forKey($key); + } + } + + public static function isValidateKey(string $key): bool + { + return in_array($key, self::KEYWORDS, true); + } } diff --git a/src/Configuration/Throwable/InvalidConfiguration.php b/src/Configuration/Throwable/InvalidConfiguration.php new file mode 100644 index 00000000..24caa83f --- /dev/null +++ b/src/Configuration/Throwable/InvalidConfiguration.php @@ -0,0 +1,21 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Configuration\Throwable; + +use Throwable; + +interface InvalidConfiguration extends Throwable +{ +} diff --git a/src/Configuration/Throwable/InvalidConfigurationFile.php b/src/Configuration/Throwable/InvalidConfigurationFile.php new file mode 100644 index 00000000..531ce2ed --- /dev/null +++ b/src/Configuration/Throwable/InvalidConfigurationFile.php @@ -0,0 +1,61 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Configuration\Throwable; + +use UnexpectedValueException; +use function get_debug_type; + +final class InvalidConfigurationFile extends UnexpectedValueException implements InvalidConfiguration +{ + public static function forNonAbsolutePath(string $path): self + { + return new self( + sprintf( + 'Expected the path of the configuration file to load to be an absolute path, got "%s" instead', + $path, + ), + ); + } + + public static function forFileNotFound(string $path): self + { + return new self( + sprintf( + 'Expected the path of the configuration file to exists but the file "%s" could not be found', + $path, + ), + ); + } + + public static function forNotAFile(string $path): self + { + return new self( + sprintf( + 'Expected the path of the configuration file to be a file but "%s" appears to be a directory.', + $path, + ), + ); + } + + public static function forInvalidValue(mixed $config): self + { + return new self( + sprintf( + 'Expected configuration to be an array, found "%s" instead.', + get_debug_type($config), + ), + ); + } +} diff --git a/src/Configuration/Throwable/InvalidConfigurationValue.php b/src/Configuration/Throwable/InvalidConfigurationValue.php new file mode 100644 index 00000000..be21aba2 --- /dev/null +++ b/src/Configuration/Throwable/InvalidConfigurationValue.php @@ -0,0 +1,127 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Configuration\Throwable; + +use Symfony\Component\Finder\Finder; +use UnexpectedValueException; +use function gettype; + +final class InvalidConfigurationValue extends UnexpectedValueException implements InvalidConfiguration +{ + public static function forInvalidPatchersType(mixed $patchers): self + { + return new self( + sprintf( + 'Expected patchers to be an array of callables, found "%s" instead.', + gettype($patchers), + ), + ); + } + + public static function forInvalidPatcherType(int|string $index, mixed $patcher): self + { + return new self( + sprintf( + 'Expected patchers to be an array of callables, the "%s" element is not (found "%s" instead).', + $index, + gettype($patcher), + ), + ); + } + + public static function forInvalidExcludedFilesTypes(mixed $excludedFiles): self + { + return new self( + sprintf( + 'Expected excluded files to be an array of strings, found "%s" instead.', + gettype($excludedFiles), + ), + ); + } + + public static function forInvalidExcludedFilePath(int|string $index, mixed $excludedFile): self + { + return new self( + sprintf( + 'Expected excluded files to be an array of string, the "%d" element is not (found "%s" instead).', + $index, + gettype($excludedFile), + ), + ); + } + + public static function forInvalidFinderTypes(mixed $finders): self + { + return new self( + sprintf( + 'Expected finders to be an array of "%s", found "%s" instead.', + Finder::class, + gettype($finders), + ), + ); + } + + public static function forInvalidFinderType(int|string $index, mixed $finder): self + { + return new self( + sprintf( + 'Expected finders to be an array of "%s", the "%s" element is not (found "%s" instead).', + Finder::class, + $index, + gettype($finder), + ), + ); + } + + public static function forFileNotFound(string $path): self + { + return new self( + sprintf( + 'Could not find the file "%s".', + $path, + ), + ); + } + + public static function forUnreadableFile(string $path): self + { + return new self( + sprintf( + 'Could not read the file "%s".', + $path, + ), + ); + } + + public static function forInvalidPrefixPattern(string $prefix): self + { + return new self( + sprintf( + 'The prefix needs to be composed solely of letters, digits and backslashes (as namespace separators). Got "%s".', + $prefix, + ), + ); + } + + public static function forInvalidNamespaceSeparator(string $prefix): self + { + return new self( + sprintf( + 'Invalid namespace separator sequence. Got "%s".', + $prefix, + ), + ); + } +} diff --git a/src/Configuration/Throwable/UnknownConfigurationKey.php b/src/Configuration/Throwable/UnknownConfigurationKey.php new file mode 100644 index 00000000..d1196930 --- /dev/null +++ b/src/Configuration/Throwable/UnknownConfigurationKey.php @@ -0,0 +1,30 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Configuration\Throwable; + +use UnexpectedValueException; + +final class UnknownConfigurationKey extends UnexpectedValueException implements InvalidConfiguration +{ + public static function forKey(string $key): self + { + return new self( + sprintf( + 'Invalid configuration key value "%s" found.', + $key, + ), + ); + } +} diff --git a/src/Console/Command/AddPrefixCommand.php b/src/Console/Command/AddPrefixCommand.php index f041fa54..285f91e3 100644 --- a/src/Console/Command/AddPrefixCommand.php +++ b/src/Console/Command/AddPrefixCommand.php @@ -23,10 +23,11 @@ use Fidry\Console\IO; use Humbug\PhpScoper\Configuration\Configuration; use Humbug\PhpScoper\Configuration\ConfigurationFactory; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue; +use Humbug\PhpScoper\Configuration\Throwable\UnknownConfigurationKey; use Humbug\PhpScoper\Console\ConfigLoader; use Humbug\PhpScoper\Console\ConsoleScoper; use Humbug\PhpScoper\Container; -use Humbug\PhpScoper\Scoper\ScoperFactory; use InvalidArgumentException; use PhpParser\PhpVersion; use Symfony\Component\Console\Exception\RuntimeException; @@ -274,6 +275,9 @@ private static function canDeleteOutputDir(IO $io, string $outputDir): bool /** * @param list $paths + * + * @throws InvalidConfigurationValue + * @throws UnknownConfigurationKey */ private function retrieveConfig(IO $io, array $paths, string $cwd): Configuration { diff --git a/src/Console/Command/InspectCommand.php b/src/Console/Command/InspectCommand.php index 82d079d4..0735e786 100644 --- a/src/Console/Command/InspectCommand.php +++ b/src/Console/Command/InspectCommand.php @@ -23,7 +23,7 @@ use Humbug\PhpScoper\Configuration\Configuration; use Humbug\PhpScoper\Configuration\ConfigurationFactory; use Humbug\PhpScoper\Console\ConfigLoader; -use Humbug\PhpScoper\Scoper\ScoperFactory; +use Humbug\PhpScoper\Scoper\Factory\ScoperFactory; use Humbug\PhpScoper\Symbol\SymbolsRegistry; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; diff --git a/src/Console/ConfigLoader.php b/src/Console/ConfigLoader.php index acb7efd1..9db2daee 100644 --- a/src/Console/ConfigLoader.php +++ b/src/Console/ConfigLoader.php @@ -18,6 +18,8 @@ use Fidry\Console\IO; use Humbug\PhpScoper\Configuration\Configuration; use Humbug\PhpScoper\Configuration\ConfigurationFactory; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue; +use Humbug\PhpScoper\Configuration\Throwable\UnknownConfigurationKey; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; @@ -46,6 +48,9 @@ public function __construct( * @param non-empty-string|null $configFilePath Canonical absolute path * @param non-empty-string $defaultConfigFilePath * @param list $paths List of canonical absolute paths + * + * @throws InvalidConfigurationValue + * @throws UnknownConfigurationKey */ public function loadConfig( IO $io, @@ -96,6 +101,9 @@ public function loadConfig( /** * @param list $paths + * + * @throws InvalidConfigurationValue + * @throws UnknownConfigurationKey */ private function loadConfigWithoutConfigFile( IO $io, @@ -179,6 +187,9 @@ private static function logConfigFilePathFound(IO $io, ?string $configFilePath): /** * @param non-empty-string|null $configFilePath * @param list $paths + * + * @throws InvalidConfigurationValue + * @throws UnknownConfigurationKey */ private function loadConfiguration( ?string $configFilePath, diff --git a/src/Console/ConsoleScoper.php b/src/Console/ConsoleScoper.php index 78115b3f..30bb489c 100644 --- a/src/Console/ConsoleScoper.php +++ b/src/Console/ConsoleScoper.php @@ -19,9 +19,7 @@ use Humbug\PhpScoper\Autoload\ComposerFileHasher; use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator; use Humbug\PhpScoper\Configuration\Configuration; -use Humbug\PhpScoper\Container; use Humbug\PhpScoper\Scoper\Scoper; -use Humbug\PhpScoper\Scoper\ScoperFactory; use Humbug\PhpScoper\Symbol\SymbolsRegistry; use Humbug\PhpScoper\Throwable\Exception\ParsingException; use PhpParser\PhpVersion; diff --git a/src/Container.php b/src/Container.php index 61dac2aa..46f7553c 100644 --- a/src/Container.php +++ b/src/Container.php @@ -17,16 +17,17 @@ use Humbug\PhpScoper\Configuration\ConfigurationFactory; use Humbug\PhpScoper\Configuration\RegexChecker; use Humbug\PhpScoper\Configuration\SymbolsConfigurationFactory; +use Humbug\PhpScoper\PhpParser\Parser\ParserFactory; +use Humbug\PhpScoper\PhpParser\Parser\StandardParserFactory; use Humbug\PhpScoper\PhpParser\Printer\Printer; +use Humbug\PhpScoper\PhpParser\Printer\PrinterFactory; use Humbug\PhpScoper\PhpParser\Printer\StandardPrinter; -use Humbug\PhpScoper\Scoper\ScoperFactory; +use Humbug\PhpScoper\PhpParser\Printer\StandardPrinterFactory; +use Humbug\PhpScoper\Scoper\Factory\ScoperFactory; +use Humbug\PhpScoper\Scoper\Factory\StandardScoperFactory; use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; use Humbug\PhpScoper\Symbol\Reflector; -use PhpParser\Lexer; -use PhpParser\Lexer\Emulative; use PhpParser\Parser; -use PhpParser\Parser\Php7; -use PhpParser\Parser\Php8; use PhpParser\PhpVersion; use PhpParser\PrettyPrinter\Standard; use Symfony\Component\Filesystem\Filesystem; @@ -36,12 +37,14 @@ final class Container { private Filesystem $filesystem; private ConfigurationFactory $configFactory; + private ParserFactory $parserFactory; private Parser $parser; private ?PhpVersion $parserPhpVersion = null; private ?PhpVersion $printerPhpVersion = null; private Reflector $reflector; private ScoperFactory $scoperFactory; private EnrichedReflectorFactory $enrichedReflectorFactory; + private PrinterFactory $printerFactory; private Printer $printer; public function getFileSystem(): Filesystem @@ -67,24 +70,27 @@ public function getConfigurationFactory(): ConfigurationFactory return $this->configFactory; } - public function getScoperFactory(?PhpVersion $phpVersion = null): ScoperFactory + public function getScoperFactory(): ScoperFactory { if (!isset($this->scoperFactory)) { - $this->scoperFactory = new ScoperFactory( - $this->getParser($phpVersion), + $this->scoperFactory = new StandardScoperFactory( $this->getEnrichedReflectorFactory(), - $this->getPrinter(), + $this->getParserFactory(), + $this->getPrinterFactory(), ); } return $this->scoperFactory; } + /** + * @deprecated Use ::getParserFactory() instead. + */ public function getParser(?PhpVersion $phpVersion = null): Parser { if (!isset($this->parser)) { $this->parserPhpVersion = $phpVersion; - $this->parser = $this->createParser($phpVersion); + $this->parser = $this->getParserFactory()->createParser($phpVersion); } self::checkSamePhpVersion($this->parserPhpVersion, $phpVersion); @@ -92,14 +98,13 @@ public function getParser(?PhpVersion $phpVersion = null): Parser return $this->parser; } - private function createParser(?PhpVersion $phpVersion): Parser + public function getParserFactory(): ParserFactory { - $version = $phpVersion ?? PhpVersion::getHostVersion(); - $lexer = $version->isHostVersion() ? new Lexer() : new Emulative($version); + if (!isset($this->parserFactory)) { + $this->parserFactory = new StandardParserFactory(); + } - return $version->id >= 80_000 - ? new Php8($lexer, $version) - : new Php7($lexer, $version); + return $this->parserFactory; } public function getReflector(): Reflector @@ -122,6 +127,9 @@ public function getEnrichedReflectorFactory(): EnrichedReflectorFactory return $this->enrichedReflectorFactory; } + /** + * @deprecated use ::getPrinterFactory() instead. + */ public function getPrinter(?PhpVersion $phpVersion = null): Printer { if (!isset($this->printer)) { @@ -138,6 +146,15 @@ public function getPrinter(?PhpVersion $phpVersion = null): Printer return $this->printer; } + public function getPrinterFactory(): PrinterFactory + { + if (!isset($this->printerFactory)) { + $this->printerFactory = new StandardPrinterFactory(); + } + + return $this->printerFactory; + } + private static function checkSamePhpVersion( ?PhpVersion $versionUsed, ?PhpVersion $versionRequest, diff --git a/src/PhpParser/Parser/ParserFactory.php b/src/PhpParser/Parser/ParserFactory.php new file mode 100644 index 00000000..bc3f14ed --- /dev/null +++ b/src/PhpParser/Parser/ParserFactory.php @@ -0,0 +1,23 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Parser; + +use PhpParser\Parser; +use PhpParser\PhpVersion; + +interface ParserFactory +{ + public function createParser(?PhpVersion $phpVersion = null): Parser; +} diff --git a/src/PhpParser/Parser/StandardParserFactory.php b/src/PhpParser/Parser/StandardParserFactory.php new file mode 100644 index 00000000..b936dbec --- /dev/null +++ b/src/PhpParser/Parser/StandardParserFactory.php @@ -0,0 +1,38 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Parser; + +use PhpParser\Lexer; +use PhpParser\Lexer\Emulative; +use PhpParser\Parser; +use PhpParser\Parser\Php7; +use PhpParser\Parser\Php8; +use PhpParser\PhpVersion; + +final class StandardParserFactory implements ParserFactory +{ + public function createParser(?PhpVersion $phpVersion = null): Parser + { + $version = $phpVersion ?? PhpVersion::getHostVersion(); + + $lexer = $version->isHostVersion() + ? new Lexer() + : new Emulative($version); + + return $version->id >= 80_000 + ? new Php8($lexer, $version) + : new Php7($lexer, $version); + } +} diff --git a/src/PhpParser/Printer/PrinterFactory.php b/src/PhpParser/Printer/PrinterFactory.php new file mode 100644 index 00000000..ddfb45f9 --- /dev/null +++ b/src/PhpParser/Printer/PrinterFactory.php @@ -0,0 +1,22 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Printer; + +use PhpParser\PhpVersion; + +interface PrinterFactory +{ + public function createPrinter(?PhpVersion $phpVersion = null): Printer; +} diff --git a/src/PhpParser/Printer/StandardPrinterFactory.php b/src/PhpParser/Printer/StandardPrinterFactory.php new file mode 100644 index 00000000..78a612e1 --- /dev/null +++ b/src/PhpParser/Printer/StandardPrinterFactory.php @@ -0,0 +1,30 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Printer; + +use PhpParser\PhpVersion; +use PhpParser\PrettyPrinter\Standard; + +final class StandardPrinterFactory implements PrinterFactory +{ + public function createPrinter(?PhpVersion $phpVersion = null): Printer + { + return new StandardPrinter( + new Standard([ + 'phpVersion' => $phpVersion, + ]), + ); + } +} diff --git a/src/Scoper/Factory/ScoperFactory.php b/src/Scoper/Factory/ScoperFactory.php new file mode 100644 index 00000000..207a124b --- /dev/null +++ b/src/Scoper/Factory/ScoperFactory.php @@ -0,0 +1,29 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Scoper\Factory; + +use Humbug\PhpScoper\Configuration\Configuration; +use Humbug\PhpScoper\Scoper\Scoper; +use Humbug\PhpScoper\Symbol\SymbolsRegistry; +use PhpParser\PhpVersion; + +interface ScoperFactory +{ + public function createScoper( + Configuration $configuration, + SymbolsRegistry $symbolsRegistry, + ?PhpVersion $phpVersion = null, + ): Scoper; +} diff --git a/src/Scoper/Factory/StandardScoperFactory.php b/src/Scoper/Factory/StandardScoperFactory.php new file mode 100644 index 00000000..0d874556 --- /dev/null +++ b/src/Scoper/Factory/StandardScoperFactory.php @@ -0,0 +1,85 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Scoper\Factory; + +use Humbug\PhpScoper\Configuration\Configuration; +use Humbug\PhpScoper\PhpParser\Parser\ParserFactory; +use Humbug\PhpScoper\PhpParser\Printer\PrinterFactory; +use Humbug\PhpScoper\PhpParser\TraverserFactory; +use Humbug\PhpScoper\Scoper\Composer\AutoloadPrefixer; +use Humbug\PhpScoper\Scoper\Composer\InstalledPackagesScoper; +use Humbug\PhpScoper\Scoper\Composer\JsonFileScoper; +use Humbug\PhpScoper\Scoper\NullScoper; +use Humbug\PhpScoper\Scoper\PatchScoper; +use Humbug\PhpScoper\Scoper\PhpScoper; +use Humbug\PhpScoper\Scoper\Scoper; +use Humbug\PhpScoper\Scoper\SymfonyScoper; +use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; +use Humbug\PhpScoper\Symbol\SymbolsRegistry; +use PhpParser\PhpVersion; + +final readonly class StandardScoperFactory implements ScoperFactory +{ + public function __construct( + private EnrichedReflectorFactory $enrichedReflectorFactory, + private ParserFactory $parserFactory, + private PrinterFactory $printerFactory, + ) { + } + + public function createScoper( + Configuration $configuration, + SymbolsRegistry $symbolsRegistry, + ?PhpVersion $phpVersion = null, + ): Scoper { + $prefix = $configuration->getPrefix(); + $symbolsConfiguration = $configuration->getSymbolsConfiguration(); + $enrichedReflector = $this->enrichedReflectorFactory->create($symbolsConfiguration); + + $parser = $this->parserFactory->createParser($phpVersion); + $printer = $this->printerFactory->createPrinter($phpVersion); + + $autoloadPrefixer = new AutoloadPrefixer( + $prefix, + $enrichedReflector, + ); + + return new PatchScoper( + new PhpScoper( + $parser, + new JsonFileScoper( + new InstalledPackagesScoper( + new SymfonyScoper( + new NullScoper(), + $prefix, + $enrichedReflector, + $symbolsRegistry, + ), + $autoloadPrefixer, + ), + $autoloadPrefixer, + ), + new TraverserFactory( + $enrichedReflector, + $prefix, + $symbolsRegistry, + ), + $printer, + ), + $prefix, + $configuration->getPatcher(), + ); + } +} diff --git a/src/Scoper/ScoperFactory.php b/src/Scoper/ScoperFactory.php index c88426b5..7212efa6 100644 --- a/src/Scoper/ScoperFactory.php +++ b/src/Scoper/ScoperFactory.php @@ -23,10 +23,11 @@ use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; use Humbug\PhpScoper\Symbol\SymbolsRegistry; use PhpParser\Parser; -use PhpParser\PhpVersion; /** * @final + * + * @deprecated Deprecated in favour of \Humbug\PhpScoper\Scoper\Factory\ScoperFactory */ class ScoperFactory { diff --git a/tests/Configuration/ConfigurationFactoryTest.php b/tests/Configuration/ConfigurationFactoryTest.php index 3722ee63..2902dafc 100644 --- a/tests/Configuration/ConfigurationFactoryTest.php +++ b/tests/Configuration/ConfigurationFactoryTest.php @@ -15,6 +15,7 @@ namespace Humbug\PhpScoper\Configuration; use Fidry\FileSystem\FS; +use Humbug\PhpScoper\Configuration\Throwable\UnknownConfigurationKey; use Humbug\PhpScoper\Container; use Humbug\PhpScoper\FileSystemTestCase; use Humbug\PhpScoper\Patcher\ComposerPatcher; @@ -23,7 +24,6 @@ use Humbug\PhpScoper\Patcher\SymfonyPatcher; use Humbug\PhpScoper\Symbol\NamespaceRegistry; use Humbug\PhpScoper\Symbol\SymbolRegistry; -use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use function array_keys; @@ -80,7 +80,7 @@ public function test_it_cannot_create_a_configuration_with_an_invalid_key(): voi PHP, ); - $this->expectException(InvalidArgumentException::class); + $this->expectException(UnknownConfigurationKey::class); $this->expectExceptionMessage('Invalid configuration key value "unknown key" found.'); $this->createConfigFromStandardFile(); diff --git a/tests/Configuration/ConfigurationTest.php b/tests/Configuration/ConfigurationTest.php index 1aa01665..2c19ac6d 100644 --- a/tests/Configuration/ConfigurationTest.php +++ b/tests/Configuration/ConfigurationTest.php @@ -14,10 +14,10 @@ namespace Humbug\PhpScoper\Configuration; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue; use Humbug\PhpScoper\Patcher\FakePatcher; use Humbug\PhpScoper\Patcher\Patcher; use Humbug\PhpScoper\Patcher\PatcherChain; -use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -36,7 +36,7 @@ public function test_it_validates_the_prefix( string $prefix, string $expectedExceptionMessage ): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(InvalidConfigurationValue::class); $this->expectExceptionMessage($expectedExceptionMessage); new Configuration( @@ -107,12 +107,12 @@ public static function prefixProvider(): iterable { yield [ ';', - 'The prefix needs to be composed solely of letters, digits and backslashes (as namespace separators). Got ";"', + 'The prefix needs to be composed solely of letters, digits and backslashes (as namespace separators). Got ";".', ]; yield [ 'App\\\\Foo', - 'Invalid namespace separator sequence. Got "App\\\\Foo"', + 'Invalid namespace separator sequence. Got "App\\\\Foo".', ]; } diff --git a/tests/Console/Command/AddPrefixCommandTest.php b/tests/Console/Command/AddPrefixCommandTest.php index 5be581ef..036d96b5 100644 --- a/tests/Console/Command/AddPrefixCommandTest.php +++ b/tests/Console/Command/AddPrefixCommandTest.php @@ -20,6 +20,7 @@ use Humbug\PhpScoper\Configuration\ConfigurationFactory; use Humbug\PhpScoper\Configuration\RegexChecker; use Humbug\PhpScoper\Configuration\SymbolsConfigurationFactory; +use Humbug\PhpScoper\Configuration\Throwable\InvalidConfigurationValue; use Humbug\PhpScoper\Console\Application; use Humbug\PhpScoper\Console\AppTesterAbilities; use Humbug\PhpScoper\Console\AppTesterTestCase; @@ -27,12 +28,8 @@ use Humbug\PhpScoper\Console\ConsoleScoper; use Humbug\PhpScoper\Container; use Humbug\PhpScoper\FileSystemTestCase; -use Humbug\PhpScoper\PhpParser\FakeParser; -use Humbug\PhpScoper\PhpParser\FakePrinter; +use Humbug\PhpScoper\Scoper\Factory\DummyScoperFactory; use Humbug\PhpScoper\Scoper\Scoper; -use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; -use Humbug\PhpScoper\Symbol\Reflector; -use InvalidArgumentException; use PhpParser\Error as PhpParserError; use PHPUnit\Framework\Attributes\CoversClass; use Prophecy\Argument; @@ -540,9 +537,9 @@ public function test_throws_an_error_if_patch_file_returns_an_array_with_invalid $this->appTester->run($input); self::fail('Expected exception to be thrown.'); - } catch (InvalidArgumentException $exception) { + } catch (InvalidConfigurationValue $exception) { self::assertSame( - 'Expected patchers to be an array of callables, the "0" element is not.', + 'Expected patchers to be an array of callables, the "0" element is not (found "string" instead).', $exception->getMessage(), ); } @@ -680,14 +677,7 @@ private function createAppTester(): ApplicationTester new SymfonyCommand( new AddPrefixCommand( $fileSystem, - new DummyScoperFactory( - new FakeParser(), - new EnrichedReflectorFactory( - Reflector::createEmpty(), - ), - new FakePrinter(), - $scoper, - ), + new DummyScoperFactory($scoper), $innerApp, new ConfigurationFactory( $fileSystem, diff --git a/tests/Console/Command/AppIntegrationTest.php b/tests/Console/Command/AppIntegrationTest.php index 40345716..d0223886 100644 --- a/tests/Console/Command/AppIntegrationTest.php +++ b/tests/Console/Command/AppIntegrationTest.php @@ -24,11 +24,8 @@ use Humbug\PhpScoper\Console\AppTesterTestCase; use Humbug\PhpScoper\Container; use Humbug\PhpScoper\FileSystemTestCase; -use Humbug\PhpScoper\PhpParser\FakeParser; -use Humbug\PhpScoper\PhpParser\FakePrinter; +use Humbug\PhpScoper\Scoper\Factory\DummyScoperFactory; use Humbug\PhpScoper\Scoper\Scoper; -use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; -use Humbug\PhpScoper\Symbol\Reflector; use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\Group; use Prophecy\Argument; @@ -158,12 +155,7 @@ private function createAppTester(): ApplicationTester new SymfonyCommand( new AddPrefixCommand( $fileSystem, - new DummyScoperFactory( - new FakeParser(), - new EnrichedReflectorFactory(Reflector::createEmpty()), - new FakePrinter(), - $scoper, - ), + new DummyScoperFactory($scoper), $innerApp, new ConfigurationFactory( $fileSystem, diff --git a/tests/Console/Command/DummyScoperFactory.php b/tests/Console/Command/DummyScoperFactory.php deleted file mode 100644 index eca95df7..00000000 --- a/tests/Console/Command/DummyScoperFactory.php +++ /dev/null @@ -1,44 +0,0 @@ -, - * Pádraic Brady - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Humbug\PhpScoper\Console\Command; - -use Humbug\PhpScoper\Configuration\Configuration; -use Humbug\PhpScoper\PhpParser\Printer\Printer; -use Humbug\PhpScoper\Scoper\Scoper; -use Humbug\PhpScoper\Scoper\ScoperFactory; -use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; -use Humbug\PhpScoper\Symbol\SymbolsRegistry; -use PhpParser\Parser; - -final class DummyScoperFactory extends ScoperFactory -{ - public function __construct( - Parser $parser, - EnrichedReflectorFactory $enrichedReflectorFactory, - Printer $printer, - private readonly Scoper $scoper - ) { - parent::__construct( - $parser, - $enrichedReflectorFactory, - $printer, - ); - } - - public function createScoper(Configuration $configuration, SymbolsRegistry $symbolsRegistry): Scoper - { - return $this->scoper; - } -} diff --git a/tests/PhpParser/Parser/DummyParserFactory.php b/tests/PhpParser/Parser/DummyParserFactory.php new file mode 100644 index 00000000..6696b30a --- /dev/null +++ b/tests/PhpParser/Parser/DummyParserFactory.php @@ -0,0 +1,31 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Parser; + +use PhpParser\Parser; +use PhpParser\PhpVersion; + +final readonly class DummyParserFactory implements ParserFactory +{ + public function __construct( + private Parser $parser, + ) { + } + + public function createParser(?PhpVersion $phpVersion = null): Parser + { + return $this->parser; + } +} diff --git a/tests/PhpParser/Printer/DummyPrinterFactory.php b/tests/PhpParser/Printer/DummyPrinterFactory.php new file mode 100644 index 00000000..95b88947 --- /dev/null +++ b/tests/PhpParser/Printer/DummyPrinterFactory.php @@ -0,0 +1,30 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\PhpParser\Printer; + +use PhpParser\PhpVersion; + +final readonly class DummyPrinterFactory implements PrinterFactory +{ + public function __construct( + private Printer $printer, + ) { + } + + public function createPrinter(?PhpVersion $phpVersion = null): Printer + { + return $this->printer; + } +} diff --git a/tests/Scoper/Factory/DummyScoperFactory.php b/tests/Scoper/Factory/DummyScoperFactory.php new file mode 100644 index 00000000..8c69206c --- /dev/null +++ b/tests/Scoper/Factory/DummyScoperFactory.php @@ -0,0 +1,36 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Scoper\Factory; + +use Humbug\PhpScoper\Configuration\Configuration; +use Humbug\PhpScoper\Scoper\Scoper; +use Humbug\PhpScoper\Symbol\SymbolsRegistry; +use PhpParser\PhpVersion; + +final readonly class DummyScoperFactory implements ScoperFactory +{ + public function __construct( + private Scoper $scoper, + ) { + } + + public function createScoper( + Configuration $configuration, + SymbolsRegistry $symbolsRegistry, + ?PhpVersion $phpVersion = null, + ): Scoper { + return $this->scoper; + } +} diff --git a/tests/Scoper/ScoperFactoryTest.php b/tests/Scoper/Factory/StandardScoperFactoryTest.php similarity index 76% rename from tests/Scoper/ScoperFactoryTest.php rename to tests/Scoper/Factory/StandardScoperFactoryTest.php index 060a022e..eb1fdf67 100644 --- a/tests/Scoper/ScoperFactoryTest.php +++ b/tests/Scoper/Factory/StandardScoperFactoryTest.php @@ -12,13 +12,15 @@ * file that was distributed with this source code. */ -namespace Humbug\PhpScoper\Scoper; +namespace Humbug\PhpScoper\Scoper\Factory; use Humbug\PhpScoper\Configuration\Configuration; use Humbug\PhpScoper\Configuration\SymbolsConfiguration; use Humbug\PhpScoper\Patcher\FakePatcher; use Humbug\PhpScoper\PhpParser\FakeParser; use Humbug\PhpScoper\PhpParser\FakePrinter; +use Humbug\PhpScoper\PhpParser\Parser\DummyParserFactory; +use Humbug\PhpScoper\PhpParser\Printer\DummyPrinterFactory; use Humbug\PhpScoper\Symbol\EnrichedReflectorFactory; use Humbug\PhpScoper\Symbol\Reflector; use Humbug\PhpScoper\Symbol\SymbolsRegistry; @@ -28,17 +30,17 @@ /** * @internal */ -#[CoversClass(ScoperFactory::class)] -final class ScoperFactoryTest extends TestCase +#[CoversClass(StandardScoperFactory::class)] +final class StandardScoperFactoryTest extends TestCase { public function test_it_can_create_a_scoper(): void { - $factory = new ScoperFactory( - new FakeParser(), + $factory = new StandardScoperFactory( new EnrichedReflectorFactory( Reflector::createEmpty(), ), - new FakePrinter(), + new DummyParserFactory(new FakeParser()), + new DummyPrinterFactory(new FakePrinter()), ); $factory->createScoper( diff --git a/tests/functions.php b/tests/functions.php index 94e4fec2..3367aee4 100644 --- a/tests/functions.php +++ b/tests/functions.php @@ -15,11 +15,12 @@ namespace Humbug\PhpScoper; use PhpParser\Parser; +use PhpParser\PhpVersion; /** * @private */ -function create_parser(): Parser +function create_parser(?PhpVersion $phpVersion = null): Parser { - return (new Container())->getParser(); + return (new Container())->getParserFactory()->createParser($phpVersion); }