SIGN IN SIGN UP

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