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

hydrateInternalAnnotations()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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

79
        $annotations = $this->parser->compile(/** @scrutinizer ignore-type */ $docComment);
Loading history...
80
81 7
        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

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

167
        $hydratedAnnotations = $this->hydrateInternalAnnotations($this->parser->compile(/** @scrutinizer ignore-type */ $docBlock), $scope);
Loading history...
168
169
        /** @var RequiredAnnotation|null $required */
170 4
        $required = $this->findAnnotation(RequiredAnnotation::class, $hydratedAnnotations);
171
        /** @var Enum|null $enum */
172 4
        $enum = $this->findAnnotation(Enum::class, $hydratedAnnotations);
173
174 4
        $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

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