Passed
Push — master ( 598273...990b98 )
by Michael
04:11
created

AttributeProcessor::process()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 4
nop 2
crap 3
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 4
    public function process(\ReflectionClass $reflection, Definition $definition)
29
    {
30 4
        foreach ($reflection->getProperties() as $property)
31
        {
32 2
            $this->processProperty($property, $definition);
33
        }
34
35 4
        foreach ($reflection->getMethods() as $method)
36
        {
37 4
            $this->processMethod($method, $definition);
38
        }
39 4
    }
40
41
    /**
42
     * Process property of class
43
     *
44
     * @param \ReflectionProperty $property
45
     * @param Definition          $definition
46
     */
47 2
    protected function processProperty(\ReflectionProperty $property, Definition $definition)
48
    {
49 2
        $annotations = $this->reader->getPropertyAnnotations($property);
50
51 2
        foreach ($annotations as $annotation)
52
        {
53 2
            if ($annotation instanceof AttributeAnnotation) {
54 2
                $attribute = $this->createAttributeByProperty($annotation, $property);
55
56 2
                $definition->addAttribute($attribute);
57 2
                continue;
58
            }
59
        }
60 2
    }
61
62
    /**
63
     * Process method of class
64
     *
65
     * @param \ReflectionMethod $method
66
     * @param Definition        $definition
67
     */
68 4
    protected function processMethod(\ReflectionMethod $method, Definition $definition)
69
    {
70 4
        $annotations = $this->reader->getMethodAnnotations($method);
71
72 4
        foreach ($annotations as $annotation)
73
        {
74 2
            if ($annotation instanceof AttributeAnnotation) {
75 2
                $this->validateMethodAttribute($annotation, $method);
76
77 2
                $attribute = $this->createAttributeByMethod($annotation, $method);
78 2
                $definition->addAttribute($attribute);
79
            }
80
        }
81 4
    }
82
83
    /**
84
     * Validate method with attribute definition
85
     *
86
     * @param  AttributeAnnotation $annotation
87
     * @param  \ReflectionMethod   $method
88
     * @throws \LogicException
89
     */
90 2
    protected function validateMethodAttribute(AttributeAnnotation $annotation, \ReflectionMethod $method)
91
    {
92 2
        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 2
        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 2
    }
106
107
    /**
108
     * Create attribute by annotation of property
109
     *
110
     * @param  AttributeAnnotation $annotation
111
     * @param  \ReflectionProperty $property
112
     * @return Attribute
113
     */
114 2
    protected function createAttributeByProperty(AttributeAnnotation $annotation, \ReflectionProperty $property): Attribute
115
    {
116 2
        $name = ($annotation->name === null)
117 2
            ? $property->getName()
118 2
            : $annotation->name;
119
120 2
        $getter = ($annotation->getter === null)
121 2
            ? $this->resolveGetter($property)
122 2
            : $annotation->getter;
123
124 2
        $setter = ($annotation->setter === null)
125 2
            ? $this->resolveSetter($property)
126 2
            : $annotation->setter;
127
128 2
        $attribute = new Attribute($name, $getter);
129 2
        $attribute->setPropertyName($property->getName());
130
131 2
        if ($setter !== null) {
132 2
            $attribute->setSetter($setter);
133
        }
134
135 2
        $this->processAttributeOptions($annotation, $attribute);
136
137 2
        return $attribute;
138
    }
139
140
    /**
141
     * Process optional properties of attribute
142
     *
143
     * @param AttributeAnnotation $annotation
144
     * @param Attribute           $attribute
145
     */
146 2
    protected function processAttributeOptions(AttributeAnnotation $annotation, Attribute $attribute)
147
    {
148 2
        if ($annotation->type !== null) {
149 2
            $this->processDataType($annotation->type, $attribute);
150
        }
151
152 2
        if ($annotation->many !== null) {
153 2
            $attribute->setMany($annotation->many);
154
        }
155
156 2
        if ($annotation->processNull !== null) {
157 2
            $attribute->setProcessNull($annotation->processNull);
158
        }
159 2
    }
160
161
    /**
162
     * Create attribute by annotation of method
163
     *
164
     * @param  AttributeAnnotation $annotation
165
     * @param  \ReflectionMethod   $method
166
     * @return Attribute
167
     */
168 2
    protected function createAttributeByMethod(AttributeAnnotation $annotation, \ReflectionMethod $method): Attribute
169
    {
170 2
        $name = ($annotation->name === null)
171 2
            ? $this->resolveNameByMethod($method)
172 2
            : $annotation->name;
173
174 2
        $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 2
        if ($annotation->type !== null) {
177 2
            $this->processDataType($annotation->type, $attribute);
178
        }
179
180 2
        return $attribute;
181
    }
182
183
    /**
184
     * Resolve name of attribute by method
185
     *
186
     * @param  \ReflectionMethod $method
187
     * @return string
188
     */
189 2
    protected function resolveNameByMethod(\ReflectionMethod $method): string
190
    {
191 2
        $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 2
        if (preg_match('~^(?:get|is)(?<name>[a-z0-9_]+)~i', $name, $matches)) {
194 2
            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 4
    protected function processDataType(string $definition, Attribute $attribute)
207
    {
208 4
        if (! preg_match(self::DATATYPE_PATTERN, $definition, $matches)) {
209
            throw new \LogicException(sprintf('Data-type definition "%s" is invalid.', $definition));
210
        }
211
212 4
        $attribute->setType($matches['type']);
213
214 4
        if (empty($matches['params'])) {
215
            return;
216
        }
217
218 4
        $parameters = explode(',', $matches['params']);
219 4
        $parameters = array_map('trim', $parameters);
220
221 4
        $attribute->setTypeParameters($parameters);
222
    }
223
}