├── .editorconfig ├── .github ├── FUNDING.yaml └── workflows │ └── code_analysis.yaml ├── LICENSE ├── composer.json ├── config └── config.php ├── phpunit.xml └── src ├── CaseConverter ├── AliasCaseConverter.php ├── ClassServiceCaseConverter.php ├── ConfiguredServiceCaseConverter.php ├── ExtensionConverter.php ├── ImportCaseConverter.php ├── NameOnlyServiceCaseConverter.php ├── NestedCaseConverter │ └── InstanceOfNestedCaseConverter.php ├── ParameterCaseConverter.php ├── ResourceCaseConverter.php └── ServicesDefaultsCaseConverter.php ├── Contract ├── CaseConverterInterface.php ├── Converter │ └── ServiceOptionsKeyYamlToPhpFactoryInterface.php ├── NodeVisitor │ └── PrePrintNodeVisitorInterface.php └── RoutingCaseConverterInterface.php ├── Enum └── RouteOption.php ├── Exception ├── NotImplementedYetException.php └── ShouldNotHappenException.php ├── ExprResolver ├── ServiceReferenceExprResolver.php ├── StringExprResolver.php ├── TaggedReturnsCloneResolver.php └── TaggedServiceResolver.php ├── Naming ├── ClassNaming.php ├── ReferenceFunctionNameResolver.php └── VariableNameResolver.php ├── NodeFactory ├── ArgsNodeFactory.php ├── CommonNodeFactory.php ├── ConstantNodeFactory.php ├── ContainerConfiguratorReturnClosureFactory.php ├── ContainerNestedNodesFactory.php ├── NewValueObjectFactory.php ├── RoutingConfiguratorReturnClosureFactory.php └── Service │ ├── AutoBindNodeFactory.php │ ├── ServiceOptionNodeFactory.php │ ├── ServicesPhpNodeFactory.php │ └── SingleServicePhpNodeFactory.php ├── NodeFinder └── TypeAwareNodeFinder.php ├── NodeModifier └── SingleFactoryReferenceNodeModifier.php ├── NodeTraverser └── ImportFullyQualifiedNamesNodeTraverser.php ├── NodeVisitor └── ImportFullyQualifiedNamesNodeVisitor.php ├── PhpParser └── NodeFactory │ └── ConfiguratorClosureNodeFactory.php ├── Printer ├── ArrayDecorator │ └── ServiceConfigurationDecorator.php ├── NodeDecorator │ └── EmptyLineNodeDecorator.php ├── PhpParserPhpConfigPrinter.php └── SmartPhpConfigPrinter.php ├── Provider └── CurrentFilePathProvider.php ├── Reflection └── ConstantNameFromValueResolver.php ├── Routing └── ControllerSplitter.php ├── RoutingCaseConverter ├── ConditionalEnvRoutingCaseConverter.php ├── ImportRoutingCaseConverter.php └── PathRoutingCaseConverter.php ├── ServiceOptionAnalyzer └── ServiceOptionAnalyzer.php ├── ServiceOptionConverter ├── AbstractServiceOptionKeyYamlToPhpFactory.php ├── ArgumentsServiceOptionKeyYamlToPhpFactory.php ├── AutowiringTypesOptionKeyYamlToPhpFactory.php ├── BindAutowireAutoconfigureServiceOptionKeyYamlToPhpFactory.php ├── CallsServiceOptionKeyYamlToPhpFactory.php ├── DecoratesServiceOptionKeyYamlToPhpFactory.php ├── DeprecatedServiceOptionKeyYamlToPhpFactory.php ├── FactoryConfiguratorServiceOptionKeyYamlToPhpFactory.php ├── ParentLazyServiceOptionKeyYamlToPhpFactory.php ├── PropertiesServiceOptionKeyYamlToPhpFactory.php ├── SharedPublicServiceOptionKeyYamlToPhpFactory.php └── TagsServiceOptionKeyYamlToPhpFactory.php ├── Sorter ├── FullyQualifiedImportSorter.php └── YamlArgumentSorter.php ├── StringFormatConverter.php ├── ValueObject ├── AttributeKey.php ├── FullyQualifiedImport.php ├── FunctionName.php ├── ImportType.php ├── MethodName.php ├── PhpConfigPrinterConfig.php ├── Routing │ └── RouteDefaults.php ├── VariableMethodName.php ├── VariableName.php ├── YamlKey.php └── YamlServiceKey.php └── Yaml └── CheckerServiceParametersShifter.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yaml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: tomasvotruba 3 | -------------------------------------------------------------------------------- /.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: 'Tests' 33 | run: vendor/bin/phpunit 34 | 35 | - 36 | name: 'Check Active Classes' 37 | run: vendor/bin/class-leak check src --ansi --skip-type="\Symplify\PhpConfigPrinter\Contract\Converter\ServiceOptionsKeyYamlToPhpFactoryInterface" --skip-type="\Symplify\PhpConfigPrinter\Contract\RoutingCaseConverterInterface" --skip-type="\Symplify\PhpConfigPrinter\Contract\CaseConverterInterface" 38 | 39 | name: ${{ matrix.actions.name }} 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - uses: actions/checkout@v3 44 | # see https://github.com/shivammathur/setup-php 45 | - uses: shivammathur/setup-php@v2 46 | with: 47 | php-version: 8.2 48 | coverage: none 49 | 50 | # composer install cache - https://github.com/ramsey/composer-install 51 | - uses: "ramsey/composer-install@v2" 52 | 53 | - run: ${{ matrix.actions.run }} 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | --------------- 3 | 4 | Copyright (c) 2020 Tomas Votruba (https://tomasvotruba.com) 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symplify/php-config-printer", 3 | "description": "Print Symfony services array with configuration to to plain PHP file format thanks to this simple php-parser wrapper", 4 | "license": "MIT", 5 | "require": { 6 | "php": ">=8.2", 7 | "nette/utils": "^3.2", 8 | "nikic/php-parser": "^5.3", 9 | "symfony/yaml": "^6.4" 10 | }, 11 | "require-dev": { 12 | "myclabs/php-enum": "^1.8", 13 | "phpstan/extension-installer": "^1.4", 14 | "phpstan/phpstan": "^2.1", 15 | "phpunit/phpunit": "^10.5", 16 | "rector/rector": "^2.0", 17 | "phpecs/phpecs": "^2.0", 18 | "symplify/easy-testing": "^11.1", 19 | "symplify/phpstan-extensions": "^12.0", 20 | "tomasvotruba/class-leak": "^2.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Symplify\\PhpConfigPrinter\\": "src" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Symplify\\PhpConfigPrinter\\Tests\\": "tests" 30 | }, 31 | "files": [ 32 | "tests/Printer/SmartPhpConfigPrinter/Source/custom_inline_objects_function.php", 33 | "tests/Printer/SmartPhpConfigPrinter/Source/custom_inline_object_function.php" 34 | ] 35 | }, 36 | "scripts": { 37 | "check-cs": "vendor/bin/ecs check --ansi", 38 | "fix-cs": "vendor/bin/ecs check --fix --ansi", 39 | "phpstan": "vendor/bin/phpstan analyse --ansi", 40 | "rector": "vendor/bin/rector process --dry-run --ansi" 41 | }, 42 | "config": { 43 | "sort-packages": true, 44 | "allow-plugins": { 45 | "cweagans/composer-patches": true, 46 | "phpstan/extension-installer": true 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | services(); 20 | 21 | $services->defaults() 22 | ->public() 23 | ->autowire(); 24 | 25 | $services->load('Symplify\PhpConfigPrinter\\', __DIR__ . '/../src') 26 | ->exclude([__DIR__ . '/../src/ValueObject']); 27 | 28 | $services->load( 29 | 'Symplify\\PhpConfigPrinter\\CaseConverter\\', 30 | __DIR__ . '/../src/CaseConverter' 31 | ) 32 | ->exclude(__DIR__ . '/../src/CaseConverter/NestedCaseConverter/InstanceOfNestedCaseConverter.php') 33 | ->tag(CaseConverterInterface::class); 34 | 35 | $services->load( 36 | 'Symplify\\PhpConfigPrinter\\RoutingCaseConverter\\', 37 | __DIR__ . '/../src/RoutingCaseConverter' 38 | )->tag(RoutingCaseConverterInterface::class); 39 | 40 | 41 | $services->load( 42 | 'Symplify\\PhpConfigPrinter\\ServiceOptionConverter\\', 43 | __DIR__ . '/../src/ServiceOptionConverter' 44 | )->tag(ServiceOptionsKeyYamlToPhpFactoryInterface::class); 45 | 46 | $services->set(ContainerConfiguratorReturnClosureFactory::class) 47 | ->arg('$caseConverters', tagged_iterator(CaseConverterInterface::class)); 48 | 49 | $services->set(RoutingConfiguratorReturnClosureFactory::class) 50 | ->arg('$routingCaseConverters', tagged_iterator(RoutingCaseConverterInterface::class)); 51 | 52 | $services->set(\Symplify\PhpConfigPrinter\NodeFactory\Service\ServiceOptionNodeFactory::class) 53 | ->arg('$serviceOptionKeyYamlToPhpFactories', tagged_iterator(ServiceOptionsKeyYamlToPhpFactoryInterface::class)); 54 | 55 | $services->set(\Symplify\PhpConfigPrinter\Printer\PhpParserPhpConfigPrinter::class) 56 | ->arg('$prePrintNodeVisitors', tagged_iterator(PrePrintNodeVisitorInterface::class)); 57 | 58 | $services->set(NodeFinder::class); 59 | $services->set(Parser::class); 60 | $services->set(BuilderFactory::class); 61 | $services->set(ParentConnectingVisitor::class); 62 | }; 63 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tests 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/CaseConverter/AliasCaseConverter.php: -------------------------------------------------------------------------------- 1 | \w+)#'; 31 | 32 | /** 33 | * @see https://regex101.com/r/DDuuVM/1 34 | * @var string 35 | */ 36 | private const NAMED_ALIAS_REGEX = '#\w+\s+\$\w+#'; 37 | 38 | public function __construct( 39 | private CommonNodeFactory $commonNodeFactory, 40 | private ArgsNodeFactory $argsNodeFactory, 41 | private ServiceOptionNodeFactory $serviceOptionNodeFactory, 42 | ) { 43 | } 44 | 45 | public function convertToMethodCallStmt(mixed $key, mixed $values): Stmt 46 | { 47 | if (! is_string($key)) { 48 | throw new ShouldNotHappenException(); 49 | } 50 | 51 | $servicesVariable = new Variable(VariableName::SERVICES); 52 | if ($this->doesClassLikeExist($key)) { 53 | return $this->createFromClassLike($key, $values, $servicesVariable); 54 | } 55 | 56 | // handles: "SomeClass $someVariable: ..." 57 | $fullClassName = Strings::before($key, ' $'); 58 | if ($fullClassName !== null) { 59 | $methodCall = $this->createAliasNode($key, $fullClassName, $values); 60 | return new Expression($methodCall); 61 | } 62 | 63 | if (is_string($values) && $values[0] === '@') { 64 | $args = $this->argsNodeFactory->createFromValues([$key, $values], true); 65 | $methodCall = new MethodCall($servicesVariable, MethodName::ALIAS, $args); 66 | return new Expression($methodCall); 67 | } 68 | 69 | if (is_array($values)) { 70 | return $this->createFromArrayValues($values, $key, $servicesVariable); 71 | } 72 | 73 | throw new ShouldNotHappenException(); 74 | } 75 | 76 | public function match(string $rootKey, mixed $key, mixed $values): bool 77 | { 78 | if ($rootKey !== YamlKey::SERVICES) { 79 | return false; 80 | } 81 | 82 | if (isset($values[YamlKey::ALIAS])) { 83 | return true; 84 | } 85 | 86 | if (Strings::match($key, self::NAMED_ALIAS_REGEX)) { 87 | return true; 88 | } 89 | 90 | if (! is_string($values)) { 91 | return false; 92 | } 93 | 94 | return $values[0] === '@'; 95 | } 96 | 97 | private function createAliasNode(string $key, string $fullClassName, mixed $serviceValues): MethodCall 98 | { 99 | $args = []; 100 | 101 | $classConstFetch = $this->commonNodeFactory->createClassReference($fullClassName); 102 | 103 | Strings::match($key, self::ARGUMENT_NAME_REGEX); 104 | $argumentName = '$' . Strings::after($key, '$'); 105 | 106 | $concat = new Concat($classConstFetch, new String_(' ' . $argumentName)); 107 | $args[] = new Arg($concat); 108 | 109 | $serviceName = ltrim((string) $serviceValues, '@'); 110 | $args[] = new Arg(new String_($serviceName)); 111 | 112 | return new MethodCall(new Variable(VariableName::SERVICES), MethodName::ALIAS, $args); 113 | } 114 | 115 | private function createFromClassLike(string $key, mixed $values, Variable $servicesVariable): Expression 116 | { 117 | $classConstFetch = $this->commonNodeFactory->createClassReference($key); 118 | 119 | $argValues = []; 120 | $argValues[] = $classConstFetch; 121 | $argValues[] = $values[MethodName::ALIAS] ?? $values; 122 | 123 | $args = $this->argsNodeFactory->createFromValues($argValues, true); 124 | $methodCall = new MethodCall($servicesVariable, MethodName::ALIAS, $args); 125 | 126 | return new Expression($methodCall); 127 | } 128 | 129 | private function createFromAlias(string $serviceName, string $key, Variable $servicesVariable): MethodCall 130 | { 131 | if ($this->doesClassLikeExist($serviceName)) { 132 | $classReference = $this->commonNodeFactory->createClassReference($serviceName); 133 | $args = $this->argsNodeFactory->createFromValues([$key, $classReference]); 134 | } else { 135 | $args = $this->argsNodeFactory->createFromValues([$key, $serviceName]); 136 | } 137 | 138 | return new MethodCall($servicesVariable, MethodName::ALIAS, $args); 139 | } 140 | 141 | /** 142 | * @param mixed[] $values 143 | */ 144 | private function createFromArrayValues(array $values, string $key, Variable $servicesVariable): Expression 145 | { 146 | if (isset($values[MethodName::ALIAS])) { 147 | $methodCall = $this->createFromAlias($values[MethodName::ALIAS], $key, $servicesVariable); 148 | unset($values[MethodName::ALIAS]); 149 | } else { 150 | throw new ShouldNotHappenException(); 151 | } 152 | 153 | /** @var MethodCall $methodCall */ 154 | $methodCall = $this->serviceOptionNodeFactory->convertServiceOptionsToNodes($values, $methodCall); 155 | return new Expression($methodCall); 156 | } 157 | 158 | private function doesClassLikeExist(string $class): bool 159 | { 160 | if (class_exists($class)) { 161 | return true; 162 | } 163 | 164 | if (interface_exists($class)) { 165 | return true; 166 | } 167 | 168 | return trait_exists($class); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/CaseConverter/ClassServiceCaseConverter.php: -------------------------------------------------------------------------------- 1 | argsNodeFactory->createFromValues([$key, $values[YamlKey::CLASS_KEY]]); 29 | $methodCall = new MethodCall(new Variable(VariableName::SERVICES), MethodName::SET, $args); 30 | 31 | unset($values[YamlKey::CLASS_KEY]); 32 | 33 | $decoratedMethodCall = $this->serviceOptionNodeFactory->convertServiceOptionsToNodes($values, $methodCall); 34 | return new Expression($decoratedMethodCall); 35 | } 36 | 37 | public function match(string $rootKey, mixed $key, mixed $values): bool 38 | { 39 | if ($rootKey !== YamlKey::SERVICES) { 40 | return false; 41 | } 42 | 43 | if (is_array($values) && count($values) !== 1) { 44 | return false; 45 | } 46 | 47 | if (! isset($values[YamlKey::CLASS_KEY])) { 48 | return false; 49 | } 50 | 51 | return ! isset($values[YamlKey::ALIAS]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CaseConverter/ConfiguredServiceCaseConverter.php: -------------------------------------------------------------------------------- 1 | argsNodeFactory->createFromValues($valuesForArgs); 35 | $methodCall = new MethodCall(new Variable(VariableName::SERVICES), MethodName::SET, $args); 36 | 37 | $decoratedMethodCall = $this->serviceOptionNodeFactory->convertServiceOptionsToNodes($values, $methodCall); 38 | return new Expression($decoratedMethodCall); 39 | } 40 | 41 | public function match(string $rootKey, mixed $key, mixed $values): bool 42 | { 43 | if ($rootKey !== YamlKey::SERVICES) { 44 | return false; 45 | } 46 | 47 | if ($key === YamlKey::_DEFAULTS) { 48 | return false; 49 | } 50 | 51 | if ($key === YamlKey::_INSTANCEOF) { 52 | return false; 53 | } 54 | 55 | if (isset($values[YamlKey::RESOURCE])) { 56 | return false; 57 | } 58 | 59 | // handled by @see \Symplify\PhpConfigPrinter\CaseConverter\CaseConverter\AliasCaseConverter 60 | if ($this->isAlias($values)) { 61 | return false; 62 | } 63 | 64 | if ($values === null) { 65 | return false; 66 | } 67 | 68 | if (array_key_exists('configure', $values)) { 69 | return true; 70 | } 71 | 72 | return $values !== []; 73 | } 74 | 75 | private function isAlias(mixed $values): bool 76 | { 77 | if (isset($values[YamlKey::ALIAS])) { 78 | return true; 79 | } 80 | 81 | if (! is_string($values)) { 82 | return false; 83 | } 84 | 85 | return \str_starts_with($values, '@'); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/CaseConverter/ExtensionConverter.php: -------------------------------------------------------------------------------- 1 | argsNodeFactory->createFromValues([ 29 | $this->rootKey, 30 | [ 31 | $key => $values, 32 | ], 33 | ]); 34 | 35 | $containerConfiguratorVariable = new Variable(VariableName::CONTAINER_CONFIGURATOR); 36 | $methodCall = new MethodCall($containerConfiguratorVariable, MethodName::EXTENSION, $args); 37 | 38 | return new Expression($methodCall); 39 | } 40 | 41 | public function match(string $rootKey, mixed $key, mixed $values): bool 42 | { 43 | $this->rootKey = $rootKey; 44 | return ! in_array($rootKey, YamlKey::provideRootKeys(), true); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/CaseConverter/ImportCaseConverter.php: -------------------------------------------------------------------------------- 1 | yamlArgumentSorter->sortArgumentsByKeyIfExists($values, [ 53 | YamlKey::RESOURCE => '', 54 | 'type' => null, 55 | YamlKey::IGNORE_ERRORS => false, 56 | ]); 57 | return $this->createImportMethodCall($arguments); 58 | } 59 | 60 | if (is_string($values)) { 61 | return $this->createImportMethodCall([$values]); 62 | } 63 | 64 | throw new NotImplementedYetException(); 65 | } 66 | 67 | /** 68 | * @param mixed[] $arguments 69 | */ 70 | private function createImportMethodCall(array $arguments): Expression 71 | { 72 | $containerConfiguratorVariable = new Variable(VariableName::CONTAINER_CONFIGURATOR); 73 | 74 | $args = $this->createArgs($arguments); 75 | $methodCall = new MethodCall($containerConfiguratorVariable, 'import', $args); 76 | 77 | return new Expression($methodCall); 78 | } 79 | 80 | /** 81 | * @param array $arguments 82 | * @return Arg[] 83 | */ 84 | private function createArgs(array $arguments): array 85 | { 86 | $args = []; 87 | foreach ($arguments as $name => $value) { 88 | if (is_string($name) && $this->shouldSkipDefaultValue($name, $value, $arguments)) { 89 | continue; 90 | } 91 | 92 | $expr = $this->resolveExpr($value); 93 | $args[] = new Arg($expr); 94 | } 95 | 96 | return $args; 97 | } 98 | 99 | /** 100 | * @param mixed[] $arguments 101 | */ 102 | private function shouldSkipDefaultValue(string $name, mixed $value, array $arguments): bool 103 | { 104 | // skip default value for "ignore_errors" 105 | if ($name === YamlKey::IGNORE_ERRORS && $value === false) { 106 | return true; 107 | } 108 | 109 | // check if default value for "type" 110 | if ($name !== 'type') { 111 | return false; 112 | } 113 | 114 | if ($value !== null) { 115 | return false; 116 | } 117 | 118 | // follow by default value for "ignore_errors" 119 | if (! isset($arguments[YamlKey::IGNORE_ERRORS])) { 120 | return false; 121 | } 122 | 123 | return $arguments[YamlKey::IGNORE_ERRORS] === false; 124 | } 125 | 126 | private function replaceImportedFileSuffix(mixed $value): mixed 127 | { 128 | if (! is_string($value)) { 129 | return $value; 130 | } 131 | 132 | return Strings::replace($value, self::INPUT_SUFFIX_REGEX, '.php'); 133 | } 134 | 135 | private function resolveExpr(mixed $value): Expr 136 | { 137 | $expr = $this->processNormalizedValue($value); 138 | if ($expr instanceof Expr) { 139 | return $expr; 140 | } 141 | 142 | if ($value === 'not_found') { 143 | return new String_('not_found'); 144 | } 145 | 146 | if (is_string($value) && \str_contains($value, '::')) { 147 | [$className, $constantName] = explode('::', $value); 148 | return new ClassConstFetch(new FullyQualified($className), $constantName); 149 | } 150 | 151 | if (is_string($value) && \str_starts_with($value, '@')) { 152 | return new String_($value); 153 | } 154 | 155 | $value = $this->replaceImportedFileSuffix($value); 156 | 157 | if (is_string($value) && \str_starts_with($value, '%')) { 158 | return new String_($value); 159 | } 160 | 161 | return $this->commonNodeFactory->createAbsoluteDirExpr($value); 162 | } 163 | 164 | private function processNormalizedValue(mixed $value): ?Expr 165 | { 166 | if (is_bool($value)) { 167 | return BuilderHelpers::normalizeValue($value); 168 | } 169 | 170 | if (in_array($value, ['annotations', 'directory', 'glob'], true)) { 171 | return BuilderHelpers::normalizeValue($value); 172 | } 173 | 174 | return null; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/CaseConverter/NameOnlyServiceCaseConverter.php: -------------------------------------------------------------------------------- 1 | commonNodeFactory->createClassReference($key); 27 | $setMethodCall = new MethodCall(new Variable(VariableName::SERVICES), 'set', [new Arg($classConstFetch)]); 28 | 29 | return new Expression($setMethodCall); 30 | } 31 | 32 | public function match(string $rootKey, mixed $key, mixed $values): bool 33 | { 34 | if ($rootKey !== YamlKey::SERVICES) { 35 | return false; 36 | } 37 | 38 | return $values === null || $values === []; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/CaseConverter/NestedCaseConverter/InstanceOfNestedCaseConverter.php: -------------------------------------------------------------------------------- 1 | commonNodeFactory->createClassReference($key); 32 | 33 | $servicesVariable = new Variable(VariableName::SERVICES); 34 | $args = [new Arg($classConstFetch)]; 35 | 36 | $instanceofMethodCall = new MethodCall($servicesVariable, MethodName::INSTANCEOF, $args); 37 | 38 | $decoreatedInstanceofMethodCall = $this->serviceOptionNodeFactory->convertServiceOptionsToNodes( 39 | $values, 40 | $instanceofMethodCall 41 | ); 42 | 43 | return new Expression($decoreatedInstanceofMethodCall); 44 | } 45 | 46 | public function isMatch(string $rootKey, int|string $subKey): bool 47 | { 48 | if ($rootKey !== YamlKey::SERVICES) { 49 | return false; 50 | } 51 | 52 | if (! is_string($subKey)) { 53 | return false; 54 | } 55 | 56 | return $subKey === YamlKey::_INSTANCEOF; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/CaseConverter/ParameterCaseConverter.php: -------------------------------------------------------------------------------- 1 | prefixWithDirConstantIfExistingPath($values); 43 | } 44 | 45 | if (is_array($values)) { 46 | foreach ($values as $subKey => $subValue) { 47 | if (! is_string($subValue)) { 48 | continue; 49 | } 50 | 51 | $values[$subKey] = $this->prefixWithDirConstantIfExistingPath($subValue); 52 | } 53 | } 54 | 55 | $args = $this->argsNodeFactory->createFromValues([$key, $values]); 56 | 57 | $parametersVariable = new Variable(VariableName::PARAMETERS); 58 | $methodCall = new MethodCall($parametersVariable, MethodName::SET, $args); 59 | 60 | return new Expression($methodCall); 61 | } 62 | 63 | private function prefixWithDirConstantIfExistingPath(string $value): string | Expr 64 | { 65 | $filePath = $this->currentFilePathProvider->getFilePath(); 66 | if ($filePath === null) { 67 | return $value; 68 | } 69 | 70 | $configDirectory = dirname($filePath); 71 | 72 | $possibleConfigPath = $configDirectory . '/' . $value; 73 | if (is_file($possibleConfigPath)) { 74 | return $this->commonNodeFactory->createAbsoluteDirExpr($value); 75 | } 76 | 77 | if (is_dir($possibleConfigPath)) { 78 | return $this->commonNodeFactory->createAbsoluteDirExpr($value); 79 | } 80 | 81 | return $value; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/CaseConverter/ResourceCaseConverter.php: -------------------------------------------------------------------------------- 1 | servicesPhpNodeFactory->createResource($key, $values); 28 | } 29 | 30 | public function match(string $rootKey, mixed $key, mixed $values): bool 31 | { 32 | return isset($values[YamlKey::RESOURCE]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/CaseConverter/ServicesDefaultsCaseConverter.php: -------------------------------------------------------------------------------- 1 | createServicesVariable(), MethodName::DEFAULTS); 27 | 28 | $decoratedMethodCall = $this->autoBindNodeFactory->createAutoBindCalls($values, $methodCall); 29 | 30 | return new Expression($decoratedMethodCall); 31 | } 32 | 33 | public function match(string $rootKey, mixed $key, mixed $values): bool 34 | { 35 | if ($rootKey !== YamlKey::SERVICES) { 36 | return false; 37 | } 38 | 39 | return $key === YamlKey::_DEFAULTS; 40 | } 41 | 42 | private function createServicesVariable(): Variable 43 | { 44 | return new Variable(VariableName::SERVICES); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Contract/CaseConverterInterface.php: -------------------------------------------------------------------------------- 1 | stringExprResolver->resolve($value, $skipServiceReference, false); 30 | 31 | if ($skipServiceReference) { 32 | return $expr; 33 | } 34 | 35 | $args = [new Arg($expr)]; 36 | return new FuncCall(new FullyQualified($functionName), $args); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ExprResolver/StringExprResolver.php: -------------------------------------------------------------------------------- 1 | constantNodeFactory->createConstant($const); 47 | } 48 | 49 | $classConstFetch = $this->constantNodeFactory->createClassConstantIfValue($value); 50 | if ($classConstFetch instanceof ClassConstFetch) { 51 | return $classConstFetch; 52 | } 53 | 54 | // do not print "\n" as empty space, but use string value instead 55 | if (in_array($value, ["\r", "\n", "\r\n"], true)) { 56 | return $this->keepNewline($value); 57 | } 58 | 59 | $value = ltrim($value, '\\'); 60 | if ($this->isClassType($value)) { 61 | return $this->resolveClassType($skipClassesToConstantReference, $value); 62 | } 63 | 64 | if (str_starts_with($value, '@=')) { 65 | return $this->resolveExpr($value, $skipServiceReference, $skipClassesToConstantReference); 66 | } 67 | 68 | // is service reference 69 | if (str_starts_with($value, '@') && ! $this->isFilePath($value)) { 70 | return $this->resolveServiceReferenceExpr( 71 | $value, 72 | $skipServiceReference, 73 | ReferenceFunctionNameResolver::resolve(), 74 | $isRoutingImport 75 | ); 76 | } 77 | 78 | return BuilderHelpers::normalizeValue($value); 79 | } 80 | 81 | private function resolveExpr( 82 | string $value, 83 | bool $skipServiceReference, 84 | bool $skipClassesToConstantReference 85 | ): FuncCall { 86 | $value = ltrim($value, '@='); 87 | $expr = $this->resolve($value, $skipServiceReference, $skipClassesToConstantReference); 88 | $args = [new Arg($expr)]; 89 | 90 | return new FuncCall(new FullyQualified(FunctionName::EXPR), $args); 91 | } 92 | 93 | private function keepNewline(string $value): String_ 94 | { 95 | $string = new String_($value); 96 | $string->setAttribute('kind', String_::KIND_DOUBLE_QUOTED); 97 | 98 | return $string; 99 | } 100 | 101 | private function isFilePath(string $value): bool 102 | { 103 | return (bool) Strings::match($value, self::TWIG_HTML_XML_SUFFIX_REGEX); 104 | } 105 | 106 | private function resolveClassType(bool $skipClassesToConstantReference, string $value): String_ | ClassConstFetch 107 | { 108 | if ($skipClassesToConstantReference) { 109 | return new String_($value); 110 | } 111 | 112 | return $this->commonNodeFactory->createClassReference($value); 113 | } 114 | 115 | private function isClassType(string $value): bool 116 | { 117 | if (! ctype_upper($value[0])) { 118 | return false; 119 | } 120 | 121 | // to avoid autoload in case of missing code sniffer dependency 122 | if (str_ends_with($value, 'Sniff')) { 123 | return true; 124 | } 125 | 126 | if (str_ends_with($value, 'Fixer')) { 127 | return true; 128 | } 129 | 130 | if (class_exists($value)) { 131 | return true; 132 | } 133 | 134 | return interface_exists($value); 135 | } 136 | 137 | /** 138 | * @param FunctionName::* $functionName 139 | */ 140 | private function resolveServiceReferenceExpr( 141 | string $value, 142 | bool $skipServiceReference, 143 | string $functionName, 144 | bool $isRoutingImport = false 145 | ): Expr { 146 | $value = ltrim($value, '@'); 147 | $expr = $this->resolve($value, $skipServiceReference, false); 148 | 149 | if ($skipServiceReference) { 150 | // return the `@` back 151 | if ($isRoutingImport && $expr instanceof String_) { 152 | $expr->value = '@' . $expr->value; 153 | } 154 | 155 | return $expr; 156 | } 157 | 158 | $args = [new Arg($expr)]; 159 | return new FuncCall(new FullyQualified($functionName), $args); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/ExprResolver/TaggedReturnsCloneResolver.php: -------------------------------------------------------------------------------- 1 | getValue()[0]; 22 | 23 | $expr = $this->serviceReferenceExprResolver->resolveServiceReferenceExpr( 24 | $serviceName, 25 | false, 26 | ReferenceFunctionNameResolver::resolve() 27 | ); 28 | 29 | return new Array_([new ArrayItem($expr)]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ExprResolver/TaggedServiceResolver.php: -------------------------------------------------------------------------------- 1 | getValue()['class']; 21 | return $this->serviceReferenceExprResolver->resolveServiceReferenceExpr( 22 | $serviceName, 23 | false, 24 | FunctionName::INLINE_SERVICE 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Naming/ClassNaming.php: -------------------------------------------------------------------------------- 1 | normalizeUpperCase($shortClassName); 19 | return lcfirst($normalizedShortClassName); 20 | } 21 | 22 | private function normalizeUpperCase(string $shortClassName): string 23 | { 24 | // turns $SOMEUppercase => $someUppercase 25 | for ($i = 0; $i <= strlen($shortClassName); ++$i) { 26 | if (ctype_upper($shortClassName[$i]) && $this->isNumberOrUpper($shortClassName[$i + 1])) { 27 | $shortClassName[$i] = strtolower($shortClassName[$i]); 28 | } else { 29 | break; 30 | } 31 | } 32 | 33 | return $shortClassName; 34 | } 35 | 36 | private function isNumberOrUpper(string $char): bool 37 | { 38 | if (ctype_upper($char)) { 39 | return true; 40 | } 41 | 42 | return ctype_digit($char); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/NodeFactory/ArgsNodeFactory.php: -------------------------------------------------------------------------------- 1 | isPhpNamedArguments = PHP_VERSION_ID >= 80000; 45 | } 46 | 47 | /** 48 | * @return Arg[] 49 | */ 50 | public function createFromValuesAndWrapInArray(mixed $values): array 51 | { 52 | if (is_array($values)) { 53 | $array = $this->resolveExprFromArray($values); 54 | } else { 55 | $expr = $this->resolveExpr($values); 56 | $items = [new ArrayItem($expr)]; 57 | $array = new Array_($items); 58 | } 59 | 60 | return [new Arg($array)]; 61 | } 62 | 63 | /** 64 | * @return mixed[]|Arg[] 65 | */ 66 | public function createFromValues( 67 | mixed $values, 68 | bool $skipServiceReference = false, 69 | bool $skipClassesToConstantReference = false, 70 | bool $isForConfig = false, 71 | bool $isRoutingImport = false 72 | ): array { 73 | if (is_array($values)) { 74 | $args = []; 75 | foreach ($values as $key => $value) { 76 | $expr = $this->resolveExpr( 77 | $value, 78 | $skipServiceReference, 79 | $skipClassesToConstantReference, 80 | $isRoutingImport 81 | ); 82 | $args = $this->resolveArgs($args, $key, $expr, $isForConfig); 83 | } 84 | 85 | return $args; 86 | } 87 | 88 | if ($values instanceof Node) { 89 | if ($values instanceof Arg) { 90 | return [$values]; 91 | } 92 | 93 | if ($values instanceof Expr) { 94 | return [new Arg($values)]; 95 | } 96 | } 97 | 98 | if (is_string($values)) { 99 | $expr = $this->resolveExpr($values); 100 | return [new Arg($expr)]; 101 | } 102 | 103 | throw new NotImplementedYetException(); 104 | } 105 | 106 | public function resolveExpr( 107 | mixed $value, 108 | bool $skipServiceReference = false, 109 | bool $skipClassesToConstantReference = false, 110 | bool $isRoutingImport = false 111 | ): Expr { 112 | if (is_string($value)) { 113 | return $this->stringExprResolver->resolve( 114 | $value, 115 | $skipServiceReference, 116 | $skipClassesToConstantReference, 117 | $isRoutingImport 118 | ); 119 | } 120 | 121 | if ($value instanceof Expr) { 122 | return $value; 123 | } 124 | 125 | if ($value instanceof TaggedValue) { 126 | return $this->createServiceReferenceFromTaggedValue($value); 127 | } 128 | 129 | if (is_array($value)) { 130 | $arrayItems = $this->resolveArrayItems($value, $skipClassesToConstantReference); 131 | return new Array_($arrayItems); 132 | } 133 | 134 | if (is_object($value)) { 135 | return $this->newValueObjectFactory->create($value); 136 | } 137 | 138 | return BuilderHelpers::normalizeValue($value); 139 | } 140 | 141 | /** 142 | * @param mixed[] $values 143 | */ 144 | public function resolveExprFromArray(array $values): Array_ 145 | { 146 | $arrayItems = []; 147 | 148 | $hasNaturalKeysOrder = $this->hasNaturalKeyOrder($values); 149 | 150 | foreach ($values as $key => $value) { 151 | $expr = is_array($value) ? $this->resolveExprFromArray($value) : $this->resolveExpr($value); 152 | 153 | if (! is_int($key) || ! $hasNaturalKeysOrder) { 154 | $keyExpr = $this->resolveExpr($key); 155 | $arrayItem = new ArrayItem($expr, $keyExpr); 156 | } else { 157 | $arrayItem = new ArrayItem($expr); 158 | } 159 | 160 | $arrayItems[] = $arrayItem; 161 | } 162 | 163 | return new Array_($arrayItems); 164 | } 165 | 166 | /** 167 | * @param Arg[] $args 168 | * @return Arg[] 169 | */ 170 | private function resolveArgs(array $args, mixed $key, Expr $expr, bool $isForConfig): array 171 | { 172 | if (is_string($key) && $isForConfig) { 173 | $key = $this->resolveExpr($key); 174 | $args[] = new Arg(new Array_([new ArrayItem($expr, $key)])); 175 | 176 | return $args; 177 | } 178 | 179 | if (! is_int($key) && $this->isPhpNamedArguments) { 180 | $args[] = new Arg($expr, false, false, [], new Identifier($key)); 181 | 182 | return $args; 183 | } 184 | 185 | $args[] = new Arg($expr); 186 | return $args; 187 | } 188 | 189 | private function createServiceReferenceFromTaggedValue(TaggedValue $taggedValue): Expr 190 | { 191 | // that's the only value 192 | if ($taggedValue->getTag() === self::TAG_RETURNS_CLONE) { 193 | return $this->taggedReturnsCloneResolver->resolve($taggedValue); 194 | } 195 | 196 | if ($taggedValue->getTag() === self::TAG_SERVICE) { 197 | return $this->taggedServiceResolver->resolve($taggedValue); 198 | } 199 | 200 | $name = match ($taggedValue->getTag()) { 201 | 'tagged_iterator' => new FullyQualified(FunctionName::TAGGED_ITERATOR), 202 | 'tagged_locator' => new FullyQualified(FunctionName::TAGGED_LOCATOR), 203 | default => new Name($taggedValue->getTag()) 204 | }; 205 | 206 | $args = $this->createFromValues($taggedValue->getValue()); 207 | 208 | return new FuncCall($name, $args); 209 | } 210 | 211 | /** 212 | * @param mixed[] $value 213 | * @return ArrayItem[] 214 | */ 215 | private function resolveArrayItems(array $value, bool $skipClassesToConstantReference): array 216 | { 217 | $arrayItems = []; 218 | 219 | $naturalKey = 0; 220 | foreach ($value as $nestedKey => $nestedValue) { 221 | $valueExpr = $this->resolveExpr($nestedValue, false, $skipClassesToConstantReference); 222 | 223 | if (! is_int($nestedKey) || $nestedKey !== $naturalKey) { 224 | $keyExpr = $this->resolveExpr($nestedKey, false, $skipClassesToConstantReference); 225 | $arrayItem = new ArrayItem($valueExpr, $keyExpr); 226 | } else { 227 | $arrayItem = new ArrayItem($valueExpr); 228 | } 229 | 230 | $arrayItems[] = $arrayItem; 231 | 232 | ++$naturalKey; 233 | } 234 | 235 | return $arrayItems; 236 | } 237 | 238 | /** 239 | * Detects, if the array keys are implicit - natural order integers: 0, 1, 2, 3... 240 | * 241 | * @param array $values 242 | */ 243 | private function hasNaturalKeyOrder(array $values): bool 244 | { 245 | $valueCount = count($values); 246 | $naturalOrderKeys = range(0, $valueCount - 1); 247 | 248 | return $naturalOrderKeys === array_keys($values); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/NodeFactory/CommonNodeFactory.php: -------------------------------------------------------------------------------- 1 | createConstFetch($className, 'class'); 41 | } 42 | 43 | public function createConstFetch(string $className, string $constantName): ClassConstFetch 44 | { 45 | return new ClassConstFetch(new FullyQualified($className), $constantName); 46 | } 47 | 48 | public function createFalse(): ConstFetch 49 | { 50 | return new ConstFetch(new Name('false')); 51 | } 52 | 53 | public function createTrue(): ConstFetch 54 | { 55 | return new ConstFetch(new Name('true')); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/NodeFactory/ConstantNodeFactory.php: -------------------------------------------------------------------------------- 1 | createClassConstantIfValue($value, false); 52 | if ($classConstFetch instanceof ClassConstFetch) { 53 | return $classConstFetch; 54 | } 55 | 56 | return new ConstFetch(new Name($value)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/NodeFactory/ContainerConfiguratorReturnClosureFactory.php: -------------------------------------------------------------------------------- 1 | $arrayData 42 | * @param string $containerConfiguratorClass Must remain string to avoid prefixing 43 | */ 44 | public function createFromYamlArray( 45 | array $arrayData, 46 | string $containerConfiguratorClass = 'Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator' 47 | ): Return_ { 48 | $stmts = $this->createClosureStmts($arrayData); 49 | 50 | $closure = $this->configuratorClosureNodeFactory->createContainerClosureFromStmts( 51 | $stmts, 52 | $containerConfiguratorClass 53 | ); 54 | 55 | return new Return_($closure); 56 | } 57 | 58 | /** 59 | * @param mixed[] $yamlData 60 | * @return Stmt[] 61 | */ 62 | private function createClosureStmts(array $yamlData): array 63 | { 64 | $yamlData = array_filter($yamlData); 65 | return $this->createStmtsFromCaseConverters($yamlData); 66 | } 67 | 68 | /** 69 | * @param array $yamlData 70 | * @return Stmt[] 71 | */ 72 | private function createStmtsFromCaseConverters(array $yamlData): array 73 | { 74 | $stmts = []; 75 | 76 | foreach ($yamlData as $key => $values) { 77 | // keys can be int, but we need string 78 | if (! is_string($key)) { 79 | $key = (string) $key; 80 | } 81 | 82 | $stmts = $this->createInitializeStmt($key, $stmts); 83 | 84 | foreach ($values as $nestedKey => $nestedValues) { 85 | $nestedNodes = $this->processNestedNodes($key, $nestedKey, $nestedValues); 86 | 87 | if ($nestedNodes !== []) { 88 | $stmts = array_merge($stmts, $nestedNodes); 89 | continue; 90 | } 91 | 92 | $expression = $this->resolveStmt($key, $nestedKey, $nestedValues); 93 | if (! $expression instanceof Expression) { 94 | continue; 95 | } 96 | 97 | $lastNode = end($stmts); 98 | $node = $this->resolveExpressionWhenAtEnv($expression, $key, $lastNode); 99 | if ($node !== null) { 100 | $stmts[] = $node; 101 | } 102 | } 103 | } 104 | 105 | return $stmts; 106 | } 107 | 108 | /** 109 | * @return Expression[]|mixed[] 110 | */ 111 | private function processNestedNodes(string $key, int|string $nestedKey, mixed $nestedValues): array 112 | { 113 | if (is_array($nestedValues)) { 114 | return $this->containerNestedNodesFactory->createFromValues($nestedValues, $key, $nestedKey); 115 | } 116 | 117 | return []; 118 | } 119 | 120 | private function resolveExpressionWhenAtEnv( 121 | Expression $expression, 122 | string $key, 123 | Expression|If_|bool $lastNode 124 | ): Expression|If_|null { 125 | $explodeAt = explode('@', $key); 126 | if (str_starts_with($key, 'when@') && count($explodeAt) === 2) { 127 | $variable = new Variable(VariableName::CONTAINER_CONFIGURATOR); 128 | 129 | $expr = $expression->expr; 130 | if (! $expr instanceof MethodCall) { 131 | throw new ShouldNotHappenException(); 132 | } 133 | 134 | $args = $expr->getArgs(); 135 | 136 | if (! isset($args[1]) || ! $args[1]->value instanceof Array_ || ! isset($args[1]->value->items[0]) 137 | || ! $args[1]->value->items[0] instanceof ArrayItem || ! $args[1]->value->items[0]->key instanceof Expr) { 138 | throw new ShouldNotHappenException(); 139 | } 140 | 141 | $newExpression = new Expression( 142 | new MethodCall( 143 | $variable, 144 | 'extension', 145 | [new Arg($args[1]->value->items[0]->key), new Arg($args[1]->value->items[0]->value)] 146 | ) 147 | ); 148 | 149 | $environmentString = new String_($explodeAt[1]); 150 | $envMethodCall = new MethodCall($variable, 'env'); 151 | 152 | $identical = new Identical($envMethodCall, $environmentString); 153 | 154 | if ($lastNode instanceof If_ && $this->isSameCond($lastNode->cond, $identical)) { 155 | $lastNode->stmts[] = $newExpression; 156 | return null; 157 | } 158 | 159 | $if = new If_($identical); 160 | $if->stmts = [$newExpression]; 161 | 162 | return $if; 163 | } 164 | 165 | return $expression; 166 | } 167 | 168 | private function isSameCond(Expr $expr, Identical $identical): bool 169 | { 170 | if ($expr instanceof Identical) { 171 | $val1 = Json::encode($expr); 172 | $val2 = Json::encode($identical); 173 | return $val1 === $val2; 174 | } 175 | 176 | return false; 177 | } 178 | 179 | /** 180 | * @param VariableMethodName::* $variableMethodName 181 | */ 182 | private function createInitializeAssign(string $variableMethodName): Expression 183 | { 184 | $servicesVariable = new Variable($variableMethodName); 185 | $containerConfiguratorVariable = new Variable(VariableName::CONTAINER_CONFIGURATOR); 186 | 187 | $assign = new Assign($servicesVariable, new MethodCall($containerConfiguratorVariable, $variableMethodName)); 188 | 189 | return new Expression($assign); 190 | } 191 | 192 | /** 193 | * @param Stmt[] $stmts 194 | * @return Stmt[] 195 | */ 196 | private function createInitializeStmt(string $key, array $stmts): array 197 | { 198 | if ($key === YamlKey::SERVICES) { 199 | $stmts[] = $this->createInitializeAssign(VariableMethodName::SERVICES); 200 | return $stmts; 201 | } 202 | 203 | if ($key === YamlKey::PARAMETERS) { 204 | $stmts[] = $this->createInitializeAssign(VariableMethodName::PARAMETERS); 205 | return $stmts; 206 | } 207 | 208 | return $stmts; 209 | } 210 | 211 | private function resolveStmt(string $key, int | string $nestedKey, mixed $nestedValues): ?Stmt 212 | { 213 | foreach ($this->caseConverters as $caseConverter) { 214 | if (! $caseConverter->match($key, $nestedKey, $nestedValues)) { 215 | continue; 216 | } 217 | 218 | /** @var string $nestedKey */ 219 | return $caseConverter->convertToMethodCallStmt($nestedKey, $nestedValues); 220 | } 221 | 222 | return null; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/NodeFactory/ContainerNestedNodesFactory.php: -------------------------------------------------------------------------------- 1 | $subNestedValue) { 27 | if (! $this->instanceOfNestedCaseConverter->isMatch($key, $nestedKey)) { 28 | continue; 29 | } 30 | 31 | $nestedStmts[] = $this->instanceOfNestedCaseConverter->convertToMethodCall( 32 | $subNestedKey, 33 | $subNestedValue 34 | ); 35 | } 36 | 37 | return $nestedStmts; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/NodeFactory/NewValueObjectFactory.php: -------------------------------------------------------------------------------- 1 | getKey()); 24 | } 25 | 26 | // assumption that constructor parameters share the same value as property names 27 | $propertyValues = $this->resolvePropertyValuesFromValueObject($valueObjectClass, $valueObject); 28 | $args = $this->createArgs($propertyValues); 29 | 30 | return new New_(new FullyQualified($valueObjectClass), $args); 31 | } 32 | 33 | /** 34 | * @param class-string $valueObjectClass 35 | * @return mixed[] 36 | */ 37 | private function resolvePropertyValuesFromValueObject(string $valueObjectClass, object $valueObject): array 38 | { 39 | $reflectionClass = new ReflectionClass($valueObjectClass); 40 | $propertyValues = []; 41 | 42 | foreach ($reflectionClass->getProperties() as $reflectionProperty) { 43 | $reflectionProperty->setAccessible(true); 44 | 45 | $defaultPropertyValue = $reflectionProperty->getDefaultValue(); 46 | $currentPropertyValue = $reflectionProperty->getValue($valueObject); 47 | 48 | // do not fill in default values 49 | if ($defaultPropertyValue === $currentPropertyValue) { 50 | continue; 51 | } 52 | 53 | $propertyValues[] = $currentPropertyValue; 54 | } 55 | 56 | return $propertyValues; 57 | } 58 | 59 | /** 60 | * @param mixed[] $propertyValues 61 | * @return Arg[] 62 | */ 63 | private function createArgs(array $propertyValues): array 64 | { 65 | $args = []; 66 | foreach ($propertyValues as $propertyValue) { 67 | if (is_object($propertyValue)) { 68 | $nestedValueObject = $this->create($propertyValue); 69 | $args[] = new Arg($nestedValueObject); 70 | } elseif (is_array($propertyValue)) { 71 | $args[] = new Arg(new Array_($this->createArgs($propertyValue))); 72 | } else { 73 | $args[] = new Arg(BuilderHelpers::normalizeValue($propertyValue)); 74 | } 75 | } 76 | 77 | return $args; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/NodeFactory/RoutingConfiguratorReturnClosureFactory.php: -------------------------------------------------------------------------------- 1 | createClosureStmts($arrayData); 29 | $closure = $this->containerConfiguratorClosureNodeFactory->createRoutingClosureFromStmts($stmts); 30 | 31 | return new Return_($closure); 32 | } 33 | 34 | /** 35 | * @param mixed[] $arrayData 36 | * @return Stmt[] 37 | */ 38 | public function createClosureStmts(array $arrayData): array 39 | { 40 | $arrayData = $this->removeEmptyValues($arrayData); 41 | return $this->createStmtsFromCaseConverters($arrayData); 42 | } 43 | 44 | /** 45 | * @param mixed[] $yamlData 46 | * @return mixed[] 47 | */ 48 | private function removeEmptyValues(array $yamlData): array 49 | { 50 | return array_filter($yamlData); 51 | } 52 | 53 | /** 54 | * @param mixed[] $arrayData 55 | * @return Stmt[] 56 | */ 57 | private function createStmtsFromCaseConverters(array $arrayData): array 58 | { 59 | $stmts = []; 60 | 61 | foreach ($arrayData as $key => $values) { 62 | $stmt = null; 63 | 64 | foreach ($this->routingCaseConverters as $routingCaseConverter) { 65 | if (! $routingCaseConverter->match($key, $values)) { 66 | continue; 67 | } 68 | 69 | $stmt = $routingCaseConverter->convertToMethodCall($key, $values); 70 | break; 71 | } 72 | 73 | if (! $stmt instanceof Stmt) { 74 | continue; 75 | } 76 | 77 | $stmts[] = $stmt; 78 | } 79 | 80 | return $stmts; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/NodeFactory/Service/AutoBindNodeFactory.php: -------------------------------------------------------------------------------- 1 | autowire() 23 | * ->autoconfigure() 24 | * ->bind() 25 | * 26 | * @param mixed[] $yaml 27 | */ 28 | public function createAutoBindCalls(array $yaml, MethodCall $methodCall): MethodCall 29 | { 30 | foreach ($yaml as $key => $value) { 31 | if ($key === YamlKey::AUTOWIRE) { 32 | $methodCall = $this->createAutowire($value, $methodCall); 33 | } 34 | 35 | if ($key === YamlKey::AUTOCONFIGURE) { 36 | $methodCall = $this->createAutoconfigure($value, $methodCall); 37 | } 38 | 39 | if ($key === YamlKey::PUBLIC) { 40 | $methodCall = $this->createPublicPrivate($value, $methodCall); 41 | } 42 | 43 | if ($key === YamlKey::BIND) { 44 | // skip empty definitions 45 | if (empty($yaml[YamlKey::BIND])) { 46 | continue; 47 | } 48 | 49 | $methodCall = $this->createBindMethodCall($methodCall, $yaml[YamlKey::BIND]); 50 | } 51 | 52 | if ($key === YamlKey::TAGS) { 53 | $methodCall = $this->tagsServiceOptionKeyYamlToPhpFactory->decorateServiceMethodCall( 54 | null, 55 | $value, 56 | [], 57 | $methodCall 58 | ); 59 | } 60 | } 61 | 62 | return $methodCall; 63 | } 64 | 65 | /** 66 | * @param mixed[] $bindValues 67 | */ 68 | private function createBindMethodCall(MethodCall $methodCall, array $bindValues): MethodCall 69 | { 70 | foreach ($bindValues as $key => $value) { 71 | $args = $this->argsNodeFactory->createFromValues([$key, $value]); 72 | $methodCall = new MethodCall($methodCall, YamlKey::BIND, $args); 73 | } 74 | 75 | return $methodCall; 76 | } 77 | 78 | private function createAutowire(mixed $value, MethodCall $methodCall): MethodCall 79 | { 80 | if ($value === true) { 81 | return new MethodCall($methodCall, YamlKey::AUTOWIRE); 82 | } 83 | 84 | return $methodCall; 85 | } 86 | 87 | private function createAutoconfigure(mixed $value, MethodCall $methodCall): MethodCall 88 | { 89 | if ($value === true) { 90 | return new MethodCall($methodCall, YamlKey::AUTOCONFIGURE); 91 | } 92 | 93 | return $methodCall; 94 | } 95 | 96 | private function createPublicPrivate(mixed $value, MethodCall $methodCall): MethodCall 97 | { 98 | if ($value !== false) { 99 | return new MethodCall($methodCall, 'public'); 100 | } 101 | 102 | // default value 103 | return $methodCall; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/NodeFactory/Service/ServiceOptionNodeFactory.php: -------------------------------------------------------------------------------- 1 | unNestArguments($servicesValues); 32 | 33 | foreach ($servicesValues as $key => $value) { 34 | if ($this->shouldSkip($key)) { 35 | continue; 36 | } 37 | 38 | foreach ($this->serviceOptionKeyYamlToPhpFactories as $serviceOptionKeyYamlToPhpFactory) { 39 | if (! $serviceOptionKeyYamlToPhpFactory->isMatch($key, $value)) { 40 | continue; 41 | } 42 | 43 | $methodCall = $serviceOptionKeyYamlToPhpFactory->decorateServiceMethodCall( 44 | $key, 45 | $value, 46 | $servicesValues, 47 | $methodCall 48 | ); 49 | 50 | continue 2; 51 | } 52 | } 53 | 54 | return $methodCall; 55 | } 56 | 57 | /** 58 | * @param array $servicesValues 59 | * @return array|array> 60 | */ 61 | private function unNestArguments(array $servicesValues): array 62 | { 63 | if (! $this->serviceOptionAnalyzer->hasNamedArguments($servicesValues)) { 64 | return $servicesValues; 65 | } 66 | 67 | return [ 68 | YamlServiceKey::ARGUMENTS => $servicesValues, 69 | ]; 70 | } 71 | 72 | private function shouldSkip(string|int $key): bool 73 | { 74 | if (\is_int($key)) { 75 | return false; 76 | } 77 | 78 | // options started by decoration_