From 6aad690d40a355762f0bf12a5b21369d58dfd0c1 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 8 Oct 2024 10:29:10 +0200 Subject: [PATCH] [Config] Allow using `defaultNull()` on `BooleanNodeDefinition` --- src/Symfony/Component/Config/CHANGELOG.md | 1 + .../Config/Definition/BooleanNode.php | 15 +++++++++- .../Builder/BooleanNodeDefinition.php | 18 +++++++++++- .../Tests/Definition/BooleanNodeTest.php | 28 ++++++++++++++++++- .../Builder/BooleanNodeDefinitionTest.php | 24 ++++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md index 07de4145130d4..e60b76d3b1f00 100644 --- a/src/Symfony/Component/Config/CHANGELOG.md +++ b/src/Symfony/Component/Config/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `#[WhenNot]` attribute to prevent service from being registered in a specific environment * Generate a meta file in JSON format for resource tracking * Add `SkippingResourceChecker` + * Add support for `defaultNull()` on `BooleanNode` 7.1 --- diff --git a/src/Symfony/Component/Config/Definition/BooleanNode.php b/src/Symfony/Component/Config/Definition/BooleanNode.php index 5c51b1dcf38be..b4ed0f0eb10f9 100644 --- a/src/Symfony/Component/Config/Definition/BooleanNode.php +++ b/src/Symfony/Component/Config/Definition/BooleanNode.php @@ -20,10 +20,23 @@ */ class BooleanNode extends ScalarNode { + public function __construct( + ?string $name, + ?NodeInterface $parent = null, + string $pathSeparator = self::DEFAULT_PATH_SEPARATOR, + private bool $nullable = false, + ) { + parent::__construct($name, $parent, $pathSeparator); + } + protected function validateType(mixed $value): void { if (!\is_bool($value)) { - $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value))); + if (null === $value && $this->nullable) { + return; + } + + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "bool%s", but got "%s".', $this->getPath(), $this->nullable ? '" or "null' : '', get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } diff --git a/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php index 15e63961ab727..bc03c9c94abc3 100644 --- a/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/BooleanNodeDefinition.php @@ -33,7 +33,7 @@ public function __construct(?string $name, ?NodeParentInterface $parent = null) */ protected function instantiateNode(): BooleanNode { - return new BooleanNode($this->name, $this->parent, $this->pathSeparator); + return new BooleanNode($this->name, $this->parent, $this->pathSeparator, null === $this->nullEquivalent); } /** @@ -43,4 +43,20 @@ public function cannotBeEmpty(): static { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.'); } + + public function defaultNull(): static + { + $this->nullEquivalent = null; + + return parent::defaultNull(); + } + + public function defaultValue(mixed $value): static + { + if (null === $value) { + $this->nullEquivalent = null; + } + + return parent::defaultValue($value); + } } diff --git a/src/Symfony/Component/Config/Tests/Definition/BooleanNodeTest.php b/src/Symfony/Component/Config/Tests/Definition/BooleanNodeTest.php index 4c9bf42e3fe67..9358a975d0dd5 100644 --- a/src/Symfony/Component/Config/Tests/Definition/BooleanNodeTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/BooleanNodeTest.php @@ -26,6 +26,33 @@ public function testNormalize(bool $value) $this->assertSame($value, $node->normalize($value)); } + public function testNullValueOnNullable() + { + $node = new BooleanNode('test', null, '.', true); + + $this->assertNull($node->normalize(null)); + } + + public function testNullValueOnNotNullable() + { + $node = new BooleanNode('test', null, '.', false); + + $this->expectException(InvalidTypeException::class); + $this->expectExceptionMessage('Invalid type for path "test". Expected "bool", but got "null".'); + + $this->assertNull($node->normalize(null)); + } + + public function testInvalidValueOnNullable() + { + $node = new BooleanNode('test', null, '.', true); + + $this->expectException(InvalidTypeException::class); + $this->expectExceptionMessage('Invalid type for path "test". Expected "bool" or "null", but got "int".'); + + $node->normalize(123); + } + /** * @dataProvider getValidValues */ @@ -60,7 +87,6 @@ public function testNormalizeThrowsExceptionOnInvalidValues($value) public static function getInvalidValues(): array { return [ - [null], [''], ['foo'], [0], diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php index 7a4706a3e8dd5..67b3aaf64fa92 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php @@ -25,6 +25,30 @@ public function testCannotBeEmptyThrowsAnException() $def->cannotBeEmpty(); } + public function testBooleanNodeWithDefaultNull() + { + $def = new BooleanNodeDefinition('foo'); + $def->defaultNull(); + + $node = $def->getNode(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertNull($node->getDefaultValue()); + + $this->assertNull($node->normalize(null)); + } + + public function testBooleanNodeWithDefaultValueAtNull() + { + $def = new BooleanNodeDefinition('foo'); + $def->defaultValue(null); + + $node = $def->getNode(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertNull($node->getDefaultValue()); + + $this->assertNull($node->normalize(null)); + } + public function testSetDeprecated() { $def = new BooleanNodeDefinition('foo');