Passed
Push — master ( 7c7fa2...d7cd6a )
by Michael
04:35
created

AnnotationDefinitionProvider::handleDataControl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
crap 1
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 13
    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 13
        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 13
    }
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 13
    public function __construct(Reader $reader)
65
    {
66 13
        self::registerAnnotations();
67
68 13
        $this->reader = $reader;
69 13
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 13
    public function getDefinition(string $class): Definition
75
    {
76 13
        if (! isset($this->definitionCache[$class])) {
77 13
            $reflection = new \ReflectionClass($class);
78
79 13
            $this->definitionCache[$class] = $this->createDefinition($reflection);
80
        }
81
82 13
        return $this->definitionCache[$class];
83
    }
84
85
    /**
86
     * Create definition for given class
87
     *
88
     * @param  \ReflectionClass $reflection
89
     * @return Definition
90
     */
91 13
    protected function createDefinition(\ReflectionClass $reflection): Definition
92
    {
93 13
        $definition = new Definition($reflection->getName());
0 ignored issues
show
Bug introduced by
Consider using $reflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
94
95 13
        $this->processProperties($reflection, $definition);
96 13
        $this->processClassAnnotations($reflection, $definition);
97
98 13
        $parent = $reflection->getParentClass();
99
100 13
        if ($parent !== false) {
101 1
            $definition->merge($this->createDefinition($parent));
0 ignored issues
show
Documentation introduced by
$this->createDefinition($parent) is of type object<Mikemirten\Compon...\Definition\Definition>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
102
        }
103
104 13
        return $definition;
105
    }
106
107
    /**
108
     * Process properties of class
109
     *
110
     * @param \ReflectionClass $reflection
111
     * @param Definition       $definition
112
     */
113 13
    protected function processProperties(\ReflectionClass $reflection, Definition $definition)
114
    {
115 13
        foreach ($reflection->getProperties() as $property)
116
        {
117 13
            $relationshipAnnotation = $this->reader->getPropertyAnnotation(
118
                $property,
119 13
                RelationshipAnnotation::class
120
            );
121
122 13
            if ($relationshipAnnotation !== null) {
123 9
                $relationship = $this->createRelationship($relationshipAnnotation, $property);
124
125 13
                $definition->addRelationship($relationship);
126
            }
127
        }
128 13
    }
129
130
    /**
131
     * Process annotations of class
132
     *
133
     * @param \ReflectionClass $reflection
134
     * @param Definition       $definition
135
     */
136 13
    protected function processClassAnnotations(\ReflectionClass $reflection, Definition $definition)
137
    {
138 13
        $annotations = $this->reader->getClassAnnotations($reflection);
139
140 13
        foreach ($annotations as $annotation)
141
        {
142 8
            if ($annotation instanceof LinkAnnotation) {
143 7
                $link = $this->createLink($annotation);
144
145 7
                $definition->addLink($link);
146 7
                continue;
147
            }
148
149 7
            if ($annotation instanceof ResourceIdentifierAnnotation) {
150 7
                $this->handlerResourceIdentifier($annotation, $definition);
151
            }
152
        }
153 13
    }
154
155
    /**
156
     * Handler resource identifier
157
     *
158
     * @param ResourceIdentifierAnnotation $annotation
159
     * @param Definition                   $definition
160
     */
161 7
    protected function handlerResourceIdentifier(ResourceIdentifierAnnotation $annotation, Definition $definition)
162
    {
163 7
        if ($annotation->type !== null) {
164 7
            $definition->setType($annotation->type);
165
        }
166 7
    }
167
168
    /**
169
     * Process relationship
170
     *
171
     * @param  RelationshipAnnotation $annotation
172
     * @param  \ReflectionProperty    $property
173
     * @return Relationship
174
     */
175 9
    protected function createRelationship(RelationshipAnnotation $annotation, \ReflectionProperty $property): Relationship
176
    {
177 9
        $name = ($annotation->name === null)
178 6
            ? $property->getName()
179 9
            : $annotation->name;
180
181 9
        $type = $this->resolveType($annotation);
182
183 9
        $relationship = new Relationship($name, $type);
184 9
        $relationship->setPropertyName($property->getName());
185
186 9
        $this->handleGetter($annotation, $relationship);
187 9
        $this->handleLinks($annotation, $relationship);
188 9
        $this->handleDataControl($annotation, $relationship);
189
190 9
        return $relationship;
191
    }
192
193
    /**
194
     * Handler getter of related object
195
     *
196
     * @param RelationshipAnnotation $annotation
197
     * @param Relationship           $relationship
198
     */
199 9
    protected function handleGetter(RelationshipAnnotation $annotation, Relationship $relationship)
200
    {
201 9
        if ($annotation->getter === null) {
202 8
            $name   = $relationship->getPropertyName();
203 8
            $getter = 'get' . ucfirst($name);
204
205 8
            $relationship->setGetter($getter);
206 8
            return;
207
        }
208
209 1
        $relationship->setGetter($annotation->getter);
210 1
    }
211
212
    /**
213
     * Handle links
214
     *
215
     * @param RelationshipAnnotation $annotation
216
     * @param Relationship           $relationship
217
     */
218 9
    protected function handleLinks(RelationshipAnnotation $annotation, Relationship $relationship)
219
    {
220 9
        foreach ($annotation->links as $linkAnnotation)
221
        {
222 7
            $link = $this->createLink($linkAnnotation);
223
224 7
            $relationship->addLink($link);
225
        }
226 9
    }
227
228
    /**
229
     * Create link by link's annotation
230
     *
231
     * @param  LinkAnnotation $annotation
232
     * @return Link
233
     */
234 8
    protected function createLink(LinkAnnotation $annotation): Link
235
    {
236 8
        if (! preg_match(self::RESOURCE_PATTERN, $annotation->resource, $matches)) {
237
            throw new \LogicException(sprintf('Invalid resource definition: "%s"', $annotation->resource));
238
        }
239
240 8
        $link = new Link(
241 8
            $annotation->name,
242 8
            $matches['repository'],
243 8
            $matches['link']
244
        );
245
246 8
        $link->setParameters($annotation->parameters);
247 8
        $link->setMetadata($annotation->metadata);
248
249 8
        return $link;
250
    }
251
252
    /**
253
     * Handle control of data-section
254
     *
255
     * @param RelationshipAnnotation $annotation
256
     * @param Relationship           $relationship
257
     */
258 9
    protected function handleDataControl(RelationshipAnnotation $annotation, Relationship $relationship)
259
    {
260 9
        $relationship->setIncludeData($annotation->dataAllowed);
261 9
        $relationship->setDataLimit($annotation->dataLimit);
262 9
    }
263
264
    /**
265
     * Resolve type of relationship
266
     *
267
     * @param  RelationshipAnnotation $annotation
268
     * @return int
269
     */
270 9
    protected function resolveType(RelationshipAnnotation $annotation): int
271
    {
272 9
        if ($annotation->type === RelationshipAnnotation::TYPE_ONE) {
273 2
            return Relationship::TYPE_X_TO_ONE;
274
        }
275
276 7
        if ($annotation->type === RelationshipAnnotation::TYPE_MANY) {
277 7
            return Relationship::TYPE_X_TO_MANY;
278
        }
279
280
        throw new \LogicException(sprintf('Invalid type of relation "%s" defined.', $annotation->type));
281
    }
282
}