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 TestCase |
||
19 | { |
||
20 | /** @var Parser */ |
||
21 | protected $parser; |
||
22 | |||
23 | protected function setUp() |
||
27 | |||
28 | protected function tearDown() |
||
32 | |||
33 | /** |
||
34 | * @dataProvider getDataFormSpecifications |
||
35 | */ |
||
36 | public function testSpecifications($file, $expected, $yaml, $comment) |
||
40 | |||
41 | public function getDataFormSpecifications() |
||
70 | |||
71 | public function testTabsInYaml() |
||
92 | |||
93 | public function testEndOfTheDocumentMarker() |
||
103 | |||
104 | public function getBlockChompingTests() |
||
410 | |||
411 | /** |
||
412 | * @dataProvider getBlockChompingTests |
||
413 | */ |
||
414 | public function testBlockChomping($expected, $yaml) |
||
418 | |||
419 | /** |
||
420 | * Regression test for issue #7989. |
||
421 | * |
||
422 | * @see https://github.com/symfony/symfony/issues/7989 |
||
423 | */ |
||
424 | public function testBlockLiteralWithLeadingNewlines() |
||
439 | |||
440 | public function testObjectSupportEnabled() |
||
454 | |||
455 | /** |
||
456 | * @dataProvider invalidDumpedObjectProvider |
||
457 | */ |
||
458 | public function testObjectSupportDisabledButNoExceptions($input) |
||
462 | |||
463 | /** |
||
464 | * @dataProvider getObjectForMapTests |
||
465 | */ |
||
466 | public function testObjectForMap($yaml, $expected) |
||
467 | { |
||
468 | $this->assertEquals($expected, $this->parser->parse($yaml, false, false, true)); |
||
469 | } |
||
470 | |||
471 | public function getObjectForMapTests() |
||
472 | { |
||
473 | $tests = array(); |
||
474 | |||
475 | $yaml = <<<'EOF' |
||
476 | foo: |
||
477 | fiz: [cat] |
||
478 | EOF; |
||
479 | $expected = new \stdClass(); |
||
480 | $expected->foo = new \stdClass(); |
||
481 | $expected->foo->fiz = array('cat'); |
||
482 | $tests['mapping'] = array($yaml, $expected); |
||
483 | |||
484 | $yaml = '{ "foo": "bar", "fiz": "cat" }'; |
||
485 | $expected = new \stdClass(); |
||
486 | $expected->foo = 'bar'; |
||
487 | $expected->fiz = 'cat'; |
||
488 | $tests['inline-mapping'] = array($yaml, $expected); |
||
489 | |||
490 | $yaml = "foo: bar\nbaz: foobar"; |
||
491 | $expected = new \stdClass(); |
||
492 | $expected->foo = 'bar'; |
||
493 | $expected->baz = 'foobar'; |
||
494 | $tests['object-for-map-is-applied-after-parsing'] = array($yaml, $expected); |
||
495 | |||
496 | $yaml = <<<'EOT' |
||
497 | array: |
||
498 | - key: one |
||
499 | - key: two |
||
500 | EOT; |
||
501 | $expected = new \stdClass(); |
||
502 | $expected->array = array(); |
||
503 | $expected->array[0] = new \stdClass(); |
||
504 | $expected->array[0]->key = 'one'; |
||
505 | $expected->array[1] = new \stdClass(); |
||
506 | $expected->array[1]->key = 'two'; |
||
507 | $tests['nest-map-and-sequence'] = array($yaml, $expected); |
||
508 | |||
509 | $yaml = <<<'YAML' |
||
510 | map: |
||
511 | 1: one |
||
512 | 2: two |
||
513 | YAML; |
||
514 | $expected = new \stdClass(); |
||
515 | $expected->map = new \stdClass(); |
||
516 | $expected->map->{1} = 'one'; |
||
517 | $expected->map->{2} = 'two'; |
||
518 | $tests['numeric-keys'] = array($yaml, $expected); |
||
519 | |||
520 | $yaml = <<<'YAML' |
||
521 | map: |
||
522 | 0: one |
||
523 | 1: two |
||
524 | YAML; |
||
525 | $expected = new \stdClass(); |
||
526 | $expected->map = new \stdClass(); |
||
527 | $expected->map->{0} = 'one'; |
||
528 | $expected->map->{1} = 'two'; |
||
529 | $tests['zero-indexed-numeric-keys'] = array($yaml, $expected); |
||
530 | |||
531 | return $tests; |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * @dataProvider invalidDumpedObjectProvider |
||
536 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
537 | */ |
||
538 | public function testObjectsSupportDisabledWithExceptions($yaml) |
||
542 | |||
543 | public function invalidDumpedObjectProvider() |
||
544 | { |
||
545 | $yamlTag = <<<'EOF' |
||
546 | foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} |
||
547 | bar: 1 |
||
548 | EOF; |
||
549 | $localTag = <<<'EOF' |
||
550 | foo: !php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} |
||
551 | bar: 1 |
||
552 | EOF; |
||
553 | |||
554 | return array( |
||
555 | 'yaml-tag' => array($yamlTag), |
||
556 | 'local-tag' => array($localTag), |
||
557 | ); |
||
558 | } |
||
559 | |||
560 | /** |
||
561 | * @requires extension iconv |
||
562 | */ |
||
563 | public function testNonUtf8Exception() |
||
581 | |||
582 | /** |
||
583 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
584 | */ |
||
585 | public function testUnindentedCollectionException() |
||
598 | |||
599 | /** |
||
600 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
601 | */ |
||
602 | public function testShortcutKeyUnindentedCollectionException() |
||
614 | |||
615 | /** |
||
616 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
617 | * @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/ |
||
618 | */ |
||
619 | public function testMultipleDocumentsNotSupportedException() |
||
635 | |||
636 | /** |
||
637 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
638 | */ |
||
639 | public function testSequenceInAMapping() |
||
648 | |||
649 | public function testSequenceInMappingStartedBySingleDashLine() |
||
676 | |||
677 | public function testSequenceFollowedByCommentEmbeddedInMapping() |
||
678 | { |
||
679 | $yaml = <<<'EOT' |
||
680 | a: |
||
681 | b: |
||
682 | - c |
||
683 | # comment |
||
684 | d: e |
||
685 | EOT; |
||
686 | $expected = array( |
||
687 | 'a' => array( |
||
688 | 'b' => array('c'), |
||
689 | 'd' => 'e', |
||
690 | ), |
||
691 | ); |
||
692 | |||
693 | $this->assertSame($expected, $this->parser->parse($yaml)); |
||
694 | } |
||
695 | |||
696 | /** |
||
697 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
698 | */ |
||
699 | public function testMappingInASequence() |
||
708 | |||
709 | /** |
||
710 | * @expectedException \Symfony\Component\Yaml\Exception\ParseException |
||
711 | * @expectedExceptionMessage missing colon |
||
712 | */ |
||
713 | public function testScalarInSequence() |
||
714 | { |
||
715 | Yaml::parse(<<<'EOF' |
||
716 | foo: |
||
717 | - bar |
||
718 | "missing colon" |
||
719 | foo: bar |
||
720 | EOF |
||
721 | ); |
||
722 | } |
||
723 | |||
724 | /** |
||
725 | * > It is an error for two equal keys to appear in the same mapping node. |
||
726 | * > In such a case the YAML processor may continue, ignoring the second |
||
727 | * > `key: value` pair and issuing an appropriate warning. This strategy |
||
728 | * > preserves a consistent information model for one-pass and random access |
||
729 | * > applications. |
||
730 | * |
||
731 | * @see http://yaml.org/spec/1.2/spec.html#id2759572 |
||
732 | * @see http://yaml.org/spec/1.1/#id932806 |
||
733 | */ |
||
734 | View Code Duplication | public function testMappingDuplicateKeyBlock() |
|
751 | |||
752 | View Code Duplication | public function testMappingDuplicateKeyFlow() |
|
765 | |||
766 | public function testEmptyValue() |
||
774 | |||
775 | public function testCommentAtTheRootIndent() |
||
776 | { |
||
777 | $this->assertEquals(array( |
||
778 | 'services' => array( |
||
779 | 'app.foo_service' => array( |
||
780 | 'class' => 'Foo', |
||
781 | ), |
||
782 | 'app/bar_service' => array( |
||
783 | 'class' => 'Bar', |
||
784 | ), |
||
785 | ), |
||
786 | ), Yaml::parse(<<<'EOF' |
||
787 | # comment 1 |
||
788 | services: |
||
789 | # comment 2 |
||
790 | # comment 3 |
||
791 | app.foo_service: |
||
792 | class: Foo |
||
793 | # comment 4 |
||
794 | # comment 5 |
||
795 | app/bar_service: |
||
796 | class: Bar |
||
797 | EOF |
||
798 | )); |
||
799 | } |
||
800 | |||
801 | public function testStringBlockWithComments() |
||
828 | |||
829 | public function testFoldedStringBlockWithComments() |
||
857 | |||
858 | public function testNestedFoldedStringBlockWithComments() |
||
889 | |||
890 | public function testReferenceResolvingInInlineStrings() |
||
915 | |||
916 | View Code Duplication | public function testYamlDirective() |
|
926 | |||
927 | View Code Duplication | public function testFloatKeys() |
|
928 | { |
||
929 | $yaml = <<<'EOF' |
||
930 | foo: |
||
931 | 1.2: "bar" |
||
932 | 1.3: "baz" |
||
933 | EOF; |
||
934 | |||
935 | $expected = array( |
||
936 | 'foo' => array( |
||
937 | '1.2' => 'bar', |
||
938 | '1.3' => 'baz', |
||
939 | ), |
||
940 | ); |
||
941 | |||
942 | $this->assertEquals($expected, $this->parser->parse($yaml)); |
||
943 | } |
||
944 | |||
945 | /** |
||
946 | * @group legacy |
||
947 | * @expectedDeprecation 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. |
||
948 | * throw ParseException in Symfony 3.0 |
||
949 | */ |
||
950 | public function testColonInMappingValueException() |
||
951 | { |
||
952 | $yaml = <<<'EOF' |
||
953 | foo: bar: baz |
||
954 | EOF; |
||
955 | |||
956 | $this->parser->parse($yaml); |
||
957 | } |
||
958 | |||
959 | public function testColonInMappingValueExceptionNotTriggeredByColonInComment() |
||
960 | { |
||
961 | $yaml = <<<'EOT' |
||
962 | foo: |
||
963 | bar: foobar # Note: a comment after a colon |
||
964 | EOT; |
||
965 | |||
966 | $this->assertSame(array('foo' => array('bar' => 'foobar')), $this->parser->parse($yaml)); |
||
967 | } |
||
968 | |||
969 | /** |
||
970 | * @dataProvider getCommentLikeStringInScalarBlockData |
||
971 | */ |
||
972 | public function testCommentLikeStringsAreNotStrippedInBlockScalars($yaml, $expectedParserResult) |
||
976 | |||
977 | public function getCommentLikeStringInScalarBlockData() |
||
1102 | |||
1103 | public function testBlankLinesAreParsedAsNewLinesInFoldedBlocks() |
||
1126 | |||
1127 | public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks() |
||
1153 | |||
1154 | /** |
||
1155 | * @param $lineNumber |
||
1156 | * @param $yaml |
||
1157 | * @dataProvider parserThrowsExceptionWithCorrectLineNumberProvider |
||
1158 | */ |
||
1159 | public function testParserThrowsExceptionWithCorrectLineNumber($lineNumber, $yaml) |
||
1160 | { |
||
1161 | if (method_exists($this, 'expectException')) { |
||
1162 | $this->expectException('\Symfony\Component\Yaml\Exception\ParseException'); |
||
1163 | $this->expectExceptionMessage(sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber)); |
||
1164 | } else { |
||
1165 | $this->setExpectedException('\Symfony\Component\Yaml\Exception\ParseException', sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber)); |
||
1166 | } |
||
1167 | |||
1168 | $this->parser->parse($yaml); |
||
1169 | } |
||
1170 | |||
1171 | public function parserThrowsExceptionWithCorrectLineNumberProvider() |
||
1172 | { |
||
1173 | return array( |
||
1174 | array( |
||
1175 | 4, |
||
1176 | <<<'YAML' |
||
1177 | foo: |
||
1178 | - |
||
1179 | # bar |
||
1180 | bar: "123", |
||
1181 | YAML |
||
1182 | ), |
||
1183 | array( |
||
1184 | 5, |
||
1185 | <<<'YAML' |
||
1186 | foo: |
||
1187 | - |
||
1188 | # bar |
||
1189 | # bar |
||
1190 | bar: "123", |
||
1191 | YAML |
||
1192 | ), |
||
1193 | array( |
||
1194 | 8, |
||
1195 | <<<'YAML' |
||
1196 | foo: |
||
1197 | - |
||
1198 | # foobar |
||
1199 | baz: 123 |
||
1200 | bar: |
||
1201 | - |
||
1202 | # bar |
||
1203 | bar: "123", |
||
1204 | YAML |
||
1205 | ), |
||
1206 | array( |
||
1207 | 10, |
||
1208 | <<<'YAML' |
||
1209 | foo: |
||
1210 | - |
||
1211 | # foobar |
||
1212 | # foobar |
||
1213 | baz: 123 |
||
1214 | bar: |
||
1215 | - |
||
1216 | # bar |
||
1217 | # bar |
||
1218 | bar: "123", |
||
1219 | YAML |
||
1220 | ), |
||
1221 | ); |
||
1222 | } |
||
1223 | |||
1224 | public function testCanParseVeryLongValue() |
||
1234 | } |
||
1235 | |||
1240 |
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.