diff --git a/Scanner.php b/Scanner.php index 4f0d35a..81dab53 100644 --- a/Scanner.php +++ b/Scanner.php @@ -18,46 +18,34 @@ class Scanner extends Component #[Container(ContainerInterface::class)] public ContainerInterface $container; - /** - * @var array 已处理的文件路径列表 - */ public array $files = []; - /** - * @var array 文件修改时间缓存 - */ - private array $fileMtimes = []; + private array $fileMtimes = []; + private ?bool $hasOpcache = null; + private string $basePath; - /** - * @var bool|null OPcache可用性缓存 - */ - private ?bool $hasOpcache = null; - - /** - * @var array 配置选项 - */ private array $config = [ - 'skip_patterns' => ['/vendor/', '/tests/', '/cache/', '/node_modules/'], - 'skip_directories' => [], - 'extensions' => ['php'], - 'max_depth' => 20, - 'follow_links' => false, - 'cache_enabled' => true, - 'cache_ttl' => 3600, - 'debug' => false, + 'skip_patterns' => ['/vendor/', '/tests/', '/cache/', '/node_modules/'], + 'skip_directories' => [], + 'extensions' => ['php'], + 'max_depth' => 20, + 'follow_links' => false, + 'cache_enabled' => false, + 'cache_ttl' => 3600, + 'debug' => false, + 'class_name_strategy' => 'auto', // 'auto', 'extract', 'rename', or 'both' ]; - /** - * 设置扫描器配置 - */ + public function __construct() + { + $this->basePath = $_SERVER['PWD'] ?? APP_PATH ?? getcwd(); + } + public function setConfig(array $config): void { $this->config = array_merge($this->config, $config); } - /** - * 主扫描方法,支持缓存 - */ public function scan(string $path, ?string $cacheFile = null): void { if ($this->config['cache_enabled']) { @@ -75,9 +63,6 @@ class Scanner extends Component } } - /** - * 扫描目录(替换原有的load_directory) - */ private function scanDirectory(string $path, int $depth = 0): void { if ($depth > $this->config['max_depth']) { @@ -103,7 +88,6 @@ class Scanner extends Component continue; } - // 如果是符号链接且不跟随链接,则跳过 if ($item->isLink() && !$this->config['follow_links']) { continue; } @@ -115,22 +99,15 @@ class Scanner extends Component } } - /** - * 判断是否应该跳过该目录项 - */ private function shouldSkipItem(DirectoryIterator $item): bool { return $item->isDot() || str_starts_with($item->getFilename(), '.'); } - /** - * 判断是否应该跳过目录 - */ private function shouldSkipDirectory(string $path): bool { $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - // 检查配置文件中的跳过目录 $skipDirs = array_merge( $this->config['skip_directories'], config('site.scanner.skip', []) @@ -140,7 +117,6 @@ class Scanner extends Component return true; } - // 检查跳过模式 foreach ($this->config['skip_patterns'] as $pattern) { if (strpos($path, $pattern) !== false) { return true; @@ -150,22 +126,16 @@ class Scanner extends Component return false; } - /** - * 处理单个文件 - */ private function processFile(string $path): void { - // 检查文件扩展名 if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $this->config['extensions'])) { return; } - // 检查是否已处理 if (in_array($path, $this->files)) { return; } - // 检查文件是否需要重新处理(基于修改时间) if (!$this->shouldProcessFile($path)) { return; } @@ -180,16 +150,12 @@ class Scanner extends Component 'action' => 'process_file', ]); - // 如果是语法错误,可以选择抛出或记录 if ($e instanceof \ParseError) { throw $e; } } } - /** - * 判断文件是否需要处理 - */ private function shouldProcessFile(string $path): bool { if (!file_exists($path)) { @@ -198,7 +164,6 @@ class Scanner extends Component $mtime = filemtime($path); - // 如果没有缓存过修改时间,或者文件已更新,则需要处理 if (!isset($this->fileMtimes[$path]) || $this->fileMtimes[$path] < $mtime) { $this->fileMtimes[$path] = $mtime; return true; @@ -207,90 +172,92 @@ class Scanner extends Component return false; } - /** - * 加载并解析文件 - */ private function loadAndParseFile(string $path): void { - // OPcache优化 $this->optimizeWithOpcache($path); - // 加载文件 require_once $path; - // 尝试从文件内容提取类名(更准确) - $class = $this->extractClassNameFromFile($path); - + // 尝试获取类名 + $class = $this->getClassNameFromPath($path); if ($class && class_exists($class)) { $this->analyzeClass($class); } } /** - * 使用OPcache优化文件 + * 从文件路径获取类名(支持多种策略) */ - private function optimizeWithOpcache(string $path): void + private function getClassNameFromPath(string $path): ?string { - if ($this->hasOpcache === null) { - $this->hasOpcache = function_exists('opcache_invalidate') - && function_exists('opcache_compile_file'); - } + $strategy = $this->config['class_name_strategy']; - if ($this->hasOpcache) { - try { - opcache_invalidate($path, true); - opcache_compile_file($path); - } catch (Throwable $e) { - // 静默处理OPcache错误,不影响主要功能 - if ($this->config['debug']) { - $this->logError($e, [ - 'file' => $path, - 'action' => 'opcache_optimize', - ]); - } - } - } + // 移除基础路径前缀,类似原代码逻辑 + $relativePath = str_replace($this->basePath, '', $path); + $relativePath = str_replace('.php', '', $relativePath); + + return $this->renamePathToClassName($relativePath); } /** - * 从文件内容提取完整的类名(命名空间+类名) + * 检查是否可以安全地从文件内容提取类名 + */ + private function canExtractClassName(): bool + { + // 如果有tokenizer扩展,优先使用它 + return function_exists('token_get_all'); + } + + /** + * 从文件内容提取类名(原代码没有的功能) */ private function extractClassNameFromFile(string $path): ?string { + if (!$this->canExtractClassName()) { + return null; + } + $content = @file_get_contents($path); if (!$content) { return null; } - $tokens = token_get_all($content); - $namespace = ''; - $class = ''; - $classToken = false; - $collecting = false; + $tokens = @token_get_all($content); + if (!$tokens) { + return null; + } + + $namespace = ''; + $class = ''; + $collectingNamespace = false; + $collectingClass = false; + + var_dump($tokens[1]); foreach ($tokens as $token) { if (is_array($token)) { if ($token[0] === T_NAMESPACE) { - $namespace = ''; - $collecting = true; - } elseif ($collecting && $token[0] === T_STRING) { + $namespace = ''; + $collectingNamespace = true; + } elseif ($collectingNamespace && $token[0] === T_STRING) { $namespace .= $token[1]; - } elseif ($collecting && $token[0] === T_NS_SEPARATOR) { + } elseif ($collectingNamespace && $token[0] === T_NS_SEPARATOR) { $namespace .= '\\'; - } elseif ($collecting && $token[0] === T_WHITESPACE) { - // 忽略空白 - } else { - $collecting = false; - } - - if ($token[0] === T_CLASS || $token[0] === T_INTERFACE || $token[0] === T_TRAIT) { - $classToken = true; - } elseif ($classToken && $token[0] === T_STRING) { + } elseif ($collectingNamespace && $token[0] === T_WHITESPACE) { + continue; + } elseif ($token[0] === T_CLASS) { + $collectingClass = true; + } elseif ($collectingClass && $token[0] === T_STRING) { $class = $token[1]; break; + } elseif ($collectingNamespace) { + $collectingNamespace = false; } } else { - if ($token === ';' || $token === '{') { - $collecting = false; + if ($collectingNamespace && ($token === ';' || $token === '{')) { + $collectingNamespace = false; + } + if ($token === '{') { + $collectingClass = false; } } } @@ -303,8 +270,48 @@ class Scanner extends Component } /** - * 分析类及其方法 + * 将路径重命名为类名(原代码的逻辑) */ + private function renamePathToClassName(string $path): string + { + // 清理路径,移除开头和结尾的斜杠 + $path = trim($path, '/\\'); + + // 分割路径部分 + $parts = explode('/', str_replace('\\', '/', $path)); + + // 过滤空值并对每个部分应用ucfirst + $parts = array_filter($parts, function ($part) { + return !empty($part); + }); + + $parts = array_map('ucfirst', $parts); + + return implode('\\', $parts); + } + + private function optimizeWithOpcache(string $path): void + { + if ($this->hasOpcache === null) { + $this->hasOpcache = function_exists('opcache_invalidate') + && function_exists('opcache_compile_file'); + } + + if ($this->hasOpcache) { + try { + opcache_invalidate($path, true); + opcache_compile_file($path); + } catch (Throwable $e) { + if ($this->config['debug']) { + $this->logError($e, [ + 'file' => $path, + 'action' => 'opcache_optimize', + ]); + } + } + } + } + private function analyzeClass(string $class): void { try { @@ -317,12 +324,10 @@ class Scanner extends Component return; } - // 检查跳过注解 if ($this->shouldSkipClass($reflect)) { return; } - // 分析类的方法 $this->analyzeClassMethods($reflect, $class); } catch (Throwable $e) { @@ -333,9 +338,6 @@ class Scanner extends Component } } - /** - * 判断是否应该跳过该类 - */ private function shouldSkipClass(ReflectionClass $reflect): bool { $attributes = array_map( @@ -347,14 +349,9 @@ class Scanner extends Component in_array(\Attribute::class, $attributes, true); } - /** - * 分析类的方法 - */ private function analyzeClassMethods(ReflectionClass $reflect, string $class): void { - var_dump($reflect->getName()); foreach ($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - // 跳过静态方法和继承的方法 if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) { continue; } @@ -363,9 +360,6 @@ class Scanner extends Component } } - /** - * 处理方法上的注解 - */ private function processMethodAttributes(ReflectionMethod $method, string $class): void { foreach ($method->getAttributes() as $attribute) { @@ -392,9 +386,6 @@ class Scanner extends Component } } - /** - * 缓存相关方法 - */ private function getDefaultCacheFile(): string { $cacheDir = sys_get_temp_dir() . '/kiri-scanner-cache/'; @@ -406,21 +397,16 @@ class Scanner extends Component return $cacheDir . 'scanner_' . $projectHash . '.json'; } - /** - * 从缓存加载 - */ private function loadFromCache(string $cacheFile, string $path): bool { if (!file_exists($cacheFile)) { return false; } - // 检查缓存是否过期 if (filemtime($cacheFile) + $this->config['cache_ttl'] < time()) { return false; } - // 检查目录是否有更新 if ($this->hasDirectoryChanged($path, $cacheFile)) { return false; } @@ -436,14 +422,10 @@ class Scanner extends Component return true; } - /** - * 检查目录是否有更新 - */ private function hasDirectoryChanged(string $path, string $cacheFile): bool { $cacheMtime = filemtime($cacheFile); - // 简单的实现:检查目录本身和一级子目录 $dirIterator = new DirectoryIterator($path); foreach ($dirIterator as $item) { if ($item->isDot()) { @@ -459,42 +441,32 @@ class Scanner extends Component return false; } - /** - * 保存到缓存 - */ private function saveToCache(string $cacheFile): void { $data = [ 'files' => $this->files, 'mtimes' => $this->fileMtimes, 'timestamp' => time(), - 'version' => '1.0', + 'version' => '1.1', ]; file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT)); - // 清理旧缓存文件 $this->cleanupOldCacheFiles(dirname($cacheFile)); } - /** - * 清理旧的缓存文件 - */ private function cleanupOldCacheFiles(string $cacheDir): void { $files = glob($cacheDir . '/scanner_*.json'); $now = time(); foreach ($files as $file) { - if (filemtime($file) + (7 * 24 * 3600) < $now) { // 清理7天前的缓存 + if (filemtime($file) + (7 * 24 * 3600) < $now) { unlink($file); } } } - /** - * 处理缓存的文件(批量处理,避免重复加载) - */ private function processCachedFiles(): void { foreach ($this->files as $file) { @@ -504,17 +476,73 @@ class Scanner extends Component } } - /** - * 错误记录方法 - */ private function logError(Throwable $e, array $context = []): void { - json_log($e, $context); + $fullContext = array_merge($context, [ + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'trace' => $e->getTraceAsString(), + 'time' => date('Y-m-d H:i:s'), + ]); + + if (function_exists('error')) { + error($e, $fullContext); + } else { + error_log(json_encode($fullContext)); + } + + if ($this->config['debug'] && php_sapi_name() === 'cli') { + echo "Scanner Error: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}\n"; + if (!empty($context)) { + echo 'Context: ' . json_encode($context) . "\n"; + } + } } /** - * 重置扫描器状态 + * 原代码的load_directory方法(保持兼容性) */ + public function load_directory(string $path): void + { + $this->scan($path); + } + + /** + * 原代码的parseFile方法(保持兼容性) + */ + protected function parseFile($file): void + { + // 这里$file是相对路径,需要转换为绝对路径 + $absolutePath = $this->basePath . ltrim($file, '/') . '.php'; + + if (file_exists($absolutePath)) { + $this->processFile($absolutePath); + } + } + + /** + * 原代码的skipNames方法(保持兼容性) + */ + protected function skipNames(ReflectionClass $reflect): array + { + return array_map( + fn($attr) => $attr->getName(), + $reflect->getAttributes() + ); + } + + public function getStats(): array + { + return [ + 'total_files' => count($this->files), + 'cached_mtimes' => count($this->fileMtimes), + 'base_path' => $this->basePath, + 'config' => $this->config, + ]; + } + public function reset(): void { $this->files = [];