console-helpers /
code-insight
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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\ConsoleKit\ConsoleIO; |
||
| 17 | use Go\ParserReflection\Locator\CallableLocator; |
||
| 18 | use Go\ParserReflection\Locator\ComposerLocator; |
||
| 19 | use Go\ParserReflection\LocatorInterface; |
||
| 20 | use Go\ParserReflection\ReflectionEngine; |
||
| 21 | use Go\ParserReflection\ReflectionFile; |
||
| 22 | use Symfony\Component\Finder\Finder; |
||
| 23 | |||
| 24 | class KnowledgeBase |
||
| 25 | { |
||
| 26 | |||
| 27 | const SCOPE_PRIVATE = 1; |
||
| 28 | |||
| 29 | const SCOPE_PROTECTED = 2; |
||
| 30 | |||
| 31 | const SCOPE_PUBLIC = 3; |
||
| 32 | |||
| 33 | const CLASS_TYPE_CLASS = 1; |
||
| 34 | |||
| 35 | const CLASS_TYPE_INTERFACE = 2; |
||
| 36 | |||
| 37 | const CLASS_TYPE_TRAIT = 3; |
||
| 38 | |||
| 39 | const RELATION_TYPE_EXTENDS = 1; |
||
| 40 | |||
| 41 | const RELATION_TYPE_IMPLEMENTS = 2; |
||
| 42 | |||
| 43 | /** |
||
| 44 | * Project path. |
||
| 45 | * |
||
| 46 | * @var string |
||
| 47 | */ |
||
| 48 | protected $projectPath = ''; |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Regular expression for removing project path. |
||
| 52 | * |
||
| 53 | * @var string |
||
| 54 | */ |
||
| 55 | protected $projectPathRegExp = ''; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * Database. |
||
| 59 | * |
||
| 60 | * @var ExtendedPdoInterface |
||
| 61 | */ |
||
| 62 | protected $db; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * Config |
||
| 66 | * |
||
| 67 | * @var array |
||
| 68 | */ |
||
| 69 | protected $config = array(); |
||
| 70 | |||
| 71 | /** |
||
| 72 | * Console IO. |
||
| 73 | * |
||
| 74 | * @var ConsoleIO |
||
| 75 | */ |
||
| 76 | protected $io; |
||
| 77 | |||
| 78 | /** |
||
| 79 | * Creates knowledge base instance. |
||
| 80 | * |
||
| 81 | * @param string $project_path Project path. |
||
| 82 | * @param ExtendedPdoInterface $db Database. |
||
| 83 | * @param ConsoleIO $io Console IO. |
||
| 84 | * |
||
| 85 | * @throws \InvalidArgumentException When project path doesn't exist. |
||
| 86 | */ |
||
| 87 | public function __construct($project_path, ExtendedPdoInterface $db, ConsoleIO $io = null) |
||
| 88 | { |
||
| 89 | if ( !file_exists($project_path) || !is_dir($project_path) ) { |
||
| 90 | throw new \InvalidArgumentException('The project path doesn\'t exist.'); |
||
| 91 | } |
||
| 92 | |||
| 93 | $this->projectPath = $project_path; |
||
| 94 | $this->projectPathRegExp = '#^' . preg_quote($project_path, '#') . '/#'; |
||
| 95 | |||
| 96 | $this->db = $db; |
||
| 97 | $this->config = $this->getConfiguration(); |
||
| 98 | $this->io = $io; |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Returns project configuration. |
||
| 103 | * |
||
| 104 | * @return array |
||
| 105 | * @throws \LogicException When configuration file is not found. |
||
| 106 | * @throws \LogicException When configuration file isn't in JSON format. |
||
| 107 | */ |
||
| 108 | protected function getConfiguration() |
||
| 109 | { |
||
| 110 | $config_file = $this->projectPath . '/.code-insight.json'; |
||
| 111 | |||
| 112 | if ( !file_exists($config_file) ) { |
||
| 113 | throw new \LogicException( |
||
| 114 | 'Configuration file ".code-insight.json" not found at "' . $this->projectPath . '".' |
||
| 115 | ); |
||
| 116 | } |
||
| 117 | |||
| 118 | $config = json_decode(file_get_contents($config_file), true); |
||
| 119 | |||
| 120 | if ( $config === null ) { |
||
| 121 | throw new \LogicException('Configuration file ".code-insight.json" is not in JSON format.'); |
||
| 122 | } |
||
| 123 | |||
| 124 | return $config; |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Refreshes database. |
||
| 129 | * |
||
| 130 | * @return void |
||
| 131 | * @throws \LogicException When "$this->io" wasn't set upfront. |
||
| 132 | */ |
||
| 133 | public function refresh() |
||
| 134 | { |
||
| 135 | if ( !isset($this->io) ) { |
||
| 136 | throw new \LogicException('The "$this->io" must be set prior to calling "$this->refresh()".'); |
||
| 137 | } |
||
| 138 | |||
| 139 | //ReflectionEngine::$maximumCachedFiles = 10; |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 140 | ReflectionEngine::init($this->detectClassLocator()); |
||
| 141 | |||
| 142 | $sql = 'UPDATE Files |
||
| 143 | SET Found = 0'; |
||
| 144 | $this->db->perform($sql); |
||
| 145 | |||
| 146 | $files = array(); |
||
| 147 | $this->io->write('Searching for files ... '); |
||
| 148 | |||
| 149 | foreach ( $this->getFinders() as $finder ) { |
||
| 150 | $files = array_merge($files, array_keys(iterator_to_array($finder))); |
||
| 151 | } |
||
| 152 | |||
| 153 | $file_count = count($files); |
||
| 154 | $this->io->writeln(array('<info>' . $file_count . ' found</info>', '')); |
||
| 155 | |||
|
0 ignored issues
–
show
|
|||
| 156 | |||
| 157 | $progress_bar = $this->io->createProgressBar($file_count + 2); |
||
| 158 | $progress_bar->setMessage(''); |
||
| 159 | $progress_bar->setFormat( |
||
| 160 | '%message%' . PHP_EOL . '%current%/%max% [%bar%] <info>%percent:3s%%</info> %elapsed:6s%/%estimated:-6s% <info>%memory:-10s%</info>' |
||
|
0 ignored issues
–
show
|
|||
| 161 | ); |
||
| 162 | $progress_bar->start(); |
||
| 163 | |||
| 164 | foreach ( $files as $file ) { |
||
| 165 | $progress_bar->setMessage('Processing File: <info>' . $this->removeProjectPath($file) . '</info>'); |
||
| 166 | $progress_bar->display(); |
||
| 167 | |||
| 168 | $this->processFile($file); |
||
| 169 | |||
| 170 | $progress_bar->advance(); |
||
| 171 | } |
||
| 172 | |||
| 173 | $sql = 'SELECT Id |
||
| 174 | FROM Files |
||
| 175 | WHERE Found = 0'; |
||
| 176 | $deleted_files = $this->db->fetchCol($sql); |
||
| 177 | |||
| 178 | if ( $deleted_files ) { |
||
| 179 | $progress_bar->setMessage('Deleting Files ...'); |
||
| 180 | $progress_bar->display(); |
||
| 181 | |||
| 182 | $sql = 'SELECT Id |
||
| 183 | FROM Classes |
||
| 184 | WHERE FileId IN (:file_ids)'; |
||
| 185 | $deleted_classes = $this->db->fetchCol($sql, array( |
||
| 186 | 'file_ids' => $deleted_files, |
||
| 187 | )); |
||
| 188 | |||
| 189 | foreach ( $deleted_classes as $deleted_class_id ) { |
||
| 190 | $this->deleteClass($deleted_class_id); |
||
| 191 | } |
||
| 192 | |||
| 193 | $progress_bar->advance(); |
||
| 194 | } |
||
| 195 | |||
| 196 | $progress_bar->setMessage('Processing Class Relations ...'); |
||
| 197 | $progress_bar->display(); |
||
| 198 | |||
| 199 | $this->processClassRawRelations(); |
||
| 200 | |||
| 201 | $progress_bar->advance(); |
||
| 202 | |||
| 203 | $progress_bar->finish(); |
||
| 204 | $progress_bar->clear(); |
||
| 205 | |||
|
0 ignored issues
–
show
|
|||
| 206 | |||
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * Prints statistics about the code. |
||
| 211 | * |
||
| 212 | * @return array |
||
| 213 | */ |
||
| 214 | public function getStatistics() |
||
| 215 | { |
||
| 216 | $ret = array(); |
||
| 217 | |||
| 218 | $sql = 'SELECT COUNT(*) |
||
| 219 | FROM Files'; |
||
| 220 | $file_count = $this->db->fetchValue($sql); |
||
| 221 | |||
| 222 | $ret['Files'] = $file_count; |
||
| 223 | |||
| 224 | $sql = 'SELECT ClassType, COUNT(*) |
||
| 225 | FROM Classes |
||
| 226 | GROUP BY ClassType'; |
||
| 227 | $classes_count = $this->db->fetchPairs($sql); |
||
| 228 | |||
| 229 | foreach ( $classes_count as $class_type => $class_count ) { |
||
| 230 | $title = 'Unknowns'; |
||
| 231 | |||
| 232 | if ( $class_type === self::CLASS_TYPE_CLASS ) { |
||
| 233 | $title = 'Classes'; |
||
| 234 | } |
||
| 235 | elseif ( $class_type === self::CLASS_TYPE_INTERFACE ) { |
||
| 236 | $title = 'Interfaces'; |
||
| 237 | } |
||
| 238 | elseif ( $class_type === self::CLASS_TYPE_TRAIT ) { |
||
| 239 | $title = 'Traits'; |
||
| 240 | } |
||
| 241 | |||
| 242 | $ret[$title] = $class_count; |
||
| 243 | } |
||
| 244 | |||
| 245 | return $ret; |
||
| 246 | } |
||
| 247 | |||
| 248 | /** |
||
| 249 | * Processes file. |
||
| 250 | * |
||
| 251 | * @param string $file File. |
||
| 252 | * |
||
| 253 | * @return integer |
||
| 254 | */ |
||
| 255 | protected function processFile($file) |
||
| 256 | { |
||
| 257 | $size = filesize($file); |
||
| 258 | $relative_file = $this->removeProjectPath($file); |
||
| 259 | |||
| 260 | $sql = 'SELECT Id, Size |
||
| 261 | FROM Files |
||
| 262 | WHERE Name = :name'; |
||
| 263 | $file_data = $this->db->fetchOne($sql, array( |
||
| 264 | 'name' => $relative_file, |
||
| 265 | )); |
||
| 266 | |||
| 267 | $this->db->beginTransaction(); |
||
| 268 | |||
| 269 | if ( $file_data === false ) { |
||
| 270 | $sql = 'INSERT INTO Files (Name, Size) VALUES (:name, :size)'; |
||
| 271 | $this->db->perform($sql, array( |
||
| 272 | 'name' => $relative_file, |
||
| 273 | 'size' => $size, |
||
| 274 | )); |
||
| 275 | |||
| 276 | $file_id = $this->db->lastInsertId(); |
||
| 277 | } |
||
| 278 | else { |
||
| 279 | $file_id = $file_data['Id']; |
||
| 280 | } |
||
| 281 | |||
| 282 | // File is not changed since last time it was indexed. |
||
| 283 | if ( $file_data !== false && (int)$file_data['Size'] === $size ) { |
||
| 284 | $sql = 'UPDATE Files |
||
| 285 | SET Found = 1 |
||
| 286 | WHERE Id = :file_id'; |
||
| 287 | $this->db->perform($sql, array( |
||
| 288 | 'file_id' => $file_data['Id'], |
||
| 289 | )); |
||
| 290 | |||
| 291 | $this->db->commit(); |
||
| 292 | |||
| 293 | return $file_data['Id']; |
||
| 294 | } |
||
| 295 | |||
| 296 | $sql = 'UPDATE Files |
||
| 297 | SET Found = 1 |
||
| 298 | WHERE Id = :file_id'; |
||
| 299 | $this->db->perform($sql, array( |
||
| 300 | 'file_id' => $file_data['Id'], |
||
| 301 | )); |
||
| 302 | |||
| 303 | $new_classes = array(); |
||
| 304 | $parsed_file = new ReflectionFile($file); |
||
| 305 | |||
| 306 | foreach ( $parsed_file->getFileNamespaces() as $namespace ) { |
||
| 307 | foreach ( $namespace->getClasses() as $class ) { |
||
| 308 | $new_classes[] = $class->getName(); |
||
| 309 | $this->processClass($file_id, $class); |
||
| 310 | } |
||
| 311 | } |
||
| 312 | |||
| 313 | if ( $new_classes ) { |
||
| 314 | $sql = 'SELECT Id |
||
| 315 | FROM Classes |
||
| 316 | WHERE FileId = :file_id AND Name NOT IN (:classes)'; |
||
| 317 | $deleted_classes = $this->db->fetchCol($sql, array( |
||
| 318 | 'file_id' => $file_id, |
||
| 319 | 'classes' => $new_classes, |
||
| 320 | )); |
||
| 321 | } |
||
| 322 | else { |
||
| 323 | $sql = 'SELECT Id |
||
| 324 | FROM Classes |
||
| 325 | WHERE FileId = :file_id'; |
||
| 326 | $deleted_classes = $this->db->fetchCol($sql, array( |
||
| 327 | 'file_id' => $file_id, |
||
| 328 | )); |
||
| 329 | } |
||
| 330 | |||
| 331 | foreach ( $deleted_classes as $deleted_class_id ) { |
||
| 332 | $this->deleteClass($deleted_class_id); |
||
| 333 | } |
||
| 334 | |||
| 335 | $this->db->commit(); |
||
| 336 | |||
| 337 | ReflectionEngine::unsetFile($file); |
||
| 338 | |||
| 339 | return $file_id; |
||
| 340 | } |
||
| 341 | |||
| 342 | /** |
||
| 343 | * Processes class. |
||
| 344 | * |
||
| 345 | * @param integer $file_id File ID. |
||
| 346 | * @param \ReflectionClass $class Class. |
||
| 347 | * |
||
| 348 | * @return void |
||
| 349 | */ |
||
| 350 | protected function processClass($file_id, \ReflectionClass $class) |
||
| 351 | { |
||
| 352 | $sql = 'SELECT Id |
||
| 353 | FROM Classes |
||
| 354 | WHERE FileId = :file_id AND Name = :name'; |
||
| 355 | $class_id = $this->db->fetchValue($sql, array( |
||
| 356 | 'file_id' => $file_id, |
||
| 357 | 'name' => $class->getName(), |
||
| 358 | )); |
||
| 359 | |||
| 360 | $raw_class_relations = $this->getRawClassRelations($class); |
||
| 361 | |||
| 362 | if ( $class_id === false ) { |
||
| 363 | $sql = 'INSERT INTO Classes (Name, ClassType, IsAbstract, IsFinal, FileId, RawRelations) |
||
| 364 | VALUES (:name, :class_type, :is_abstract, :is_final, :file_id, :raw_relations)'; |
||
| 365 | |||
| 366 | $this->db->perform( |
||
| 367 | $sql, |
||
| 368 | array( |
||
| 369 | 'name' => $class->getName(), |
||
| 370 | 'class_type' => $this->getClassType($class), |
||
| 371 | 'is_abstract' => (int)$class->isAbstract(), |
||
| 372 | 'is_final' => (int)$class->isFinal(), |
||
| 373 | 'file_id' => $file_id, |
||
| 374 | 'raw_relations' => $raw_class_relations ? json_encode($raw_class_relations) : null, |
||
| 375 | ) |
||
| 376 | ); |
||
| 377 | |||
| 378 | $class_id = $this->db->lastInsertId(); |
||
| 379 | } |
||
| 380 | else { |
||
| 381 | $sql = 'UPDATE Classes |
||
| 382 | SET ClassType = :class_type, IsAbstract = :is_abstract, IsFinal = :is_final, RawRelations = :raw_relations |
||
| 383 | WHERE Id = :class_id'; |
||
| 384 | |||
| 385 | $this->db->perform( |
||
| 386 | $sql, |
||
| 387 | array( |
||
| 388 | 'class_type' => $this->getClassType($class), |
||
| 389 | 'is_abstract' => (int)$class->isAbstract(), |
||
| 390 | 'is_final' => (int)$class->isFinal(), |
||
| 391 | 'raw_relations' => $raw_class_relations ? json_encode($raw_class_relations) : null, |
||
| 392 | 'class_id' => $class_id, |
||
| 393 | ) |
||
| 394 | ); |
||
| 395 | } |
||
| 396 | |||
| 397 | $this->processClassConstants($class_id, $class); |
||
| 398 | $this->processClassProperties($class_id, $class); |
||
| 399 | $this->processClassMethods($class_id, $class); |
||
| 400 | } |
||
| 401 | |||
| 402 | /** |
||
| 403 | * Returns class type. |
||
| 404 | * |
||
| 405 | * @param \ReflectionClass $class Class. |
||
| 406 | * |
||
| 407 | * @return integer |
||
| 408 | */ |
||
| 409 | protected function getClassType(\ReflectionClass $class) |
||
| 410 | { |
||
| 411 | if ( $class->isInterface() ) { |
||
| 412 | return self::CLASS_TYPE_INTERFACE; |
||
| 413 | } |
||
| 414 | |||
| 415 | if ( $class->isTrait() ) { |
||
| 416 | return self::CLASS_TYPE_TRAIT; |
||
| 417 | } |
||
| 418 | |||
| 419 | return self::CLASS_TYPE_CLASS; |
||
| 420 | } |
||
| 421 | |||
| 422 | /** |
||
| 423 | * Get relations. |
||
| 424 | * |
||
| 425 | * @param \ReflectionClass $class Class. |
||
| 426 | * |
||
| 427 | * @return array |
||
| 428 | */ |
||
| 429 | protected function getRawClassRelations(\ReflectionClass $class) |
||
| 430 | { |
||
| 431 | $raw_relations = array(); |
||
| 432 | $parent_class = $class->getParentClass(); |
||
| 433 | |||
| 434 | if ( $parent_class ) { |
||
| 435 | $raw_relations[] = array( |
||
| 436 | $parent_class->getName(), |
||
| 437 | self::RELATION_TYPE_EXTENDS, |
||
| 438 | $parent_class->isInternal(), |
||
| 439 | ); |
||
| 440 | } |
||
| 441 | |||
| 442 | foreach ( $class->getInterfaces() as $interface ) { |
||
| 443 | $raw_relations[] = array( |
||
| 444 | $interface->getName(), |
||
| 445 | self::RELATION_TYPE_IMPLEMENTS, |
||
| 446 | $interface->isInternal(), |
||
| 447 | ); |
||
| 448 | } |
||
| 449 | |||
| 450 | return $raw_relations; |
||
| 451 | } |
||
| 452 | |||
| 453 | /** |
||
| 454 | * Deletes a class. |
||
| 455 | * |
||
| 456 | * @param integer $class_id Class ID. |
||
| 457 | * |
||
| 458 | * @return void |
||
| 459 | */ |
||
| 460 | protected function deleteClass($class_id) |
||
| 461 | { |
||
| 462 | $sql = 'DELETE FROM ClassConstants WHERE ClassId = :class_id'; |
||
| 463 | $this->db->perform($sql, array('class_id' => $class_id)); |
||
| 464 | |||
| 465 | $sql = 'DELETE FROM ClassProperties WHERE ClassId = :class_id'; |
||
| 466 | $this->db->perform($sql, array('class_id' => $class_id)); |
||
| 467 | |||
| 468 | $this->deleteClassMethods($class_id, array()); |
||
| 469 | |||
| 470 | $sql = 'DELETE FROM ClassRelations WHERE ClassId = :class_id'; |
||
| 471 | $this->db->perform($sql, array('class_id' => $class_id)); |
||
| 472 | |||
| 473 | $sql = 'DELETE FROM ClassRelations WHERE RelatedClassId = :class_id'; |
||
| 474 | $this->db->perform($sql, array('class_id' => $class_id)); |
||
| 475 | } |
||
| 476 | |||
| 477 | /** |
||
| 478 | * Processes constants. |
||
| 479 | * |
||
| 480 | * @param integer $class_id Class ID. |
||
| 481 | * @param \ReflectionClass $class Class. |
||
| 482 | * |
||
| 483 | * @return void |
||
| 484 | */ |
||
| 485 | protected function processClassConstants($class_id, \ReflectionClass $class) |
||
| 486 | { |
||
| 487 | $constants = $class->getConstants(); |
||
| 488 | |||
| 489 | $sql = 'SELECT Name |
||
| 490 | FROM ClassConstants |
||
| 491 | WHERE ClassId = :class_id'; |
||
| 492 | $old_constants = $this->db->fetchCol($sql, array( |
||
| 493 | 'class_id' => $class_id, |
||
| 494 | )); |
||
| 495 | |||
| 496 | $insert_sql = 'INSERT INTO ClassConstants (ClassId, Name, Value) VALUES (:class_id, :name, :value)'; |
||
| 497 | $update_sql = 'UPDATE ClassConstants SET Value = :value WHERE ClassId = :class_id AND Name = :name'; |
||
| 498 | |||
| 499 | foreach ( $constants as $constant_name => $constant_value ) { |
||
| 500 | $this->db->perform( |
||
| 501 | in_array($constant_name, $old_constants) ? $update_sql : $insert_sql, |
||
| 502 | array( |
||
| 503 | 'class_id' => $class_id, |
||
| 504 | 'name' => $constant_name, |
||
| 505 | 'value' => json_encode($constant_value), |
||
| 506 | ) |
||
| 507 | ); |
||
| 508 | } |
||
| 509 | |||
| 510 | $deleted_constants = array_diff($old_constants, array_keys($constants)); |
||
| 511 | |||
| 512 | if ( $deleted_constants ) { |
||
| 513 | $sql = 'DELETE FROM ClassConstants |
||
| 514 | WHERE ClassId = :class_id AND Name IN (:names)'; |
||
| 515 | $this->db->perform($sql, array( |
||
| 516 | 'class_id' => $class_id, |
||
| 517 | 'names' => $deleted_constants, |
||
| 518 | )); |
||
| 519 | } |
||
| 520 | } |
||
| 521 | |||
| 522 | /** |
||
| 523 | * Processes properties. |
||
| 524 | * |
||
| 525 | * @param integer $class_id Class ID. |
||
| 526 | * @param \ReflectionClass $class Class. |
||
| 527 | * |
||
| 528 | * @return void |
||
| 529 | */ |
||
| 530 | protected function processClassProperties($class_id, \ReflectionClass $class) |
||
| 531 | { |
||
| 532 | $sql = 'SELECT Name |
||
| 533 | FROM ClassProperties |
||
| 534 | WHERE ClassId = :class_id'; |
||
| 535 | $old_properties = $this->db->fetchCol($sql, array( |
||
| 536 | 'class_id' => $class_id, |
||
| 537 | )); |
||
| 538 | |||
| 539 | $insert_sql = ' INSERT INTO ClassProperties (ClassId, Name, Value, Scope, IsStatic) |
||
| 540 | VALUES (:class_id, :name, :value, :scope, :is_static)'; |
||
| 541 | $update_sql = ' UPDATE ClassProperties |
||
| 542 | SET Value = :value, Scope = :scope, IsStatic = :is_static |
||
| 543 | WHERE ClassId = :class_id AND Name = :name'; |
||
| 544 | |||
| 545 | $new_properties = array(); |
||
| 546 | $property_defaults = $class->getDefaultProperties(); |
||
| 547 | $static_properties = $class->getStaticProperties(); |
||
| 548 | $class_name = $class->getName(); |
||
| 549 | |||
| 550 | foreach ( $class->getProperties() as $property ) { |
||
| 551 | if ( $property->class !== $class_name ) { |
||
| 552 | continue; |
||
| 553 | } |
||
| 554 | |||
| 555 | $property_name = $property->getName(); |
||
| 556 | $property_value = isset($property_defaults[$property_name]) ? $property_defaults[$property_name] : null; |
||
| 557 | $new_properties[] = $property_name; |
||
| 558 | |||
| 559 | $this->db->perform( |
||
| 560 | in_array($property_name, $old_properties) ? $update_sql : $insert_sql, |
||
| 561 | array( |
||
| 562 | 'class_id' => $class_id, |
||
| 563 | 'name' => $property_name, |
||
| 564 | 'value' => json_encode($property_value), |
||
| 565 | 'scope' => $this->getPropertyScope($property), |
||
| 566 | 'is_static' => (int)in_array($property_name, $static_properties), |
||
| 567 | ) |
||
| 568 | ); |
||
| 569 | } |
||
| 570 | |||
| 571 | $deleted_properties = array_diff($old_properties, $new_properties); |
||
| 572 | |||
| 573 | if ( $deleted_properties ) { |
||
| 574 | $sql = 'DELETE FROM ClassProperties |
||
| 575 | WHERE ClassId = :class_id AND Name IN (:names)'; |
||
| 576 | $this->db->perform($sql, array( |
||
| 577 | 'class_id' => $class_id, |
||
| 578 | 'names' => $deleted_properties, |
||
| 579 | )); |
||
| 580 | } |
||
| 581 | } |
||
| 582 | |||
| 583 | /** |
||
| 584 | * Returns property scope. |
||
| 585 | * |
||
| 586 | * @param \ReflectionProperty $property Property. |
||
| 587 | * |
||
| 588 | * @return integer |
||
| 589 | */ |
||
| 590 | protected function getPropertyScope(\ReflectionProperty $property) |
||
| 591 | { |
||
| 592 | if ( $property->isPrivate() ) { |
||
| 593 | return self::SCOPE_PRIVATE; |
||
| 594 | } |
||
| 595 | |||
| 596 | if ( $property->isProtected() ) { |
||
| 597 | return self::SCOPE_PROTECTED; |
||
| 598 | } |
||
| 599 | |||
| 600 | return self::SCOPE_PUBLIC; |
||
| 601 | } |
||
| 602 | |||
| 603 | /** |
||
| 604 | * Processes methods. |
||
| 605 | * |
||
| 606 | * @param integer $class_id Class ID. |
||
| 607 | * @param \ReflectionClass $class Class. |
||
| 608 | * |
||
| 609 | * @return void |
||
| 610 | */ |
||
| 611 | protected function processClassMethods($class_id, \ReflectionClass $class) |
||
| 612 | { |
||
| 613 | $sql = 'SELECT Name, Id |
||
| 614 | FROM ClassMethods |
||
| 615 | WHERE ClassId = :class_id'; |
||
| 616 | $old_methods = $this->db->fetchPairs($sql, array( |
||
| 617 | 'class_id' => $class_id, |
||
| 618 | )); |
||
| 619 | |||
| 620 | $insert_sql = ' INSERT INTO ClassMethods (ClassId, Name, ParameterCount, RequiredParameterCount, Scope, IsAbstract, IsFinal, IsStatic, ReturnsReference, HasReturnType, ReturnType) |
||
|
0 ignored issues
–
show
|
|||
| 621 | VALUES (:class_id, :name, :parameter_count, :required_parameter_count, :scope, :is_abstract, :is_final, :is_static, :returns_reference, :has_return_type, :return_type)'; |
||
|
0 ignored issues
–
show
|
|||
| 622 | $update_sql = ' UPDATE ClassMethods |
||
| 623 | SET ParameterCount = :parameter_count, RequiredParameterCount = :required_parameter_count, Scope = :scope, IsAbstract = :is_abstract, IsFinal = :is_final, IsStatic = :is_static, ReturnsReference = :returns_reference, ReturnType = :return_type, HasReturnType = :has_return_type |
||
|
0 ignored issues
–
show
|
|||
| 624 | WHERE ClassId = :class_id AND Name = :name'; |
||
| 625 | |||
| 626 | $new_methods = array(); |
||
| 627 | $class_name = $class->getName(); |
||
| 628 | |||
| 629 | foreach ( $class->getMethods() as $method ) { |
||
| 630 | if ( $method->class !== $class_name ) { |
||
| 631 | continue; |
||
| 632 | } |
||
| 633 | |||
| 634 | $method_name = $method->getName(); |
||
| 635 | $new_methods[] = $method_name; |
||
| 636 | |||
| 637 | // Doesn't work for parent classes (see https://github.com/goaop/parser-reflection/issues/16). |
||
| 638 | $has_return_type = $method->hasReturnType(); |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
ReflectionMethod as the method hasReturnType() does only exist in the following sub-classes of ReflectionMethod: Go\ParserReflection\ReflectionMethod. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 639 | $return_type = $has_return_type ? (string)$method->getReturnType() : null; |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
ReflectionMethod as the method getReturnType() does only exist in the following sub-classes of ReflectionMethod: Go\ParserReflection\ReflectionMethod. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 640 | |||
| 641 | $this->db->perform( |
||
| 642 | isset($old_methods[$method_name]) ? $update_sql : $insert_sql, |
||
| 643 | array( |
||
| 644 | 'class_id' => $class_id, |
||
| 645 | 'name' => $method_name, |
||
| 646 | 'parameter_count' => $method->getNumberOfParameters(), |
||
| 647 | 'required_parameter_count' => $method->getNumberOfRequiredParameters(), |
||
| 648 | 'scope' => $this->getMethodScope($method), |
||
| 649 | 'is_abstract' => (int)$method->isAbstract(), |
||
| 650 | 'is_final' => (int)$method->isFinal(), |
||
| 651 | 'is_static' => (int)$method->isStatic(), |
||
| 652 | 'returns_reference' => (int)$method->returnsReference(), |
||
| 653 | 'has_return_type' => (int)$has_return_type, |
||
| 654 | 'return_type' => $return_type, |
||
| 655 | ) |
||
| 656 | ); |
||
| 657 | |||
| 658 | $method_id = isset($old_methods[$method_name]) ? $old_methods[$method_name] : $this->db->lastInsertId(); |
||
| 659 | $this->processClassMethodParameters($method_id, $method); |
||
| 660 | } |
||
| 661 | |||
| 662 | $deleted_methods = array_diff($old_methods, $new_methods); |
||
| 663 | |||
| 664 | if ( $deleted_methods ) { |
||
| 665 | $this->deleteClassMethods($class_id, $deleted_methods); |
||
| 666 | } |
||
| 667 | } |
||
| 668 | |||
| 669 | /** |
||
| 670 | * Deletes methods. |
||
| 671 | * |
||
| 672 | * @param integer $class_id Class ID. |
||
| 673 | * @param array $methods Methods. |
||
| 674 | * |
||
| 675 | * @return void |
||
| 676 | */ |
||
| 677 | protected function deleteClassMethods($class_id, array $methods) |
||
| 678 | { |
||
| 679 | if ( $methods ) { |
||
| 680 | $sql = 'SELECT Id |
||
| 681 | FROM ClassMethods |
||
| 682 | WHERE ClassId = :class_id AND Name IN (:names)'; |
||
| 683 | $method_ids = $this->db->fetchCol($sql, array( |
||
| 684 | 'class_id' => $class_id, |
||
| 685 | 'names' => $methods, |
||
| 686 | )); |
||
| 687 | } |
||
| 688 | else { |
||
| 689 | $sql = 'SELECT Id |
||
| 690 | FROM ClassMethods |
||
| 691 | WHERE ClassId = :class_id'; |
||
| 692 | $method_ids = $this->db->fetchCol($sql, array( |
||
| 693 | 'class_id' => $class_id, |
||
| 694 | )); |
||
| 695 | } |
||
| 696 | |||
| 697 | if ( !$method_ids ) { |
||
| 698 | return; |
||
| 699 | } |
||
| 700 | |||
| 701 | $sql = 'DELETE FROM ClassMethods WHERE Id IN (:method_ids)'; |
||
| 702 | $this->db->perform($sql, array('method_ids' => $method_ids)); |
||
| 703 | |||
| 704 | $sql = 'DELETE FROM MethodParameters WHERE MethodId IN (:method_ids)'; |
||
| 705 | $this->db->perform($sql, array('method_ids' => $method_ids)); |
||
| 706 | |||
| 707 | } |
||
| 708 | |||
| 709 | /** |
||
| 710 | * Processes method parameters. |
||
| 711 | * |
||
| 712 | * @param integer $method_id Method ID. |
||
| 713 | * @param \ReflectionMethod $method Method. |
||
| 714 | * |
||
| 715 | * @return void |
||
| 716 | */ |
||
| 717 | protected function processClassMethodParameters($method_id, \ReflectionMethod $method) |
||
| 718 | { |
||
| 719 | $sql = 'SELECT Name |
||
| 720 | FROM MethodParameters |
||
| 721 | WHERE MethodId = :method_id'; |
||
| 722 | $old_parameters = $this->db->fetchCol($sql, array( |
||
| 723 | 'method_id' => $method_id, |
||
| 724 | )); |
||
| 725 | |||
| 726 | $insert_sql = ' INSERT INTO MethodParameters (MethodId, Name, TypeClass, HasType, TypeName, AllowsNull, IsArray, IsCallable, IsOptional, IsVariadic, CanBePassedByValue, IsPassedByReference, HasDefaultValue, DefaultValue, DefaultConstant) |
||
|
0 ignored issues
–
show
|
|||
| 727 | VALUES (:method_id, :name, :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)'; |
||
|
0 ignored issues
–
show
|
|||
| 728 | $update_sql = ' UPDATE MethodParameters |
||
| 729 | SET TypeClass = :type_class, HasType = :has_type, TypeName = :type_name, AllowsNull = :allows_null, IsArray = :is_array, IsCallable = :is_callable, IsOptional = :is_optional, IsVariadic = :is_variadic, CanBePassedByValue = :can_be_passed_by_value, IsPassedByReference = :is_passed_by_reference, HasDefaultValue = :has_default_value, DefaultValue = :default_value, DefaultConstant = :default_constant |
||
|
0 ignored issues
–
show
|
|||
| 730 | WHERE MethodId = :method_id AND Name = :name'; |
||
| 731 | |||
| 732 | $new_parameters = array(); |
||
| 733 | |||
| 734 | foreach ( $method->getParameters() as $parameter ) { |
||
| 735 | $parameter_name = $parameter->getName(); |
||
| 736 | $new_parameters[] = $parameter_name; |
||
| 737 | |||
| 738 | $type_class = $parameter->getClass(); |
||
| 739 | $type_class = $type_class ? $type_class->getName() : null; |
||
| 740 | |||
| 741 | // Doesn't work for parent classes (see https://github.com/goaop/parser-reflection/issues/16). |
||
| 742 | $has_type = $parameter->hasType(); |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
ReflectionParameter as the method hasType() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 743 | $type_name = $has_type ? (string)$parameter->getType() : null; |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
ReflectionParameter as the method getType() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 744 | |||
| 745 | $has_default_value = $parameter->isDefaultValueAvailable(); |
||
| 746 | $default_value_is_constant = $has_default_value ? $parameter->isDefaultValueConstant() : false; |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
ReflectionParameter as the method isDefaultValueConstant() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 747 | |||
| 748 | $this->db->perform( |
||
| 749 | isset($old_parameters[$parameter_name]) ? $update_sql : $insert_sql, |
||
| 750 | array( |
||
| 751 | 'method_id' => $method_id, |
||
| 752 | 'name' => $parameter_name, |
||
| 753 | 'type_class' => $type_class, |
||
| 754 | 'has_type' => (int)$has_type, |
||
| 755 | 'type_name' => $type_name, |
||
| 756 | 'allows_null' => (int)$parameter->allowsNull(), |
||
| 757 | 'is_array' => (int)$parameter->isArray(), |
||
| 758 | 'is_callable' => (int)$parameter->isCallable(), |
||
| 759 | 'is_optional' => (int)$parameter->isOptional(), |
||
| 760 | 'is_variadic' => (int)$parameter->isVariadic(), |
||
|
0 ignored issues
–
show
It seems like you code against a specific sub-type and not the parent class
ReflectionParameter as the method isVariadic() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?
Let’s take a look at an example: abstract class User
{
/** @return string */
abstract public function getPassword();
}
class MyUser extends User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 761 | 'can_be_passed_by_value' => (int)$parameter->canBePassedByValue(), |
||
| 762 | 'is_passed_by_reference' => (int)$parameter->isPassedByReference(), |
||
| 763 | 'has_default_value' => (int)$has_default_value, |
||
| 764 | 'default_value' => $has_default_value ? json_encode($parameter->getDefaultValue()) : null, |
||
| 765 | 'default_constant' => $default_value_is_constant ? $parameter->getDefaultValueConstantName() : null, |
||
|
0 ignored issues
–
show
The method
getDefaultValueConstantName() does not exist on ReflectionParameter. Did you maybe mean getDefaultValue()?
This check marks calls to methods that do not seem to exist on an object. This is most likely the result of a method being renamed without all references to it being renamed likewise. Loading history...
|
|||
| 766 | ) |
||
| 767 | ); |
||
| 768 | } |
||
| 769 | |||
| 770 | $deleted_parameters = array_diff($old_parameters, $new_parameters); |
||
| 771 | |||
| 772 | if ( $deleted_parameters ) { |
||
| 773 | $sql = 'DELETE FROM MethodParameters |
||
| 774 | WHERE MethodId = :method_id AND Name IN (:names)'; |
||
| 775 | $this->db->perform($sql, array( |
||
| 776 | 'method_id' => $method_id, |
||
| 777 | 'names' => $deleted_parameters, |
||
| 778 | )); |
||
| 779 | } |
||
| 780 | } |
||
| 781 | |||
| 782 | /** |
||
| 783 | * Returns method scope. |
||
| 784 | * |
||
| 785 | * @param \ReflectionMethod $method Method. |
||
| 786 | * |
||
| 787 | * @return integer |
||
| 788 | */ |
||
| 789 | protected function getMethodScope(\ReflectionMethod $method) |
||
| 790 | { |
||
| 791 | if ( $method->isPrivate() ) { |
||
| 792 | return self::SCOPE_PRIVATE; |
||
| 793 | } |
||
| 794 | |||
| 795 | if ( $method->isProtected() ) { |
||
| 796 | return self::SCOPE_PROTECTED; |
||
| 797 | } |
||
| 798 | |||
| 799 | return self::SCOPE_PUBLIC; |
||
| 800 | } |
||
| 801 | |||
| 802 | /** |
||
| 803 | * Processes raw relations for all classes. |
||
| 804 | * |
||
| 805 | * @return void |
||
| 806 | */ |
||
| 807 | protected function processClassRawRelations() |
||
| 808 | { |
||
| 809 | $sql = 'SELECT Id, RawRelations |
||
| 810 | FROM Classes |
||
| 811 | WHERE RawRelations IS NOT NULL'; |
||
| 812 | $raw_relations = $this->db->yieldPairs($sql, array( |
||
| 813 | 'empty_relations' => json_encode(array()), |
||
| 814 | )); |
||
| 815 | |||
| 816 | foreach ( $raw_relations as $class_id => $class_raw_relations ) { |
||
| 817 | $sql = 'SELECT RelatedClass |
||
| 818 | FROM ClassRelations |
||
| 819 | WHERE ClassId = :class_id'; |
||
| 820 | $old_class_relations = $this->db->fetchCol($sql, array( |
||
| 821 | 'class_id' => $class_id, |
||
| 822 | )); |
||
| 823 | |||
| 824 | $new_class_relations = array(); |
||
| 825 | |||
| 826 | foreach ( json_decode($class_raw_relations, true) as $class_raw_relation ) { |
||
| 827 | list ($related_class, $relation_type, $is_internal) = $class_raw_relation; |
||
| 828 | |||
| 829 | $new_class_relations[] = $this->addRelation( |
||
| 830 | $class_id, |
||
| 831 | $related_class, |
||
| 832 | $relation_type, |
||
| 833 | $is_internal, |
||
| 834 | $old_class_relations |
||
| 835 | ); |
||
| 836 | } |
||
| 837 | |||
| 838 | $delete_class_relations = array_diff($old_class_relations, $new_class_relations); |
||
| 839 | |||
| 840 | if ( $delete_class_relations ) { |
||
| 841 | $sql = 'DELETE FROM ClassRelations |
||
| 842 | WHERE ClassId = :class_id AND RelatedClassId IN (:related_class_ids)'; |
||
| 843 | $this->db->perform($sql, array( |
||
| 844 | 'class_id' => $class_id, |
||
| 845 | 'related_class_ids' => $delete_class_relations, |
||
| 846 | )); |
||
| 847 | } |
||
| 848 | } |
||
| 849 | |||
| 850 | $sql = 'UPDATE Classes |
||
| 851 | SET RawRelations = NULL'; |
||
| 852 | $this->db->perform($sql); |
||
| 853 | } |
||
| 854 | |||
| 855 | /** |
||
| 856 | * Adds a relation. |
||
| 857 | * |
||
| 858 | * @param integer $class_id Class ID. |
||
| 859 | * @param string $related_class Related class. |
||
| 860 | * @param integer $relation_type Relation type. |
||
| 861 | * @param boolean $is_internal Is internal. |
||
| 862 | * @param array $old_relations Old relations. |
||
| 863 | * |
||
| 864 | * @return string |
||
| 865 | */ |
||
| 866 | protected function addRelation($class_id, $related_class, $relation_type, $is_internal, array $old_relations) |
||
| 867 | { |
||
| 868 | $insert_sql = ' INSERT INTO ClassRelations (ClassId, RelatedClass, RelatedClassId, RelationType) |
||
| 869 | VALUES (:class_id, :related_class, :related_class_id, :relation_type)'; |
||
| 870 | $update_sql = ' UPDATE ClassRelations |
||
| 871 | SET RelationType = :relation_type |
||
| 872 | WHERE ClassId = :class_id AND RelatedClassId = :related_class_id'; |
||
| 873 | |||
| 874 | if ( $is_internal ) { |
||
| 875 | $related_class_id = 0; |
||
| 876 | } |
||
| 877 | else { |
||
| 878 | $related_class_file = realpath(ReflectionEngine::locateClassFile($related_class)); |
||
| 879 | |||
| 880 | $sql = 'SELECT Id |
||
| 881 | FROM Classes |
||
| 882 | WHERE FileId = :file_id AND Name = :name'; |
||
| 883 | $related_class_id = $this->db->fetchValue($sql, array( |
||
| 884 | 'file_id' => $this->processFile($related_class_file), |
||
| 885 | 'name' => $related_class, |
||
| 886 | )); |
||
| 887 | } |
||
| 888 | |||
| 889 | $this->db->perform( |
||
| 890 | in_array($related_class, $old_relations) ? $update_sql : $insert_sql, |
||
| 891 | array( |
||
| 892 | 'class_id' => $class_id, |
||
| 893 | 'related_class' => $related_class, |
||
| 894 | 'related_class_id' => $related_class_id, |
||
| 895 | 'relation_type' => $relation_type, |
||
| 896 | ) |
||
| 897 | ); |
||
| 898 | |||
| 899 | return $related_class; |
||
| 900 | } |
||
| 901 | |||
| 902 | /** |
||
| 903 | * Determines class locator. |
||
| 904 | * |
||
| 905 | * @return LocatorInterface |
||
| 906 | * @throws \LogicException When file in "class_locator" setting doesn't exist. |
||
| 907 | */ |
||
| 908 | protected function detectClassLocator() |
||
| 909 | { |
||
| 910 | $class_locator = null; |
||
| 911 | |||
| 912 | if ( isset($this->config['class_locator']) ) { |
||
| 913 | $class_locator_file = $this->resolveProjectPath($this->config['class_locator']); |
||
| 914 | |||
| 915 | if ( !file_exists($class_locator_file) || !is_file($class_locator_file) ) { |
||
| 916 | throw new \LogicException( |
||
| 917 | 'The "' . $this->config['class_locator'] . '" class locator doesn\'t exist.' |
||
| 918 | ); |
||
| 919 | } |
||
| 920 | |||
| 921 | $class_locator = require $class_locator_file; |
||
| 922 | } |
||
| 923 | else { |
||
| 924 | $class_locator_file = $this->resolveProjectPath('vendor/autoload.php'); |
||
| 925 | |||
| 926 | if ( file_exists($class_locator_file) && is_file($class_locator_file) ) { |
||
| 927 | $class_locator = require $class_locator_file; |
||
| 928 | } |
||
| 929 | } |
||
| 930 | |||
| 931 | // Make sure memory limit isn't changed by class locator. |
||
| 932 | ini_restore('memory_limit'); |
||
| 933 | |||
| 934 | if ( is_callable($class_locator) ) { |
||
| 935 | return new CallableLocator($class_locator); |
||
| 936 | } |
||
| 937 | elseif ( $class_locator instanceof ClassLoader ) { |
||
| 938 | return new ComposerLocator($class_locator); |
||
| 939 | } |
||
| 940 | |||
| 941 | throw new \LogicException( |
||
| 942 | 'The "class_loader" setting must point to "vendor/autoload.php" or a file, that would return closure.' |
||
| 943 | ); |
||
| 944 | } |
||
| 945 | |||
| 946 | /** |
||
| 947 | * Processes the Finders configuration list. |
||
| 948 | * |
||
| 949 | * @return Finder[] |
||
| 950 | * @throws \LogicException If "finder" setting doesn't exist. |
||
| 951 | * @throws \LogicException If the configured method does not exist. |
||
| 952 | */ |
||
| 953 | protected function getFinders() |
||
| 954 | { |
||
| 955 | // Process "finder" config setting. |
||
| 956 | if ( !isset($this->config['finder']) ) { |
||
| 957 | throw new \LogicException('The "finder" setting must be present in config file.'); |
||
| 958 | } |
||
| 959 | |||
| 960 | $finders = array(); |
||
| 961 | |||
| 962 | foreach ( $this->config['finder'] as $methods ) { |
||
| 963 | $finder = Finder::create()->files(); |
||
| 964 | |||
| 965 | if ( isset($methods['in']) ) { |
||
| 966 | $methods['in'] = (array)$methods['in']; |
||
| 967 | |||
| 968 | foreach ( $methods['in'] as $folder_index => $in_folder ) { |
||
| 969 | $methods['in'][$folder_index] = $this->resolveProjectPath($in_folder); |
||
| 970 | } |
||
| 971 | } |
||
| 972 | |||
| 973 | foreach ( $methods as $method => $arguments ) { |
||
| 974 | if ( !method_exists($finder, $method) ) { |
||
| 975 | throw new \LogicException(sprintf( |
||
| 976 | 'The method "Finder::%s" does not exist.', |
||
| 977 | $method |
||
| 978 | )); |
||
| 979 | } |
||
| 980 | |||
| 981 | $arguments = (array)$arguments; |
||
| 982 | |||
| 983 | foreach ( $arguments as $argument ) { |
||
| 984 | $finder->$method($argument); |
||
| 985 | } |
||
| 986 | } |
||
| 987 | |||
| 988 | $finders[] = $finder; |
||
| 989 | } |
||
| 990 | |||
| 991 | return $finders; |
||
| 992 | } |
||
| 993 | |||
| 994 | /** |
||
| 995 | * Resolves path within project. |
||
| 996 | * |
||
| 997 | * @param string $relative_path Relative path. |
||
| 998 | * |
||
| 999 | * @return string |
||
| 1000 | */ |
||
| 1001 | protected function resolveProjectPath($relative_path) |
||
| 1002 | { |
||
| 1003 | return realpath($this->projectPath . DIRECTORY_SEPARATOR . $relative_path); |
||
| 1004 | } |
||
| 1005 | |||
| 1006 | /** |
||
| 1007 | * Removes project path from file path. |
||
| 1008 | * |
||
| 1009 | * @param string $absolute_path Absolute path. |
||
| 1010 | * |
||
| 1011 | * @return string |
||
| 1012 | */ |
||
| 1013 | protected function removeProjectPath($absolute_path) |
||
| 1014 | { |
||
| 1015 | return preg_replace($this->projectPathRegExp, '', $absolute_path, 1); |
||
| 1016 | } |
||
| 1017 | |||
| 1018 | } |
||
| 1019 |