Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.00% |
38 / 40 |
|
85.71% |
6 / 7 |
CRAP | |
0.00% |
0 / 1 |
ZObjectMapDiffer | |
95.00% |
38 / 40 |
|
85.71% |
6 / 7 |
23 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
doDiff | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
getAllKeys | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getDiffOpForElement | |
85.71% |
12 / 14 |
|
0.00% |
0 / 1 |
8.19 | |||
getDiffForArrays | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
isAssociative | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
arrayDiffAssoc | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | /** |
3 | * WikiLambda ZObjectMapDiffer. Implements doDiff to calculate the diff |
4 | * between two associative arrays, or maps. |
5 | * |
6 | * @file |
7 | * @ingroup Extensions |
8 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
9 | * @license MIT |
10 | */ |
11 | |
12 | namespace MediaWiki\Extension\WikiLambda\Diff; |
13 | |
14 | use Diff\Comparer\ValueComparer; |
15 | use Diff\DiffOp\Diff\Diff; |
16 | use Diff\DiffOp\DiffOp; |
17 | use Diff\DiffOp\DiffOpAdd; |
18 | use Diff\DiffOp\DiffOpChange; |
19 | use Diff\DiffOp\DiffOpRemove; |
20 | use Exception; |
21 | |
22 | class ZObjectMapDiffer { |
23 | |
24 | private ZObjectListDiffer $listDiffer; |
25 | private ValueComparer $valueComparer; |
26 | |
27 | /** |
28 | * Creates a ZObjectMapDiffer object |
29 | * |
30 | * @param ZObjectListDiffer $listDiffer |
31 | * @param ValueComparer $comparer |
32 | */ |
33 | public function __construct( ZObjectListDiffer $listDiffer, ValueComparer $comparer ) { |
34 | $this->listDiffer = $listDiffer; |
35 | $this->valueComparer = $comparer; |
36 | } |
37 | |
38 | /** |
39 | * Computes the diff between two ZObject associate arrays. |
40 | * |
41 | * @param array $oldValues The first array |
42 | * @param array $newValues The second array |
43 | * |
44 | * @throws Exception |
45 | * @return DiffOp[] |
46 | */ |
47 | public function doDiff( array $oldValues, array $newValues ): array { |
48 | $newSet = $this->arrayDiffAssoc( $newValues, $oldValues ); |
49 | $oldSet = $this->arrayDiffAssoc( $oldValues, $newValues ); |
50 | |
51 | $diffSet = []; |
52 | |
53 | foreach ( $this->getAllKeys( $oldSet, $newSet ) as $key ) { |
54 | $diffOp = $this->getDiffOpForElement( $key, $oldSet, $newSet ); |
55 | |
56 | if ( $diffOp !== null ) { |
57 | $diffSet[$key] = $diffOp; |
58 | } |
59 | } |
60 | |
61 | return $diffSet; |
62 | } |
63 | |
64 | /** |
65 | * Returns the union of all keys present in old and new sets |
66 | * |
67 | * @param array $oldSet |
68 | * @param array $newSet |
69 | * @return string[] |
70 | */ |
71 | private function getAllKeys( array $oldSet, array $newSet ): array { |
72 | return array_unique( array_merge( |
73 | array_keys( $oldSet ), |
74 | array_keys( $newSet ) |
75 | ) ); |
76 | } |
77 | |
78 | /** |
79 | * Returns the DiffOp found for the old and new values of a given key |
80 | * or null if no diffs were found. |
81 | * |
82 | * @param string $key |
83 | * @param array $oldSet |
84 | * @param array $newSet |
85 | * @return DiffOp|null |
86 | */ |
87 | private function getDiffOpForElement( $key, array $oldSet, array $newSet ) { |
88 | $hasOld = array_key_exists( $key, $oldSet ); |
89 | $hasNew = array_key_exists( $key, $newSet ); |
90 | |
91 | if ( $hasOld && $hasNew ) { |
92 | $oldValue = $oldSet[$key]; |
93 | $newValue = $newSet[$key]; |
94 | |
95 | if ( is_array( $oldValue ) && is_array( $newValue ) ) { |
96 | $diffOp = $this->getDiffForArrays( $oldValue, $newValue ); |
97 | return $diffOp->isEmpty() ? null : $diffOp; |
98 | } else { |
99 | return new DiffOpChange( $oldValue, $newValue ); |
100 | } |
101 | } elseif ( $hasOld ) { |
102 | return new DiffOpRemove( $oldSet[$key] ); |
103 | } elseif ( $hasNew ) { |
104 | return new DiffOpAdd( $newSet[$key] ); |
105 | } |
106 | |
107 | return null; |
108 | } |
109 | |
110 | /** |
111 | * Calculates the Diff between two arrays, calling ZObjectMapDiffer |
112 | * if the arrays are associative or ZObjectListDiffer if they are not |
113 | * |
114 | * @param array $old |
115 | * @param array $new |
116 | * @return Diff |
117 | */ |
118 | private function getDiffForArrays( array $old, array $new ): Diff { |
119 | if ( $this->isAssociative( $old ) || $this->isAssociative( $new ) ) { |
120 | return new Diff( $this->doDiff( $old, $new ), true ); |
121 | } |
122 | |
123 | return new Diff( $this->listDiffer->doDiff( $old, $new ), false ); |
124 | } |
125 | |
126 | /** |
127 | * Returns if an array is associative or not. |
128 | * |
129 | * @param array $array |
130 | * @return bool |
131 | */ |
132 | private function isAssociative( array $array ): bool { |
133 | foreach ( $array as $key => $value ) { |
134 | if ( is_string( $key ) ) { |
135 | return true; |
136 | } |
137 | } |
138 | |
139 | return false; |
140 | } |
141 | |
142 | /** |
143 | * Similar to the native array_diff_assoc function, except that it will |
144 | * spot differences between array values. Very weird the native |
145 | * function just ignores these... |
146 | * |
147 | * @see http://php.net/manual/en/function.array-diff-assoc.php |
148 | * @param array $from |
149 | * @param array $to |
150 | * @return array |
151 | */ |
152 | private function arrayDiffAssoc( array $from, array $to ): array { |
153 | $diff = []; |
154 | |
155 | foreach ( $from as $key => $value ) { |
156 | if ( !array_key_exists( $key, $to ) || !$this->valueComparer->valuesAreEqual( $to[$key], $value ) ) { |
157 | $diff[$key] = $value; |
158 | } |
159 | } |
160 | |
161 | return $diff; |
162 | } |
163 | |
164 | } |