Failed Conditions
Push — master ( a2f8a2...e02dc1 )
by Alexander
01:53
created

ClassChecker::check()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 25
cp 0
rs 6.7272
c 0
b 0
f 0
nc 6
cc 7
eloc 18
nop 2
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 method data.
36
	 *
37
	 * @var array
38
	 */
39
	protected $sourceMethodData = array();
40
41
	/**
42
	 * Target method data.
43
	 *
44
	 * @var array
45
	 */
46
	protected $targetMethodData = array();
47
48
	/**
49
	 * ClassChecker constructor.
50
	 */
51
	public function __construct()
52
	{
53
		$this->incidents = array(
54
			'Class Deleted' => array(),
55
			'Class Made Abstract' => array(),
56
			'Class Made Final' => array(),
57
			'Method Deleted' => array(),
58
			'Method Made Abstract' => array(),
59
			'Method Made Final' => array(),
60
			'Method Scope Reduced' => array(),
61
			'Method Signature Changed' => array(),
62
		);
63
	}
64
65
	/**
66
	 * Returns backwards compatibility checker name.
67
	 *
68
	 * @return string
69
	 */
70
	public function getName()
71
	{
72
		return 'class';
73
	}
74
75
	/**
76
	 * Checks backwards compatibility and returns violations by category.
77
	 *
78
	 * @param ExtendedPdoInterface $source_db Source DB.
79
	 * @param ExtendedPdoInterface $target_db Target DB.
80
	 *
81
	 * @return array
82
	 */
83
	public function check(ExtendedPdoInterface $source_db, ExtendedPdoInterface $target_db)
84
	{
85
		$this->sourceDatabase = $source_db;
86
		$this->targetDatabase = $target_db;
87
88
		$classes_sql = 'SELECT Name, Id, IsAbstract, IsFinal 
89
						FROM Classes';
90
		$source_classes = $this->sourceDatabase->fetchAssoc($classes_sql);
91
		$target_classes = $this->targetDatabase->fetchAssoc($classes_sql);
92
93
		foreach ( $source_classes as $class_name => $source_class_data ) {
94
			if ( !isset($target_classes[$class_name]) ) {
95
				$this->addIncident('Class Deleted', $class_name);
96
				continue;
97
			}
98
99
			$this->sourceClassData = $source_class_data;
100
			$this->targetClassData = $target_classes[$class_name];
101
102
			if ( !$this->sourceClassData['IsAbstract'] && $this->targetClassData['IsAbstract'] ) {
103
				$this->addIncident('Class Made Abstract', $class_name);
104
			}
105
106
			if ( !$this->sourceClassData['IsFinal'] && $this->targetClassData['IsFinal'] ) {
107
				$this->addIncident('Class Made Final', $class_name);
108
			}
109
110
			$this->processMethods();
111
		}
112
113
		return array_filter($this->incidents);
114
	}
115
116
	/**
117
	 * Checks methods.
118
	 *
119
	 * @return void
120
	 */
121
	protected function processMethods()
122
	{
123
		$class_name = $this->sourceClassData['Name'];
124
		$source_methods = $this->_getSourceMethods($this->sourceClassData['Id']);
125
		$target_methods = $this->_getTargetMethods($this->targetClassData['Id']);
126
127
		foreach ( $source_methods as $source_method_name => $source_method_data ) {
128
			$target_method_name = $source_method_name;
129
			$full_method_name = $class_name . '::' . $source_method_name;
130
131
			// Ignore PHP4 constructor rename into PHP5 constructor.
132
			if ( !isset($target_methods[$target_method_name]) && $target_method_name === $class_name ) {
133
				$target_method_name = '__construct';
134
			}
135
136
			if ( !isset($target_methods[$target_method_name]) ) {
137
				$this->addIncident('Method Deleted', $full_method_name);
138
				continue;
139
			}
140
141
			$this->sourceMethodData = $source_method_data;
142
			$this->sourceMethodData['ParameterSignature'] = $this->getMethodParameterSignature(
143
				$this->sourceDatabase,
144
				$this->sourceMethodData['Id']
145
			);
146
147
			$this->targetMethodData = $target_methods[$target_method_name];
148
			$this->targetMethodData['ParameterSignature'] = $this->getMethodParameterSignature(
149
				$this->targetDatabase,
150
				$this->targetMethodData['Id']
151
			);
152
153
			$this->processMethod();
154
		}
155
	}
156
157
	/**
158
	 * Returns source methods.
159
	 *
160
	 * @param integer $class_id Class ID.
161
	 *
162
	 * @return array
163
	 */
164
	private function _getSourceMethods($class_id)
165
	{
166
		$scopes = ClassDataCollector::SCOPE_PUBLIC . ',' . ClassDataCollector::SCOPE_PROTECTED;
167
		$sql = 'SELECT Name, Id, Scope, IsAbstract, IsFinal
168
				FROM ClassMethods
169
				WHERE ClassId = :class_id AND Scope IN (' . $scopes . ')';
170
171
		return $this->sourceDatabase->fetchAssoc($sql, array('class_id' => $class_id));
172
	}
173
174
	/**
175
	 * Returns target methods.
176
	 *
177
	 * @param integer $class_id Class ID.
178
	 *
179
	 * @return array
180
	 */
181
	private function _getTargetMethods($class_id)
182
	{
183
		$sql = 'SELECT Name, Id, Scope, IsAbstract, IsFinal
184
				FROM ClassMethods
185
				WHERE ClassId = :class_id';
186
187
		return $this->targetDatabase->fetchAssoc($sql, array('class_id' => $class_id));
188
	}
189
190
	/**
191
	 * Processes method.
192
	 *
193
	 * @return void
194
	 */
195
	protected function processMethod()
196
	{
197
		$class_name = $this->sourceClassData['Name'];
198
		$method_name = $this->sourceMethodData['Name'];
199
200
		$full_method_name = $class_name . '::' . $method_name;
201
202
		if ( !$this->sourceMethodData['IsAbstract'] && $this->targetMethodData['IsAbstract'] ) {
203
			$this->addIncident('Method Made Abstract', $full_method_name);
204
		}
205
206
		if ( !$this->sourceMethodData['IsFinal'] && $this->targetMethodData['IsFinal'] ) {
207
			$this->addIncident('Method Made Final', $full_method_name);
208
		}
209
210
		if ( $this->sourceMethodData['ParameterSignature'] !== $this->targetMethodData['ParameterSignature'] ) {
211
			$this->addIncident(
212
				'Method Signature Changed',
213
				$full_method_name,
214
				$this->sourceMethodData['ParameterSignature'],
215
				$this->targetMethodData['ParameterSignature']
216
			);
217
		}
218
219
		if ( $this->sourceMethodData['Scope'] > $this->targetMethodData['Scope'] ) {
220
			$this->addIncident(
221
				'Method Scope Reduced',
222
				$full_method_name,
223
				$this->getScopeName($this->sourceMethodData['Scope']),
224
				$this->getScopeName($this->targetMethodData['Scope'])
225
			);
226
		}
227
	}
228
229
	/**
230
	 * Calculates method parameter signature.
231
	 *
232
	 * @param ExtendedPdoInterface $db        Database.
233
	 * @param integer              $method_id Method ID.
234
	 *
235
	 * @return integer
236
	 */
237
	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...
238
	{
239
		$sql = 'SELECT *
240
				FROM MethodParameters
241
				WHERE MethodId = :method_id
242
				ORDER BY Position ASC';
243
		$method_parameters = $db->fetchAll($sql, array('method_id' => $method_id));
244
245
		$hash_parts = array();
246
247
		foreach ( $method_parameters as $method_parameter_data ) {
248
			$hash_parts[] = $this->paramToString($method_parameter_data);
249
		}
250
251
		return implode(', ', $hash_parts);
252
	}
253
254
	/**
255
	 * Returns scope name.
256
	 *
257
	 * @param integer $scope Scope.
258
	 *
259
	 * @return string
260
	 */
261
	protected function getScopeName($scope)
262
	{
263
		$mapping = array(
264
			ClassDataCollector::SCOPE_PRIVATE => 'private',
265
			ClassDataCollector::SCOPE_PROTECTED => 'protected',
266
			ClassDataCollector::SCOPE_PUBLIC => 'public',
267
		);
268
269
		return $mapping[$scope];
270
	}
271
272
}
273