Failed Conditions
Push — master ( 54d828...30bd5c )
by Alexander
02:59 queued 01:52
created

AbstractChecker   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 92.21%

Importance

Changes 0
Metric Value
wmc 27
lcom 2
cbo 1
dl 0
loc 250
ccs 71
cts 77
cp 0.9221
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
getName() 0 1 ?
doCheck() 0 1 ?
A __construct() 0 4 1
A check() 0 11 1
A sortByType() 0 11 3
A decodeValue() 0 8 1
A getCacheKey() 0 4 1
B paramToString() 0 37 8
B isParamSignatureCompatible() 0 39 9
A addIncident() 0 14 3
1
<?php
2
/**
3
 * This file is part of the Code-Insight library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/code-insight
9
 */
10
11
namespace ConsoleHelpers\CodeInsight\BackwardsCompatibility\Checker;
12
13
14
use Aura\Sql\ExtendedPdoInterface;
15
use Doctrine\Common\Cache\CacheProvider;
16
17
abstract class AbstractChecker
18
{
19
20
	/**
21
	 * Source database.
22
	 *
23
	 * @var ExtendedPdoInterface
24
	 */
25
	protected $sourceDatabase;
26
27
	/**
28
	 * Target database.
29
	 *
30
	 * @var ExtendedPdoInterface
31
	 */
32
	protected $targetDatabase;
33
34
	/**
35
	 * Cache.
36
	 *
37
	 * @var CacheProvider
38
	 */
39
	protected $cache;
40
41
	/**
42
	 * Incidents.
43
	 *
44
	 * @var array
45
	 */
46
	private $_incidents = array();
47
48
	/**
49
	 * Determines the order used to sort incidents with different types.
50
	 *
51
	 * @var array
52
	 */
53
	protected $typeSorting = array();
54
55
	/**
56
	 * AbstractChecker constructor.
57
	 *
58
	 * @param CacheProvider $cache Cache provider.
59
	 */
60 12
	public function __construct(CacheProvider $cache)
61
	{
62 12
		$this->cache = $cache;
63 12
	}
64
65
	/**
66
	 * Returns backwards compatibility checker name.
67
	 *
68
	 * @return string
69
	 */
70
	abstract public function getName();
71
72
	/**
73
	 * Checks backwards compatibility and returns violations by category.
74
	 *
75
	 * @param ExtendedPdoInterface $source_db Source DB.
76
	 * @param ExtendedPdoInterface $target_db Target DB.
77
	 *
78
	 * @return array
79
	 */
80 8
	public function check(ExtendedPdoInterface $source_db, ExtendedPdoInterface $target_db)
81
	{
82 8
		$this->sourceDatabase = $source_db;
83 8
		$this->targetDatabase = $target_db;
84
85 8
		$this->doCheck();
86
87 8
		usort($this->_incidents, array($this, 'sortByType'));
88
89 8
		return $this->_incidents;
90
	}
91
92
	/**
93
	 * Sorts incidents by type.
94
	 *
95
	 * @param array $incident_a Incident A.
96
	 * @param array $incident_b Incident B.
97
	 *
98
	 * @return integer
99
	 */
100 3
	public function sortByType(array $incident_a, array $incident_b)
101
	{
102 3
		$sort_key_a = $this->typeSorting[$incident_a['type']];
103 3
		$sort_key_b = $this->typeSorting[$incident_b['type']];
104
105 3
		if ( $sort_key_a === $sort_key_b ) {
106 3
			return 0;
107
		}
108
109 3
		return $sort_key_a > $sort_key_b ? 1 : -1;
110
	}
111
112
	/**
113
	 * Collects backwards compatibility violations.
114
	 *
115
	 * @return void
116
	 */
117
	abstract protected function doCheck();
118
119
	/**
120
	 * Builds string representation of a parameter.
121
	 *
122
	 * @param array $parameter_data Parameter data.
123
	 *
124
	 * @return string
125
	 */
126 6
	protected function paramToString(array $parameter_data)
127
	{
128 6
		if ( $parameter_data['HasType'] ) {
129 4
			$type = $parameter_data['TypeName'];
130 4
		}
131 6
		elseif ( $parameter_data['IsArray'] ) {
132
			$type = 'array';
133
		}
134 6
		elseif ( $parameter_data['IsCallable'] ) {
135
			$type = 'callable';
136
		}
137
		else {
138 6
			$type = $parameter_data['TypeClass'];
139
		}
140
141 6
		$hash_part = strlen($type) ? $type . ' ' : '';
142
143 6
		if ( $parameter_data['IsPassedByReference'] ) {
144 4
			$hash_part .= '&$' . $parameter_data['Name'];
145 4
		}
146
		else {
147 6
			$hash_part .= '$' . $parameter_data['Name'];
148
		}
149
150 6
		if ( $parameter_data['HasDefaultValue'] ) {
151 6
			$hash_part .= ' = ';
152
153 6
			if ( $parameter_data['DefaultConstant'] ) {
154
				$hash_part .= $parameter_data['DefaultConstant'];
155
			}
156
			else {
157 6
				$hash_part .= $this->decodeValue($parameter_data['DefaultValue']);
158
			}
159 6
		}
160
161 6
		return $hash_part;
162
	}
163
164
	/**
165
	 * Determines if 2 param signatures are compatible.
166
	 *
167
	 * @param string $source_signature Source signature.
168
	 * @param string $target_signature Target signature.
169
	 *
170
	 * @return boolean
171
	 */
172 6
	protected function isParamSignatureCompatible($source_signature, $target_signature)
173
	{
174 6
		if ( $source_signature === $target_signature ) {
175 6
			return true;
176
		}
177
178 3
		$source_params = $source_signature ? explode(', ', $source_signature) : array();
179 3
		$target_params = $target_signature ? explode(', ', $target_signature) : array();
180 3
		$source_param_count = count($source_params);
181
182
		// Beginning of target signature doesn't match source signature.
183 3
		if ( $source_params !== array_slice($target_params, 0, $source_param_count) ) {
184 3
			$target_params_wo_defaults = preg_replace('/ = [^,]*/', '', $target_params);
185
186
			// Making existing parameter optional doesn't break compatibility.
187 3
			if ( $source_params !== array_slice($target_params_wo_defaults, 0, $source_param_count) ) {
188 3
				return false;
189
			}
190 3
		}
191
192 3
		$added_params = array_slice($target_params, $source_param_count);
193
194
		// No new parameters added.
195 3
		if ( !$added_params ) {
196 3
			return true;
197
		}
198
199 3
		$is_compatible = true;
200
201
		// When all added parameters are optional, then signature is compatible.
202 3
		foreach ( $added_params as $added_param ) {
203 3
			if ( strpos($added_param, '=') === false ) {
204 3
				$is_compatible = false;
205 3
				break;
206
			}
207 3
		}
208
209 3
		return $is_compatible;
210
	}
211
212
	/**
213
	 * Decodes json-encoded PHP value.
214
	 *
215
	 * @param string $json_string JSON string.
216
	 *
217
	 * @return string
218
	 */
219 6
	protected function decodeValue($json_string)
220
	{
221 6
		$value = var_export(json_decode($json_string), true);
222 6
		$value = str_replace(array("\t", "\n"), '', $value);
223 6
		$value = str_replace('array (', 'array(', $value);
224
225 6
		return $value;
226
	}
227
228
	/**
229
	 * Adds incident.
230
	 *
231
	 * @param string      $type      Incident type.
232
	 * @param string      $element   Element affected.
233
	 * @param string|null $old_value Old value.
234
	 * @param string|null $new_value New value.
235
	 *
236
	 * @return void
237
	 */
238 4
	protected function addIncident($type, $element, $old_value = null, $new_value = null)
239
	{
240
		$incident_record = array(
241 4
			'type' => $type,
242 4
			'element' => $element,
243 4
		);
244
245 4
		if ( isset($old_value) || isset($new_value) ) {
246 3
			$incident_record['old'] = $old_value;
247 3
			$incident_record['new'] = $new_value;
248 3
		}
249
250 4
		$this->_incidents[] = $incident_record;
251 4
	}
252
253
	/**
254
	 * Returns cache key valid for specific database only.
255
	 *
256
	 * @param ExtendedPdoInterface $db        Database.
257
	 * @param string               $cache_key Cache key.
258
	 *
259
	 * @return string
260
	 */
261 4
	protected function getCacheKey(ExtendedPdoInterface $db, $cache_key)
262
	{
263 4
		return sha1($db->getDsn()) . ':' . $cache_key;
264
	}
265
266
}
267