8000 [YAML] Added support for object maps · symfony/symfony@f32705e · GitHub
[go: up one dir, main page]

Skip to content

Commit f32705e

Browse files
committed
[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 d99507b commit f32705e

File tree

2 files changed

+158
-18
lines changed

2 files changed

+158
-18
lines changed

src/Symfony/Component/Yaml/Inline.php

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,20 @@ class Inline
2626
private static $exceptionOnInvalidType = false;
2727
private static $objectSupport = false;
2828

29+
2930
/**
3031
* Converts a YAML string to a PHP array.
3132
*
3233
* @param string $value A YAML string
3334
* @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
3435
* @param Boolean $objectSupport true if object support is enabled, false otherwise
36+
* @param Boolean $objectForMap true if maps should return a stdClass instead of array()
3537
*
38+
* @throws Exception\ParseException
3639
* @return array A PHP array representing the YAML string
3740
*
38-
* @throws ParseException
3941
*/
40-
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
42+
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
4143
{
4244
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
4345
self::$objectSupport = $objectSupport;
@@ -56,11 +58,11 @@ public static function parse($value, $exceptionOnInvalidType = false, $objectSup
5658
$i = 0;
5759
switch ($value[0]) {
5860
case '[':
59-
$result = self::parseSequence($value, $i);
61+
$result = self::parseSequence($value, $i, $objectForMap);
6062
++$i;
6163
break;
6264
case '{':
63-
$result = self::parseMapping($value, $i);
65+
$result = self::parseMapping($value, $i, $objectForMap);
6466
++$i;
6567
break;
6668
default:
@@ -256,17 +258,18 @@ private static function parseQuotedScalar($scalar, &$i)
256258
return $output;
257259
}
258260

261+
259262
/**
260263
* Parses a sequence to a YAML string.
261264
*
262-
* @param string $sequence
265+
* @param string $sequence
263266
* @param integer &$i
267+
* @param Boolean $objectForMap true if maps should return a stdClass instead of array()
264268
*
269+
* @throws Exception\ParseException
265270
* @return string A YAML string
266-
*
267-
* @throws ParseException When malformed inline YAML string is parsed
268271
*/
269-
private static function parseSequence($sequence, &$i = 0)
272+
private static function parseSequence($sequence, &$i = 0, $objectForMap = false)
270273
{
271274
$output = array();
272275
$len = strlen($sequence);
@@ -277,11 +280,11 @@ private static function parseSequence($sequence, &$i = 0)
277280
switch ($sequence[$i]) {
278281
case '[':
279282
// nested sequence
280-
$output[] = self::parseSequence($sequence, $i);
283+
$output[] = self::parseSequence($sequence, $i, $objectForMap);
281284
break;
282285
case '{':
283286
// nested mapping
284-
$output[] = self::parseMapping($sequence, $i);
287+
$output[] = self::parseMapping($sequence, $i, $objectForMap);
285288
break;
286289
case ']':
287290
return $output;
@@ -295,7 +298,8 @@ private static function parseSequence($sequence, &$i = 0)
295298
if (!$isQuoted && false !== strpos($value, ': ')) {
296299
// embedded mapping?
297300
try {
298-
$value = self::parseMapping('{'.$value.'}');
301+
$j = 0;
302+
$value = self::parseMapping('{'.$value.'}', $j, $objectForMap);
299303
} catch (\InvalidArgumentException $e) {
300304
// no, it's not
301305
}
@@ -312,19 +316,26 @@ private static function parseSequence($sequence, &$i = 0)
312316
throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence));
313317
}
314318

319+
315320
/**
316321
* Parses a mapping to a YAML string.
317322
*
318-
* @param string $mapping
323+
* @param string $mapping
319324
* @param integer &$i
325+
* @param Boolean $objectForMap true if maps should return a stdClass instead of array()
320326
*
327+
* @throws Exception\ParseException
321328
* @return string A YAML string
322329
*
323-
* @throws ParseException When malformed inline YAML string is parsed
324330
*/
325-
private static function parseMapping($mapping, &$i = 0)
331+
private static function parseMapping($mapping, &$i = 0, $objectForMap = false)
326332
{
327-
$output = array();
333+
if ($objectForMap === true) {
334+
$output = new \stdClass();
335+
} else {
336+
$output = array();
337+
}
338+
328339
$len = strlen($mapping);
329340
$i += 1;
330341

@@ -344,23 +355,30 @@ private static function parseMapping($mapping, &$i = 0)
344355

345356
// value
346357
$done = false;
358+
359+
if ($objectForMap === true) {
360+
$editPosition = &$output->$key;
361+
} else {
362+
$editPosition = &$output[$key];
363+
}
364+
347365
while ($i < $len) {
348366
switch ($mapping[$i]) {
349367
case '[':
350368
// nested sequence
351-
$output[$key] = self::parseSequence($mapping, $i);
369+
$editPosition = self::parseSequence($mapping, $i, $objectForMap);
352370
$done = true;
353371
break;
354372
case '{':
355373
// nested mapping
356-
$output[$key] = self::parseMapping($mapping, $i);
374+
$editPosition = self::parseMapping($mapping, $i, $objectForMap);
357375
$done = true;
358376
break;
359377
case ':':
360378
case ' ':
361379
break;
362380
default:
363-
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
381+
$editPosition = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
364382
$done = true;
365383
--$i;
366384
}

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

Lines changed: 122 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 (is_object($value) === true) {
30+
$this->assertInstanceOf(get_class($value), $actual);
31+
$this->assertEquals(get_object_vars($value), get_object_vars($actual));
32+
} elseif (is_array($value) === true) {
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,88 @@ 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 }' F438 => (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+
268+
'{}' => new \stdClass(),
269+
'{ foo : bar, bar : {} }' => (object)array('foo' => 'bar', 'bar' => new \stdClass()),
270+
'{ foo : [], bar : {} }' => (object)array('foo' => array(), 'bar' => new \stdClass()),
271+
'{foo: \'bar\', bar: {} }' => (object)array('foo' => 'bar', 'bar' => new \stdClass()),
272+
'{\'foo\': \'bar\', "bar": {}}' => (object)array('foo' => 'bar', 'bar' => new \stdClass()),
273+
'{\'foo\': \'bar\', "bar": \'{}\'}' => (object)array('foo' => 'bar', 'bar' => '{}'),
274+
275+
'[foo, [{}, {}]]' => array('foo', array(new \stdClass(), new \stdClass())),
276+
'[foo, [[], {}]]' => array('foo', array(array(), new \stdClass())),
277+
'[foo, [[{}, {}], {}]]' => array('foo', array(array(new \stdClass(), new \stdClass()), new \stdClass())),
278+
'[foo, {bar: {}}]' => array('foo', '1' => (object)array('bar' => new \stdClass())),
279+
);
280+
}
281+
282+
283+
185284
protected function getTestsForDump()
186285
{
187286
return array(
@@ -229,4 +328,27 @@ protected function getTestsForDump()
229328
'[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',),
230329
);
231330
}
331+
332+
protected function assertMixedArraysSame($a, $b)
333+
{
334+
335+
foreach ($a as $key => $value) {
336+
if (array_key_exists($key, $b)) {
337+
if (is_array($value)) {
338+
$this->assertMixedArraysSame($value, $b[$key]);
339+
} else {
340+
if (is_object($value) === true) {
341+
$this->assertEquals($value, $b[$key]);
342+
$this->assertInstanceOf(get_class($value), $b[$key]);
343+
$this->assertEquals(get_object_vars($value), get_object_vars($b[$key]));
344+
} else {
345+
$this->assertSame($value, $b[$key]);
346+
}
347+
}
348+
} else {
349+
$this->assertFail();
350+
}
351+
}
352+
353+
}
232354
}

0 commit comments

Comments
 (0)
0