Completed
Push — master ( af7add...6e606c )
by Karsten
02:11
created

AnnotatedEntityConfigReader   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 13
dl 0
loc 193
ccs 70
cts 70
cp 1
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 2
A getEntityConfig() 0 11 1
A enrich() 0 6 1
A getCreator() 0 12 1
A getClassMarkers() 0 9 1
A getPropertyMarkersRecursive() 0 18 3
A getPropertyMarkersForClass() 0 18 4
A getPropertyValidationContext() 0 4 1
B getPropertyAnnotationsOfType() 0 33 3
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 method Doctrine\Common\Annotati...:registerUniqueLoader() has been deprecated with message: this method is deprecated and will be removed in doctrine/annotations 2.0

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

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class 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);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $marker is correct as $this->getPropertyAnnota...ject, $base, $property) (which targets PeekAndPoke\Component\Sl...ertyAnnotationsOfType()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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