Completed
Push — devel ( a59919...df32c1 )
by Alex
8s
created

ObjectNormalizer::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
crap 1
1
<?php
2
namespace PSB\Core\Serialization\Json;
3
4
5
use PSB\Core\Exception\JsonSerializerException;
6
use PSB\Core\Util\Guard;
7
8
class ObjectNormalizer
9
{
10
    /**
11
     * @var string
12
     */
13
    private $classAnnotation;
14
15
    /**
16
     * Maps objects to their corresponding index. Used to deconstruct cyclic references when normalizing.
17
     *
18
     * @var \SplObjectStorage
19
     */
20
    private $objectToIndex;
21
22
    /**
23
     * Maps indexes to their corresponding object. Used to reconstruct cyclic references when de-normalizing.
24
     *
25
     * @var array
26
     */
27
    private $indexToObject = [];
28
29
    /**
30
     * @var integer
31
     */
32
    private $objectIndex = 0;
33
34
35
    /**
36
     * @param string $classAnnotation
37
     */
38 21
    public function __construct($classAnnotation = '@type')
39
    {
40 21
        Guard::againstNullAndEmpty('classAnnotation', $classAnnotation);
41 21
        $this->classAnnotation = $classAnnotation;
42 21
    }
43
44
    /**
45
     * @param object $object
46
     *
47
     * @return array
48
     */
49 11
    public function normalize($object)
50
    {
51 11
        if (!is_object($object)) {
52 1
            throw new JsonSerializerException("Can only serialize objects.");
53
        }
54
55 10
        $this->reset();
56 10
        return $this->normalizeObject($object);
57
    }
58
59
    /**
60
     * @param array $data
61
     *
62
     * @return object
63
     */
64 10
    public function denormalize(array $data)
65
    {
66 10
        $this->reset();
67 10
        return $this->denormalizeData($data);
68
    }
69
70 19
    private function reset()
71
    {
72 19
        $this->objectToIndex = new \SplObjectStorage();
73 19
        $this->indexToObject = [];
74 19
        $this->objectIndex = 0;
75 19
    }
76
77
    /**
78
     * Extract the data from an object
79
     *
80
     * @param object $object
81
     *
82
     * @return array
83
     */
84 10
    private function normalizeObject($object)
85
    {
86 10
        if ($this->objectToIndex->contains($object)) {
87 2
            return [$this->classAnnotation => '@' . $this->objectToIndex[$object]];
88
        }
89 10
        $this->objectToIndex->attach($object, $this->objectIndex++);
90
91 10
        $normalizedObject = [$this->classAnnotation => get_class($object)];
92 10
        $normalizedObject += array_map([$this, 'normalizeValue'], $this->extractObjectProperties($object));
93
94 10
        return $normalizedObject;
95
    }
96
97
    /**
98
     * Parse the data to be json encoded
99
     *
100
     * @param mixed $value
101
     *
102
     * @return mixed
103
     * @throws JsonSerializerException
104
     */
105 8
    private function normalizeValue($value)
106
    {
107 8
        if (is_resource($value)) {
108
            throw new JsonSerializerException("Can't serialize PHP resources.");
109
        }
110
111 8
        if ($value instanceof \Closure) {
112
            throw new JsonSerializerException("Can't serialize closures.");
113
        }
114
115 8
        if (is_object($value)) {
116 4
            return $this->normalizeObject($value);
117
        }
118
119 7
        if (is_array($value)) {
120 1
            return array_map([$this, 'normalizeValue'], $value);
121
        }
122
123 6
        return $value;
124
    }
125
126
    /**
127
     * Returns an array containing the object's properties to values
128
     *
129
     * @param object $object
130
     *
131
     * @return array
132
     */
133 10
    private function extractObjectProperties($object)
134
    {
135 10
        $propertyToValue = [];
136
137 10
        if (method_exists($object, '__sleep')) {
138 1
            $properties = $object->__sleep();
139 1
            foreach ($properties as $property) {
140 1
                $propertyToValue[$property] = $object->$property;
141 1
            }
142
143 1
            return $propertyToValue;
144
        }
145
146
147 9
        $reflectedProperties = [];
148 9
        $ref = new \ReflectionClass($object);
149 9
        foreach ($ref->getProperties() as $property) {
150 2
            $property->setAccessible(true);
151 2
            $propertyToValue[$property->getName()] = $property->getValue($object);
152 2
            $reflectedProperties[] = $property->getName();
153 9
        }
154
155 9
        $dynamicProperties = array_diff(array_keys(get_object_vars($object)), $reflectedProperties);
156
157 9
        foreach ($dynamicProperties as $property) {
158 5
            $propertyToValue[$property] = $object->$property;
159 9
        }
160
161 9
        return $propertyToValue;
162
    }
163
164
    /**
165
     * Parse the json decode to convert to objects again
166
     *
167
     * @param mixed $data
168
     *
169
     * @return mixed
170
     */
171 10
    private function denormalizeData($data)
172
    {
173 10
        if (is_scalar($data) || $data === null) {
174 4
            return $data;
175
        }
176
177 10
        if (isset($data[$this->classAnnotation])) {
178 10
            return $this->denormalizeObject($data);
1 ignored issue
show
Bug introduced by
It seems like $data defined by parameter $data on line 171 can also be of type object; however, PSB\Core\Serialization\J...er::denormalizeObject() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
179
        }
180
181 1
        return array_map([$this, 'denormalizeData'], $data);
182
    }
183
184
    /**
185
     * Convert the serialized array into an object
186
     *
187
     * @param array $data
188
     *
189
     * @return object
190
     * @throws JsonSerializerException
191
     */
192 10
    private function denormalizeObject(array $data)
193
    {
194 10
        $className = $data[$this->classAnnotation];
195 10
        unset($data[$this->classAnnotation]);
196
197 10
        if ($className[0] === '@') {
198 2
            $index = substr($className, 1);
199 2
            return $this->indexToObject[$index];
200
        }
201
202 10
        if (!class_exists($className)) {
203 1
            throw new JsonSerializerException("Unable to find class $className for deserialization.");
204
        }
205
206 9
        if ($className === 'DateTime') {
207 1
            $object = $this->denormalizeDateTime($className, $data);
208 1
            $this->indexToObject[$this->objectIndex++] = $object;
209 1
            return $object;
210
        }
211
212 8
        $ref = new \ReflectionClass($className);
213 8
        $object = $ref->newInstanceWithoutConstructor();
214 8
        $this->indexToObject[$this->objectIndex++] = $object;
215 8
        foreach ($data as $property => $propertyValue) {
216 6
            if ($ref->hasProperty($property)) {
217 2
                $propRef = $ref->getProperty($property);
218 2
                $propRef->setAccessible(true);
219 2
                $propRef->setValue($object, $this->denormalizeData($propertyValue));
220 2
            } else {
221 4
                $object->$property = $this->denormalizeData($propertyValue);
222
            }
223 8
        }
224
225 8
        if (method_exists($object, '__wakeup')) {
226 1
            $object->__wakeup();
227 1
        }
228
229 8
        return $object;
230
    }
231
232
    /**
233
     * @param string $className
234
     * @param array  $attributes
235
     *
236
     * @return \DateTime
237
     */
238 1
    private function denormalizeDateTime($className, array $attributes)
239
    {
240 1
        $obj = (object)$attributes;
241 1
        $serialized = preg_replace(
242 1
            '|^O:\d+:"\w+":|',
243 1
            'O:' . strlen($className) . ':"' . $className . '":',
244 1
            serialize($obj)
245 1
        );
246 1
        return unserialize($serialized);
247
    }
248
}
249