Passed
Push — master ( b37527...aa9965 )
by Jeroen De
42s
created

MapPatcher   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 9
dl 0
loc 205
ccs 65
cts 65
cp 1
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A patch() 0 7 2
A applyDiffOpAdd() 0 8 2
A applyDiffOpRemove() 0 13 3
A applyDiffOpChange() 0 13 3
A applyDiff() 0 12 3
A isAttemptToModifyNotExistingElement() 0 4 3
A patchMapOrList() 0 7 2
A valuesAreEqual() 0 7 2
A setValueComparer() 0 3 1
B applyOperation() 0 17 5
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace Diff\Patcher;
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
15
/**
16
 * Map patcher.
17
 *
18
 * @since 0.4
19
 *
20
 * @license GPL-2.0+
21
 * @author Jeroen De Dauw < [email protected] >
22
 */
23
class MapPatcher extends ThrowingPatcher {
24
25
	/**
26
	 * @var Patcher
27
	 */
28
	private $listPatcher;
29
30
	/**
31
	 * @var ValueComparer|null
32
	 */
33
	private $comparer = null;
34
35
	/**
36
	 * @since 0.4
37
	 *
38
	 * @param bool $throwErrors
39
	 * @param Patcher|null $listPatcher The patcher that will be used for lists in the value
40
	 */
41 13
	public function __construct( bool $throwErrors = false, Patcher $listPatcher = null ) {
42 13
		parent::__construct( $throwErrors );
43
44 13
		$this->listPatcher = $listPatcher ?: new ListPatcher( $throwErrors );
45 13
	}
46
47
	/**
48
	 * @see Patcher::patch
49
	 *
50
	 * Applies the provided diff to the provided array and returns the result.
51
	 * The array is treated as a map, ie keys are held into account.
52
	 *
53
	 * It is possible to pass in non-associative diffs (those for which isAssociative)
54
	 * returns false, however the likely intended behavior can be obtained via
55
	 * a list patcher.
56
	 *
57
	 * @since 0.4
58
	 *
59
	 * @param array $base
60
	 * @param Diff $diff
61
	 *
62
	 * @return array
63
	 * @throws PatcherException
64
	 */
65 27
	public function patch( array $base, Diff $diff ): array {
66 27
		foreach ( $diff as $key => $diffOp ) {
67 23
			$this->applyOperation( $base, $key, $diffOp );
68
		}
69
70 27
		return $base;
71
	}
72
73
	/**
74
	 * @param array &$base
75
	 * @param int|string $key
76
	 * @param DiffOp $diffOp
77
	 *
78
	 * @throws PatcherException
79
	 */
80 23
	private function applyOperation( array &$base, $key, DiffOp $diffOp ) {
81 23
		if ( $diffOp instanceof DiffOpAdd ) {
82 11
			$this->applyDiffOpAdd( $base, $key, $diffOp );
83
		}
84 19
		elseif ( $diffOp instanceof DiffOpChange ) {
85 9
			$this->applyDiffOpChange( $base, $key, $diffOp );
86
		}
87 13
		elseif ( $diffOp instanceof DiffOpRemove ) {
88 9
			$this->applyDiffOpRemove( $base, $key, $diffOp );
89
		}
90 4
		elseif ( $diffOp instanceof Diff ) {
91 3
			$this->applyDiff( $base, $key, $diffOp );
92
		}
93
		else {
94 1
			$this->handleError( 'Unknown diff operation cannot be applied to map element' );
95
		}
96 23
	}
97
98
	/**
99
	 * @param array &$base
100
	 * @param int|string $key
101
	 * @param DiffOpAdd $diffOp
102
	 *
103
	 * @throws PatcherException
104
	 */
105 11
	private function applyDiffOpAdd( array &$base, $key, DiffOpAdd $diffOp ) {
106 11
		if ( array_key_exists( $key, $base ) ) {
107 3
			$this->handleError( 'Cannot add an element already present in a map' );
108 3
			return;
109
		}
110
111 8
		$base[$key] = $diffOp->getNewValue();
112 8
	}
113
114
	/**
115
	 * @param array &$base
116
	 * @param int|string $key
117
	 * @param DiffOpRemove $diffOp
118
	 *
119
	 * @throws PatcherException
120
	 */
121 9
	private function applyDiffOpRemove( array &$base, $key, DiffOpRemove $diffOp ) {
122 9
		if ( !array_key_exists( $key, $base ) ) {
123 4
			$this->handleError( 'Cannot do a non-add operation with an element not present in a map' );
124 4
			return;
125
		}
126
127 5
		if ( !$this->valuesAreEqual( $base[$key], $diffOp->getOldValue() ) ) {
128 2
			$this->handleError( 'Tried removing a map value that mismatches the current value' );
129 2
			return;
130
		}
131
132 3
		unset( $base[$key] );
133 3
	}
134
135
	/**
136
	 * @param array &$base
137
	 * @param int|string $key
138
	 * @param DiffOpChange $diffOp
139
	 *
140
	 * @throws PatcherException
141
	 */
142 9
	private function applyDiffOpChange( array &$base, $key, DiffOpChange $diffOp ) {
143 9
		if ( !array_key_exists( $key, $base ) ) {
144 1
			$this->handleError( 'Cannot do a non-add operation with an element not present in a map' );
145 1
			return;
146
		}
147
148 8
		if ( !$this->valuesAreEqual( $base[$key], $diffOp->getOldValue() ) ) {
149 4
			$this->handleError( 'Tried changing a map value from an invalid source value' );
150 4
			return;
151
		}
152
153 6
		$base[$key] = $diffOp->getNewValue();
154 6
	}
155
156
	/**
157
	 * @param array &$base
158
	 * @param int|string $key
159
	 * @param Diff $diffOp
160
	 *
161
	 * @throws PatcherException
162
	 */
163 3
	private function applyDiff( &$base, $key, Diff $diffOp ) {
164 3
		if ( $this->isAttemptToModifyNotExistingElement( $base, $key, $diffOp ) ) {
165 1
			$this->handleError( 'Cannot apply a diff with non-add operations to an element not present in a map' );
166 1
			return;
167
		}
168
169 3
		if ( !array_key_exists( $key, $base ) ) {
170 2
			$base[$key] = array();
171
		}
172
173 3
		$base[$key] = $this->patchMapOrList( $base[$key], $diffOp );
174 3
	}
175
176
	/**
177
	 * @param array &$base
178
	 * @param int|string $key
179
	 * @param Diff $diffOp
180
	 *
181
	 * @return bool
182
	 */
183 3
	private function isAttemptToModifyNotExistingElement( $base, $key, Diff $diffOp ): bool {
184 3
		return !array_key_exists( $key, $base )
185 3
			&& ( $diffOp->getChanges() !== array() || $diffOp->getRemovals() !== array() );
186
	}
187
188
	/**
189
	 * @param array $base
190
	 * @param Diff $diff
191
	 *
192
	 * @return array
193
	 */
194 3
	private function patchMapOrList( array $base, Diff $diff ): array {
195 3
		if ( $diff->looksAssociative() ) {
196 2
			return $this->patch( $base, $diff );
197
		}
198
199 3
		return $this->listPatcher->patch( $base, $diff );
200
	}
201
202
	/**
203
	 * @param mixed $firstValue
204
	 * @param mixed $secondValue
205
	 *
206
	 * @return bool
207
	 */
208 11
	private function valuesAreEqual( $firstValue, $secondValue ): bool {
209 11
		if ( $this->comparer === null ) {
210 9
			$this->comparer = new StrictComparer();
211
		}
212
213 11
		return $this->comparer->valuesAreEqual( $firstValue, $secondValue );
214
	}
215
216
	/**
217
	 * Sets the value comparer that should be used to determine if values are equal.
218
	 *
219
	 * @since 0.6
220
	 *
221
	 * @param ValueComparer $comparer
222
	 */
223 2
	public function setValueComparer( ValueComparer $comparer ) {
224 2
		$this->comparer = $comparer;
225 2
	}
226
227
}
228