Completed
Push — master ( f30d53...1e60b8 )
by JHONATAN
05:19
created

ObjectHydrator::hydrate()   D

Complexity

Conditions 10
Paths 11

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 18
cts 18
cp 1
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 18
nc 11
nop 2
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Vox\Data;
4
5
use DateTime;
6
use Metadata\MetadataFactoryInterface;
7
use RuntimeException;
8
use Vox\Data\Mapping\Bindings;
9
use Vox\Data\Mapping\Discriminator;
10
use Vox\Data\Mapping\Exclude;
11
use Vox\Metadata\ClassMetadata;
12
use Vox\Metadata\PropertyMetadata;
13
14
/**
15
 * Hydrates objects based on its metadata information, uses data mapping
16
 * 
17
 * @author Jhonatan Teixeira <[email protected]>
18
 */
19
class ObjectHydrator implements ObjectHydratorInterface
20
{
21
    /**
22
     * @var MetadataFactoryInterface
23
     */
24
    private $metadataFactory;
25
    
26 38
    public function __construct(MetadataFactoryInterface $metadataFactory)
27
    {
28 38
        $this->metadataFactory = $metadataFactory;
29 38
    }
30
    
31 26
    public function hydrate($object, array $data)
32
    {
33 26
        $objectMetadata = $this->getObjectMetadata($object, $data);
34
35
        /* @var $propertyMetadata PropertyMetadata  */
36 26
        foreach ($objectMetadata->propertyMetadata as $propertyMetadata) {
37 26
            $annotation = $propertyMetadata->getAnnotation(Bindings::class);
38 26
            $source     = $annotation ? ($annotation->source ?? $propertyMetadata->name) : $propertyMetadata->name;
39 26
            $type       = $propertyMetadata->type;
40
            
41 26
            if (!isset($data[$source]) 
42 26
                || ($propertyMetadata->hasAnnotation(Exclude::class) 
43 26
                    && $propertyMetadata->getAnnotation(Exclude::class)->input)) {
44 20
                continue;
45
            }
46
            
47 26
            $value = $data[$source];
48
            
49 26
            if ($type && $value) {
50 19
                if ($propertyMetadata->isDecoratedType()) {
51 2
                    $value = $this->convertDecorated($type, $value);
52 18
                } elseif ($propertyMetadata->isNativeType()) {
53 17
                    $value = $this->convertNativeType($type, $value);
54
                } else {
55 5
                    $value = $this->convertObjectValue($type, $value);
56
                }
57
            }
58
59 26
            $propertyMetadata->setValue($object, $value);
60
        }
61 26
    }
62
    
63 17
    private function convertNativeType($type, $value)
64
    {
65
        switch ($type) {
66 17
            case 'int':
67 11
            case 'integer':
68 17
                return (int) $value;
69 11
            case 'string':
70 9
                return (string) $value;
71 2
            case 'boolean':
72 2
            case 'bool':
73
                return (bool) $value;
74 2
            case 'array':
75 2
                if (!is_array($value)) {
76
                    throw new RuntimeException('value is not array');
77
                }
78
                
79 2
                return $value;
80
            case 'DateTime':
81
            case '\DateTime':
82
                return new DateTime($value);
83
            default:
84
                return $value;
85
        }
86
    }
87
    
88 2
    private function convertDecorated(string $type, $value)
89
    {
90 2
        preg_match('/(?P<class>.*)((\<(?P<decoration>.*)\>)|(?P<brackets>\[\]))/', $type, $matches);
91
        
92 2
        $class      = isset($matches['brackets']) ? 'array' : $matches['class'];
93 2
        $decoration = isset($matches['brackets']) ? $matches['class'] : $matches['decoration'];
94
95
        switch ($class) {
96 2
            case 'array':
97 2
                if (!is_array($value)) {
98
                    throw new RuntimeException('value mapped as array is not array');
99
                }
100
101 2
                $data = [];
102
103 2
                foreach ($value as $item) {
104 2
                    $object = $this->convertObjectValue($decoration, $item);
105
106 2
                    $data[] = $object;
107
                }
108
109 2
                break;
110 1
            case 'DateTime':
111
            case '\DateTime':
112 1
                $data = DateTime::createFromFormat($decoration, $value);
113 1
                break;
114
        }
115
116 2
        return $data;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.
Loading history...
117
    }
118
    
119 6
    private function convertObjectValue(string $type, array $data)
120
    {
121 6
        $metadata = $this->getObjectMetadata($type, $data);
122 6
        $object   = $metadata->reflection->newInstanceWithoutConstructor();
123
124 6
        $this->hydrate(
125 6
            $object, 
126 6
            $data
127
        );
128
129 6
        return $object;
130
    }
131
    
132 26
    private function getObjectMetadata($object, array $data): ClassMetadata
133
    {
134 26
        $metadata      = $this->metadataFactory->getMetadataForClass(is_string($object) ? $object : get_class($object));
135 26
        $discriminator = $metadata->getAnnotation(Discriminator::class);
0 ignored issues
show
Bug introduced by
The method getAnnotation() does not exist on Metadata\ClassHierarchyMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
        /** @scrutinizer ignore-call */ 
136
        $discriminator = $metadata->getAnnotation(Discriminator::class);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
introduced by
The method getAnnotation() does not exist on Metadata\MergeableClassMetadata. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
        /** @scrutinizer ignore-call */ 
136
        $discriminator = $metadata->getAnnotation(Discriminator::class);
Loading history...
136
137 26
        if ($discriminator instanceof Discriminator && isset($data[$discriminator->field])) {
138 3
            if (!isset($discriminator->map[$data[$discriminator->field]])) {
139
                throw new RuntimeException("no discrimination for {$data[$discriminator->field]}");
140
            }
141
142 3
            $type     = $discriminator->map[$data[$discriminator->field]];
143 3
            $metadata = $this->metadataFactory->getMetadataForClass($type);
144
        }
145
        
146 26
        return $metadata;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $metadata returns the type null|Metadata\ClassHierarchyMetadata which is incompatible with the type-hinted return Vox\Metadata\ClassMetadata.
Loading history...
147
    }
148
}
149