Passed
Push — master ( eb7ffa...cb92e6 )
by Alberto
55s queued 11s
created

JsonApiSerializer::serializeString()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

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