Skip to content

Commit 19f6580

Browse files
bug #62344 [OptionsResolver] Fix missing prototype key in nested error paths (yoeunes)
This PR was merged into the 6.4 branch. Discussion ---------- [OptionsResolver] Fix missing prototype key in nested error paths | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | - | License | MIT This PR fixes a bug where `MissingOptionsException` error messages for nested prototypes generated an incomplete path. When an option was missing inside a prototype that was *itself* nested inside another prototype, the key of the parent prototype was omitted from the error message. The fix ensures the parent's prototype index is correctly tracked and prepended when resolving nested options, providing a full and accurate path for easier debugging. **Example:** Given the following nested prototype setup: ```php $resolver->setOptions('connections', static function (OptionsResolver $connResolver) { $connResolver->setPrototype(true); // Parent prototype // ... $connResolver->setOptions('replicas', static function (OptionsResolver $replicaResolver) { $replicaResolver->setPrototype(true); // Nested prototype $replicaResolver->setRequired(['host']); }); }); ``` And this configuration, which is missing a `host` in `main_db`'s second replica: ```php $options = [ 'connections' => [ 'main_db' => [ // ... 'replicas' => [ ['host' => 'replica-01.local'], [], // Missing 'host' here ], ], ], ]; $resolver->resolve($options); ``` **Before (The Bug):** The exception message was incorrect, missing the `main_db` key: `The required option "connections[replicas][1][host]" is missing.` **After (The Fix):** The exception message is now correct and includes the full path: `The required option "connections[main_db][replicas][1][host]" is missing.` A test case (`testNestedPrototypeErrorPathHasFullContext`) has been added to cover this scenario and prevent regressions. Commits ------- 7d9c810 [OptionsResolver] Fix missing prototype key in nested error paths
2 parents 9e0088f + 7d9c810 commit 19f6580

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,11 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
964964
$resolver = new self();
965965
$resolver->prototype = false;
966966
$resolver->parentsOptions = $this->parentsOptions;
967+
968+
if ($this->prototype && null !== $this->prototypeIndex && null !== ($parentOptionKey = array_key_last($resolver->parentsOptions))) {
969+
$resolver->parentsOptions[$parentOptionKey] .= \sprintf('[%s]', $this->prototypeIndex);
970+
}
971+
967972
$resolver->parentsOptions[] = $option;
968973
foreach ($this->nested[$option] as $closure) {
969974
$closure($resolver, $this);

src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2595,4 +2595,46 @@ public function testRemoveAlsoRemovesDeprecation()
25952595
restore_error_handler();
25962596
}
25972597
}
2598+
2599+
public function testNestedPrototypeErrorPathHasFullContext()
2600+
{
2601+
$resolver = new OptionsResolver();
2602+
2603+
$resolver->setDefault('connections', static function (OptionsResolver $connResolver) {
2604+
$connResolver->setPrototype(true);
2605+
$connResolver->setRequired(['host', 'database']);
2606+
$connResolver->setDefault('user', 'root');
2607+
2608+
$connResolver->setDefault('replicas', static function (OptionsResolver $replicaResolver) {
2609+
$replicaResolver->setPrototype(true);
2610+
$replicaResolver->setRequired(['host']);
2611+
$replicaResolver->setDefault('user', 'read_only');
2612+
});
2613+
});
2614+
2615+
$this->expectException(MissingOptionsException::class);
2616+
$this->expectExceptionMessage('The required option "connections[main_db][replicas][1][host]" is missing.');
2617+
2618+
$options = [
2619+
'connections' => [
2620+
'main_db' => [
2621+
'host' => 'localhost',
2622+
'database' => 'app_db',
2623+
'replicas' => [
2624+
['host' => 'replica-01.local', 'user' => 'read_only'],
2625+
['user' => 'other_user'], // Index 1 -> "host" is missing here
2626+
],
2627+
],
2628+
'audit_db' => [
2629+
'host' => 'audit.local',
2630+
'database' => 'audit_db',
2631+
'replicas' => [
2632+
['host' => 'audit-replica.local'],
2633+
],
2634+
],
2635+
],
2636+
];
2637+
2638+
$resolver->resolve($options);
2639+
}
25982640
}

0 commit comments

Comments
 (0)