Failed Conditions
Push — new-parser-ast-metadata ( 704204...1e15b6 )
by Michael
02:04
created

NewAnnotationReader::getMethodAnnotations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Annotations;
6
7
use Doctrine\Annotations\Annotation\IgnoreAnnotation;
8
use Doctrine\Annotations\Assembler\Acceptor\CompositeAcceptor;
9
use Doctrine\Annotations\Assembler\Acceptor\IgnoredAcceptor;
10
use Doctrine\Annotations\Assembler\Acceptor\InternalAcceptor;
11
use Doctrine\Annotations\Assembler\Acceptor\NegatedAcceptor;
12
use Doctrine\Annotations\Assembler\Assembler;
13
use Doctrine\Annotations\Constructor\Constructor;
14
use Doctrine\Annotations\Constructor\Instantiator\ConstructorInstantiatorStrategy;
15
use Doctrine\Annotations\Constructor\Instantiator\Instantiator;
16
use Doctrine\Annotations\Constructor\Instantiator\PropertyInstantiatorStrategy;
17
use Doctrine\Annotations\Metadata\Assembler\AnnotationMetadataAssembler;
18
use Doctrine\Annotations\Metadata\InternalAnnotations;
19
use Doctrine\Annotations\Metadata\MetadataCollection;
20
use Doctrine\Annotations\Metadata\MetadataCollector;
21
use Doctrine\Annotations\Metadata\Reflection\ClassReflectionProvider;
22
use Doctrine\Annotations\Metadata\ScopeManufacturer;
23
use Doctrine\Annotations\Parser\Compiler;
24
use Doctrine\Annotations\Parser\IgnoredAnnotations;
25
use Doctrine\Annotations\Parser\Imports;
26
use Doctrine\Annotations\Parser\Reference\FallbackReferenceResolver;
27
use Doctrine\Annotations\Parser\Reference\StaticReferenceResolver;
28
use Doctrine\Annotations\Parser\Scope;
29
use Doctrine\Annotations\TypeParser\PHPStanTypeParser;
30
use PHPStan\PhpDocParser\Lexer\Lexer;
31
use PHPStan\PhpDocParser\Parser\ConstExprParser;
32
use PHPStan\PhpDocParser\Parser\PhpDocParser;
33
use PHPStan\PhpDocParser\Parser\TypeParser;
34
use ReflectionClass;
35
use ReflectionFunctionAbstract;
36
use ReflectionMethod;
37
use ReflectionProperty;
38
use Reflector;
39
use function assert;
40
use function iterator_to_array;
41
42
final class NewAnnotationReader implements Reader
43
{
44
    /** @var MetadataCollection */
45
    private $metadataCollection;
46
47
    /** @var ClassReflectionProvider */
48
    private $reflectionProvider;
49
50
    /** @var PhpParser */
51
    private $usesParser;
52
53
    /** @var Compiler */
54
    private $compiler;
55
56
    /** @var Constructor */
57
    private $constructor;
58
59
    /** @var AnnotationMetadataAssembler */
60
    private $metadataAssembler;
61
62
    /** @var MetadataCollector */
63
    private $metadataBuilder;
64
65
    /** @var Assembler */
66
    private $prePublicAssembler;
67
68
    /** @var Assembler */
69
    private $publicAnnotationAssembler;
70
71
    public function __construct(
72
        MetadataCollection $metadataCollection,
73
        ClassReflectionProvider $reflectionProvider
74
    ) {
75
        $this->metadataCollection = $metadataCollection;
76
        $this->reflectionProvider = $reflectionProvider;
77
        $this->usesParser         = new PhpParser();
78
        $this->compiler           = new Compiler();
79
        $this->constructor        = new Constructor(
80
            new Instantiator(
81
                new ConstructorInstantiatorStrategy(),
82
                new PropertyInstantiatorStrategy()
83
            )
84
        );
85
86
        $fallbackReferenceResolver = new FallbackReferenceResolver();
87
        $staticReferenceResolver   = new StaticReferenceResolver();
88
89
        $this->metadataAssembler = new AnnotationMetadataAssembler(
90
            $this->compiler,
91
            $fallbackReferenceResolver,
92
            $this->reflectionProvider,
93
            new PHPStanTypeParser(
94
                new Lexer(),
95
                new PhpDocParser(new TypeParser(), new ConstExprParser()),
96
                $fallbackReferenceResolver
97
            ),
98
            new ScopeManufacturer($this->usesParser),
99
            new Assembler(
100
                $this->metadataCollection,
101
                $staticReferenceResolver,
102
                $this->constructor,
103
                $this->reflectionProvider,
104
                new InternalAcceptor($staticReferenceResolver)
105
            )
106
        );
107
108
        $this->metadataBuilder = new MetadataCollector(
109
            $this->metadataAssembler,
110
            new NegatedAcceptor(
111
                new IgnoredAcceptor($fallbackReferenceResolver)
112
            ),
113
            $fallbackReferenceResolver
114
        );
115
116
        $this->prePublicAssembler = new Assembler(
117
            $this->metadataCollection,
118
            $staticReferenceResolver,
119
            $this->constructor,
120
            $this->reflectionProvider,
121
            new InternalAcceptor($staticReferenceResolver)
122
        );
123
124
        $this->publicAnnotationAssembler = new Assembler(
125
            $this->metadataCollection,
126
            $fallbackReferenceResolver,
127
            $this->constructor,
128
            $this->reflectionProvider,
129
            new CompositeAcceptor(
130
                new NegatedAcceptor(new IgnoredAcceptor($fallbackReferenceResolver)),
131
                new NegatedAcceptor(new InternalAcceptor($staticReferenceResolver))
132
            )
133
        );
134
    }
135
136
    /**
137
     * @return object[]
138
     */
139
    public function getClassAnnotations(ReflectionClass $class) : iterable
140
    {
141
        return iterator_to_array($this->collectAnnotations($class), false);
142
    }
143
144
    /**
145
     * @param string $annotationName
146
     *
147
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
148
     */
149
    public function getClassAnnotation(ReflectionClass $class, $annotationName) : ?object
150
    {
151
        return $this->getFirstAnnotationOfType($this->getClassAnnotations($class), $annotationName);
152
    }
153
154
    /**
155
     * @return object[]
156
     */
157
    public function getMethodAnnotations(ReflectionMethod $method) : iterable
158
    {
159
        return iterator_to_array($this->collectAnnotations($method), false);
160
    }
161
162
    /**
163
     * @param string $annotationName
164
     *
165
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
166
     */
167
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName) : ?object
168
    {
169
        return $this->getFirstAnnotationOfType($this->getMethodAnnotations($method), $annotationName);
170
    }
171
172
    /**
173
     * @return object[]
174
     */
175
    public function getPropertyAnnotations(ReflectionProperty $property) : iterable
176
    {
177
        return iterator_to_array($this->collectAnnotations($property), false);
178
    }
179
180
    /**
181
     * @param string $annotationName
182
     *
183
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
184
     */
185
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) : ?object
186
    {
187
        return $this->getFirstAnnotationOfType($this->getPropertyAnnotations($property), $annotationName);
188
    }
189
190
    /**
191
     * @param object[] $annotations
192
     */
193
    private function getFirstAnnotationOfType(iterable $annotations, string $desiredType) : ?object
194
    {
195
        foreach ($annotations as $annotation) {
196
            if (! $annotation instanceof $desiredType) {
197
                continue;
198
            }
199
200
            return $annotation;
201
        }
202
203
        return null;
204
    }
205
206
    /**
207
     * @return object[]
208
     */
209
    private function collectAnnotations(Reflector $subject) : iterable
210
    {
211
        assert(
212
            $subject instanceof ReflectionClass
213
            || $subject instanceof ReflectionProperty
214
            || $subject instanceof ReflectionFunctionAbstract
215
        );
216
217
        $docComment = $subject->getDocComment();
218
219
        if ($docComment === false) {
220
            return [];
221
        }
222
223
        $scope = $this->createScope($subject);
224
        $ast   = $this->compiler->compile($docComment);
0 ignored issues
show
Bug introduced by
It seems like $docComment can also be of type true; however, parameter $docblock of Doctrine\Annotations\Parser\Compiler::compile() 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

224
        $ast   = $this->compiler->compile(/** @scrutinizer ignore-type */ $docComment);
Loading history...
225
226
        $this->metadataBuilder->collect($ast, $scope, $this->metadataCollection);
227
228
        $internalOnPublic = $this->prePublicAssembler->collect(
229
            $ast,
230
            new Scope($subject, InternalAnnotations::createImports(), new IgnoredAnnotations())
231
        );
232
        foreach ($internalOnPublic as $internalAnnotation) {
233
            if (! $internalAnnotation instanceof IgnoreAnnotation) {
234
                continue;
235
            }
236
237
            $scope->getIgnoredAnnotations()->add(...$internalAnnotation->names);
238
        }
239
240
        $scope->getIgnoredAnnotations()->add('IgnoreAnnotation');
241
        $scope->getIgnoredAnnotations()->add(IgnoreAnnotation::class);
242
243
        yield from $this->publicAnnotationAssembler->collect($ast, $scope);
0 ignored issues
show
Bug Best Practice introduced by
The expression YieldFromNode returns the type Generator which is incompatible with the documented return type array<mixed,object>.
Loading history...
244
    }
245
246
    private function createScope(Reflector $subject) : Scope
247
    {
248
        return new Scope(
249
            $subject,
250
            $this->collectImports($subject),
251
            new IgnoredAnnotations(...ImplicitIgnoredAnnotationNames::LIST)
252
        );
253
    }
254
255
    private function collectImports(Reflector $subject) : Imports
256
    {
257
        if ($subject instanceof ReflectionClass) {
258
            return new Imports($this->usesParser->parseClass($subject));
259
        }
260
261
        if ($subject instanceof ReflectionMethod || $subject instanceof ReflectionProperty) {
262
            return new Imports($this->usesParser->parseClass($subject->getDeclaringClass()));
263
        }
264
265
        // TODO also support standalone functions
266
        return new Imports([]);
267
    }
268
}
269