DocBlockGenerator   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 48
c 5
b 1
f 0
dl 0
loc 158
rs 10
wmc 20

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A getExistingTags() 0 10 2
A getExistingDocBlock() 0 3 1
A getGeneratedDocBlock() 0 5 1
C mergeGeneratedTagsIntoDocBlock() 0 41 12
A removeExistingSupportedTags() 0 9 1
A getGeneratedTags() 0 3 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
                if ($refObject->hasProperty('description')) {
133
                    // If the property exists, we can set it
134
                    $refProperty = $refObject->getProperty('description');
135
                    $refProperty->setAccessible(true);
136
                    $refProperty->setValue($generatedTag, $currentTag->getDescription());
137
                }
138
            }
139
            $mergedTags[] = $generatedTag;
140
        }
141
        foreach ($docBlock->getTags() as $existingTag) {
142
            // Skip any property, method or mixin tag
143
            if ($existingTag instanceof Property || $existingTag instanceof Method || $existingTag instanceof Mixin) {
144
                continue;
145
            }
146
            $mergedTags[] = $existingTag;
147
        }
148
149
        $docBlock = new DocBlock($summary, $docBlock->getDescription(), $mergedTags);
150
151
        $serializer = new Serializer();
152
        $docBlock = $serializer->getDocComment($docBlock);
153
154
        return $docBlock;
155
    }
156
157
    /**
158
     * Remove all existing tags that are supported by this module.
159
     *
160
     * This will make sure that removed ORM properties and Extenions will not remain in the docblock,
161
     * while providing the option to manually add docblocks like @author etc.
162
     *
163
     * @param $docBlock
164
     * @return string
165
     */
166
    public function removeExistingSupportedTags($docBlock)
167
    {
168
        $replacements = [
169
            "/ \* @property ([\s\S]*?)\n/",
170
            "/ \* @method ([\s\S]*?)\n/",
171
            "/ \* @mixin ([\s\S]*?)\n/"
172
        ];
173
174
        return (string)preg_replace($replacements, '', $docBlock);
175
    }
176
177
    /**
178
     * @return Tag[]
179
     */
180
    public function getGeneratedTags()
181
    {
182
        return $this->tagGenerator->getTags();
183
    }
184
}
185