diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md new file mode 100644 index 0000000000000..d048c9f62dc34 --- /dev/null +++ b/UPGRADE-2.8.md @@ -0,0 +1,10 @@ +UPGRADE FROM 2.7 to 2.8 +======================= + +Yaml +----- + + * The ability to pass $timestampAsDateTime = false to the Yaml::parse method is + deprecated since version 2.8. The argument will be removed in 3.0. Pass true instead. + * The ability to pass $dateTimeSupport = false to the Yaml::dump method is deprecated + since version 2.8. The argument will be removed in 3.0. Pass true instead. diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index 096cf654d8058..37e4dda18fa5f 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -6,3 +6,12 @@ CHANGELOG * Yaml::parse() does not evaluate loaded files as PHP files by default anymore (call Yaml::enablePhpParsing() to get back the old behavior) + +2.8 +--- + + * Added a $timestampAsDateTime argument to the Yaml::parse() and Yaml::dump() methods. + * The ability to pass $timestampAsDateTime = false to the Yaml::parse method is + deprecated since version 2.8. The argument will be removed in 3.0. Pass true instead. + * The ability to pass $timestampAsDateTime = false to the Yaml::dump method is deprecated + since version 2.8. The argument will be removed in 3.0. Pass true instead. diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index 39cdcfc536a1b..4178f4dedf3c1 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -43,16 +43,17 @@ public function setIndentation($num) * @param int $indent The level of indentation (used internally) * @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 $timestampAsDateTime true if DateTime objects must be dumped as YAML timestamps, false if DateTime objects are not supported * * @return string The YAML representation of the PHP value */ - public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false) + public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false, $timestampAsDateTime = false) { $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; if ($inline <= 0 || !is_array($input) || empty($input)) { - $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport); + $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport, $timestampAsDateTime); } else { $isAHash = array_keys($input) !== range(0, count($input) - 1); @@ -61,9 +62,9 @@ public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = $output .= sprintf('%s%s%s%s', $prefix, - $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-', + $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport, $timestampAsDateTime).':' : '-', $willBeInlined ? ' ' : "\n", - $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport) + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport, $timestampAsDateTime) ).($willBeInlined ? "\n" : ''); } } diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 52ea724cf4070..c265a612bf3b2 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -26,6 +26,7 @@ class Inline private static $exceptionOnInvalidType = false; private static $objectSupport = false; private static $objectForMap = false; + private static $timestampAsDateTime = false; /** * Converts a YAML string to a PHP array. @@ -35,16 +36,18 @@ class Inline * @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 bool $timestampAsDateTime true if timestamps must be parsed as DateTime objects rather than Unix timestamps (integers) * * @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, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array(), $timestampAsDateTime = false) { self::$exceptionOnInvalidType = $exceptionOnInvalidType; self::$objectSupport = $objectSupport; self::$objectForMap = $objectForMap; + self::$timestampAsDateTime = $timestampAsDateTime; $value = trim($value); @@ -89,13 +92,16 @@ public static function parse($value, $exceptionOnInvalidType = false, $objectSup * @param mixed $value The PHP variable to convert * @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 $timestampAsDateTime true if DateTime objects must be dumped as YAML timestamps, false if DateTime objects are not supported * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ - public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) + public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false, $timestampAsDateTime = false) { + self::$timestampAsDateTime = $timestampAsDateTime; + switch (true) { case is_resource($value): if ($exceptionOnInvalidType) { @@ -104,6 +110,18 @@ public static function dump($value, $exceptionOnInvalidType = false, $objectSupp return 'null'; case is_object($value): + if (self::$timestampAsDateTime && ($value instanceof \DateTime || $value instanceof \DateTimeImmutable)) { + if ($value->getTimezone()->getName() === date_default_timezone_get()) { + if ('000000' === $value->format('His')) { + return $value->format('Y-m-d'); + } + + return $value->format('Y-m-d H:i:s'); + } + + return $value->format(\DateTime::W3C); + } + if ($objectSupport) { return '!!php/object:'.serialize($value); } @@ -502,6 +520,10 @@ private static function evaluateScalar($scalar, $references = array()) case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): return (float) str_replace(',', '', $scalar); case preg_match(self::getTimestampRegex(), $scalar): + if (self::$timestampAsDateTime) { + return new \DateTime($scalar); + } + return strtotime($scalar); } default: diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index d72c7639c8337..e74ffbfd8e8f1 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -45,12 +45,13 @@ public function __construct($offset = 0) * @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 bool $timestampAsDateTime true if timestamps must be parsed as DateTime objects rather than Unix timestamps (integers) * * @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, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $timestampAsDateTime = false) { if (!preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.'); @@ -95,7 +96,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), $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); } else { if (isset($values['leadspaces']) && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) @@ -110,9 +111,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, $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); } else { - $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); + $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); } } } elseif (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))) { @@ -122,7 +123,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = $context = 'mapping'; // force correct settings - Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, null, $timestampAsDateTime); try { $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { @@ -161,7 +162,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, $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); if (!is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); @@ -212,7 +213,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(), $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); // 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])) { @@ -220,7 +221,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = } } } else { - $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); + $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); // 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])) { @@ -236,7 +237,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = // 1-liner optionally followed by newline(s) if ($this->lines[0] === trim($value)) { try { - $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs, $timestampAsDateTime); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); @@ -433,15 +434,16 @@ 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 $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 bool $timestampAsDateTime true if timestamps must be parsed as DateTime objects rather than Unix timestamps (integers) * * @return mixed A PHP value * * @throws ParseException When reference does not exist */ - private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap) + private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime) { if (0 === strpos($value, '*')) { if (false !== $pos = strpos($value, '#')) { @@ -464,7 +466,7 @@ private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $ob } try { - return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs, $timestampAsDateTime); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 320eb5072132b..b900807edaa41 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -33,6 +33,16 @@ public function testParseWithMapObjects($yaml, $value) $this->assertSame(serialize($value), serialize($actual)); } + /** + * @dataProvider getTestsForParseWithTimestampAsDateTime + */ + public function testParseTimestampAsDateTime($yamlTimestamp) + { + $expected = serialize(new \DateTime($yamlTimestamp)); + $actual = serialize(Inline::parse($yamlTimestamp, false, false, false, null, true)); + $this->assertSame($expected, $actual); + } + /** * @dataProvider getTestsForDump */ @@ -43,6 +53,14 @@ public function testDump($yaml, $value) $this->assertSame($value, Inline::parse(Inline::dump($value)), 'check consistency'); } + /** + * @dataProvider getTestsForDumpWithDateTimeSupport + */ + public function testDumpWithDateTimeSupport($yaml, $value) + { + $this->assertSame($yaml, Inline::dump($value, false, false, true)); + } + public function testDumpNumericValueWithLocale() { $locale = setlocale(LC_NUMERIC, 0); @@ -290,7 +308,7 @@ public function getTestsForParseWithMapObjects() array('{foo: \'bar\', bar: \'foo: bar\'}', (object) array('foo' => 'bar', 'bar' => 'foo: bar')), array('{\'foo\': \'bar\', "bar": \'foo: bar\'}', (object) array('foo' => 'bar', 'bar' => 'foo: bar')), array('{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', (object) array('foo\'' => 'bar', "bar\"" => 'foo: bar')), - array('{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', (object) array('foo: ' => 'bar', "bar: " => 'foo: bar')), + array('{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', (object) array('foo: ' => 'bar', 'bar: ' => 'foo: bar')), // nested sequences and mappings array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))), @@ -323,6 +341,17 @@ public function getTestsForParseWithMapObjects() ); } + public function getTestsForParseWithTimestampAsDateTime() + { + return array( + array('2007-10-30'), + array('2007-10-30T02:59:43Z'), + array('2007-10-30 02:59:43 Z'), + array('1960-10-30 02:59:43 Z'), + array('1730-10-30T02:59:43Z'), + ); + } + public function getTestsForDump() { return array( @@ -377,4 +406,14 @@ public function getTestsForDump() array('[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')), ); } + + public function getTestsForDumpWithDateTimeSupport () { + return array( + array('2015-04-21T05:30:30-08:00', new \DateTime('2015-04-21 05:30:30 -8')), + array('2015-04-21T00:00:00-02:00', new \DateTime('2015-04-21 -2')), + array('2015-04-21T00:00:00+00:00', new \DateTime('2015-04-21 -0')), + array('2015-04-21 05:30:30', new \DateTime('2015-04-21 05:30:30')), + array('2015-04-21', new \DateTime('2015-04-21')), + ); + } } diff --git a/src/Symfony/Component/Yaml/Yaml.php b/src/Symfony/Component/Yaml/Yaml.php index c9295b094bb0a..a9a6c6beee387 100644 --- a/src/Symfony/Component/Yaml/Yaml.php +++ b/src/Symfony/Component/Yaml/Yaml.php @@ -41,21 +41,28 @@ class Yaml * @param string $input Path to a YAML file or 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 bool $timestampAsDateTime True if timestamps must be parsed as DateTime objects rather than Unix timestamps (integers) * * @return array The YAML converted to a PHP array * * @throws ParseException If the YAML is not valid * - * @deprecated The ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. + * @deprecated The ability to pass file names to the Yaml::parse method is deprecated since version 2.7 and will be removed in 3.0. Pass the YAML contents of the file instead. + * @deprecated The ability to pass $timestampAsDateTime = false to the Yaml::parse method is deprecated since version 2.8. The argument will be removed in 3.0. Pass true instead. * * @api */ - public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false) + public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $timestampAsDateTime = false) { + if (!$timestampAsDateTime) { + trigger_error('The ability to pass $timestampAsDateTime = false to the '.__METHOD__.' method is deprecated since version 2.8. The argument will be removed in 3.0. Pass true instead.', E_USER_DEPRECATED); + } + // if input is a file, process it $file = ''; if (strpos($input, "\n") === false && is_file($input)) { - trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); + trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.7 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); if (false === is_readable($input)) { throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); @@ -68,7 +75,7 @@ public static function parse($input, $exceptionOnInvalidType = false, $objectSup $yaml = new Parser(); try { - return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport); + return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap, $timestampAsDateTime); } catch (ParseException $e) { if ($file) { $e->setParsedFile($file); @@ -89,16 +96,23 @@ public static function parse($input, $exceptionOnInvalidType = false, $objectSup * @param int $indent The amount of spaces to use for indentation of nested nodes. * @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 $timestampAsDateTime true if DateTime objects must be dumped as YAML timestamps, false if DateTime objects are not supported * * @return string A YAML string representing the original PHP array * + * @deprecated The ability to pass $timestampAsDateTime = false to the Yaml::dump method is deprecated since version 2.8. The argument will be removed in 3.0. Pass true instead. + * * @api */ - public static function dump($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) + public static function dump($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false, $timestampAsDateTime = false) { + if (!$timestampAsDateTime) { + trigger_error('The ability to pass $timestampAsDateTime = false to the '.__METHOD__.' method is deprecated since version 2.8. The argument will be removed in 3.0. Pass true instead.', E_USER_DEPRECATED); + } + $yaml = new Dumper(); $yaml->setIndentation($indent); - return $yaml->dump($array, $inline, 0, $exceptionOnInvalidType, $objectSupport); + return $yaml->dump($array, $inline, 0, $exceptionOnInvalidType, $objectSupport, $timestampAsDateTime); } }