Completed
Push — master ( 852c83...41042e )
by Alexey
07:15 queued 05:22
created

Structure::getAnnotationAdapter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace PhMap\Mapper;
4
5
use \Phalcon\Annotations\Annotation,
6
    \Phalcon\Annotations\Reflection,
7
    \Phalcon\Annotations\Collection as Annotations,
8
    \Phalcon\Annotations\AdapterInterface,
9
    \Phalcon\Annotations\Adapter\Memory,
10
    \Phalcon\Annotations\Adapter\Files,
11
    \Phalcon\Annotations\Adapter\Apc,
12
    \Phalcon\Annotations\Adapter\Xcache,
13
14
    \PhMap\Mapper,
15
    \PhMap\Transform,
16
    \PhMap\Exception\UnknownAnnotationAdapter,
17
    \PhMap\Exception\FieldValidator\UnknownField as UnknownFieldException,
18
    \PhMap\Exception\FieldValidator\MustBeSimple as MustBeSimpleException,
19
    \PhMap\Exception\FieldValidator\MustBeSequence as MustBeSequenceException,
20
    \PhMap\Exception\FieldValidator\MustBeObject as MustBeObjectException;
21
22
/**
23
 * Class Structure
24
 * @abstract
25
 * @package PhMap\Mapper
26
 */
27
abstract class Structure extends Mapper {
28
29
    /**
30
     * @var array|object
31
     */
32
    private $structure;
33
34
    /**
35
     * @var AdapterInterface
36
     */
37
    private $annotationAdapter;
38
39
    /**
40
     * @var Reflection
41
     */
42
    private $reflector;
43
44
    /**
45
     * @return array|object
46
     */
47 47
    protected function getInputStructure() {
48 47
        return $this->structure;
49
    }
50
51
    /**
52
     * @param array|object $structure
53
     * @return $this
54
     */
55 47
    protected function setInputStructure($structure) {
56 47
        $this->structure = $structure;
57
58 47
        return $this;
59
    }
60
61
    /**
62
     * @return Reflection
63
     */
64 47
    private function getReflector() {
65 47
        return $this->reflector;
66
    }
67
68
    /**
69
     * @param Reflection $reflector
70
     * @return $this
71
     */
72 47
    private function setReflector(Reflection $reflector) {
73 47
        $this->reflector = $reflector;
74
75 47
        return $this;
76
    }
77
78
    /**
79
     * @return AdapterInterface
80
     */
81 47
    private function getAnnotationAdapter() {
82 47
        return $this->annotationAdapter;
83
    }
84
85
    /**
86
     * @param AdapterInterface $adapter
87
     * @return $this
88
     */
89 47
    private function setAnnotationAdapter(AdapterInterface $adapter) {
90 47
        $this->annotationAdapter = $adapter;
91
92 47
        return $this;
93
    }
94
95
    /**
96
     * @param integer $adapter
97
     * @return $this
98
     */
99 50
    public function setAnnotationAdapterType($adapter) {
100 50
        if ($adapter !== $this->getAnnotationAdapterType()) {
101 50
            parent::setAnnotationAdapterType($adapter);
102
103 50
            $this->createAnnotationAdapter()
104 47
                ->updateReflector();
105 47
        }
106
107 47
        return $this;
108
    }
109
110
    /**
111
     * @param string $class
112
     * @return $this
113
     */
114 1
    public function setOutputClass($class) {
115 1
        $this->setOutputClassInternal($class);
116
117 1
        return $this->updateReflector();
118
    }
119
120
    /**
121
     * @param object $object
122
     * @return $this
123
     */
124 3
    public function setOutputObject($object) {
125 3
        $this->setOutputObjectInternal($object);
126
127 3
        return $this->updateReflector();
128
    }
129
130
    /**
131
     * @param array|object $inputStructure
132
     * @param string|object $outputClassOrObject
133
     * @param integer $adapter
134
     */
135 50
    public function __construct(
136
        $inputStructure, 
137
        $outputClassOrObject, 
138
        $adapter = self::MEMORY_ANNOTATION_ADAPTER
139
    ) {
140 50
        parent::__construct($outputClassOrObject, $adapter);
141
142 47
        $this
143 47
            ->setInputStructure($inputStructure)
144 47
            ->createAnnotationAdapter()
145 47
            ->updateReflector();
146 47
    }
147
148
    /**
149
     * @return $this
150
     * @throws UnknownAnnotationAdapter
151
     */
152 50
    private function createAnnotationAdapter() {
153 50
        switch ($this->getAnnotationAdapterType()) {
154 50
            case self::MEMORY_ANNOTATION_ADAPTER:
155 36
                $this->setAnnotationAdapter(new Memory());
156 36
                break;
157 46
            case self::FILES_ANNOTATION_ADAPTER:
158 43
                $this->setAnnotationAdapter(new Files([
159 43
                    'annotationsDir' => $this->getCacheDirectory()
160 43
                ]));
161 43
                break;
162 3
            case self::APC_ANNOTATION_ADAPTER:
163
                $this->setAnnotationAdapter(new Apc());
164
                break;
165 3
            case self::X_CACHE_ANNOTATION_ADAPTER:
166
                $this->setAnnotationAdapter(new Xcache());
167
                break;
168 3
            default:
169 3
                throw new UnknownAnnotationAdapter();
170 50
        }
171
172 47
        return $this;
173
    }
174
175
    /**
176
     * @return string
177
     */
178 43
    private function getCacheDirectory() {
179 43
        return dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR .
180 43
            'cache' . DIRECTORY_SEPARATOR;
181
    }
182
183
    /**
184
     * @return $this
185
     */
186 47
    protected function updateReflector() {
187 47
        return $this->setReflector(
188 47
            $this->getAnnotationAdapter()
189 47
                ->get($this->getOutputClass())
190 47
        );
191
    }
192
193
    /**
194
     * @return object
195
     * @throws UnknownFieldException
196
     */
197 47
    public function map() {
198 47
        $methodsAnnotations = $this
199 47
            ->getReflector()
200 47
            ->getMethodsAnnotations();
201
202 47
        foreach ($this->getInputAttributes() as $attribute => $value) {
203 47
            $transform = $this->getTransforms() ? $this->getTransforms()->findByInputFieldName($attribute) : null;
204 47
            $transformedAttribute = $transform ? $transform->getOutputFieldName() : $attribute;
205
206 47
            if ($this->needToSkip($transformedAttribute)) {
207 3
                continue;
208
            }
209
210 47
            $setter = $this->createSetter($transformedAttribute);
211
212 47
            if ($this->hasAttribute($transformedAttribute)) {
213
                /** @var Annotations $methodAnnotations */
214 47
                $methodAnnotations = $methodsAnnotations[$setter];
215
216 47
                $valueToMap = $this->buildValueToMap(
217 47
                    $transformedAttribute,
218 47
                    $value,
219 47
                    $methodAnnotations,
220
                    $transform
221 47
                );
222
223 47
                if (!is_null($valueToMap)) {
224 47
                    $this
225 47
                        ->getOutputObject()
226 47
                        ->$setter($valueToMap);
227 47
                }
228 47
            } elseif ($this->hasValidation()) {
229 3
                throw new UnknownFieldException($transformedAttribute, $this->getOutputClass());
230
            }
231 47
        }
232
233 38
        return $this->getOutputObject();
234
    }
235
236
    /**
237
     * @return array
238
     */
239
    protected function getCurrentSkipAttributes() {
240
        static $skipAttributes = [];
241
242
        if (empty($skipAttributes)) {
243
            foreach ($this->getSkipAttributes() as $attribute) {
244
                $attributes = explode('.', $attribute);
245
                if (count($attributes) === 1) {
246
                    $skipAttributes[] = array_pop($attributes);
247
                }
248
            }
249
        }
250
251
        return $skipAttributes;
252
    }
253
254
    /**
255
     * @param string $parentAttribute
256
     * @return array
257
     */
258 42
    protected function getSkipAttributesByParent($parentAttribute) {
259 42
        $skipAttributes = [];
260
261 42
        $parentAttribute = $parentAttribute . '.';
262
263 42
        foreach ($this->getSkipAttributes() as $skipAttribute) {
264 3
            if (strpos($skipAttribute, $parentAttribute) === 0) {
265 3
                $skipAttributes[] = str_replace($parentAttribute, '', $skipAttribute);
266 3
            }
267 42
        }
268
269 42
        return $skipAttributes;
270
    }
271
272
    /**
273
     * @param $attribute
274
     * @return boolean
275
     */
276 47
    protected function needToSkip($attribute) {
277 47
        return in_array($attribute, $this->getSkipAttributes());
278
    }
279
280
    /**
281
     * @param string $attribute
282
     * @param mixed $value
283
     * @param Annotations $annotations
284
     * @param Transform|null $transform
285
     * @return mixed
286
     * @throws MustBeSimpleException
287
     * @throws MustBeSequenceException
288
     * @throws MustBeObjectException
289
     */
290 47
    protected function buildValueToMap(
291
        $attribute,
292
        $value,
293
        Annotations $annotations,
294
        Transform $transform = null
295
    ) {
296 47
        $valueToMap = $value;
297
298 47
        if ($annotations->has('mapper')) {
299
            /** @var Annotation $annotation */
300 42
            $annotation = $annotations->get('mapper');
301 42
            $annotationClass = $annotation->getArgument('class');
302 42
            $annotationIsArray = $annotation->getArgument('isArray');
303
304 42
            $transforms = $transform ? $transform->getTransforms() : null;
305
306 42
            $skipAttributes = $this->getSkipAttributesByParent($attribute);
307
308 42
            if ($this->isObject($value)) {
309 36
                if ($annotationIsArray) {
310 6
                    if ($this->hasValidation()) {
311 3
                        throw new MustBeSequenceException($attribute, $this->getOutputClass());
312
                    } else {
313 3
                        $valueToMap = null;
314
                    }
315 3
                } else {
316
                    /** @var object|array $value */
317
                    /** @var Mapper $mapper */
318 36
                    $mapper = new static($value, $annotationClass);
319
                    $valueToMap = $mapper
320 36
                        ->setTransforms($transforms)
321 36
                        ->setValidation($this->hasValidation())
322 36
                        ->setSkipAttributes($skipAttributes)
323 36
                        ->map();
324
                }
325 33
            } else {
326 36
                if ($annotationIsArray) {
327 30
                    $validation = $this->hasValidation();
328 30
                    $valueToMap = array_map(function($value) use (
329 30
                        $annotationClass,
330 30
                        $transforms,
331 30
                        $validation,
332 30
                        $skipAttributes
333
                    ) {
334
                        /** @var object|array $value */
335
                        /** @var Mapper $mapper */
336 30
                        $mapper = new static($value, $annotationClass);
337
                        return $mapper
338 30
                            ->setTransforms($transforms)
339 30
                            ->setValidation($validation)
340 30
                            ->setSkipAttributes($skipAttributes)
341 30
                            ->map();
342 30
                    }, $value);
343 36
                } elseif ($this->hasValidation()) {
344 3
                    throw new MustBeObjectException($attribute, $this->getOutputClass());
345
                } else {
346 3
                    $valueToMap = null;
347
                }
348
            }
349 47
        } elseif ($this->isStructure($value)) {
350 6
            if ($this->hasValidation()) {
351 3
                throw new MustBeSimpleException($attribute, $this->getOutputClass());
352
            } else {
353 3
                $valueToMap = null;
354
            }
355 3
        }
356
357 47
        return $valueToMap;
358
    }
359
360
    /**
361
     * @param string $attribute
362
     * @return boolean
363
     */
364 47
    protected function hasAttribute($attribute) {
365 47
        $setter = $this->createSetter($attribute);
366 47
        $getter = $this->createGetter($attribute);
367
368 47
        return method_exists($this->getOutputClass(), $getter)
369 47
            && method_exists($this->getOutputClass(), $setter);
370
    }
371
372
    /**
373
     * @param string $attribute
374
     * @return string
375
     */
376 47
    protected function createSetter($attribute) {
377 47
        return 'set' . ucfirst($attribute);
378
    }
379
380
    /**
381
     * @param string $attribute
382
     * @return string
383
     */
384 47
    protected function createGetter($attribute) {
385 47
        return 'get' . ucfirst($attribute);
386
    }
387
388
    /**
389
     * @param mixed $value
390
     * @return boolean
391
     */
392
    abstract protected function isObject($value);
393
394
    /**
395
     * @param mixed $value
396
     * @return boolean
397
     */
398
    abstract protected function isSequential($value);
399
400
    /**
401
     * @param mixed $value
402
     * @return boolean
403
     */
404
    abstract protected function isStructure($value);
405
406
    /**
407
     * @return array
408
     */
409
    abstract protected function getInputAttributes();
410
411
}