Completed
Push — master ( b0bc1b...c71b41 )
by Viacheslav
11:04
created

JsonDiff::rearrangeArray()   D

Complexity

Conditions 22
Paths 172

Size

Total Lines 84
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 50
nc 172
nop 2
dl 0
loc 84
rs 4.5263
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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