diff --git a/UPGRADE-3.1.md b/UPGRADE-3.1.md index e4844dd62077c..c2f1627ce0b10 100644 --- a/UPGRADE-3.1.md +++ b/UPGRADE-3.1.md @@ -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: diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index a485e69023766..97148148ca0e1 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -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: diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index c3fc3a83c21dd..cdbc491ae4578 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -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 diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 810e93bf31356..298ca1a212f1a 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -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); diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 9e4da1766431e..bdc30ba92d42f 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -41,17 +41,41 @@ public function __construct($offset = 0) /** * 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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = $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'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) @@ -110,9 +134,9 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = $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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = $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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = $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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = $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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = } } } 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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = // 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 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = 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 @@ private function moveToPreviousLine() /** * 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 @@ private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $ob } 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.'); diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index ab541dade2bdf..6136a46f6e918 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -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 @@ public function testParse($yaml, $value) * @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 @@ public function testParseScalarWithCorrectlyQuotedStringShouldReturnString() * @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 @@ public function getDataForParseReferences() } 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', diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 579cf8ce7d37e..f32fca13e595f 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -424,6 +424,18 @@ public function testObjectSupportEnabled() $input = <<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 = <<assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects'); } @@ -437,7 +449,7 @@ public function testObjectSupportEnabledWithDeprecatedTag() 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 @@ public function testObjectForMapEnabledWithMapping() $yaml = <<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 = <<parser->parse($yaml, false, false, true); @@ -462,6 +490,18 @@ public function testObjectForMapEnabledWithMapping() } 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 @@ public function testObjectForMapIsAppliedAfterParsing() $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 @@ public function testObjectForMapIsAppliedAfterParsing() */ 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() diff --git a/src/Symfony/Component/Yaml/Yaml.php b/src/Symfony/Component/Yaml/Yaml.php index b02f8a396dcf8..b06a7250ed0e9 100644 --- a/src/Symfony/Component/Yaml/Yaml.php +++ b/src/Symfony/Component/Yaml/Yaml.php @@ -21,6 +21,9 @@ 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); * * - * @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); } /**