├── .github ├── FUNDING.yml └── workflows │ └── code_analysis.yaml ├── composer.json ├── config └── config.php ├── phpstan-baseline.neon └── src ├── Composer └── VendorDirProvider.php ├── Console ├── Command │ └── AbstractSymplifyCommand.php ├── Formatter │ └── ColorConsoleDiffFormatter.php ├── Input │ └── StaticInputDetector.php ├── Output │ └── ConsoleDiffer.php └── Style │ └── SymfonyStyleFactory.php ├── DependencyInjection ├── CompilerPass │ └── AutowireInterfacesCompilerPass.php └── FileLoader │ └── ParameterMergingPhpFileLoader.php ├── Diff ├── DifferFactory.php └── Output │ └── CompleteUnifiedDiffOutputBuilderFactory.php ├── Exception ├── HttpKernel │ └── MissingInterfaceException.php ├── InvalidPrivatePropertyTypeException.php ├── MissingPrivatePropertyException.php └── MissingServiceException.php ├── Parameter └── ParameterProvider.php ├── Php └── TypeChecker.php ├── Reflection ├── ClassLikeExistenceChecker.php ├── PrivatesAccessor.php └── PrivatesCaller.php ├── Strings ├── StringFormatConverter.php └── StringFormatConverter.phpqIy1Ap ├── Testing └── AbstractKernelTestCase.php ├── ValueObject ├── ConsoleColorDiffConfig.php ├── MethodName.php └── Option.php └── Yaml └── ParametersMerger.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: tomasvotruba 3 | custom: https://www.paypal.me/rectorphp 4 | -------------------------------------------------------------------------------- /.github/workflows/code_analysis.yaml: -------------------------------------------------------------------------------- 1 | name: Code Analysis 2 | 3 | on: 4 | pull_request: null 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | # see https://github.com/composer/composer/issues/9368#issuecomment-718112361 11 | COMPOSER_ROOT_VERSION: "dev-main" 12 | 13 | jobs: 14 | code_analysis: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | actions: 19 | - 20 | name: 'PHPStan' 21 | run: composer phpstan --ansi 22 | 23 | - 24 | name: 'Composer Validate' 25 | run: composer validate --ansi 26 | 27 | - 28 | name: 'Rector' 29 | run: composer rector --ansi 30 | 31 | - 32 | name: 'Coding Standard' 33 | run: composer fix-cs --ansi 34 | 35 | - 36 | name: 'Tests' 37 | run: vendor/bin/phpunit 38 | 39 | - 40 | name: 'PHP Linter' 41 | run: vendor/bin/parallel-lint src tests 42 | 43 | - 44 | name: 'Check Commented Code' 45 | run: vendor/bin/easy-ci check-commented-code src tests --ansi 46 | 47 | - 48 | name: 'Check Active Classes' 49 | run: vendor/bin/easy-ci check-active-class src --ansi 50 | 51 | name: ${{ matrix.actions.name }} 52 | runs-on: ubuntu-latest 53 | 54 | steps: 55 | - uses: actions/checkout@v3 56 | # see https://github.com/shivammathur/setup-php 57 | - uses: shivammathur/setup-php@v2 58 | with: 59 | php-version: 8.1 60 | coverage: none 61 | 62 | # composer install cache - https://github.com/ramsey/composer-install 63 | - uses: "ramsey/composer-install@v2" 64 | 65 | - run: ${{ matrix.actions.run }} 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symplify/package-builder", 3 | "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", 4 | "license": "MIT", 5 | "require": { 6 | "php": ">=8.1", 7 | "nette/utils": "^3.2 || ^4.0", 8 | "sebastian/diff": "^5.0", 9 | "symfony/config": "^6.2 || ^7.0", 10 | "symfony/console": "^6.2 || ^7.0", 11 | "symfony/dependency-injection": "^6.2 || ^7.0", 12 | "symfony/http-kernel": "^6.2 || ^7.0", 13 | "symfony/finder": "^6.2 || ^7.0" 14 | }, 15 | "require-dev": { 16 | "php-parallel-lint/php-parallel-lint": "^1.3", 17 | "phpstan/extension-installer": "^1.2", 18 | "phpunit/phpunit": "^10.0", 19 | "rector/rector": "^0.15.10", 20 | "symplify/easy-ci": "^11.2.0", 21 | "symplify/easy-coding-standard": "^11.2", 22 | "symplify/phpstan-extensions": "^11.1", 23 | "symplify/phpstan-rules": "^11.2", 24 | "symplify/symplify-kernel": "^11.1", 25 | "tomasvotruba/unused-public": "^0.0.34" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Symplify\\PackageBuilder\\": "src" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Symplify\\PackageBuilder\\Tests\\": "tests" 35 | } 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-main": "11.2-dev" 40 | } 41 | }, 42 | "scripts": { 43 | "check-cs": "vendor/bin/ecs check --ansi", 44 | "fix-cs": "vendor/bin/ecs check --fix --ansi", 45 | "phpstan": "vendor/bin/phpstan analyse --ansi --error-format symplify", 46 | "rector": "vendor/bin/rector process --dry-run --ansi" 47 | }, 48 | "minimum-stability": "dev", 49 | "prefer-stable": true, 50 | "config": { 51 | "sort-packages": true, 52 | "platform-check": false, 53 | "allow-plugins": { 54 | "cweagans/composer-patches": true, 55 | "phpstan/extension-installer": true 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | services(); 15 | 16 | $services->defaults() 17 | ->public() 18 | ->autowire(); 19 | 20 | $services->set(ColorConsoleDiffFormatter::class); 21 | 22 | $services->set(ConsoleDiffer::class); 23 | 24 | $services->set(DifferFactory::class); 25 | $services->set(Differ::class) 26 | ->factory([service(DifferFactory::class), 'create']); 27 | 28 | $services->set(PrivatesAccessor::class); 29 | }; 30 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" 5 | count: 1 6 | path: src/Composer/VendorDirProvider.php 7 | 8 | - 9 | message: "#^Call to an undefined method Symfony\\\\Component\\\\DependencyInjection\\\\ContainerInterface\\:\\:getParameterBag\\(\\)\\.$#" 10 | count: 1 11 | path: src/Parameter/ParameterProvider.php 12 | 13 | - 14 | message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" 15 | count: 1 16 | path: src/Reflection/ClassLikeExistenceChecker.php 17 | 18 | - 19 | message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" 20 | count: 2 21 | path: src/Reflection/PrivatesCaller.php 22 | 23 | - 24 | message: "#^Call to method setConfigs\\(\\) on an unknown class Symplify\\\\PackageBuilder\\\\Contract\\\\HttpKernel\\\\ExtraConfigAwareKernelInterface\\.$#" 25 | count: 1 26 | path: src/Testing/AbstractKernelTestCase.php 27 | 28 | - 29 | message: "#^Class Symplify\\\\PackageBuilder\\\\Contract\\\\HttpKernel\\\\ExtraConfigAwareKernelInterface not found\\.$#" 30 | count: 2 31 | path: src/Testing/AbstractKernelTestCase.php 32 | 33 | - 34 | message: "#^PHPDoc tag @var for variable \\$kernel contains unknown class Symplify\\\\PackageBuilder\\\\Contract\\\\HttpKernel\\\\ExtraConfigAwareKernelInterface\\.$#" 35 | count: 1 36 | path: src/Testing/AbstractKernelTestCase.php 37 | 38 | - 39 | message: "#^Parameter \\#1 \\$kernel of method Symplify\\\\PackageBuilder\\\\Testing\\\\AbstractKernelTestCase\\:\\:bootAndReturnKernel\\(\\) expects Symfony\\\\Component\\\\HttpKernel\\\\KernelInterface, Symplify\\\\PackageBuilder\\\\Contract\\\\HttpKernel\\\\ExtraConfigAwareKernelInterface given\\.$#" 40 | count: 1 41 | path: src/Testing/AbstractKernelTestCase.php 42 | 43 | - 44 | message: "#^Static property Symplify\\\\PackageBuilder\\\\Testing\\\\AbstractKernelTestCase\\:\\:\\$container \\(Symfony\\\\Component\\\\DependencyInjection\\\\ContainerInterface\\|null\\) does not accept Psr\\\\Container\\\\ContainerInterface\\.$#" 45 | count: 2 46 | path: src/Testing/AbstractKernelTestCase.php 47 | -------------------------------------------------------------------------------- /src/Composer/VendorDirProvider.php: -------------------------------------------------------------------------------- 1 | reflectionFallback(); 30 | } 31 | 32 | private function reflectionFallback(): string 33 | { 34 | $reflectionClass = new ReflectionClass(ClassLoader::class); 35 | return dirname($reflectionClass->getFileName(), 2); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Console/Command/AbstractSymplifyCommand.php: -------------------------------------------------------------------------------- 1 | addOption(Option::CONFIG, 'c', InputOption::VALUE_REQUIRED, 'Path to config file'); 31 | } 32 | 33 | #[Required] 34 | public function autowire( 35 | SymfonyStyle $symfonyStyle, 36 | SmartFileSystem $smartFileSystem, 37 | SmartFinder $smartFinder, 38 | FileSystemGuard $fileSystemGuard 39 | ): void { 40 | $this->symfonyStyle = $symfonyStyle; 41 | $this->smartFileSystem = $smartFileSystem; 42 | $this->smartFinder = $smartFinder; 43 | $this->fileSystemGuard = $fileSystemGuard; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Console/Formatter/ColorConsoleDiffFormatter.php: -------------------------------------------------------------------------------- 1 | 13 | * 14 | * @see \Symplify\PackageBuilder\Tests\Console\Formatter\ColorConsoleDiffFormatterTest 15 | */ 16 | final class ColorConsoleDiffFormatter 17 | { 18 | /** 19 | * @var string 20 | * @see https://regex101.com/r/ovLMDF/1 21 | */ 22 | private const PLUS_START_REGEX = '#^(\+.*)#'; 23 | 24 | /** 25 | * @var string 26 | * @see https://regex101.com/r/xwywpa/1 27 | */ 28 | private const MINUT_START_REGEX = '#^(\-.*)#'; 29 | 30 | /** 31 | * @var string 32 | * @see https://regex101.com/r/CMlwa8/1 33 | */ 34 | private const AT_START_REGEX = '#^(@.*)#'; 35 | 36 | /** 37 | * @var string 38 | * @see https://regex101.com/r/qduj2O/1 39 | */ 40 | private const NEWLINES_REGEX = "#\n\r|\n#"; 41 | 42 | private readonly string $template; 43 | 44 | public function __construct() 45 | { 46 | $this->template = sprintf( 47 | ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------' . PHP_EOL, 48 | PHP_EOL, 49 | PHP_EOL 50 | ); 51 | } 52 | 53 | public function format(string $diff): string 54 | { 55 | return $this->formatWithTemplate($diff, $this->template); 56 | } 57 | 58 | private function formatWithTemplate(string $diff, string $template): string 59 | { 60 | $escapedDiff = OutputFormatter::escape(rtrim($diff)); 61 | 62 | $escapedDiffLines = Strings::split($escapedDiff, self::NEWLINES_REGEX); 63 | 64 | // remove description of added + remove; obvious on diffs 65 | foreach ($escapedDiffLines as $key => $escapedDiffLine) { 66 | if ($escapedDiffLine === '--- Original') { 67 | unset($escapedDiffLines[$key]); 68 | } 69 | 70 | if ($escapedDiffLine === '+++ New') { 71 | unset($escapedDiffLines[$key]); 72 | } 73 | } 74 | 75 | $coloredLines = array_map(function (string $string): string { 76 | $string = $this->makePlusLinesGreen($string); 77 | $string = $this->makeMinusLinesRed($string); 78 | $string = $this->makeAtNoteCyan($string); 79 | 80 | if ($string === ' ') { 81 | return ''; 82 | } 83 | 84 | return $string; 85 | }, $escapedDiffLines); 86 | 87 | return sprintf($template, implode(PHP_EOL, $coloredLines)); 88 | } 89 | 90 | private function makePlusLinesGreen(string $string): string 91 | { 92 | return Strings::replace($string, self::PLUS_START_REGEX, '$1'); 93 | } 94 | 95 | private function makeMinusLinesRed(string $string): string 96 | { 97 | return Strings::replace($string, self::MINUT_START_REGEX, '$1'); 98 | } 99 | 100 | private function makeAtNoteCyan(string $string): string 101 | { 102 | return Strings::replace($string, self::AT_START_REGEX, '$1'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Console/Input/StaticInputDetector.php: -------------------------------------------------------------------------------- 1 | hasParameterOption(['--debug', '-v', '-vv', '-vvv']); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Console/Output/ConsoleDiffer.php: -------------------------------------------------------------------------------- 1 | differ->diff($old, $new); 24 | return $this->colorConsoleDiffFormatter->format($diff); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Console/Style/SymfonyStyleFactory.php: -------------------------------------------------------------------------------- 1 | privatesCaller = new PrivatesCaller(); 24 | } 25 | 26 | public function create(): SymfonyStyle 27 | { 28 | // to prevent missing argv indexes 29 | if (! isset($_SERVER['argv'])) { 30 | $_SERVER['argv'] = []; 31 | } 32 | 33 | $argvInput = new ArgvInput(); 34 | $consoleOutput = new ConsoleOutput(); 35 | 36 | // to configure all -v, -vv, -vvv options without memory-lock to Application run() arguments 37 | $this->privatesCaller->callPrivateMethod(new Application(), 'configureIO', [$argvInput, $consoleOutput]); 38 | 39 | // --debug is called 40 | if ($argvInput->hasParameterOption('--debug')) { 41 | $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG); 42 | } 43 | 44 | // disable output for tests 45 | if ($this->isPHPUnitRun()) { 46 | $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET); 47 | } 48 | 49 | return new SymfonyStyle($argvInput, $consoleOutput); 50 | } 51 | 52 | /** 53 | * Never ever used static methods if not neccesary, this is just handy for tests + src to prevent duplication. 54 | */ 55 | private function isPHPUnitRun(): bool 56 | { 57 | return defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/AutowireInterfacesCompilerPass.php: -------------------------------------------------------------------------------- 1 | getDefinitions(); 23 | 24 | foreach ($definitions as $definition) { 25 | foreach ($this->typesToAutowire as $typeToAutowire) { 26 | if (! is_a((string) $definition->getClass(), $typeToAutowire, true)) { 27 | continue; 28 | } 29 | 30 | $definition->setAutowired(true); 31 | continue 2; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/DependencyInjection/FileLoader/ParameterMergingPhpFileLoader.php: -------------------------------------------------------------------------------- 1 | parametersMerger = new ParametersMerger(); 28 | 29 | parent::__construct($containerBuilder, $fileLocator); 30 | } 31 | 32 | /** 33 | * Same as parent, just merging parameters instead overriding them 34 | * 35 | * @see https://github.com/symplify/symplify/pull/697 36 | */ 37 | public function load(mixed $resource, string $type = null): mixed 38 | { 39 | // get old parameters 40 | $parameterBag = $this->container->getParameterBag(); 41 | $oldParameters = $parameterBag->all(); 42 | 43 | parent::load($resource); 44 | 45 | foreach ($oldParameters as $key => $oldValue) { 46 | $currentParameterValue = $this->container->getParameter($key); 47 | $newValue = $this->parametersMerger->merge($oldValue, $currentParameterValue); 48 | 49 | $this->container->setParameter($key, $newValue); 50 | } 51 | 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Diff/DifferFactory.php: -------------------------------------------------------------------------------- 1 | create(); 23 | 24 | return new Differ($unifiedDiffOutputBuilder); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Diff/Output/CompleteUnifiedDiffOutputBuilderFactory.php: -------------------------------------------------------------------------------- 1 | privatesAccessor->setPrivateProperty($unifiedDiffOutputBuilder, 'contextLines', 10000); 28 | return $unifiedDiffOutputBuilder; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/HttpKernel/MissingInterfaceException.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | private array $parameters = []; 21 | 22 | public function __construct(ContainerInterface $container) 23 | { 24 | /** @var ParameterBag $parameterBag */ 25 | $parameterBag = $container->getParameterBag(); 26 | $this->parameters = $parameterBag->all(); 27 | } 28 | 29 | public function hasParameter(string $name): bool 30 | { 31 | return isset($this->parameters[$name]); 32 | } 33 | 34 | /** 35 | * @api 36 | */ 37 | public function provideParameter(string $name): mixed 38 | { 39 | return $this->parameters[$name] ?? null; 40 | } 41 | 42 | /** 43 | * @api 44 | */ 45 | public function provideStringParameter(string $name): string 46 | { 47 | $this->ensureParameterIsSet($name); 48 | 49 | return (string) $this->parameters[$name]; 50 | } 51 | 52 | /** 53 | * @api 54 | * @return mixed[] 55 | */ 56 | public function provideArrayParameter(string $name): array 57 | { 58 | $this->ensureParameterIsSet($name); 59 | 60 | return $this->parameters[$name]; 61 | } 62 | 63 | /** 64 | * @api 65 | */ 66 | public function provideBoolParameter(string $parameterName): bool 67 | { 68 | return $this->parameters[$parameterName] ?? false; 69 | } 70 | 71 | public function changeParameter(string $name, mixed $value): void 72 | { 73 | $this->parameters[$name] = $value; 74 | } 75 | 76 | /** 77 | * @api 78 | * @return mixed[] 79 | */ 80 | public function provide(): array 81 | { 82 | return $this->parameters; 83 | } 84 | 85 | /** 86 | * @api 87 | */ 88 | public function provideIntParameter(string $name): int 89 | { 90 | $this->ensureParameterIsSet($name); 91 | 92 | return (int) $this->parameters[$name]; 93 | } 94 | 95 | /** 96 | * @api 97 | */ 98 | public function ensureParameterIsSet(string $name): void 99 | { 100 | if (array_key_exists($name, $this->parameters)) { 101 | return; 102 | } 103 | 104 | throw new ParameterNotFoundException($name); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Php/TypeChecker.php: -------------------------------------------------------------------------------- 1 | $types 14 | */ 15 | public function isInstanceOf(object | string $object, array $types): bool 16 | { 17 | foreach ($types as $type) { 18 | if (is_a($object, $type, true)) { 19 | return true; 20 | } 21 | } 22 | 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Reflection/ClassLikeExistenceChecker.php: -------------------------------------------------------------------------------- 1 | doesClassLikeExist($classLikeName)) { 43 | return false; 44 | } 45 | 46 | // already known values 47 | if (in_array($classLikeName, $this->sensitiveExistingClasses, true)) { 48 | return true; 49 | } 50 | 51 | if (in_array($classLikeName, $this->sensitiveNonExistingClasses, true)) { 52 | return false; 53 | } 54 | 55 | $reflectionClass = new ReflectionClass($classLikeName); 56 | if ($classLikeName !== $reflectionClass->getName()) { 57 | $this->sensitiveNonExistingClasses[] = $classLikeName; 58 | return false; 59 | } 60 | 61 | $this->sensitiveExistingClasses[] = $classLikeName; 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Reflection/PrivatesAccessor.php: -------------------------------------------------------------------------------- 1 | $valueClassName 21 | * @return T 22 | */ 23 | public function getPrivatePropertyOfClass(object $object, string $propertyName, string $valueClassName): object 24 | { 25 | $value = $this->getPrivateProperty($object, $propertyName); 26 | if ($value instanceof $valueClassName) { 27 | return $value; 28 | } 29 | 30 | $errorMessage = sprintf('The type "%s" is required, but "%s" type given', $valueClassName, $value::class); 31 | throw new InvalidPrivatePropertyTypeException($errorMessage); 32 | } 33 | 34 | public function getPrivateProperty(object $object, string $propertyName): mixed 35 | { 36 | $propertyReflection = $this->resolvePropertyReflection($object, $propertyName); 37 | $propertyReflection->setAccessible(true); 38 | 39 | return $propertyReflection->getValue($object); 40 | } 41 | 42 | /** 43 | * @template T of object 44 | * 45 | * @param class-string $valueClassName 46 | * @param T $value 47 | */ 48 | public function setPrivatePropertyOfClass( 49 | object $object, 50 | string $propertyName, 51 | mixed $value, 52 | string $valueClassName 53 | ): void { 54 | if ($value instanceof $valueClassName) { 55 | $this->setPrivateProperty($object, $propertyName, $value); 56 | return; 57 | } 58 | 59 | $errorMessage = sprintf('The type "%s" is required, but "%s" type given', $valueClassName, $value::class); 60 | throw new InvalidPrivatePropertyTypeException($errorMessage); 61 | } 62 | 63 | public function setPrivateProperty(object $object, string $propertyName, mixed $value): void 64 | { 65 | $propertyReflection = $this->resolvePropertyReflection($object, $propertyName); 66 | $propertyReflection->setAccessible(true); 67 | 68 | $propertyReflection->setValue($object, $value); 69 | } 70 | 71 | private function resolvePropertyReflection(object $object, string $propertyName): ReflectionProperty 72 | { 73 | if (property_exists($object, $propertyName)) { 74 | return new ReflectionProperty($object, $propertyName); 75 | } 76 | 77 | $parentClass = get_parent_class($object); 78 | if ($parentClass !== false) { 79 | return new ReflectionProperty($parentClass, $propertyName); 80 | } 81 | 82 | $errorMessage = sprintf('Property "$%s" was not found in "%s" class', $propertyName, $object::class); 83 | throw new MissingPrivatePropertyException($errorMessage); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Reflection/PrivatesCaller.php: -------------------------------------------------------------------------------- 1 | newInstanceWithoutConstructor(); 24 | } 25 | 26 | $methodReflection = $this->createAccessibleMethodReflection($object, $methodName); 27 | 28 | return $methodReflection->invokeArgs($object, $arguments); 29 | } 30 | 31 | /** 32 | * @api 33 | */ 34 | public function callPrivateMethodWithReference(object | string $object, string $methodName, mixed $argument): mixed 35 | { 36 | if (is_string($object)) { 37 | $reflectionClass = new ReflectionClass($object); 38 | $object = $reflectionClass->newInstanceWithoutConstructor(); 39 | } 40 | 41 | $methodReflection = $this->createAccessibleMethodReflection($object, $methodName); 42 | $methodReflection->invokeArgs($object, [&$argument]); 43 | 44 | return $argument; 45 | } 46 | 47 | private function createAccessibleMethodReflection(object $object, string $methodName): ReflectionMethod 48 | { 49 | $reflectionMethod = new ReflectionMethod($object::class, $methodName); 50 | $reflectionMethod->setAccessible(true); 51 | 52 | return $reflectionMethod; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Strings/StringFormatConverter.php: -------------------------------------------------------------------------------- 1 | camelCaseToGlue($input, '_'); 33 | } 34 | 35 | public function camelCaseToDashed(string $input): string 36 | { 37 | return $this->camelCaseToGlue($input, '-'); 38 | } 39 | 40 | /** 41 | * @param array $items 42 | * @return array 43 | */ 44 | public function camelCaseToUnderscoreInArrayKeys(array $items): array 45 | { 46 | foreach ($items as $key => $value) { 47 | if (! is_string($key)) { 48 | continue; 49 | } 50 | 51 | $newKey = $this->camelCaseToUnderscore($key); 52 | if ($key === $newKey) { 53 | continue; 54 | } 55 | 56 | $items[$newKey] = $value; 57 | unset($items[$key]); 58 | } 59 | 60 | return $items; 61 | } 62 | 63 | private function camelCaseToGlue(string $input, string $glue): string 64 | { 65 | $matches = Strings::matchAll($input, self::BIG_LETTER_REGEX); 66 | 67 | $parts = []; 68 | foreach ($matches as $match) { 69 | $parts[] = $match[0] === strtoupper((string) $match[0]) ? strtolower($match[0]) : lcfirst( 70 | (string) $match[0] 71 | ); 72 | } 73 | 74 | return implode($glue, $parts); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Strings/StringFormatConverter.phpqIy1Ap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deprecated-packages/package-builder/5f4d9614b7a165f52097727218966ccc4d1752b8/src/Strings/StringFormatConverter.phpqIy1Ap -------------------------------------------------------------------------------- /src/Testing/AbstractKernelTestCase.php: -------------------------------------------------------------------------------- 1 | $kernelClass 34 | * @param string[]|SmartFileInfo[] $configs 35 | */ 36 | protected function bootKernelWithConfigs(string $kernelClass, array $configs): void 37 | { 38 | // unwrap file infos to real paths 39 | $configFilePaths = $this->resolveConfigFilePaths($configs); 40 | $configsHash = $this->resolveConfigsHash($configFilePaths); 41 | 42 | $this->ensureKernelShutdown(); 43 | 44 | $bootedKernel = $this->createBootedKernelFromConfigs($kernelClass, $configsHash, $configFilePaths); 45 | 46 | static::$kernel = $bootedKernel; 47 | self::$container = $bootedKernel->getContainer(); 48 | } 49 | 50 | /** 51 | * Syntax sugger to remove static from the test cases vision 52 | * 53 | * @template T of object 54 | * @param class-string $type 55 | * @return T 56 | */ 57 | protected function getService(string $type): object 58 | { 59 | if (self::$container === null) { 60 | throw new ShouldNotHappenException('First, create container with booKernel(KernelClass::class)'); 61 | } 62 | 63 | $service = self::$container->get($type); 64 | if ($service === null) { 65 | $errorMessage = sprintf('Services "%s" was not found', $type); 66 | throw new MissingServiceException($errorMessage); 67 | } 68 | 69 | return $service; 70 | } 71 | 72 | /** 73 | * @param class-string $kernelClass 74 | */ 75 | protected function bootKernel(string $kernelClass): void 76 | { 77 | if (is_a($kernelClass, LightKernelInterface::class, true)) { 78 | /** @var LightKernelInterface $kernel */ 79 | $kernel = new $kernelClass(); 80 | $kernel->createFromConfigs([]); 81 | 82 | static::$kernel = $kernel; 83 | self::$container = $kernel->getContainer(); 84 | return; 85 | } 86 | 87 | $this->ensureKernelShutdown(); 88 | 89 | $kernel = new $kernelClass('test', true); 90 | if (! $kernel instanceof KernelInterface) { 91 | throw new ShouldNotHappenException(); 92 | } 93 | 94 | static::$kernel = $this->bootAndReturnKernel($kernel); 95 | } 96 | 97 | /** 98 | * Shuts the kernel down if it was used in the test. 99 | */ 100 | protected function ensureKernelShutdown(): void 101 | { 102 | if (static::$kernel !== null && static::$kernel instanceof KernelInterface) { 103 | // make sure boot() is called 104 | // @see https://github.com/symfony/symfony/pull/31202/files 105 | $kernelReflectionClass = new ReflectionClass(static::$kernel); 106 | 107 | $containerReflectionProperty = $kernelReflectionClass->getProperty('container'); 108 | $containerReflectionProperty->setAccessible(true); 109 | 110 | $kernel = $containerReflectionProperty->getValue(static::$kernel); 111 | if ($kernel !== null) { 112 | $container = static::$kernel->getContainer(); 113 | static::$kernel->shutdown(); 114 | if ($container instanceof ResetInterface) { 115 | $container->reset(); 116 | } 117 | } 118 | } 119 | 120 | static::$container = null; 121 | } 122 | 123 | /** 124 | * @param string[] $configs 125 | */ 126 | protected function resolveConfigsHash(array $configs): string 127 | { 128 | $configsHash = ''; 129 | foreach ($configs as $config) { 130 | $configsHash .= md5_file($config); 131 | } 132 | 133 | return md5($configsHash); 134 | } 135 | 136 | /** 137 | * @param string[]|SmartFileInfo[] $configs 138 | * @return string[] 139 | */ 140 | protected function resolveConfigFilePaths(array $configs): array 141 | { 142 | $configFilePaths = []; 143 | 144 | foreach ($configs as $config) { 145 | $configFilePaths[] = $config instanceof SmartFileInfo ? $config->getRealPath() : $config; 146 | } 147 | 148 | return $configFilePaths; 149 | } 150 | 151 | private function ensureIsConfigAwareKernel(KernelInterface|LightKernelInterface $kernel): void 152 | { 153 | if ($kernel instanceof LightKernelInterface) { 154 | return; 155 | } 156 | 157 | if ($kernel instanceof ExtraConfigAwareKernelInterface) { 158 | return; 159 | } 160 | 161 | throw new MissingInterfaceException(sprintf( 162 | '"%s" is missing an "%s" interface', 163 | $kernel::class, 164 | ExtraConfigAwareKernelInterface::class 165 | )); 166 | } 167 | 168 | private function bootAndReturnKernel(KernelInterface $kernel): KernelInterface 169 | { 170 | $kernel->boot(); 171 | 172 | $container = $kernel->getContainer(); 173 | 174 | // private → public service hack? 175 | if ($container->has('test.service_container')) { 176 | $container = $container->get('test.service_container'); 177 | } 178 | 179 | if (! $container instanceof ContainerInterface) { 180 | throw new ShouldNotHappenException(); 181 | } 182 | 183 | // has output? keep it silent out of tests 184 | if ($container->has(SymfonyStyle::class)) { 185 | $symfonyStyle = $container->get(SymfonyStyle::class); 186 | $symfonyStyle->setVerbosity(OutputInterface::VERBOSITY_QUIET); 187 | } 188 | 189 | static::$container = $container; 190 | 191 | return $kernel; 192 | } 193 | 194 | /** 195 | * @param class-string $kernelClass 196 | * @param string[] $configFilePaths 197 | */ 198 | private function createBootedKernelFromConfigs( 199 | string $kernelClass, 200 | string $configsHash, 201 | array $configFilePaths 202 | ): KernelInterface|LightKernelInterface { 203 | if (is_a($kernelClass, LightKernelInterface::class, true)) { 204 | /** @var LightKernelInterface $kernel */ 205 | $kernel = new $kernelClass(); 206 | $kernel->createFromConfigs($configFilePaths); 207 | return $kernel; 208 | } 209 | 210 | $kernel = new $kernelClass('test_' . $configsHash, true); 211 | $this->ensureIsConfigAwareKernel($kernel); 212 | 213 | /** @var ExtraConfigAwareKernelInterface $kernel */ 214 | $kernel->setConfigs($configFilePaths); 215 | 216 | return $this->bootAndReturnKernel($kernel); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/ValueObject/ConsoleColorDiffConfig.php: -------------------------------------------------------------------------------- 1 | mergeLeftToRightWithCallable( 19 | $left, 20 | $right, 21 | fn ($leftValue, $rightValue) => $this->merge($leftValue, $rightValue) 22 | ); 23 | } 24 | 25 | if ($left !== null) { 26 | return $left; 27 | } 28 | 29 | if (! is_array($right)) { 30 | return $left; 31 | } 32 | 33 | return $right; 34 | } 35 | 36 | /** 37 | * The same as above, just with the case if both values being non-array, it will combined them to array: 38 | * 39 | * $this->mergeWithCombine(1, 2); // [1, 2] 40 | */ 41 | public function mergeWithCombine(mixed $left, mixed $right): mixed 42 | { 43 | if (is_array($left) && is_array($right)) { 44 | return $this->mergeLeftToRightWithCallable( 45 | $left, 46 | $right, 47 | fn ($leftValue, $rightValue) => $this->mergeWithCombine($leftValue, $rightValue) 48 | ); 49 | } 50 | 51 | if ($left === null && is_array($right)) { 52 | return $right; 53 | } 54 | 55 | if (! empty($right) && (array) $left !== (array) $right) { 56 | return $this->mergeWithCombine((array) $right, (array) $left); 57 | } 58 | 59 | return $left; 60 | } 61 | 62 | /** 63 | * @param array $left 64 | * @param array $right 65 | * @return mixed[] 66 | */ 67 | private function mergeLeftToRightWithCallable(array $left, array $right, callable $mergeCallback): array 68 | { 69 | foreach ($left as $key => $val) { 70 | if (is_int($key)) { 71 | // prevent duplicated values in unindexed arrays 72 | if (! in_array($val, $right, true)) { 73 | $right[] = $val; 74 | } 75 | } else { 76 | if (isset($right[$key])) { 77 | $val = $mergeCallback($val, $right[$key]); 78 | } 79 | 80 | $right[$key] = $val; 81 | } 82 | } 83 | 84 | return $right; 85 | } 86 | } 87 | --------------------------------------------------------------------------------