Skip to content

Commit ba255ed

Browse files
committed
parse merge keys with PARSE_OBJECT_FOR_MAP flag
1 parent 3f4c47c commit ba255ed

File tree

2 files changed

+70
-26
lines changed

2 files changed

+70
-26
lines changed

src/Symfony/Component/Yaml/Parser.php

+38-26
Original file line numberDiff line numberDiff line change
@@ -249,46 +249,58 @@ private function doParse($value, $flags)
249249
if ('<<' === $key) {
250250
$mergeNode = true;
251251
$allowOverwrite = true;
252-
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
252+
if (isset($values['value']) && isset($values['value'][0]) && '*' === $values['value'][0]) {
253253
$refName = substr(rtrim($values['value']), 1);
254254
if (!array_key_exists($refName, $this->refs)) {
255255
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
256256
}
257257

258258
$refValue = $this->refs[$refName];
259259

260-
if (!is_array($refValue)) {
260+
if (is_array($refValue)) {
261+
$data += $refValue; // array union
262+
} elseif ((bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags) && $refValue instanceof \stdClass) {
263+
foreach ($refValue as $refValueKey => $refValueValue) {
264+
if (!isset($data[$refValueKey])) {
265+
$data[$refValueKey] = $refValueValue;
266+
}
267+
}
268+
} else {
261269
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
262270
}
263-
264-
$data += $refValue; // array union
265271
} else {
266-
if (isset($values['value']) && $values['value'] !== '') {
272+
if (isset($values['value']) && '' !== $values['value']) {
267273
$value = $values['value'];
268274
} else {
269275
$value = $this->getNextEmbedBlock();
270276
}
271277
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
272278

273-
if (!is_array($parsed)) {
274-
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
275-
}
276-
277-
if (isset($parsed[0])) {
278-
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
279-
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
280-
// in the sequence override keys specified in later mapping nodes.
281-
foreach ($parsed as $parsedItem) {
282-
if (!is_array($parsedItem)) {
283-
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
279+
if (is_array($parsed)) {
280+
if (isset($parsed[0])) {
281+
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
282+
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
283+
// in the sequence override keys specified in later mapping nodes.
284+
foreach ($parsed as $parsedItem) {
285+
if (!is_array($parsedItem)) {
286+
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
287+
}
288+
289+
$data += $parsedItem; // array union
290+
}
291+
} else {
292+
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
293+
// current mapping, unless the key already exists in it.
294+
$data += $parsed; // array union
295+
}
296+
} elseif ((bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags) && $parsed instanceof \stdClass) {
297+
foreach ($parsed as $parsedKey => $parsedValue) {
298+
if (!isset($data[$parsedKey])) {
299+
$data[$parsedKey] = $parsedValue;
284300
}
285-
286-
$data += $parsedItem; // array union
287301
}
288302
} else {
289-
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
290-
// current mapping, unless the key already exists in it.
291-
$data += $parsed; // array union
303+
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
292304
}
293305
}
294306
} elseif (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
@@ -544,7 +556,7 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false)
544556
$indent = $this->getCurrentLineIndentation();
545557

546558
// terminate all block scalars that are more indented than the current line
547-
if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && trim($this->currentLine) !== '') {
559+
if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
548560
foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
549561
if ($blockScalarIndentation >= $indent) {
550562
unset($blockScalarIndentations[$key]);
@@ -680,7 +692,7 @@ private function parseValue($value, $flags, $context)
680692

681693
while ($this->moveToNextLine()) {
682694
// unquoted strings end before the first unindented line
683-
if (null === $quotation && $this->getCurrentLineIndentation() === 0) {
695+
if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
684696
$this->moveToPreviousLine();
685697

686698
break;
@@ -876,7 +888,7 @@ private function isCurrentLineComment()
876888
//checking explicitly the first char of the trim is faster than loops or strpos
877889
$ltrimmedLine = ltrim($this->currentLine, ' ');
878890

879-
return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#';
891+
return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
880892
}
881893

882894
private function isCurrentLineLastLineInDocument()
@@ -902,15 +914,15 @@ private function cleanup($value)
902914

903915
// remove leading comments
904916
$trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
905-
if ($count == 1) {
917+
if (1 === $count) {
906918
// items have been removed, update the offset
907919
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
908920
$value = $trimmedValue;
909921
}
910922

911923
// remove start of the document marker (---)
912924
$trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
913-
if ($count == 1) {
925+
if (1 === $count) {
914926
// items have been removed, update the offset
915927
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
916928
$value = $trimmedValue;

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

+32
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,38 @@ public function testPhpConstantTagMappingKeyWithKeysCastToStrings()
18651865

18661866
$this->assertSame($expected, $this->parser->parse($yaml, Yaml::PARSE_CONSTANT | Yaml::PARSE_KEYS_AS_STRINGS));
18671867
}
1868+
1869+
public function testMergeKeysWhenMappingsAreParsedAsObjects()
1870+
{
1871+
$yaml = <<<YAML
1872+
aaa: &A
1873+
bbb: 1
1874+
1875+
ccc:
1876+
ddd: 2
1877+
<<: *A
1878+
1879+
eee:
1880+
fff: 3
1881+
<<:
1882+
ggg: 4
1883+
YAML;
1884+
$expected = (object) array(
1885+
'aaa' => (object) array(
1886+
'bbb' => 1,
1887+
),
1888+
'ccc' => (object) array(
1889+
'ddd' => 2,
1890+
'bbb' => 1,
1891+
),
1892+
'eee' => (object) array(
1893+
'fff' => 3,
1894+
'ggg' => 4,
1895+
),
1896+
);
1897+
1898+
$this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP));
1899+
}
18681900
}
18691901

18701902
class B

0 commit comments

Comments
 (0)