Completed
Push — master ( 66aff4...321ed1 )
by John
02:40
created

ObjectHydrator::hydrateNode()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 42
Code Lines 25

Duplication

Lines 5
Ratio 11.9 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 5
loc 42
rs 4.909
cc 9
eloc 25
nc 9
nop 2
1
<?php declare(strict_types = 1);
2
/*
3
 * This file is part of the KleijnWeb\PhpApi\Hydrator package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace KleijnWeb\PhpApi\Hydrator;
10
11
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ArraySchema;
12
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ObjectSchema;
13
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ScalarSchema;
14
use KleijnWeb\PhpApi\Descriptions\Description\Schema\Schema;
15
use KleijnWeb\PhpApi\Hydrator\Exception\UnsupportedException;
16
17
/**
18
 * @author John Kleijn <[email protected]>
19
 */
20
class ObjectHydrator implements Hydrator
21
{
22
    /**
23
     * @var bool
24
     */
25
    private $is32Bit;
26
27
    /**
28
     * @var DateTimeSerializer
29
     */
30
    private $dateTimeSerializer;
31
32
    /**
33
     * @var ClassNameResolver
34
     */
35
    private $classNameResolver;
36
37
    /**
38
     * ObjectHydrator constructor.
39
     *
40
     * @param ClassNameResolver  $classNameResolver
41
     * @param DateTimeSerializer $dateTimeSerializer
42
     * @param bool               $is32Bit
43
     */
44
    public function __construct(
45
        ClassNameResolver $classNameResolver,
46
        DateTimeSerializer $dateTimeSerializer = null,
47
        $is32Bit = null
48
    ) {
49
50
        $this->is32Bit            = $is32Bit !== null ? $is32Bit : PHP_INT_SIZE === 4;
51
        $this->dateTimeSerializer = $dateTimeSerializer ?: new DateTimeSerializer();
52
        $this->classNameResolver  = $classNameResolver;
53
    }
54
55
    /**
56
     * @param mixed  $data
57
     * @param Schema $schema
58
     *
59
     * @return mixed
60
     */
61
    public function hydrate($data, Schema $schema)
62
    {
63
        return $this->hydrateNode($data, $schema);
64
    }
65
66
    /**
67
     * @param mixed  $data
68
     * @param Schema $schema
69
     *
70
     * @return mixed
71
     */
72
    public function dehydrate($data, Schema $schema)
73
    {
74
        return $this->dehydrateNode($data, $schema);
75
    }
76
77
    /**
78
     * @param mixed  $node
79
     * @param Schema $schema
80
     *
81
     * @return mixed
82
     */
83
    private function hydrateNode($node, Schema $schema)
84
    {
85 View Code Duplication
        if ($schema instanceof ArraySchema) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
86
            return array_map(function ($value) use ($schema) {
87
                return $this->hydrateNode($value, $schema->getItemsSchema());
88
            }, $node);
89
        }
90
        if ($schema instanceof ObjectSchema) {
91
            if (!$schema->hasComplexType()) {
92
                $object = clone $node;
93
                foreach ($node as $name => $value) {
94
                    if ($schema->hasPropertySchema($name)) {
95
                        $object->$name = $this->hydrateNode($value, $schema->getPropertySchema($name));
96
                    }
97
                }
98
99
                return $object;
100
            }
101
            $fqcn = $this->classNameResolver->resolve($schema->getComplexType()->getName());;
102
            $object    = unserialize(sprintf('O:%d:"%s":0:{}', strlen($fqcn), $fqcn));
103
            $reflector = new \ReflectionObject($object);
104
105
            foreach ($node as $name => $value) {
106
                if (!$reflector->hasProperty($name)) {
107
                    continue;
108
                }
109
110
                if ($schema->hasPropertySchema($name)) {
111
                    $value = $this->hydrateNode($value, $schema->getPropertySchema($name));
112
                }
113
114
                $attribute = $reflector->getProperty($name);
115
                $attribute->setAccessible(true);
116
                $attribute->setValue($object, $value);
117
            }
118
119
            return $object;
120
        }
121
122
        /** @var ScalarSchema $schema */
123
        return $this->castScalarValue($node, $schema);
124
    }
125
126
    /**
127
     * @param mixed  $node
128
     * @param Schema $schema
129
     *
130
     * @return mixed
131
     */
132
    private function dehydrateNode($node, Schema $schema)
133
    {
134
        if ($node instanceof \DateTimeInterface) {
135
            /** @var ScalarSchema $schema */
136
            return $this->dateTimeSerializer->serialize($node, $schema);
137
        }
138 View Code Duplication
        if ($schema instanceof ArraySchema) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
            return array_map(function ($value) use ($schema) {
140
                return $this->dehydrateNode($value, $schema->getItemsSchema());
141
            }, $node);
142
        }
143
        if ($schema instanceof ObjectSchema) {
144
            if (!$node instanceof \stdClass) {
145
                $class  = get_class($node);
146
                $offset = strlen($class) + 2;
147
                $data   = (array)$node;
148
                $array  = array_filter(array_combine(array_map(function ($k) use ($offset) {
149
                    return substr($k, $offset);
150
                }, array_keys($data)), array_values($data)));
151
                $node   = (object)$array;
152
            } else {
153
                $node = clone $node;
154
            }
155
156
            foreach ($node as $name => $value) {
157
                $node->$name = $schema->hasPropertySchema($name)
158
                    ? $this->dehydrateNode($value, $schema->getPropertySchema($name))
159
                    : $value;
160
            }
161
162
            return $node;
163
        }
164
165
        return $node;
166
    }
167
168
    /**
169
     * Cast a scalar value using the schema.
170
     *
171
     * @param mixed        $value
172
     * @param ScalarSchema $schema
173
     *
174
     * @return float|int|string|\DateTimeInterface
175
     * @throws UnsupportedException
176
     */
177
    private function castScalarValue($value, ScalarSchema $schema)
178
    {
179
        if ($schema->isType(Schema::TYPE_NUMBER)) {
180
            return ctype_digit($value) ? (int)$value : (float)$value;
181
        }
182
        if ($schema->isType(Schema::TYPE_INT)) {
183
            if ($this->is32Bit && $schema->hasFormat(Schema::FORMAT_INT64)) {
184
                throw new UnsupportedException("Operating system does not support 64 bit integers");
185
            }
186
187
            return (int)$value;
188
        }
189
        if ($schema->isDateTime()) {
190
            return $this->dateTimeSerializer->deserialize($value, $schema);
191
        }
192
193
        settype($value, $schema->getType());
194
195
        return $value;
196
    }
197
}
198