Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ParserTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ParserTest, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 18 | class ParserTest extends \PHPUnit_Framework_TestCase  | 
            ||
| 19 | { | 
            ||
| 20 | protected $parser;  | 
            ||
| 21 | |||
| 22 | protected function setUp()  | 
            ||
| 26 | |||
| 27 | protected function tearDown()  | 
            ||
| 31 | |||
| 32 | /**  | 
            ||
| 33 | * @dataProvider getDataFormSpecifications  | 
            ||
| 34 | */  | 
            ||
| 35 | public function testSpecifications($file, $expected, $yaml, $comment)  | 
            ||
| 39 | |||
| 40 | public function getDataFormSpecifications()  | 
            ||
| 69 | |||
| 70 | public function testTabsInYaml()  | 
            ||
| 91 | |||
| 92 | public function testEndOfTheDocumentMarker()  | 
            ||
| 102 | |||
| 103 | public function getBlockChompingTests()  | 
            ||
| 393 | |||
| 394 | /**  | 
            ||
| 395 | * @dataProvider getBlockChompingTests  | 
            ||
| 396 | */  | 
            ||
| 397 | public function testBlockChomping($expected, $yaml)  | 
            ||
| 401 | |||
| 402 | /**  | 
            ||
| 403 | * Regression test for issue #7989.  | 
            ||
| 404 | *  | 
            ||
| 405 | * @see https://github.com/symfony/symfony/issues/7989  | 
            ||
| 406 | */  | 
            ||
| 407 | public function testBlockLiteralWithLeadingNewlines()  | 
            ||
| 422 | |||
| 423 | public function testObjectSupportEnabled()  | 
            ||
| 437 | |||
| 438 | /**  | 
            ||
| 439 | * @dataProvider invalidDumpedObjectProvider  | 
            ||
| 440 | */  | 
            ||
| 441 | public function testObjectSupportDisabledButNoExceptions($input)  | 
            ||
| 445 | |||
| 446 | /**  | 
            ||
| 447 | * @dataProvider getObjectForMapTests  | 
            ||
| 448 | */  | 
            ||
| 449 | public function testObjectForMap($yaml, $expected)  | 
            ||
| 450 |     { | 
            ||
| 451 | $this->assertEquals($expected, $this->parser->parse($yaml, false, false, true));  | 
            ||
| 452 | }  | 
            ||
| 453 | |||
| 454 | public function getObjectForMapTests()  | 
            ||
| 455 |     { | 
            ||
| 456 | $tests = array();  | 
            ||
| 457 | |||
| 458 | $yaml = <<<EOF  | 
            ||
| 459 | foo:  | 
            ||
| 460 | fiz: [cat]  | 
            ||
| 461 | EOF;  | 
            ||
| 462 | $expected = new \stdClass();  | 
            ||
| 463 | $expected->foo = new \stdClass();  | 
            ||
| 464 |         $expected->foo->fiz = array('cat'); | 
            ||
| 465 | $tests['mapping'] = array($yaml, $expected);  | 
            ||
| 466 | |||
| 467 |         $yaml = '{ "foo": "bar", "fiz": "cat" }'; | 
            ||
| 468 | $expected = new \stdClass();  | 
            ||
| 469 | $expected->foo = 'bar';  | 
            ||
| 470 | $expected->fiz = 'cat';  | 
            ||
| 471 | $tests['inline-mapping'] = array($yaml, $expected);  | 
            ||
| 472 | |||
| 473 | $yaml = "foo: bar\nbaz: foobar";  | 
            ||
| 474 | $expected = new \stdClass();  | 
            ||
| 475 | $expected->foo = 'bar';  | 
            ||
| 476 | $expected->baz = 'foobar';  | 
            ||
| 477 | $tests['object-for-map-is-applied-after-parsing'] = array($yaml, $expected);  | 
            ||
| 478 | |||
| 479 | $yaml = <<<EOT  | 
            ||
| 480 | array:  | 
            ||
| 481 | - key: one  | 
            ||
| 482 | - key: two  | 
            ||
| 483 | EOT;  | 
            ||
| 484 | $expected = new \stdClass();  | 
            ||
| 485 | $expected->array = array();  | 
            ||
| 486 | $expected->array[0] = new \stdClass();  | 
            ||
| 487 | $expected->array[0]->key = 'one';  | 
            ||
| 488 | $expected->array[1] = new \stdClass();  | 
            ||
| 489 | $expected->array[1]->key = 'two';  | 
            ||
| 490 | $tests['nest-map-and-sequence'] = array($yaml, $expected);  | 
            ||
| 491 | |||
| 492 | $yaml = <<<YAML  | 
            ||
| 493 | map:  | 
            ||
| 494 | 1: one  | 
            ||
| 495 | 2: two  | 
            ||
| 496 | YAML;  | 
            ||
| 497 | $expected = new \stdClass();  | 
            ||
| 498 | $expected->map = new \stdClass();  | 
            ||
| 499 |         $expected->map->{1} = 'one'; | 
            ||
| 500 |         $expected->map->{2} = 'two'; | 
            ||
| 501 | $tests['numeric-keys'] = array($yaml, $expected);  | 
            ||
| 502 | |||
| 503 | $yaml = <<<YAML  | 
            ||
| 504 | map:  | 
            ||
| 505 | 0: one  | 
            ||
| 506 | 1: two  | 
            ||
| 507 | YAML;  | 
            ||
| 508 | $expected = new \stdClass();  | 
            ||
| 509 | $expected->map = new \stdClass();  | 
            ||
| 510 |         $expected->map->{0} = 'one'; | 
            ||
| 511 |         $expected->map->{1} = 'two'; | 
            ||
| 512 | $tests['zero-indexed-numeric-keys'] = array($yaml, $expected);  | 
            ||
| 513 | |||
| 514 | return $tests;  | 
            ||
| 515 | }  | 
            ||
| 516 | |||
| 517 | /**  | 
            ||
| 518 | * @dataProvider invalidDumpedObjectProvider  | 
            ||
| 519 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 520 | */  | 
            ||
| 521 | public function testObjectsSupportDisabledWithExceptions($yaml)  | 
            ||
| 525 | |||
| 526 | public function invalidDumpedObjectProvider()  | 
            ||
| 527 |     { | 
            ||
| 528 | $yamlTag = <<<EOF  | 
            ||
| 529 | foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} | 
            ||
| 530 | bar: 1  | 
            ||
| 531 | EOF;  | 
            ||
| 532 | $localTag = <<<EOF  | 
            ||
| 533 | foo: !php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} | 
            ||
| 534 | bar: 1  | 
            ||
| 535 | EOF;  | 
            ||
| 536 | |||
| 537 | return array(  | 
            ||
| 538 | 'yaml-tag' => array($yamlTag),  | 
            ||
| 539 | 'local-tag' => array($localTag),  | 
            ||
| 540 | );  | 
            ||
| 541 | }  | 
            ||
| 542 | |||
| 543 | /**  | 
            ||
| 544 | * @requires extension iconv  | 
            ||
| 545 | */  | 
            ||
| 546 | public function testNonUtf8Exception()  | 
            ||
| 564 | |||
| 565 | /**  | 
            ||
| 566 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 567 | */  | 
            ||
| 568 | public function testUnindentedCollectionException()  | 
            ||
| 581 | |||
| 582 | /**  | 
            ||
| 583 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 584 | */  | 
            ||
| 585 | public function testShortcutKeyUnindentedCollectionException()  | 
            ||
| 597 | |||
| 598 | /**  | 
            ||
| 599 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 600 | * @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/  | 
            ||
| 601 | */  | 
            ||
| 602 | public function testMultipleDocumentsNotSupportedException()  | 
            ||
| 618 | |||
| 619 | /**  | 
            ||
| 620 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 621 | */  | 
            ||
| 622 | public function testSequenceInAMapping()  | 
            ||
| 631 | |||
| 632 | public function testSequenceInMappingStartedBySingleDashLine()  | 
            ||
| 659 | |||
| 660 | public function testSequenceFollowedByCommentEmbeddedInMapping()  | 
            ||
| 661 |     { | 
            ||
| 662 | $yaml = <<<EOT  | 
            ||
| 663 | a:  | 
            ||
| 664 | b:  | 
            ||
| 665 | - c  | 
            ||
| 666 | # comment  | 
            ||
| 667 | d: e  | 
            ||
| 668 | EOT;  | 
            ||
| 669 | $expected = array(  | 
            ||
| 670 | 'a' => array(  | 
            ||
| 671 |                 'b' => array('c'), | 
            ||
| 672 | 'd' => 'e',  | 
            ||
| 673 | ),  | 
            ||
| 674 | );  | 
            ||
| 675 | |||
| 676 | $this->assertSame($expected, $this->parser->parse($yaml));  | 
            ||
| 677 | }  | 
            ||
| 678 | |||
| 679 | /**  | 
            ||
| 680 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 681 | */  | 
            ||
| 682 | public function testMappingInASequence()  | 
            ||
| 691 | |||
| 692 | /**  | 
            ||
| 693 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException  | 
            ||
| 694 | * @expectedExceptionMessage missing colon  | 
            ||
| 695 | */  | 
            ||
| 696 | public function testScalarInSequence()  | 
            ||
| 697 |     { | 
            ||
| 698 | Yaml::parse(<<<EOF  | 
            ||
| 699 | foo:  | 
            ||
| 700 | - bar  | 
            ||
| 701 | "missing colon"  | 
            ||
| 702 | foo: bar  | 
            ||
| 703 | EOF  | 
            ||
| 704 | );  | 
            ||
| 705 | }  | 
            ||
| 706 | |||
| 707 | /**  | 
            ||
| 708 | * > It is an error for two equal keys to appear in the same mapping node.  | 
            ||
| 709 | * > In such a case the YAML processor may continue, ignoring the second  | 
            ||
| 710 | * > `key: value` pair and issuing an appropriate warning. This strategy  | 
            ||
| 711 | * > preserves a consistent information model for one-pass and random access  | 
            ||
| 712 | * > applications.  | 
            ||
| 713 | *  | 
            ||
| 714 | * @see http://yaml.org/spec/1.2/spec.html#id2759572  | 
            ||
| 715 | * @see http://yaml.org/spec/1.1/#id932806  | 
            ||
| 716 | */  | 
            ||
| 717 | View Code Duplication | public function testMappingDuplicateKeyBlock()  | 
            |
| 734 | |||
| 735 | View Code Duplication | public function testMappingDuplicateKeyFlow()  | 
            |
| 748 | |||
| 749 | public function testEmptyValue()  | 
            ||
| 757 | |||
| 758 | public function testCommentAtTheRootIndent()  | 
            ||
| 759 |     { | 
            ||
| 760 | $this->assertEquals(array(  | 
            ||
| 761 | 'services' => array(  | 
            ||
| 762 | 'app.foo_service' => array(  | 
            ||
| 763 | 'class' => 'Foo',  | 
            ||
| 764 | ),  | 
            ||
| 765 | 'app/bar_service' => array(  | 
            ||
| 766 | 'class' => 'Bar',  | 
            ||
| 767 | ),  | 
            ||
| 768 | ),  | 
            ||
| 769 | ), Yaml::parse(<<<'EOF'  | 
            ||
| 770 | # comment 1  | 
            ||
| 771 | services:  | 
            ||
| 772 | # comment 2  | 
            ||
| 773 | # comment 3  | 
            ||
| 774 | app.foo_service:  | 
            ||
| 775 | class: Foo  | 
            ||
| 776 | # comment 4  | 
            ||
| 777 | # comment 5  | 
            ||
| 778 | app/bar_service:  | 
            ||
| 779 | class: Bar  | 
            ||
| 780 | EOF  | 
            ||
| 781 | ));  | 
            ||
| 782 | }  | 
            ||
| 783 | |||
| 784 | public function testStringBlockWithComments()  | 
            ||
| 811 | |||
| 812 | public function testFoldedStringBlockWithComments()  | 
            ||
| 840 | |||
| 841 | public function testNestedFoldedStringBlockWithComments()  | 
            ||
| 872 | |||
| 873 | public function testReferenceResolvingInInlineStrings()  | 
            ||
| 898 | |||
| 899 | View Code Duplication | public function testYamlDirective()  | 
            |
| 909 | |||
| 910 | View Code Duplication | public function testFloatKeys()  | 
            |
| 911 |     { | 
            ||
| 912 | $yaml = <<<'EOF'  | 
            ||
| 913 | foo:  | 
            ||
| 914 | 1.2: "bar"  | 
            ||
| 915 | 1.3: "baz"  | 
            ||
| 916 | EOF;  | 
            ||
| 917 | |||
| 918 | $expected = array(  | 
            ||
| 919 | 'foo' => array(  | 
            ||
| 920 | '1.2' => 'bar',  | 
            ||
| 921 | '1.3' => 'baz',  | 
            ||
| 922 | ),  | 
            ||
| 923 | );  | 
            ||
| 924 | |||
| 925 | $this->assertEquals($expected, $this->parser->parse($yaml));  | 
            ||
| 926 | }  | 
            ||
| 927 | |||
| 928 | /**  | 
            ||
| 929 | * @group legacy  | 
            ||
| 930 | * throw ParseException in Symfony 3.0  | 
            ||
| 931 | * @requires function Symfony\Bridge\PhpUnit\ErrorAssert::assertDeprecationsAreTriggered  | 
            ||
| 932 | */  | 
            ||
| 933 | public function testColonInMappingValueException()  | 
            ||
| 934 |     { | 
            ||
| 935 | $parser = $this->parser;  | 
            ||
| 936 | |||
| 937 |         ErrorAssert::assertDeprecationsAreTriggered('Using a colon in the unquoted mapping value "bar: baz" in line 1 is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', function () use ($parser) { | 
            ||
| 938 | $yaml = <<<EOF  | 
            ||
| 939 | foo: bar: baz  | 
            ||
| 940 | EOF;  | 
            ||
| 941 | $parser->parse($yaml);  | 
            ||
| 942 | });  | 
            ||
| 943 | }  | 
            ||
| 944 | |||
| 945 | public function testColonInMappingValueExceptionNotTriggeredByColonInComment()  | 
            ||
| 946 |     { | 
            ||
| 947 | $yaml = <<<EOT  | 
            ||
| 948 | foo:  | 
            ||
| 949 | bar: foobar # Note: a comment after a colon  | 
            ||
| 950 | EOT;  | 
            ||
| 951 | |||
| 952 |         $this->assertSame(array('foo' => array('bar' => 'foobar')), $this->parser->parse($yaml)); | 
            ||
| 953 | }  | 
            ||
| 954 | |||
| 955 | /**  | 
            ||
| 956 | * @dataProvider getCommentLikeStringInScalarBlockData  | 
            ||
| 957 | */  | 
            ||
| 958 | public function testCommentLikeStringsAreNotStrippedInBlockScalars($yaml, $expectedParserResult)  | 
            ||
| 962 | |||
| 963 | public function getCommentLikeStringInScalarBlockData()  | 
            ||
| 1088 | |||
| 1089 | public function testBlankLinesAreParsedAsNewLinesInFoldedBlocks()  | 
            ||
| 1112 | |||
| 1113 | public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks()  | 
            ||
| 1139 | |||
| 1140 | /**  | 
            ||
| 1141 | * @param $lineNumber  | 
            ||
| 1142 | * @param $yaml  | 
            ||
| 1143 | * @dataProvider parserThrowsExceptionWithCorrectLineNumberProvider  | 
            ||
| 1144 | */  | 
            ||
| 1145 | public function testParserThrowsExceptionWithCorrectLineNumber($lineNumber, $yaml)  | 
            ||
| 1146 |     { | 
            ||
| 1147 | $this->setExpectedException(  | 
            ||
| 1148 | '\Symfony\Component\Yaml\Exception\ParseException',  | 
            ||
| 1149 |             sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber) | 
            ||
| 1150 | );  | 
            ||
| 1151 | |||
| 1152 | $this->parser->parse($yaml);  | 
            ||
| 1153 | }  | 
            ||
| 1154 | |||
| 1155 | public function parserThrowsExceptionWithCorrectLineNumberProvider()  | 
            ||
| 1156 |     { | 
            ||
| 1157 | return array(  | 
            ||
| 1158 | array(  | 
            ||
| 1159 | 4,  | 
            ||
| 1160 | <<<YAML  | 
            ||
| 1161 | foo:  | 
            ||
| 1162 | -  | 
            ||
| 1163 | # bar  | 
            ||
| 1164 | bar: "123",  | 
            ||
| 1165 | YAML  | 
            ||
| 1166 | ),  | 
            ||
| 1167 | array(  | 
            ||
| 1168 | 5,  | 
            ||
| 1169 | <<<YAML  | 
            ||
| 1170 | foo:  | 
            ||
| 1171 | -  | 
            ||
| 1172 | # bar  | 
            ||
| 1173 | # bar  | 
            ||
| 1174 | bar: "123",  | 
            ||
| 1175 | YAML  | 
            ||
| 1176 | ),  | 
            ||
| 1177 | array(  | 
            ||
| 1178 | 8,  | 
            ||
| 1179 | <<<YAML  | 
            ||
| 1180 | foo:  | 
            ||
| 1181 | -  | 
            ||
| 1182 | # foobar  | 
            ||
| 1183 | baz: 123  | 
            ||
| 1184 | bar:  | 
            ||
| 1185 | -  | 
            ||
| 1186 | # bar  | 
            ||
| 1187 | bar: "123",  | 
            ||
| 1188 | YAML  | 
            ||
| 1189 | ),  | 
            ||
| 1190 | array(  | 
            ||
| 1191 | 10,  | 
            ||
| 1192 | <<<YAML  | 
            ||
| 1193 | foo:  | 
            ||
| 1194 | -  | 
            ||
| 1195 | # foobar  | 
            ||
| 1196 | # foobar  | 
            ||
| 1197 | baz: 123  | 
            ||
| 1198 | bar:  | 
            ||
| 1199 | -  | 
            ||
| 1200 | # bar  | 
            ||
| 1201 | # bar  | 
            ||
| 1202 | bar: "123",  | 
            ||
| 1203 | YAML  | 
            ||
| 1204 | ),  | 
            ||
| 1205 | );  | 
            ||
| 1206 | }  | 
            ||
| 1207 | }  | 
            ||
| 1208 | |||
| 1213 | 
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.