Failed Conditions
Push — new-parser-ast-metadata ( e8e091...17d881 )
by Michael
21s
created

NewAnnotationReader   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Test Coverage

Coverage 76%

Importance

Changes 0
Metric Value
wmc 21
eloc 96
dl 0
loc 229
ccs 76
cts 100
cp 0.76
rs 10
c 0
b 0
f 0

11 Methods

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

229
        $ast   = $this->compiler->compile(/** @scrutinizer ignore-type */ $docComment);
Loading history...
230
231 1
        $this->metadataBuilder->collect($ast, $scope, $this->metadataCollection);
232
233 1
        $internalOnPublic = $this->prePublicAssembler->collect(
234 1
            $ast,
235 1
            new Scope($subject, InternalAnnotations::createImports(), new IgnoredAnnotations())
236
        );
237 1
        foreach ($internalOnPublic as $internalAnnotation) {
238
            if (! $internalAnnotation instanceof IgnoreAnnotation) {
239
                continue;
240
            }
241
242
            $scope->getIgnoredAnnotations()->add(...$internalAnnotation->names);
243
        }
244
245 1
        $scope->getIgnoredAnnotations()->add('IgnoreAnnotation');
246 1
        $scope->getIgnoredAnnotations()->add(IgnoreAnnotation::class);
247
248 1
        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...
249 1
    }
250
251 1
    private function createScope(Reflector $subject) : Scope
252
    {
253 1
        return new Scope(
254 1
            $subject,
255 1
            $this->collectImports($subject),
256 1
            new IgnoredAnnotations(...ImplicitIgnoredAnnotationNames::LIST)
257
        );
258
    }
259
260 1
    private function collectImports(Reflector $subject) : Imports
261
    {
262 1
        if ($subject instanceof ReflectionClass) {
263 1
            return new Imports($this->usesParser->parseClass($subject));
264
        }
265
266
        if ($subject instanceof ReflectionMethod || $subject instanceof ReflectionProperty) {
267
            return new Imports($this->usesParser->parseClass($subject->getDeclaringClass()));
268
        }
269
270
        // TODO also support standalone functions
271
        return new Imports([]);
272
    }
273
}
274