DocBlockGenerator   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 47
c 4
b 1
f 0
dl 0
loc 155
rs 10
wmc 19

7 Methods

Rating   Name   Duplication   Size   Complexity  
B mergeGeneratedTagsIntoDocBlock() 0 38 11
A __construct() 0 10 2
A getExistingTags() 0 10 2
A removeExistingSupportedTags() 0 9 1
A getExistingDocBlock() 0 3 1
A getGeneratedTags() 0 3 1
A getGeneratedDocBlock() 0 5 1
1
<?php
2
3
namespace SilverLeague\IDEAnnotator\Generators;
4
5
use phpDocumentor\Reflection\DocBlock;
6
use phpDocumentor\Reflection\DocBlock\Serializer;
7
use phpDocumentor\Reflection\DocBlock\Tag;
8
use phpDocumentor\Reflection\DocBlockFactory;
9
use SilverStripe\Control\Controller;
10
use InvalidArgumentException;
11
use LogicException;
12
use phpDocumentor\Reflection\DocBlock\Tags\BaseTag;
13
use phpDocumentor\Reflection\DocBlock\Tags\Method;
14
use phpDocumentor\Reflection\DocBlock\Tags\Mixin;
15
use phpDocumentor\Reflection\DocBlock\Tags\Property;
16
use ReflectionClass;
17
use ReflectionException;
18
use ReflectionObject;
19
20
/**
21
 * Class DocBlockGenerator
22
 *
23
 * @package IDEAnnotator/Generators
24
 */
25
class DocBlockGenerator
26
{
27
    /**
28
     * The current class we are working with
29
     * @var string
30
     */
31
    protected $className = '';
32
33
    /**
34
     * @var ReflectionClass
35
     */
36
    protected $reflector;
37
38
    /**
39
     * @var AbstractTagGenerator
40
     */
41
    protected $tagGenerator;
42
43
    /**
44
     * @var DocBlockFactory
45
     */
46
    protected $docBlockFactory;
47
48
    /**
49
     * DocBlockGenerator constructor.
50
     *
51
     * @param $className
52
     * @throws ReflectionException
53
     * @throws InvalidArgumentException
54
     */
55
    public function __construct($className)
56
    {
57
        $this->className = $className;
58
        $this->reflector = new ReflectionClass($className);
59
        $this->docBlockFactory = DocBlockFactory::createInstance();
60
61
        $generatorClass = $this->reflector->isSubclassOf(Controller::class)
62
            ? ControllerTagGenerator::class : OrmTagGenerator::class;
63
64
        $this->tagGenerator = new $generatorClass($className, $this->getExistingTags());
65
    }
66
67
    /**
68
     * @return Tag[]
69
     * @throws InvalidArgumentException
70
     */
71
    public function getExistingTags()
72
    {
73
        $docBlock = $this->getExistingDocBlock();
74
        if (!$docBlock) {
75
            return [];
76
        }
77
78
        $docBlock = $this->docBlockFactory->create($docBlock);
79
80
        return $docBlock->getTags();
81
    }
82
83
    /**
84
     * Not that in case there are multiple doblocks for a class,
85
     * the last one will be returned
86
     *
87
     * If we file old style generated docblocks we remove them
88
     *
89
     * @return bool|string
90
     */
91
    public function getExistingDocBlock()
92
    {
93
        return $this->reflector->getDocComment();
94
    }
95
96
    /**
97
     * @return DocBlock|string
98
     * @throws LogicException
99
     * @throws InvalidArgumentException
100
     */
101
    public function getGeneratedDocBlock()
102
    {
103
        $docBlock = $this->getExistingDocBlock();
104
105
        return $this->mergeGeneratedTagsIntoDocBlock($docBlock);
106
    }
107
108
    /**
109
     * @param string $existingDocBlock
110
     * @return string
111
     * @throws LogicException
112
     * @throws InvalidArgumentException
113
     */
114
    protected function mergeGeneratedTagsIntoDocBlock($existingDocBlock)
115
    {
116
        $docBlock = $this->docBlockFactory->create(($existingDocBlock ?: "/**\n*/"));
117
118
        $summary = $docBlock->getSummary();
119
        if (!$summary) {
120
            $summary = sprintf('Class \\%s', $this->className);
121
        }
122
123
        $generatedTags = $this->getGeneratedTags();
124
        $mergedTags = [];
125
        foreach ($generatedTags as $generatedTag) {
126
            $currentTag = $docBlock->getTagsByName($generatedTag->getName())[0] ?? null;
127
128
            // If there is an existing tag with the same name, preserve its description
129
            // There is no setDescription method so we use reflection
130
            if ($currentTag && $currentTag instanceof BaseTag && $currentTag->getDescription()) {
131
                $refObject = new ReflectionObject($generatedTag);
132
                $refProperty = $refObject->getProperty('description');
133
                $refProperty->setAccessible(true);
134
                $refProperty->setValue($generatedTag, $currentTag->getDescription());
135
            }
136
            $mergedTags[] = $generatedTag;
137
        }
138
        foreach ($docBlock->getTags() as $existingTag) {
139
            // Skip any property, method or mixin tag
140
            if ($existingTag instanceof Property || $existingTag instanceof Method || $existingTag instanceof Mixin) {
141
                continue;
142
            }
143
            $mergedTags[] = $existingTag;
144
        }
145
146
        $docBlock = new DocBlock($summary, $docBlock->getDescription(), $mergedTags);
147
148
        $serializer = new Serializer();
149
        $docBlock = $serializer->getDocComment($docBlock);
150
151
        return $docBlock;
152
    }
153
154
    /**
155
     * Remove all existing tags that are supported by this module.
156
     *
157
     * This will make sure that removed ORM properties and Extenions will not remain in the docblock,
158
     * while providing the option to manually add docblocks like @author etc.
159
     *
160
     * @param $docBlock
161
     * @return string
162
     */
163
    public function removeExistingSupportedTags($docBlock)
164
    {
165
        $replacements = [
166
            "/ \* @property ([\s\S]*?)\n/",
167
            "/ \* @method ([\s\S]*?)\n/",
168
            "/ \* @mixin ([\s\S]*?)\n/"
169
        ];
170
171
        return (string)preg_replace($replacements, '', $docBlock);
172
    }
173
174
    /**
175
     * @return Tag[]
176
     */
177
    public function getGeneratedTags()
178
    {
179
        return $this->tagGenerator->getTags();
180
    }
181
}
182