| Total Complexity | 98 |
| Total Lines | 407 |
| Duplicated Lines | 9.34 % |
| Changes | 5 | ||
| Bugs | 0 | Features | 0 |
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 PhpDomainBuilder 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.
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 PhpDomainBuilder, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 49 | class PhpDomainBuilder extends RstBuilder { |
||
| 50 | |||
| 51 | const SECTION_BEFORE_DESCRIPTION = self::class . '::SECTION_BEFORE_DESCRIPTION'; |
||
| 52 | const SECTION_AFTER_DESCRIPTION = self::class . '::SECTION_AFTER_DESCRIPTION'; |
||
| 53 | const SECTION_AFTER_TITLE = self::class . '::SECTION_AFTER_TITLE'; |
||
| 54 | const SECTION_AFTER_INTRODUCTION = self::class . '::SECTION_AFTER_INTRODUCTION'; |
||
| 55 | |||
| 56 | use ExtensionBuilder { |
||
| 57 | ExtensionBuilder::__construct as private __extensionConstructor; |
||
| 58 | } |
||
| 59 | |||
| 60 | public function __construct($extensions) { |
||
| 61 | $this->__extensionConstructor($extensions); |
||
| 62 | $this->addMultiline('.. role:: php(code)' . PHP_EOL . ':language: php', true); |
||
| 63 | $this->addLine(); |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Strip element name from Fqsen to return the namespace only |
||
| 68 | * |
||
| 69 | * @param Element $element |
||
| 70 | * @return mixed |
||
| 71 | */ |
||
| 72 | public static function getNamespace(Element $element) { |
||
| 73 | return substr($element->getFqsen(), 0, strlen($element->getFqsen())-strlen('\\'. $element->getName())); |
||
| 74 | //return str_replace('\\' . $element->getName(), '', $element->getFqsen()); |
||
| 75 | } |
||
| 76 | |||
| 77 | /** |
||
| 78 | * Add namespace |
||
| 79 | * @param Element $element |
||
| 80 | */ |
||
| 81 | protected function addPageHeader(Element $element) { |
||
| 82 | $this->addH1(self::escape($element->getName()))->addLine(); |
||
| 83 | if (self::getNamespace($element) !== '') { |
||
| 84 | $this->beginPhpDomain('namespace', substr(self::getNamespace($element), 1), false); |
||
| 85 | } |
||
| 86 | if ($element instanceof Class_) { |
||
| 87 | $modifiers = $element->isAbstract() ? ' abstract' : ''; |
||
| 88 | $modifiers = $element->isFinal() ? ' final' : $modifiers; |
||
| 89 | if ($modifiers !== '') { |
||
| 90 | $this->addLine('.. rst-class:: ' . $modifiers)->addLine(); |
||
| 91 | } |
||
| 92 | } |
||
| 93 | |||
| 94 | $this->callExtensions(self::SECTION_AFTER_TITLE, $element); |
||
| 95 | |||
| 96 | |||
| 97 | $this->beginPhpDomain($this->getTypeForClass($element), $element->getName(), false); |
||
| 98 | $this->addLine(); |
||
| 99 | } |
||
| 100 | |||
| 101 | private function getTypeForClass($element) { |
||
| 102 | switch (get_class($element)) { |
||
| 103 | case Class_::class: |
||
| 104 | return 'class'; |
||
| 105 | case Interface_::class: |
||
| 106 | return 'interface'; |
||
| 107 | case Trait_::class: |
||
| 108 | return 'trait'; |
||
| 109 | case Function_::class: |
||
| 110 | return 'function'; |
||
| 111 | case Method::class: |
||
| 112 | return 'method'; |
||
| 113 | default: |
||
| 114 | return ''; |
||
| 115 | } |
||
| 116 | } |
||
| 117 | |||
| 118 | protected function addAfterIntroduction($element) { |
||
| 119 | $this->callExtensions(self::SECTION_AFTER_INTRODUCTION, $element); |
||
| 120 | } |
||
| 121 | |||
| 122 | |||
| 123 | protected function addConstants($constants) { |
||
| 124 | if (count($constants) > 0) { |
||
| 125 | $this->addH2('Constants'); |
||
| 126 | foreach ($constants as $constant) { |
||
| 127 | if ($this->shouldRenderElement($constant)) { |
||
| 128 | $this->addConstant($constant); |
||
| 129 | } |
||
| 130 | } |
||
| 131 | } |
||
| 132 | } |
||
| 133 | |||
| 134 | /** |
||
| 135 | * @param Constant $constant |
||
| 136 | */ |
||
| 137 | private function addConstant(Constant $constant) { |
||
| 138 | $this->beginPhpDomain('const', $constant->getName() . ' = ' . self::escape($constant->getValue())); |
||
| 139 | $docBlock = $constant->getDocBlock(); |
||
| 140 | $this->addDocBlockDescription($constant); |
||
| 141 | if ($docBlock) { |
||
| 142 | foreach ($docBlock->getTags() as $tag) { |
||
| 143 | $this->addDocblockTag($tag->getName(), $docBlock); |
||
| 144 | } |
||
| 145 | } |
||
| 146 | $this->endPhpDomain(); |
||
| 147 | } |
||
| 148 | |||
| 149 | /** |
||
| 150 | * @param Property[] $properties |
||
| 151 | */ |
||
| 152 | protected function addProperties($properties) { |
||
| 153 | if (count($properties) > 0) { |
||
| 154 | $this->addH2('Properties'); |
||
| 155 | foreach ($properties as $property) { |
||
| 156 | if ($this->shouldRenderElement($property)) { |
||
| 157 | $this->addProperty($property); |
||
| 158 | } |
||
| 159 | } |
||
| 160 | } |
||
| 161 | } |
||
| 162 | |||
| 163 | /** |
||
| 164 | * @param Property $property |
||
| 165 | */ |
||
| 166 | private function addProperty(Property $property) { |
||
| 167 | $modifiers = $property->isStatic() ? '' : ' static' ; |
||
| 168 | $this->beginPhpDomain('attr', $property->getVisibility() . $modifiers . ' ' . $property->getName()); |
||
| 169 | $docBlock = $property->getDocBlock(); |
||
| 170 | $this->addDocBlockDescription($property); |
||
| 171 | if ($docBlock) { |
||
| 172 | foreach ($docBlock->getTags() as $tag) { |
||
| 173 | $this->addDocblockTag($tag->getName(), $docBlock); |
||
| 174 | } |
||
| 175 | } |
||
| 176 | $this->endPhpDomain(); |
||
| 177 | } |
||
| 178 | |||
| 179 | /** |
||
| 180 | * @param Interface_|Class_ $element |
||
| 181 | */ |
||
| 182 | protected function addParent($element) { |
||
| 183 | if ($element instanceof Class_) { |
||
| 184 | $parent = $element->getParent(); |
||
| 185 | if ($parent !== null) { |
||
| 186 | $this->addFieldList('Parent', $parent !== null ? $this->getLink('class', $parent) : ''); |
||
| 187 | } |
||
| 188 | } |
||
| 189 | if ($element instanceof Interface_) { |
||
| 190 | $parents = $element->getParents(); |
||
| 191 | foreach ($parents as $parent) { |
||
| 192 | $this->addFieldList('Parent', $parent !== null ? $this->getLink('interface', $parent) : ''); |
||
| 193 | } |
||
| 194 | } |
||
| 195 | } |
||
| 196 | |||
| 197 | /** |
||
| 198 | * @param Class_|Trait_ $element |
||
| 199 | */ |
||
| 200 | protected function addUsedTraits($element) { |
||
| 207 | } |
||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * @param $methods |
||
| 212 | */ |
||
| 213 | protected function addMethods($methods) { |
||
| 214 | if (count($methods) > 0) { |
||
| 215 | $this->addH2('Methods'); |
||
| 218 | } |
||
| 219 | } |
||
| 220 | } |
||
| 221 | |||
| 222 | private function addMethod(Method $method) { |
||
| 223 | if (!$this->shouldRenderElement($method)) { |
||
| 224 | return; |
||
| 225 | } |
||
| 226 | $docBlock = $method->getDocBlock(); |
||
| 227 | $params = []; |
||
| 228 | $deprecated = []; |
||
| 229 | View Code Duplication | if ($docBlock !== null) { |
|
|
|
|||
| 230 | /** @var Param $param */ |
||
| 231 | foreach ($docBlock->getTagsByName('param') as $param) { |
||
| 232 | $params[$param->getVariableName()] = $param; |
||
| 233 | } |
||
| 234 | $deprecated = $docBlock->getTagsByName('deprecated'); |
||
| 235 | } |
||
| 236 | $args = ''; |
||
| 237 | /** @var Argument $argument */ |
||
| 238 | foreach ($method->getArguments() as $argument) { |
||
| 239 | // This will work after https://github.com/phpDocumentor/Reflection/pull/109 is merged |
||
| 240 | foreach ($argument->getTypes() as $type) { |
||
| 241 | $args .= self::escape($type) . '|'; |
||
| 242 | } |
||
| 243 | $args = substr($args, 0, -1) . ' '; |
||
| 244 | if($argument->isVariadic()) { |
||
| 245 | $args .= '...'; |
||
| 246 | } |
||
| 247 | if($argument->isByReference()) { |
||
| 248 | $args .= '&'; |
||
| 249 | } |
||
| 250 | $args .= '$' . $argument->getName(); |
||
| 251 | $default = $argument->getDefault(); |
||
| 252 | if ($default !== null) { |
||
| 253 | $default = $default === '' ? '""' : $default; |
||
| 254 | $args .= '=' . self::escape($default); |
||
| 255 | } |
||
| 256 | $args .= ', '; |
||
| 257 | } |
||
| 258 | $args = substr($args, 0, -2); |
||
| 259 | |||
| 260 | $modifiers = $method->getVisibility(); |
||
| 261 | $modifiers .= $method->isAbstract() ? ' abstract' : ''; |
||
| 262 | $modifiers .= $method->isFinal() ? ' final' : ''; |
||
| 263 | $modifiers .= $method->isStatic() ? ' static' : ''; |
||
| 264 | $deprecated = count($deprecated) > 0 ? ' deprecated' : ''; |
||
| 265 | $this->addLine('.. rst-class:: ' . $modifiers . $deprecated)->addLine(); |
||
| 266 | $this->indent(); |
||
| 267 | $this->beginPhpDomain('method', $modifiers . ' ' . $method->getName() . '(' . $args . ')'); |
||
| 268 | $this->addDocBlockDescription($method); |
||
| 269 | $this->addLine(); |
||
| 270 | if (!empty($params)) { |
||
| 271 | $parameterDetails = ''; |
||
| 272 | foreach ($method->getArguments() as $argument) { |
||
| 273 | /** @var Param $param */ |
||
| 274 | $param = $params[$argument->getName()]; |
||
| 275 | if ($param !== null) { |
||
| 276 | $typString = $param->getType(); |
||
| 277 | // Remove first \ to allow references |
||
| 278 | if (0 === strpos($typString, '\\')) { |
||
| 279 | $typString = substr($typString, 1); |
||
| 280 | } |
||
| 281 | $paramItem = '* '; |
||
| 282 | $paramItem .= '**$' . $argument->getName() . '** '; |
||
| 283 | if ($typString !== null) { |
||
| 284 | $paramItem .= '(' . self::typesToRst($typString) . ') '; |
||
| 285 | } |
||
| 286 | $paramItem .= ' ' . $param->getDescription(); |
||
| 287 | $parameterDetails .= $paramItem . PHP_EOL; |
||
| 288 | } |
||
| 289 | } |
||
| 290 | $this->addFieldList('Parameters', $parameterDetails); |
||
| 291 | } |
||
| 292 | if ($docBlock !== null) { |
||
| 293 | foreach ($docBlock->getTags() as $tag) { |
||
| 294 | $this->addDocblockTag($tag->getName(), $docBlock); |
||
| 295 | } |
||
| 296 | } |
||
| 297 | $this->endPhpDomain('method'); |
||
| 298 | $this->unindent(); |
||
| 299 | } |
||
| 300 | |||
| 301 | /** |
||
| 302 | * @param $type string |
||
| 303 | * @param $fqsen string |
||
| 304 | * @return string |
||
| 305 | */ |
||
| 306 | public static function getLink($type, $fqsen, $description='') { |
||
| 307 | if($description !== '') { |
||
| 308 | return ':php:' . $type . ':`' . RstBuilder::escape($description) . '<' . RstBuilder::escape(substr($fqsen, 1)) . '>`'; |
||
| 309 | } |
||
| 310 | return ':php:' . $type . ':`' . RstBuilder::escape(substr($fqsen, 1)) . '`'; |
||
| 311 | } |
||
| 312 | |||
| 313 | /** |
||
| 314 | * @param $type string |
||
| 315 | * @param $name string |
||
| 316 | * @param $indent bool Should indent after the section started |
||
| 317 | */ |
||
| 318 | public function beginPhpDomain($type, $name, $indent = true) { |
||
| 323 | } |
||
| 324 | } |
||
| 325 | |||
| 326 | /** |
||
| 327 | * @param string $type |
||
| 328 | * @return $this |
||
| 329 | */ |
||
| 330 | public function endPhpDomain($type = '') { |
||
| 333 | } |
||
| 334 | |||
| 335 | /** |
||
| 336 | * @param Class_|Interface_|Trait_|Property|Method|Constant $element |
||
| 337 | * @return $this |
||
| 338 | */ |
||
| 339 | public function addDocBlockDescription($element) { |
||
| 340 | if ($element === null) { |
||
| 341 | return $this; |
||
| 342 | } |
||
| 343 | $docBlock = $element->getDocBlock(); |
||
| 344 | $this->callExtensions(self::SECTION_BEFORE_DESCRIPTION, $element); |
||
| 345 | if ($docBlock !== null && $docBlock->getSummary() !== '') { |
||
| 346 | $this->addLine('.. rst-class:: phpdoc-description')->addLine(); |
||
| 347 | $this->indent(); |
||
| 348 | $this->addMultilineWithoutRendering(RstBuilder::escape($docBlock->getSummary()))->addLine(); |
||
| 349 | if ((string)$docBlock->getDescription() !== '') { |
||
| 350 | $this->addMultilineWithoutRendering(RstBuilder::escape($docBlock->getDescription()))->addLine(); |
||
| 351 | } |
||
| 352 | $this->unindent(); |
||
| 353 | } |
||
| 354 | $this->callExtensions(self::SECTION_AFTER_DESCRIPTION, $element); |
||
| 355 | return $this; |
||
| 356 | } |
||
| 357 | |||
| 358 | /** |
||
| 359 | * @param string $tagName Name of the tag to parse |
||
| 360 | * @param DocBlock $docBlock |
||
| 361 | */ |
||
| 362 | protected function addDocblockTag($tagName, DocBlock $docBlock) { |
||
| 363 | $tags = $docBlock->getTagsByName($tagName); |
||
| 364 | switch ($tagName) { |
||
| 365 | View Code Duplication | case 'return': |
|
| 366 | if (count($tags) === 0) continue; |
||
| 367 | /** @var Return_ $return */ |
||
| 368 | $return = $tags[0]; |
||
| 369 | $this->addMultiline(':Returns: ' . self::typesToRst($return->getType()) . ' ' . RstBuilder::escape($return->getDescription()), true); |
||
| 370 | break; |
||
| 371 | case 'var': |
||
| 372 | if (count($tags) === 0) continue; |
||
| 373 | /** @var DocBlock\Tags\Var_ $return */ |
||
| 374 | $return = $tags[0]; |
||
| 375 | $this->addMultiline(':Type: ' . self::typesToRst($return->getType()) . ' ' . RstBuilder::escape($return->getDescription()), true); |
||
| 376 | break; |
||
| 377 | View Code Duplication | case 'throws': |
|
| 378 | if (count($tags) === 0) continue; |
||
| 379 | /** @var Throws $tag */ |
||
| 380 | foreach ($tags as $tag) { |
||
| 381 | $this->addMultiline(':Throws: ' . self::typesToRst($tag->getType()) . ' ' . RstBuilder::escape($tag->getDescription()), true); |
||
| 382 | } |
||
| 383 | break; |
||
| 384 | View Code Duplication | case 'since': |
|
| 385 | if (count($tags) === 0) continue; |
||
| 386 | /** @var Since $return */ |
||
| 387 | $return = $tags[0]; |
||
| 388 | $this->addMultiline(':Since: ' . $return->getVersion() . ' ' . RstBuilder::escape($return->getDescription()), true); |
||
| 389 | break; |
||
| 390 | View Code Duplication | case 'deprecated': |
|
| 391 | if (count($tags) === 0) continue; |
||
| 392 | /** @var Deprecated $return */ |
||
| 393 | $return = $tags[0]; |
||
| 394 | $this->addMultiline(':Deprecated: ' . $return->getVersion() . ' ' . RstBuilder::escape($return->getDescription()), true); |
||
| 395 | break; |
||
| 396 | case 'see': |
||
| 397 | if (count($tags) === 0) continue; |
||
| 398 | /** @var See $return */ |
||
| 399 | $return = $tags[0]; |
||
| 400 | $this->addMultiline(':See: ' . self::typesToRst($return->getReference()) . ' ' . RstBuilder::escape($return->getDescription()), true); |
||
| 401 | break; |
||
| 402 | View Code Duplication | case 'license': |
|
| 403 | if (count($tags) === 0) continue; |
||
| 404 | /** @var DocBlock\Tags\BaseTag $return */ |
||
| 405 | $return = $tags[0]; |
||
| 406 | $this->addMultiline(':License: ' . RstBuilder::escape($return->getDescription()), true); |
||
| 407 | break; |
||
| 408 | case 'param': |
||
| 409 | // param handling is done by subclasses since it is more that docbook parsing |
||
| 410 | break; |
||
| 411 | default: |
||
| 412 | //echo 'Tag handling not defined for: ' . $tag . PHP_EOL; |
||
| 413 | break; |
||
| 414 | } |
||
| 415 | |||
| 416 | } |
||
| 417 | |||
| 418 | /** |
||
| 419 | * @param string $typesString |
||
| 420 | * @return bool|string |
||
| 421 | */ |
||
| 422 | public static function typesToRst($typesString) { |
||
| 442 | } |
||
| 443 | |||
| 444 | /** |
||
| 445 | * @param Element $element |
||
| 446 | * @return bool |
||
| 447 | */ |
||
| 448 | public function shouldRenderElement(Element $element) { |
||
| 456 | } |
||
| 457 | |||
| 458 | |||
| 459 | } |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.