Completed
Push — master ( 16f044...205892 )
by Viacheslav
10s
created

JsonDiff   D

Complexity

Total Complexity 60

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Test Coverage

Coverage 89.13%

Importance

Changes 0
Metric Value
wmc 60
dl 0
loc 359
ccs 123
cts 138
cp 0.8913
rs 4.2857
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getDiffCnt() 0 3 1
A getModifiedOriginal() 0 3 1
A getRemoved() 0 3 1
A getRemovedPaths() 0 3 1
A rearrange() 0 3 1
D rearrangeArray() 0 84 22
A getRearranged() 0 3 1
A getRemovedCnt() 0 3 1
D process() 0 83 22
A getPatch() 0 3 1
A getAdded() 0 3 1
A getModifiedNew() 0 3 1
A getAddedCnt() 0 3 1
A __construct() 0 13 2
A getModifiedCnt() 0 3 1
A getModifiedPaths() 0 3 1
A getAddedPaths() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like JsonDiff 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 JsonDiff, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Swaggest\JsonDiff;
4
5
use Swaggest\JsonDiff\JsonPatch\Add;
6
use Swaggest\JsonDiff\JsonPatch\Remove;
7
use Swaggest\JsonDiff\JsonPatch\Replace;
8
use Swaggest\JsonDiff\JsonPatch\Test;
9
10
class JsonDiff
11
{
12
    const REARRANGE_ARRAYS = 1;
13
    const STOP_ON_DIFF = 2;
14
15
    /**
16
     * Use URI Fragment Identifier Representation will be used (example: "#/c%25d").
17
     * If not set default JSON String Representation (example: "/c%d").
18
     */
19
    const JSON_URI_FRAGMENT_ID = 4;
20
21
    private $options = 0;
22
    private $original;
23
    private $new;
24
25
    private $added;
26
    private $addedCnt = 0;
27
    private $addedPaths = array();
28
29
    private $removed;
30
    private $removedCnt = 0;
31
    private $removedPaths = array();
32
33
    private $modifiedOriginal;
34
    private $modifiedNew;
35
    private $modifiedCnt = 0;
36
    private $modifiedPaths = array();
37
38
    private $path = '';
39
    private $pathItems = array();
40
41
    private $rearranged;
42
43
    /** @var JsonPatch */
44
    private $jsonPatch;
45
46
    /**
47
     * Processor constructor.
48
     * @param mixed $original
49
     * @param mixed $new
50
     * @param int $options
51
     * @throws Exception
52
     */
53 24
    public function __construct($original, $new, $options = 0)
54
    {
55 24
        $this->jsonPatch = new JsonPatch();
56
57 24
        $this->original = $original;
58 24
        $this->new = $new;
59 24
        $this->options = $options;
60
61 24
        if ($options & self::JSON_URI_FRAGMENT_ID) {
62 1
            $this->path = '#';
63
        }
64
65 24
        $this->rearranged = $this->rearrange();
66 24
    }
67
68
    /**
69
     * Returns total number of differences
70
     * @return int
71
     */
72 15
    public function getDiffCnt()
73
    {
74 15
        return $this->addedCnt + $this->modifiedCnt + $this->removedCnt;
75
    }
76
77
    /**
78
     * Returns removals as partial value of original.
79
     * @return mixed
80
     */
81 5
    public function getRemoved()
82
    {
83 5
        return $this->removed;
84
    }
85
86
    /**
87
     * Returns list of `JSON` paths that were removed from original.
88
     * @return array
89
     */
90 5
    public function getRemovedPaths()
91
    {
92 5
        return $this->removedPaths;
93
    }
94
95
    /**
96
     * Returns number of removals.
97
     * @return int
98
     */
99 4
    public function getRemovedCnt()
100
    {
101 4
        return $this->removedCnt;
102
    }
103
104
    /**
105
     * Returns additions as partial value of new.
106
     * @return mixed
107
     */
108 2
    public function getAdded()
109
    {
110 2
        return $this->added;
111
    }
112
113
    /**
114
     * Returns number of additions.
115
     * @return int
116
     */
117 1
    public function getAddedCnt()
118
    {
119 1
        return $this->addedCnt;
120
    }
121
122
    /**
123
     * Returns list of `JSON` paths that were added to new.
124
     * @return array
125
     */
126 2
    public function getAddedPaths()
127
    {
128 2
        return $this->addedPaths;
129
    }
130
131
    /**
132
     * Returns changes as partial value of original.
133
     * @return mixed
134
     */
135 2
    public function getModifiedOriginal()
136
    {
137 2
        return $this->modifiedOriginal;
138
    }
139
140
    /**
141
     * Returns changes as partial value of new.
142
     * @return mixed
143
     */
144 2
    public function getModifiedNew()
145
    {
146 2
        return $this->modifiedNew;
147
    }
148
149
    /**
150
     * Returns number of changes.
151
     * @return int
152
     */
153 1
    public function getModifiedCnt()
154
    {
155 1
        return $this->modifiedCnt;
156
    }
157
158
    /**
159
     * Returns list of `JSON` paths that were changed from original to new.
160
     * @return array
161
     */
162 2
    public function getModifiedPaths()
163
    {
164 2
        return $this->modifiedPaths;
165
    }
166
167
    /**
168
     * Returns new value, rearranged with original order.
169
     * @return array|object
170
     */
171 4
    public function getRearranged()
172
    {
173 4
        return $this->rearranged;
174
    }
175
176
    /**
177
     * Returns JsonPatch of difference
178
     * @return JsonPatch
179
     */
180 3
    public function getPatch()
181
    {
182 3
        return $this->jsonPatch;
183
    }
184
185
    /**
186
     * @return array|null|object|\stdClass
187
     * @throws Exception
188
     */
189 24
    private function rearrange()
190
    {
191 24
        return $this->process($this->original, $this->new);
192
    }
193
194
    /**
195
     * @param mixed $original
196
     * @param mixed $new
197
     * @return array|null|object|\stdClass
198
     * @throws Exception
199
     */
200 24
    private function process($original, $new)
201
    {
202
        if (
203 24
            (!$original instanceof \stdClass && !is_array($original))
204 24
            || (!$new instanceof \stdClass && !is_array($new))
205
        ) {
206 23
            if ($original !== $new) {
207 10
                $this->modifiedCnt++;
208 10
                if ($this->options & self::STOP_ON_DIFF) {
209 3
                    return null;
210
                }
211 7
                $this->modifiedPaths [] = $this->path;
212
213 7
                $this->jsonPatch->op(new Test($this->path, $original));
214 7
                $this->jsonPatch->op(new Replace($this->path, $new));
215
216 7
                JsonPointer::add($this->modifiedOriginal, $this->pathItems, $original);
217 7
                JsonPointer::add($this->modifiedNew, $this->pathItems, $new);
218
219
            }
220 20
            return $new;
221
        }
222
223
        if (
224 16
            ($this->options & self::REARRANGE_ARRAYS)
225 16
            && is_array($original) && is_array($new)
226
        ) {
227 7
            $new = $this->rearrangeArray($original, $new);
228
        }
229
230 16
        $newArray = $new instanceof \stdClass ? get_object_vars($new) : $new;
231 16
        $newOrdered = array();
232
233 16
        $originalKeys = $original instanceof \stdClass ? get_object_vars($original) : $original;
234
235 16
        foreach ($originalKeys as $key => $originalValue) {
236 16
            if ($this->options & self::STOP_ON_DIFF) {
237 5
                if ($this->modifiedCnt || $this->addedCnt || $this->removedCnt) {
238 1
                    return null;
239
                }
240
            }
241
242 16
            $path = $this->path;
243 16
            $pathItems = $this->pathItems;
244 16
            $this->path .= '/' . JsonPointer::escapeSegment($key, $this->options & self::JSON_URI_FRAGMENT_ID);
245 16
            $this->pathItems[] = $key;
246
247 16
            if (array_key_exists($key, $newArray)) {
248 15
                $newOrdered[$key] = $this->process($originalValue, $newArray[$key]);
249 15
                unset($newArray[$key]);
250
            } else {
251 11
                $this->removedCnt++;
252 11
                if ($this->options & self::STOP_ON_DIFF) {
253 1
                    return null;
254
                }
255 10
                $this->removedPaths [] = $this->path;
256
257 10
                $this->jsonPatch->op(new Remove($this->path));
258
259 10
                JsonPointer::add($this->removed, $this->pathItems, $originalValue);
260
            }
261 15
            $this->path = $path;
262 15
            $this->pathItems = $pathItems;
263
        }
264
265
        // additions
266 14
        foreach ($newArray as $key => $value) {
267 8
            $this->addedCnt++;
268 8
            if ($this->options & self::STOP_ON_DIFF) {
269
                return null;
270
            }
271 8
            $newOrdered[$key] = $value;
272 8
            $path = $this->path . '/' . JsonPointer::escapeSegment($key, $this->options & self::JSON_URI_FRAGMENT_ID);
273 8
            $pathItems = $this->pathItems;
274 8
            $pathItems[] = $key;
275 8
            JsonPointer::add($this->added, $pathItems, $value);
276 8
            $this->addedPaths [] = $path;
277
278 8
            $this->jsonPatch->op(new Add($path, $value));
279
280
        }
281
282 14
        return is_array($new) ? $newOrdered : (object)$newOrdered;
283
    }
284
285 7
    private function rearrangeArray(array $original, array $new)
286
    {
287 7
        $first = reset($original);
288 7
        if (!$first instanceof \stdClass) {
289 5
            return $new;
290
        }
291
292 7
        $uniqueKey = false;
293 7
        $uniqueIdx = array();
294
295
        // find unique key for all items
296 7
        $f = get_object_vars($first);
297 7
        foreach ($f as $key => $value) {
298 7
            if (is_array($value) || $value instanceof \stdClass) {
299
                continue;
300
            }
301
302 7
            $keyIsUnique = true;
303 7
            $uniqueIdx = array();
304 7
            foreach ($original as $item) {
305 7
                if (!$item instanceof \stdClass) {
306
                    return $new;
307
                }
308 7
                if (!isset($item->$key)) {
309
                    $keyIsUnique = false;
310
                    break;
311
                }
312 7
                $value = $item->$key;
313 7
                if ($value instanceof \stdClass || is_array($value)) {
314
                    $keyIsUnique = false;
315
                    break;
316
                }
317
318 7
                if (isset($uniqueIdx[$value])) {
319
                    $keyIsUnique = false;
320
                    break;
321
                }
322 7
                $uniqueIdx[$value] = true;
323
            }
324
325 7
            if ($keyIsUnique) {
326 7
                $uniqueKey = $key;
327 7
                break;
328
            }
329
        }
330
331 7
        if ($uniqueKey) {
332 7
            $newIdx = array();
333 7
            foreach ($new as $item) {
334 7
                if (!$item instanceof \stdClass) {
335
                    return $new;
336
                }
337
338 7
                if (!property_exists($item, $uniqueKey)) {
339
                    return $new;
340
                }
341
342 7
                $value = $item->$uniqueKey;
343
344 7
                if ($value instanceof \stdClass || is_array($value)) {
345
                    return $new;
346
                }
347
348 7
                if (isset($newIdx[$value])) {
349
                    return $new;
350
                }
351
352 7
                $newIdx[$value] = $item;
353
            }
354
355 7
            $newRearranged = array();
356 7
            foreach ($uniqueIdx as $key => $item) {
357 7
                if (isset($newIdx[$key])) {
358 7
                    $newRearranged [] = $newIdx[$key];
359 7
                    unset($newIdx[$key]);
360
                }
361
            }
362 7
            foreach ($newIdx as $item) {
363
                $newRearranged [] = $item;
364
            }
365 7
            return $newRearranged;
366
        }
367
368
        return $new;
369
    }
370
}