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

ObjectHydrator   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
wmc 35
dl 0
loc 128
ccs 63
cts 72
cp 0.875
rs 9
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
D hydrate() 0 29 10
A __construct() 0 3 1
C convertDecorated() 0 29 8
B getObjectMetadata() 0 15 5
A convertObjectValue() 0 11 1
C convertNativeType() 0 22 10
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