Completed
Push — master ( 96799a...ba7e45 )
by John
02:18
created

ObjectHydrator::shouldTreatAsArray()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 3
eloc 2
nc 3
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\AnySchema;
12
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ArraySchema;
13
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ObjectSchema;
14
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ScalarSchema;
15
use KleijnWeb\PhpApi\Descriptions\Description\Schema\Schema;
16
use KleijnWeb\PhpApi\Hydrator\Exception\UnsupportedException;
17
18
/**
19
 * @author John Kleijn <[email protected]>
20
 */
21
class ObjectHydrator implements Hydrator
22
{
23
    /**
24
     * @var AnySchema
25
     */
26
    private $anySchema;
27
28
    /**
29
     * @var bool
30
     */
31
    private $is32Bit;
32
33
    /**
34
     * @var DateTimeSerializer
35
     */
36
    private $dateTimeSerializer;
37
38
    /**
39
     * @var ClassNameResolver
40
     */
41
    private $classNameResolver;
42
43
    /**
44
     * ObjectHydrator constructor.
45
     *
46
     * @param ClassNameResolver  $classNameResolver
47
     * @param DateTimeSerializer $dateTimeSerializer
48
     * @param bool               $is32Bit
49
     */
50
    public function __construct(
51
        ClassNameResolver $classNameResolver,
52
        DateTimeSerializer $dateTimeSerializer = null,
53
        $is32Bit = null
54
    ) {
55
56
        $this->anySchema          = new AnySchema();
57
        $this->is32Bit            = $is32Bit !== null ? $is32Bit : PHP_INT_SIZE === 4;
58
        $this->dateTimeSerializer = $dateTimeSerializer ?: new DateTimeSerializer();
59
        $this->classNameResolver  = $classNameResolver;
60
    }
61
62
    /**
63
     * @param mixed  $data
64
     * @param Schema $schema
65
     *
66
     * @return mixed
67
     */
68
    public function hydrate($data, Schema $schema)
69
    {
70
        return $this->hydrateNode($data, $schema);
71
    }
72
73
    /**
74
     * @param mixed  $data
75
     * @param Schema $schema
76
     *
77
     * @return mixed
78
     */
79
    public function dehydrate($data, Schema $schema)
80
    {
81
        return $this->dehydrateNode($data, $schema);
82
    }
83
84
    /**
85
     * @param mixed  $node
86
     * @param Schema $schema
87
     *
88
     * @return mixed
89
     */
90
    private function hydrateNode($node, Schema $schema)
91
    {
92 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...
93
            return array_map(function ($value) use ($schema) {
94
                return $this->hydrateNode($value, $schema->getItemsSchema());
95
            }, $node);
96
        }
97
        if ($schema instanceof ObjectSchema) {
98
            if (!$schema->hasComplexType()) {
99
                $object = clone $node;
100
                foreach ($node as $name => $value) {
101
                    if ($schema->hasPropertySchema($name)) {
102
                        $object->$name = $this->hydrateNode($value, $schema->getPropertySchema($name));
103
                    }
104
                }
105
106
                return $object;
107
            }
108
            $fqcn = $this->classNameResolver->resolve($schema->getComplexType()->getName());;
109
            $object    = unserialize(sprintf('O:%d:"%s":0:{}', strlen($fqcn), $fqcn));
110
            $reflector = new \ReflectionObject($object);
111
112
            foreach ($node as $name => $value) {
113
                if (!$reflector->hasProperty($name)) {
114
                    continue;
115
                }
116
117
                if ($schema->hasPropertySchema($name)) {
118
                    $value = $this->hydrateNode($value, $schema->getPropertySchema($name));
119
                }
120
121
                $attribute = $reflector->getProperty($name);
122
                $attribute->setAccessible(true);
123
                $attribute->setValue($object, $value);
124
            }
125
126
            return $object;
127
        }
128
129
        /** @var ScalarSchema $schema */
130
        return $this->castScalarValue($node, $schema);
131
    }
132
133
    /**
134
     * @param mixed  $node
135
     * @param Schema $schema
136
     *
137
     * @return mixed
138
     */
139
    private function dehydrateNode($node, Schema $schema)
140
    {
141
        if ($node instanceof \DateTimeInterface) {
142
            return $this->dateTimeSerializer->serialize($node, $schema);
143
        }
144
        if ($this->shouldTreatAsArray($node, $schema)) {
145 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...
146
                return array_map(function ($value) use ($schema) {
147
                    return $this->dehydrateNode($value, $schema->getItemsSchema());
148
                }, $node);
149
            }
150
151
            return array_map(function ($value) {
152
                return $this->dehydrateNode($value, $this->anySchema);
153
            }, $node);
154
        }
155
        if ($this->shouldTreatAsObject($node, $schema)) {
156
            if (!$node instanceof \stdClass) {
157
                $class  = get_class($node);
158
                $offset = strlen($class) + 2;
159
                $data   = (array)$node;
160
                $array  = array_filter(array_combine(array_map(function ($k) use ($offset) {
161
                    return substr($k, $offset);
162
                }, array_keys($data)), array_values($data)));
163
                $node   = (object)$array;
164
            } else {
165
                $node = clone $node;
166
            }
167
            foreach ($node as $name => $value) {
168
                if ($schema instanceof ObjectSchema) {
169
                    $valueSchema = $schema->hasPropertySchema($name)
170
                        ? $schema->getPropertySchema($name)
171
                        : $this->anySchema;
172
                }
173
                $node->$name = $this->dehydrateNode($value, isset($valueSchema) ? $valueSchema : $this->anySchema);
174
            }
175
176
            return $node;
177
        }
178
179
        return $node;
180
    }
181
182
    /**
183
     * Cast a scalar value using the schema.
184
     *
185
     * @param mixed        $value
186
     * @param ScalarSchema $schema
187
     *
188
     * @return float|int|string|\DateTimeInterface
189
     * @throws UnsupportedException
190
     */
191
    private function castScalarValue($value, ScalarSchema $schema)
192
    {
193
        if ($schema->isType(Schema::TYPE_NUMBER)) {
194
            return ctype_digit($value) ? (int)$value : (float)$value;
195
        }
196
        if ($schema->isType(Schema::TYPE_INT)) {
197
            if ($this->is32Bit && $schema->hasFormat(Schema::FORMAT_INT64)) {
198
                throw new UnsupportedException("Operating system does not support 64 bit integers");
199
            }
200
201
            return (int)$value;
202
        }
203
        if ($schema->isDateTime()) {
204
            return $this->dateTimeSerializer->deserialize($value, $schema);
205
        }
206
207
        settype($value, $schema->getType());
208
209
        return $value;
210
    }
211
212
    /**
213
     * @param mixed  $node
214
     * @param Schema $schema
215
     *
216
     * @return bool
217
     */
218
    private function shouldTreatAsObject($node, Schema $schema): bool
219
    {
220
        return $schema instanceof ObjectSchema || $schema instanceof AnySchema && is_object($node);
0 ignored issues
show
Bug introduced by
The class KleijnWeb\PhpApi\Descrip...iption\Schema\AnySchema does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
221
    }
222
223
    /**
224
     * @param mixed  $node
225
     * @param Schema $schema
226
     *
227
     * @return bool
228
     */
229
    private function shouldTreatAsArray($node, Schema $schema): bool
230
    {
231
        return $schema instanceof ArraySchema || $schema instanceof AnySchema && is_array($node);
0 ignored issues
show
Bug introduced by
The class KleijnWeb\PhpApi\Descrip...iption\Schema\AnySchema does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
232
    }
233
}
234