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
|
6 |
|
$type = $parameter_data['TypeName']; |
130
|
|
|
} |
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
|
|
|
} |
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
|
|
|
} |
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
|
|
|
} |
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
|
|
|
} |
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
|
|
|
); |
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
|
|
|
} |
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
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.