Completed
Push — master ( 968957...ef4d61 )
by Viacheslav
02:06
created

JsonDiff::getModifiedNew()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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