GenerateMarkdownDoc   F
last analyzed

Complexity

Total Complexity 84

Size/Duplication

Total Lines 741
Duplicated Lines 2.7 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 84
lcom 2
cbo 3
dl 20
loc 741
rs 1.859
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 4 1
A __construct() 0 4 1
A docClass() 0 5 1
A filterMethods() 0 5 1
A filterClasses() 0 5 1
A filterProperties() 0 5 1
A processClass() 0 5 1
A processClassSignature() 0 5 1
A processClassDocBlock() 0 5 1
A processMethod() 0 5 1
A processMethodSignature() 0 5 1
A processMethodDocBlock() 0 5 1
A processProperty() 0 5 1
A processPropertySignature() 0 5 1
A processPropertyDocBlock() 0 5 1
A reorder() 0 5 1
A reorderMethods() 0 5 1
A reorderProperties() 0 5 1
A filename() 0 5 1
A prepend() 0 5 1
A append() 0 5 1
A text() 0 5 1
A textForClass() 0 5 1
A run() 0 25 3
B documentClass() 0 43 9
B documentClassSignature() 0 25 6
A documentClassDocBlock() 0 11 3
B documentMethod() 10 24 6
B documentProperty() 10 23 6
A documentPropertySignature() 0 12 3
B documentPropertyDocBlock() 0 22 6
A documentParam() 0 20 5
A indentDoc() 0 15 2
A documentMethodSignature() 0 21 3
B documentMethodDocBlock() 0 35 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like GenerateMarkdownDoc 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 GenerateMarkdownDoc, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Robo\Task\Development;
3
4
use Robo\Task\BaseTask;
5
use Robo\Result;
6
use Robo\Contract\BuilderAwareInterface;
7
use Robo\Common\BuilderAwareTrait;
8
9
/**
10
 * Simple documentation generator from source files.
11
 * Takes classes, properties and methods with their docblocks and writes down a markdown file.
12
 *
13
 * ``` php
14
 * <?php
15
 * $this->taskGenDoc('models.md')
16
 *      ->docClass('Model\User') // take class Model\User
17
 *      ->docClass('Model\Post') // take class Model\Post
18
 *      ->filterMethods(function(\ReflectionMethod $r) {
19
 *          return $r->isPublic() or $r->isProtected(); // process public and protected methods
20
 *      })->processClass(function(\ReflectionClass $r, $text) {
21
 *          return "Class ".$r->getName()."\n\n$text\n\n###Methods\n";
22
 *      })->run();
23
 * ```
24
 *
25
 * By default this task generates a documentation for each public method of a class, interface or trait.
26
 * It combines method signature with a docblock. Both can be post-processed.
27
 *
28
 * ``` php
29
 * <?php
30
 * $this->taskGenDoc('models.md')
31
 *      ->docClass('Model\User')
32
 *      ->processClassSignature(false) // false can be passed to not include class signature
33
 *      ->processClassDocBlock(function(\ReflectionClass $r, $text) {
34
 *          return "[This is part of application model]\n" . $text;
35
 *      })->processMethodSignature(function(\ReflectionMethod $r, $text) {
36
 *          return "#### {$r->name}()";
37
 *      })->processMethodDocBlock(function(\ReflectionMethod $r, $text) {
38
 *          return strpos($r->name, 'save')===0 ? "[Saves to the database]\n" . $text : $text;
39
 *      })->run();
40
 * ```
41
 */
42
class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
43
{
44
    use BuilderAwareTrait;
45
46
    /**
47
     * @var string[]
48
     */
49
    protected $docClass = [];
50
51
    /**
52
     * @var callable
53
     */
54
    protected $filterMethods;
55
56
    /**
57
     * @var callable
58
     */
59
    protected $filterClasses;
60
61
    /**
62
     * @var callable
63
     */
64
    protected $filterProperties;
65
66
    /**
67
     * @var callable
68
     */
69
    protected $processClass;
70
71
    /**
72
     * @var callable|false
73
     */
74
    protected $processClassSignature;
75
76
    /**
77
     * @var callable|false
78
     */
79
    protected $processClassDocBlock;
80
81
    /**
82
     * @var callable|false
83
     */
84
    protected $processMethod;
85
86
    /**
87
     * @var callable|false
88
     */
89
    protected $processMethodSignature;
90
91
    /**
92
     * @var callable|false
93
     */
94
    protected $processMethodDocBlock;
95
96
    /**
97
     * @var callable|false
98
     */
99
    protected $processProperty;
100
101
    /**
102
     * @var callable|false
103
     */
104
    protected $processPropertySignature;
105
106
    /**
107
     * @var callable|false
108
     */
109
    protected $processPropertyDocBlock;
110
111
    /**
112
     * @var callable
113
     */
114
    protected $reorder;
115
116
    /**
117
     * @var callable
118
     */
119
    protected $reorderMethods;
120
121
    /**
122
     * @todo Unused property.
123
     *
124
     * @var callable
125
     */
126
    protected $reorderProperties;
127
128
    /**
129
     * @var string
130
     */
131
    protected $filename;
132
133
    /**
134
     * @var string
135
     */
136
    protected $prepend = "";
137
138
    /**
139
     * @var string
140
     */
141
    protected $append = "";
142
143
    /**
144
     * @var string
145
     */
146
    protected $text;
147
148
    /**
149
     * @var string[]
150
     */
151
    protected $textForClass = [];
152
153
    /**
154
     * @param string $filename
155
     *
156
     * @return static
157
     */
158
    public static function init($filename)
159
    {
160
        return new static($filename);
161
    }
162
163
    /**
164
     * @param string $filename
165
     */
166
    public function __construct($filename)
167
    {
168
        $this->filename = $filename;
169
    }
170
171
    /**
172
     * Put a class you want to be documented.
173
     *
174
     * @param string $item
175
     *
176
     * @return $this
177
     */
178
    public function docClass($item)
179
    {
180
        $this->docClass[] = $item;
181
        return $this;
182
    }
183
184
    /**
185
     * Using a callback function filter out methods that won't be documented.
186
     *
187
     * @param callable $filterMethods
188
     *
189
     * @return $this
190
     */
191
    public function filterMethods($filterMethods)
192
    {
193
        $this->filterMethods = $filterMethods;
194
        return $this;
195
    }
196
197
    /**
198
     * Using a callback function filter out classes that won't be documented.
199
     *
200
     * @param callable $filterClasses
201
     *
202
     * @return $this
203
     */
204
    public function filterClasses($filterClasses)
205
    {
206
        $this->filterClasses = $filterClasses;
207
        return $this;
208
    }
209
210
    /**
211
     * Using a callback function filter out properties that won't be documented.
212
     *
213
     * @param callable $filterProperties
214
     *
215
     * @return $this
216
     */
217
    public function filterProperties($filterProperties)
218
    {
219
        $this->filterProperties = $filterProperties;
220
        return $this;
221
    }
222
223
    /**
224
     * Post-process class documentation.
225
     *
226
     * @param callable $processClass
227
     *
228
     * @return $this
229
     */
230
    public function processClass($processClass)
231
    {
232
        $this->processClass = $processClass;
233
        return $this;
234
    }
235
236
    /**
237
     * Post-process class signature. Provide *false* to skip.
238
     *
239
     * @param callable|false $processClassSignature
240
     *
241
     * @return $this
242
     */
243
    public function processClassSignature($processClassSignature)
244
    {
245
        $this->processClassSignature = $processClassSignature;
246
        return $this;
247
    }
248
249
    /**
250
     * Post-process class docblock contents. Provide *false* to skip.
251
     *
252
     * @param callable|false $processClassDocBlock
253
     *
254
     * @return $this
255
     */
256
    public function processClassDocBlock($processClassDocBlock)
257
    {
258
        $this->processClassDocBlock = $processClassDocBlock;
259
        return $this;
260
    }
261
262
    /**
263
     * Post-process method documentation. Provide *false* to skip.
264
     *
265
     * @param callable|false $processMethod
266
     *
267
     * @return $this
268
     */
269
    public function processMethod($processMethod)
270
    {
271
        $this->processMethod = $processMethod;
272
        return $this;
273
    }
274
275
    /**
276
     * Post-process method signature. Provide *false* to skip.
277
     *
278
     * @param callable|false $processMethodSignature
279
     *
280
     * @return $this
281
     */
282
    public function processMethodSignature($processMethodSignature)
283
    {
284
        $this->processMethodSignature = $processMethodSignature;
285
        return $this;
286
    }
287
288
    /**
289
     * Post-process method docblock contents. Provide *false* to skip.
290
     *
291
     * @param callable|false $processMethodDocBlock
292
     *
293
     * @return $this
294
     */
295
    public function processMethodDocBlock($processMethodDocBlock)
296
    {
297
        $this->processMethodDocBlock = $processMethodDocBlock;
298
        return $this;
299
    }
300
301
    /**
302
     * Post-process property documentation. Provide *false* to skip.
303
     *
304
     * @param callable|false $processProperty
305
     *
306
     * @return $this
307
     */
308
    public function processProperty($processProperty)
309
    {
310
        $this->processProperty = $processProperty;
311
        return $this;
312
    }
313
314
    /**
315
     * Post-process property signature. Provide *false* to skip.
316
     *
317
     * @param callable|false $processPropertySignature
318
     *
319
     * @return $this
320
     */
321
    public function processPropertySignature($processPropertySignature)
322
    {
323
        $this->processPropertySignature = $processPropertySignature;
324
        return $this;
325
    }
326
327
    /**
328
     * Post-process property docblock contents. Provide *false* to skip.
329
     *
330
     * @param callable|false $processPropertyDocBlock
331
     *
332
     * @return $this
333
     */
334
    public function processPropertyDocBlock($processPropertyDocBlock)
335
    {
336
        $this->processPropertyDocBlock = $processPropertyDocBlock;
337
        return $this;
338
    }
339
340
    /**
341
     * Use a function to reorder classes.
342
     *
343
     * @param callable $reorder
344
     *
345
     * @return $this
346
     */
347
    public function reorder($reorder)
348
    {
349
        $this->reorder = $reorder;
350
        return $this;
351
    }
352
353
    /**
354
     * Use a function to reorder methods in class.
355
     *
356
     * @param callable $reorderMethods
357
     *
358
     * @return $this
359
     */
360
    public function reorderMethods($reorderMethods)
361
    {
362
        $this->reorderMethods = $reorderMethods;
363
        return $this;
364
    }
365
366
    /**
367
     * @param callable $reorderProperties
368
     *
369
     * @return $this
370
     */
371
    public function reorderProperties($reorderProperties)
372
    {
373
        $this->reorderProperties = $reorderProperties;
374
        return $this;
375
    }
376
377
    /**
378
     * @param string $filename
379
     *
380
     * @return $this
381
     */
382
    public function filename($filename)
383
    {
384
        $this->filename = $filename;
385
        return $this;
386
    }
387
388
    /**
389
     * Inserts text at the beginning of markdown file.
390
     *
391
     * @param string $prepend
392
     *
393
     * @return $this
394
     */
395
    public function prepend($prepend)
396
    {
397
        $this->prepend = $prepend;
398
        return $this;
399
    }
400
401
    /**
402
     * Inserts text at the end of markdown file.
403
     *
404
     * @param string $append
405
     *
406
     * @return $this
407
     */
408
    public function append($append)
409
    {
410
        $this->append = $append;
411
        return $this;
412
    }
413
414
    /**
415
     * @param string $text
416
     *
417
     * @return $this
418
     */
419
    public function text($text)
420
    {
421
        $this->text = $text;
422
        return $this;
423
    }
424
425
    /**
426
     * @param string $item
427
     *
428
     * @return $this
429
     */
430
    public function textForClass($item)
431
    {
432
        $this->textForClass[] = $item;
433
        return $this;
434
    }
435
436
    /**
437
     * {@inheritdoc}
438
     */
439
    public function run()
440
    {
441
        foreach ($this->docClass as $class) {
442
            $this->printTaskInfo("Processing {class}", ['class' => $class]);
443
            $this->textForClass[$class] = $this->documentClass($class);
444
        }
445
446
        if (is_callable($this->reorder)) {
447
            $this->printTaskInfo("Applying reorder function");
448
            call_user_func_array($this->reorder, [$this->textForClass]);
449
        }
450
451
        $this->text = implode("\n", $this->textForClass);
452
453
        /** @var \Robo\Result $result */
454
        $result = $this->collectionBuilder()->taskWriteToFile($this->filename)
0 ignored issues
show
Documentation Bug introduced by Greg Anderson
The method taskWriteToFile does not exist on object<Robo\Collection\CollectionBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
455
            ->line($this->prepend)
456
            ->text($this->text)
457
            ->line($this->append)
458
            ->run();
459
460
        $this->printTaskSuccess('{filename} created. {class-count} classes documented', ['filename' => $this->filename, 'class-count' => count($this->docClass)]);
461
462
        return new Result($this, $result->getExitCode(), $result->getMessage(), $this->textForClass);
463
    }
464
465
    /**
466
     * @param string $class
467
     *
468
     * @return null|string
469
     */
470
    protected function documentClass($class)
471
    {
472
        if (!class_exists($class) && !trait_exists($class)) {
473
            return "";
474
        }
475
        $refl = new \ReflectionClass($class);
476
477
        if (is_callable($this->filterClasses)) {
478
            $ret = call_user_func($this->filterClasses, $refl);
479
            if (!$ret) {
480
                return;
481
            }
482
        }
483
        $doc = $this->documentClassSignature($refl);
484
        $doc .= "\n" . $this->documentClassDocBlock($refl);
485
        $doc .= "\n";
486
487
        if (is_callable($this->processClass)) {
488
            $doc = call_user_func($this->processClass, $refl, $doc);
489
        }
490
491
        $properties = [];
492
        foreach ($refl->getProperties() as $reflProperty) {
493
            $properties[] = $this->documentProperty($reflProperty);
494
        }
495
496
        $properties = array_filter($properties);
497
        $doc .= implode("\n", $properties);
498
499
        $methods = [];
500
        foreach ($refl->getMethods() as $reflMethod) {
501
            $methods[$reflMethod->name] = $this->documentMethod($reflMethod);
502
        }
503
        if (is_callable($this->reorderMethods)) {
504
            call_user_func_array($this->reorderMethods, [&$methods]);
505
        }
506
507
        $methods = array_filter($methods);
508
509
        $doc .= implode("\n", $methods)."\n";
510
511
        return $doc;
512
    }
513
514
    /**
515
     * @param \ReflectionClass $reflectionClass
516
     *
517
     * @return string
518
     */
519
    protected function documentClassSignature(\ReflectionClass $reflectionClass)
520
    {
521
        if ($this->processClassSignature === false) {
522
            return "";
523
        }
524
525
        $signature = "## {$reflectionClass->name}\n\n";
526
527
        if ($parent = $reflectionClass->getParentClass()) {
528
            $signature .= "* *Extends* `{$parent->name}`";
529
        }
530
        $interfaces = $reflectionClass->getInterfaceNames();
531
        if (count($interfaces)) {
532
            $signature .= "\n* *Implements* `" . implode('`, `', $interfaces) . '`';
533
        }
534
        $traits = $reflectionClass->getTraitNames();
535
        if (count($traits)) {
536
            $signature .= "\n* *Uses* `" . implode('`, `', $traits) . '`';
537
        }
538
        if (is_callable($this->processClassSignature)) {
539
            $signature = call_user_func($this->processClassSignature, $reflectionClass, $signature);
540
        }
541
542
        return $signature;
543
    }
544
545
    /**
546
     * @param \ReflectionClass $reflectionClass
547
     *
548
     * @return string
549
     */
550
    protected function documentClassDocBlock(\ReflectionClass $reflectionClass)
551
    {
552
        if ($this->processClassDocBlock === false) {
553
            return "";
554
        }
555
        $doc = self::indentDoc($reflectionClass->getDocComment());
556
        if (is_callable($this->processClassDocBlock)) {
557
            $doc = call_user_func($this->processClassDocBlock, $reflectionClass, $doc);
558
        }
559
        return $doc;
560
    }
561
562
    /**
563
     * @param \ReflectionMethod $reflectedMethod
564
     *
565
     * @return string
566
     */
567
    protected function documentMethod(\ReflectionMethod $reflectedMethod)
568
    {
569
        if ($this->processMethod === false) {
570
            return "";
571
        }
572 View Code Duplication
        if (is_callable($this->filterMethods)) {
0 ignored issues
show
Duplication introduced by Davert
This code seems to be duplicated across your project.

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.

Loading history...
573
            $ret = call_user_func($this->filterMethods, $reflectedMethod);
574
            if (!$ret) {
575
                return "";
576
            }
577
        } else {
578
            if (!$reflectedMethod->isPublic()) {
579
                return "";
580
            }
581
        }
582
583
        $signature = $this->documentMethodSignature($reflectedMethod);
584
        $docblock = $this->documentMethodDocBlock($reflectedMethod);
585
        $methodDoc = "$signature $docblock";
586
        if (is_callable($this->processMethod)) {
587
            $methodDoc = call_user_func($this->processMethod, $reflectedMethod, $methodDoc);
588
        }
589
        return $methodDoc;
590
    }
591
592
    /**
593
     * @param \ReflectionProperty $reflectedProperty
594
     *
595
     * @return string
596
     */
597
    protected function documentProperty(\ReflectionProperty $reflectedProperty)
598
    {
599
        if ($this->processProperty === false) {
600
            return "";
601
        }
602 View Code Duplication
        if (is_callable($this->filterProperties)) {
0 ignored issues
show
Duplication introduced by Davert
This code seems to be duplicated across your project.

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.

Loading history...
603
            $ret = call_user_func($this->filterProperties, $reflectedProperty);
604
            if (!$ret) {
605
                return "";
606
            }
607
        } else {
608
            if (!$reflectedProperty->isPublic()) {
609
                return "";
610
            }
611
        }
612
        $signature = $this->documentPropertySignature($reflectedProperty);
613
        $docblock = $this->documentPropertyDocBlock($reflectedProperty);
614
        $propertyDoc = $signature . $docblock;
615
        if (is_callable($this->processProperty)) {
616
            $propertyDoc = call_user_func($this->processProperty, $reflectedProperty, $propertyDoc);
617
        }
618
        return $propertyDoc;
619
    }
620
621
    /**
622
     * @param \ReflectionProperty $reflectedProperty
623
     *
624
     * @return string
625
     */
626
    protected function documentPropertySignature(\ReflectionProperty $reflectedProperty)
627
    {
628
        if ($this->processPropertySignature === false) {
629
            return "";
630
        }
631
        $modifiers = implode(' ', \Reflection::getModifierNames($reflectedProperty->getModifiers()));
632
        $signature = "#### *$modifiers* {$reflectedProperty->name}";
633
        if (is_callable($this->processPropertySignature)) {
634
            $signature = call_user_func($this->processPropertySignature, $reflectedProperty, $signature);
635
        }
636
        return $signature;
637
    }
638
639
    /**
640
     * @param \ReflectionProperty $reflectedProperty
641
     *
642
     * @return string
643
     */
644
    protected function documentPropertyDocBlock(\ReflectionProperty $reflectedProperty)
645
    {
646
        if ($this->processPropertyDocBlock === false) {
647
            return "";
648
        }
649
        $propertyDoc = $reflectedProperty->getDocComment();
650
        // take from parent
651
        if (!$propertyDoc) {
652
            $parent = $reflectedProperty->getDeclaringClass();
653
            while ($parent = $parent->getParentClass()) {
654
                if ($parent->hasProperty($reflectedProperty->name)) {
655
                    $propertyDoc = $parent->getProperty($reflectedProperty->name)->getDocComment();
656
                }
657
            }
658
        }
659
        $propertyDoc = self::indentDoc($propertyDoc, 7);
660
        $propertyDoc = preg_replace("~^@(.*?)([$\s])~", ' * `$1` $2', $propertyDoc); // format annotations
661
        if (is_callable($this->processPropertyDocBlock)) {
662
            $propertyDoc = call_user_func($this->processPropertyDocBlock, $reflectedProperty, $propertyDoc);
663
        }
664
        return ltrim($propertyDoc);
665
    }
666
667
    /**
668
     * @param \ReflectionParameter $param
669
     *
670
     * @return string
671
     */
672
    protected function documentParam(\ReflectionParameter $param)
673
    {
674
        $text = "";
675
        if ($param->isArray()) {
676
            $text .= 'array ';
677
        }
678
        if ($param->isCallable()) {
679
            $text .= 'callable ';
680
        }
681
        $text .= '$' . $param->name;
682
        if ($param->isDefaultValueAvailable()) {
683
            if ($param->allowsNull()) {
684
                $text .= ' = null';
685
            } else {
686
                $text .= ' = ' . str_replace("\n", ' ', print_r($param->getDefaultValue(), true));
687
            }
688
        }
689
690
        return $text;
691
    }
692
693
    /**
694
     * @param string $doc
695
     * @param int $indent
696
     *
697
     * @return string
698
     */
699
    public static function indentDoc($doc, $indent = 3)
700
    {
701
        if (!$doc) {
702
            return $doc;
703
        }
704
        return implode(
705
            "\n",
706
            array_map(
707
                function ($line) use ($indent) {
708
                    return substr($line, $indent);
709
                },
710
                explode("\n", $doc)
711
            )
712
        );
713
    }
714
715
    /**
716
     * @param \ReflectionMethod $reflectedMethod
717
     *
718
     * @return string
719
     */
720
    protected function documentMethodSignature(\ReflectionMethod $reflectedMethod)
721
    {
722
        if ($this->processMethodSignature === false) {
723
            return "";
724
        }
725
        $modifiers = implode(' ', \Reflection::getModifierNames($reflectedMethod->getModifiers()));
726
        $params = implode(
727
            ', ',
728
            array_map(
729
                function ($p) {
730
                    return $this->documentParam($p);
731
                },
732
                $reflectedMethod->getParameters()
733
            )
734
        );
735
        $signature = "#### *$modifiers* {$reflectedMethod->name}($params)";
736
        if (is_callable($this->processMethodSignature)) {
737
            $signature = call_user_func($this->processMethodSignature, $reflectedMethod, $signature);
738
        }
739
        return $signature;
740
    }
741
742
    /**
743
     * @param \ReflectionMethod $reflectedMethod
744
     *
745
     * @return string
746
     */
747
    protected function documentMethodDocBlock(\ReflectionMethod $reflectedMethod)
748
    {
749
        if ($this->processMethodDocBlock === false) {
750
            return "";
751
        }
752
        $methodDoc = $reflectedMethod->getDocComment();
753
        // take from parent
754
        if (!$methodDoc) {
755
            $parent = $reflectedMethod->getDeclaringClass();
756
            while ($parent = $parent->getParentClass()) {
757
                if ($parent->hasMethod($reflectedMethod->name)) {
758
                    $methodDoc = $parent->getMethod($reflectedMethod->name)->getDocComment();
759
                }
760
            }
761
        }
762
        // take from interface
763
        if (!$methodDoc) {
764
            $interfaces = $reflectedMethod->getDeclaringClass()->getInterfaces();
765
            foreach ($interfaces as $interface) {
766
                $i = new \ReflectionClass($interface->name);
767
                if ($i->hasMethod($reflectedMethod->name)) {
768
                    $methodDoc = $i->getMethod($reflectedMethod->name)->getDocComment();
769
                    break;
770
                }
771
            }
772
        }
773
774
        $methodDoc = self::indentDoc($methodDoc, 7);
775
        $methodDoc = preg_replace("~^@(.*?) ([$\s])~m", ' * `$1` $2', $methodDoc); // format annotations
776
        if (is_callable($this->processMethodDocBlock)) {
777
            $methodDoc = call_user_func($this->processMethodDocBlock, $reflectedMethod, $methodDoc);
778
        }
779
780
        return $methodDoc;
781
    }
782
}
783