Failed Conditions
Pull Request — new-parser-ast-metadata (#2)
by
unknown
02:16
created

DefaultAnnotationMetadataAssembler::assemble()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 24
ccs 17
cts 17
cp 1
rs 9.7333
c 0
b 0
f 0
cc 3
nc 1
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Annotations\Metadata\Assembler;
6
7
use Doctrine\Annotations\Annotation\Annotation as AnnotationAnnotation;
8
use Doctrine\Annotations\Annotation\Required as RequiredAnnotation;
9
use Doctrine\Annotations\Annotation\Target as TargetAnnotation;
10
use Doctrine\Annotations\Annotation\Target;
11
use Doctrine\Annotations\Assembler\Assembler;
12
use Doctrine\Annotations\Metadata\AnnotationMetadata;
13
use Doctrine\Annotations\Metadata\AnnotationTarget;
14
use Doctrine\Annotations\Metadata\InternalAnnotations;
15
use Doctrine\Annotations\Metadata\PropertyMetadata;
16
use Doctrine\Annotations\Metadata\Reflection\ClassReflectionProvider;
17
use Doctrine\Annotations\Metadata\ScopeManufacturer;
18
use Doctrine\Annotations\Metadata\Type\MixedType;
19
use Doctrine\Annotations\Metadata\Type\NullType;
20
use Doctrine\Annotations\Metadata\Type\Type;
21
use Doctrine\Annotations\Metadata\Type\UnionType;
22
use Doctrine\Annotations\TypeParser\PHPStanTypeParser;
23
use Doctrine\Annotations\Parser\Ast\Annotations;
24
use Doctrine\Annotations\Parser\Ast\Reference;
25
use Doctrine\Annotations\Parser\Compiler;
26
use Doctrine\Annotations\Parser\Reference\ReferenceResolver;
27
use Doctrine\Annotations\Parser\Scope;
28
use Doctrine\Annotations\TypeParser\TypeParser;
29
use ReflectionClass;
30
use ReflectionProperty;
31
use function assert;
32
use function is_array;
33
use function iterator_to_array;
34
use function stripos;
35
36
final class DefaultAnnotationMetadataAssembler implements AnnotationMetadataAssembler
37
{
38
    /** @var Compiler */
39
    private $parser;
40
41
    /** @var ReferenceResolver */
42
    private $referenceResolver;
43
44
    /** @var ClassReflectionProvider */
45
    private $classReflectionProvider;
46
47
    /** @var TypeParser */
48
    private $typeParser;
49
50
    /** @var ScopeManufacturer */
51
    private $scopeManufacturer;
52
53
    /** @var Assembler */
54
    private $internalAssembler;
55
56 6
    public function __construct(
57
        Compiler $parser,
58
        ReferenceResolver $referenceResolver,
59
        ClassReflectionProvider $classReflectionProvider,
60
        TypeParser $typeParser,
61
        ScopeManufacturer $scopeManufacturer,
62
        Assembler $internalAssembler
63
    ) {
64 6
        $this->parser                  = $parser;
65 6
        $this->referenceResolver       = $referenceResolver;
66 6
        $this->classReflectionProvider = $classReflectionProvider;
67 6
        $this->typeParser              = $typeParser;
68 6
        $this->scopeManufacturer       = $scopeManufacturer;
69 6
        $this->internalAssembler       = $internalAssembler;
70 6
    }
71
72 6
    public function assemble(Reference $reference, Scope $scope) : AnnotationMetadata
73
    {
74 6
        $realName        = $this->referenceResolver->resolve($reference, $scope);
75 6
        $classReflection = $this->classReflectionProvider->getClassReflection($realName);
76 6
        $docComment      = $classReflection->getDocComment();
77 6
        $hasConstructor  = $classReflection->getConstructor() !== null;
78
79 6
        assert($docComment !== false, 'not an annotation');
80
81 6
        $annotations = $this->parser->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

81
        $annotations = $this->parser->compile(/** @scrutinizer ignore-type */ $docComment);
Loading history...
82
83 6
        assert(stripos($docComment, '@') !== false);
0 ignored issues
show
Bug introduced by
It seems like $docComment can also be of type true; however, parameter $haystack of stripos() 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

83
        assert(stripos(/** @scrutinizer ignore-type */ $docComment, '@') !== false);
Loading history...
84
85 6
        $hydratedAnnotations = $this->hydrateInternalAnnotations($annotations, $scope);
86
87 6
        assert($this->findAnnotation(AnnotationAnnotation::class, $hydratedAnnotations) !== null, 'not annotated with @Annotation');
88 6
        assert(! $hasConstructor || $classReflection->getConstructor()->isPublic(), 'constructor must be public');
89 6
        assert(! $hasConstructor || $classReflection->getConstructor()->getNumberOfParameters() === 1, 'constructor must accept a single parameter');
90
91 6
        return new AnnotationMetadata(
92 6
            $realName,
93 6
            $this->determineTarget($hydratedAnnotations),
94 6
            $hasConstructor,
95 6
            $this->assembleProperties($classReflection)
96
        );
97
    }
98
99
    /**
100
     * @param object[] $annotations
101
     */
102 6
    private function determineTarget(array $annotations) : AnnotationTarget
103
    {
104
        /** @var TargetAnnotation|null $target */
105 6
        $target = $this->findAnnotation(TargetAnnotation::class, $annotations);
106
107 6
        if ($target === null) {
108
            return new AnnotationTarget(AnnotationTarget::TARGET_ALL);
109
        }
110
111 6
        return new AnnotationTarget($target->targets);
112
113
    }
114
115
    /**
116
     * @return object[]
117
     */
118 6
    private function hydrateInternalAnnotations(Annotations $annotations, Scope $scope) : array
119
    {
120 6
        $assembled = $this->internalAssembler->collect(
121 6
            $annotations,
122 6
            new Scope($scope->getSubject(), InternalAnnotations::createImports(), clone $scope->getIgnoredAnnotations())
123
        );
124
125 6
        return is_array($assembled) ? $assembled : iterator_to_array($assembled);
126
    }
127
128
    /**
129
     * @param object[] $annotations
130
     */
131 6
    private function findAnnotation(string $name, array $annotations) : ?object
132
    {
133 6
        foreach ($annotations as $annotation) {
134 6
            if (! $annotation instanceof $name) {
135 6
                continue;
136
            }
137
138 6
            return $annotation;
139
        }
140
141 2
        return null;
142
    }
143
144
    /**
145
     * @return PropertyMetadata[]
146
     */
147 6
    private function assembleProperties(ReflectionClass $class) : array
148
    {
149 6
        $metadatas = [];
150
151 6
        foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $i => $property) {
152 5
            $metadatas[] = $this->assembleProperty($property, $i === 0);
153
        }
154
155 6
        return $metadatas;
156
    }
157
158 5
    private function assembleProperty(ReflectionProperty $property, bool $first) : PropertyMetadata
159
    {
160 5
        $docBlock = $property->getDocComment();
161
162 5
        if ($docBlock === false) {
163 2
            return new PropertyMetadata(
164 2
                $property->getName(),
165 2
                new MixedType(),
166 2
                $first
167
            );
168
        }
169
170 3
        $scope               = $this->scopeManufacturer->manufacturePropertyScope($property);
171 3
        $hydratedAnnotations = $this->hydrateInternalAnnotations($this->parser->compile($docBlock), $scope);
0 ignored issues
show
Bug introduced by
It seems like $docBlock 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

171
        $hydratedAnnotations = $this->hydrateInternalAnnotations($this->parser->compile(/** @scrutinizer ignore-type */ $docBlock), $scope);
Loading history...
172
173 3
        $required = $this->findAnnotation(RequiredAnnotation::class, $hydratedAnnotations) !== null;
174
175 3
        $type = $this->typeParser->parsePropertyType($property->getDocComment(), $scope);
0 ignored issues
show
Bug introduced by
It seems like $property->getDocComment() can also be of type true; however, parameter $docBlock of Doctrine\Annotations\Typ...er::parsePropertyType() 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

175
        $type = $this->typeParser->parsePropertyType(/** @scrutinizer ignore-type */ $property->getDocComment(), $scope);
Loading history...
176
177 3
        if ($required && ! $type->acceptsNull()) {
178
            // TODO: throw deprecated warning?
179 1
            $type = new UnionType($type, new NullType());
180
        }
181
182 3
        return new PropertyMetadata($property->getName(), $type, $first);
183
    }
184
}
185