Created
October 1, 2025 13:25
-
-
Save GromNaN/49ebdd48f1922a201c353145efdee9b2 to your computer and use it in GitHub Desktop.
Convert XML services configuration files into PHP DSL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| if ($argc < 2) { | |
| echo "Usage: php convert_xml_to_php.php src/Symfony/Bundle/Resources/config/\n"; | |
| exit(1); | |
| } | |
| function inline_var_export($value) | |
| { | |
| $php = var_export($value, true); | |
| $php = preg_replace('/\s+/', ' ', $php); | |
| return str_replace('\\\\', '\\', $php); | |
| } | |
| function convertValueToPhp(mixed $value): mixed | |
| { | |
| if (is_array($value)) { | |
| foreach ($value as &$v) { | |
| $v = convertValueToPhp($v); | |
| } | |
| return $value; | |
| } | |
| if (null !== filter_var($value, \FILTER_VALIDATE_INT, \FILTER_NULL_ON_FAILURE)) { | |
| return (int) $value; | |
| } | |
| if (null !== filter_var($value, \FILTER_VALIDATE_FLOAT, \FILTER_NULL_ON_FAILURE)) { | |
| return (float) $value; | |
| } | |
| if (null === $value || 'null' === strtolower($value)) { | |
| return null; | |
| } | |
| if (in_array(strtolower($value), ['true', 'false'], true)) { | |
| return 'true' === strtolower($value); | |
| } | |
| return $value; | |
| } | |
| function xmlToPhpDslValue($argument) | |
| { | |
| $argType = isset($argument['type']) ? (string) $argument['type'] : null; | |
| $argValue = (string) $argument; | |
| $argId = isset($argument['id']) ? (string) $argument['id'] : null; | |
| $onInvalid = isset($argument['on-invalid']) ? (string) $argument['on-invalid'] : null; | |
| // Inline service definition | |
| if (isset($argument->service)) { | |
| $inlineService = $argument->service; | |
| $class = isset($inlineService['class']) ? (string) $inlineService['class'] : null; | |
| $inlineCall = "inline_service('$class')"; | |
| // Handle inline service arguments | |
| $inlineArgIndex = 0; | |
| foreach ($inlineService->argument as $inlineArg) { | |
| $inlineArgKey = isset($inlineArg['key']) ? (string) $inlineArg['key'] : null; | |
| $inlineArgType = isset($inlineArg['type']) ? (string) $inlineArg['type'] : null; | |
| // Collection type | |
| if ('collection' === $inlineArgType) { | |
| $collectionItems = []; | |
| foreach ($inlineArg->argument as $collectionArg) { | |
| $collectionItems[] = xmlToPhpDslValue($collectionArg); | |
| } | |
| $inlineArgValue = '['.implode(', ', $collectionItems).']'; | |
| } else { | |
| $inlineArgValue = xmlToPhpDslValue($inlineArg); | |
| } | |
| if ($inlineArgKey) { | |
| $inlineCall .= "->arg('".$inlineArgKey."', $inlineArgValue)"; | |
| } else { | |
| $inlineCall .= "->arg($inlineArgIndex, $inlineArgValue)"; | |
| } | |
| ++$inlineArgIndex; | |
| } | |
| // Handle inline service tags | |
| foreach ($inlineService->tag as $tag) { | |
| $tagName = (string) $tag['name']; | |
| $tagAttrs = []; | |
| foreach ($tag->attributes() as $attrName => $attrValue) { | |
| if ('name' !== $attrName) { | |
| $tagAttrs[$attrName] = (string) $attrValue; | |
| } | |
| } | |
| if (!empty($tagAttrs)) { | |
| $inlineCall .= "->tag('$tagName', ".inline_var_export(convertValueToPhp($tagAttrs)).')'; | |
| } else { | |
| $inlineCall .= "->tag('$tagName')"; | |
| } | |
| } | |
| // Handle inline service calls | |
| foreach ($inlineService->call as $call) { | |
| $method = (string) $call['method']; | |
| $callArgs = []; | |
| foreach ($call->argument as $callArg) { | |
| $callArgs[] = xmlToPhpDslValue($callArg); | |
| } | |
| $inlineCall .= "->call('$method', [".implode(', ', $callArgs).'])'; | |
| } | |
| return $inlineCall; | |
| } | |
| if ('service' === $argType && $argId) { | |
| $call = "service('$argId')"; | |
| if ('ignore' === $onInvalid) { | |
| $call .= '->ignoreOnInvalid()'; | |
| } elseif ('null' === $onInvalid) { | |
| $call .= '->nullOnInvalid()'; | |
| } | |
| return $call; | |
| } | |
| if ('collection' === $argType) { | |
| $collectionItems = []; | |
| foreach ($argument->argument as $collectionArg) { | |
| $collectionItems[] = ($collectionArg['key'] ? "'".$collectionArg['key']."' => " : '').xmlToPhpDslValue($collectionArg); | |
| } | |
| return '['.implode(', ', $collectionItems).']'; | |
| } | |
| if ('tagged_iterator' === $argType && isset($argument['tag'])) { | |
| return "tagged_iterator('".$argument['tag']."')"; | |
| } | |
| if ('tagged_locator' === $argType && isset($argument['tag'])) { | |
| $indexBy = isset($argument['index-by']) ? (string) $argument['index-by'] : null; | |
| $call = "tagged_locator('".$argument['tag']."'"; | |
| if ($indexBy) { | |
| $call .= ", '$indexBy'"; | |
| } | |
| $call .= ')'; | |
| return $call; | |
| } | |
| return inline_var_export(convertValueToPhp($argValue)); | |
| } | |
| function transformXmlToPhpFile(string $xmlPath): void | |
| { | |
| if (!file_exists($xmlPath)) { | |
| echo "File not found: $xmlPath\n"; | |
| return; | |
| } | |
| echo "Processing: $xmlPath\n"; | |
| if (!str_ends_with($xmlPath, '.xml')) { | |
| echo "Error: The file must have a .xml extension.\n"; | |
| return; | |
| } | |
| $xml = simplexml_load_file($xmlPath); | |
| if (!$xml) { | |
| echo "Failed to parse XML file.\n"; | |
| return; | |
| } | |
| // Check for required xmlns attribute in root | |
| $namespaces = $xml->getNamespaces(true); | |
| if (!isset($xml['xmlns']) && (!isset($namespaces['']) || 'http://symfony.com/schema/dic/services' !== $namespaces[''])) { | |
| echo "Error: The root element must contain xmlns=\"http://symfony.com/schema/dic/services\"\n"; | |
| return; | |
| } | |
| $phpPath = preg_replace('/\.xml$/', '.php', $xmlPath); | |
| $php = <<<PHP | |
| <?php | |
| namespace Symfony\Component\DependencyInjection\Loader\Configurator; | |
| return function (ContainerConfigurator \$container) { | |
| \$services = \$container->services(); | |
| PHP; | |
| foreach ($xml->services->service as $service) { | |
| $id = (string) $service['id']; | |
| if (isset($service['alias'])) { | |
| $aliasId = (string) $service['id']; | |
| $aliasTo = (string) $service['alias']; | |
| $setCall = " \$services->alias('$aliasId', '$aliasTo')"; | |
| } else { | |
| $class = isset($service['class']) ? (string) $service['class'] : null; | |
| $setCall = " \$services->set('$id'"; | |
| if ($class) { | |
| $setCall .= ", '$class'"; | |
| } | |
| $setCall .= ')'; | |
| } | |
| // Handle attributes | |
| if (isset($service['public']) && ('true' == $service['public'] || '1' == $service['public'])) { | |
| $setCall .= "\n ->public()"; | |
| } | |
| if (isset($service['abstract']) && ('true' == $service['abstract'] || '1' == $service['abstract'])) { | |
| $setCall .= "\n ->abstract()"; | |
| } | |
| if (isset($service['parent'])) { | |
| $setCall .= "\n ->parent('".$service['parent']."')"; | |
| } | |
| if (isset($service['decorates'])) { | |
| $decorateArgs = "'".$service['decorates']."'"; | |
| $decorateArgs .= isset($service['decoration-inner-name']) ? ", '".$service['decoration-inner-name']."'" : ', null'; | |
| $decorateArgs .= isset($service['decoration-priority']) ? ', '.(int) $service['decoration-priority'] : ', 0'; | |
| $setCall .= "\n ->decorate($decorateArgs)"; | |
| } | |
| if (isset($service['factory'])) { | |
| $factory = $service['factory']; | |
| $factoryParts = explode(':', $factory); | |
| if (2 == count($factoryParts)) { | |
| $setCall .= "->factory([service('".$factoryParts[0]."'), '".$factoryParts[1]."'])"; | |
| } else { | |
| $setCall .= "->factory('".$factory."')"; | |
| } | |
| } | |
| // Handle <factory> child | |
| if (isset($service->factory)) { | |
| $factoryService = (string) $service->factory['service']; | |
| $factoryMethod = (string) $service->factory['method']; | |
| if ($factoryService && $factoryMethod) { | |
| $setCall .= "->factory([service('$factoryService'), '$factoryMethod'])"; | |
| } | |
| } | |
| // Handle <argument> child elements | |
| if ($service->argument) { | |
| $argumentsAsArray = false; | |
| $argIndex = 0; | |
| foreach ($service->argument as $argument) { | |
| if (isset($argument['key']) && (string) $argument['key'] !== (string) $argIndex) { | |
| $argumentsAsArray = true; | |
| break; | |
| } | |
| ++$argIndex; | |
| } | |
| if (!$argumentsAsArray) { | |
| if (1 === count($service->argument)) { | |
| $setCall .= "\n ->args([".xmlToPhpDslValue($argument).'])'; | |
| } else { | |
| $setCall .= "\n ->args([\n"; | |
| foreach ($service->argument as $argument) { | |
| $setCall .= ' '.xmlToPhpDslValue($argument).",\n"; | |
| } | |
| $setCall .= ' ])'; | |
| } | |
| } else { | |
| $argIndex = 0; | |
| foreach ($service->argument as $argument) { | |
| $argKey = isset($argument['key']) ? (string) $argument['key'] : null; | |
| $argCall = xmlToPhpDslValue($argument); | |
| if ($argKey) { | |
| $setCall .= "\n ->arg('".$argKey."', $argCall)"; | |
| } else { | |
| $setCall .= "\n ->arg($argIndex, $argCall)"; | |
| } | |
| ++$argIndex; | |
| } | |
| } | |
| } | |
| // Handle <tag> child elements | |
| foreach ($service->tag as $tag) { | |
| $tagName = (string) $tag['name']; | |
| $tagAttrs = []; | |
| foreach ($tag->attributes() as $attrName => $attrValue) { | |
| if ('name' !== $attrName) { | |
| $tagAttrs[$attrName] = (string) $attrValue; | |
| } | |
| } | |
| if (!empty($tagAttrs)) { | |
| $setCall .= "\n ->tag('$tagName', ".inline_var_export(convertValueToPhp($tagAttrs)).')'; | |
| } else { | |
| $setCall .= "\n ->tag('$tagName')"; | |
| } | |
| } | |
| // Handle <call> child elements | |
| foreach ($service->call as $call) { | |
| $method = (string) $call['method']; | |
| $callArgs = []; | |
| foreach ($call->argument as $callArg) { | |
| $callArgs[] = xmlToPhpDslValue($callArg); | |
| } | |
| $setCall .= "\n ->call('$method', [".implode(', ', $callArgs).'])'; | |
| } | |
| $setCall .= ";\n"; | |
| $php .= "\n".$setCall; | |
| } | |
| $php .= <<<PHP | |
| }; | |
| PHP; | |
| file_put_contents($phpPath, $php); | |
| echo "Generated: $phpPath\n"; | |
| } | |
| $xmlPath = rtrim($argv[1], '/').'/'; | |
| $files = array_merge( | |
| glob($xmlPath.'*.xml'), | |
| glob($xmlPath.'**/*.xml') | |
| ); | |
| foreach ($files as $file) { | |
| if (str_contains('/routing/', $file) || str_contains('/doctrine/', $file)) { | |
| // Skip routing and doctrine config files | |
| continue; | |
| } | |
| transformXmlToPhpFile($file); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment