1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Yaml\Tests; 13 14use PHPUnit\Framework\TestCase; 15use Symfony\Component\Yaml\Exception\ParseException; 16use Symfony\Component\Yaml\Parser; 17use Symfony\Component\Yaml\Tag\TaggedValue; 18use Symfony\Component\Yaml\Yaml; 19 20class ParserTest extends TestCase 21{ 22 /** @var Parser */ 23 protected $parser; 24 25 protected function setUp() 26 { 27 $this->parser = new Parser(); 28 } 29 30 protected function tearDown() 31 { 32 $this->parser = null; 33 34 chmod(__DIR__.'/Fixtures/not_readable.yml', 0644); 35 } 36 37 /** 38 * @dataProvider getDataFormSpecifications 39 */ 40 public function testSpecifications($expected, $yaml, $comment) 41 { 42 $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment); 43 } 44 45 public function getDataFormSpecifications() 46 { 47 return $this->loadTestsFromFixtureFiles('index.yml'); 48 } 49 50 public function getNonStringMappingKeysData() 51 { 52 return $this->loadTestsFromFixtureFiles('nonStringKeys.yml'); 53 } 54 55 public function testTabsInYaml() 56 { 57 // test tabs in YAML 58 $yamls = [ 59 "foo:\n bar", 60 "foo:\n bar", 61 "foo:\n bar", 62 "foo:\n bar", 63 ]; 64 65 foreach ($yamls as $yaml) { 66 try { 67 $content = $this->parser->parse($yaml); 68 69 $this->fail('YAML files must not contain tabs'); 70 } catch (\Exception $e) { 71 $this->assertInstanceOf('\Exception', $e, 'YAML files must not contain tabs'); 72 $this->assertEquals('A YAML file cannot contain tabs as indentation at line 2 (near "'.strpbrk($yaml, "\t").'").', $e->getMessage(), 'YAML files must not contain tabs'); 73 } 74 } 75 } 76 77 public function testEndOfTheDocumentMarker() 78 { 79 $yaml = <<<'EOF' 80--- %YAML:1.0 81foo 82... 83EOF; 84 85 $this->assertEquals('foo', $this->parser->parse($yaml)); 86 } 87 88 public function getBlockChompingTests() 89 { 90 $tests = []; 91 92 $yaml = <<<'EOF' 93foo: |- 94 one 95 two 96bar: |- 97 one 98 two 99 100EOF; 101 $expected = [ 102 'foo' => "one\ntwo", 103 'bar' => "one\ntwo", 104 ]; 105 $tests['Literal block chomping strip with single trailing newline'] = [$expected, $yaml]; 106 107 $yaml = <<<'EOF' 108foo: |- 109 one 110 two 111 112bar: |- 113 one 114 two 115 116 117EOF; 118 $expected = [ 119 'foo' => "one\ntwo", 120 'bar' => "one\ntwo", 121 ]; 122 $tests['Literal block chomping strip with multiple trailing newlines'] = [$expected, $yaml]; 123 124 $yaml = <<<'EOF' 125{} 126 127 128EOF; 129 $expected = []; 130 $tests['Literal block chomping strip with multiple trailing newlines after a 1-liner'] = [$expected, $yaml]; 131 132 $yaml = <<<'EOF' 133foo: |- 134 one 135 two 136bar: |- 137 one 138 two 139EOF; 140 $expected = [ 141 'foo' => "one\ntwo", 142 'bar' => "one\ntwo", 143 ]; 144 $tests['Literal block chomping strip without trailing newline'] = [$expected, $yaml]; 145 146 $yaml = <<<'EOF' 147foo: | 148 one 149 two 150bar: | 151 one 152 two 153 154EOF; 155 $expected = [ 156 'foo' => "one\ntwo\n", 157 'bar' => "one\ntwo\n", 158 ]; 159 $tests['Literal block chomping clip with single trailing newline'] = [$expected, $yaml]; 160 161 $yaml = <<<'EOF' 162foo: | 163 one 164 two 165 166bar: | 167 one 168 two 169 170 171EOF; 172 $expected = [ 173 'foo' => "one\ntwo\n", 174 'bar' => "one\ntwo\n", 175 ]; 176 $tests['Literal block chomping clip with multiple trailing newlines'] = [$expected, $yaml]; 177 178 $yaml = <<<'EOF' 179foo: 180- bar: | 181 one 182 183 two 184EOF; 185 $expected = [ 186 'foo' => [ 187 [ 188 'bar' => "one\n\ntwo", 189 ], 190 ], 191 ]; 192 $tests['Literal block chomping clip with embedded blank line inside unindented collection'] = [$expected, $yaml]; 193 194 $yaml = <<<'EOF' 195foo: | 196 one 197 two 198bar: | 199 one 200 two 201EOF; 202 $expected = [ 203 'foo' => "one\ntwo\n", 204 'bar' => "one\ntwo", 205 ]; 206 $tests['Literal block chomping clip without trailing newline'] = [$expected, $yaml]; 207 208 $yaml = <<<'EOF' 209foo: |+ 210 one 211 two 212bar: |+ 213 one 214 two 215 216EOF; 217 $expected = [ 218 'foo' => "one\ntwo\n", 219 'bar' => "one\ntwo\n", 220 ]; 221 $tests['Literal block chomping keep with single trailing newline'] = [$expected, $yaml]; 222 223 $yaml = <<<'EOF' 224foo: |+ 225 one 226 two 227 228bar: |+ 229 one 230 two 231 232 233EOF; 234 $expected = [ 235 'foo' => "one\ntwo\n\n", 236 'bar' => "one\ntwo\n\n", 237 ]; 238 $tests['Literal block chomping keep with multiple trailing newlines'] = [$expected, $yaml]; 239 240 $yaml = <<<'EOF' 241foo: |+ 242 one 243 two 244bar: |+ 245 one 246 two 247EOF; 248 $expected = [ 249 'foo' => "one\ntwo\n", 250 'bar' => "one\ntwo", 251 ]; 252 $tests['Literal block chomping keep without trailing newline'] = [$expected, $yaml]; 253 254 $yaml = <<<'EOF' 255foo: >- 256 one 257 two 258bar: >- 259 one 260 two 261 262EOF; 263 $expected = [ 264 'foo' => 'one two', 265 'bar' => 'one two', 266 ]; 267 $tests['Folded block chomping strip with single trailing newline'] = [$expected, $yaml]; 268 269 $yaml = <<<'EOF' 270foo: >- 271 one 272 two 273 274bar: >- 275 one 276 two 277 278 279EOF; 280 $expected = [ 281 'foo' => 'one two', 282 'bar' => 'one two', 283 ]; 284 $tests['Folded block chomping strip with multiple trailing newlines'] = [$expected, $yaml]; 285 286 $yaml = <<<'EOF' 287foo: >- 288 one 289 two 290bar: >- 291 one 292 two 293EOF; 294 $expected = [ 295 'foo' => 'one two', 296 'bar' => 'one two', 297 ]; 298 $tests['Folded block chomping strip without trailing newline'] = [$expected, $yaml]; 299 300 $yaml = <<<'EOF' 301foo: > 302 one 303 two 304bar: > 305 one 306 two 307 308EOF; 309 $expected = [ 310 'foo' => "one two\n", 311 'bar' => "one two\n", 312 ]; 313 $tests['Folded block chomping clip with single trailing newline'] = [$expected, $yaml]; 314 315 $yaml = <<<'EOF' 316foo: > 317 one 318 two 319 320bar: > 321 one 322 two 323 324 325EOF; 326 $expected = [ 327 'foo' => "one two\n", 328 'bar' => "one two\n", 329 ]; 330 $tests['Folded block chomping clip with multiple trailing newlines'] = [$expected, $yaml]; 331 332 $yaml = <<<'EOF' 333foo: > 334 one 335 two 336bar: > 337 one 338 two 339EOF; 340 $expected = [ 341 'foo' => "one two\n", 342 'bar' => 'one two', 343 ]; 344 $tests['Folded block chomping clip without trailing newline'] = [$expected, $yaml]; 345 346 $yaml = <<<'EOF' 347foo: >+ 348 one 349 two 350bar: >+ 351 one 352 two 353 354EOF; 355 $expected = [ 356 'foo' => "one two\n", 357 'bar' => "one two\n", 358 ]; 359 $tests['Folded block chomping keep with single trailing newline'] = [$expected, $yaml]; 360 361 $yaml = <<<'EOF' 362foo: >+ 363 one 364 two 365 366bar: >+ 367 one 368 two 369 370 371EOF; 372 $expected = [ 373 'foo' => "one two\n\n", 374 'bar' => "one two\n\n", 375 ]; 376 $tests['Folded block chomping keep with multiple trailing newlines'] = [$expected, $yaml]; 377 378 $yaml = <<<'EOF' 379foo: >+ 380 one 381 two 382bar: >+ 383 one 384 two 385EOF; 386 $expected = [ 387 'foo' => "one two\n", 388 'bar' => 'one two', 389 ]; 390 $tests['Folded block chomping keep without trailing newline'] = [$expected, $yaml]; 391 392 return $tests; 393 } 394 395 /** 396 * @dataProvider getBlockChompingTests 397 */ 398 public function testBlockChomping($expected, $yaml) 399 { 400 $this->assertSame($expected, $this->parser->parse($yaml)); 401 } 402 403 /** 404 * Regression test for issue #7989. 405 * 406 * @see https://github.com/symfony/symfony/issues/7989 407 */ 408 public function testBlockLiteralWithLeadingNewlines() 409 { 410 $yaml = <<<'EOF' 411foo: |- 412 413 414 bar 415 416EOF; 417 $expected = [ 418 'foo' => "\n\nbar", 419 ]; 420 421 $this->assertSame($expected, $this->parser->parse($yaml)); 422 } 423 424 public function testObjectSupportEnabled() 425 { 426 $input = <<<'EOF' 427foo: !php/object O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";} 428bar: 1 429EOF; 430 $this->assertEquals(['foo' => new B(), 'bar' => 1], $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects'); 431 } 432 433 public function testObjectSupportDisabledButNoExceptions() 434 { 435 $input = <<<'EOF' 436foo: !php/object O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} 437bar: 1 438EOF; 439 $this->assertEquals(['foo' => null, 'bar' => 1], $this->parser->parse($input), '->parse() does not parse objects'); 440 } 441 442 /** 443 * @dataProvider getObjectForMapTests 444 */ 445 public function testObjectForMap($yaml, $expected) 446 { 447 $flags = Yaml::PARSE_OBJECT_FOR_MAP; 448 449 $this->assertEquals($expected, $this->parser->parse($yaml, $flags)); 450 } 451 452 public function getObjectForMapTests() 453 { 454 $tests = []; 455 456 $yaml = <<<'EOF' 457foo: 458 fiz: [cat] 459EOF; 460 $expected = new \stdClass(); 461 $expected->foo = new \stdClass(); 462 $expected->foo->fiz = ['cat']; 463 $tests['mapping'] = [$yaml, $expected]; 464 465 $yaml = '{ "foo": "bar", "fiz": "cat" }'; 466 $expected = new \stdClass(); 467 $expected->foo = 'bar'; 468 $expected->fiz = 'cat'; 469 $tests['inline-mapping'] = [$yaml, $expected]; 470 471 $yaml = "foo: bar\nbaz: foobar"; 472 $expected = new \stdClass(); 473 $expected->foo = 'bar'; 474 $expected->baz = 'foobar'; 475 $tests['object-for-map-is-applied-after-parsing'] = [$yaml, $expected]; 476 477 $yaml = <<<'EOT' 478array: 479 - key: one 480 - key: two 481EOT; 482 $expected = new \stdClass(); 483 $expected->array = []; 484 $expected->array[0] = new \stdClass(); 485 $expected->array[0]->key = 'one'; 486 $expected->array[1] = new \stdClass(); 487 $expected->array[1]->key = 'two'; 488 $tests['nest-map-and-sequence'] = [$yaml, $expected]; 489 490 $yaml = <<<'YAML' 491map: 492 1: one 493 2: two 494YAML; 495 $expected = new \stdClass(); 496 $expected->map = new \stdClass(); 497 $expected->map->{1} = 'one'; 498 $expected->map->{2} = 'two'; 499 $tests['numeric-keys'] = [$yaml, $expected]; 500 501 $yaml = <<<'YAML' 502map: 503 '0': one 504 '1': two 505YAML; 506 $expected = new \stdClass(); 507 $expected->map = new \stdClass(); 508 $expected->map->{0} = 'one'; 509 $expected->map->{1} = 'two'; 510 $tests['zero-indexed-numeric-keys'] = [$yaml, $expected]; 511 512 return $tests; 513 } 514 515 /** 516 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 517 */ 518 public function testObjectsSupportDisabledWithExceptions() 519 { 520 $yaml = <<<'EOF' 521foo: !php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} 522bar: 1 523EOF; 524 525 $this->parser->parse($yaml, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); 526 } 527 528 public function testCanParseContentWithTrailingSpaces() 529 { 530 $yaml = "items: \n foo: bar"; 531 532 $expected = [ 533 'items' => ['foo' => 'bar'], 534 ]; 535 536 $this->assertSame($expected, $this->parser->parse($yaml)); 537 } 538 539 /** 540 * @requires extension iconv 541 */ 542 public function testNonUtf8Exception() 543 { 544 $yamls = [ 545 iconv('UTF-8', 'ISO-8859-1', "foo: 'äöüß'"), 546 iconv('UTF-8', 'ISO-8859-15', "euro: '€'"), 547 iconv('UTF-8', 'CP1252', "cp1252: '©ÉÇáñ'"), 548 ]; 549 550 foreach ($yamls as $yaml) { 551 try { 552 $this->parser->parse($yaml); 553 554 $this->fail('charsets other than UTF-8 are rejected.'); 555 } catch (\Exception $e) { 556 $this->assertInstanceOf('Symfony\Component\Yaml\Exception\ParseException', $e, 'charsets other than UTF-8 are rejected.'); 557 } 558 } 559 } 560 561 /** 562 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 563 */ 564 public function testUnindentedCollectionException() 565 { 566 $yaml = <<<'EOF' 567 568collection: 569-item1 570-item2 571-item3 572 573EOF; 574 575 $this->parser->parse($yaml); 576 } 577 578 /** 579 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 580 */ 581 public function testShortcutKeyUnindentedCollectionException() 582 { 583 $yaml = <<<'EOF' 584 585collection: 586- key: foo 587 foo: bar 588 589EOF; 590 591 $this->parser->parse($yaml); 592 } 593 594 /** 595 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 596 * @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/ 597 */ 598 public function testMultipleDocumentsNotSupportedException() 599 { 600 Yaml::parse(<<<'EOL' 601# Ranking of 1998 home runs 602--- 603- Mark McGwire 604- Sammy Sosa 605- Ken Griffey 606 607# Team ranking 608--- 609- Chicago Cubs 610- St Louis Cardinals 611EOL 612 ); 613 } 614 615 /** 616 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 617 */ 618 public function testSequenceInAMapping() 619 { 620 Yaml::parse(<<<'EOF' 621yaml: 622 hash: me 623 - array stuff 624EOF 625 ); 626 } 627 628 public function testSequenceInMappingStartedBySingleDashLine() 629 { 630 $yaml = <<<'EOT' 631a: 632- 633 b: 634 - 635 bar: baz 636- foo 637d: e 638EOT; 639 $expected = [ 640 'a' => [ 641 [ 642 'b' => [ 643 [ 644 'bar' => 'baz', 645 ], 646 ], 647 ], 648 'foo', 649 ], 650 'd' => 'e', 651 ]; 652 653 $this->assertSame($expected, $this->parser->parse($yaml)); 654 } 655 656 public function testSequenceFollowedByCommentEmbeddedInMapping() 657 { 658 $yaml = <<<'EOT' 659a: 660 b: 661 - c 662# comment 663 d: e 664EOT; 665 $expected = [ 666 'a' => [ 667 'b' => ['c'], 668 'd' => 'e', 669 ], 670 ]; 671 672 $this->assertSame($expected, $this->parser->parse($yaml)); 673 } 674 675 public function testNonStringFollowedByCommentEmbeddedInMapping() 676 { 677 $yaml = <<<'EOT' 678a: 679 b: 680 {} 681# comment 682 d: 683 1.1 684# another comment 685EOT; 686 $expected = [ 687 'a' => [ 688 'b' => [], 689 'd' => 1.1, 690 ], 691 ]; 692 693 $this->assertSame($expected, $this->parser->parse($yaml)); 694 } 695 696 public function getParseExceptionNotAffectedMultiLineStringLastResortParsing() 697 { 698 $tests = []; 699 700 $yaml = <<<'EOT' 701a 702 b: 703EOT; 704 $tests['parse error on first line'] = [$yaml]; 705 706 $yaml = <<<'EOT' 707a 708 709b 710 c: 711EOT; 712 $tests['parse error due to inconsistent indentation'] = [$yaml]; 713 714 $yaml = <<<'EOT' 715 & * ! | > ' " % @ ` #, { asd a;sdasd }-@^qw3 716EOT; 717 $tests['symfony/symfony/issues/22967#issuecomment-322067742'] = [$yaml]; 718 719 return $tests; 720 } 721 722 /** 723 * @dataProvider getParseExceptionNotAffectedMultiLineStringLastResortParsing 724 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 725 */ 726 public function testParseExceptionNotAffectedByMultiLineStringLastResortParsing($yaml) 727 { 728 $this->parser->parse($yaml); 729 } 730 731 public function testMultiLineStringLastResortParsing() 732 { 733 $yaml = <<<'EOT' 734test: 735 You can have things that don't look like strings here 736 true 737 yes you can 738EOT; 739 $expected = [ 740 'test' => 'You can have things that don\'t look like strings here true yes you can', 741 ]; 742 743 $this->assertSame($expected, $this->parser->parse($yaml)); 744 745 $yaml = <<<'EOT' 746a: 747 b 748 c 749EOT; 750 $expected = [ 751 'a' => 'b c', 752 ]; 753 754 $this->assertSame($expected, $this->parser->parse($yaml)); 755 } 756 757 /** 758 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 759 */ 760 public function testMappingInASequence() 761 { 762 Yaml::parse(<<<'EOF' 763yaml: 764 - array stuff 765 hash: me 766EOF 767 ); 768 } 769 770 /** 771 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 772 * @expectedExceptionMessage missing colon 773 */ 774 public function testScalarInSequence() 775 { 776 Yaml::parse(<<<'EOF' 777foo: 778 - bar 779"missing colon" 780 foo: bar 781EOF 782 ); 783 } 784 785 /** 786 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 787 * @expectedExceptionMessage Duplicate key "child" detected 788 * 789 * > It is an error for two equal keys to appear in the same mapping node. 790 * > In such a case the YAML processor may continue, ignoring the second 791 * > "key: value" pair and issuing an appropriate warning. This strategy 792 * > preserves a consistent information model for one-pass and random access 793 * > applications. 794 * 795 * @see http://yaml.org/spec/1.2/spec.html#id2759572 796 * @see http://yaml.org/spec/1.1/#id932806 797 */ 798 public function testMappingDuplicateKeyBlock() 799 { 800 $input = <<<'EOD' 801parent: 802 child: first 803 child: duplicate 804parent: 805 child: duplicate 806 child: duplicate 807EOD; 808 $expected = [ 809 'parent' => [ 810 'child' => 'first', 811 ], 812 ]; 813 $this->assertSame($expected, Yaml::parse($input)); 814 } 815 816 /** 817 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 818 * @expectedExceptionMessage Duplicate key "child" detected 819 */ 820 public function testMappingDuplicateKeyFlow() 821 { 822 $input = <<<'EOD' 823parent: { child: first, child: duplicate } 824parent: { child: duplicate, child: duplicate } 825EOD; 826 $expected = [ 827 'parent' => [ 828 'child' => 'first', 829 ], 830 ]; 831 $this->assertSame($expected, Yaml::parse($input)); 832 } 833 834 /** 835 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 836 * @dataProvider getParseExceptionOnDuplicateData 837 */ 838 public function testParseExceptionOnDuplicate($input, $duplicateKey, $lineNumber) 839 { 840 $this->expectExceptionMessage(sprintf('Duplicate key "%s" detected at line %d', $duplicateKey, $lineNumber)); 841 842 Yaml::parse($input); 843 } 844 845 public function getParseExceptionOnDuplicateData() 846 { 847 $tests = []; 848 849 $yaml = <<<EOD 850parent: { child: first, child: duplicate } 851EOD; 852 $tests[] = [$yaml, 'child', 1]; 853 854 $yaml = <<<EOD 855parent: 856 child: first, 857 child: duplicate 858EOD; 859 $tests[] = [$yaml, 'child', 3]; 860 861 $yaml = <<<EOD 862parent: { child: foo } 863parent: { child: bar } 864EOD; 865 $tests[] = [$yaml, 'parent', 2]; 866 867 $yaml = <<<EOD 868parent: { child_mapping: { value: bar}, child_mapping: { value: bar} } 869EOD; 870 $tests[] = [$yaml, 'child_mapping', 1]; 871 872 $yaml = <<<EOD 873parent: 874 child_mapping: 875 value: bar 876 child_mapping: 877 value: bar 878EOD; 879 $tests[] = [$yaml, 'child_mapping', 4]; 880 881 $yaml = <<<EOD 882parent: { child_sequence: ['key1', 'key2', 'key3'], child_sequence: ['key1', 'key2', 'key3'] } 883EOD; 884 $tests[] = [$yaml, 'child_sequence', 1]; 885 886 $yaml = <<<EOD 887parent: 888 child_sequence: 889 - key1 890 - key2 891 - key3 892 child_sequence: 893 - key1 894 - key2 895 - key3 896EOD; 897 $tests[] = [$yaml, 'child_sequence', 6]; 898 899 return $tests; 900 } 901 902 public function testEmptyValue() 903 { 904 $input = <<<'EOF' 905hash: 906EOF; 907 908 $this->assertEquals(['hash' => null], Yaml::parse($input)); 909 } 910 911 public function testCommentAtTheRootIndent() 912 { 913 $this->assertEquals([ 914 'services' => [ 915 'app.foo_service' => [ 916 'class' => 'Foo', 917 ], 918 'app/bar_service' => [ 919 'class' => 'Bar', 920 ], 921 ], 922 ], Yaml::parse(<<<'EOF' 923# comment 1 924services: 925# comment 2 926 # comment 3 927 app.foo_service: 928 class: Foo 929# comment 4 930 # comment 5 931 app/bar_service: 932 class: Bar 933EOF 934 )); 935 } 936 937 public function testStringBlockWithComments() 938 { 939 $this->assertEquals(['content' => <<<'EOT' 940# comment 1 941header 942 943 # comment 2 944 <body> 945 <h1>title</h1> 946 </body> 947 948footer # comment3 949EOT 950 ], Yaml::parse(<<<'EOF' 951content: | 952 # comment 1 953 header 954 955 # comment 2 956 <body> 957 <h1>title</h1> 958 </body> 959 960 footer # comment3 961EOF 962 )); 963 } 964 965 public function testFoldedStringBlockWithComments() 966 { 967 $this->assertEquals([['content' => <<<'EOT' 968# comment 1 969header 970 971 # comment 2 972 <body> 973 <h1>title</h1> 974 </body> 975 976footer # comment3 977EOT 978 ]], Yaml::parse(<<<'EOF' 979- 980 content: | 981 # comment 1 982 header 983 984 # comment 2 985 <body> 986 <h1>title</h1> 987 </body> 988 989 footer # comment3 990EOF 991 )); 992 } 993 994 public function testNestedFoldedStringBlockWithComments() 995 { 996 $this->assertEquals([[ 997 'title' => 'some title', 998 'content' => <<<'EOT' 999# comment 1 1000header 1001 1002 # comment 2 1003 <body> 1004 <h1>title</h1> 1005 </body> 1006 1007footer # comment3 1008EOT 1009 ]], Yaml::parse(<<<'EOF' 1010- 1011 title: some title 1012 content: | 1013 # comment 1 1014 header 1015 1016 # comment 2 1017 <body> 1018 <h1>title</h1> 1019 </body> 1020 1021 footer # comment3 1022EOF 1023 )); 1024 } 1025 1026 public function testReferenceResolvingInInlineStrings() 1027 { 1028 $this->assertEquals([ 1029 'var' => 'var-value', 1030 'scalar' => 'var-value', 1031 'list' => ['var-value'], 1032 'list_in_list' => [['var-value']], 1033 'map_in_list' => [['key' => 'var-value']], 1034 'embedded_mapping' => [['key' => 'var-value']], 1035 'map' => ['key' => 'var-value'], 1036 'list_in_map' => ['key' => ['var-value']], 1037 'map_in_map' => ['foo' => ['bar' => 'var-value']], 1038 ], Yaml::parse(<<<'EOF' 1039var: &var var-value 1040scalar: *var 1041list: [ *var ] 1042list_in_list: [[ *var ]] 1043map_in_list: [ { key: *var } ] 1044embedded_mapping: [ key: *var ] 1045map: { key: *var } 1046list_in_map: { key: [*var] } 1047map_in_map: { foo: { bar: *var } } 1048EOF 1049 )); 1050 } 1051 1052 public function testYamlDirective() 1053 { 1054 $yaml = <<<'EOF' 1055%YAML 1.2 1056--- 1057foo: 1 1058bar: 2 1059EOF; 1060 $this->assertEquals(['foo' => 1, 'bar' => 2], $this->parser->parse($yaml)); 1061 } 1062 1063 /** 1064 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1065 * @expectedExceptionMessage Numeric keys are not supported. Quote your evaluable mapping keys instead 1066 */ 1067 public function testFloatKeys() 1068 { 1069 $yaml = <<<'EOF' 1070foo: 1071 1.2: "bar" 1072 1.3: "baz" 1073EOF; 1074 1075 $this->parser->parse($yaml); 1076 } 1077 1078 /** 1079 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1080 * @expectedExceptionMessage Non-string keys are not supported. Quote your evaluable mapping keys instead 1081 */ 1082 public function testBooleanKeys() 1083 { 1084 $yaml = <<<'EOF' 1085true: foo 1086false: bar 1087EOF; 1088 1089 $this->parser->parse($yaml); 1090 } 1091 1092 public function testExplicitStringCasting() 1093 { 1094 $yaml = <<<'EOF' 1095'1.2': "bar" 1096!!str 1.3: "baz" 1097 1098'true': foo 1099!!str false: bar 1100 1101!!str null: 'null' 1102'~': 'null' 1103EOF; 1104 1105 $expected = [ 1106 '1.2' => 'bar', 1107 '1.3' => 'baz', 1108 'true' => 'foo', 1109 'false' => 'bar', 1110 'null' => 'null', 1111 '~' => 'null', 1112 ]; 1113 1114 $this->assertEquals($expected, $this->parser->parse($yaml)); 1115 } 1116 1117 /** 1118 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1119 * @expectedExceptionMessage A colon cannot be used in an unquoted mapping value 1120 */ 1121 public function testColonInMappingValueException() 1122 { 1123 $yaml = <<<'EOF' 1124foo: bar: baz 1125EOF; 1126 1127 $this->parser->parse($yaml); 1128 } 1129 1130 public function testColonInMappingValueExceptionNotTriggeredByColonInComment() 1131 { 1132 $yaml = <<<'EOT' 1133foo: 1134 bar: foobar # Note: a comment after a colon 1135EOT; 1136 1137 $this->assertSame(['foo' => ['bar' => 'foobar']], $this->parser->parse($yaml)); 1138 } 1139 1140 /** 1141 * @dataProvider getCommentLikeStringInScalarBlockData 1142 */ 1143 public function testCommentLikeStringsAreNotStrippedInBlockScalars($yaml, $expectedParserResult) 1144 { 1145 $this->assertSame($expectedParserResult, $this->parser->parse($yaml)); 1146 } 1147 1148 public function getCommentLikeStringInScalarBlockData() 1149 { 1150 $tests = []; 1151 1152 $yaml = <<<'EOT' 1153pages: 1154 - 1155 title: some title 1156 content: | 1157 # comment 1 1158 header 1159 1160 # comment 2 1161 <body> 1162 <h1>title</h1> 1163 </body> 1164 1165 footer # comment3 1166EOT; 1167 $expected = [ 1168 'pages' => [ 1169 [ 1170 'title' => 'some title', 1171 'content' => <<<'EOT' 1172# comment 1 1173header 1174 1175 # comment 2 1176 <body> 1177 <h1>title</h1> 1178 </body> 1179 1180footer # comment3 1181EOT 1182 , 1183 ], 1184 ], 1185 ]; 1186 $tests[] = [$yaml, $expected]; 1187 1188 $yaml = <<<'EOT' 1189test: | 1190 foo 1191 # bar 1192 baz 1193collection: 1194 - one: | 1195 foo 1196 # bar 1197 baz 1198 - two: | 1199 foo 1200 # bar 1201 baz 1202EOT; 1203 $expected = [ 1204 'test' => <<<'EOT' 1205foo 1206# bar 1207baz 1208 1209EOT 1210 , 1211 'collection' => [ 1212 [ 1213 'one' => <<<'EOT' 1214foo 1215# bar 1216baz 1217 1218EOT 1219 , 1220 ], 1221 [ 1222 'two' => <<<'EOT' 1223foo 1224# bar 1225baz 1226EOT 1227 , 1228 ], 1229 ], 1230 ]; 1231 $tests[] = [$yaml, $expected]; 1232 1233 $yaml = <<<'EOT' 1234foo: 1235 bar: 1236 scalar-block: > 1237 line1 1238 line2> 1239 baz: 1240# comment 1241 foobar: ~ 1242EOT; 1243 $expected = [ 1244 'foo' => [ 1245 'bar' => [ 1246 'scalar-block' => "line1 line2>\n", 1247 ], 1248 'baz' => [ 1249 'foobar' => null, 1250 ], 1251 ], 1252 ]; 1253 $tests[] = [$yaml, $expected]; 1254 1255 $yaml = <<<'EOT' 1256a: 1257 b: hello 1258# c: | 1259# first row 1260# second row 1261 d: hello 1262EOT; 1263 $expected = [ 1264 'a' => [ 1265 'b' => 'hello', 1266 'd' => 'hello', 1267 ], 1268 ]; 1269 $tests[] = [$yaml, $expected]; 1270 1271 return $tests; 1272 } 1273 1274 public function testBlankLinesAreParsedAsNewLinesInFoldedBlocks() 1275 { 1276 $yaml = <<<'EOT' 1277test: > 1278 <h2>A heading</h2> 1279 1280 <ul> 1281 <li>a list</li> 1282 <li>may be a good example</li> 1283 </ul> 1284EOT; 1285 1286 $this->assertSame( 1287 [ 1288 'test' => <<<'EOT' 1289<h2>A heading</h2> 1290<ul> <li>a list</li> <li>may be a good example</li> </ul> 1291EOT 1292 , 1293 ], 1294 $this->parser->parse($yaml) 1295 ); 1296 } 1297 1298 public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks() 1299 { 1300 $yaml = <<<'EOT' 1301test: > 1302 <h2>A heading</h2> 1303 1304 <ul> 1305 <li>a list</li> 1306 <li>may be a good example</li> 1307 </ul> 1308EOT; 1309 1310 $this->assertSame( 1311 [ 1312 'test' => <<<'EOT' 1313<h2>A heading</h2> 1314<ul> 1315 <li>a list</li> 1316 <li>may be a good example</li> 1317</ul> 1318EOT 1319 , 1320 ], 1321 $this->parser->parse($yaml) 1322 ); 1323 } 1324 1325 /** 1326 * @dataProvider getBinaryData 1327 */ 1328 public function testParseBinaryData($data) 1329 { 1330 $this->assertSame(['data' => 'Hello world'], $this->parser->parse($data)); 1331 } 1332 1333 public function getBinaryData() 1334 { 1335 return [ 1336 'enclosed with double quotes' => ['data: !!binary "SGVsbG8gd29ybGQ="'], 1337 'enclosed with single quotes' => ["data: !!binary 'SGVsbG8gd29ybGQ='"], 1338 'containing spaces' => ['data: !!binary "SGVs bG8gd 29ybGQ="'], 1339 'in block scalar' => [ 1340 <<<'EOT' 1341data: !!binary | 1342 SGVsbG8gd29ybGQ= 1343EOT 1344 ], 1345 'containing spaces in block scalar' => [ 1346 <<<'EOT' 1347data: !!binary | 1348 SGVs bG8gd 29ybGQ= 1349EOT 1350 ], 1351 ]; 1352 } 1353 1354 /** 1355 * @dataProvider getInvalidBinaryData 1356 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1357 */ 1358 public function testParseInvalidBinaryData($data, $expectedMessage) 1359 { 1360 if (method_exists($this, 'expectException')) { 1361 $this->expectExceptionMessageRegExp($expectedMessage); 1362 } else { 1363 $this->setExpectedExceptionRegExp(ParseException::class, $expectedMessage); 1364 } 1365 1366 $this->parser->parse($data); 1367 } 1368 1369 public function getInvalidBinaryData() 1370 { 1371 return [ 1372 'length not a multiple of four' => ['data: !!binary "SGVsbG8d29ybGQ="', '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/'], 1373 'invalid characters' => ['!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'], 1374 'too many equals characters' => ['data: !!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'], 1375 'misplaced equals character' => ['data: !!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'], 1376 'length not a multiple of four in block scalar' => [ 1377 <<<'EOT' 1378data: !!binary | 1379 SGVsbG8d29ybGQ= 1380EOT 1381 , 1382 '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/', 1383 ], 1384 'invalid characters in block scalar' => [ 1385 <<<'EOT' 1386data: !!binary | 1387 SGVsbG8#d29ybGQ= 1388EOT 1389 , 1390 '/The base64 encoded data \(.*\) contains invalid characters/', 1391 ], 1392 'too many equals characters in block scalar' => [ 1393 <<<'EOT' 1394data: !!binary | 1395 SGVsbG8gd29yb=== 1396EOT 1397 , 1398 '/The base64 encoded data \(.*\) contains invalid characters/', 1399 ], 1400 'misplaced equals character in block scalar' => [ 1401 <<<'EOT' 1402data: !!binary | 1403 SGVsbG8gd29ybG=Q 1404EOT 1405 , 1406 '/The base64 encoded data \(.*\) contains invalid characters/', 1407 ], 1408 ]; 1409 } 1410 1411 public function testParseDateAsMappingValue() 1412 { 1413 $yaml = <<<'EOT' 1414date: 2002-12-14 1415EOT; 1416 $expectedDate = new \DateTime(); 1417 $expectedDate->setTimeZone(new \DateTimeZone('UTC')); 1418 $expectedDate->setDate(2002, 12, 14); 1419 $expectedDate->setTime(0, 0, 0); 1420 1421 $this->assertEquals(['date' => $expectedDate], $this->parser->parse($yaml, Yaml::PARSE_DATETIME)); 1422 } 1423 1424 /** 1425 * @param $lineNumber 1426 * @param $yaml 1427 * @dataProvider parserThrowsExceptionWithCorrectLineNumberProvider 1428 */ 1429 public function testParserThrowsExceptionWithCorrectLineNumber($lineNumber, $yaml) 1430 { 1431 if (method_exists($this, 'expectException')) { 1432 $this->expectException('\Symfony\Component\Yaml\Exception\ParseException'); 1433 $this->expectExceptionMessage(sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber)); 1434 } else { 1435 $this->setExpectedException('\Symfony\Component\Yaml\Exception\ParseException', sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber)); 1436 } 1437 1438 $this->parser->parse($yaml); 1439 } 1440 1441 public function parserThrowsExceptionWithCorrectLineNumberProvider() 1442 { 1443 return [ 1444 [ 1445 4, 1446 <<<'YAML' 1447foo: 1448 - 1449 # bar 1450 bar: "123", 1451YAML 1452 ], 1453 [ 1454 5, 1455 <<<'YAML' 1456foo: 1457 - 1458 # bar 1459 # bar 1460 bar: "123", 1461YAML 1462 ], 1463 [ 1464 8, 1465 <<<'YAML' 1466foo: 1467 - 1468 # foobar 1469 baz: 123 1470bar: 1471 - 1472 # bar 1473 bar: "123", 1474YAML 1475 ], 1476 [ 1477 10, 1478 <<<'YAML' 1479foo: 1480 - 1481 # foobar 1482 # foobar 1483 baz: 123 1484bar: 1485 - 1486 # bar 1487 # bar 1488 bar: "123", 1489YAML 1490 ], 1491 ]; 1492 } 1493 1494 public function testParseMultiLineQuotedString() 1495 { 1496 $yaml = <<<EOT 1497foo: "bar 1498 baz 1499 foobar 1500foo" 1501bar: baz 1502EOT; 1503 1504 $this->assertSame(['foo' => 'bar baz foobar foo', 'bar' => 'baz'], $this->parser->parse($yaml)); 1505 } 1506 1507 public function testMultiLineQuotedStringWithTrailingBackslash() 1508 { 1509 $yaml = <<<YAML 1510foobar: 1511 "foo\ 1512 bar" 1513YAML; 1514 1515 $this->assertSame(['foobar' => 'foobar'], $this->parser->parse($yaml)); 1516 } 1517 1518 public function testCommentCharactersInMultiLineQuotedStrings() 1519 { 1520 $yaml = <<<YAML 1521foo: 1522 foobar: 'foo 1523 #bar' 1524 bar: baz 1525YAML; 1526 $expected = [ 1527 'foo' => [ 1528 'foobar' => 'foo #bar', 1529 'bar' => 'baz', 1530 ], 1531 ]; 1532 1533 $this->assertSame($expected, $this->parser->parse($yaml)); 1534 } 1535 1536 public function testBlankLinesInQuotedMultiLineString() 1537 { 1538 $yaml = <<<YAML 1539foobar: 'foo 1540 1541 bar' 1542YAML; 1543 $expected = [ 1544 'foobar' => "foo\nbar", 1545 ]; 1546 1547 $this->assertSame($expected, $this->parser->parse($yaml)); 1548 } 1549 1550 public function testParseMultiLineUnquotedString() 1551 { 1552 $yaml = <<<EOT 1553foo: bar 1554 baz 1555 foobar 1556 foo 1557bar: baz 1558EOT; 1559 1560 $this->assertSame(['foo' => 'bar baz foobar foo', 'bar' => 'baz'], $this->parser->parse($yaml)); 1561 } 1562 1563 public function testParseMultiLineString() 1564 { 1565 $this->assertEquals("foo bar\nbaz", $this->parser->parse("foo\nbar\n\nbaz")); 1566 } 1567 1568 /** 1569 * @dataProvider multiLineDataProvider 1570 */ 1571 public function testParseMultiLineMappingValue($yaml, $expected, $parseError) 1572 { 1573 $this->assertEquals($expected, $this->parser->parse($yaml)); 1574 } 1575 1576 public function multiLineDataProvider() 1577 { 1578 $tests = []; 1579 1580 $yaml = <<<'EOF' 1581foo: 1582- bar: 1583 one 1584 1585 two 1586 three 1587EOF; 1588 $expected = [ 1589 'foo' => [ 1590 [ 1591 'bar' => "one\ntwo three", 1592 ], 1593 ], 1594 ]; 1595 1596 $tests[] = [$yaml, $expected, false]; 1597 1598 $yaml = <<<'EOF' 1599bar 1600"foo" 1601EOF; 1602 $expected = 'bar "foo"'; 1603 1604 $tests[] = [$yaml, $expected, false]; 1605 1606 $yaml = <<<'EOF' 1607bar 1608"foo 1609EOF; 1610 $expected = 'bar "foo'; 1611 1612 $tests[] = [$yaml, $expected, false]; 1613 1614 $yaml = <<<'EOF' 1615bar 1616 1617'foo' 1618EOF; 1619 $expected = "bar\n'foo'"; 1620 1621 $tests[] = [$yaml, $expected, false]; 1622 1623 $yaml = <<<'EOF' 1624bar 1625 1626foo' 1627EOF; 1628 $expected = "bar\nfoo'"; 1629 1630 $tests[] = [$yaml, $expected, false]; 1631 1632 return $tests; 1633 } 1634 1635 public function testTaggedInlineMapping() 1636 { 1637 $this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS)); 1638 } 1639 1640 /** 1641 * @dataProvider taggedValuesProvider 1642 */ 1643 public function testCustomTagSupport($expected, $yaml) 1644 { 1645 $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS)); 1646 } 1647 1648 public function taggedValuesProvider() 1649 { 1650 return [ 1651 'scalars' => [ 1652 [ 1653 'foo' => new TaggedValue('inline', 'bar'), 1654 'quz' => new TaggedValue('long', 'this is a long text'), 1655 ], 1656 <<<YAML 1657foo: !inline bar 1658quz: !long > 1659 this is a long 1660 text 1661YAML 1662 ], 1663 'sequences' => [ 1664 [new TaggedValue('foo', ['yaml']), new TaggedValue('quz', ['bar'])], 1665 <<<YAML 1666- !foo 1667 - yaml 1668- !quz [bar] 1669YAML 1670 ], 1671 'mappings' => [ 1672 new TaggedValue('foo', ['foo' => new TaggedValue('quz', ['bar']), 'quz' => new TaggedValue('foo', ['quz' => 'bar'])]), 1673 <<<YAML 1674!foo 1675foo: !quz [bar] 1676quz: !foo 1677 quz: bar 1678YAML 1679 ], 1680 'inline' => [ 1681 [new TaggedValue('foo', ['foo', 'bar']), new TaggedValue('quz', ['foo' => 'bar', 'quz' => new TaggedValue('bar', ['one' => 'bar'])])], 1682 <<<YAML 1683- !foo [foo, bar] 1684- !quz {foo: bar, quz: !bar {one: bar}} 1685YAML 1686 ], 1687 'spaces-around-tag-value-in-sequence' => [ 1688 [new TaggedValue('foo', 'bar')], 1689 '[ !foo bar ]', 1690 ], 1691 ]; 1692 } 1693 1694 public function testNonSpecificTagSupport() 1695 { 1696 $this->assertSame('12', $this->parser->parse('! 12')); 1697 } 1698 1699 /** 1700 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1701 * @expectedExceptionMessage Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!iterator" at line 1 (near "!iterator [foo]"). 1702 */ 1703 public function testCustomTagsDisabled() 1704 { 1705 $this->parser->parse('!iterator [foo]'); 1706 } 1707 1708 /** 1709 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1710 * @expectedExceptionMessage Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!iterator" at line 1 (near "!iterator foo"). 1711 */ 1712 public function testUnsupportedTagWithScalar() 1713 { 1714 $this->parser->parse('!iterator foo'); 1715 } 1716 1717 /** 1718 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1719 * @expectedExceptionMessage The string "!!iterator foo" could not be parsed as it uses an unsupported built-in tag at line 1 (near "!!iterator foo"). 1720 */ 1721 public function testUnsupportedBuiltInTagWithScalar() 1722 { 1723 $this->parser->parse('!!iterator foo'); 1724 } 1725 1726 /** 1727 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1728 * @expectedExceptionMessage The built-in tag "!!foo" is not implemented at line 1 (near "!!foo"). 1729 */ 1730 public function testExceptionWhenUsingUnsuportedBuiltInTags() 1731 { 1732 $this->parser->parse('!!foo'); 1733 } 1734 1735 /** 1736 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1737 * @expectedExceptionMessage Complex mappings are not supported at line 1 (near "? "1""). 1738 */ 1739 public function testComplexMappingThrowsParseException() 1740 { 1741 $yaml = <<<YAML 1742? "1" 1743: 1744 name: végétalien 1745YAML; 1746 1747 $this->parser->parse($yaml); 1748 } 1749 1750 /** 1751 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1752 * @expectedExceptionMessage Complex mappings are not supported at line 2 (near "? "1""). 1753 */ 1754 public function testComplexMappingNestedInMappingThrowsParseException() 1755 { 1756 $yaml = <<<YAML 1757diet: 1758 ? "1" 1759 : 1760 name: végétalien 1761YAML; 1762 1763 $this->parser->parse($yaml); 1764 } 1765 1766 /** 1767 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1768 * @expectedExceptionMessage Complex mappings are not supported at line 1 (near "- ? "1""). 1769 */ 1770 public function testComplexMappingNestedInSequenceThrowsParseException() 1771 { 1772 $yaml = <<<YAML 1773- ? "1" 1774 : 1775 name: végétalien 1776YAML; 1777 1778 $this->parser->parse($yaml); 1779 } 1780 1781 /** 1782 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1783 * @expectedExceptionMessage Unable to parse at line 1 (near "[parameters]"). 1784 */ 1785 public function testParsingIniThrowsException() 1786 { 1787 $ini = <<<INI 1788[parameters] 1789 foo = bar 1790 bar = %foo% 1791INI; 1792 1793 $this->parser->parse($ini); 1794 } 1795 1796 private function loadTestsFromFixtureFiles($testsFile) 1797 { 1798 $parser = new Parser(); 1799 1800 $tests = []; 1801 $files = $parser->parseFile(__DIR__.'/Fixtures/'.$testsFile); 1802 foreach ($files as $file) { 1803 $yamls = file_get_contents(__DIR__.'/Fixtures/'.$file.'.yml'); 1804 1805 // split YAMLs documents 1806 foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) { 1807 if (!$yaml) { 1808 continue; 1809 } 1810 1811 $test = $parser->parse($yaml); 1812 if (isset($test['todo']) && $test['todo']) { 1813 // TODO 1814 } else { 1815 eval('$expected = '.trim($test['php']).';'); 1816 1817 $tests[] = [var_export($expected, true), $test['yaml'], $test['test']]; 1818 } 1819 } 1820 } 1821 1822 return $tests; 1823 } 1824 1825 public function testCanParseVeryLongValue() 1826 { 1827 $longStringWithSpaces = str_repeat('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ', 20000); 1828 $trickyVal = ['x' => $longStringWithSpaces]; 1829 1830 $yamlString = Yaml::dump($trickyVal); 1831 $arrayFromYaml = $this->parser->parse($yamlString); 1832 1833 $this->assertEquals($trickyVal, $arrayFromYaml); 1834 } 1835 1836 /** 1837 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1838 * @expectedExceptionMessage Reference "foo" does not exist at line 2 1839 */ 1840 public function testParserCleansUpReferencesBetweenRuns() 1841 { 1842 $yaml = <<<YAML 1843foo: &foo 1844 baz: foobar 1845bar: 1846 <<: *foo 1847YAML; 1848 $this->parser->parse($yaml); 1849 1850 $yaml = <<<YAML 1851bar: 1852 <<: *foo 1853YAML; 1854 $this->parser->parse($yaml); 1855 } 1856 1857 public function testPhpConstantTagMappingKey() 1858 { 1859 $yaml = <<<YAML 1860transitions: 1861 !php/const 'Symfony\Component\Yaml\Tests\B::FOO': 1862 from: 1863 - !php/const 'Symfony\Component\Yaml\Tests\B::BAR' 1864 to: !php/const 'Symfony\Component\Yaml\Tests\B::BAZ' 1865YAML; 1866 $expected = [ 1867 'transitions' => [ 1868 'foo' => [ 1869 'from' => [ 1870 'bar', 1871 ], 1872 'to' => 'baz', 1873 ], 1874 ], 1875 ]; 1876 1877 $this->assertSame($expected, $this->parser->parse($yaml, Yaml::PARSE_CONSTANT)); 1878 } 1879 1880 public function testMergeKeysWhenMappingsAreParsedAsObjects() 1881 { 1882 $yaml = <<<YAML 1883foo: &FOO 1884 bar: 1 1885bar: &BAR 1886 baz: 2 1887 <<: *FOO 1888baz: 1889 baz_foo: 3 1890 <<: 1891 baz_bar: 4 1892foobar: 1893 bar: ~ 1894 <<: [*FOO, *BAR] 1895YAML; 1896 $expected = (object) [ 1897 'foo' => (object) [ 1898 'bar' => 1, 1899 ], 1900 'bar' => (object) [ 1901 'baz' => 2, 1902 'bar' => 1, 1903 ], 1904 'baz' => (object) [ 1905 'baz_foo' => 3, 1906 'baz_bar' => 4, 1907 ], 1908 'foobar' => (object) [ 1909 'bar' => null, 1910 'baz' => 2, 1911 ], 1912 ]; 1913 1914 $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); 1915 } 1916 1917 public function testFilenamesAreParsedAsStringsWithoutFlag() 1918 { 1919 $file = __DIR__.'/Fixtures/index.yml'; 1920 1921 $this->assertSame($file, $this->parser->parse($file)); 1922 } 1923 1924 public function testParseFile() 1925 { 1926 $this->assertInternalType('array', $this->parser->parseFile(__DIR__.'/Fixtures/index.yml')); 1927 } 1928 1929 /** 1930 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1931 * @expectedExceptionMessageRegExp #^File ".+/Fixtures/nonexistent.yml" does not exist\.$# 1932 */ 1933 public function testParsingNonExistentFilesThrowsException() 1934 { 1935 $this->parser->parseFile(__DIR__.'/Fixtures/nonexistent.yml'); 1936 } 1937 1938 /** 1939 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 1940 * @expectedExceptionMessageRegExp #^File ".+/Fixtures/not_readable.yml" cannot be read\.$# 1941 */ 1942 public function testParsingNotReadableFilesThrowsException() 1943 { 1944 if ('\\' === \DIRECTORY_SEPARATOR) { 1945 $this->markTestSkipped('chmod is not supported on Windows'); 1946 } 1947 1948 if (!getenv('USER') || 'root' === getenv('USER')) { 1949 $this->markTestSkipped('This test will fail if run under superuser'); 1950 } 1951 1952 $file = __DIR__.'/Fixtures/not_readable.yml'; 1953 chmod($file, 0200); 1954 1955 $this->parser->parseFile($file); 1956 } 1957 1958 public function testParseReferencesOnMergeKeys() 1959 { 1960 $yaml = <<<YAML 1961mergekeyrefdef: 1962 a: foo 1963 <<: &quux 1964 b: bar 1965 c: baz 1966mergekeyderef: 1967 d: quux 1968 <<: *quux 1969YAML; 1970 $expected = [ 1971 'mergekeyrefdef' => [ 1972 'a' => 'foo', 1973 'b' => 'bar', 1974 'c' => 'baz', 1975 ], 1976 'mergekeyderef' => [ 1977 'd' => 'quux', 1978 'b' => 'bar', 1979 'c' => 'baz', 1980 ], 1981 ]; 1982 1983 $this->assertSame($expected, $this->parser->parse($yaml)); 1984 } 1985 1986 public function testParseReferencesOnMergeKeysWithMappingsParsedAsObjects() 1987 { 1988 $yaml = <<<YAML 1989mergekeyrefdef: 1990 a: foo 1991 <<: &quux 1992 b: bar 1993 c: baz 1994mergekeyderef: 1995 d: quux 1996 <<: *quux 1997YAML; 1998 $expected = (object) [ 1999 'mergekeyrefdef' => (object) [ 2000 'a' => 'foo', 2001 'b' => 'bar', 2002 'c' => 'baz', 2003 ], 2004 'mergekeyderef' => (object) [ 2005 'd' => 'quux', 2006 'b' => 'bar', 2007 'c' => 'baz', 2008 ], 2009 ]; 2010 2011 $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); 2012 } 2013 2014 /** 2015 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 2016 * @expectedExceptionMessage Reference "foo" does not exist 2017 */ 2018 public function testEvalRefException() 2019 { 2020 $yaml = <<<EOE 2021foo: { &foo { a: Steve, <<: *foo} } 2022EOE; 2023 $this->parser->parse($yaml); 2024 } 2025 2026 /** 2027 * @dataProvider circularReferenceProvider 2028 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 2029 * @expectedExceptionMessage Circular reference [foo, bar, foo] detected 2030 */ 2031 public function testDetectCircularReferences($yaml) 2032 { 2033 $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS); 2034 } 2035 2036 public function circularReferenceProvider() 2037 { 2038 $tests = []; 2039 2040 $yaml = <<<YAML 2041foo: 2042 - &foo 2043 - &bar 2044 bar: foobar 2045 baz: *foo 2046YAML; 2047 $tests['sequence'] = [$yaml]; 2048 2049 $yaml = <<<YAML 2050foo: &foo 2051 bar: &bar 2052 foobar: baz 2053 baz: *foo 2054YAML; 2055 $tests['mapping'] = [$yaml]; 2056 2057 $yaml = <<<YAML 2058foo: &foo 2059 bar: &bar 2060 foobar: baz 2061 <<: *foo 2062YAML; 2063 $tests['mapping with merge key'] = [$yaml]; 2064 2065 return $tests; 2066 } 2067 2068 /** 2069 * @dataProvider indentedMappingData 2070 */ 2071 public function testParseIndentedMappings($yaml, $expected) 2072 { 2073 $this->assertSame($expected, $this->parser->parse($yaml)); 2074 } 2075 2076 public function indentedMappingData() 2077 { 2078 $tests = []; 2079 2080 $yaml = <<<YAML 2081foo: 2082 - bar: "foobar" 2083 # A comment 2084 baz: "foobaz" 2085YAML; 2086 $expected = [ 2087 'foo' => [ 2088 [ 2089 'bar' => 'foobar', 2090 'baz' => 'foobaz', 2091 ], 2092 ], 2093 ]; 2094 $tests['comment line is first line in indented block'] = [$yaml, $expected]; 2095 2096 $yaml = <<<YAML 2097foo: 2098 - bar: 2099 # comment 2100 baz: [1, 2, 3] 2101YAML; 2102 $expected = [ 2103 'foo' => [ 2104 [ 2105 'bar' => [ 2106 'baz' => [1, 2, 3], 2107 ], 2108 ], 2109 ], 2110 ]; 2111 $tests['mapping value on new line starting with a comment line'] = [$yaml, $expected]; 2112 2113 $yaml = <<<YAML 2114foo: 2115 - 2116 bar: foobar 2117YAML; 2118 $expected = [ 2119 'foo' => [ 2120 [ 2121 'bar' => 'foobar', 2122 ], 2123 ], 2124 ]; 2125 $tests['mapping in sequence starting on a new line'] = [$yaml, $expected]; 2126 2127 $yaml = <<<YAML 2128foo: 2129 2130 bar: baz 2131YAML; 2132 $expected = [ 2133 'foo' => [ 2134 'bar' => 'baz', 2135 ], 2136 ]; 2137 $tests['blank line at the beginning of an indented mapping value'] = [$yaml, $expected]; 2138 2139 return $tests; 2140 } 2141} 2142 2143class B 2144{ 2145 public $b = 'foo'; 2146 2147 const FOO = 'foo'; 2148 const BAR = 'bar'; 2149 const BAZ = 'baz'; 2150} 2151