Passed
Push — master ( 419528...d82956 )
by Jeroen De
32s
created

MapDiffer::setComparisonCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace Diff\Differ;
6
7
use Diff\Comparer\StrictComparer;
8
use Diff\Comparer\ValueComparer;
9
use Diff\DiffOp\Diff\Diff;
10
use Diff\DiffOp\DiffOp;
11
use Diff\DiffOp\DiffOpAdd;
12
use Diff\DiffOp\DiffOpChange;
13
use Diff\DiffOp\DiffOpRemove;
14
use Exception;
15
use LogicException;
16
17
/**
18
 * Differ that does an associative diff between two arrays,
19
 * with the option to do this recursively.
20
 *
21
 * @since 0.4
22
 *
23
 * @license GPL-2.0+
24
 * @author Jeroen De Dauw < [email protected] >
25
 */
26
class MapDiffer implements Differ {
27
28
	/**
29
	 * @var bool
30
	 */
31
	private $recursively;
32
33
	/**
34
	 * @var Differ
35
	 */
36
	private $listDiffer;
37
38
	/**
39
	 * @var ValueComparer
40
	 */
41
	private $valueComparer;
42
43
	/**
44
	 * The third argument ($comparer) was added in 3.0
45
	 */
46 27
	public function __construct( bool $recursively = false, Differ $listDiffer = null, ValueComparer $comparer = null ) {
47 27
		$this->recursively = $recursively;
48 27
		$this->listDiffer = $listDiffer ?? new ListDiffer();
49 27
		$this->valueComparer = $comparer ?? new StrictComparer();
50 27
	}
51
52
	/**
53
	 * @see Differ::doDiff
54
	 *
55
	 * Computes the diff between two associate arrays.
56
	 *
57
	 * @since 0.4
58
	 *
59
	 * @param array $oldValues The first array
60
	 * @param array $newValues The second array
61
	 *
62
	 * @throws Exception
63
	 * @return DiffOp[]
64
	 */
65 27
	public function doDiff( array $oldValues, array $newValues ): array {
66 27
		$newSet = $this->arrayDiffAssoc( $newValues, $oldValues );
67 27
		$oldSet = $this->arrayDiffAssoc( $oldValues, $newValues );
68
69 27
		$diffSet = array();
70
71 27
		foreach ( $this->getAllKeys( $oldSet, $newSet ) as $key ) {
72 18
			$diffOp = $this->getDiffOpForElement( $key, $oldSet, $newSet );
73
74 18
			if ( $diffOp !== null ) {
75 18
				$diffSet[$key] = $diffOp;
76
			}
77
		}
78
79 27
		return $diffSet;
80
	}
81
82 27
	private function getAllKeys( $oldSet, $newSet ): array {
83 27
		return array_unique( array_merge(
84
			array_keys( $oldSet ),
85
			array_keys( $newSet )
86
		) );
87
	}
88
89 18
	private function getDiffOpForElement( $key, array $oldSet, array $newSet ) {
90 18
		$hasOld = array_key_exists( $key, $oldSet );
91 18
		$hasNew = array_key_exists( $key, $newSet );
92
93 18
		if ( $this->recursively ) {
94 10
			$diffOp = $this->getDiffOpForElementRecursively( $key, $oldSet, $newSet );
95
96 10
			if ( $diffOp !== null ) {
97 10
				if ( $diffOp->isEmpty() ) {
98
					// there is no (relevant) difference
99 2
					return null;
100
				} else {
101 9
					return $diffOp;
102
				}
103
			}
104
		}
105
106 13
		if ( $hasOld && $hasNew ) {
107 7
			return new DiffOpChange( $oldSet[$key], $newSet[$key] );
108
		}
109 7
		elseif ( $hasOld ) {
110 1
			return new DiffOpRemove( $oldSet[$key] );
111
		}
112 7
		elseif ( $hasNew ) {
113 7
			return new DiffOpAdd( $newSet[$key] );
114
		}
115
116
		// @codeCoverageIgnoreStart
117
		throw new LogicException( 'The element needs to exist in either the old or new list to compare' );
118
		// @codeCoverageIgnoreEnd
119
	}
120
121 10
	private function getDiffOpForElementRecursively( $key, array $oldSet, array $newSet ) {
122 10
		$old = array_key_exists( $key, $oldSet ) ? $oldSet[$key] : array();
123 10
		$new = array_key_exists( $key, $newSet ) ? $newSet[$key] : array();
124
125 10
		if ( is_array( $old ) && is_array( $new ) ) {
126 10
			return $this->getDiffForArrays( $old, $new );
127
		}
128
129 5
		return null;
130
	}
131
132 10
	private function getDiffForArrays( array $old, array $new ): Diff {
133 10
		if ( $this->isAssociative( $old ) || $this->isAssociative( $new ) ) {
134 7
			return new Diff( $this->doDiff( $old, $new ), true );
135
		}
136
137 6
		return new Diff( $this->listDiffer->doDiff( $old, $new ), false );
138
139
	}
140
141
	/**
142
	 * Returns if an array is associative or not.
143
	 *
144
	 * @param array $array
145
	 *
146
	 * @return bool
147
	 */
148 10
	private function isAssociative( array $array ): bool {
149 10
		foreach ( $array as $key => $value ) {
150 10
			if ( is_string( $key ) ) {
151 10
				return true;
152
			}
153
		}
154
155 7
		return false;
156
	}
157
158
	/**
159
	 * Similar to the native array_diff_assoc function, except that it will
160
	 * spot differences between array values. Very weird the native
161
	 * function just ignores these...
162
	 *
163
	 * @see http://php.net/manual/en/function.array-diff-assoc.php
164
	 *
165
	 * @param array $from
166
	 * @param array $to
167
	 *
168
	 * @return array
169
	 */
170 27
	private function arrayDiffAssoc( array $from, array $to ): array {
171 27
		$diff = array();
172
173 27
		foreach ( $from as $key => $value ) {
174 26
			if ( !array_key_exists( $key, $to ) || !$this->valueComparer->valuesAreEqual( $to[$key], $value ) ) {
175 26
				$diff[$key] = $value;
176
			}
177
		}
178
179 27
		return $diff;
180
	}
181
182
}
183