*/ /** * Used to determine which files reference other files, necessary for using the --diff * option from the command line. */ class FileReferenceProvider { /** * @var bool */ private $loaded_from_cache = false; /** * A lookup table used for getting all the files that reference a class * * @var array> */ private static $file_references_to_class = []; /** * A lookup table used for getting all the files that reference any other file * * @var array> */ private static $referencing_files = []; /** * @var array> */ private static $files_inheriting_classes = []; /** * A list of all files deleted since the last successful run * * @var array|null */ private static $deleted_files = null; /** * A lookup table used for getting all the files referenced by a file * * @var array, i:array}> */ private static $file_references = []; /** * @var array> */ private static $class_method_references = []; /** * @var array> */ private static $analyzed_methods = []; /** * @var array> */ private static $issues = []; /** * @var array, 1: array}> */ private static $file_maps = []; /** * @var ?FileReferenceCacheProvider */ public $cache; public function __construct(FileReferenceCacheProvider $cache = null) { $this->cache = $cache; } /** * @return array */ public function getDeletedReferencedFiles() { if (self::$deleted_files === null) { self::$deleted_files = array_filter( array_keys(self::$file_references), /** * @param string $file_name * * @return bool */ function ($file_name) { return !file_exists($file_name); } ); } return self::$deleted_files; } /** * @param string $source_file * @param string $fq_class_name_lc * * @return void */ public function addFileReferenceToClass($source_file, $fq_class_name_lc) { self::$referencing_files[$source_file] = true; self::$file_references_to_class[$fq_class_name_lc][$source_file] = true; } /** * @return array> */ public function getAllFileReferences() { return self::$file_references_to_class; } /** * @param array> $references * @psalm-suppress MixedTypeCoercion * * @return void */ public function addFileReferences(array $references) { self::$file_references_to_class = array_merge_recursive($references, self::$file_references_to_class); } /** * @param string $source_file * @param string $fq_class_name_lc * * @return void */ public function addFileInheritanceToClass($source_file, $fq_class_name_lc) { self::$files_inheriting_classes[$fq_class_name_lc][$source_file] = true; } /** * @param string $file * * @return array */ private function calculateFilesReferencingFile(Codebase $codebase, $file) { $referenced_files = []; $file_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $file); foreach ($file_classes as $file_class_lc => $_) { if (isset(self::$file_references_to_class[$file_class_lc])) { $referenced_files = array_merge( $referenced_files, array_keys(self::$file_references_to_class[$file_class_lc]) ); } } return array_unique($referenced_files); } /** * @param string $file * * @return array */ private function calculateFilesInheritingFile(Codebase $codebase, $file) { $referenced_files = []; $file_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $file); foreach ($file_classes as $file_class_lc => $_) { if (isset(self::$files_inheriting_classes[$file_class_lc])) { $referenced_files = array_merge( $referenced_files, array_keys(self::$files_inheriting_classes[$file_class_lc]) ); } } return array_unique($referenced_files); } /** * @return void */ public function removeDeletedFilesFromReferences() { $deleted_files = self::getDeletedReferencedFiles(); if ($deleted_files) { foreach ($deleted_files as $file) { unset(self::$file_references[$file]); } if ($this->cache) { $this->cache->setCachedFileReferences(self::$file_references); } } } /** * @param string $file * * @return array */ public function getFilesReferencingFile($file) { return isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : []; } /** * @param string $file * * @return array */ public function getFilesInheritingFromFile($file) { return isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : []; } /** * @return array> */ public function getMethodsReferencing() { return self::$class_method_references; } /** * @param bool $force_reload * @return bool * @psalm-suppress MixedAssignment * @psalm-suppress MixedTypeCoercion */ public function loadReferenceCache($force_reload = true) { if ($this->cache && (!$this->loaded_from_cache || $force_reload)) { $this->loaded_from_cache = true; $file_references = $this->cache->getCachedFileReferences(); if ($file_references === null) { return false; } self::$file_references = $file_references; $class_method_references = $this->cache->getCachedMethodReferences(); if ($class_method_references === null) { return false; } self::$class_method_references = $class_method_references; $analyzed_methods = $this->cache->getAnalyzedMethodCache(); if ($analyzed_methods === false) { return false; } self::$analyzed_methods = $analyzed_methods; $issues = $this->cache->getCachedIssues(); if ($issues === null) { return false; } self::$issues = $issues; self::$file_maps = $this->cache->getFileMapCache() ?: []; return true; } return false; } /** * @param array $visited_files * * @return void */ public function updateReferenceCache(Codebase $codebase, array $visited_files) { foreach ($visited_files as $file => $_) { $all_file_references = array_unique( array_merge( isset(self::$file_references[$file]['a']) ? self::$file_references[$file]['a'] : [], $this->calculateFilesReferencingFile($codebase, $file) ) ); $inheritance_references = array_unique( array_merge( isset(self::$file_references[$file]['i']) ? self::$file_references[$file]['i'] : [], $this->calculateFilesInheritingFile($codebase, $file) ) ); self::$file_references[$file] = [ 'a' => $all_file_references, 'i' => $inheritance_references, ]; } if ($this->cache) { $this->cache->setCachedFileReferences(self::$file_references); $this->cache->setCachedMethodReferences(self::$class_method_references); $this->cache->setCachedIssues(self::$issues); $this->cache->setFileMapCache(self::$file_maps); $this->cache->setAnalyzedMethodCache(self::$analyzed_methods); } } /** * @param string $calling_method_id * @param string $referenced_member_id * @return void */ public function addReferenceToClassMethod($calling_method_id, $referenced_member_id) { if (!isset(self::$class_method_references[$referenced_member_id])) { self::$class_method_references[$referenced_member_id] = [$calling_method_id => true]; } else { self::$class_method_references[$referenced_member_id][$calling_method_id] = true; } } /** * @return array> */ public function getClassMethodReferences() : array { return self::$class_method_references; } /** * @param array> $references * @psalm-suppress MixedTypeCoercion * * @return void */ public function addClassMethodReferences(array $references) { foreach ($references as $referenced_member_id => $calling_method_ids) { if (isset(self::$class_method_references[$referenced_member_id])) { self::$class_method_references[$referenced_member_id] = array_merge( self::$class_method_references[$referenced_member_id], $calling_method_ids ); } else { self::$class_method_references[$referenced_member_id] = $calling_method_ids; } } } /** * @return array> */ public function getExistingIssues() : array { return self::$issues; } /** * @param string $file_path * @return void */ public function clearExistingIssuesForFile($file_path) { unset(self::$issues[$file_path]); } /** * @param string $file_path * @param IssueData $issue * @return void */ public function clearExistingFileMapsForFile($file_path) { unset(self::$file_maps[$file_path]); } /** * @param string $file_path * @return void */ public function addIssue($file_path, array $issue) { // don’t save parse errors ever, as they're not responsive to AST diffing if ($issue['type'] === 'ParseError') { return; } if (!isset(self::$issues[$file_path])) { self::$issues[$file_path] = [$issue]; } else { self::$issues[$file_path][] = $issue; } } /** * @param array> $analyzed_methods * @return void */ public function setAnalyzedMethods(array $analyzed_methods) { self::$analyzed_methods = $analyzed_methods; } /** * @param array $file_maps * @return void */ public function setFileMaps(array $file_maps) { self::$file_maps = $file_maps; } /** * @return array> */ public function getAnalyzedMethods() { return self::$analyzed_methods; } /** * @return array */ public function getFileMaps() { return self::$file_maps; } /** * @return void */ public static function clearCache() { self::$file_references_to_class = []; self::$referencing_files = []; self::$files_inheriting_classes = []; self::$deleted_files = null; self::$file_references = []; self::$class_method_references = []; self::$analyzed_methods = []; self::$issues = []; self::$file_maps = []; } }