Failed Conditions
Push — master ( 9e0fbd...284e4a )
by Alexander
01:54
created

KnowledgeBase::refresh()   C

Complexity

Conditions 7
Paths 17

Size

Total Lines 68
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 68
ccs 0
cts 45
cp 0
rs 6.9654
c 1
b 0
f 0
cc 7
eloc 38
nc 17
nop 0
crap 56

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
12
13
14
use Aura\Sql\ExtendedPdoInterface;
15
use Composer\Autoload\ClassLoader;
16
use ConsoleHelpers\CodeInsight\KnowledgeBase\DataCollector\AbstractDataCollector;
17
use ConsoleHelpers\CodeInsight\KnowledgeBase\DataCollector\ClassDataCollector;
18
use ConsoleHelpers\CodeInsight\KnowledgeBase\DataCollector\ConstantDataCollector;
19
use ConsoleHelpers\CodeInsight\KnowledgeBase\DataCollector\FunctionDataCollector;
20
use ConsoleHelpers\ConsoleKit\ConsoleIO;
21
use Go\ParserReflection\Locator\CallableLocator;
22
use Go\ParserReflection\Locator\ComposerLocator;
23
use Go\ParserReflection\LocatorInterface;
24
use Go\ParserReflection\ReflectionEngine;
25
use Go\ParserReflection\ReflectionFile;
26
use Symfony\Component\Finder\Finder;
27
28
class KnowledgeBase
29
{
30
31
	/**
32
	 * Project path.
33
	 *
34
	 * @var string
35
	 */
36
	protected $projectPath = '';
37
38
	/**
39
	 * Regular expression for removing project path.
40
	 *
41
	 * @var string
42
	 */
43
	protected $projectPathRegExp = '';
44
45
	/**
46
	 * Database.
47
	 *
48
	 * @var ExtendedPdoInterface
49
	 */
50
	protected $db;
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...
51
52
	/**
53
	 * Config
54
	 *
55
	 * @var array
56
	 */
57
	protected $config = array();
58
59
	/**
60
	 * Data collectors.
61
	 *
62
	 * @var AbstractDataCollector[]
63
	 */
64
	protected $dataCollectors = array();
65
66
	/**
67
	 * Console IO.
68
	 *
69
	 * @var ConsoleIO
70
	 */
71
	protected $io;
1 ignored issue
show
Comprehensibility introduced by
Avoid variables with short names like $io. 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...
72
73
	/**
74
	 * Creates knowledge base instance.
75
	 *
76
	 * @param string               $project_path Project path.
77
	 * @param ExtendedPdoInterface $db           Database.
78
	 * @param ConsoleIO            $io           Console IO.
79
	 *
80
	 * @throws \InvalidArgumentException When project path doesn't exist.
81
	 */
82
	public function __construct($project_path, ExtendedPdoInterface $db, ConsoleIO $io = null)
2 ignored issues
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...
Comprehensibility introduced by
Avoid variables with short names like $io. 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...
83
	{
84
		if ( !file_exists($project_path) || !is_dir($project_path) ) {
85
			throw new \InvalidArgumentException('The project path doesn\'t exist.');
86
		}
87
88
		$this->projectPath = $project_path;
89
		$this->projectPathRegExp = '#^' . preg_quote($project_path, '#') . '/#';
90
91
		$this->db = $db;
92
		$this->config = $this->getConfiguration();
93
		$this->io = $io;
94
95
		$this->dataCollectors[] = new ClassDataCollector($db);
96
		$this->dataCollectors[] = new ConstantDataCollector($db);
97
		$this->dataCollectors[] = new FunctionDataCollector($db);
98
	}
99
100
	/**
101
	 * Returns database.
102
	 *
103
	 * @return ExtendedPdoInterface
104
	 */
105
	public function getDatabase()
106
	{
107
		return $this->db;
108
	}
109
110
	/**
111
	 * Returns project configuration.
112
	 *
113
	 * @return array
114
	 * @throws \LogicException When configuration file is not found.
115
	 * @throws \LogicException When configuration file isn't in JSON format.
116
	 */
117
	protected function getConfiguration()
118
	{
119
		$config_file = $this->projectPath . '/.code-insight.json';
120
121
		if ( !file_exists($config_file) ) {
122
			throw new \LogicException(
123
				'Configuration file ".code-insight.json" not found at "' . $this->projectPath . '".'
124
			);
125
		}
126
127
		$config = json_decode(file_get_contents($config_file), true);
128
129
		if ( $config === null ) {
130
			throw new \LogicException('Configuration file ".code-insight.json" is not in JSON format.');
131
		}
132
133
		return $config;
134
	}
135
136
	/**
137
	 * Refreshes database.
138
	 *
139
	 * @return void
140
	 * @throws \LogicException When "$this->io" wasn't set upfront.
141
	 */
142
	public function refresh()
143
	{
144
		if ( !isset($this->io) ) {
145
			throw new \LogicException('The "$this->io" must be set prior to calling "$this->refresh()".');
146
		}
147
148
		ReflectionEngine::setMaximumCachedFiles(20);
149
		ReflectionEngine::init($this->detectClassLocator());
150
151
		$sql = 'UPDATE Files
152
				SET Found = 0';
153
		$this->db->perform($sql);
154
155
		$files = array();
156
		$this->io->write('Searching for files ... ');
157
158
		foreach ( $this->getFinders() as $finder ) {
159
			$files = array_merge($files, array_keys(iterator_to_array($finder)));
160
		}
161
162
		$file_count = count($files);
163
		$this->io->writeln(array('<info>' . $file_count . ' found</info>', ''));
164
165
166
		$progress_bar = $this->io->createProgressBar($file_count + 2);
167
		$progress_bar->setMessage('');
168
		$progress_bar->setFormat(
169
			'%message%' . PHP_EOL . '%current%/%max% [%bar%] <info>%percent:3s%%</info> %elapsed:6s%/%estimated:-6s% <info>%memory:-10s%</info>'
170
		);
171
		$progress_bar->start();
172
173
		foreach ( $files as $file ) {
174
			$progress_bar->setMessage('Processing file: <info>' . $this->removeProjectPath($file) . '</info>');
175
			$progress_bar->display();
176
177
			$this->processFile($file);
178
179
			$progress_bar->advance();
180
		}
181
182
		$sql = 'SELECT Id
183
				FROM Files
184
				WHERE Found = 0';
185
		$deleted_files = $this->db->fetchCol($sql);
186
187
		if ( $deleted_files ) {
188
			$progress_bar->setMessage('Erasing information about deleted files ...');
189
			$progress_bar->display();
190
191
			foreach ( $this->dataCollectors as $data_collector ) {
192
				$data_collector->deleteData($deleted_files);
193
			}
194
195
			$progress_bar->advance();
196
		}
197
198
		$progress_bar->setMessage('Aggregating processed data ...');
199
		$progress_bar->display();
200
201
		foreach ( $this->dataCollectors as $data_collector ) {
202
			$data_collector->aggregateData($this);
203
		}
204
205
		$progress_bar->advance();
206
207
		$progress_bar->finish();
208
		$progress_bar->clear();
209
	}
210
211
	/**
212
	 * Prints statistics about the code.
213
	 *
214
	 * @return array
215
	 */
216
	public function getStatistics()
217
	{
218
		$ret = array();
219
220
		$sql = 'SELECT COUNT(*)
221
				FROM Files';
222
		$ret['Files'] = $this->db->fetchValue($sql);
223
224
		foreach ( $this->dataCollectors as $data_collector ) {
225
			$ret = array_merge($ret, $data_collector->getStatistics());
226
		}
227
228
		return $ret;
229
	}
230
231
	/**
232
	 * Processes file.
233
	 *
234
	 * @param string $file File.
235
	 *
236
	 * @return integer
237
	 */
238
	public function processFile($file)
239
	{
240
		$size = filesize($file);
241
		$relative_file = $this->removeProjectPath($file);
242
243
		$sql = 'SELECT Id, Size
244
				FROM Files
245
				WHERE Name = :name';
246
		$file_data = $this->db->fetchOne($sql, array(
247
			'name' => $relative_file,
248
		));
249
250
		$this->db->beginTransaction();
251
252
		if ( $file_data === false ) {
253
			$sql = 'INSERT INTO Files (Name, Size) VALUES (:name, :size)';
254
			$this->db->perform($sql, array(
255
				'name' => $relative_file,
256
				'size' => $size,
257
			));
258
259
			$file_id = $this->db->lastInsertId();
260
		}
261
		else {
262
			$file_id = $file_data['Id'];
263
		}
264
265
		// File is not changed since last time it was indexed.
266
		if ( $file_data !== false && (int)$file_data['Size'] === $size ) {
267
			$sql = 'UPDATE Files
268
					SET Found = 1
269
					WHERE Id = :file_id';
270
			$this->db->perform($sql, array(
271
				'file_id' => $file_data['Id'],
272
			));
273
274
			$this->db->commit();
275
276
			return $file_data['Id'];
277
		}
278
279
		$sql = 'UPDATE Files
280
				SET Found = 1
281
				WHERE Id = :file_id';
282
		$this->db->perform($sql, array(
283
			'file_id' => $file_data['Id'],
284
		));
285
286
		$parsed_file = new ReflectionFile($file);
287
288
		foreach ( $parsed_file->getFileNamespaces() as $namespace ) {
289
			foreach ( $this->dataCollectors as $data_collector ) {
290
				$data_collector->collectData($file_id, $namespace);
291
			}
292
		}
293
294
		$this->db->commit();
295
296
		return $file_id;
297
	}
298
299
	/**
300
	 * Determines class locator.
301
	 *
302
	 * @return LocatorInterface
303
	 * @throws \LogicException When class locator from "class_locator" setting doesn't exist.
304
	 * @throws \LogicException When class locator from "class_locator" setting has non supported type.
305
	 */
306
	protected function detectClassLocator()
307
	{
308
		$class_locator = null;
309
310
		if ( isset($this->config['class_locator']) ) {
311
			$class_locator_file = $this->resolveProjectPath($this->config['class_locator']);
312
313
			if ( !file_exists($class_locator_file) || !is_file($class_locator_file) ) {
314
				throw new \LogicException(
315
					'The "' . $this->config['class_locator'] . '" class locator doesn\'t exist.'
316
				);
317
			}
318
319
			$class_locator = require $class_locator_file;
320
		}
321
		else {
322
			$class_locator_file = $this->resolveProjectPath('vendor/autoload.php');
323
324
			if ( file_exists($class_locator_file) && is_file($class_locator_file) ) {
325
				$class_locator = require $class_locator_file;
326
			}
327
		}
328
329
		// Make sure memory limit isn't changed by class locator.
330
		ini_restore('memory_limit');
331
332
		if ( is_callable($class_locator) ) {
333
			return new CallableLocator($class_locator);
334
		}
335
		elseif ( $class_locator instanceof ClassLoader ) {
1 ignored issue
show
Bug introduced by
The class Composer\Autoload\ClassLoader does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
336
			return new ComposerLocator($class_locator);
337
		}
338
339
		throw new \LogicException(
340
			'The "class_loader" setting must point to "vendor/autoload.php" or a file, that would return the closure.'
341
		);
342
	}
343
344
	/**
345
	 * Processes the Finders configuration list.
346
	 *
347
	 * @return Finder[]
348
	 * @throws \LogicException If "finder" setting doesn't exist.
349
	 * @throws \LogicException If the configured method does not exist.
350
	 */
351
	protected function getFinders()
352
	{
353
		// Process "finder" config setting.
354
		if ( !isset($this->config['finder']) ) {
355
			throw new \LogicException('The "finder" setting must be present in config file.');
356
		}
357
358
		$finders = array();
359
360
		foreach ( $this->config['finder'] as $methods ) {
361
			$finder = Finder::create()->files();
362
363
			if ( isset($methods['in']) ) {
364
				$methods['in'] = (array)$methods['in'];
365
366
				foreach ( $methods['in'] as $folder_index => $in_folder ) {
367
					$methods['in'][$folder_index] = $this->resolveProjectPath($in_folder);
368
				}
369
			}
370
371
			foreach ( $methods as $method => $arguments ) {
372
				if ( !method_exists($finder, $method) ) {
373
					throw new \LogicException(sprintf(
374
						'The method "Finder::%s" does not exist.',
375
						$method
376
					));
377
				}
378
379
				$arguments = (array)$arguments;
380
381
				foreach ( $arguments as $argument ) {
382
					$finder->$method($argument);
383
				}
384
			}
385
386
			$finders[] = $finder;
387
		}
388
389
		return $finders;
390
	}
391
392
	/**
393
	 * Resolves path within project.
394
	 *
395
	 * @param string $relative_path Relative path.
396
	 *
397
	 * @return string
398
	 */
399
	protected function resolveProjectPath($relative_path)
400
	{
401
		return realpath($this->projectPath . DIRECTORY_SEPARATOR . $relative_path);
402
	}
403
404
	/**
405
	 * Removes project path from file path.
406
	 *
407
	 * @param string $absolute_path Absolute path.
408
	 *
409
	 * @return string
410
	 */
411
	protected function removeProjectPath($absolute_path)
412
	{
413
		return preg_replace($this->projectPathRegExp, '', $absolute_path, 1);
414
	}
415
416
	/**
417
	 * Finds backward compatibility breaks.
418
	 *
419
	 * @param ExtendedPdoInterface $source_db Source database.
420
	 *
421
	 * @return array
422
	 */
423
	public function getBackwardsCompatibilityBreaks(ExtendedPdoInterface $source_db)
424
	{
425
		$breaks = array();
426
427
		foreach ( $this->dataCollectors as $data_collector ) {
428
			$breaks = array_merge(
429
				$breaks,
430
				$data_collector->getBackwardsCompatibilityBreaks($source_db)
431
			);
432
		}
433
434
		return $breaks;
435
	}
436
437
}
438