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 |
||
| 45 | class DocumentGenerator |
||
| 46 | { |
||
| 47 | /** |
||
| 48 | * @var bool |
||
| 49 | */ |
||
| 50 | private $backupExisting = true; |
||
| 51 | |||
| 52 | /** The extension to use for written php files */ |
||
| 53 | private $extension = '.php'; |
||
| 54 | |||
| 55 | /** Whether or not the current ClassMetadataInfo instance is new or old */ |
||
| 56 | private $isNew = true; |
||
| 57 | |||
| 58 | private $staticReflection = array(); |
||
| 59 | |||
| 60 | /** Number of spaces to use for indention in generated code */ |
||
| 61 | private $numSpaces = 4; |
||
| 62 | |||
| 63 | /** The actual spaces to use for indention */ |
||
| 64 | private $spaces = ' '; |
||
| 65 | |||
| 66 | /** The class all generated documents should extend */ |
||
| 67 | private $classToExtend; |
||
| 68 | |||
| 69 | /** Whether or not to generate annotations */ |
||
| 70 | private $generateAnnotations = false; |
||
| 71 | |||
| 72 | /** Whether or not to generate stub methods */ |
||
| 73 | private $generateDocumentStubMethods = false; |
||
| 74 | |||
| 75 | /** Whether or not to update the document class if it exists already */ |
||
| 76 | private $updateDocumentIfExists = false; |
||
| 77 | |||
| 78 | /** Whether or not to re-generate document class if it exists already */ |
||
| 79 | private $regenerateDocumentIfExists = false; |
||
| 80 | |||
| 81 | private static $classTemplate = |
||
| 82 | '<?php |
||
| 83 | |||
| 84 | <namespace> |
||
| 85 | |||
| 86 | <imports> |
||
| 87 | |||
| 88 | <documentAnnotation> |
||
| 89 | <documentClassName> |
||
| 90 | { |
||
| 91 | <documentBody> |
||
| 92 | }'; |
||
| 93 | |||
| 94 | private static $getMethodTemplate = |
||
| 95 | '/** |
||
| 96 | * <description> |
||
| 97 | * |
||
| 98 | * @return <variableType>$<variableName> |
||
| 99 | */ |
||
| 100 | public function <methodName>() |
||
| 101 | { |
||
| 102 | <spaces>return $this-><fieldName>; |
||
| 103 | }'; |
||
| 104 | |||
| 105 | private static $setMethodTemplate = |
||
| 106 | '/** |
||
| 107 | * <description> |
||
| 108 | * |
||
| 109 | * @param <variableType>$<variableName> |
||
| 110 | * @return self |
||
| 111 | */ |
||
| 112 | public function <methodName>(<methodTypeHint>$<variableName><variableDefault>) |
||
| 113 | { |
||
| 114 | <spaces>$this-><fieldName> = $<variableName>; |
||
| 115 | <spaces>return $this; |
||
| 116 | }'; |
||
| 117 | |||
| 118 | private static $addMethodTemplate = |
||
| 119 | '/** |
||
| 120 | * <description> |
||
| 121 | * |
||
| 122 | * @param <variableType>$<variableName> |
||
| 123 | */ |
||
| 124 | public function <methodName>(<methodTypeHint>$<variableName>) |
||
| 125 | { |
||
| 126 | <spaces>$this-><fieldName>[] = $<variableName>; |
||
| 127 | }'; |
||
| 128 | |||
| 129 | private static $removeMethodTemplate = |
||
| 130 | '/** |
||
| 131 | * <description> |
||
| 132 | * |
||
| 133 | * @param <variableType>$<variableName> |
||
| 134 | */ |
||
| 135 | public function <methodName>(<methodTypeHint>$<variableName>) |
||
| 136 | { |
||
| 137 | <spaces>$this-><fieldName>->removeElement($<variableName>); |
||
| 138 | }'; |
||
| 139 | |||
| 140 | private static $lifecycleCallbackMethodTemplate = |
||
| 141 | '<comment> |
||
| 142 | public function <methodName>() |
||
| 143 | { |
||
| 144 | <spaces>// Add your code here |
||
| 145 | }'; |
||
| 146 | |||
| 147 | private static $constructorMethodTemplate = |
||
| 148 | 'public function __construct() |
||
| 149 | { |
||
| 150 | <collections> |
||
| 151 | } |
||
| 152 | '; |
||
| 153 | |||
| 154 | /** |
||
| 155 | * Generate and write document classes for the given array of ClassMetadataInfo instances |
||
| 156 | * |
||
| 157 | * @param array $metadatas |
||
| 158 | * @param string $outputDirectory |
||
| 159 | * @return void |
||
| 160 | */ |
||
| 161 | public function generate(array $metadatas, $outputDirectory) |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Generated and write document class to disk for the given ClassMetadataInfo instance |
||
| 170 | * |
||
| 171 | * @param ClassMetadataInfo $metadata |
||
| 172 | * @param string $outputDirectory |
||
| 173 | * @throws \RuntimeException |
||
| 174 | * @return void |
||
| 175 | */ |
||
| 176 | 7 | public function writeDocumentClass(ClassMetadataInfo $metadata, $outputDirectory) |
|
| 208 | |||
| 209 | /** |
||
| 210 | * Generate a PHP5 Doctrine 2 document class from the given ClassMetadataInfo instance |
||
| 211 | * |
||
| 212 | * @param ClassMetadataInfo $metadata |
||
| 213 | * @return string $code |
||
| 214 | */ |
||
| 215 | 6 | public function generateDocumentClass(ClassMetadataInfo $metadata) |
|
| 236 | |||
| 237 | /** |
||
| 238 | * Generate the updated code for the given ClassMetadataInfo and document at path |
||
| 239 | * |
||
| 240 | * @param ClassMetadataInfo $metadata |
||
| 241 | * @param string $path |
||
| 242 | * @return string $code; |
||
| 243 | */ |
||
| 244 | 2 | public function generateUpdatedDocumentClass(ClassMetadataInfo $metadata, $path) |
|
| 254 | |||
| 255 | /** |
||
| 256 | * Set the number of spaces the exported class should have |
||
| 257 | * |
||
| 258 | * @param integer $numSpaces |
||
| 259 | * @return void |
||
| 260 | */ |
||
| 261 | public function setNumSpaces($numSpaces) |
||
| 266 | |||
| 267 | /** |
||
| 268 | * Set the extension to use when writing php files to disk |
||
| 269 | * |
||
| 270 | * @param string $extension |
||
| 271 | * @return void |
||
| 272 | */ |
||
| 273 | public function setExtension($extension) |
||
| 277 | |||
| 278 | /** |
||
| 279 | * Set the name of the class the generated classes should extend from |
||
| 280 | * |
||
| 281 | * @return void |
||
| 282 | */ |
||
| 283 | 1 | public function setClassToExtend($classToExtend) |
|
| 287 | |||
| 288 | /** |
||
| 289 | * Set whether or not to generate annotations for the document |
||
| 290 | * |
||
| 291 | * @param bool $bool |
||
| 292 | * @return void |
||
| 293 | */ |
||
| 294 | 7 | public function setGenerateAnnotations($bool) |
|
| 298 | |||
| 299 | /** |
||
| 300 | * Set whether or not to try and update the document if it already exists |
||
| 301 | * |
||
| 302 | * @param bool $bool |
||
| 303 | * @return void |
||
| 304 | */ |
||
| 305 | 7 | public function setUpdateDocumentIfExists($bool) |
|
| 309 | |||
| 310 | /** |
||
| 311 | * Set whether or not to regenerate the document if it exists |
||
| 312 | * |
||
| 313 | * @param bool $bool |
||
| 314 | * @return void |
||
| 315 | */ |
||
| 316 | 7 | public function setRegenerateDocumentIfExists($bool) |
|
| 320 | |||
| 321 | /** |
||
| 322 | * Set whether or not to generate stub methods for the document |
||
| 323 | * |
||
| 324 | * @param bool $bool |
||
| 325 | * @return void |
||
| 326 | */ |
||
| 327 | 7 | public function setGenerateStubMethods($bool) |
|
| 331 | |||
| 332 | /** |
||
| 333 | * Should an existing document be backed up if it already exists? |
||
| 334 | */ |
||
| 335 | public function setBackupExisting($bool) |
||
| 339 | |||
| 340 | 6 | private function generateDocumentNamespace(ClassMetadataInfo $metadata) |
|
| 346 | |||
| 347 | 6 | private function generateDocumentClassName(ClassMetadataInfo $metadata) |
|
| 352 | |||
| 353 | 7 | private function generateDocumentBody(ClassMetadataInfo $metadata) |
|
| 382 | |||
| 383 | 7 | private function generateDocumentConstructor(ClassMetadataInfo $metadata) |
|
| 400 | |||
| 401 | /** |
||
| 402 | * @todo this won't work if there is a namespace in brackets and a class outside of it. |
||
| 403 | * @param string $path |
||
| 404 | */ |
||
| 405 | 2 | private function parseTokensInDocumentFile($path) |
|
| 438 | |||
| 439 | 7 | View Code Duplication | private function hasProperty($property, ClassMetadataInfo $metadata) |
| 440 | { |
||
| 441 | 7 | if ($this->extendsClass() || class_exists($metadata->name)) { |
|
| 442 | // don't generate property if its already on the base class. |
||
| 443 | 2 | $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name); |
|
| 444 | |||
| 445 | 2 | if ($reflClass->hasProperty($property)) { |
|
| 446 | 1 | return true; |
|
| 447 | } |
||
| 448 | 1 | } |
|
| 449 | |||
| 450 | 6 | foreach ($this->getTraits($metadata) as $trait) { |
|
| 451 | if ($trait->hasProperty($property)) { |
||
| 452 | return true; |
||
| 453 | } |
||
| 454 | 6 | } |
|
| 455 | |||
| 456 | return ( |
||
| 457 | 6 | isset($this->staticReflection[$metadata->name]) && |
|
| 458 | 1 | in_array($property, $this->staticReflection[$metadata->name]['properties']) |
|
| 459 | 6 | ); |
|
| 460 | } |
||
| 461 | |||
| 462 | 7 | View Code Duplication | private function hasMethod($method, ClassMetadataInfo $metadata) |
| 463 | { |
||
| 464 | 7 | if ($this->extendsClass() || class_exists($metadata->name)) { |
|
| 465 | // don't generate method if its already on the base class. |
||
| 466 | 2 | $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name); |
|
| 467 | |||
| 468 | 2 | if ($reflClass->hasMethod($method)) { |
|
| 469 | 1 | return true; |
|
| 470 | } |
||
| 471 | 2 | } |
|
| 472 | |||
| 473 | 7 | foreach ($this->getTraits($metadata) as $trait) { |
|
| 474 | if ($trait->hasMethod($method)) { |
||
| 475 | return true; |
||
| 476 | } |
||
| 477 | 7 | } |
|
| 478 | |||
| 479 | return ( |
||
| 480 | 7 | isset($this->staticReflection[$metadata->name]) && |
|
| 481 | 2 | in_array($method, $this->staticReflection[$metadata->name]['methods']) |
|
| 482 | 7 | ); |
|
| 483 | } |
||
| 484 | |||
| 485 | 6 | private function hasNamespace(ClassMetadataInfo $metadata) |
|
| 489 | |||
| 490 | 7 | private function extendsClass() |
|
| 494 | |||
| 495 | 2 | private function getClassToExtend() |
|
| 499 | |||
| 500 | 1 | private function getClassToExtendName() |
|
| 506 | |||
| 507 | 6 | private function getClassName(ClassMetadataInfo $metadata) |
|
| 512 | |||
| 513 | 6 | private function getNamespace(ClassMetadataInfo $metadata) |
|
| 517 | |||
| 518 | /** |
||
| 519 | * @param ClassMetadataInfo $metadata |
||
| 520 | * |
||
| 521 | * @return array |
||
| 522 | */ |
||
| 523 | 7 | protected function getTraits(ClassMetadataInfo $metadata) |
|
| 524 | { |
||
| 525 | 7 | if (PHP_VERSION_ID >= 50400 && ($metadata->reflClass !== null || class_exists($metadata->name))) { |
|
| 526 | $reflClass = $metadata->reflClass === null ? new \ReflectionClass($metadata->name) : $metadata->reflClass; |
||
| 527 | $traits = array(); |
||
| 528 | while ($reflClass !== false) { |
||
| 529 | $traits = array_merge($traits, $reflClass->getTraits()); |
||
| 530 | $reflClass = $reflClass->getParentClass(); |
||
| 531 | } |
||
| 532 | return $traits; |
||
| 533 | } |
||
| 534 | 7 | return array(); |
|
| 535 | } |
||
| 536 | |||
| 537 | 6 | private function generateDocumentImports(ClassMetadataInfo $metadata) |
|
| 543 | |||
| 544 | 6 | private function generateDocumentDocBlock(ClassMetadataInfo $metadata) |
|
| 619 | |||
| 620 | 6 | private function generateInheritanceAnnotation(ClassMetadataInfo $metadata) |
|
| 626 | |||
| 627 | 6 | private function generateDiscriminatorFieldAnnotation(ClassMetadataInfo $metadata) |
|
| 633 | |||
| 634 | 6 | private function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata) |
|
| 646 | |||
| 647 | 6 | private function generateDefaultDiscriminatorValueAnnotation(ClassMetadataInfo $metadata) |
|
| 653 | |||
| 654 | 6 | private function generateChangeTrackingPolicyAnnotation(ClassMetadataInfo $metadata) |
|
| 658 | |||
| 659 | 7 | private function generateDocumentStubMethods(ClassMetadataInfo $metadata) |
|
| 703 | |||
| 704 | /** |
||
| 705 | * @param array $fieldMapping |
||
| 706 | * |
||
| 707 | * @return bool |
||
| 708 | */ |
||
| 709 | 6 | protected function isAssociationNullable($fieldMapping) |
|
| 713 | |||
| 714 | 7 | private function generateDocumentLifecycleCallbackMethods(ClassMetadataInfo $metadata) |
|
| 732 | |||
| 733 | 7 | private function generateDocumentAssociationMappingProperties(ClassMetadataInfo $metadata) |
|
| 753 | |||
| 754 | 7 | private function generateDocumentFieldMappingProperties(ClassMetadataInfo $metadata) |
|
| 774 | |||
| 775 | 7 | private function generateDocumentStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) |
|
| 815 | |||
| 816 | 6 | private function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata) |
|
| 835 | |||
| 836 | 6 | private function generateAssociationMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) |
|
| 885 | |||
| 886 | 6 | private function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) |
|
| 887 | { |
||
| 888 | 6 | $lines = array(); |
|
| 889 | 6 | $lines[] = $this->spaces . '/**'; |
|
| 890 | 6 | if (isset($fieldMapping['id']) && $fieldMapping['id']) { |
|
| 891 | 6 | $fieldMapping['strategy'] = isset($fieldMapping['strategy']) ? $fieldMapping['strategy'] : ClassMetadataInfo::GENERATOR_TYPE_AUTO; |
|
| 892 | 6 | if ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_AUTO) { |
|
| 893 | 6 | $lines[] = $this->spaces . ' * @var MongoId $' . $fieldMapping['fieldName']; |
|
| 894 | 6 | View Code Duplication | } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_INCREMENT) { |
| 895 | $lines[] = $this->spaces . ' * @var integer $' . $fieldMapping['fieldName']; |
||
| 896 | } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_UUID) { |
||
| 897 | $lines[] = $this->spaces . ' * @var string $' . $fieldMapping['fieldName']; |
||
| 898 | View Code Duplication | } elseif ($fieldMapping['strategy'] === ClassMetadataInfo::GENERATOR_TYPE_NONE) { |
|
| 899 | $lines[] = $this->spaces . ' * @var $' . $fieldMapping['fieldName']; |
||
| 900 | } else { |
||
| 901 | $lines[] = $this->spaces . ' * @var $' . $fieldMapping['fieldName']; |
||
| 902 | } |
||
| 903 | 6 | } else { |
|
| 904 | 6 | $lines[] = $this->spaces . ' * @var ' . $fieldMapping['type'] . ' $' . $fieldMapping['fieldName']; |
|
| 905 | } |
||
| 906 | |||
| 907 | 6 | if ($this->generateAnnotations) { |
|
| 908 | 6 | $lines[] = $this->spaces . ' *'; |
|
| 909 | |||
| 910 | 6 | $field = array(); |
|
| 911 | 6 | if (isset($fieldMapping['id']) && $fieldMapping['id']) { |
|
| 912 | 6 | if (isset($fieldMapping['strategy'])) { |
|
| 913 | 6 | $field[] = 'strategy="' . $this->getIdGeneratorTypeString($metadata->generatorType) . '"'; |
|
| 914 | 6 | } |
|
| 915 | 6 | $lines[] = $this->spaces . ' * @ODM\\Id(' . implode(', ', $field) . ')'; |
|
| 916 | 6 | } else { |
|
| 917 | 6 | if (isset($fieldMapping['name'])) { |
|
| 918 | 6 | $field[] = 'name="' . $fieldMapping['name'] . '"'; |
|
| 919 | 6 | } |
|
| 920 | |||
| 921 | 6 | if (isset($fieldMapping['type'])) { |
|
| 922 | 6 | $field[] = 'type="' . $fieldMapping['type'] . '"'; |
|
| 923 | 6 | } |
|
| 924 | |||
| 925 | 6 | if (isset($fieldMapping['nullable']) && $fieldMapping['nullable'] === true) { |
|
| 926 | $field[] = 'nullable=' . var_export($fieldMapping['nullable'], true); |
||
| 927 | } |
||
| 928 | 6 | if (isset($fieldMapping['options'])) { |
|
| 929 | $options = array(); |
||
| 930 | foreach ($fieldMapping['options'] as $key => $value) { |
||
| 931 | $options[] = '"' . $key . '" = "' . $value . '"'; |
||
| 932 | } |
||
| 933 | $field[] = "options={" . implode(', ', $options) . "}"; |
||
| 934 | } |
||
| 935 | 6 | $lines[] = $this->spaces . ' * @ODM\\Field(' . implode(', ', $field) . ')'; |
|
| 936 | } |
||
| 937 | |||
| 938 | 6 | if (isset($fieldMapping['version']) && $fieldMapping['version']) { |
|
| 939 | $lines[] = $this->spaces . ' * @ODM\\Version'; |
||
| 940 | } |
||
| 941 | 6 | } |
|
| 942 | |||
| 943 | 6 | $lines[] = $this->spaces . ' */'; |
|
| 944 | |||
| 945 | 6 | return implode("\n", $lines); |
|
| 946 | } |
||
| 947 | |||
| 948 | 7 | private function prefixCodeWithSpaces($code, $num = 1) |
|
| 958 | |||
| 959 | private function getInheritanceTypeString($type) |
||
| 975 | |||
| 976 | 6 | private function getChangeTrackingPolicyString($policy) |
|
| 992 | |||
| 993 | 6 | private function getIdGeneratorTypeString($type) |
|
| 994 | { |
||
| 995 | switch ($type) { |
||
| 996 | 6 | case ClassMetadataInfo::GENERATOR_TYPE_AUTO: |
|
| 1018 | } |
||
| 1019 |
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: