Completed
Push — master ( 84c755...24c463 )
by Jeroen De
03:13
created

MapPatcher   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 95.45%

Importance

Changes 9
Bugs 1 Features 2
Metric Value
wmc 28
c 9
b 1
f 2
lcom 1
cbo 9
dl 0
loc 161
ccs 63
cts 66
cp 0.9545
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A patch() 0 7 2
B applyOperation() 0 17 5
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 10 2
A valuesAreEqual() 0 7 2
A setValueComparer() 0 3 1
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
		elseif ( $diffOp instanceof DiffOpChange ) {
83 9
			$this->applyDiffOpChange( $base, $key, $diffOp );
84
		}
85
		elseif ( $diffOp instanceof DiffOpRemove ) {
86 9
			$this->applyDiffOpRemove( $base, $key, $diffOp );
87
		}
88
		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 11
	private function applyDiffOpAdd( &$base, $key, DiffOpAdd $diffOp ) {
97 11
		if ( array_key_exists( $key, $base ) ) {
98 3
			$this->handleError( 'Cannot add an element already present in a map' );
99 3
			return;
100
		}
101
102 8
		$base[$key] = $diffOp->getNewValue();
103 8
	}
104
105 9
	private function applyDiffOpRemove( &$base, $key, DiffOpRemove $diffOp ) {
106 9
		if ( !array_key_exists( $key, $base ) ) {
107 4
			$this->handleError( 'Cannot do a non-add operation with an element not present in a map' );
108 4
			return;
109
		}
110
111 5
		if ( !$this->valuesAreEqual( $base[$key], $diffOp->getOldValue() ) ) {
112 2
			$this->handleError( 'Tried removing a map value that mismatches the current value' );
113 2
			return;
114
		}
115
116 3
		unset( $base[$key] );
117 3
	}
118
119 9
	private function applyDiffOpChange( &$base, $key, DiffOpChange $diffOp ) {
120 9
		if ( !array_key_exists( $key, $base ) ) {
121 1
			$this->handleError( 'Cannot do a non-add operation with an element not present in a map' );
122 1
			return;
123
		}
124
125 8
		if ( !$this->valuesAreEqual( $base[$key], $diffOp->getOldValue() ) ) {
126 4
			$this->handleError( 'Tried changing a map value from an invalid source value' );
127 4
			return;
128
		}
129
130 6
		$base[$key] = $diffOp->getNewValue();
131 6
	}
132
133 3
	private function applyDiff( &$base, $key, Diff $diffOp ) {
134 3
		if ( $this->isAttemptToModifyNotExistingElement( $base, $key, $diffOp ) ) {
135 1
			$this->handleError( 'Cannot apply a diff with non-add operations to an element not present in a map' );
136 1
			return;
137
		}
138
139 3
		if ( !array_key_exists( $key, $base ) ) {
140 2
			$base[$key] = array();
141
		}
142
143 3
		$base[$key] = $this->patchMapOrList( $base[$key], $diffOp );
144 3
	}
145
146 3
	private function isAttemptToModifyNotExistingElement( $base, $key, Diff $diffOp ) {
147 3
		return !array_key_exists( $key, $base )
148 3
			&& ( $diffOp->getChanges() !== array() || $diffOp->getRemovals() !== array() );
149
	}
150
151 3
	private function patchMapOrList( array $base, Diff $diff ) {
152 3
		if ( $diff->looksAssociative() ) {
153 2
			$base = $this->patch( $base, $diff );
154
		}
155
		else {
156 3
			$base = $this->listPatcher->patch( $base, $diff );
157
		}
158
159 3
		return $base;
160
	}
161
162 11
	private function valuesAreEqual( $firstValue, $secondValue ) {
163 11
		if ( $this->comparer === null ) {
164 9
			$this->comparer = new StrictComparer();
165
		}
166
167 11
		return $this->comparer->valuesAreEqual( $firstValue, $secondValue );
168
	}
169
170
	/**
171
	 * Sets the value comparer that should be used to determine if values are equal.
172
	 *
173
	 * @since 0.6
174
	 *
175
	 * @param ValueComparer $comparer
176
	 */
177 2
	public function setValueComparer( ValueComparer $comparer ) {
178 2
		$this->comparer = $comparer;
179 2
	}
180
181
}
182