Passed
Push — master ( 1fb040...5fddea )
by Satoshi
02:25
created

ExtraPhpDocTagResolver::resolveDepsInternalTag()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace DependencyAnalyzer\DependencyGraphBuilder;
5
6
use DependencyAnalyzer\DependencyDumper;
7
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTags\DepsInternal;
8
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTags\Internal;
9
use DependencyAnalyzer\DependencyGraph\FullyQualifiedStructuralElementName as FQSEN;
10
use DependencyAnalyzer\Exceptions\InvalidFullyQualifiedStructureElementNameException;
11
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
12
use PHPStan\PhpDocParser\Lexer\Lexer;
13
use PHPStan\PhpDocParser\Parser\PhpDocParser;
14
use PHPStan\PhpDocParser\Parser\TokenIterator;
15
use PHPStan\Reflection\ClassReflection;
16
17
class ExtraPhpDocTagResolver
18
{
19
    const ONLY_USED_BY_TAGS = '@canOnlyUsedBy';
20
    const DEPENDER_TAGS = '@dependee';
21
    const DEPENDEE_TAGS = '@dependee';
22
23
    /**
24
     * @var Lexer
25
     */
26
    protected $phpDocLexer;
27
28
    /**
29
     * @var PhpDocParser
30
     */
31
    protected $phpDocParser;
32
33
    /**
34
     * @var ObserverInterface
35
     */
36
    protected $observer = null;
37
38
    public function __construct(Lexer $phpDocLexer, PhpDocParser $phpDocParser)
39
    {
40
        $this->phpDocLexer = $phpDocLexer;
41
        $this->phpDocParser = $phpDocParser;
42
    }
43
44
    public function setObserver(ObserverInterface $observer): void
45
    {
46
        $this->observer = $observer;
47
    }
48
49
    protected function notifyError(string $file, string $fqsen, InvalidFullyQualifiedStructureElementNameException $exception)
50
    {
51
        if (!is_null($this->observer)) {
52
            $this->observer->notifyResolvePhpDocError($file, $fqsen, $exception);
53
        }
54
    }
55
56
    /**
57
     * @param \ReflectionClass $reflectionClass
58
     * @return DepsInternal[]
59
     */
60
    public function resolveDepsInternalTag(\ReflectionClass $reflectionClass): array
61
    {
62
        $ret = [];
63
64
        foreach ($this->collectReflectionsHavingTag($reflectionClass, DepsInternal::getTagName()) as $reflection) {
65
            try {
66
                $ret[] = new DepsInternal(
67
                    FQSEN::createFromReflection($reflection),
68
                    $this->resolve($reflection->getDocComment(), DepsInternal::getTagName())
69
                );
70
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
71
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
72
            }
73
        }
74
75
        return $ret;
76
    }
77
78
    /**
79
     * @param \ReflectionClass $reflectionClass
80
     * @return Internal[]
81
     */
82
    public function resolveInternalTag(\ReflectionClass $reflectionClass): array
83
    {
84
        $ret = [];
85
86
        foreach ($this->collectReflectionsHavingTag($reflectionClass, Internal::getTagName()) as $reflection) {
87
            try {
88
                $ret[] = new Internal(FQSEN::createFromReflection($reflection));
89
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
90
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
91
            }
92
        }
93
94
        return $ret;
95
    }
96
97
    protected function collectReflectionsHavingTag(\ReflectionClass $reflectionClass, string $tagName): array
98
    {
99
        $ret = [];
100
101
        if ($this->haveTag($reflectionClass, $tagName)) {
102
            $ret[] = $reflectionClass;
103
        }
104
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
105
            if ($this->haveTag($reflectionProperty, $tagName)) {
106
                $ret[] = $reflectionProperty;
107
            }
108
        }
109
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
110
            if ($this->haveTag($reflectionMethod, DepsInternal::getTagName())) {
111
                $ret[] = $reflectionMethod;
112
            }
113
        }
114
        foreach ($reflectionClass->getReflectionConstants() as $reflectionClassConstant) {
115
            if ($this->haveTag($reflectionClassConstant, DepsInternal::getTagName())) {
116
                $ret[] = $reflectionClassConstant;
117
            }
118
        }
119
120
        return $ret;
121
    }
122
123
124
//    protected function resolveUsesTag(\ReflectionClass $reflectionClass)
125
//    {
126
//        $reflectionClass->getDocComment();
127
//    }
128
129
    /**
130
     * @param \ReflectionClass $reflectionClass
131
     * @return DepsInternal[]
132
     */
133
    public function resolveCanOnlyUsedByTag(\ReflectionClass $reflectionClass): array
134
    {
135
        $ret = [];
136
        if ($this->haveTag($reflectionClass, self::ONLY_USED_BY_TAGS)) {
137
            try {
138
                $ret[] = new DepsInternal(
139
                    FQSEN::createClass($reflectionClass->getName()),
140
                    $this->resolve($reflectionClass->getDocComment(), self::ONLY_USED_BY_TAGS)
0 ignored issues
show
Bug introduced by
It seems like $reflectionClass->getDocComment() can also be of type boolean; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
                    $this->resolve(/** @scrutinizer ignore-type */ $reflectionClass->getDocComment(), self::ONLY_USED_BY_TAGS)
Loading history...
141
                );
142
            } catch (InvalidFullyQualifiedStructureElementNameException $e) {
143
                $this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e);
144
            }
145
        }
146
147
        return $ret;
148
    }
149
150
    /**
151
     * @param ClassReflection $classReflection
152
     * @return string[]
153
     */
154
    public function resolveDependerTag(ClassReflection $classReflection): array
155
    {
156
        if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) {
157
            return $this->resolve($phpdoc, self::DEPENDER_TAGS);
0 ignored issues
show
Bug introduced by
It seems like $phpdoc can also be of type true; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
            return $this->resolve(/** @scrutinizer ignore-type */ $phpdoc, self::DEPENDER_TAGS);
Loading history...
158
        }
159
160
        return [];
161
    }
162
163
    /**
164
     * @param ClassReflection $classReflection
165
     * @return string[]
166
     */
167
    public function resolveDependeeTag(ClassReflection $classReflection): array
168
    {
169
        if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) {
170
            return $this->resolve($phpdoc, self::DEPENDEE_TAGS);
0 ignored issues
show
Bug introduced by
It seems like $phpdoc can also be of type true; however, parameter $phpdoc of DependencyAnalyzer\Depen...cTagResolver::resolve() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
            return $this->resolve(/** @scrutinizer ignore-type */ $phpdoc, self::DEPENDEE_TAGS);
Loading history...
171
        }
172
173
        return [];
174
    }
175
176
    protected function haveTag(\Reflector $reflector, string $tag): bool
177
    {
178
        if (!method_exists($reflector, 'getDocComment') && $reflector->getDocComment() !== false) {
0 ignored issues
show
Bug introduced by
The method getDocComment() does not exist on Reflector. It seems like you code against a sub-type of said class. However, the method does not exist in ReflectionExtension or ReflectionZendExtension or ReflectionParameter. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

178
        if (!method_exists($reflector, 'getDocComment') && $reflector->/** @scrutinizer ignore-call */ getDocComment() !== false) {
Loading history...
179
            return false;
180
        } elseif (($docComment = $reflector->getDocComment()) === false) {
0 ignored issues
show
Unused Code introduced by
The assignment to $docComment is dead and can be removed.
Loading history...
181
            return false;
182
    }
183
184
        $tokens = new TokenIterator($this->phpDocLexer->tokenize($reflector->getDocComment()));
185
        $phpDocNode = $this->phpDocParser->parse($tokens);
186
187
        foreach ($phpDocNode->getTagsByName($tag) as $phpDocTagNode) {
188
            /** @var PhpDocTagNode $phpDocTagNode */
189
            if (preg_match('/^' . $tag . '/', $phpDocTagNode->__toString()) === 1) {
190
                return true;
191
            }
192
        }
193
194
        return false;
195
    }
196
197
    protected function resolve(string $phpdoc, string $tag): array
198
    {
199
        $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpdoc));
200
        $phpDocNode = $this->phpDocParser->parse($tokens);
201
202
        $ret = [];
203
        foreach ($phpDocNode->getTagsByName($tag) as $phpDocTagNode) {
204
            /** @var PhpDocTagNode $phpDocTagNode */
205
            if (preg_match('/^' . $tag . '\s+(.+)$/', $phpDocTagNode->__toString(), $matches) === 1) {
206
                $ret[] = $matches[1];
207
            }
208
        };
209
210
        return $ret;
211
    }
212
}
213