Skip to content

Commit da15553

Browse files
committed
feature #58243 [Yaml] Add support for dumping null as an empty value by using the Yaml::DUMP_NULL_AS_EMPTY flag (alexandre-daubois)
This PR was merged into the 7.3 branch. Discussion ---------- [Yaml] Add support for dumping `null` as an empty value by using the `Yaml::DUMP_NULL_AS_EMPTY` flag | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #58155 | License | MIT Another try after #58232. I went for the second solution from `@stof` (#58232 (comment)), namely dumping empty value as null just in expanded sequences and mappings. When the inline limit of the dumper is reached, it fallbacks on using `null` (examples in tests). I'm not 100% happy about the name of the constant, `DUMP_NULL_AS_EMPTY_IN_EXPANDED_NOTATION`. It's verbose. I'm open to suggestions! Commits ------- 1fc1379 [Yaml] Add support for dumping `null` as an empty value by using the `Yaml::DUMP_NULL_AS_EMPTY` flag
2 parents 4078cb1 + 1fc1379 commit da15553

File tree

6 files changed

+110
-11
lines changed

6 files changed

+110
-11
lines changed

src/Symfony/Component/Yaml/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Deprecate parsing duplicate mapping keys whose value is `null`
8+
* Add support for dumping `null` as an empty value by using the `Yaml::DUMP_NULL_AS_EMPTY` flag
89

910
7.1
1011
---

src/Symfony/Component/Yaml/Dumper.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ public function __construct(private int $indentation = 4)
4141
* @param int-mask-of<Yaml::DUMP_*> $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
4242
*/
4343
public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0): string
44+
{
45+
if ($flags & Yaml::DUMP_NULL_AS_EMPTY && $flags & Yaml::DUMP_NULL_AS_TILDE) {
46+
throw new \InvalidArgumentException('The Yaml::DUMP_NULL_AS_EMPTY and Yaml::DUMP_NULL_AS_TILDE flags cannot be used together.');
47+
}
48+
49+
return $this->doDump($input, $inline, $indent, $flags);
50+
}
51+
52+
private function doDump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0, int $nestingLevel = 0): string
4453
{
4554
$output = '';
4655
$prefix = $indent ? str_repeat(' ', $indent) : '';
@@ -51,9 +60,9 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags
5160
}
5261

5362
if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || !$input) {
54-
$output .= $prefix.Inline::dump($input, $flags);
63+
$output .= $prefix.Inline::dump($input, $flags, 0 === $nestingLevel);
5564
} elseif ($input instanceof TaggedValue) {
56-
$output .= $this->dumpTaggedValue($input, $inline, $indent, $flags, $prefix);
65+
$output .= $this->dumpTaggedValue($input, $inline, $indent, $flags, $prefix, $nestingLevel);
5766
} else {
5867
$dumpAsMap = Inline::isHash($input);
5968

@@ -105,10 +114,10 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags
105114
}
106115

107116
if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) {
108-
$output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n";
117+
$output .= ' '.$this->doDump($value->getValue(), $inline - 1, 0, $flags, $nestingLevel + 1)."\n";
109118
} else {
110119
$output .= "\n";
111-
$output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags);
120+
$output .= $this->doDump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags, $nestingLevel + 1);
112121
}
113122

114123
continue;
@@ -126,15 +135,15 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags
126135
$prefix,
127136
$dumpAsMap ? Inline::dump($key, $flags).':' : '-',
128137
$willBeInlined ? ' ' : "\n",
129-
$this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags)
138+
$this->doDump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags, $nestingLevel + 1)
130139
).($willBeInlined ? "\n" : '');
131140
}
132141
}
133142

134143
return $output;
135144
}
136145

137-
private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, int $flags, string $prefix): string
146+
private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, int $flags, string $prefix, int $nestingLevel): string
138147
{
139148
$output = \sprintf('%s!%s', $prefix ? $prefix.' ' : '', $value->getTag());
140149

@@ -150,10 +159,10 @@ private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, i
150159
}
151160

152161
if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) {
153-
return $output.' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n";
162+
return $output.' '.$this->doDump($value->getValue(), $inline - 1, 0, $flags, $nestingLevel + 1)."\n";
154163
}
155164

156-
return $output."\n".$this->dump($value->getValue(), $inline - 1, $indent, $flags);
165+
return $output."\n".$this->doDump($value->getValue(), $inline - 1, $indent, $flags, $nestingLevel + 1);
157166
}
158167

159168
private function getBlockIndentationIndicator(string $value): string

src/Symfony/Component/Yaml/Inline.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public static function parse(string $value, int $flags = 0, array &$references =
100100
*
101101
* @throws DumpException When trying to dump PHP resource
102102
*/
103-
public static function dump(mixed $value, int $flags = 0): string
103+
public static function dump(mixed $value, int $flags = 0, bool $rootLevel = false): string
104104
{
105105
switch (true) {
106106
case \is_resource($value):
@@ -138,7 +138,7 @@ public static function dump(mixed $value, int $flags = 0): string
138138
case \is_array($value):
139139
return self::dumpArray($value, $flags);
140140
case null === $value:
141-
return self::dumpNull($flags);
141+
return self::dumpNull($flags, $rootLevel);
142142
case true === $value:
143143
return 'true';
144144
case false === $value:
@@ -253,12 +253,16 @@ private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $
253253
return \sprintf('{ %s }', implode(', ', $output));
254254
}
255255

256-
private static function dumpNull(int $flags): string
256+
private static function dumpNull(int $flags, bool $rootLevel = false): string
257257
{
258258
if (Yaml::DUMP_NULL_AS_TILDE & $flags) {
259259
return '~';
260260
}
261261

262+
if (Yaml::DUMP_NULL_AS_EMPTY & $flags && !$rootLevel) {
263+
return '';
264+
}
265+
262266
return 'null';
263267
}
264268

src/Symfony/Component/Yaml/Tests/DumperTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,63 @@ public function testObjectSupportDisabledWithExceptions()
216216
$this->dumper->dump(['foo' => new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE);
217217
}
218218

219+
public function testDumpWithMultipleNullFlagsFormatsThrows()
220+
{
221+
$this->expectException(\InvalidArgumentException::class);
222+
$this->expectExceptionMessage('The Yaml::DUMP_NULL_AS_EMPTY and Yaml::DUMP_NULL_AS_TILDE flags cannot be used together.');
223+
224+
$this->dumper->dump(['foo' => 'bar'], 0, 0, Yaml::DUMP_NULL_AS_EMPTY | Yaml::DUMP_NULL_AS_TILDE);
225+
}
226+
227+
public function testDumpNullAsEmptyInExpandedMapping()
228+
{
229+
$expected = "qux:\n foo: bar\n baz: \n";
230+
231+
$this->assertSame($expected, $this->dumper->dump(['qux' => ['foo' => 'bar', 'baz' => null]], 2, flags: Yaml::DUMP_NULL_AS_EMPTY));
232+
}
233+
234+
public function testDumpNullAsEmptyWithObject()
235+
{
236+
$class = new \stdClass();
237+
$class->foo = 'bar';
238+
$class->baz = null;
239+
240+
$this->assertSame("foo: bar\nbaz: \n", $this->dumper->dump($class, 2, flags: Yaml::DUMP_NULL_AS_EMPTY | Yaml::DUMP_OBJECT_AS_MAP));
241+
}
242+
243+
public function testDumpNullAsEmptyDumpsWhenInInlineMapping()
244+
{
245+
$expected = "foo: \nqux: { foo: bar, baz: }\n";
246+
247+
$this->assertSame($expected, $this->dumper->dump(['foo' => null, 'qux' => ['foo' => 'bar', 'baz' => null]], 1, flags: Yaml::DUMP_NULL_AS_EMPTY));
248+
}
249+
250+
public function testDumpNullAsEmptyDumpsNestedMaps()
251+
{
252+
$expected = "foo: \nqux:\n foo: bar\n baz: \n";
253+
254+
$this->assertSame($expected, $this->dumper->dump(['foo' => null, 'qux' => ['foo' => 'bar', 'baz' => null]], 10, flags: Yaml::DUMP_NULL_AS_EMPTY));
255+
}
256+
257+
public function testDumpNullAsEmptyInExpandedSequence()
258+
{
259+
$expected = "qux:\n - foo\n - \n - bar\n";
260+
261+
$this->assertSame($expected, $this->dumper->dump(['qux' => ['foo', null, 'bar']], 2, flags: Yaml::DUMP_NULL_AS_EMPTY));
262+
}
263+
264+
public function testDumpNullAsEmptyWhenInInlineSequence()
265+
{
266+
$expected = "foo: \nqux: [foo, , bar]\n";
267+
268+
$this->assertSame($expected, $this->dumper->dump(['foo' => null, 'qux' => ['foo', null, 'bar']], 1, flags: Yaml::DUMP_NULL_AS_EMPTY));
269+
}
270+
271+
public function testDumpNullAsEmptyAtRoot()
272+
{
273+
$this->assertSame('null', $this->dumper->dump(null, 2, flags: Yaml::DUMP_NULL_AS_EMPTY));
274+
}
275+
219276
/**
220277
* @dataProvider getEscapeSequences
221278
*/

src/Symfony/Component/Yaml/Tests/ParserTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,33 @@ public function testTopLevelNull()
5252
$this->assertSameData($expected, $data);
5353
}
5454

55+
public function testEmptyValueInExpandedMappingIsSupported()
56+
{
57+
$yml = <<<'YAML'
58+
foo:
59+
bar:
60+
baz: qux
61+
YAML;
62+
63+
$data = $this->parser->parse($yml);
64+
$expected = ['foo' => ['bar' => null, 'baz' => 'qux']];
65+
$this->assertSameData($expected, $data);
66+
}
67+
68+
public function testEmptyValueInExpandedSequenceIsSupported()
69+
{
70+
$yml = <<<'YAML'
71+
foo:
72+
- bar
73+
-
74+
- baz
75+
YAML;
76+
77+
$data = $this->parser->parse($yml);
78+
$expected = ['foo' => ['bar', null, 'baz']];
79+
$this->assertSameData($expected, $data);
80+
}
81+
5582
public function testTaggedValueTopLevelNumber()
5683
{
5784
$yml = '!number 5';

src/Symfony/Component/Yaml/Yaml.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Yaml
3535
public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;
3636
public const DUMP_NULL_AS_TILDE = 2048;
3737
public const DUMP_NUMERIC_KEY_AS_STRING = 4096;
38+
public const DUMP_NULL_AS_EMPTY = 8192;
3839

3940
/**
4041
* Parses a YAML file into a PHP value.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy