AnnotatedEntityConfigReader   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 17
eloc 66
dl 0
loc 191
c 0
b 0
f 0
rs 10
ccs 70
cts 70
cp 1

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getPropertyValidationContext() 0 3 1
A getClassMarkers() 0 8 1
A enrich() 0 5 1
A getCreator() 0 11 1
A __construct() 0 15 2
A getPropertyMarkersRecursive() 0 17 3
A getPropertyAnnotationsOfType() 0 32 3
A getEntityConfig() 0 10 1
A getPropertyMarkersForClass() 0 14 4
1
<?php
2
/**
3
 * File was created 06.10.2015 06:22
4
 */
5
6
namespace PeekAndPoke\Component\Slumber\Core\LookUp;
7
8
use Doctrine\Common\Annotations\AnnotationRegistry;
9
use Doctrine\Common\Annotations\Reader;
10
use PeekAndPoke\Component\Creator\Creator;
11
use PeekAndPoke\Component\Creator\CreatorFactory;
12
use PeekAndPoke\Component\Creator\CreatorFactoryImpl;
13
use PeekAndPoke\Component\PropertyAccess\PropertyAccessFactory;
14
use PeekAndPoke\Component\PropertyAccess\PropertyAccessFactoryImpl;
15
use PeekAndPoke\Component\Psi\Psi;
16
use PeekAndPoke\Component\Slumber\Annotation\ClassCreatorMarker;
17
use PeekAndPoke\Component\Slumber\Annotation\ClassMarker;
18
use PeekAndPoke\Component\Slumber\Annotation\PropertyMappingMarker;
19
use PeekAndPoke\Component\Slumber\Annotation\PropertyMarker;
20
use PeekAndPoke\Component\Slumber\Core\Exception\SlumberException;
21
use PeekAndPoke\Component\Slumber\Core\Validation\ClassAnnotationValidationContext;
22
use PeekAndPoke\Component\Slumber\Core\Validation\PropertyAnnotationValidationContext;
23
use Psr\Container\ContainerInterface;
24
25
/**
26
 * @author Karsten J. Gerber <[email protected]>
27
 */
28
class AnnotatedEntityConfigReader implements EntityConfigReader
29
{
30
    /** @var Reader */
31
    private $annotationReader;
32
    /** @var ContainerInterface */
33
    private $serviceProvider;
34
    /** @var PropertyMarker2Mapper */
35
    private $mappings;
36
    /** @var CreatorFactory */
37
    private $creatorFactory;
38
    /** @var PropertyAccessFactory */
39
    private $propertyAccessFactory;
40
41
    /**
42
     * @param ContainerInterface    $serviceProvider
43
     * @param Reader                $annotationReader
44
     * @param PropertyMarker2Mapper $mappings
45
     */
46 394
    public function __construct(ContainerInterface $serviceProvider, Reader $annotationReader, PropertyMarker2Mapper $mappings)
47
    {
48 394
        static $autoloader = false;
49
50 394
        if ($autoloader === false) {
51 1
            $autoloader = true;
52 1
            AnnotationRegistry::registerUniqueLoader('class_exists');
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Common\Annotati...:registerUniqueLoader() has been deprecated: this method is deprecated and will be removed in doctrine/annotations 2.0 ( Ignorable by Annotation )

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

52
            /** @scrutinizer ignore-deprecated */ AnnotationRegistry::registerUniqueLoader('class_exists');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
53
        }
54
55 394
        $this->annotationReader = $annotationReader;
56 394
        $this->serviceProvider  = $serviceProvider;
57 394
        $this->mappings         = $mappings;
58
59 394
        $this->creatorFactory        = new CreatorFactoryImpl();
60 394
        $this->propertyAccessFactory = new PropertyAccessFactoryImpl();
61 394
    }
62
63
    /**
64
     * @param \ReflectionClass $subject
65
     *
66
     * @return EntityConfig
67
     * @throws SlumberException
68
     */
69 38
    public function getEntityConfig(\ReflectionClass $subject)
70
    {
71 38
        $config = new EntityConfig(
72 38
            $subject->name,
73 38
            $this->getCreator($subject),
74 38
            $this->getClassMarkers($subject),
75 38
            $this->getPropertyMarkersRecursive($subject)
76
        );
77
78 38
        return $config;
79
    }
80
81
    /**
82
     * @param PropertyMarkedForSlumber $marked
83
     *
84
     * @return PropertyMarkedForSlumber
85
     */
86 36
    protected function enrich(PropertyMarkedForSlumber $marked)
87
    {
88 36
        $marked->mapper = $this->mappings->createMapper($marked->marker);
89
90 36
        return $marked;
91
    }
92
93
    /**
94
     * @param \ReflectionClass $subject
95
     *
96
     * @return Creator
97
     */
98 38
    private function getCreator(\ReflectionClass $subject) : Creator
99
    {
100 38
        $validationContext = new ClassAnnotationValidationContext($this->serviceProvider, $subject);
101
102 38
        return Psi::it($this->annotationReader->getClassAnnotations($subject))
103 38
            ->filter(new Psi\IsInstanceOf(ClassCreatorMarker::class))
104 38
            ->each($validationContext)
105
            // map the first one to a Creator
106
            ->map(function (ClassCreatorMarker $marker) { return $marker->getCreator($this->creatorFactory); })
107
            // or get the default creator
108 38
            ->getFirst($this->creatorFactory->create($subject));
109
    }
110
111
    /**
112
     * @param \ReflectionClass $subject
113
     *
114
     * @return ClassMarker[]
115
     */
116 38
    private function getClassMarkers(\ReflectionClass $subject) : array
117
    {
118 38
        $validationContext = new ClassAnnotationValidationContext($this->serviceProvider, $subject);
119
120 38
        return Psi::it($this->annotationReader->getClassAnnotations($subject))
121 38
            ->filter(new Psi\IsInstanceOf(ClassMarker::class))
122 38
            ->each($validationContext)
123 38
            ->toArray();
124
    }
125
126
    /**
127
     * @param \ReflectionClass $subject
128
     *
129
     * @return PropertyMarkedForSlumber[]
130
     */
131 38
    private function getPropertyMarkersRecursive(\ReflectionClass $subject) : array
132
    {
133
        /** @var PropertyMarkedForSlumber[] $result */
134 38
        $result = [];
135
136 38
        $base = $subject;
137
138
        // We climb up the inheritance ladder. Be doing so we can also capture private properties of base classes
139
        // that are NOT shadowed by the inheriting classes.
140 38
        while ($base instanceof \ReflectionClass && $base->isUserDefined()) {
141
142 36
            $this->getPropertyMarkersForClass($subject, $base, $result);
143
144 36
            $base = $base->getParentClass();
145
        }
146
147 38
        return array_values($result);
148
    }
149
150 36
    private function getPropertyMarkersForClass(\ReflectionClass $subject, \ReflectionClass $base, array &$result) : void
151
    {
152 36
        $properties = $base->getProperties();
153
154 36
        foreach ($properties as $property) {
155
156 36
            $propertyName = $property->getName();
157
158 36
            if (! isset($result[$propertyName])) {
159
160 36
                $marker = $this->getPropertyAnnotationsOfType($subject, $base, $property);
161
162 36
                if ($marker) {
163 36
                    $result[$propertyName] = $this->enrich($marker);
164
                }
165
            }
166
        }
167 36
    }
168
169
    /**
170
     * @param \ReflectionClass    $cls
171
     * @param \ReflectionProperty $property
172
     *
173
     * @return PropertyAnnotationValidationContext
174
     */
175 36
    private function getPropertyValidationContext(\ReflectionClass $cls, \ReflectionProperty $property)
176
    {
177 36
        return new PropertyAnnotationValidationContext($this->serviceProvider, $cls, $property);
178
    }
179
180
    /**
181
     * @param \ReflectionClass    $subject
182
     * @param \ReflectionClass    $base
183
     * @param \ReflectionProperty $property
184
     *
185
     * @return null|PropertyMarkedForSlumber
186
     */
187 36
    private function getPropertyAnnotationsOfType(\ReflectionClass $subject, \ReflectionClass $base, \ReflectionProperty $property) : ?PropertyMarkedForSlumber
188
    {
189 36
        $annotations = $this->annotationReader->getPropertyAnnotations($property);
190
191
        // get all slumber marker annotations
192 36
        $allMarkers = Psi::it($annotations)
193 36
            ->filter(new Psi\IsInstanceOf(PropertyMarker::class))
194 36
            ->each($this->getPropertyValidationContext($base, $property))
195 36
            ->toArray();
196
197
        // get the FIRST mapping marker like AsString() or AsObject() ...
198 36
        $mappingMarker = Psi::it($allMarkers)
199 36
            ->filter(new Psi\IsInstanceOf(PropertyMappingMarker::class))
200 36
            ->getFirst();
201
202 36
        if ($mappingMarker === null) {
203 10
            return null;
204
        }
205
206 36
        $newEntry = new PropertyMarkedForSlumber();
207
208 36
        $newEntry->name       = $property->getName();
209 36
        $newEntry->alias      = $mappingMarker->hasAlias() ? $mappingMarker->getAlias() : $property->getName();
210 36
        $newEntry->marker     = $mappingMarker;
211 36
        $newEntry->allMarkers = $allMarkers;
212 36
        $newEntry->mapper     = $this->mappings->createMapper($mappingMarker);
213
214
        // We need to create the property access with the main subject class in mind.
215
        // By doing so we can access private properties of base classes.
216 36
        $newEntry->propertyAccess = $this->propertyAccessFactory->create($subject, $property);
217
218 36
        return $newEntry;
219
    }
220
}
221