ClassDataCollector   F
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 745
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 298
c 5
b 0
f 0
dl 0
loc 745
ccs 303
cts 303
cp 1
rs 3.04
wmc 67

17 Methods

Rating   Name   Duplication   Size   Complexity  
A addRelation() 0 40 3
A getStatistics() 0 32 5
A aggregateData() 0 5 1
A processClass() 0 54 5
B processClassMethodParameters() 0 75 9
A deleteClass() 0 18 1
A getRawClassRelations() 0 30 4
B processClassMethods() 0 65 7
A deleteClassMethods() 0 33 3
A getClassType() 0 11 3
A collectData() 0 25 4
A deleteData() 0 11 2
A getPropertyScope() 0 11 3
A processClassRawRelations() 0 45 4
A getMethodScope() 0 11 3
B processClassProperties() 0 51 6
A processClassConstants() 0 37 4

How to fix   Complexity   

Complex Class

Complex classes like ClassDataCollector often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassDataCollector, and based on these observations, apply Extract Interface, too.

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\KnowledgeBase\DataCollector;
12
13
14
use ConsoleHelpers\CodeInsight\KnowledgeBase\KnowledgeBase;
15
use Go\ParserReflection\ReflectionEngine;
16
use Go\ParserReflection\ReflectionFileNamespace;
17
18
class ClassDataCollector extends AbstractDataCollector
19
{
20
21
	const SCOPE_PRIVATE = 1;
22
23
	const SCOPE_PROTECTED = 2;
24
25
	const SCOPE_PUBLIC = 3;
26
27
	const TYPE_CLASS = 1;
28
29
	const TYPE_INTERFACE = 2;
30
31
	const TYPE_TRAIT = 3;
32
33
	const RELATION_TYPE_EXTENDS = 1;
34
35
	const RELATION_TYPE_IMPLEMENTS = 2;
36
37
	const RELATION_TYPE_USES = 3;
38
39
	/**
40
	 * Collect data from a namespace.
41
	 *
42
	 * @param integer                 $file_id   File id.
43
	 * @param ReflectionFileNamespace $namespace Namespace.
44
	 *
45
	 * @return void
46
	 */
47 15
	public function collectData($file_id, ReflectionFileNamespace $namespace)
48
	{
49 15
		$found_classes = array();
50
51 15
		foreach ( $namespace->getClasses() as $class ) {
52 14
			$found_classes[] = $class->getName();
53 14
			$this->processClass($file_id, $class);
54
		}
55
56 15
		if ( $found_classes ) {
57
			// FIXME: Would delete classes outside given namespace in same file.
58 14
			$sql = 'SELECT Id
59
					FROM Classes
60
					WHERE FileId = :file_id AND Name NOT IN (:classes)';
61 14
			$delete_classes = $this->db->fetchCol($sql, array(
62 14
				'file_id' => $file_id,
63 14
				'classes' => $found_classes,
64
			));
65
66 14
			foreach ( $delete_classes as $delete_class_id ) {
67 14
				$this->deleteClass($delete_class_id);
68
			}
69
		}
70
		else {
71 1
			$this->deleteData(array($file_id));
72
		}
73 15
	}
74
75
	/**
76
	 * Aggregate previously collected data.
77
	 *
78
	 * @param KnowledgeBase $knowledge_base Knowledge base.
79
	 *
80
	 * @return void
81
	 */
82 4
	public function aggregateData(KnowledgeBase $knowledge_base)
83
	{
84 4
		parent::aggregateData($knowledge_base);
85
86 4
		$this->processClassRawRelations($knowledge_base);
87 4
	}
88
89
	/**
90
	 * Delete previously collected data for a files.
91
	 *
92
	 * @param array $file_ids File IDs.
93
	 *
94
	 * @return void
95
	 */
96 2
	public function deleteData(array $file_ids)
97
	{
98 2
		$sql = 'SELECT Id
99
				FROM Classes
100
				WHERE FileId IN (:file_ids)';
101 2
		$delete_classes = $this->db->fetchCol($sql, array(
102 2
			'file_ids' => $file_ids,
103
		));
104
105 2
		foreach ( $delete_classes as $delete_class_id ) {
106 1
			$this->deleteClass($delete_class_id);
107
		}
108 2
	}
109
110
	/**
111
	 * Returns statistics about the code.
112
	 *
113
	 * @return array
114
	 */
115 1
	public function getStatistics()
116
	{
117 1
		$ret = array();
118
119 1
		$sql = 'SELECT ClassType, COUNT(*)
120
				FROM Classes
121
				GROUP BY ClassType';
122 1
		$classes_count = $this->db->fetchPairs($sql);
123
124 1
		foreach ( $classes_count as $class_type => $class_count ) {
125 1
			$title = 'Unknowns';
126
127 1
			if ( $class_type === self::TYPE_CLASS ) {
128 1
				$title = 'Classes';
129
			}
130 1
			elseif ( $class_type === self::TYPE_INTERFACE ) {
131 1
				$title = 'Interfaces';
132
			}
133 1
			elseif ( $class_type === self::TYPE_TRAIT ) {
134 1
				$title = 'Traits';
135
			}
136
137 1
			$ret[$title] = $class_count;
138
		}
139
140 1
		$sql = 'SELECT FileId
141
				FROM Classes
142
				GROUP BY FileId
143
				HAVING COUNT(*) > 1';
144 1
		$ret['Files With Multiple Classes'] = count($this->db->fetchCol($sql));
145
146 1
		return $ret;
147
	}
148
149
	/**
150
	 * Processes class.
151
	 *
152
	 * @param integer          $file_id File ID.
153
	 * @param \ReflectionClass $class   Class.
154
	 *
155
	 * @return void
156
	 */
157 14
	protected function processClass($file_id, \ReflectionClass $class)
158
	{
159 14
		$sql = 'SELECT Id
160
				FROM Classes
161
				WHERE FileId = :file_id AND Name = :name';
162 14
		$class_id = $this->db->fetchValue($sql, array(
163 14
			'file_id' => $file_id,
164 14
			'name' => $class->getName(),
165
		));
166
167 14
		$raw_class_relations = $this->getRawClassRelations($class);
168
169 14
		if ( $class_id === false ) {
170 14
			$sql = 'INSERT INTO Classes (Name, ClassType, IsAbstract, IsFinal, FileId, RawRelations)
171
					VALUES (:name, :class_type, :is_abstract, :is_final, :file_id, :raw_relations)';
172
173 14
			$this->db->perform(
174 14
				$sql,
175
				array(
176 14
					'name' => $class->getName(),
177 14
					'class_type' => $this->getClassType($class),
178 14
					'is_abstract' => $class->isTrait() ? 0 : (int)$class->isAbstract(),
179 14
					'is_final' => (int)$class->isFinal(),
180 14
					'file_id' => $file_id,
181 14
					'raw_relations' => $raw_class_relations ? json_encode($raw_class_relations) : null,
0 ignored issues
show
introduced by
$raw_class_relations is a non-empty array, thus is always true.
Loading history...
182
				)
183
			);
184
185 14
			$class_id = $this->db->lastInsertId();
186
		}
187
		else {
188 12
			$sql = 'UPDATE Classes
189
					SET	ClassType = :class_type,
190
						IsAbstract = :is_abstract,
191
						IsFinal = :is_final,
192
						RawRelations = :raw_relations
193
					WHERE Id = :class_id';
194
195
			// Always store relations as-is to detect fact, when all relations are removed.
196 12
			$this->db->perform(
197 12
				$sql,
198
				array(
199 12
					'class_type' => $this->getClassType($class),
200 12
					'is_abstract' => $class->isTrait() ? 0 : (int)$class->isAbstract(),
201 12
					'is_final' => (int)$class->isFinal(),
202 12
					'raw_relations' => json_encode($raw_class_relations),
203 12
					'class_id' => $class_id,
204
				)
205
			);
206
		}
207
208 14
		$this->processClassConstants($class_id, $class);
0 ignored issues
show
Bug introduced by
It seems like $class_id can also be of type string; however, parameter $class_id of ConsoleHelpers\CodeInsig...processClassConstants() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

208
		$this->processClassConstants(/** @scrutinizer ignore-type */ $class_id, $class);
Loading history...
209 14
		$this->processClassProperties($class_id, $class);
0 ignored issues
show
Bug introduced by
It seems like $class_id can also be of type string; however, parameter $class_id of ConsoleHelpers\CodeInsig...rocessClassProperties() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

209
		$this->processClassProperties(/** @scrutinizer ignore-type */ $class_id, $class);
Loading history...
210 14
		$this->processClassMethods($class_id, $class);
0 ignored issues
show
Bug introduced by
It seems like $class_id can also be of type string; however, parameter $class_id of ConsoleHelpers\CodeInsig...::processClassMethods() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

210
		$this->processClassMethods(/** @scrutinizer ignore-type */ $class_id, $class);
Loading history...
211 14
	}
212
213
	/**
214
	 * Returns class type.
215
	 *
216
	 * @param \ReflectionClass $class Class.
217
	 *
218
	 * @return integer
219
	 */
220 14
	protected function getClassType(\ReflectionClass $class)
221
	{
222 14
		if ( $class->isInterface() ) {
223 4
			return self::TYPE_INTERFACE;
224
		}
225
226 14
		if ( $class->isTrait() ) {
227 4
			return self::TYPE_TRAIT;
228
		}
229
230 14
		return self::TYPE_CLASS;
231
	}
232
233
	/**
234
	 * Get relations.
235
	 *
236
	 * @param \ReflectionClass $class Class.
237
	 *
238
	 * @return array
239
	 */
240 14
	protected function getRawClassRelations(\ReflectionClass $class)
241
	{
242 14
		$raw_relations = array();
243 14
		$parent_class = $class->getParentClass();
244
245 14
		if ( $parent_class ) {
0 ignored issues
show
introduced by
$parent_class is of type ReflectionClass, thus it always evaluated to true.
Loading history...
246 4
			$raw_relations[] = array(
247 4
				$parent_class->getName(),
248 4
				self::RELATION_TYPE_EXTENDS,
249 4
				$parent_class->isInternal(),
250
			);
251
		}
252
253 14
		foreach ( $class->getInterfaces() as $interface ) {
254 2
			$raw_relations[] = array(
255 2
				$interface->getName(),
256 2
				self::RELATION_TYPE_IMPLEMENTS,
257 2
				$interface->isInternal(),
258
			);
259
		}
260
261 14
		foreach ( $class->getTraits() as $trait ) {
262 2
			$raw_relations[] = array(
263 2
				$trait->getName(),
264 2
				self::RELATION_TYPE_USES,
265 2
				$trait->isInternal(),
266
			);
267
		}
268
269 14
		return $raw_relations;
270
	}
271
272
	/**
273
	 * Deletes a class.
274
	 *
275
	 * @param integer $class_id Class ID.
276
	 *
277
	 * @return void
278
	 */
279 2
	protected function deleteClass($class_id)
280
	{
281 2
		$sql = 'DELETE FROM Classes WHERE Id = :class_id';
282 2
		$this->db->perform($sql, array('class_id' => $class_id));
283
284 2
		$sql = 'DELETE FROM ClassConstants WHERE ClassId = :class_id';
285 2
		$this->db->perform($sql, array('class_id' => $class_id));
286
287 2
		$sql = 'DELETE FROM ClassProperties WHERE ClassId = :class_id';
288 2
		$this->db->perform($sql, array('class_id' => $class_id));
289
290 2
		$this->deleteClassMethods($class_id, array());
291
292 2
		$sql = 'DELETE FROM ClassRelations WHERE ClassId = :class_id';
293 2
		$this->db->perform($sql, array('class_id' => $class_id));
294
295 2
		$sql = 'DELETE FROM ClassRelations WHERE RelatedClassId = :class_id';
296 2
		$this->db->perform($sql, array('class_id' => $class_id));
297 2
	}
298
299
	/**
300
	 * Processes class constants.
301
	 *
302
	 * @param integer          $class_id Class ID.
303
	 * @param \ReflectionClass $class    Class.
304
	 *
305
	 * @return void
306
	 */
307 14
	protected function processClassConstants($class_id, \ReflectionClass $class)
308
	{
309
		// TODO: Find a way, how to get only constants from current class without related classes.
310 14
		$constants = $class->getConstants();
311
312 14
		$sql = 'SELECT Name
313
				FROM ClassConstants
314
				WHERE ClassId = :class_id';
315 14
		$old_constants = $this->db->fetchCol($sql, array(
316 14
			'class_id' => $class_id,
317
		));
318
319 14
		$insert_sql = '	INSERT INTO ClassConstants (ClassId, Name, Value)
320
						VALUES (:class_id, :name, :value)';
321 14
		$update_sql = '	UPDATE ClassConstants
322
						SET Value = :value
323
						WHERE ClassId = :class_id AND Name = :name';
324
325 14
		foreach ( $constants as $constant_name => $constant_value ) {
326 3
			$this->db->perform(
327 3
				in_array($constant_name, $old_constants) ? $update_sql : $insert_sql,
328
				array(
329 3
					'class_id' => $class_id,
330 3
					'name' => $constant_name,
331 3
					'value' => json_encode($constant_value),
332
				)
333
			);
334
		}
335
336 14
		$delete_constants = array_diff($old_constants, array_keys($constants));
337
338 14
		if ( $delete_constants ) {
339 1
			$sql = 'DELETE FROM ClassConstants
340
					WHERE ClassId = :class_id AND Name IN (:names)';
341 1
			$this->db->perform($sql, array(
342 1
				'class_id' => $class_id,
343 1
				'names' => $delete_constants,
344
			));
345
		}
346 14
	}
347
348
	/**
349
	 * Processes class properties.
350
	 *
351
	 * @param integer          $class_id Class ID.
352
	 * @param \ReflectionClass $class    Class.
353
	 *
354
	 * @return void
355
	 */
356 14
	protected function processClassProperties($class_id, \ReflectionClass $class)
357
	{
358 14
		$sql = 'SELECT Name
359
				FROM ClassProperties
360
				WHERE ClassId = :class_id';
361 14
		$old_properties = $this->db->fetchCol($sql, array(
362 14
			'class_id' => $class_id,
363
		));
364
365 14
		$insert_sql = '	INSERT INTO ClassProperties (ClassId, Name, Value, Scope, IsStatic)
366
						VALUES (:class_id, :name, :value, :scope, :is_static)';
367 14
		$update_sql = '	UPDATE ClassProperties
368
						SET	Value = :value,
369
							Scope = :scope,
370
							IsStatic = :is_static
371
						WHERE ClassId = :class_id AND Name = :name';
372
373 14
		$new_properties = array();
374 14
		$property_defaults = $class->getDefaultProperties();
375 14
		$static_properties = $class->getStaticProperties();
376 14
		$class_name = $class->getName();
377
378 14
		foreach ( $class->getProperties() as $property ) {
379 5
			if ( $property->class !== $class_name ) {
380 1
				continue;
381
			}
382
383 5
			$property_name = $property->getName();
384 5
			$property_value = isset($property_defaults[$property_name]) ? $property_defaults[$property_name] : null;
385 5
			$new_properties[] = $property_name;
386
387 5
			$this->db->perform(
388 5
				in_array($property_name, $old_properties) ? $update_sql : $insert_sql,
389
				array(
390 5
					'class_id' => $class_id,
391 5
					'name' => $property_name,
392 5
					'value' => json_encode($property_value),
393 5
					'scope' => $this->getPropertyScope($property),
394 5
					'is_static' => (int)array_key_exists($property_name, $static_properties),
395
				)
396
			);
397
		}
398
399 14
		$delete_properties = array_diff($old_properties, $new_properties);
400
401 14
		if ( $delete_properties ) {
402 1
			$sql = 'DELETE FROM ClassProperties
403
					WHERE ClassId = :class_id AND Name IN (:names)';
404 1
			$this->db->perform($sql, array(
405 1
				'class_id' => $class_id,
406 1
				'names' => $delete_properties,
407
			));
408
		}
409 14
	}
410
411
	/**
412
	 * Returns property scope.
413
	 *
414
	 * @param \ReflectionProperty $property Property.
415
	 *
416
	 * @return integer
417
	 */
418 5
	protected function getPropertyScope(\ReflectionProperty $property)
419
	{
420 5
		if ( $property->isPrivate() ) {
421 1
			return self::SCOPE_PRIVATE;
422
		}
423
424 5
		if ( $property->isProtected() ) {
425 2
			return self::SCOPE_PROTECTED;
426
		}
427
428 5
		return self::SCOPE_PUBLIC;
429
	}
430
431
	/**
432
	 * Processes methods.
433
	 *
434
	 * @param integer          $class_id Class ID.
435
	 * @param \ReflectionClass $class    Class.
436
	 *
437
	 * @return void
438
	 */
439 14
	protected function processClassMethods($class_id, \ReflectionClass $class)
440
	{
441 14
		$sql = 'SELECT Name, Id
442
				FROM ClassMethods
443
				WHERE ClassId = :class_id';
444 14
		$old_methods = $this->db->fetchPairs($sql, array(
445 14
			'class_id' => $class_id,
446
		));
447
448 14
		$insert_sql = '	INSERT INTO ClassMethods (ClassId, Name, ParameterCount, RequiredParameterCount, Scope, IsAbstract, IsFinal, IsStatic, IsVariadic, ReturnsReference, HasReturnType, ReturnType)
449
						VALUES (:class_id, :name, :parameter_count, :required_parameter_count, :scope, :is_abstract, :is_final, :is_static, :is_variadic, :returns_reference, :has_return_type, :return_type)';
450 14
		$update_sql = '	UPDATE ClassMethods
451
						SET	ParameterCount = :parameter_count,
452
							RequiredParameterCount = :required_parameter_count,
453
							Scope = :scope,
454
							IsAbstract = :is_abstract,
455
							IsFinal = :is_final,
456
							IsStatic = :is_static,
457
							IsVariadic = :is_variadic,
458
							ReturnsReference = :returns_reference,
459
							ReturnType = :return_type,
460
							HasReturnType = :has_return_type
461
						WHERE ClassId = :class_id AND Name = :name';
462
463 14
		$new_methods = array();
464 14
		$class_name = $class->getName();
465
466 14
		foreach ( $class->getMethods() as $method ) {
467 5
			if ( $method->class !== $class_name ) {
468 1
				continue;
469
			}
470
471 5
			$method_name = $method->getName();
472 5
			$new_methods[] = $method_name;
473
474
			// Doesn't work for parent classes (see https://github.com/goaop/parser-reflection/issues/16).
475 5
			$has_return_type = $method->hasReturnType();
476 5
			$return_type = $has_return_type ? (string)$method->getReturnType() : null;
477
478 5
			$this->db->perform(
479 5
				isset($old_methods[$method_name]) ? $update_sql : $insert_sql,
480
				array(
481 5
					'class_id' => $class_id,
482 5
					'name' => $method_name,
483 5
					'parameter_count' => $method->getNumberOfParameters(),
484 5
					'required_parameter_count' => $method->getNumberOfRequiredParameters(),
485 5
					'scope' => $this->getMethodScope($method),
486 5
					'is_abstract' => (int)$method->isAbstract(),
487 5
					'is_final' => (int)$method->isFinal(),
488 5
					'is_static' => (int)$method->isStatic(),
489 5
					'is_variadic' => (int)$method->isVariadic(),
490 5
					'returns_reference' => (int)$method->returnsReference(),
491 5
					'has_return_type' => (int)$has_return_type,
492 5
					'return_type' => $return_type,
493
				)
494
			);
495
496 5
			$method_id = isset($old_methods[$method_name]) ? $old_methods[$method_name] : $this->db->lastInsertId();
497 5
			$this->processClassMethodParameters($method_id, $method);
0 ignored issues
show
Bug introduced by
It seems like $method_id can also be of type string; however, parameter $method_id of ConsoleHelpers\CodeInsig...ClassMethodParameters() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

497
			$this->processClassMethodParameters(/** @scrutinizer ignore-type */ $method_id, $method);
Loading history...
498
		}
499
500 14
		$delete_methods = array_diff(array_keys($old_methods), $new_methods);
501
502 14
		if ( $delete_methods ) {
503 1
			$this->deleteClassMethods($class_id, $delete_methods);
504
		}
505 14
	}
506
507
	/**
508
	 * Deletes methods.
509
	 *
510
	 * @param integer $class_id Class ID.
511
	 * @param array   $methods  Methods.
512
	 *
513
	 * @return void
514
	 */
515 2
	protected function deleteClassMethods($class_id, array $methods)
516
	{
517 2
		if ( $methods ) {
518
			// Delete only given methods.
519 1
			$sql = 'SELECT Id
520
					FROM ClassMethods
521
					WHERE ClassId = :class_id AND Name IN (:names)';
522 1
			$method_ids = $this->db->fetchCol($sql, array(
523 1
				'class_id' => $class_id,
524 1
				'names' => $methods,
525
			));
526
		}
527
		else {
528
			// Delete all methods in a class.
529 2
			$sql = 'SELECT Id
530
					FROM ClassMethods
531
					WHERE ClassId = :class_id';
532 2
			$method_ids = $this->db->fetchCol($sql, array(
533 2
				'class_id' => $class_id,
534
			));
535
		}
536
537
		// @codeCoverageIgnoreStart
538
		if ( !$method_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $method_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
539
			return;
540
		}
541
		// @codeCoverageIgnoreEnd
542
543 2
		$sql = 'DELETE FROM ClassMethods WHERE Id IN (:method_ids)';
544 2
		$this->db->perform($sql, array('method_ids' => $method_ids));
545
546 2
		$sql = 'DELETE FROM MethodParameters WHERE MethodId IN (:method_ids)';
547 2
		$this->db->perform($sql, array('method_ids' => $method_ids));
548 2
	}
549
550
	/**
551
	 * Processes method parameters.
552
	 *
553
	 * @param integer           $method_id Method ID.
554
	 * @param \ReflectionMethod $method    Method.
555
	 *
556
	 * @return void
557
	 */
558 5
	protected function processClassMethodParameters($method_id, \ReflectionMethod $method)
559
	{
560 5
		$sql = 'SELECT Name
561
				FROM MethodParameters
562
				WHERE MethodId = :method_id';
563 5
		$old_parameters = $this->db->fetchCol($sql, array(
564 5
			'method_id' => $method_id,
565
		));
566
567 5
		$insert_sql = '	INSERT INTO MethodParameters (MethodId, Name, Position, TypeClass, HasType, TypeName, AllowsNull, IsArray, IsCallable, IsOptional, IsVariadic, CanBePassedByValue, IsPassedByReference, HasDefaultValue, DefaultValue, DefaultConstant)
568
						VALUES (:method_id, :name, :position, :type_class, :has_type, :type_name, :allows_null, :is_array, :is_callable, :is_optional, :is_variadic, :can_be_passed_by_value, :is_passed_by_reference, :has_default_value, :default_value, :default_constant)';
569 5
		$update_sql = '	UPDATE MethodParameters
570
						SET	Position = :position,
571
							TypeClass = :type_class,
572
							HasType = :has_type,
573
							TypeName = :type_name,
574
							AllowsNull = :allows_null,
575
							IsArray = :is_array,
576
							IsCallable = :is_callable,
577
							IsOptional = :is_optional,
578
							IsVariadic = :is_variadic,
579
							CanBePassedByValue = :can_be_passed_by_value,
580
							IsPassedByReference = :is_passed_by_reference,
581
							HasDefaultValue = :has_default_value,
582
							DefaultValue = :default_value,
583
							DefaultConstant = :default_constant
584
						WHERE MethodId = :method_id AND Name = :name';
585
586 5
		$new_parameters = array();
587
588 5
		foreach ( $method->getParameters() as $position => $parameter ) {
589 4
			$parameter_name = $parameter->getName();
590 4
			$new_parameters[] = $parameter_name;
591
592 4
			$type_class = $parameter->getClass();
593 4
			$type_class = $type_class ? $type_class->getName() : null;
594
595
			// Doesn't work for parent classes (see https://github.com/goaop/parser-reflection/issues/16).
596 4
			$has_type = $parameter->hasType();
597 4
			$type_name = $has_type ? (string)$parameter->getType() : null;
598
599 4
			$has_default_value = $parameter->isDefaultValueAvailable();
600 4
			$default_value_is_constant = $has_default_value ? $parameter->isDefaultValueConstant() : false;
601
602 4
			$this->db->perform(
603 4
				in_array($parameter_name, $old_parameters) ? $update_sql : $insert_sql,
604
				array(
605 4
					'method_id' => $method_id,
606 4
					'name' => $parameter_name,
607 4
					'position' => $position,
608 4
					'type_class' => $type_class,
609 4
					'has_type' => (int)$has_type,
610 4
					'type_name' => $type_name,
611 4
					'allows_null' => (int)$parameter->allowsNull(),
612 4
					'is_array' => (int)$parameter->isArray(),
613 4
					'is_callable' => (int)$parameter->isCallable(),
614 4
					'is_optional' => (int)$parameter->isOptional(),
615 4
					'is_variadic' => (int)$parameter->isVariadic(),
616 4
					'can_be_passed_by_value' => (int)$parameter->canBePassedByValue(),
617 4
					'is_passed_by_reference' => (int)$parameter->isPassedByReference(),
618 4
					'has_default_value' => (int)$has_default_value,
619 4
					'default_value' => $has_default_value ? json_encode($parameter->getDefaultValue()) : null,
620 4
					'default_constant' => $default_value_is_constant ? $parameter->getDefaultValueConstantName() : null,
621
				)
622
			);
623
		}
624
625 5
		$delete_parameters = array_diff($old_parameters, $new_parameters);
626
627 5
		if ( $delete_parameters ) {
628 2
			$sql = 'DELETE FROM MethodParameters
629
					WHERE MethodId = :method_id AND Name IN (:names)';
630 2
			$this->db->perform($sql, array(
631 2
				'method_id' => $method_id,
632 2
				'names' => $delete_parameters,
633
			));
634
		}
635 5
	}
636
637
	/**
638
	 * Returns method scope.
639
	 *
640
	 * @param \ReflectionMethod $method Method.
641
	 *
642
	 * @return integer
643
	 */
644 5
	protected function getMethodScope(\ReflectionMethod $method)
645
	{
646 5
		if ( $method->isPrivate() ) {
647 1
			return self::SCOPE_PRIVATE;
648
		}
649
650 5
		if ( $method->isProtected() ) {
651 1
			return self::SCOPE_PROTECTED;
652
		}
653
654 5
		return self::SCOPE_PUBLIC;
655
	}
656
657
	/**
658
	 * Processes raw relations for all classes.
659
	 *
660
	 * @param KnowledgeBase $knowledge_base Knowledge base.
661
	 *
662
	 * @return void
663
	 */
664 4
	protected function processClassRawRelations(KnowledgeBase $knowledge_base)
665
	{
666 4
		$sql = 'SELECT Id, RawRelations
667
				FROM Classes
668
				WHERE RawRelations IS NOT NULL';
669 4
		$raw_relations = $this->db->yieldPairs($sql);
670
671 4
		foreach ( $raw_relations as $class_id => $class_raw_relations ) {
672 4
			$sql = 'SELECT RelatedClass
673
					FROM ClassRelations
674
					WHERE ClassId = :class_id';
675 4
			$old_class_relations = $this->db->fetchCol($sql, array(
676 4
				'class_id' => $class_id,
677
			));
678
679 4
			$new_class_relations = array();
680
681 4
			foreach ( json_decode($class_raw_relations, true) as $class_raw_relation ) {
682 4
				list ($related_class, $relation_type, $is_internal) = $class_raw_relation;
683
684 4
				$new_class_relations[] = $this->addRelation(
685 4
					$knowledge_base,
686 4
					$class_id,
0 ignored issues
show
Bug introduced by
It seems like $class_id can also be of type double and string and true; however, parameter $class_id of ConsoleHelpers\CodeInsig...ollector::addRelation() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

686
					/** @scrutinizer ignore-type */ $class_id,
Loading history...
687 4
					$related_class,
688 4
					$relation_type,
689 4
					$is_internal,
690 4
					$old_class_relations
691
				);
692
			}
693
694 4
			$delete_class_relations = array_diff($old_class_relations, $new_class_relations);
695
696 4
			if ( $delete_class_relations ) {
697 1
				$sql = 'DELETE FROM ClassRelations
698
						WHERE ClassId = :class_id AND RelatedClass IN (:related_classes)';
699 1
				$this->db->perform($sql, array(
700 1
					'class_id' => $class_id,
701 4
					'related_classes' => $delete_class_relations,
702
				));
703
			}
704
		}
705
706 4
		$sql = 'UPDATE Classes
707
				SET RawRelations = NULL';
708 4
		$this->db->perform($sql);
709 4
	}
710
711
	/**
712
	 * Adds a relation.
713
	 *
714
	 * @param KnowledgeBase $knowledge_base Knowledge base.
715
	 * @param integer       $class_id       Class ID.
716
	 * @param string        $related_class  Related class.
717
	 * @param integer       $relation_type  Relation type.
718
	 * @param boolean       $is_internal    Is internal.
719
	 * @param array         $old_relations  Old relations.
720
	 *
721
	 * @return string
722
	 */
723 4
	protected function addRelation(
724
		KnowledgeBase $knowledge_base,
725
		$class_id,
726
		$related_class,
727
		$relation_type,
728
		$is_internal,
729
		array $old_relations
730
	) {
731 4
		$insert_sql = '	INSERT INTO ClassRelations (ClassId, RelatedClass, RelatedClassId, RelationType)
732
						VALUES (:class_id, :related_class, :related_class_id, :relation_type)';
733 4
		$update_sql = ' UPDATE ClassRelations
734
						SET RelationType = :relation_type
735
						WHERE ClassId = :class_id AND RelatedClassId = :related_class_id';
736
737 4
		if ( $is_internal ) {
738 3
			$related_class_id = 0;
739
		}
740
		else {
741 2
			$related_class_file = realpath(ReflectionEngine::locateClassFile($related_class));
742
743 2
			$sql = 'SELECT Id
744
					FROM Classes
745
					WHERE FileId = :file_id AND Name = :name';
746 2
			$related_class_id = $this->db->fetchValue($sql, array(
747 2
				'file_id' => $knowledge_base->processFile($related_class_file),
748 2
				'name' => $related_class,
749
			));
750
		}
751
752 4
		$this->db->perform(
753 4
			in_array($related_class, $old_relations) ? $update_sql : $insert_sql,
754
			array(
755 4
				'class_id' => $class_id,
756 4
				'related_class' => $related_class,
757 4
				'related_class_id' => $related_class_id,
758 4
				'relation_type' => $relation_type,
759
			)
760
		);
761
762 4
		return $related_class;
763
	}
764
765
}
766