Passed
Pull Request — master (#6)
by Alberto
02:41
created

JsonApiDeserializer   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Test Coverage

Coverage 89.43%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 124
dl 0
loc 309
ccs 110
cts 123
cp 0.8943
rs 8.64
c 6
b 0
f 0
wmc 47

10 Methods

Rating   Name   Duplication   Size   Complexity  
B completeRelationship() 0 44 8
A parseRelationship() 0 30 5
A __invoke() 0 5 1
A parseData() 0 17 3
A keepReferenceKeys() 0 18 5
A isReference() 0 7 2
A deserialize() 0 8 1
A moveReferences() 0 18 4
A deserializeToString() 0 15 3
C unnestRelationships() 0 53 15

How to fix   Complexity   

Complex Class

Complex classes like JsonApiDeserializer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JsonApiDeserializer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Facile\JsonApiPhp\Serialization;
6
7
use RuntimeException;
8
9
class JsonApiDeserializer implements JsonApiDeserializerInterface
10
{
11
    /** @var string */
12
    private const REFERENCE_KEYS_TYPE = 'type';
13
14
    /** @var string */
15
    private const REFERENCE_KEYS_ID = 'id';
16
17
    /** @var bool */
18
    private $flattenedRelationships = self::DEFAULT_FLATTENED_RELATIONSHIPS;
19
20
    /** @var array */
21
    private $referencesContainer = [];
22
23
    use JsonApiSerializerDeserializerTrait;
24
25
    /**
26
     * @param array $elements
27
     * @param bool $flattenedRelationships
28
     *
29
     * @return array
30
     */
31 4
    public function deserialize(
32
        array $elements,
33
        bool $flattenedRelationships = self::DEFAULT_FLATTENED_RELATIONSHIPS
34
    ): array {
35 4
        $this->flattenedRelationships = $flattenedRelationships;
36 4
        $this->referencesContainer = self::moveReferences($elements);
37
38 4
        return $this->parseData($elements[self::REFERENCE_DATA] ?? []);
39
    }
40
41
    /**
42
     * @param string $jsonApiString
43
     * @param bool $flattenedRelationships
44
     *
45
     * @throws RuntimeException
46
     *
47
     * @return string
48
     */
49 4
    public function deserializeToString(
50
        string $jsonApiString,
51
        bool $flattenedRelationships = self::DEFAULT_FLATTENED_RELATIONSHIPS
52
    ): string {
53 4
        $elements = json_decode($jsonApiString, true);
54 4
        if (null === $elements) {
55
            throw new RuntimeException('Not valid JSON string');
56
        }
57
58 4
        $outputString = json_encode($this->deserialize($elements, $flattenedRelationships));
59 4
        if (false === $outputString) {
60
            throw new RuntimeException('Error during JSON encoding of the object');
61
        }
62
63 4
        return $outputString;
64
    }
65
66
    /**
67
     * @param array $elements
68
     * @param bool $flattenedRelationships
69
     *
70
     * @return array
71
     */
72
    public function __invoke(
73
        array $elements,
74
        bool $flattenedRelationships = self::DEFAULT_FLATTENED_RELATIONSHIPS
75
    ): array {
76
        return $this->deserialize($elements, $flattenedRelationships);
77
    }
78
79
    /**
80
     * @param mixed $element
81
     *
82
     * @return bool
83
     */
84 4
    protected static function isReference($element): bool
85
    {
86 4
        if (false === is_array($element)) {
87
            return false;
88
        }
89
90 4
        return true === array_keys_exists([self::REFERENCE_KEYS_TYPE, self::REFERENCE_KEYS_ID], $element);
91
    }
92
93
    /**
94
     * @param array $reference
95
     *
96
     * @return array
97
     */
98 4
    private static function keepReferenceKeys(array $reference): array
99
    {
100 4
        if (true === empty($reference)) {
101 3
            return $reference;
102
        }
103
104 4
        $keys = [];
105 4
        if (true === array_key_exists(self::REFERENCE_KEYS_TYPE, $reference)) {
106 4
            $keys['_' . self::REFERENCE_KEYS_TYPE] = $reference[self::REFERENCE_KEYS_TYPE];
107
        }
108
109 4
        if (true === array_key_exists(self::REFERENCE_KEYS_ID, $reference)) {
110 4
            $keys['_' . self::REFERENCE_KEYS_ID] = is_numeric($reference[self::REFERENCE_KEYS_ID])
111 4
                ? (int) $reference[self::REFERENCE_KEYS_ID]
112
                : $reference[self::REFERENCE_KEYS_ID];
113
        }
114
115 4
        return $keys;
116
    }
117
118
    /**
119
     * @param array $elements
120
     *
121
     * @return array
122
     */
123 4
    private static function moveReferences(array &$elements): array
124
    {
125 4
        $references = [];
126 4
        if (false === array_key_exists(self::REFERENCE_INCLUDED, $elements)) {
127
            return $references;
128
        }
129
130 4
        foreach ($elements[self::REFERENCE_INCLUDED] as $reference) {
131 4
            if (false === array_keys_exists([self::REFERENCE_KEYS_TYPE, self::REFERENCE_KEYS_ID], $reference)) {
132
                continue;
133
            }
134
135 4
            $references[$reference[self::REFERENCE_KEYS_TYPE]][$reference[self::REFERENCE_KEYS_ID]] = $reference;
136
        }
137
138 4
        unset($elements[self::REFERENCE_INCLUDED]);
139
140 4
        return $references;
141
    }
142
143
    /**
144
     * @param array $relationships
145
     *
146
     * @return array
147
     */
148 4
    private static function unnestRelationships(array $relationships): array
149
    {
150 4
        if (true === empty($relationships)) {
151 4
            return $relationships;
152
        }
153
154 4
        foreach ($relationships as $path => $relation) {
155 4
            if (self::REFERENCE_DATA === $path
156 4
                || self::REFERENCE_ATTRIBUTES === $path
157 4
                || self::REFERENCE_RELATIONSHIPS === $path
158 4
                || self::REFERENCE_KEYS_ID === $path
159 4
                || self::REFERENCE_KEYS_TYPE === $path
160
            ) {
161
                continue;
162
            }
163
164 4
            if (false === is_string($path)) {
165
                continue;
166
            }
167
168 4
            $pathParts = explode(self::NESTED_SEPARATOR, $path);
169 4
            if (count($pathParts) <= 1) {
170 4
                continue;
171
            }
172
173 2
            $counter = 0;
174 2
            foreach ($pathParts as $pathPart) {
175 2
                if (true === is_numeric($pathPart)) {
176
                    continue;
177
                }
178
179 2
                if (++$counter % 2 === 0) {
180 2
                    continue;
181
                }
182
183 2
                array_splice($pathParts, $counter, 0, self::REFERENCE_DATA);
184
            }
185
186 2
            $root = &$relationships[array_shift($pathParts)] ?? [];
187 2
            while (count($pathParts) >= 1) {
188 2
                $currentPath = array_shift($pathParts);
189 2
                $root = &$root[is_numeric($currentPath) ? (int) $currentPath : $currentPath] ?? [];
190
            }
191
192 2
            $root = array_merge_recursive(
193 2
                $root ?? [],
194
                $relation
195
            );
196
197 2
            unset($relationships[$path]);
198
        }
199
200 4
        return $relationships;
201
    }
202
203
    /**
204
     * @param array $relationship
205
     *
206
     * @return array|null
207
     */
208 4
    private function completeRelationship(array $relationship): ?array
209
    {
210 4
        $attributes = $relationship[self::REFERENCE_ATTRIBUTES] ?? [];
211 4
        $relationships = $relationship[self::REFERENCE_RELATIONSHIPS] ?? [];
212
213 4
        if (true === $this->flattenedRelationships) {
214 4
            $relationships = self::unnestRelationships($relationships);
215
        }
216
217 4
        if (false === empty($this->referencesContainer)
218 4
            && false === array_keys_exists([self::REFERENCE_ATTRIBUTES, self::REFERENCE_RELATIONSHIPS], $relationship)
219
        ) {
220 4
            $reference = self::isReference($relationship)
221 4
                ? $this->referencesContainer[$relationship[self::REFERENCE_KEYS_TYPE]][$relationship[self::REFERENCE_KEYS_ID]] ?? null
222 4
                : null;
223 4
            if (null !== $reference) {
224 4
                $attributes = $reference[self::REFERENCE_ATTRIBUTES] ?? [];
225 4
                $relationships = $reference[self::REFERENCE_RELATIONSHIPS] ?? [];
226
            }
227
        }
228
229 4
        unset($relationship[self::REFERENCE_ATTRIBUTES], $relationship[self::REFERENCE_RELATIONSHIPS]);
230
231 4
        $relationships = array_map(
232
            function ($key, $item) {
233 4
                return $this->parseRelationship($key, $item[self::REFERENCE_DATA] ?? $item);
234 4
            },
235 4
            array_keys($relationships),
236
            $relationships
237
        );
238
239 4
        $completedRelationship = array_merge_recursive(
240 4
            self::keepReferenceKeys($relationship),
241
            $attributes,
242 4
            true === empty($relationships)
243 4
                ? []
244 4
                : array_merge(
245 4
                    ...$relationships
246
                )
247
        );
248
249 4
        return true === empty($completedRelationship)
250 3
            ? null
251 4
            : $completedRelationship;
252
    }
253
254
    /**
255
     * @param string $key
256
     * @param array $relationship
257
     * @param bool $arrayOf
258
     *
259
     * @return array|null
260
     */
261 4
    private function parseRelationship(string $key, array $relationship, bool $arrayOf = false): ?array
262
    {
263 4
        if (true === empty($relationship)) {
264
            return $relationship;
265
        }
266
267 4
        $recursiveRelationships = array_filter(
268 4
            $relationship,
269
            static function ($item, $key) {
270 4
                return true === is_int($key) || (true === is_array($item) && true === array_key_exists(self::REFERENCE_DATA, $item));
271 4
            },
272 4
            ARRAY_FILTER_USE_BOTH
273
        );
274 4
        $normalRelationships = array_diff_key($relationship, $recursiveRelationships);
275
276
        return [
277 4
            $key => array_merge(
278 4
                $this->completeRelationship($normalRelationships) ?? [],
279 4
                ...array_map(
280
                    function ($subKey, $item) {
281 3
                        if (false === is_int($subKey)) {
282 2
                            return $this->parseRelationship($subKey, $item[self::REFERENCE_DATA], true);
283
                        }
284
285
                        return [
286 3
                            $subKey => $this->completeRelationship($item),
287
                        ];
288 4
                    },
289 4
                    array_keys($recursiveRelationships),
290 4
                    array_values($recursiveRelationships)
291
                )
292
            ),
293
        ];
294
    }
295
296
    /**
297
     * @param mixed $data
298
     *
299
     * @return mixed
300
     */
301 4
    private function parseData($data)
302
    {
303 4
        $completedRelationship = $this->completeRelationship($data);
304 4
        if (null === $completedRelationship) {
305 1
            if (false === is_array($data)) {
306
                return $data;
307
            }
308
309 1
            return array_map(
310
                function ($element) {
311 1
                    return $this->parseData($element);
312 1
                },
313
                $data
314
            );
315
        }
316
317 4
        return $completedRelationship;
318
    }
319
}
320