Passed
Push — master ( a9c512...146f63 )
by Michael
02:32
created

AnnotationDefinitionProvider::resolveType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

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