8000 [Yaml] fix priority of sequence merges according to spec · symfony/symfony@8c621ab · GitHub
[go: up one dir, main page]

Skip to content

Commit 8c621ab

Browse files
committed
[Yaml] fix priority of sequence merges according to spec
fixes #11154
1 parent 02614e0 commit 8c621ab

File tree

2 files changed

+43
-21
lines changed

2 files changed

+43
-21
lines changed

src/Symfony/Component/Yaml/Parser.php

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
7676
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
7777
}
7878

79-
$isRef = $isInPlace = $isProcessed = false;
79+
$isRef = $mergeNode = false;
8080
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
8181
if ($context && 'mapping' == $context) {
8282
throw new ParseException('You cannot define a sequence item when in a mapping');
@@ -132,10 +132,23 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
132132
}
133133

134134
if ('<<' === $key) {
135+
$mergeNode = true;
135136
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
136-
$isInPlace = substr($values['value'], 1);
137-
if (!array_key_exists($isInPlace, $this->refs)) {
138-
throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine);
137+
$refName = substr($values['value'], 1);
138+
if (!array_key_exists($refName, $this->refs)) {
139+
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
140+
}
141+
142+
$refValue = $this->refs[$refName];
143+
144+
if (!is_array($refValue)) {
145+
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
146+
}
147+
148+
foreach ($refValue as $key => $value) {
149+
if (!isset($data[$key])) {
150+
$data[$key] = $value;
151+
}
139152
}
140153
} else {
141154
if (isset($values['value']) && $values['value'] !== '') {
@@ -152,26 +165,37 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
152165
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
153166
}
154167

155-
$isProcessed = true;
156168
if (isset($parsed[0])) {
157-
// Numeric array, merge individual elements
169+
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
170+
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
171+
// in the sequence override keys specified in later mapping nodes.
158172
foreach ($parsed as $parsedItem) {
159173
if (!is_array($parsedItem)) {
160174
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
161175
}
162-
$data = array_merge($data, $parsedItem);
176+
177+
foreach ($parsedItem as $key => $value) {
178+
if (!isset($data[$key])) {
179+
$data[$key] = $value;
180+
}
181+
}
163182
}
164183
} else {
165-
// Associative array
166-
$data = $parsed;
184+
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
185+
// current mapping, unless the key already exists in it.
186+
foreach ($parsed as $key => $value) {
187+
if (!isset($data[$key])) {
188+
$data[$key] = $value;
189+
}
190+
}
167191
}
168192
}
169193
} elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
170194
$isRef = $matches['ref'];
171195
$values['value'] = $matches['value'];
172196
}
173197

174-
if ($isProcessed) {
198+
if ($mergeNode) {
175199
// Merge keys
176200
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
177201
// hash
@@ -196,16 +220,12 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
196220
}
197221
}
198222
} else {
199-
if ($isInPlace) {
200-
$data = $this->refs[$isInPlace];
201-
} else {
202-
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);;
203-
// Spec: Keys MUST be unique; first one wins.
204-
// Parser cannot abort this mapping earlier, since lines
205-
// are processed sequentially.
206-
if (!isset($data[$key])) {
207-
$data[$key] = $value;
208-
}
223+
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
224+
// Spec: Keys MUST be unique; first one wins.
225+
// Parser cannot abort this mapping earlier, since lines
226+
// are processed sequentially.
227+
if (!isset($data[$key])) {
228+
$data[$key] = $value;
209229
}
210230
}
211231
} else {

src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ yaml: |
1111
b: Clark
1212
c: Brian
1313
bar: &bar
14+
a: before
15+
d: other
1416
<<: *foo
1517
x: Oren
1618
foo2: &foo2
@@ -24,4 +26,4 @@ yaml: |
2426
head:
2527
<<: [ *foo , *dong , *foo2 ]
2628
php: |
27-
array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Ballmer', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam'))
29+
array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'before', 'd' => 'other', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam'))

0 commit comments

Comments
 (0)
0