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!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: