introduce flags to customize the parser behavior

This commit is contained in:
Christian Flothmann 2016-02-08 19:49:14 +01:00
parent 80f3410da9
commit 9cb85522eb
8 changed files with 309 additions and 38 deletions

View File

@ -33,6 +33,51 @@ Serializer
Yaml
----
* Deprecated support for passing `true`/`false` as the second argument to the
`parse()` method to trigger exceptions when an invalid type was passed.
Before:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', true);
```
After:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', YAML::PARSE_EXCEPTION_ON_INVALID_TYPE);
```
* Deprecated support for passing `true`/`false` as the third argument to the
`parse()` method to toggle object support.
Before:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', false, true);
```
After:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', Yaml::PARSE_OBJECT);
```
* Deprecated support for passing `true`/`false` as the fourth argument to the
`parse()` method to parse objects as maps.
Before:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', false, false, true);
```
After:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', Yaml::PARSE_OBJECT_FOR_MAP);
```
* Deprecated support for passing `true`/`false` as the third argument to the `dump()` methods to toggle object support.
Before:

View File

@ -24,6 +24,51 @@ Serializer
Yaml
----
* Removed support for passing `true`/`false` as the second argument to the
`parse()` method to trigger exceptions when an invalid type was passed.
Before:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', true);
```
After:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', YAML::PARSE_EXCEPTION_ON_INVALID_TYPE);
```
* Removed support for passing `true`/`false` as the third argument to the
`parse()` method to toggle object support.
Before:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', false, true);
```
After:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', Yaml::PARSE_OBJECT);
```
* Removed support for passing `true`/`false` as the fourth argument to the
`parse()` method to parse objects as maps.
Before:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', false, false, true);
```
After:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', Yaml::PARSE_OBJECT_FOR_MAP);
```
* Removed support for passing `true`/`false` as the third argument to the `dump()` methods to toggle object support.
Before:

View File

@ -4,6 +4,12 @@ CHANGELOG
3.1.0
-----
* Added support for customizing the YAML parser behavior through an optional bit field:
```php
Yaml::parse('{ "foo": "bar", "fiz": "cat" }', Yaml::EXCEPTION_ON_INVALID_TYPE | Yaml::PARSE_OBJECT | Yaml::PARSE_OBJECT_FOR_MAP);
```
* Added support for customizing the dumped YAML string through an optional bit field:
```php

View File

@ -30,21 +30,51 @@ class Inline
/**
* Converts a YAML string to a PHP array.
*
* @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param bool $objectForMap true if maps should return a stdClass instead of array()
* @param array $references Mapping of variable names to values
* @param string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param array $references Mapping of variable names to values
*
* @return array A PHP array representing the YAML string
*
* @throws ParseException
*/
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
public static function parse($value, $flags = 0, $references = array())
{
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
self::$objectSupport = $objectSupport;
self::$objectForMap = $objectForMap;
if (is_bool($flags)) {
@trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);
if ($flags) {
$flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
} else {
$flags = 0;
}
}
if (func_num_args() >= 3 && !is_array($references)) {
@trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED);
if ($references) {
$flags |= Yaml::PARSE_OBJECT;
}
if (func_num_args() >= 4) {
@trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);
if (func_get_arg(3)) {
$flags |= Yaml::PARSE_OBJECT_FOR_MAP;
}
}
if (func_num_args() >= 5) {
$references = func_get_arg(4);
} else {
$references = array();
}
}
self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
$value = trim($value);

View File

@ -41,17 +41,41 @@ class Parser
/**
* Parses a YAML string to a PHP value.
*
* @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param bool $objectForMap true if maps should return a stdClass instead of array()
* @param string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed A PHP value
*
* @throws ParseException If the YAML is not valid
*/
public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
public function parse($value, $flags = 0)
{
if (is_bool($flags)) {
@trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);
if ($flags) {
$flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
} else {
$flags = 0;
}
}
if (func_num_args() >= 3) {
@trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED);
if (func_get_arg(2)) {
$flags |= Yaml::PARSE_OBJECT;
}
}
if (func_num_args() >= 4) {
@trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);
if (func_get_arg(3)) {
$flags |= Yaml::PARSE_OBJECT_FOR_MAP;
}
}
if (!preg_match('//u', $value)) {
throw new ParseException('The YAML value does not appear to be valid UTF-8.');
}
@ -95,7 +119,7 @@ class Parser
$c = $this->getRealCurrentLineNb() + 1;
$parser = new self($c);
$parser->refs = &$this->refs;
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $flags);
} else {
if (isset($values['leadspaces'])
&& preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
@ -110,9 +134,9 @@ class Parser
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
}
$data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
$data[] = $parser->parse($block, $flags);
} else {
$data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
$data[] = $this->parseValue($values['value'], $flags, $context);
}
}
if ($isRef) {
@ -125,7 +149,7 @@ class Parser
$context = 'mapping';
// force correct settings
Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
Inline::parse(null, $flags, $this->refs);
try {
$key = Inline::parseScalar($values['key']);
} catch (ParseException $e) {
@ -169,7 +193,7 @@ class Parser
$c = $this->getRealCurrentLineNb() + 1;
$parser = new self($c);
$parser->refs = &$this->refs;
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
$parsed = $parser->parse($value, $flags);
if (!is_array($parsed)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
@ -220,7 +244,7 @@ class Parser
$c = $this->getRealCurrentLineNb() + 1;
$parser = new self($c);
$parser->refs = &$this->refs;
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
$value = $parser->parse($this->getNextEmbedBlock(), $flags);
// 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])) {
@ -228,7 +252,7 @@ class Parser
}
}
} else {
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
$value = $this->parseValue($values['value'], $flags, $context);
// 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])) {
@ -247,7 +271,7 @@ class Parser
// 1-liner optionally followed by newline(s)
if (is_string($value) && $this->lines[0] === trim($value)) {
try {
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
$value = Inline::parse($this->lines[0], $flags, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
@ -301,7 +325,7 @@ class Parser
mb_internal_encoding($mbEncoding);
}
if ($objectForMap && !is_object($data)) {
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !is_object($data)) {
$data = (object) $data;
}
@ -462,17 +486,15 @@ class Parser
/**
* Parses a YAML value.
*
* @param string $value A YAML value
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
* @param bool $objectSupport True if object support is enabled, false otherwise
* @param bool $objectForMap true if maps should return a stdClass instead of array()
* @param string $context The parser context (either sequence or mapping)
* @param string $value A YAML value
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param string $context The parser context (either sequence or mapping)
*
* @return mixed A PHP value
*
* @throws ParseException When reference does not exist
*/
private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context)
private function parseValue($value, $flags, $context)
{
if (0 === strpos($value, '*')) {
if (false !== $pos = strpos($value, '#')) {
@ -495,7 +517,7 @@ class Parser
}
try {
$parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
$parsedValue = Inline::parse($value, $flags, $this->refs);
if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
throw new ParseException('A colon cannot be used in an unquoted mapping value.');

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Yaml\Tests;
use Symfony\Component\Yaml\Inline;
use Symfony\Component\Yaml\Yaml;
class InlineTest extends \PHPUnit_Framework_TestCase
{
@ -27,6 +28,17 @@ class InlineTest extends \PHPUnit_Framework_TestCase
* @dataProvider getTestsForParseWithMapObjects
*/
public function testParseWithMapObjects($yaml, $value)
{
$actual = Inline::parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP);
$this->assertSame(serialize($value), serialize($actual));
}
/**
* @group legacy
* @dataProvider getTestsForParseWithMapObjects
*/
public function testParseWithMapObjectsPassingTrue($yaml, $value)
{
$actual = Inline::parse($yaml, false, false, true);
@ -142,6 +154,15 @@ class InlineTest extends \PHPUnit_Framework_TestCase
* @dataProvider getDataForParseReferences
*/
public function testParseReferences($yaml, $expected)
{
$this->assertSame($expected, Inline::parse($yaml, 0, array('var' => 'var-value')));
}
/**
* @group legacy
* @dataProvider getDataForParseReferences
*/
public function testParseReferencesAsFifthArgument($yaml, $expected)
{
$this->assertSame($expected, Inline::parse($yaml, false, false, false, array('var' => 'var-value')));
}
@ -161,6 +182,19 @@ class InlineTest extends \PHPUnit_Framework_TestCase
}
public function testParseMapReferenceInSequence()
{
$foo = array(
'a' => 'Steve',
'b' => 'Clark',
'c' => 'Brian',
);
$this->assertSame(array($foo), Inline::parse('[*foo]', 0, array('foo' => $foo)));
}
/**
* @group legacy
*/
public function testParseMapReferenceInSequenceAsFifthArgument()
{
$foo = array(
'a' => 'Steve',

View File

@ -424,6 +424,18 @@ EOF;
$input = <<<EOF
foo: !php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
bar: 1
EOF;
$this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects');
}
/**
* @group legacy
*/
public function testObjectSupportEnabledPassingTrue()
{
$input = <<<EOF
foo: !php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
bar: 1
EOF;
$this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects');
}
@ -437,7 +449,7 @@ EOF;
foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
bar: 1
EOF;
$this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects');
$this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects');
}
/**
@ -453,6 +465,22 @@ EOF;
$yaml = <<<EOF
foo:
fiz: [cat]
EOF;
$result = $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP);
$this->assertInstanceOf('stdClass', $result);
$this->assertInstanceOf('stdClass', $result->foo);
$this->assertEquals(array('cat'), $result->foo->fiz);
}
/**
* @group legacy
*/
public function testObjectForMapEnabledWithMappingUsingBooleanToggles()
{
$yaml = <<<EOF
foo:
fiz: [cat]
EOF;
$result = $this->parser->parse($yaml, false, false, true);
@ -462,6 +490,18 @@ EOF;
}
public function testObjectForMapEnabledWithInlineMapping()
{
$result = $this->parser->parse('{ "foo": "bar", "fiz": "cat" }', Yaml::PARSE_OBJECT_FOR_MAP);
$this->assertInstanceOf('stdClass', $result);
$this->assertEquals('bar', $result->foo);
$this->assertEquals('cat', $result->fiz);
}
/**
* @group legacy
*/
public function testObjectForMapEnabledWithInlineMappingUsingBooleanToggles()
{
$result = $this->parser->parse('{ "foo": "bar", "fiz": "cat" }', false, false, true);
@ -476,6 +516,18 @@ EOF;
$expected->foo = 'bar';
$expected->baz = 'foobar';
$this->assertEquals($expected, $this->parser->parse("foo: bar\nbaz: foobar", Yaml::PARSE_OBJECT_FOR_MAP));
}
/**
* @group legacy
*/
public function testObjectForMapIsAppliedAfterParsingUsingBooleanToggles()
{
$expected = new \stdClass();
$expected->foo = 'bar';
$expected->baz = 'foobar';
$this->assertEquals($expected, $this->parser->parse("foo: bar\nbaz: foobar", false, false, true));
}
@ -485,7 +537,17 @@ EOF;
*/
public function testObjectsSupportDisabledWithExceptions($yaml)
{
$this->parser->parse($yaml, true, false);
$this->parser->parse($yaml, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
}
/**
* @group legacy
* @dataProvider invalidDumpedObjectProvider
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
*/
public function testObjectsSupportDisabledWithExceptionsUsingBooleanToggles($yaml)
{
$this->parser->parse($yaml, true);
}
public function invalidDumpedObjectProvider()

View File

@ -21,6 +21,9 @@ use Symfony\Component\Yaml\Exception\ParseException;
class Yaml
{
const DUMP_OBJECT = 1;
const PARSE_EXCEPTION_ON_INVALID_TYPE = 2;
const PARSE_OBJECT = 4;
const PARSE_OBJECT_FOR_MAP = 8;
/**
* Parses YAML into a PHP value.
@ -31,20 +34,44 @@ class Yaml
* print_r($array);
* </code>
*
* @param string $input A string containing YAML
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
* @param bool $objectSupport True if object support is enabled, false otherwise
* @param bool $objectForMap True if maps should return a stdClass instead of array()
* @param string $input A string containing YAML
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the YAML is not valid
*/
public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
public static function parse($input, $flags = 0)
{
if (is_bool($flags)) {
@trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);
if ($flags) {
$flags = self::PARSE_EXCEPTION_ON_INVALID_TYPE;
} else {
$flags = 0;
}
}
if (func_num_args() >= 3) {
@trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the PARSE_OBJECT flag instead.', E_USER_DEPRECATED);
if (func_get_arg(2)) {
$flags |= self::PARSE_OBJECT;
}
}
if (func_num_args() >= 4) {
@trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);
if (func_get_arg(3)) {
$flags |= self::PARSE_OBJECT_FOR_MAP;
}
}
$yaml = new Parser();
return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap);
return $yaml->parse($input, $flags);
}
/**