ObjectHydrator   B
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Test Coverage

Coverage 86.49%

Importance

Changes 0
Metric Value
wmc 36
dl 0
loc 132
ccs 64
cts 74
cp 0.8649
rs 8.8
c 0
b 0
f 0

6 Methods

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

139
        /** @scrutinizer ignore-call */ 
140
        $discriminator = $metadata->getAnnotation(Discriminator::class);
Loading history...
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

139
        /** @scrutinizer ignore-call */ 
140
        $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...
140
141 29
        if ($discriminator instanceof Discriminator && isset($data[$discriminator->field])) {
142 3
            if (!isset($discriminator->map[$data[$discriminator->field]])) {
143
                throw new RuntimeException("no discrimination for {$data[$discriminator->field]}");
144
            }
145
146 3
            $type     = $discriminator->map[$data[$discriminator->field]];
147 3
            $metadata = $this->metadataFactory->getMetadataForClass($type);
148
        }
149
        
150 29
        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...
151
    }
152
}
153