feature #65884 [Config][DependencyInjection][FrameworkBundle] Allow a configuration node to alias the configuration of another extension (nicolas-grekas)
This PR was merged into the 8.2 branch.
Discussion
----------
[Config][DependencyInjection][FrameworkBundle] Allow a configuration node to alias the configuration of another extension
| Q | A
| ------------- | ---
| Branch? | 8.2
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | -
| License | MIT
`ServicesBundle` and `ConsoleBundle` showed that a component can carry its own bundle. Moving the sections that own a `framework.*` configuration key is blocked by the configuration namespace: `framework.workflows` has to keep working while the Workflow component owns the `workflow` configuration. This PR adds the missing piece. It changes no behavior on its own.
A node can declare that its value belongs to the configuration rooted at another name:
```php
$rootNode
->children()
->variableNode('workflows')->aliasOf('workflow')->end()
->end();
```
`MergeExtensionConfigurationPass` then moves the value of that node to the configuration of the extension with that alias. This happens after all `prepend()` calls and before any `load()`, so bundles that prepend `framework.workflows` keep working: prepends run in bundle registration order, so doing this inside `FrameworkExtension::prepend()` would miss the bundles registered after it.
The Config component knows nothing about extensions. It carries the name, checks where an alias may appear, and lets a dumper resolve it. Turning that name into an extension is the job of the DependencyInjection component.
Details that the implementation has to get right:
- The value is normalized by the node first, so `treatFalseLike()` and friends apply, and `null` becomes `[]`.
- Forwarded values sort before the configuration written directly under the new key, so the new key wins when both are used.
- The XML remappings of the parent node are honored, so a singular key still normalizes to its plural.
- A key whose node name contains an underscore is also matched in its hyphenated spelling, because `ArrayNode::preNormalize()` accepts that form today. Without this, `framework.web-link` would be silently dropped rather than forwarded: the forwarding reads the raw configuration before normalization, so it would miss the key, and the alias node would then swallow it.
- A missing target extension is an error rather than a silent drop.
- The key is stripped before the tree is processed, so the pass triggers the node deprecation itself when the node is deprecated. Aliases do not have to be deprecated; this PR adds no deprecation.
- Only the direct children of a root node can be aliases. `TreeBuilder::buildTree()` throws when `aliasOf()` sits deeper, because the dumpers would describe such a node as a reference while the pass would never forward it.
Because the node's own normalizers run, an alias can also carry the shorthand of the section it replaces. Sections such as `framework.lock` and `framework.semaphore` accept a bare string, and the forwarded value has to be an array, so those aliases declare the wrapping themselves:
```php
->variableNode('semaphore')->aliasOf('semaphore')
->beforeNormalization()->ifString()->then(static fn ($v) => ['resources' => ['default' => $v]])->end()
->end()
```
The dumpers reference the target instead of describing the node. `ArrayShapeGenerator::generate()` takes a `$resolveAlias` closure and emits `workflows?: WorkflowConfig`, and `JsonSchemaDumper` emits `{"$ref": "#/$defs/nodes/workflow"}`. Both FrameworkBundle dumper passes use it, so `config/reference.php` and `config/schema.json` stop inlining an aliased section and describe it once.
Public API added:
- `NodeDefinition::aliasOf(string $alias)`
- `ArrayShapeGenerator::generate(NodeInterface $node, ?\Closure $resolveAlias = null)`
- `JsonSchemaDumper::__construct(array $parameterSchemas = [], ?\Closure $resolveAlias = null)`
- `ContainerBuilder::setExtensionConfig(string $name, array $configs)`
Limits: aliases are not chained.
The next PR uses this to move the workflow configuration into a new `WorkflowBundle`.
## Checks
The Config, DependencyInjection and FrameworkBundle suites pass locally, and php-cs-fixer is clean. Each new test was checked to fail on the base sources. Not run locally: PHPStan and Psalm.
## Documentation
`NodeDefinition::aliasOf()` and the `$resolveAlias` argument of the two dumpers.
Commits
-------
36938b9f4b9 [Config][DependencyInjection][FrameworkBundle] Allow a configuration node to alias the configuration of another extension N
Nicolas Grekas committed
1070babf986f47e0a1bbd83ecf1e887d96fddd15