DumperTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Yaml\Tests;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\Yaml\Dumper;
  13. use Symfony\Component\Yaml\Parser;
  14. use Symfony\Component\Yaml\Yaml;
  15. class DumperTest extends TestCase
  16. {
  17. protected $parser;
  18. protected $dumper;
  19. protected $path;
  20. protected $array = [
  21. '' => 'bar',
  22. 'foo' => '#bar',
  23. 'foo\'bar' => [],
  24. 'bar' => [1, 'foo'],
  25. 'foobar' => [
  26. 'foo' => 'bar',
  27. 'bar' => [1, 'foo'],
  28. 'foobar' => [
  29. 'foo' => 'bar',
  30. 'bar' => [1, 'foo'],
  31. ],
  32. ],
  33. ];
  34. protected function setUp()
  35. {
  36. $this->parser = new Parser();
  37. $this->dumper = new Dumper();
  38. $this->path = __DIR__.'/Fixtures';
  39. }
  40. protected function tearDown()
  41. {
  42. $this->parser = null;
  43. $this->dumper = null;
  44. $this->path = null;
  45. $this->array = null;
  46. }
  47. public function testIndentationInConstructor()
  48. {
  49. $dumper = new Dumper(7);
  50. $expected = <<<'EOF'
  51. '': bar
  52. foo: '#bar'
  53. 'foo''bar': { }
  54. bar:
  55. - 1
  56. - foo
  57. foobar:
  58. foo: bar
  59. bar:
  60. - 1
  61. - foo
  62. foobar:
  63. foo: bar
  64. bar:
  65. - 1
  66. - foo
  67. EOF;
  68. $this->assertEquals($expected, $dumper->dump($this->array, 4, 0));
  69. }
  70. public function testSpecifications()
  71. {
  72. $files = $this->parser->parse(file_get_contents($this->path.'/index.yml'));
  73. foreach ($files as $file) {
  74. $yamls = file_get_contents($this->path.'/'.$file.'.yml');
  75. // split YAMLs documents
  76. foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) {
  77. if (!$yaml) {
  78. continue;
  79. }
  80. $test = $this->parser->parse($yaml);
  81. if (isset($test['dump_skip']) && $test['dump_skip']) {
  82. continue;
  83. } elseif (isset($test['todo']) && $test['todo']) {
  84. // TODO
  85. } else {
  86. eval('$expected = '.trim($test['php']).';');
  87. $this->assertSame($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']);
  88. }
  89. }
  90. }
  91. }
  92. public function testInlineLevel()
  93. {
  94. $expected = <<<'EOF'
  95. { '': bar, foo: '#bar', 'foo''bar': { }, bar: [1, foo], foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } } }
  96. EOF;
  97. $this->assertEquals($expected, $this->dumper->dump($this->array, -10), '->dump() takes an inline level argument');
  98. $this->assertEquals($expected, $this->dumper->dump($this->array, 0), '->dump() takes an inline level argument');
  99. $expected = <<<'EOF'
  100. '': bar
  101. foo: '#bar'
  102. 'foo''bar': { }
  103. bar: [1, foo]
  104. foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } }
  105. EOF;
  106. $this->assertEquals($expected, $this->dumper->dump($this->array, 1), '->dump() takes an inline level argument');
  107. $expected = <<<'EOF'
  108. '': bar
  109. foo: '#bar'
  110. 'foo''bar': { }
  111. bar:
  112. - 1
  113. - foo
  114. foobar:
  115. foo: bar
  116. bar: [1, foo]
  117. foobar: { foo: bar, bar: [1, foo] }
  118. EOF;
  119. $this->assertEquals($expected, $this->dumper->dump($this->array, 2), '->dump() takes an inline level argument');
  120. $expected = <<<'EOF'
  121. '': bar
  122. foo: '#bar'
  123. 'foo''bar': { }
  124. bar:
  125. - 1
  126. - foo
  127. foobar:
  128. foo: bar
  129. bar:
  130. - 1
  131. - foo
  132. foobar:
  133. foo: bar
  134. bar: [1, foo]
  135. EOF;
  136. $this->assertEquals($expected, $this->dumper->dump($this->array, 3), '->dump() takes an inline level argument');
  137. $expected = <<<'EOF'
  138. '': bar
  139. foo: '#bar'
  140. 'foo''bar': { }
  141. bar:
  142. - 1
  143. - foo
  144. foobar:
  145. foo: bar
  146. bar:
  147. - 1
  148. - foo
  149. foobar:
  150. foo: bar
  151. bar:
  152. - 1
  153. - foo
  154. EOF;
  155. $this->assertEquals($expected, $this->dumper->dump($this->array, 4), '->dump() takes an inline level argument');
  156. $this->assertEquals($expected, $this->dumper->dump($this->array, 10), '->dump() takes an inline level argument');
  157. }
  158. public function testObjectSupportEnabled()
  159. {
  160. $dump = $this->dumper->dump(['foo' => new A(), 'bar' => 1], 0, 0, Yaml::DUMP_OBJECT);
  161. $this->assertEquals('{ foo: !php/object \'O:30:"Symfony\Component\Yaml\Tests\A":1:{s:1:"a";s:3:"foo";}\', bar: 1 }', $dump, '->dump() is able to dump objects');
  162. }
  163. public function testObjectSupportDisabledButNoExceptions()
  164. {
  165. $dump = $this->dumper->dump(['foo' => new A(), 'bar' => 1]);
  166. $this->assertEquals('{ foo: null, bar: 1 }', $dump, '->dump() does not dump objects when disabled');
  167. }
  168. /**
  169. * @expectedException \Symfony\Component\Yaml\Exception\DumpException
  170. */
  171. public function testObjectSupportDisabledWithExceptions()
  172. {
  173. $this->dumper->dump(['foo' => new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE);
  174. }
  175. /**
  176. * @dataProvider getEscapeSequences
  177. */
  178. public function testEscapedEscapeSequencesInQuotedScalar($input, $expected)
  179. {
  180. $this->assertEquals($expected, $this->dumper->dump($input));
  181. }
  182. public function getEscapeSequences()
  183. {
  184. return [
  185. 'empty string' => ['', "''"],
  186. 'null' => ["\x0", '"\\0"'],
  187. 'bell' => ["\x7", '"\\a"'],
  188. 'backspace' => ["\x8", '"\\b"'],
  189. 'horizontal-tab' => ["\t", '"\\t"'],
  190. 'line-feed' => ["\n", '"\\n"'],
  191. 'vertical-tab' => ["\v", '"\\v"'],
  192. 'form-feed' => ["\xC", '"\\f"'],
  193. 'carriage-return' => ["\r", '"\\r"'],
  194. 'escape' => ["\x1B", '"\\e"'],
  195. 'space' => [' ', "' '"],
  196. 'double-quote' => ['"', "'\"'"],
  197. 'slash' => ['/', '/'],
  198. 'backslash' => ['\\', '\\'],
  199. 'next-line' => ["\xC2\x85", '"\\N"'],
  200. 'non-breaking-space' => ["\xc2\xa0", '"\\_"'],
  201. 'line-separator' => ["\xE2\x80\xA8", '"\\L"'],
  202. 'paragraph-separator' => ["\xE2\x80\xA9", '"\\P"'],
  203. 'colon' => [':', "':'"],
  204. ];
  205. }
  206. public function testBinaryDataIsDumpedBase64Encoded()
  207. {
  208. $binaryData = file_get_contents(__DIR__.'/Fixtures/arrow.gif');
  209. $expected = '{ data: !!binary '.base64_encode($binaryData).' }';
  210. $this->assertSame($expected, $this->dumper->dump(['data' => $binaryData]));
  211. }
  212. public function testNonUtf8DataIsDumpedBase64Encoded()
  213. {
  214. // "für" (ISO-8859-1 encoded)
  215. $this->assertSame('!!binary ZsM/cg==', $this->dumper->dump("f\xc3\x3fr"));
  216. }
  217. /**
  218. * @dataProvider objectAsMapProvider
  219. */
  220. public function testDumpObjectAsMap($object, $expected)
  221. {
  222. $yaml = $this->dumper->dump($object, 0, 0, Yaml::DUMP_OBJECT_AS_MAP);
  223. $this->assertEquals($expected, Yaml::parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP));
  224. }
  225. public function objectAsMapProvider()
  226. {
  227. $tests = [];
  228. $bar = new \stdClass();
  229. $bar->class = 'classBar';
  230. $bar->args = ['bar'];
  231. $zar = new \stdClass();
  232. $foo = new \stdClass();
  233. $foo->bar = $bar;
  234. $foo->zar = $zar;
  235. $object = new \stdClass();
  236. $object->foo = $foo;
  237. $tests['stdClass'] = [$object, $object];
  238. $arrayObject = new \ArrayObject();
  239. $arrayObject['foo'] = 'bar';
  240. $arrayObject['baz'] = 'foobar';
  241. $parsedArrayObject = new \stdClass();
  242. $parsedArrayObject->foo = 'bar';
  243. $parsedArrayObject->baz = 'foobar';
  244. $tests['ArrayObject'] = [$arrayObject, $parsedArrayObject];
  245. $a = new A();
  246. $tests['arbitrary-object'] = [$a, null];
  247. return $tests;
  248. }
  249. public function testDumpingArrayObjectInstancesRespectsInlineLevel()
  250. {
  251. $deep = new \ArrayObject(['deep1' => 'd', 'deep2' => 'e']);
  252. $inner = new \ArrayObject(['inner1' => 'b', 'inner2' => 'c', 'inner3' => $deep]);
  253. $outer = new \ArrayObject(['outer1' => 'a', 'outer2' => $inner]);
  254. $yaml = $this->dumper->dump($outer, 2, 0, Yaml::DUMP_OBJECT_AS_MAP);
  255. $expected = <<<YAML
  256. outer1: a
  257. outer2:
  258. inner1: b
  259. inner2: c
  260. inner3: { deep1: d, deep2: e }
  261. YAML;
  262. $this->assertSame($expected, $yaml);
  263. }
  264. public function testDumpingArrayObjectInstancesWithNumericKeysInlined()
  265. {
  266. $deep = new \ArrayObject(['d', 'e']);
  267. $inner = new \ArrayObject(['b', 'c', $deep]);
  268. $outer = new \ArrayObject(['a', $inner]);
  269. $yaml = $this->dumper->dump($outer, 0, 0, Yaml::DUMP_OBJECT_AS_MAP);
  270. $expected = <<<YAML
  271. { 0: a, 1: { 0: b, 1: c, 2: { 0: d, 1: e } } }
  272. YAML;
  273. $this->assertSame($expected, $yaml);
  274. }
  275. public function testDumpingArrayObjectInstancesWithNumericKeysRespectsInlineLevel()
  276. {
  277. $deep = new \ArrayObject(['d', 'e']);
  278. $inner = new \ArrayObject(['b', 'c', $deep]);
  279. $outer = new \ArrayObject(['a', $inner]);
  280. $yaml = $this->dumper->dump($outer, 2, 0, Yaml::DUMP_OBJECT_AS_MAP);
  281. $expected = <<<YAML
  282. 0: a
  283. 1:
  284. 0: b
  285. 1: c
  286. 2: { 0: d, 1: e }
  287. YAML;
  288. $this->assertEquals($expected, $yaml);
  289. }
  290. public function testDumpEmptyArrayObjectInstanceAsMap()
  291. {
  292. $this->assertSame('{ }', $this->dumper->dump(new \ArrayObject(), 2, 0, Yaml::DUMP_OBJECT_AS_MAP));
  293. }
  294. public function testDumpEmptyStdClassInstanceAsMap()
  295. {
  296. $this->assertSame('{ }', $this->dumper->dump(new \stdClass(), 2, 0, Yaml::DUMP_OBJECT_AS_MAP));
  297. }
  298. public function testDumpingStdClassInstancesRespectsInlineLevel()
  299. {
  300. $deep = new \stdClass();
  301. $deep->deep1 = 'd';
  302. $deep->deep2 = 'e';
  303. $inner = new \stdClass();
  304. $inner->inner1 = 'b';
  305. $inner->inner2 = 'c';
  306. $inner->inner3 = $deep;
  307. $outer = new \stdClass();
  308. $outer->outer1 = 'a';
  309. $outer->outer2 = $inner;
  310. $yaml = $this->dumper->dump($outer, 2, 0, Yaml::DUMP_OBJECT_AS_MAP);
  311. $expected = <<<YAML
  312. outer1: a
  313. outer2:
  314. inner1: b
  315. inner2: c
  316. inner3: { deep1: d, deep2: e }
  317. YAML;
  318. $this->assertSame($expected, $yaml);
  319. }
  320. public function testDumpMultiLineStringAsScalarBlock()
  321. {
  322. $data = [
  323. 'data' => [
  324. 'single_line' => 'foo bar baz',
  325. 'multi_line' => "foo\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz",
  326. 'multi_line_with_carriage_return' => "foo\nbar\r\nbaz",
  327. 'nested_inlined_multi_line_string' => [
  328. 'inlined_multi_line' => "foo\nbar\r\nempty line:\n\nbaz",
  329. ],
  330. ],
  331. ];
  332. $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
  333. }
  334. public function testDumpMultiLineStringAsScalarBlockWhenFirstLineHasLeadingSpace()
  335. {
  336. $data = [
  337. 'data' => [
  338. 'multi_line' => " the first line has leading spaces\nThe second line does not.",
  339. ],
  340. ];
  341. $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
  342. }
  343. public function testCarriageReturnIsMaintainedWhenDumpingAsMultiLineLiteralBlock()
  344. {
  345. $this->assertSame("- \"a\\r\\nb\\nc\"\n", $this->dumper->dump(["a\r\nb\nc"], 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
  346. }
  347. /**
  348. * @expectedException \InvalidArgumentException
  349. * @expectedExceptionMessage The indentation must be greater than zero
  350. */
  351. public function testZeroIndentationThrowsException()
  352. {
  353. new Dumper(0);
  354. }
  355. /**
  356. * @expectedException \InvalidArgumentException
  357. * @expectedExceptionMessage The indentation must be greater than zero
  358. */
  359. public function testNegativeIndentationThrowsException()
  360. {
  361. new Dumper(-4);
  362. }
  363. }
  364. class A
  365. {
  366. public $a = 'foo';
  367. }