Failed Conditions
Push — master ( e02dc1...26605a )
by Alexander
01:39
created

ClassChecker::doCheck()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 0
cts 24
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 17
nc 6
nop 0
crap 56
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;
12
13
14
use Aura\Sql\ExtendedPdoInterface;
15
use ConsoleHelpers\CodeInsight\KnowledgeBase\DataCollector\ClassDataCollector;
16
17
class ClassChecker extends AbstractChecker
18
{
19
20
	/**
21
	 * Source class data.
22
	 *
23
	 * @var array
24
	 */
25
	protected $sourceClassData = array();
26
27
	/**
28
	 * Target class data.
29
	 *
30
	 * @var array
31
	 */
32
	protected $targetClassData = array();
33
34
	/**
35
	 * Source property data.
36
	 *
37
	 * @var array
38
	 */
39
	protected $sourcePropertyData = array();
40
41
	/**
42
	 * Target property data.
43
	 *
44
	 * @var array
45
	 */
46
	protected $targetPropertyData = array();
47
48
	/**
49
	 * Source method data.
50
	 *
51
	 * @var array
52
	 */
53
	protected $sourceMethodData = array();
54
55
	/**
56
	 * Target method data.
57
	 *
58
	 * @var array
59
	 */
60
	protected $targetMethodData = array();
61
62
	/**
63
	 * ClassChecker constructor.
64
	 */
65
	public function __construct()
66
	{
67
		$this->defineIncidentGroups(array(
68
			'Class Deleted',
69
			'Class Made Abstract',
70
			'Class Made Final',
71
72
			'Class Constant Deleted',
73
74
			'Property Deleted',
75
			'Property Scope Reduced',
76
77
			'Method Deleted',
78
			'Method Made Abstract',
79
			'Method Made Final',
80
			'Method Scope Reduced',
81
			'Method Signature Changed',
82
		));
83
	}
84
85
	/**
86
	 * Returns backwards compatibility checker name.
87
	 *
88
	 * @return string
89
	 */
90
	public function getName()
91
	{
92
		return 'class';
93
	}
94
95
	/**
96
	 * Collects backwards compatibility violations.
97
	 *
98
	 * @return void
99
	 */
100
	protected function doCheck()
101
	{
102
		$classes_sql = 'SELECT Name, Id, IsAbstract, IsFinal
103
						FROM Classes';
104
		$source_classes = $this->sourceDatabase->fetchAssoc($classes_sql);
105
		$target_classes = $this->targetDatabase->fetchAssoc($classes_sql);
106
107
		foreach ( $source_classes as $class_name => $source_class_data ) {
108
			if ( !isset($target_classes[$class_name]) ) {
109
				$this->addIncident('Class Deleted', $class_name);
110
				continue;
111
			}
112
113
			$this->sourceClassData = $source_class_data;
114
			$this->targetClassData = $target_classes[$class_name];
115
116
			if ( !$this->sourceClassData['IsAbstract'] && $this->targetClassData['IsAbstract'] ) {
117
				$this->addIncident('Class Made Abstract', $class_name);
118
			}
119
120
			if ( !$this->sourceClassData['IsFinal'] && $this->targetClassData['IsFinal'] ) {
121
				$this->addIncident('Class Made Final', $class_name);
122
			}
123
124
			$this->processConstants();
125
			$this->processProperties();
126
			$this->processMethods();
127
		}
128
	}
129
130
	/**
131
	 * Checks constants.
132
	 *
133
	 * @return void
134
	 */
135
	protected function processConstants()
136
	{
137
		$class_name = $this->sourceClassData['Name'];
138
139
		$sql = 'SELECT Name
140
				FROM ClassConstants
141
				WHERE ClassId = :class_id';
142
		$source_constants = $this->sourceDatabase->fetchCol($sql, array('class_id' => $this->sourceClassData['Id']));
143
		$target_constants = $this->targetDatabase->fetchCol($sql, array('class_id' => $this->targetClassData['Id']));
144
145
		foreach ( $source_constants as $source_constant_name ) {
146
			$full_constant_name = $class_name . '::' . $source_constant_name;
147
148
			if ( !in_array($source_constant_name, $target_constants) ) {
149
				$this->addIncident('Class Constant Deleted', $full_constant_name);
150
				continue;
151
			}
152
		}
153
	}
154
155
	/**
156
	 * Checks properties.
157
	 *
158
	 * @return void
159
	 */
160
	protected function processProperties()
161
	{
162
		$class_name = $this->sourceClassData['Name'];
163
164
		$sql = 'SELECT Name, Scope
165
				FROM ClassProperties
166
				WHERE ClassId = :class_id AND Scope IN (' . $this->coveredScopes() . ')';
167
		$source_properties = $this->sourceDatabase->fetchAssoc($sql, array('class_id' => $this->sourceClassData['Id']));
168
169
		$sql = 'SELECT Name, Scope
170
				FROM ClassProperties
171
				WHERE ClassId = :class_id';
172
		$target_properties = $this->targetDatabase->fetchAssoc($sql, array('class_id' => $this->targetClassData['Id']));
173
174
		foreach ( $source_properties as $source_property_name => $source_property_data ) {
175
			$full_property_name = $class_name . '::' . $source_property_name;
176
177
			if ( !isset($target_properties[$source_property_name]) ) {
178
				$this->addIncident('Property Deleted', $full_property_name);
179
				continue;
180
			}
181
182
			$this->sourcePropertyData = $source_property_data;
183
			$this->targetPropertyData = $target_properties[$source_property_name];
184
185
			$this->processProperty();
186
		}
187
	}
188
189
	/**
190
	 * Processes property.
191
	 *
192
	 * @return void
193
	 */
194
	protected function processProperty()
195
	{
196
		$class_name = $this->sourceClassData['Name'];
197
		$property_name = $this->sourcePropertyData['Name'];
198
199
		$full_property_name = $class_name . '::' . $property_name;
200
201
		if ( $this->sourcePropertyData['Scope'] > $this->targetPropertyData['Scope'] ) {
202
			$this->addIncident(
203
				'Property Scope Reduced',
204
				$full_property_name,
205
				$this->getScopeName($this->sourcePropertyData['Scope']),
206
				$this->getScopeName($this->targetPropertyData['Scope'])
207
			);
208
		}
209
	}
210
211
	/**
212
	 * Checks methods.
213
	 *
214
	 * @return void
215
	 */
216
	protected function processMethods()
217
	{
218
		$class_name = $this->sourceClassData['Name'];
219
220
		$sql = 'SELECT Name, Id, Scope, IsAbstract, IsFinal
221
				FROM ClassMethods
222
				WHERE ClassId = :class_id AND Scope IN (' . $this->coveredScopes() . ')';
223
		$source_methods = $this->sourceDatabase->fetchAssoc($sql, array('class_id' => $this->sourceClassData['Id']));
224
225
		$sql = 'SELECT Name, Id, Scope, IsAbstract, IsFinal
226
				FROM ClassMethods
227
				WHERE ClassId = :class_id';
228
		$target_methods = $this->targetDatabase->fetchAssoc($sql, array('class_id' => $this->targetClassData['Id']));
229
230
		foreach ( $source_methods as $source_method_name => $source_method_data ) {
231
			$target_method_name = $source_method_name;
232
			$full_method_name = $class_name . '::' . $source_method_name;
233
234
			// Ignore PHP4 constructor rename into PHP5 constructor.
235
			if ( !isset($target_methods[$target_method_name]) && $target_method_name === $class_name ) {
236
				$target_method_name = '__construct';
237
			}
238
239
			if ( !isset($target_methods[$target_method_name]) ) {
240
				$this->addIncident('Method Deleted', $full_method_name);
241
				continue;
242
			}
243
244
			$this->sourceMethodData = $source_method_data;
245
			$this->sourceMethodData['ParameterSignature'] = $this->getMethodParameterSignature(
246
				$this->sourceDatabase,
247
				$this->sourceMethodData['Id']
248
			);
249
250
			$this->targetMethodData = $target_methods[$target_method_name];
251
			$this->targetMethodData['ParameterSignature'] = $this->getMethodParameterSignature(
252
				$this->targetDatabase,
253
				$this->targetMethodData['Id']
254
			);
255
256
			$this->processMethod();
257
		}
258
	}
259
260
	/**
261
	 * Calculates method parameter signature.
262
	 *
263
	 * @param ExtendedPdoInterface $db        Database.
264
	 * @param integer              $method_id Method ID.
265
	 *
266
	 * @return integer
267
	 */
268
	protected function getMethodParameterSignature(ExtendedPdoInterface $db, $method_id)
1 ignored issue
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
269
	{
270
		$sql = 'SELECT *
271
				FROM MethodParameters
272
				WHERE MethodId = :method_id
273
				ORDER BY Position ASC';
274
		$method_parameters = $db->fetchAll($sql, array('method_id' => $method_id));
275
276
		$hash_parts = array();
277
278
		foreach ( $method_parameters as $method_parameter_data ) {
279
			$hash_parts[] = $this->paramToString($method_parameter_data);
280
		}
281
282
		return implode(', ', $hash_parts);
283
	}
284
285
	/**
286
	 * Processes method.
287
	 *
288
	 * @return void
289
	 */
290
	protected function processMethod()
291
	{
292
		$class_name = $this->sourceClassData['Name'];
293
		$method_name = $this->sourceMethodData['Name'];
294
295
		$full_method_name = $class_name . '::' . $method_name;
296
297
		if ( !$this->sourceMethodData['IsAbstract'] && $this->targetMethodData['IsAbstract'] ) {
298
			$this->addIncident('Method Made Abstract', $full_method_name);
299
		}
300
301
		if ( !$this->sourceMethodData['IsFinal'] && $this->targetMethodData['IsFinal'] ) {
302
			$this->addIncident('Method Made Final', $full_method_name);
303
		}
304
305
		if ( $this->sourceMethodData['ParameterSignature'] !== $this->targetMethodData['ParameterSignature'] ) {
306
			$this->addIncident(
307
				'Method Signature Changed',
308
				$full_method_name,
309
				$this->sourceMethodData['ParameterSignature'],
310
				$this->targetMethodData['ParameterSignature']
311
			);
312
		}
313
314
		if ( $this->sourceMethodData['Scope'] > $this->targetMethodData['Scope'] ) {
315
			$this->addIncident(
316
				'Method Scope Reduced',
317
				$full_method_name,
318
				$this->getScopeName($this->sourceMethodData['Scope']),
319
				$this->getScopeName($this->targetMethodData['Scope'])
320
			);
321
		}
322
	}
323
324
	/**
325
	 * Returns scope name.
326
	 *
327
	 * @param integer $scope Scope.
328
	 *
329
	 * @return string
330
	 */
331
	protected function getScopeName($scope)
332
	{
333
		$mapping = array(
334
			ClassDataCollector::SCOPE_PRIVATE => 'private',
335
			ClassDataCollector::SCOPE_PROTECTED => 'protected',
336
			ClassDataCollector::SCOPE_PUBLIC => 'public',
337
		);
338
339
		return $mapping[$scope];
340
	}
341
342
	/**
343
	 * Scopes covered by backwards compatibility checks.
344
	 *
345
	 * @return string
346
	 */
347
	protected function coveredScopes()
348
	{
349
		return ClassDataCollector::SCOPE_PUBLIC . ',' . ClassDataCollector::SCOPE_PROTECTED;
350
	}
351
352
}
353