10000 [Yaml] Added support for object maps · symfony/symfony@e2d5468 · GitHub
[go: up one dir, main page]

Skip to content

Commit e2d5468

Browse files
polyfractalnico
10000
las-grekas
authored andcommitted
[Yaml] Added support for object maps
Previously, the parser treated maps ( {} ) the same as sets ( [] ). Both were returned as PHP associative arrays. Since these are distinct entities, this can cause considerably problems for the users, especially when YAML is being serialized into another format such as JSON. This commit allows the user to enable object-map support via a third parameter on the Parse method. It defaults to `false`, which means that this commit does not break backwards compatibility. If the user enables object-map support, maps are represented by stdClass() objects. Sets remain as arrays.
1 parent 4c12b7b commit e2d5468

File tree

2 files changed

+152
-23
lines changed

2 files changed

+152
-23
lines changed

src/Symfony/Component/Yaml/Inline.php

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ class Inline
2929
/**
3030
* Converts a YAML string to a PHP array.
3131
*
32-
* @param string $value A YAML string
33-
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
34-
* @param bool $objectSupport true if object support is enabled, false otherwise
32+
* @param string $value A YAML string
33+
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
34+
* @param bool $objectSupport true if object support is enabled, false otherwise
35+
* @param bool $objectForMap true if maps should return a stdClass instead of array()
3536
*
3637
* @return array A PHP array representing the YAML string
3738
*
38-
* @throws ParseException
39+
* @throws Exception\ParseException
3940
*/
40-
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
41+
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
4142
{
4243
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
4344
self::$objectSupport = $objectSupport;
@@ -56,11 +57,11 @@ public static function parse($value, $exceptionOnInvalidType = false, $objectSup
5657
$i = 0;
5758
switch ($value[0]) {
5859
case '[':
59-
$result = self::parseSequence($value, $i);
60+
$result = self::parseSequence($value, $i, $objectForMap);
6061
++$i;
6162
break;
6263
case '{':
63-
$result = self::parseMapping($value, $i);
64+
$result = self::parseMapping($value, $i, $objectForMap);
6465
++$i;
6566
break;
6667
default:
@@ -181,9 +182,9 @@ private static function dumpArray($value, $exceptionOnInvalidType, $objectSuppor
181182
/**
182183
* Parses a scalar to a YAML string.
183184
*
184-
* @param scalar $scalar
185-
* @param string $delimiters
186-
* @param array $stringDelimiters
185+
* @param scalar $scalar
186+
* @param string $delimiters
187+
* @param array $stringDelimiters
187188
* @param int &$i
188189
* @param bool $evaluate
189190
*
@@ -232,7 +233,7 @@ public static function parseScalar($scalar, $delimiters = null, $stringDelimiter
232233
* Parses a quoted scalar to YAML.
233234
*
234235
* @param string $scalar
235-
* @param int &$i
236+
* @param int &$i
236237
*
237238
* @return string A YAML string
238239
*
@@ -261,14 +262,15 @@ private static function parseQuotedScalar($scalar, &$i)
261262
/**
262263
* Parses a sequence to a YAML string.
263264
*
264-
* @param string $sequence
265+
* @param string $sequence
265266
* @param int &$i
267+
* @param bool $objectForMap true if maps should return a stdClass instead of array()
266268
*
267269
* @return string A YAML string
268270
*
269-
* @throws ParseException When malformed inline YAML string is parsed
271+
* @throws Exception\ParseException
270272
*/
271-
private static function parseSequence($sequence, &$i = 0)
273+
private static function parseSequence($sequence, &$i = 0, $objectForMap = false)
272274
{
273275
$output = array();
274276
$len = strlen($sequence);
@@ -279,11 +281,11 @@ private static function parseSequence($sequence, &$i = 0)
279281
switch ($sequence[$i]) {
280282
case '[':
281283
// nested sequence
282-
$output[] = self::parseSequence($sequence, $i);
284+
$output[] = self::parseSequence($sequence, $i, $objectForMap);
283285
break;
284286
case '{':
285287
// nested mapping
286-
$output[] = self::parseMapping($sequence, $i);
288+
$output[] = self::parseMapping($sequence, $i, $objectForMap);
287289
break;
288290
case ']':
289291
return $output;
@@ -297,7 +299,8 @@ private static function parseSequence($sequence, &$i = 0)
297299
if (!$isQuoted && false !== strpos($value, ': ')) {
298300
// embedded mapping?
299301
try {
300-
$value = self::parseMapping('{'.$value.'}');
302+
$j = 0;
303+
$value = self::parseMapping('{'.$value.'}', $j, $objectForMap);
301304
} catch (\InvalidArgumentException $e) {
302305
// no, it's not
303306
}
@@ -317,14 +320,15 @@ private static function parseSequence($sequence, &$i = 0)
317320
/**
318321
* Parses a mapping to a YAML string.
319322
*
320-
* @param string $mapping
323+
* @param string $mapping
321324
* @param int &$i
325+
* @param bool $objectForMap true if maps should return a stdClass instead of array()
322326
*
323327
* @return string A YAML string
324328
*
325-
* @throws ParseException When malformed inline YAML string is parsed
329+
* @throws Exception\ParseException
326330
*/
327-
private static function parseMapping($mapping, &$i = 0)
331+
private static function parseMapping($mapping, &$i = 0, $objectForMap = false)
328332
{
329333
$output = array();
330334
$len = strlen($mapping);
@@ -338,6 +342,10 @@ private static function parseMapping($mapping, &$i = 0)
338342
++$i;
339343
continue 2;
340344
case '}':
345+
if (true === $objectForMap) {
346+
return (object) $output;
347+
}
348+
341349
return $output;
342350
}
343351

@@ -346,11 +354,13 @@ private static function parseMapping($mapping, &$i = 0)
346354

347355
// value
348356
$done = false;
357+
358+
349359
while ($i < $len) {
350360
switch ($mapping[$i]) {
351361
case '[':
352362
// nested sequence
353-
$value = self::parseSequence($mapping, $i);
363+
$value = self::parseSequence($mapping, $i, $objectForMap);
354364
// Spec: Keys MUST be unique; first one wins.
355365
// Parser cannot abort this mapping earlier, since lines
356366
// are processed sequentially.
@@ -361,7 +371,7 @@ private static function parseMapping($mapping, &$i = 0)
361371
break;
362372
case '{':
363373
// nested mapping
364-
$value = self::parseMapping($mapping, $i);
374+
$value = self::parseMapping($mapping, $i, $objectForMap);
365375
// Spec: Keys MUST be unique; first one wins.
366376
// Parser cannot abort this mapping earlier, since lines
367377
// are processed sequentially.
@@ -399,7 +409,7 @@ private static function parseMapping($mapping, &$i = 0)
399409
/**
400410
* Evaluates scalars and replaces magic values.
401411
*
402-
* @param string $scalar
412+
* @param string $scalar
403413
*
404414
* @return string A YAML string
405415
*/

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

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ public function testParse()
2222
}
2323
}
2424

25+
public function testParseWithMapObjects()
26+
{
27+
foreach ($this->getTestsForMapObjectParse() as $yaml => $value) {
28+
$actual = Inline::parse($yaml, false, false, true);
29+
if (true === is_object($value)) {
30+
$this->assertInstanceOf(get_class($value), $actual);
31+
$this->assertEquals(get_object_vars($value), get_object_vars($actual));
32+
} elseif (true === is_array($value)) {
33+
$this->assertEquals($value, $actual);
34+
$this->assertMixedArraysSame($value, $actual);
35+
} else {
36+
$this->assertSame($value, $actual);
37+
}
38+
}
39+
40+
}
41+
2542
public function testDump()
2643
{
2744
$testsForDump = $this->getTestsForDump();
@@ -182,6 +199,85 @@ protected function getTestsForParse()
182199
);
183200
}
184201

202+
protected function getTestsForMapObjectParse()
203+
{
204+
return array(
205+
'' => '',
206+
'null' => null,
207+
'false' => false,
208+
'true' => true,
209+
'12' => 12,
210+
'-12' => -12,
211+
'"quoted string"' => 'quoted string',
212+
"'quoted string'" => 'quoted string',
213+
'12.30e+02' => 12.30e+02,
214+
'0x4D2' => 0x4D2,
215+
'02333' => 02333,
216+
'.Inf' => -log(0),
217+
'-.Inf' => log(0),
218+
"'686e444'" => '686e444',
219+
'686e444' => 646e444,
220+
'123456789123456789123456789123456789' => '123456789123456789123456789123456789',
221+
'"foo\r\nbar"' => "foo\r\nbar",
222+
"'foo#bar'" => 'foo#bar',
223+
"'foo # bar'" => 'foo # bar',
224+
"'#cfcfcf'" => '#cfcfcf',
225+
'::form_base.html.twig' => '::form_base.html.twig',
226+
227+
'2007-10-30' => mktime(0, 0, 0, 10, 30, 2007),
228+
'2007-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 2007),
229+
'2007-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 2007),
230+
'1960-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 1960),
231+
'1730-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 1730),
232+
233+
'"a \\"string\\" with \'quoted strings inside\'"' => 'a "string" with \'quoted strings inside\'',
234+
"'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'',
235+
236+
// sequences
237+
// urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
238+
'[foo, http://urls.are/no/mappings, false, null, 12]' => array('foo', 'http://urls.are/no/mappings', false, null, 12),
239+
'[ foo , bar , false , null , 12 ]' => array('foo', 'bar', false, null, 12),
240+
'[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'),
241+
242+
// mappings
243+
'{foo:bar,bar:foo,false:false,null:null,integer:12}' => (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12),
244+
'{ foo : bar, bar : foo, false : false, null : null, integer : 12 }' => (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12),
245+
'{foo: \'bar\', bar: \'foo: bar\'}' => (object) array('foo' => 'bar', 'bar' => 'foo: bar'),
246+
'{\'foo\': \'bar\', "bar": \'foo: bar\'}' => (object) array('foo' => 'bar', 'bar' => 'foo: bar'),
247+
'{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}' => (object) array('foo\'' => 'bar', "bar\"" => 'foo: bar'),
248+
'{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}' => (object) array('foo: ' => 'bar', "bar: " => 'foo: bar'),
249+
250+
// nested sequences and mappings
251+
'[foo, [bar, foo]]' => array('foo', array('bar', 'foo')),
252+
'[foo, {bar: foo}]' => array('foo', (object) array('bar' => 'foo')),
253+
'{ foo: {bar: foo} }' => (object) array('foo' => (object) array('bar' => 'foo')),
254+
'{ foo: [bar, foo] }' => (object) array('foo' => array('bar', 'foo')),
255+
256+
'[ foo, [ bar, foo ] ]' => array('foo', array('bar', 'foo')),
257+
258+
'[{ foo: {bar: foo} }]' => array((object) array('foo' => (object) array('bar' => 'foo'))),
259+
260+
'[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')),
261+
262+
'[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]' => array('foo', (object) array('bar' => 'foo', 'foo' => array('foo', (object) array('bar' => 'foo'))), array('foo', (object) array('bar' => 'foo'))),
263+
264+
'[foo, bar: { foo: bar }]' => array('foo', '1' => (object) array('bar' => (object) array('foo' => 'bar'))),
265+
'[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']' => array('foo', '@foo.baz', (object) array('%foo%' => 'foo is %foo%', 'bar' => '%foo%',), true, '@service_container',),
266+
267+
'{}' => new \stdClass(),
268+
'{ foo : bar, bar : {} }' => (object) array('foo' => 'bar', 'bar' => new \stdClass()),
269+
'{ foo : [], bar : {} }' => (object) array('foo' => array(), 'bar' => new \stdClass()),
270+
'{foo: \'bar\', bar: {} }' => (object) array('foo' => 'bar', 'bar' => new \stdClass()),
271+
'{\'foo\': \'bar\', "bar": {}}' => (object) array('foo' => 'bar', 'bar' => new \stdClass()),
272+
'{\'foo\': \'bar\', "bar": \'{}\'}' => (object) array('foo' => 'bar', 'bar' => '{}'),
273+
274+
'[foo, [{}, {}]]' => array('foo', array(new \stdClass(), new \stdClass())),
275+
'[foo, [[], {}]]' => array('foo', array(array(), new \stdClass())),
276+
'[foo, [[{}, {}], {}]]' => array('foo', array(array(new \stdClass(), new \stdClass()), new \stdClass())),
277+
'[foo, {bar: {}}]' => array('foo', '1' => (object) array('bar' => new \stdClass())),
278+
);
279+
}
280+
185281
protected function getTestsForDump()
186282
{
187283
return array(
@@ -228,4 +324,27 @@ protected function getTestsForDump()
228324
'[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']' => array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%',), true, '@service_container',),
229325
);
230326
}
327+
328+
protected function assertMixedArraysSame($a, $b)
329+
{
330+
331+
foreach ($a as $key => $value) {
332+
if (array_key_exists($key, $b)) {
333+
if (is_array($value)) {
334+
$this->assertMixedArraysSame($value, $b[$key]);
335+
} else {
336+
if (true === is_object($value)) {
337+
$this->assertEquals($value, $b[$key]);
338+
$this->assertInstanceOf(get_class($value), $b[$key]);
339+
$this->assertEquals(get_object_vars($value), get_object_vars($b[$key]));
340+
} else {
341+
$this->assertSame($value, $b[$key]);
342+
}
343+
}
344+
} else {
345+
$this->assertFail();
346+
}
347+
}
348+
349+
}
231350
}

0 commit comments

Comments
 (0)
0