bug #11168 [YAML] fix merge node (<<) (Tobion)
This PR was merged into the 2.5 branch. Discussion ---------- [YAML] fix merge node (<<) | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | yes but according to spec | Deprecations? | no | Tests pass? | yes | Needs merge to | 2.5 | Fixed tickets | #11142 and #11154 | License | MIT | Doc PR | — First commit small refactoring. Second fixes #11154 (causing a BC break for a less common feature) Third fixes #11142 Commits -------dee1562
[Yaml] fix overwriting of keys after merged map8c621ab
[Yaml] fix priority of sequence merges according to spec02614e0
[Yaml] refactoring of merges for performance
This commit is contained in:
commit
a50aca04e4
@ -66,6 +66,7 @@ class Parser
|
||||
|
||||
$data = array();
|
||||
$context = null;
|
||||
$allowOverwrite = false;
|
||||
while ($this->moveToNextLine()) {
|
||||
if ($this->isCurrentLineEmpty()) {
|
||||
continue;
|
||||
@ -76,7 +77,7 @@ class Parser
|
||||
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
$isRef = $isInPlace = $isProcessed = false;
|
||||
$isRef = $mergeNode = false;
|
||||
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
|
||||
if ($context && 'mapping' == $context) {
|
||||
throw new ParseException('You cannot define a sequence item when in a mapping');
|
||||
@ -132,10 +133,24 @@ class Parser
|
||||
}
|
||||
|
||||
if ('<<' === $key) {
|
||||
$mergeNode = true;
|
||||
$allowOverwrite = true;
|
||||
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
|
||||
$isInPlace = substr($values['value'], 1);
|
||||
if (!array_key_exists($isInPlace, $this->refs)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
$refName = substr($values['value'], 1);
|
||||
if (!array_key_exists($refName, $this->refs)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
$refValue = $this->refs[$refName];
|
||||
|
||||
if (!is_array($refValue)) {
|
||||
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
foreach ($refValue as $key => $value) {
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isset($values['value']) && $values['value'] !== '') {
|
||||
@ -148,40 +163,49 @@ class Parser
|
||||
$parser->refs =& $this->refs;
|
||||
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport);
|
||||
|
||||
$merged = array();
|
||||
if (!is_array($parsed)) {
|
||||
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
} elseif (isset($parsed[0])) {
|
||||
// Numeric array, merge individual elements
|
||||
foreach (array_reverse($parsed) as $parsedItem) {
|
||||
}
|
||||
|
||||
if (isset($parsed[0])) {
|
||||
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
|
||||
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
|
||||
// in the sequence override keys specified in later mapping nodes.
|
||||
foreach ($parsed as $parsedItem) {
|
||||
if (!is_array($parsedItem)) {
|
||||
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
|
||||
}
|
||||
$merged = array_merge($parsedItem, $merged);
|
||||
|
||||
foreach ($parsedItem as $key => $value) {
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Associative array, merge
|
||||
$merged = array_merge($merged, $parsed);
|
||||
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
|
||||
// current mapping, unless the key already exists in it.
|
||||
foreach ($parsed as $key => $value) {
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$isProcessed = $merged;
|
||||
}
|
||||
} elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
|
||||
$isRef = $matches['ref'];
|
||||
$values['value'] = $matches['value'];
|
||||
}
|
||||
|
||||
if ($isProcessed) {
|
||||
if ($mergeNode) {
|
||||
// Merge keys
|
||||
$data = $isProcessed;
|
||||
// hash
|
||||
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||
// hash
|
||||
// if next line is less indented or equal, then it means that the current value is null
|
||||
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($data[$key])) {
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ($allowOverwrite || !isset($data[$key])) {
|
||||
$data[$key] = null;
|
||||
}
|
||||
} else {
|
||||
@ -190,23 +214,17 @@ class Parser
|
||||
$parser->refs =& $this->refs;
|
||||
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($data[$key])) {
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ($allowOverwrite || !isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($isInPlace) {
|
||||
$data = $this->refs[$isInPlace];
|
||||
} else {
|
||||
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);;
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// But overwriting is allowed when a merge node is used in current block.
|
||||
if ($allowOverwrite || !isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -10,9 +10,19 @@ yaml: |
|
||||
a: Steve
|
||||
b: Clark
|
||||
c: Brian
|
||||
bar: &bar
|
||||
bar:
|
||||
a: before
|
||||
d: other
|
||||
<<: *foo
|
||||
b: new
|
||||
x: Oren
|
||||
c:
|
||||
foo: bar
|
||||
foo: ignore
|
||||
bar: foo
|
||||
duplicate:
|
||||
foo: bar
|
||||
foo: ignore
|
||||
foo2: &foo2
|
||||
a: Ballmer
|
||||
ding: &dong [ fi, fei, fo, fam]
|
||||
@ -24,4 +34,12 @@ yaml: |
|
||||
head:
|
||||
<<: [ *foo , *dong , *foo2 ]
|
||||
php: |
|
||||
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'))
|
||||
array(
|
||||
'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'),
|
||||
'bar' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'),
|
||||
'duplicate' => array('foo' => 'bar'),
|
||||
'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')
|
||||
)
|
||||
|
Reference in New Issue
Block a user