Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[YAML] Added support for object-maps #10114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions src/Symfony/Component/Yaml/Inline.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ class Inline
* @param string $value A YAML string
* @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param Boolean $objectSupport true if object support is enabled, false otherwise
* @param Boolean $objectForMap true if maps should return a stdClass instead of array()
*
* @throws Exception\ParseException
* @return array A PHP array representing the YAML string
*
* @throws ParseException
*/
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
{
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
self::$objectSupport = $objectSupport;
Expand All @@ -56,11 +57,11 @@ public static function parse($value, $exceptionOnInvalidType = false, $objectSup
$i = 0;
switch ($value[0]) {
case '[':
$result = self::parseSequence($value, $i);
$result = self::parseSequence($value, $i, $objectForMap);
++$i;
break;
case '{':
$result = self::parseMapping($value, $i);
$result = self::parseMapping($value, $i, $objectForMap);
++$i;
break;
default:
Expand Down Expand Up @@ -259,14 +260,14 @@ private static function parseQuotedScalar($scalar, &$i)
/**
* Parses a sequence to a YAML string.
*
* @param string $sequence
* @param string $sequence
* @param integer &$i
* @param Boolean $objectForMap true if maps should return a stdClass instead of array()
*
* @throws Exception\ParseException
* @return string A YAML string
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseSequence($sequence, &$i = 0)
private static function parseSequence($sequence, &$i = 0, $objectForMap = false)
{
$output = array();
$len = strlen($sequence);
Expand All @@ -277,11 +278,11 @@ private static function parseSequence($sequence, &$i = 0)
switch ($sequence[$i]) {
case '[':
// nested sequence
$output[] = self::parseSequence($sequence, $i);
$output[] = self::parseSequence($sequence, $i, $objectForMap);
break;
case '{':
// nested mapping
$output[] = self::parseMapping($sequence, $i);
$output[] = self::parseMapping($sequence, $i, $objectForMap);
break;
case ']':
return $output;
Expand All @@ -295,7 +296,8 @@ private static function parseSequence($sequence, &$i = 0)
if (!$isQuoted && false !== strpos($value, ': ')) {
// embedded mapping?
try {
$value = self::parseMapping('{'.$value.'}');
$j = 0;
$value = self::parseMapping('{'.$value.'}', $j, $objectForMap);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The j variable is a bit useless here, why don't you use 0 as argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it is required. :(

parseMapping() requires the second parameter to be passed by reference:

private static function parseMapping($mapping, &$i = 0, $objectForMap = false)

PHP disallows passing constants by reference because the method may mutate the value (and indeed it does). So you must create a temporary variable and pass that instead. PHP doesn't mind if you omit the parameter because of the default on the function, but if you specify arguments that come after the referenced parameter, you must supply one.

I could do the variable assignment inline, but it's effectively the same thing:

$value = self::parseMapping('{'.$value.'}', $j = 0, $objectForMap);

Open to alternatives, but I did it this way because alternative approaches (removing the reference) would require fairly drastic restructuring of how the parsing works for what is ultimately just a little semantic blemish :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, missed that. Sad it has to be done this way :(

} catch (\InvalidArgumentException $e) {
// no, it's not
}
Expand All @@ -315,14 +317,15 @@ private static function parseSequence($sequence, &$i = 0)
/**
* Parses a mapping to a YAML string.
*
* @param string $mapping
* @param string $mapping
* @param integer &$i
* @param Boolean $objectForMap true if maps should return a stdClass instead of array()
*
* @throws Exception\ParseException
* @return string A YAML string
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping($mapping, &$i = 0)
private static function parseMapping($mapping, &$i = 0, $objectForMap = false)
{
$output = array();
$len = strlen($mapping);
Expand All @@ -336,6 +339,10 @@ private static function parseMapping($mapping, &$i = 0)
++$i;
continue 2;
case '}':
if (true === $objectForMap) {
return (object) $output;
}

return $output;
}

Expand All @@ -344,16 +351,18 @@ private static function parseMapping($mapping, &$i = 0)

// value
$done = false;


while ($i < $len) {
switch ($mapping[$i]) {
case '[':
// nested sequence
$output[$key] = self::parseSequence($mapping, $i);
$output[$key] = self::parseSequence($mapping, $i, $objectForMap);
$done = true;
break;
case '{':
// nested mapping
$output[$key] = self::parseMapping($mapping, $i);
$output[$key] = self::parseMapping($mapping, $i, $objectForMap);
$done = true;
break;
case ':':
Expand Down
119 changes: 119 additions & 0 deletions src/Symfony/Component/Yaml/Tests/InlineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ public function testParse()
}
}

public function testParseWithMapObjects()
{
foreach ($this->getTestsForMapObjectParse() as $yaml => $value) {
$actual = Inline::parse($yaml, false, false, true);
if (true === is_object($value)) {
$this->assertInstanceOf(get_class($value), $actual);
$this->assertEquals(get_object_vars($value), get_object_vars($actual));
} elseif (true === is_array($value)) {
$this->assertEquals($value, $actual);
$this->assertMixedArraysSame($value, $actual);
} else {
$this->assertSame($value, $actual);
}
}

}

public function testDump()
{
$testsForDump = $this->getTestsForDump();
Expand Down Expand Up @@ -182,6 +199,85 @@ protected function getTestsForParse()
);
}

protected function getTestsForMapObjectParse()
{
return array(
'' => '',
'null' => null,
'false' => false,
'true' => true,
'12' => 12,
'-12' => -12,
'"quoted string"' => 'quoted string',
"'quoted string'" => 'quoted string',
'12.30e+02' => 12.30e+02,
'0x4D2' => 0x4D2,
'02333' => 02333,
'.Inf' => -log(0),
'-.Inf' => log(0),
"'686e444'" => '686e444',
'686e444' => 646e444,
'123456789123456789123456789123456789' => '123456789123456789123456789123456789',
'"foo\r\nbar"' => "foo\r\nbar",
"'foo#bar'" => 'foo#bar',
"'foo # bar'" => 'foo # bar',
"'#cfcfcf'" => '#cfcfcf',
'::form_base.html.twig' => '::form_base.html.twig',

'2007-10-30' => mktime(0, 0, 0, 10, 30, 2007),
'2007-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 2007),
'2007-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 2007),
'1960-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 1960),
'1730-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 1730),

'"a \\"string\\" with \'quoted strings inside\'"' => 'a "string" with \'quoted strings inside\'',
"'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'',

// sequences
// urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
'[foo, http://urls.are/no/mappings, false, null, 12]' => array('foo', 'http://urls.are/no/mappings', false, null, 12),
'[ foo , bar , false , null , 12 ]' => array('foo', 'bar', false, null, 12),
'[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'),

// mappings
'{foo:bar,bar:foo,false:false,null:null,integer:12}' => (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12),
'{ foo : bar, bar : foo, false : false, null : null, integer : 12 }' => (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12),
'{foo: \'bar\', bar: \'foo: bar\'}' => (object) array('foo' => 'bar', 'bar' => 'foo: bar'),
'{\'foo\': \'bar\', "bar": \'foo: bar\'}' => (object) array('foo' => 'bar', 'bar' => 'foo: bar'),
'{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}' => (object) array('foo\'' => 'bar', "bar\"" => 'foo: bar'),
'{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}' => (object) array('foo: ' => 'bar', "bar: " => 'foo: bar'),

// nested sequences and mappings
'[foo, [bar, foo]]' => array('foo', array('bar', 'foo')),
'[foo, {bar: foo}]' => array('foo', (object) array('bar' => 'foo')),
'{ foo: {bar: foo} }' => (object) array('foo' => (object) array('bar' => 'foo')),
'{ foo: [bar, foo] }' => (object) array('foo' => array('bar', 'foo')),

'[ foo, [ bar, foo ] ]' => array('foo', array('bar', 'foo')),

'[{ foo: {bar: foo} }]' => array((object) array('foo' => (object) array('bar' => 'foo'))),

'[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')),

'[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'))),

'[foo, bar: { foo: bar }]' => array('foo', '1' => (object) array('bar' => (object) array('foo' => 'bar'))),
'[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',),

'{}' => new \stdClass(),
'{ foo : bar, bar : {} }' => (object) array('foo' => 'bar', 'bar' => new \stdClass()),
'{ foo : [], bar : {} }' => (object) array('foo' => array(), 'bar' => new \stdClass()),
'{foo: \'bar\', bar: {} }' => (object) array('foo' => 'bar', 'bar' => new \stdClass()),
'{\'foo\': \'bar\', "bar": {}}' => (object) array('foo' => 'bar', 'bar' => new \stdClass()),
'{\'foo\': \'bar\', "bar": \'{}\'}' => (object) array('foo' => 'bar', 'bar' => '{}'),

'[foo, [{}, {}]]' => array('foo', array(new \stdClass(), new \stdClass())),
'[foo, [[], {}]]' => array('foo', array(array(), new \stdClass())),
'[foo, [[{}, {}], {}]]' => array('foo', array(array(new \stdClass(), new \stdClass()), new \stdClass())),
'[foo, {bar: {}}]' => array('foo', '1' => (object) array('bar' => new \stdClass())),
);
}

protected function getTestsForDump()
{
return array(
Expand Down Expand Up @@ -229,4 +325,27 @@ protected function getTestsForDump()
'[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',),
);
}

protected function assertMixedArraysSame($a, $b)
{

foreach ($a as $key => $value) {
if (array_key_exists($key, $b)) {
if (is_array($value)) {
$this->assertMixedArraysSame($value, $b[$key]);
} else {
if (true === is_object($value)) {
$this->assertEquals($value, $b[$key]);
$this->assertInstanceOf(get_class($value), $b[$key]);
$this->assertEquals(get_object_vars($value), get_object_vars($b[$key]));
} else {
$this->assertSame($value, $b[$key]);
}
}
} else {
$this->assertFail();
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh, yep. Lemme look through the whole PR and see if I've done this anywhere else :)

}
}