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

ObjectHydrator::isDecorated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

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 1
nc 1
nop 1
crap 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