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 DocumentGenerator 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 DocumentGenerator, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 41 | class DocumentGenerator |
||
| 42 | { |
||
| 43 | /** |
||
| 44 | * @var bool |
||
| 45 | */ |
||
| 46 | private $backupExisting = true; |
||
| 47 | |||
| 48 | /** The extension to use for written php files */ |
||
| 49 | private $extension = '.php'; |
||
| 50 | |||
| 51 | /** Whether or not the current ClassMetadataInfo instance is new or old */ |
||
| 52 | private $isNew = true; |
||
| 53 | |||
| 54 | private $staticReflection = array(); |
||
| 55 | |||
| 56 | /** Number of spaces to use for indention in generated code */ |
||
| 57 | private $numSpaces = 4; |
||
| 58 | |||
| 59 | /** The actual spaces to use for indention */ |
||
| 60 | private $spaces = ' '; |
||
| 61 | |||
| 62 | /** The class all generated documents should extend */ |
||
| 63 | private $classToExtend; |
||
| 64 | |||
| 65 | /** Whether or not to generate annotations */ |
||
| 66 | private $generateAnnotations = false; |
||
| 67 | |||
| 68 | /** Whether or not to generate stub methods */ |
||
| 69 | private $generateDocumentStubMethods = false; |
||
| 70 | |||
| 71 | /** Whether or not to update the document class if it exists already */ |
||
| 72 | private $updateDocumentIfExists = false; |
||
| 73 | |||
| 74 | /** Whether or not to re-generate document class if it exists already */ |
||
| 75 | private $regenerateDocumentIfExists = false; |
||
| 76 | |||
| 77 | private static $classTemplate = |
||
| 78 | '<?php |
||
| 79 | |||
| 80 | <namespace> |
||
| 81 | |||
| 82 | <imports> |
||
| 83 | |||
| 84 | <documentAnnotation> |
||
| 85 | <documentClassName> |
||
| 86 | { |
||
| 87 | <documentBody> |
||
| 88 | }'; |
||
| 89 | |||
| 90 | private static $getMethodTemplate = |
||
| 91 | '/** |
||
| 92 | * <description> |
||
| 93 | * |
||
| 94 | * @return <variableType>$<variableName> |
||
| 95 | */ |
||
| 96 | public function <methodName>() |
||
| 97 | { |
||
| 98 | <spaces>return $this-><fieldName>; |
||
| 99 | }'; |
||
| 100 | |||
| 101 | private static $setMethodTemplate = |
||
| 102 | '/** |
||
| 103 | * <description> |
||
| 104 | * |
||
| 105 | * @param <variableType>$<variableName> |
||
| 106 | * @return $this |
||
| 107 | */ |
||
| 108 | public function <methodName>(<methodTypeHint>$<variableName><variableDefault>) |
||
| 109 | { |
||
| 110 | <spaces>$this-><fieldName> = $<variableName>; |
||
| 111 | <spaces>return $this; |
||
| 112 | }'; |
||
| 113 | |||
| 114 | private static $addMethodTemplate = |
||
| 115 | '/** |
||
| 116 | * <description> |
||
| 117 | * |
||
| 118 | * @param <variableType>$<variableName> |
||
| 119 | */ |
||
| 120 | public function <methodName>(<methodTypeHint>$<variableName>) |
||
| 121 | { |
||
| 122 | <spaces>$this-><fieldName>[] = $<variableName>; |
||
| 123 | }'; |
||
| 124 | |||
| 125 | private static $removeMethodTemplate = |
||
| 126 | '/** |
||
| 127 | * <description> |
||
| 128 | * |
||
| 129 | * @param <variableType>$<variableName> |
||
| 130 | */ |
||
| 131 | public function <methodName>(<methodTypeHint>$<variableName>) |
||
| 132 | { |
||
| 133 | <spaces>$this-><fieldName>->removeElement($<variableName>); |
||
| 134 | }'; |
||
| 135 | |||
| 136 | private static $lifecycleCallbackMethodTemplate = |
||
| 137 | '<comment> |
||
| 138 | public function <methodName>() |
||
| 139 | { |
||
| 140 | <spaces>// Add your code here |
||
| 141 | }'; |
||
| 142 | |||
| 143 | private static $constructorMethodTemplate = |
||
| 144 | 'public function __construct() |
||
| 145 | { |
||
| 146 | <collections> |
||
| 147 | } |
||
| 148 | '; |
||
| 149 | |||
| 150 | /** |
||
| 151 | * Generate and write document classes for the given array of ClassMetadataInfo instances |
||
| 152 | * |
||
| 153 | * @param array $metadatas |
||
| 154 | * @param string $outputDirectory |
||
| 155 | * @return void |
||
| 156 | */ |
||
| 157 | public function generate(array $metadatas, $outputDirectory) |
||
| 163 | |||
| 164 | /** |
||
| 165 | * Generated and write document class to disk for the given ClassMetadataInfo instance |
||
| 166 | * |
||
| 167 | * @param ClassMetadataInfo $metadata |
||
| 168 | * @param string $outputDirectory |
||
| 169 | * @throws \RuntimeException |
||
| 170 | * @return void |
||
| 171 | */ |
||
| 172 | 9 | public function writeDocumentClass(ClassMetadataInfo $metadata, $outputDirectory) |
|
| 204 | |||
| 205 | /** |
||
| 206 | * Generate a PHP5 Doctrine 2 document class from the given ClassMetadataInfo instance |
||
| 207 | * |
||
| 208 | * @param ClassMetadataInfo $metadata |
||
| 209 | * @return string $code |
||
| 210 | */ |
||
| 211 | 8 | public function generateDocumentClass(ClassMetadataInfo $metadata) |
|
| 232 | |||
| 233 | /** |
||
| 234 | * Generate the updated code for the given ClassMetadataInfo and document at path |
||
| 235 | * |
||
| 236 | * @param ClassMetadataInfo $metadata |
||
| 237 | * @param string $path |
||
| 238 | * @return string $code; |
||
| 239 | */ |
||
| 240 | 2 | public function generateUpdatedDocumentClass(ClassMetadataInfo $metadata, $path) |
|
| 250 | |||
| 251 | /** |
||
| 252 | * Set the number of spaces the exported class should have |
||
| 253 | * |
||
| 254 | * @param integer $numSpaces |
||
| 255 | * @return void |
||
| 256 | */ |
||
| 257 | public function setNumSpaces($numSpaces) |
||
| 262 | |||
| 263 | /** |
||
| 264 | * Set the extension to use when writing php files to disk |
||
| 265 | * |
||
| 266 | * @param string $extension |
||
| 267 | * @return void |
||
| 268 | */ |
||
| 269 | public function setExtension($extension) |
||
| 273 | |||
| 274 | /** |
||
| 275 | * Set the name of the class the generated classes should extend from |
||
| 276 | * |
||
| 277 | * @param string $classToExtend Class name. |
||
| 278 | * @return void |
||
| 279 | */ |
||
| 280 | 1 | public function setClassToExtend($classToExtend) |
|
| 284 | |||
| 285 | /** |
||
| 286 | * Set whether or not to generate annotations for the document |
||
| 287 | * |
||
| 288 | * @param bool $bool |
||
| 289 | * @return void |
||
| 290 | */ |
||
| 291 | 9 | public function setGenerateAnnotations($bool) |
|
| 295 | |||
| 296 | /** |
||
| 297 | * Set whether or not to try and update the document if it already exists |
||
| 298 | * |
||
| 299 | * @param bool $bool |
||
| 300 | * @return void |
||
| 301 | */ |
||
| 302 | 9 | public function setUpdateDocumentIfExists($bool) |
|
| 306 | |||
| 307 | /** |
||
| 308 | * Set whether or not to regenerate the document if it exists |
||
| 309 | * |
||
| 310 | * @param bool $bool |
||
| 311 | * @return void |
||
| 312 | */ |
||
| 313 | 9 | public function setRegenerateDocumentIfExists($bool) |
|
| 317 | |||
| 318 | /** |
||
| 319 | * Set whether or not to generate stub methods for the document |
||
| 320 | * |
||
| 321 | * @param bool $bool |
||
| 322 | * @return void |
||
| 323 | */ |
||
| 324 | 9 | public function setGenerateStubMethods($bool) |
|
| 328 | |||
| 329 | /** |
||
| 330 | * Sets a value indicating whether existing documents will be backed up. |
||
| 331 | * |
||
| 332 | * @param bool $bool True to backup existing document, false to overwrite. |
||
| 333 | */ |
||
| 334 | public function setBackupExisting($bool) |
||
| 338 | |||
| 339 | 8 | private function generateDocumentNamespace(ClassMetadataInfo $metadata) |
|
| 345 | |||
| 346 | 8 | private function generateDocumentClassName(ClassMetadataInfo $metadata) |
|
| 351 | |||
| 352 | 9 | private function generateDocumentBody(ClassMetadataInfo $metadata) |
|
| 381 | |||
| 382 | 9 | private function generateDocumentConstructor(ClassMetadataInfo $metadata) |
|
| 399 | |||
| 400 | /** |
||
| 401 | * @todo this won't work if there is a namespace in brackets and a class outside of it. |
||
| 402 | * @param string $path |
||
| 403 | */ |
||
| 404 | 2 | private function parseTokensInDocumentFile($path) |
|
| 437 | |||
| 438 | 9 | View Code Duplication | private function hasProperty($property, ClassMetadataInfo $metadata) |
| 460 | |||
| 461 | 9 | View Code Duplication | private function hasMethod($method, ClassMetadataInfo $metadata) |
| 483 | |||
| 484 | 8 | private function hasNamespace(ClassMetadataInfo $metadata) |
|
| 488 | |||
| 489 | 9 | private function extendsClass() |
|
| 493 | |||
| 494 | 2 | private function getClassToExtend() |
|
| 498 | |||
| 499 | 1 | private function getClassToExtendName() |
|
| 505 | |||
| 506 | 8 | private function getClassName(ClassMetadataInfo $metadata) |
|
| 511 | |||
| 512 | 8 | private function getNamespace(ClassMetadataInfo $metadata) |
|
| 516 | |||
| 517 | /** |
||
| 518 | * @param ClassMetadataInfo $metadata |
||
| 519 | * |
||
| 520 | * @return array |
||
| 521 | */ |
||
| 522 | 9 | protected function getTraits(ClassMetadataInfo $metadata) |
|
| 535 | |||
| 536 | 8 | private function generateDocumentImports() |
|
| 542 | |||
| 543 | 8 | private function generateDocumentDocBlock(ClassMetadataInfo $metadata) |
|
| 544 | { |
||
| 545 | 8 | $lines = array(); |
|
| 546 | 8 | $lines[] = '/**'; |
|
| 547 | 8 | $lines[] = ' * ' . $metadata->name; |
|
| 548 | |||
| 549 | 8 | if ($this->generateAnnotations) { |
|
| 550 | 8 | $lines[] = ' *'; |
|
| 551 | |||
| 552 | 8 | if ($metadata->isMappedSuperclass) { |
|
| 553 | $lines[] = ' * @ODM\\MappedSuperclass'; |
||
| 554 | 8 | } elseif ($metadata->isEmbeddedDocument) { |
|
| 555 | $lines[] = ' * @ODM\\EmbeddedDocument'; |
||
| 556 | 8 | } elseif ($metadata->isAggregationResultDocument) { |
|
| 557 | $lines[] = ' * @ODM\\AggregationResultDocument'; |
||
| 558 | } else { |
||
| 559 | 8 | $lines[] = ' * @ODM\\Document'; |
|
| 560 | } |
||
| 561 | |||
| 562 | 8 | $document = array(); |
|
| 563 | 8 | if ( ! $metadata->isMappedSuperclass && ! $metadata->isEmbeddedDocument && ! $metadata->isAggregationResultDocument) { |
|
| 564 | 8 | if ($metadata->collection) { |
|
| 565 | 8 | $document[] = ' * collection="' . $metadata->collection . '"'; |
|
| 566 | } |
||
| 567 | 8 | if ($metadata->customRepositoryClassName) { |
|
| 568 | 6 | $document[] = ' * repositoryClass="' . $metadata->customRepositoryClassName . '"'; |
|
| 569 | } |
||
| 570 | } |
||
| 571 | 8 | if ($metadata->indexes) { |
|
| 572 | $indexes = array(); |
||
| 573 | $indexLines = array(); |
||
| 574 | $indexLines[] = ' * indexes={'; |
||
| 575 | foreach ($metadata->indexes as $index) { |
||
| 576 | $keys = array(); |
||
| 577 | View Code Duplication | foreach ($index['keys'] as $key => $value) { |
|
| 578 | $keys[] = '"' . $key . '"="' . $value . '"'; |
||
| 579 | } |
||
| 580 | $options = array(); |
||
| 581 | View Code Duplication | foreach ($index['options'] as $key => $value) { |
|
| 582 | $options[] = '"' . $key . '"="' . $value . '"'; |
||
| 583 | } |
||
| 584 | $indexes[] = '@ODM\\Index(keys={' . implode(', ', $keys) . '}, options={' . implode(', ', $options) . '})'; |
||
| 585 | } |
||
| 586 | $indexLines[] = "\n * " . implode(",\n * ", $indexes); |
||
| 587 | $indexLines[] = "\n * }"; |
||
| 588 | |||
| 589 | $document[] = implode(null, $indexLines); |
||
| 590 | } |
||
| 591 | |||
| 592 | 8 | if ($document) { |
|
| 593 | 8 | $lines[count($lines) - 1] .= '('; |
|
| 594 | 8 | $lines[] = implode(",\n", $document); |
|
| 595 | 8 | $lines[] = ' * )'; |
|
| 596 | } |
||
| 597 | |||
| 598 | 8 | if ( ! empty($metadata->lifecycleCallbacks)) { |
|
| 599 | 6 | $lines[] = ' * @ODM\HasLifecycleCallbacks'; |
|
| 600 | } |
||
| 601 | |||
| 602 | $methods = array( |
||
| 603 | 8 | 'generateInheritanceAnnotation', |
|
| 604 | 'generateDiscriminatorFieldAnnotation', |
||
| 605 | 'generateDiscriminatorMapAnnotation', |
||
| 606 | 'generateDefaultDiscriminatorValueAnnotation', |
||
| 607 | 'generateChangeTrackingPolicyAnnotation' |
||
| 608 | ); |
||
| 609 | |||
| 610 | 8 | foreach ($methods as $method) { |
|
| 611 | 8 | if ($code = $this->$method($metadata)) { |
|
| 612 | 8 | $lines[] = ' * ' . $code; |
|
| 613 | } |
||
| 614 | } |
||
| 615 | } |
||
| 616 | |||
| 617 | 8 | $lines[] = ' */'; |
|
| 618 | 8 | return implode("\n", $lines); |
|
| 619 | } |
||
| 620 | |||
| 621 | 8 | private function generateInheritanceAnnotation(ClassMetadataInfo $metadata) |
|
| 627 | |||
| 628 | 8 | private function generateDiscriminatorFieldAnnotation(ClassMetadataInfo $metadata) |
|
| 634 | |||
| 635 | 8 | private function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata) |
|
| 636 | { |
||
| 637 | 8 | if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) { |
|
| 638 | $inheritanceClassMap = array(); |
||
| 639 | |||
| 640 | foreach ($metadata->discriminatorMap as $type => $class) { |
||
| 641 | $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"'; |
||
| 642 | } |
||
| 643 | |||
| 644 | return '@ODM\\DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; |
||
| 645 | } |
||
| 646 | 8 | } |
|
| 647 | |||
| 648 | 8 | private function generateDefaultDiscriminatorValueAnnotation(ClassMetadataInfo $metadata) |
|
| 654 | |||
| 655 | 8 | private function generateChangeTrackingPolicyAnnotation(ClassMetadataInfo $metadata) |
|
| 659 | |||
| 660 | 9 | private function generateDocumentStubMethods(ClassMetadataInfo $metadata) |
|
| 661 | { |
||
| 662 | 9 | $methods = array(); |
|
| 663 | |||
| 664 | 9 | foreach ($metadata->fieldMappings as $fieldMapping) { |
|
| 665 | 9 | if (isset($fieldMapping['id'])) { |
|
| 666 | 9 | if ($metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) { |
|
| 667 | if ($code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) { |
||
| 668 | $methods[] = $code; |
||
| 669 | } |
||
| 670 | } |
||
| 671 | 9 | if ($code = $code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) { |
|
| 672 | 9 | $methods[] = $code; |
|
| 673 | } |
||
| 674 | 9 | } elseif ( ! isset($fieldMapping['association'])) { |
|
| 675 | 9 | if ($code = $code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) { |
|
| 676 | 9 | $methods[] = $code; |
|
| 677 | } |
||
| 678 | 9 | if ($code = $code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) { |
|
| 679 | 9 | $methods[] = $code; |
|
| 680 | } |
||
| 681 | 8 | } elseif ($fieldMapping['type'] === ClassMetadataInfo::ONE) { |
|
| 682 | 8 | $nullable = $this->isAssociationNullable($fieldMapping) ? 'null' : null; |
|
| 683 | 8 | View Code Duplication | if ($code = $this->generateDocumentStubMethod($metadata, 'set', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null, $nullable)) { |
| 684 | 6 | $methods[] = $code; |
|
| 685 | } |
||
| 686 | 8 | View Code Duplication | if ($code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null)) { |
| 687 | 8 | $methods[] = $code; |
|
| 688 | } |
||
| 689 | 6 | } elseif ($fieldMapping['type'] === ClassMetadataInfo::MANY) { |
|
| 690 | 6 | View Code Duplication | if ($code = $this->generateDocumentStubMethod($metadata, 'add', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null)) { |
| 691 | 6 | $methods[] = $code; |
|
| 692 | } |
||
| 693 | 6 | View Code Duplication | if ($code = $this->generateDocumentStubMethod($metadata, 'remove', $fieldMapping['fieldName'], isset($fieldMapping['targetDocument']) ? $fieldMapping['targetDocument'] : null)) { |
| 694 | 6 | $methods[] = $code; |
|
| 695 | } |
||
| 696 | 6 | if ($code = $this->generateDocumentStubMethod($metadata, 'get', $fieldMapping['fieldName'], '\Doctrine\Common\Collections\Collection')) { |
|
| 697 | 9 | $methods[] = $code; |
|
| 698 | } |
||
| 699 | } |
||
| 700 | } |
||
| 701 | |||
| 702 | 9 | return implode("\n\n", $methods); |
|
| 703 | } |
||
| 704 | |||
| 705 | /** |
||
| 706 | * @param array $fieldMapping |
||
| 707 | * |
||
| 708 | * @return bool |
||
| 709 | */ |
||
| 710 | 8 | protected function isAssociationNullable($fieldMapping) |
|
| 714 | |||
| 715 | 9 | private function generateDocumentLifecycleCallbackMethods(ClassMetadataInfo $metadata) |
|
| 716 | { |
||
| 717 | 9 | if (empty($metadata->lifecycleCallbacks)) { |
|
| 733 | |||
| 734 | 9 | private function generateDocumentAssociationMappingProperties(ClassMetadataInfo $metadata) |
|
| 754 | |||
| 755 | 9 | private function generateDocumentFieldMappingProperties(ClassMetadataInfo $metadata) |
|
| 775 | |||
| 776 | 9 | private function generateDocumentStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) |
|
| 816 | |||
| 817 | 6 | private function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata) |
|
| 836 | |||
| 837 | 6 | private function generateAssociationMappingPropertyDocBlock(array $fieldMapping) |
|
| 886 | |||
| 887 | 7 | private function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) |
|
| 948 | |||
| 949 | 9 | private function prefixCodeWithSpaces($code, $num = 1) |
|
| 959 | |||
| 960 | private function getInheritanceTypeString($type) |
||
| 976 | |||
| 977 | 8 | private function getChangeTrackingPolicyString($policy) |
|
| 993 | |||
| 994 | 7 | private function getIdGeneratorTypeString($type) |
|
| 1019 | } |
||
| 1020 |
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: