Test Failed
Push — master ( 385400...b007ca )
by Michael
02:12
created

AttributeProcessor::process()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 5
nc 4
nop 2
1
<?php
2
declare(strict_types = 1);
3
4
namespace Mikemirten\Component\JsonApi\Mapper\Definition\AnnotationProcessor;
5
6
use Mikemirten\Component\JsonApi\Mapper\Definition\Annotation\Attribute as AttributeAnnotation;
7
use Mikemirten\Component\JsonApi\Mapper\Definition\Definition;
8
use Mikemirten\Component\JsonApi\Mapper\Definition\Attribute;
9
10
/**
11
 * Processor of attributes
12
 *
13
 * @package Mikemirten\Component\JsonApi\Mapper\Definition\AnnotationProcessor
14
 */
15
class AttributeProcessor extends AbstractProcessor
16
{
17
    /**
18
     * Pattern of "type" parameter of attribute annotation
19
     */
20
    const DATATYPE_PATTERN = '~^(?<type>[a-z_][a-z0-9_]*)\s*(?:\((?<params>[^\)]*)\))?$~i';
21
22
    /**
23
     * Process annotations
24
     *
25
     * @param \ReflectionClass $reflection
26
     * @param Definition       $definition
27
     */
28
    public function process(\ReflectionClass $reflection, Definition $definition): void
29
    {
30
        foreach ($reflection->getProperties() as $property)
31
        {
32
            $this->processProperty($property, $definition);
33
        }
34
35
        foreach ($reflection->getMethods() as $method)
36
        {
37
            $this->processMethod($method, $definition);
38
        }
39
    }
40
41
    /**
42
     * Process property of class
43
     *
44
     * @param \ReflectionProperty $property
45
     * @param Definition          $definition
46
     */
47
    protected function processProperty(\ReflectionProperty $property, Definition $definition)
48
    {
49
        $annotations = $this->reader->getPropertyAnnotations($property);
50
51
        foreach ($annotations as $annotation)
52
        {
53
            if ($annotation instanceof AttributeAnnotation) {
54
                $attribute = $this->createAttributeByProperty($annotation, $property);
55
56
                $definition->addAttribute($attribute);
57
                continue;
58
            }
59
        }
60
    }
61
62
    /**
63
     * Process method of class
64
     *
65
     * @param \ReflectionMethod $method
66
     * @param Definition        $definition
67
     */
68
    protected function processMethod(\ReflectionMethod $method, Definition $definition)
69
    {
70
        $annotations = $this->reader->getMethodAnnotations($method);
71
72
        foreach ($annotations as $annotation)
73
        {
74
            if ($annotation instanceof AttributeAnnotation) {
75
                $this->validateMethodAttribute($annotation, $method);
76
77
                $attribute = $this->createAttributeByMethod($annotation, $method);
78
                $definition->addAttribute($attribute);
79
            }
80
        }
81
    }
82
83
    /**
84
     * Validate method with attribute definition
85
     *
86
     * @param  AttributeAnnotation $annotation
87
     * @param  \ReflectionMethod   $method
88
     * @throws \LogicException
89
     */
90
    protected function validateMethodAttribute(AttributeAnnotation $annotation, \ReflectionMethod $method)
91
    {
92
        if (! $method->isPublic()) {
93
            throw new \LogicException(sprintf(
94
                'Attribute annotation can be applied only to non public method "%s".',
95
                $method->getName()
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
96
            ));
97
        }
98
99
        if ($annotation->getter !== null) {
100
            throw new \LogicException(sprintf(
101
                'The "getter" property of Attribute annotation applied to method "%s" is useless.',
102
                $method->getName()
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
103
            ));
104
        }
105
    }
106
107
    /**
108
     * Create attribute by annotation of property
109
     *
110
     * @param  AttributeAnnotation $annotation
111
     * @param  \ReflectionProperty $property
112
     * @return Attribute
113
     */
114
    protected function createAttributeByProperty(AttributeAnnotation $annotation, \ReflectionProperty $property): Attribute
115
    {
116
        $name = ($annotation->name === null)
117
            ? $property->getName()
118
            : $annotation->name;
119
120
        $getter = ($annotation->getter === null)
121
            ? $this->resolveGetter($property)
122
            : $annotation->getter;
123
124
        $setter = ($annotation->setter === null)
125
            ? $this->resolveSetter($property)
126
            : $annotation->setter;
127
128
        $attribute = new Attribute($name, $getter);
129
        $attribute->setPropertyName($property->getName());
130
131
        if ($setter !== null) {
132
            $attribute->setSetter($setter);
133
        }
134
135
        $this->processAttributeOptions($annotation, $attribute);
136
137
        return $attribute;
138
    }
139
140
    /**
141
     * Process optional properties of attribute
142
     *
143
     * @param AttributeAnnotation $annotation
144
     * @param Attribute           $attribute
145
     */
146
    protected function processAttributeOptions(AttributeAnnotation $annotation, Attribute $attribute)
147
    {
148
        if ($annotation->type !== null) {
149
            $this->processDataType($annotation->type, $attribute);
150
        }
151
152
        if ($annotation->many !== null) {
153
            $attribute->setMany($annotation->many);
154
        }
155
156
        if ($annotation->processNull !== null) {
157
            $attribute->setProcessNull($annotation->processNull);
158
        }
159
    }
160
161
    /**
162
     * Create attribute by annotation of method
163
     *
164
     * @param  AttributeAnnotation $annotation
165
     * @param  \ReflectionMethod   $method
166
     * @return Attribute
167
     */
168
    protected function createAttributeByMethod(AttributeAnnotation $annotation, \ReflectionMethod $method): Attribute
169
    {
170
        $name = ($annotation->name === null)
171
            ? $this->resolveNameByMethod($method)
172
            : $annotation->name;
173
174
        $attribute = new Attribute($name, $method->getName());
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
175
176
        if ($annotation->type !== null) {
177
            $this->processDataType($annotation->type, $attribute);
178
        }
179
180
        return $attribute;
181
    }
182
183
    /**
184
     * Resolve name of attribute by method
185
     *
186
     * @param  \ReflectionMethod $method
187
     * @return string
188
     */
189
    protected function resolveNameByMethod(\ReflectionMethod $method): string
190
    {
191
        $name = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
192
193
        if (preg_match('~^(?:get|is)(?<name>[a-z0-9_]+)~i', $name, $matches)) {
194
            return lcfirst($matches['name']);
195
        }
196
197
        return $name;
198
    }
199
200
    /**
201
     * Process data-type
202
     *
203
     * @param string    $definition
204
     * @param Attribute $attribute
205
     */
206
    protected function processDataType(string $definition, Attribute $attribute)
207
    {
208
        if (! preg_match(self::DATATYPE_PATTERN, $definition, $matches)) {
209
            throw new \LogicException(sprintf('Data-type definition "%s" is invalid.', $definition));
210
        }
211
212
        $attribute->setType($matches['type']);
213
214
        if (empty($matches['params'])) {
215
            return;
216
        }
217
218
        $parameters = explode(',', $matches['params']);
219
        $parameters = array_map('trim', $parameters);
220
221
        $attribute->setTypeParameters($parameters);
222
    }
223
}