Skip to content

Commit 69271cf

Browse files
bug #63478 [Config] Fix ArrayShapeGenerator required keys with deep merging (lacatoire)
This PR was squashed before being merged into the 7.4 branch. Discussion ---------- [Config] Fix ArrayShapeGenerator required keys with deep merging | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | - | License | MIT ## Summary When configuration is deep-merged across files, a required node only needs to be present after the merge, not in every individual file. The `ArrayShapeGenerator` was marking all required keys as non-optional (`node: type`) in the generated array shape, which made PHPStan complain when a required key (e.g. `resource` in `router`) was not set in every single config file. This PR marks required keys as optional (`node?: type`) in the array shape when the parent node performs deep merging (the default behavior). Only when the parent explicitly disables deep merging (`performNoDeepMerging()`) are required keys kept non-optional. ### Changes - Add `ArrayNode::shouldPerformDeepMerging()` getter - Update `ArrayShapeGenerator::dumpNodeKey()` to account for the parent's deep merging setting - Update existing test and add a new test for the `performNoDeepMerging()` case Commits ------- 7c05f4ab67c [Config] Fix ArrayShapeGenerator required keys with deep merging
2 parents 14a2318 + f2dbc7f commit 69271cf

File tree

3 files changed

+25
-3
lines changed

3 files changed

+25
-3
lines changed

Definition/ArrayNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ public function setPerformDeepMerging(bool $boolean): void
126126
$this->performDeepMerging = $boolean;
127127
}
128128

129+
/**
130+
* Whether deep merging should occur.
131+
*/
132+
public function shouldPerformDeepMerging(): bool
133+
{
134+
return $this->performDeepMerging;
135+
}
136+
129137
/**
130138
* Whether extra keys should just be ignored without an exception.
131139
*

Definition/ArrayShapeGenerator.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel
6060
$arrayShape = \sprintf("array{%s\n", self::generateInlinePhpDocForNode($node));
6161

6262
foreach ($children as $child) {
63-
$arrayShape .= str_repeat(' ', $nestingLevel).self::dumpNodeKey($child).': ';
63+
$arrayShape .= str_repeat(' ', $nestingLevel).self::dumpNodeKey($child, $node).': ';
6464

6565
if ($child instanceof PrototypedArrayNode) {
6666
$isHashmap = (bool) $child->getKeyAttribute();
@@ -82,7 +82,7 @@ private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel
8282
return implode('|', [...self::getNormalizedTypes($node, ['array', 'any']), $arrayShape]);
8383
}
8484

85-
private static function dumpNodeKey(NodeInterface $node): string
85+
private static function dumpNodeKey(NodeInterface $node, ?ArrayNode $parent = null): string
8686
{
8787
$name = $node->getName();
8888
$quoted = str_starts_with($name, '@')
@@ -93,7 +93,9 @@ private static function dumpNodeKey(NodeInterface $node): string
9393
$name = "'".addslashes($name)."'";
9494
}
9595

96-
return $name.($node->isRequired() ? '' : '?');
96+
$optional = !$node->isRequired() || ($parent instanceof ArrayNode && $parent->shouldPerformDeepMerging());
97+
98+
return $name.($optional ? '?' : '');
9799
}
98100

99101
private static function handleNumericNode(NumericNode $node): string

Tests/Definition/ArrayShapeGeneratorTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@ public function testPhpDocHandlesRequiredNode()
105105
$root = new ArrayNode('root');
106106
$root->addChild($child);
107107

108+
$this->assertStringContainsString('node?: bool', ArrayShapeGenerator::generate($root));
109+
}
110+
111+
public function testPhpDocHandlesRequiredNodeWithNoDeepMerging()
112+
{
113+
$child = new BooleanNode('node');
114+
$child->setRequired(true);
115+
116+
$root = new ArrayNode('root');
117+
$root->setPerformDeepMerging(false);
118+
$root->addChild($child);
119+
108120
$expected = 'node: bool';
109121

110122
$this->assertStringContainsString($expected, ArrayShapeGenerator::generate($root));

0 commit comments

Comments
 (0)