Completed
Push — 2.x ( f49b88...9f2b65 )
by Leszek
15:52 queued 15:50
created

MapPatcher::applyOperation()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

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