Passed
Branch nested_save (73f795)
by JHONATAN
02:34
created

ObjectHydrator   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Test Coverage

Coverage 81.82%

Importance

Changes 0
Metric Value
wmc 37
dl 0
loc 148
ccs 63
cts 77
cp 0.8182
rs 8.6
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A isDecorated() 0 3 1
C convertNativeType() 0 22 10
A isNativeType() 0 12 1
C convertDecorated() 0 29 8
B getObjectMetadata() 0 15 5
A convertObjectValue() 0 11 1
D hydrate() 0 29 10
A __construct() 0 3 1
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
class ObjectHydrator implements ObjectHydratorInterface
15
{
16
    /**
17
     * @var MetadataFactoryInterface
18
     */
19
    private $metadataFactory;
20
    
21 16
    public function __construct(MetadataFactoryInterface $metadataFactory)
22
    {
23 16
        $this->metadataFactory = $metadataFactory;
24 16
    }
25
    
26 15
    public function hydrate($object, array $data)
27
    {
28 15
        $objectMetadata = $this->getObjectMetadata($object, $data);
29
30
        /* @var $propertyMetadata PropertyMetadata  */
31 15
        foreach ($objectMetadata->propertyMetadata as $propertyMetadata) {
32 15
            $annotation = $propertyMetadata->getAnnotation(Bindings::class);
33 15
            $source     = $annotation ? ($annotation->source ?? $propertyMetadata->name) : $propertyMetadata->name;
34 15
            $type       = $propertyMetadata->type;
35
            
36 15
            if (!isset($data[$source]) 
37 15
                || ($propertyMetadata->hasAnnotation(Exclude::class) 
38 15
                    && $propertyMetadata->getAnnotation(Exclude::class)->input)) {
39 11
                continue;
40
            }
41
            
42 15
            $value = $data[$source];
43
            
44 15
            if ($type && $value) {
45 10
                if ($this->isDecorated($type)) {
46 2
                    $value = $this->convertDecorated($type, $value);
47 9
                } elseif ($this->isNativeType($type)) {
48 8
                    $value = $this->convertNativeType($type, $value);
49
                } else {
50 5
                    $value = $this->convertObjectValue($type, $value);
51
                }
52
            }
53
54 15
            $propertyMetadata->setValue($object, $value);
55
        }
56 15
    }
57
    
58 9
    private function isNativeType(string $type)
59
    {
60 9
        return in_array($type, [
61 9
            'string',
62
            'array',
63
            'int',
64
            'integer',
65
            'float',
66
            'boolean',
67
            'bool',
68
            'DateTime',
69
            '\DateTime',
70
        ]);
71
    }
72
    
73 10
    private function isDecorated(string $type): bool
74
    {
75 10
        return (bool) preg_match('/(.*)((\<(.*)\>)|(\[\]))/', $type);
76
    }
77
    
78 8
    private function convertNativeType($type, $value)
79
    {
80
        switch ($type) {
81 8
            case 'int':
82 5
            case 'integer':
83 8
                return (int) $value;
84 5
            case 'string':
85 5
                return (string) $value;
86
            case 'boolean':
87
            case 'bool':
88
                return (bool) $value;
89
            case 'array':
90
                if (!is_array($value)) {
91
                    throw new RuntimeException('value is not array');
92
                }
93
                
94
                return $value;
95
            case 'DateTime':
96
            case '\DateTime':
97
                return new DateTime($value);
98
            default:
99
                return $value;
100
        }
101
    }
102
    
103 2
    private function convertDecorated(string $type, $value)
104
    {
105 2
        preg_match('/(?P<class>.*)((\<(?P<decoration>.*)\>)|(?P<brackets>\[\]))/', $type, $matches);
106
        
107 2
        $class      = isset($matches['brackets']) ? 'array' : $matches['class'];
108 2
        $decoration = isset($matches['brackets']) ? $matches['class'] : $matches['decoration'];
109
110
        switch ($class) {
111 2
            case 'array':
112 2
                if (!is_array($value)) {
113
                    throw new RuntimeException('value mapped as array is not array');
114
                }
115
116 2
                $data = [];
117
118 2
                foreach ($value as $item) {
119 2
                    $object = $this->convertObjectValue($decoration, $item);
120
121 2
                    $data[] = $object;
122
                }
123
124 2
                break;
125 1
            case 'DateTime':
126
            case '\DateTime':
127 1
                $data = DateTime::createFromFormat($decoration, $value);
128 1
                break;
129
        }
130
131 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...
132
    }
133
    
134 6
    private function convertObjectValue(string $type, array $data)
135
    {
136 6
        $metadata = $this->getObjectMetadata($type, $data);
137 6
        $object   = $metadata->reflection->newInstanceWithoutConstructor();
138
139 6
        $this->hydrate(
140 6
            $object, 
141 6
            $data
142
        );
143
144 6
        return $object;
145
    }
146
    
147 15
    private function getObjectMetadata($object, array $data): ClassMetadata
148
    {
149 15
        $metadata      = $this->metadataFactory->getMetadataForClass(is_string($object) ? $object : get_class($object));
150 15
        $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

150
        /** @scrutinizer ignore-call */ 
151
        $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

150
        /** @scrutinizer ignore-call */ 
151
        $discriminator = $metadata->getAnnotation(Discriminator::class);
Loading history...
151
152 15
        if ($discriminator instanceof Discriminator && isset($data[$discriminator->field])) {
153 3
            if (!isset($discriminator->map[$data[$discriminator->field]])) {
154
                throw new RuntimeException("no discrimination for {$data[$discriminator->field]}");
155
            }
156
157 3
            $type     = $discriminator->map[$data[$discriminator->field]];
158 3
            $metadata = $this->metadataFactory->getMetadataForClass($type);
159
        }
160
        
161 15
        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...
162
    }
163
}
164