Complex classes like PhpDocController 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 PhpDocController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class PhpDocController extends Controller |
||
23 | { |
||
24 | public $defaultAction = 'property'; |
||
25 | /** |
||
26 | * @var boolean whether to update class docs directly. Setting this to false will just output docs |
||
27 | * for copy and paste. |
||
28 | */ |
||
29 | public $updateFiles = true; |
||
30 | /** |
||
31 | * @var bool whether to add copyright header to php files. This should be skipped in application code. |
||
32 | */ |
||
33 | public $skipFrameworkRequirements = false; |
||
34 | |||
35 | |||
36 | /** |
||
37 | * Generates `@property` annotations in class files from getters and setters |
||
38 | * |
||
39 | * Property description will be taken from getter or setter or from an `@property` annotation |
||
40 | * in the getters docblock if there is one defined. |
||
41 | * |
||
42 | * See https://github.com/yiisoft/yii2/wiki/Core-framework-code-style#documentation for details. |
||
43 | * |
||
44 | * @param string $root the directory to parse files from. Defaults to YII2_PATH. |
||
45 | */ |
||
46 | public function actionProperty($root = null) |
||
47 | { |
||
48 | $files = $this->findFiles($root); |
||
49 | |||
50 | $nFilesTotal = 0; |
||
51 | $nFilesUpdated = 0; |
||
52 | foreach ($files as $file) { |
||
53 | $result = $this->generateClassPropertyDocs($file); |
||
54 | if ($result !== false) { |
||
55 | list($className, $phpdoc) = $result; |
||
56 | if ($this->updateFiles) { |
||
57 | if ($this->updateClassPropertyDocs($file, $className, $phpdoc)) { |
||
58 | $nFilesUpdated++; |
||
59 | } |
||
60 | } elseif (!empty($phpdoc)) { |
||
61 | $this->stdout("\n[ " . $file . " ]\n\n", Console::BOLD); |
||
62 | $this->stdout($phpdoc); |
||
63 | } |
||
64 | } |
||
65 | $nFilesTotal++; |
||
66 | } |
||
67 | |||
68 | $this->stdout("\nParsed $nFilesTotal files.\n"); |
||
69 | $this->stdout("Updated $nFilesUpdated files.\n"); |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * Fix some issues with PHPdoc in files |
||
74 | * |
||
75 | * @param string $root the directory to parse files from. Defaults to YII2_PATH. |
||
76 | */ |
||
77 | public function actionFix($root = null) |
||
108 | |||
109 | /** |
||
110 | * @inheritdoc |
||
111 | */ |
||
112 | public function options($actionID) |
||
116 | |||
117 | protected function findFiles($root, $needsInclude = true) |
||
239 | |||
240 | private function setUpExtensionAliases($extensionPath) |
||
248 | |||
249 | /** |
||
250 | * Fix file PHPdoc |
||
251 | */ |
||
252 | protected function fixFileDoc(&$lines) |
||
253 | { |
||
254 | // find namespace |
||
255 | $namespace = false; |
||
256 | $namespaceLine = ''; |
||
257 | $contentAfterNamespace = false; |
||
258 | foreach($lines as $i => $line) { |
||
259 | $line = trim($line); |
||
260 | if (!empty($line)) { |
||
261 | if (strncmp($line, 'namespace', 9) === 0) { |
||
262 | $namespace = $i; |
||
263 | $namespaceLine = $line; |
||
264 | } elseif ($namespace !== false) { |
||
265 | $contentAfterNamespace = $i; |
||
266 | break; |
||
267 | } |
||
268 | } |
||
269 | } |
||
270 | |||
271 | if ($namespace !== false && $contentAfterNamespace !== false) { |
||
272 | while($contentAfterNamespace > 0) { |
||
273 | array_shift($lines); |
||
274 | $contentAfterNamespace--; |
||
275 | } |
||
276 | $lines = array_merge([ |
||
277 | "<?php", |
||
278 | "/**", |
||
279 | " * @link http://www.yiiframework.com/", |
||
280 | " * @copyright Copyright (c) 2008 Yii Software LLC", |
||
281 | " * @license http://www.yiiframework.com/license/", |
||
282 | " */", |
||
283 | "", |
||
284 | $namespaceLine, |
||
285 | "" |
||
286 | ], $lines); |
||
287 | } |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Markdown aware fix of whitespace issues in doc comments |
||
292 | */ |
||
293 | protected function fixDocBlockIndentation(&$lines) |
||
294 | { |
||
295 | $docBlock = false; |
||
296 | $codeBlock = false; |
||
297 | $listIndent = ''; |
||
298 | $tag = false; |
||
299 | $indent = ''; |
||
300 | foreach($lines as $i => $line) { |
||
301 | if (preg_match('~^(\s*)/\*\*$~', $line, $matches)) { |
||
302 | $docBlock = true; |
||
303 | $indent = $matches[1]; |
||
304 | } elseif (preg_match('~^(\s*)\*+/~', $line)) { |
||
305 | if ($docBlock) { // could be the end of normal comment |
||
306 | $lines[$i] = $indent . ' */'; |
||
307 | } |
||
308 | $docBlock = false; |
||
309 | $codeBlock = false; |
||
310 | $listIndent = ''; |
||
311 | $tag = false; |
||
312 | } elseif ($docBlock) { |
||
313 | $line = ltrim($line); |
||
314 | if (isset($line[0]) && $line[0] === '*') { |
||
315 | $line = substr($line, 1); |
||
316 | } |
||
317 | if (isset($line[0]) && $line[0] === ' ') { |
||
318 | $line = substr($line, 1); |
||
319 | } |
||
320 | $docLine = str_replace("\t", ' ', rtrim($line)); |
||
321 | if (empty($docLine)) { |
||
322 | $listIndent = ''; |
||
323 | } elseif ($docLine[0] === '@') { |
||
324 | $listIndent = ''; |
||
325 | $codeBlock = false; |
||
326 | $tag = true; |
||
327 | $docLine = preg_replace('/\s+/', ' ', $docLine); |
||
328 | $docLine = $this->fixParamTypes($docLine); |
||
329 | } elseif (preg_match('/^(~~~|```)/', $docLine)) { |
||
330 | $codeBlock = !$codeBlock; |
||
331 | $listIndent = ''; |
||
332 | } elseif (preg_match('/^(\s*)([0-9]+\.|-|\*|\+) /', $docLine, $matches)) { |
||
333 | $listIndent = str_repeat(' ', strlen($matches[0])); |
||
334 | $tag = false; |
||
335 | $lines[$i] = $indent . ' * ' . $docLine; |
||
336 | continue; |
||
337 | } |
||
338 | if ($codeBlock) { |
||
339 | $lines[$i] = rtrim($indent . ' * ' . $docLine); |
||
340 | } else { |
||
341 | $lines[$i] = rtrim($indent . ' * ' . (empty($listIndent) && !$tag ? $docLine : ($listIndent . ltrim($docLine)))); |
||
342 | } |
||
343 | } |
||
344 | } |
||
345 | } |
||
346 | |||
347 | protected function fixParamTypes($line) |
||
348 | { |
||
349 | return preg_replace_callback('~@(param|return) ([\w\\|]+)~i', function($matches) { |
||
350 | $types = explode('|', $matches[2]); |
||
351 | foreach($types as $i => $type) { |
||
352 | switch($type){ |
||
353 | case 'int': $types[$i] = 'integer'; break; |
||
354 | case 'bool': $types[$i] = 'boolean'; break; |
||
355 | } |
||
356 | } |
||
357 | return '@' . $matches[1] . ' ' . implode('|', $types); |
||
358 | }, $line); |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Fixes line spacing code style for properties and constants |
||
363 | */ |
||
364 | protected function fixLineSpacing($lines) |
||
365 | { |
||
366 | $propertiesOnly = false; |
||
367 | // remove blank lines between properties |
||
368 | $skip = true; |
||
369 | $level = 0; |
||
370 | foreach($lines as $i => $line) { |
||
371 | if (strpos($line, 'class ') !== false) { |
||
372 | $skip = false; |
||
373 | } |
||
374 | if ($skip) { |
||
375 | continue; |
||
376 | } |
||
377 | |||
378 | // keep spaces in multi line arrays |
||
379 | if (strpos($line, '*') === false && strncmp(trim($line), "'SQLSTATE[", 10) !== 0) { |
||
380 | $level += substr_count($line, '[') - substr_count($line, ']'); |
||
381 | } |
||
382 | |||
383 | if (trim($line) === '') { |
||
384 | if ($level == 0) { |
||
385 | unset($lines[$i]); |
||
386 | } |
||
387 | } elseif (ltrim($line)[0] !== '*' && strpos($line, 'function ') !== false) { |
||
388 | break; |
||
389 | } elseif (trim($line) === '}') { |
||
390 | $propertiesOnly = true; |
||
391 | break; |
||
392 | } |
||
393 | } |
||
394 | $lines = array_values($lines); |
||
395 | |||
396 | // add back some |
||
397 | $endofUse = false; |
||
398 | $endofConst = false; |
||
399 | $endofPublic = false; |
||
400 | $endofProtected = false; |
||
401 | $endofPrivate = false; |
||
402 | $skip = true; |
||
403 | $level = 0; // track array properties |
||
404 | $property = ''; |
||
405 | foreach($lines as $i => $line) { |
||
406 | if (strpos($line, 'class ') !== false) { |
||
407 | $skip = false; |
||
408 | } |
||
409 | if ($skip) { |
||
410 | continue; |
||
411 | } |
||
412 | |||
413 | // check for multi line array |
||
414 | if ($level > 0) { |
||
415 | ${'endof'.$property} = $i; |
||
416 | } |
||
417 | |||
418 | $line = trim($line); |
||
419 | if (strncmp($line, 'public $', 8) === 0 || strncmp($line, 'public static $', 15) === 0) { |
||
420 | $endofPublic = $i; |
||
421 | $property = 'Public'; |
||
422 | $level = 0; |
||
423 | } elseif (strncmp($line, 'protected $', 11) === 0 || strncmp($line, 'protected static $', 18) === 0) { |
||
424 | $endofProtected = $i; |
||
425 | $property = 'Protected'; |
||
426 | $level = 0; |
||
427 | } elseif (strncmp($line, 'private $', 9) === 0 || strncmp($line, 'private static $', 16) === 0) { |
||
428 | $endofPrivate = $i; |
||
429 | $property = 'Private'; |
||
430 | $level = 0; |
||
431 | } elseif (substr($line,0 , 6) === 'const ') { |
||
432 | $endofConst = $i; |
||
433 | $property = false; |
||
434 | } elseif (substr($line,0 , 4) === 'use ') { |
||
435 | $endofUse = $i; |
||
436 | $property = false; |
||
437 | } elseif (!empty($line) && $line[0] === '*') { |
||
438 | $property = false; |
||
439 | } elseif (!empty($line) && $line[0] !== '*' && strpos($line, 'function ') !== false || $line === '}') { |
||
440 | break; |
||
441 | } |
||
442 | |||
443 | // check for multi line array |
||
444 | if ($property !== false && strncmp($line, "'SQLSTATE[", 10) !== 0) { |
||
445 | $level += substr_count($line, '[') - substr_count($line, ']'); |
||
446 | } |
||
447 | } |
||
448 | |||
449 | $endofAll = false; |
||
450 | foreach(['Private', 'Protected', 'Public', 'Const', 'Use'] as $var) { |
||
451 | if (${'endof'.$var} !== false) { |
||
452 | $endofAll = ${'endof'.$var}; |
||
453 | break; |
||
454 | } |
||
455 | } |
||
456 | |||
457 | // $this->checkPropertyOrder($lineInfo); |
||
458 | $result = []; |
||
459 | foreach($lines as $i => $line) { |
||
460 | $result[] = $line; |
||
461 | if (!($propertiesOnly && $i === $endofAll)) { |
||
462 | if ($i === $endofUse || $i === $endofConst || $i === $endofPublic || |
||
463 | $i === $endofProtected || $i === $endofPrivate) { |
||
464 | $result[] = ''; |
||
465 | } |
||
466 | if ($i === $endofAll) { |
||
467 | $result[] = ''; |
||
468 | } |
||
469 | } |
||
470 | } |
||
471 | |||
472 | return $result; |
||
473 | } |
||
474 | |||
475 | protected function checkPropertyOrder($lineInfo) |
||
|
|||
476 | { |
||
477 | // TODO |
||
478 | } |
||
479 | |||
480 | protected function updateClassPropertyDocs($file, $className, $propertyDoc) |
||
551 | |||
552 | /** |
||
553 | * remove multi empty lines and trim trailing whitespace |
||
554 | * |
||
555 | * @param $doc |
||
556 | * @return string |
||
557 | */ |
||
558 | protected function cleanDocComment($doc) |
||
559 | { |
||
560 | $lines = explode("\n", $doc); |
||
561 | $n = count($lines); |
||
562 | for ($i = 0; $i < $n; $i++) { |
||
563 | $lines[$i] = rtrim($lines[$i]); |
||
564 | if (trim($lines[$i]) == '*' && trim($lines[$i + 1]) == '*') { |
||
565 | unset($lines[$i]); |
||
566 | } |
||
567 | } |
||
568 | |||
569 | return implode("\n", $lines); |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * Replace property annotations in doc comment |
||
574 | * @param $doc |
||
575 | * @param $properties |
||
576 | * @return string |
||
577 | */ |
||
578 | protected function updateDocComment($doc, $properties) |
||
615 | |||
616 | protected function generateClassPropertyDocs($fileName) |
||
741 | |||
742 | protected function match($pattern, $subject, $split = false) |
||
743 | { |
||
744 | $sets = []; |
||
745 | // split subject by double newlines because regex sometimes has problems with matching |
||
746 | // in the complete set of methods |
||
747 | // example: yii\di\ServiceLocator setComponents() is not recognized in the whole but in |
||
748 | // a part of the class. |
||
762 | |||
763 | protected function fixSentence($str) |
||
770 | |||
771 | protected function getPropParam($prop, $param) |
||
775 | } |
||
776 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.