Passed
Push — master ( 19a485...913337 )
by Michael
02:43
created

processClassAnnotations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
ccs 7
cts 7
cp 1
rs 9.4285
cc 3
eloc 6
nc 3
nop 2
crap 3
1
<?php
2
declare(strict_types = 1);
3
4
namespace Mikemirten\Component\JsonApi\Mapper\Definition;
5
6
use Doctrine\Common\Annotations\AnnotationRegistry;
7
use Doctrine\Common\Annotations\Reader;
8
use Mikemirten\Component\JsonApi\Mapper\Definition\Annotation\Relationship as RelationshipAnnotation;
9
use Mikemirten\Component\JsonApi\Mapper\Definition\Annotation\Link as LinkAnnotation;
10
11
/**
12
 * Annotation Definition Provider based on the Doctrine-Annotation library
13
 *
14
 * @package Mikemirten\Component\JsonApi\Mapper\Definition
15
 */
16
class AnnotationDefinitionProvider implements DefinitionProviderInterface
17
{
18
    /**
19
     * Pattern of "resource" parameter
20
     */
21
    const RESOURCE_PATTERN = '~^(?<repository>[a-z_][a-z0-9_]*)\.(?<link>[a-z_][a-z0-9_]*)$~i';
22
23
    /**
24
     * Annotation classes ha been registered.
25
     *
26
     * @var bool
27
     */
28
    static private $annotationsRegistered = false;
29
30
    /**
31
     * Cache of created definitions
32
     *
33
     * @var array
34
     */
35
    private $definitionCache = [];
36
37
    /**
38
     * Register annotation classes.
39
     * Supports a medieval-aged way of "autoloading" for the Doctrine Annotation library.
40
     */
41 7
    static protected function registerAnnotations()
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
42
    {
43 7
        if (self::$annotationsRegistered === false) {
44 1
            AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Relationship.php');
45 1
            AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Link.php');
46
47 1
            self::$annotationsRegistered = true;
48
        }
49 7
    }
50
51
    /**
52
     * Doctrine annotation reader
53
     *
54
     * @var Reader
55
     */
56
    protected $reader;
57
58
    /**
59
     * AnnotationDefinitionProvider constructor.
60
     *
61
     * @param Reader $reader
62
     */
63 7
    public function __construct(Reader $reader)
64
    {
65 7
        self::registerAnnotations();
66
67 7
        $this->reader = $reader;
68 7
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 7
    public function getDefinition(string $class): Definition
74
    {
75 7
        if (! isset($this->definitionCache[$class])) {
76 7
            $this->definitionCache[$class] = $this->createDefinition($class);
77
        }
78
79 7
        return $this->definitionCache[$class];
80
    }
81
82
    /**
83
     * Create definition for given class
84
     *
85
     * @param  string $class
86
     * @return Definition
87
     */
88 7
    public function createDefinition(string $class): Definition
89
    {
90 7
        $definition = new Definition($class);
91 7
        $reflection = new \ReflectionClass($class);
92
93 7
        $this->processProperties($reflection, $definition);
94 7
        $this->processClassAnnotations($reflection, $definition);
95
96 7
        return $definition;
97
    }
98
99
    /**
100
     * Process properties of class
101
     *
102
     * @param \ReflectionClass $reflection
103
     * @param Definition       $definition
104
     */
105 7
    protected function processProperties(\ReflectionClass $reflection, Definition $definition)
106
    {
107 7
        foreach ($reflection->getProperties() as $property)
108
        {
109 7
            $relationshipAnnotation = $this->reader->getPropertyAnnotation(
110
                $property,
111 7
                RelationshipAnnotation::class
112
            );
113
114 7
            if ($relationshipAnnotation !== null) {
115 5
                $relationship = $this->createRelationship($relationshipAnnotation, $property);
116
117 7
                $definition->addRelationship($relationship);
118
            }
119
        }
120 7
    }
121
122
    /**
123
     * Process annotations of class
124
     *
125
     * @param \ReflectionClass $reflection
126
     * @param Definition       $definition
127
     */
128 7
    protected function processClassAnnotations(\ReflectionClass $reflection, Definition $definition)
129
    {
130 7
        $annotations = $this->reader->getClassAnnotations($reflection);
131
132 7
        foreach ($annotations as $annotation)
133
        {
134 4
            if ($annotation instanceof LinkAnnotation) {
135 4
                $link = $this->createLink($annotation);
136
137 4
                $definition->addLink($link);
138
            }
139
        }
140 7
    }
141
142
    /**
143
     * Process relationship
144
     *
145
     * @param  RelationshipAnnotation $annotation
146
     * @param  \ReflectionProperty    $property
147
     * @return Relationship
148
     */
149 5
    protected function createRelationship(RelationshipAnnotation $annotation, \ReflectionProperty $property): Relationship
150
    {
151 5
        $name = ($annotation->name === null)
152 3
            ? $property->getName()
153 5
            : $annotation->name;
154
155 5
        $type = $this->resolveType($annotation);
156
157 5
        $relationship = new Relationship($name, $type);
158 5
        $relationship->setPropertyName($property->getName());
159
160 5
        if ($annotation->resourceType !== null) {
161 4
            $relationship->setResourceType($annotation->resourceType);
162
        }
163
164 5
        $this->handleGetter($annotation, $relationship);
165 5
        $this->handleIdentifier($annotation, $relationship);
166 5
        $this->handleLinks($annotation, $relationship);
167
168 5
        return $relationship;
169
    }
170
171
    /**
172
     * Handler getter of related object
173
     *
174
     * @param RelationshipAnnotation $annotation
175
     * @param Relationship           $relationship
176
     */
177 5
    protected function handleGetter(RelationshipAnnotation $annotation, Relationship $relationship)
178
    {
179 5
        if ($annotation->getter === null) {
180 4
            $name   = $relationship->getPropertyName();
181 4
            $getter = 'get' . ucfirst($name);
182
183 4
            $relationship->setGetter($getter);
184 4
            return;
185
        }
186
187 1
        $relationship->setGetter($annotation->getter);
188 1
    }
189
190
    /**
191
     * Handle identifier
192
     *
193
     * @param RelationshipAnnotation $annotation
194
     * @param Relationship           $relationship
195
     */
196 5
    protected function handleIdentifier(RelationshipAnnotation $annotation, Relationship $relationship)
197
    {
198 5
        if ($annotation->idGetter !== null) {
199
            $relationship->setIdentifierGetter($annotation->idGetter);
200
            return;
201
        }
202
203 5
        if ($annotation->idProperty !== null) {
204 4
            $getter = 'get' . ucfirst($annotation->idProperty);
205
206 4
            $relationship->setIdentifierGetter($getter);
207 4
            return;
208
        }
209 1
    }
210
211
    /**
212
     * Handle links
213
     *
214
     * @param RelationshipAnnotation $annotation
215
     * @param Relationship           $relationship
216
     */
217 5
    protected function handleLinks(RelationshipAnnotation $annotation, Relationship $relationship)
218
    {
219 5
        foreach ($annotation->links as $linkAnnotation)
220
        {
221 4
            $link = $this->createLink($linkAnnotation);
222
223 4
            $relationship->addLink($link);
224
        }
225 5
    }
226
227
    /**
228
     * Create link by link's annotation
229
     *
230
     * @param  LinkAnnotation $annotation
231
     * @return Link
232
     */
233 5
    protected function createLink(LinkAnnotation $annotation): Link
234
    {
235 5
        if (! preg_match(self::RESOURCE_PATTERN, $annotation->resource, $matches)) {
236
            throw new \LogicException(sprintf('Invalid resource definition: "%s"', $annotation->resource));
237
        }
238
239 5
        $link = new Link(
240 5
            $annotation->name,
241 5
            $matches['repository'],
242 5
            $matches['link']
243
        );
244
245 5
        $link->setParameters($annotation->parameters);
246 5
        $link->setMetadata($annotation->metadata);
247
248 5
        return $link;
249
    }
250
251
    /**
252
     * Resolve type of relationship
253
     *
254
     * @param  RelationshipAnnotation $annotation
255
     * @return int
256
     */
257 5
    protected function resolveType(RelationshipAnnotation $annotation): int
258
    {
259 5
        if ($annotation->type === RelationshipAnnotation::TYPE_ONE) {
260 1
            return Relationship::TYPE_X_TO_ONE;
261
        }
262
263 4
        if ($annotation->type === RelationshipAnnotation::TYPE_MANY) {
264 4
            return Relationship::TYPE_X_TO_MANY;
265
        }
266
267
        throw new \LogicException(sprintf('Invalid type of relation "%s" defined.', $annotation->type));
268
    }
269
}