Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 689fd09358 | |||
| 2753a42d82 | |||
| 8d340a8a45 | |||
| e1a23090d6 | |||
| b08fdb1801 | |||
| a0b975003a | |||
| 81250722ea | |||
| b7712d3d9d | |||
| c38fb6ac4c |
+73
-4
@@ -207,6 +207,75 @@ class Container implements ContainerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理所有非基础设施类的单例引用,释放 Swoole 常驻进程中的内存
|
||||||
|
* 保留框架核心类(Kiri、Database、Psr、Symfony)的单例
|
||||||
|
* 应在请求/任务完成后调用,防止单例映射无限增长
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clearNonInfrastructure(): void
|
||||||
|
{
|
||||||
|
$keepPrefixes = [
|
||||||
|
ContainerInterface::class,
|
||||||
|
'Kiri\\', 'Database\\', 'Psr\\', 'Symfony\\',
|
||||||
|
'Swoole\\', 'MongoDB\\',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (array_keys($this->_singletons) as $className) {
|
||||||
|
$shouldKeep = false;
|
||||||
|
foreach ($keepPrefixes as $prefix) {
|
||||||
|
if (str_starts_with($className, $prefix)) {
|
||||||
|
$shouldKeep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$shouldKeep) {
|
||||||
|
unset($this->_singletons[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($this->_reflection) as $className) {
|
||||||
|
$shouldKeep = false;
|
||||||
|
foreach ($keepPrefixes as $prefix) {
|
||||||
|
if (str_starts_with($className, $prefix)) {
|
||||||
|
$shouldKeep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$shouldKeep) {
|
||||||
|
unset($this->_reflection[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($this->_parameters) as $className) {
|
||||||
|
$shouldKeep = false;
|
||||||
|
foreach ($keepPrefixes as $prefix) {
|
||||||
|
if (str_starts_with($className, $prefix)) {
|
||||||
|
$shouldKeep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$shouldKeep) {
|
||||||
|
unset($this->_parameters[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前单例缓存统计信息,用于内存监控
|
||||||
|
* @return array{singletons: int, reflections: int, params: int}
|
||||||
|
*/
|
||||||
|
public function getMemoryStats(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'singletons' => count($this->_singletons),
|
||||||
|
'reflections' => count($this->_reflection),
|
||||||
|
'params' => count($this->_parameters),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $className
|
* @param string $className
|
||||||
* @param array $construct
|
* @param array $construct
|
||||||
@@ -226,10 +295,10 @@ class Container implements ContainerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$isController = class_exists(\Kiri\Router\Base\Controller::class) && $reflect->isSubclassOf(\Kiri\Router\Base\Controller::class);
|
$isController = class_exists(\Kiri\Router\Base\Controller::class) && $reflect->isSubclassOf(\Kiri\Router\Base\Controller::class);
|
||||||
$needsProxy = !$isController && class_exists(\Kiri\Router\Annotate\DeferRegistry::class) && \Kiri\Router\Annotate\DeferRegistry::hasAny($className);
|
$needsProxy = !$isController && class_exists(\Kiri\Router\Defer\DeferRegistry::class) && \Kiri\Router\Defer\DeferRegistry::hasAny($className);
|
||||||
|
|
||||||
if ($needsProxy) {
|
if ($needsProxy) {
|
||||||
$newInstance = \Kiri\Router\Annotate\DeferProxyGenerator::create($className, $construct);
|
$newInstance = \Kiri\Router\Defer\DeferProxyGenerator::create($className, $construct);
|
||||||
} else {
|
} else {
|
||||||
$newInstance = $reflect->newInstanceArgs($construct);
|
$newInstance = $reflect->newInstanceArgs($construct);
|
||||||
}
|
}
|
||||||
@@ -260,10 +329,10 @@ class Container implements ContainerInterface
|
|||||||
$construct = $this->getMethodParams($handler);
|
$construct = $this->getMethodParams($handler);
|
||||||
}
|
}
|
||||||
$isController = class_exists(\Kiri\Router\Base\Controller::class) && $reflect->isSubclassOf(\Kiri\Router\Base\Controller::class);
|
$isController = class_exists(\Kiri\Router\Base\Controller::class) && $reflect->isSubclassOf(\Kiri\Router\Base\Controller::class);
|
||||||
$needsProxy = !$isController && class_exists(\Kiri\Router\Annotate\DeferRegistry::class) && \Kiri\Router\Annotate\DeferRegistry::hasAny($reflect->getName());
|
$needsProxy = !$isController && class_exists(\Kiri\Router\Defer\DeferRegistry::class) && \Kiri\Router\Defer\DeferRegistry::hasAny($reflect->getName());
|
||||||
|
|
||||||
if ($needsProxy) {
|
if ($needsProxy) {
|
||||||
$newInstance = \Kiri\Router\Annotate\DeferProxyGenerator::create($reflect->getName(), $construct);
|
$newInstance = \Kiri\Router\Defer\DeferProxyGenerator::create($reflect->getName(), $construct);
|
||||||
} else {
|
} else {
|
||||||
$newInstance = $reflect->newInstanceArgs($construct);
|
$newInstance = $reflect->newInstanceArgs($construct);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-8
@@ -10,13 +10,12 @@ class HotReloadState
|
|||||||
|
|
||||||
public function store(array $changedFiles): void
|
public function store(array $changedFiles): void
|
||||||
{
|
{
|
||||||
|
$normalizedFiles = array_map([$this, 'normalizePath'], array_filter($changedFiles));
|
||||||
|
$normalizedFiles = array_values(array_unique($normalizedFiles));
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'timestamp' => time(),
|
'timestamp' => time(),
|
||||||
'changed_files' => $changedFiles
|
'changed_files' => $normalizedFiles,
|
||||||
|> array_filter(...)
|
|
||||||
|> (fn($x) => array_map([$this, 'normalizePath'], $x))
|
|
||||||
|> array_unique(...)
|
|
||||||
|> array_values(...),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$directory = dirname($this->getFilePath());
|
$directory = dirname($this->getFilePath());
|
||||||
@@ -50,9 +49,9 @@ class HotReloadState
|
|||||||
}
|
}
|
||||||
|
|
||||||
$files = is_array($data['changed_files'] ?? null) ? $data['changed_files'] : [];
|
$files = is_array($data['changed_files'] ?? null) ? $data['changed_files'] : [];
|
||||||
return array_map([$this, 'normalizePath'], $files)
|
$files = array_map([$this, 'normalizePath'], $files);
|
||||||
|> array_unique(...)
|
$files = array_values(array_unique($files));
|
||||||
|> array_values(...);
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getFilePath(): string
|
private function getFilePath(): string
|
||||||
|
|||||||
+3
-4
@@ -85,10 +85,9 @@ class ScanManifest
|
|||||||
return array_keys($this->entries);
|
return array_keys($this->entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->entries
|
$keys = array_keys($this->entries);
|
||||||
|> array_keys(...)
|
$filtered = array_filter($keys, fn(string $path) => str_starts_with($path, $prefix));
|
||||||
|> (fn($x) => array_filter($x, fn(string $path) => str_starts_with($path, $prefix)))
|
return array_values($filtered);
|
||||||
|> array_values(...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+435
-27
@@ -41,9 +41,13 @@ class Scanner extends Component
|
|||||||
'follow_links' => false,
|
'follow_links' => false,
|
||||||
'cache_enabled' => false,
|
'cache_enabled' => false,
|
||||||
'cache_ttl' => 3600,
|
'cache_ttl' => 3600,
|
||||||
|
'opcache_compile' => false,
|
||||||
|
'require_files_without_classes' => false,
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static ?string $currentFile = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->basePath = $this->normalizePath($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
|
$this->basePath = $this->normalizePath($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
|
||||||
@@ -79,15 +83,13 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->syncLegacyState();
|
$this->syncLegacyState();
|
||||||
|
if ($this->config['cache_enabled'] && $cacheFile !== null) {
|
||||||
if ($this->config['cache_enabled']) {
|
|
||||||
$this->saveToCache($cacheFile);
|
$this->saveToCache($cacheFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->changeSet;
|
return $this->changeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scanFiles(array $files, ?string $scopePath = null, ?string $cacheFile = null): ChangeSet
|
public function scanFiles(array $files, ?string $scopePath = null, ?string $cacheFile = null, bool $force = false, bool $replayManifest = true): ChangeSet
|
||||||
{
|
{
|
||||||
$this->changeSet = new ChangeSet();
|
$this->changeSet = new ChangeSet();
|
||||||
$this->visitedFiles = [];
|
$this->visitedFiles = [];
|
||||||
@@ -106,21 +108,85 @@ class Scanner extends Component
|
|||||||
foreach (array_values(array_unique($files)) as $file) {
|
foreach (array_values(array_unique($files)) as $file) {
|
||||||
$path = $this->normalizePath($file);
|
$path = $this->normalizePath($file);
|
||||||
if (file_exists($path)) {
|
if (file_exists($path)) {
|
||||||
$this->processFile($path);
|
$this->processFile($path, $force);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->markRemovedFile($path);
|
$this->markRemovedFile($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($cacheLoaded && $scopePath !== null) {
|
if ($replayManifest && $cacheLoaded && $scopePath !== null) {
|
||||||
$this->replayManifest($scopePath, $this->changeSet->getChangedFiles());
|
$this->replayManifest($scopePath, $this->changeSet->getChangedFiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->syncLegacyState();
|
$this->syncLegacyState();
|
||||||
|
if ($this->config['cache_enabled'] && $scopePath !== null && $cacheFile !== null) {
|
||||||
|
$this->saveToCache($cacheFile);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->changeSet;
|
return $this->changeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function expandDependentFiles(array $files, ?string $scopePath = null): array
|
||||||
|
{
|
||||||
|
$expanded = [];
|
||||||
|
foreach (array_values(array_unique($files)) as $file) {
|
||||||
|
$path = $this->normalizePath($file);
|
||||||
|
$expanded[$path] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scopePath === null || $expanded === []) {
|
||||||
|
return array_keys($expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scopePath = $this->normalizePath($scopePath);
|
||||||
|
$scopeFiles = $this->collectFiles($scopePath);
|
||||||
|
$symbols = [];
|
||||||
|
foreach (array_keys($expanded) as $file) {
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($this->parseDeclaredSymbols($file) as $class => $kind) {
|
||||||
|
$symbols[$class] = $kind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($symbols === []) {
|
||||||
|
return array_keys($expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
$processedSymbols = [];
|
||||||
|
do {
|
||||||
|
$newSymbols = array_diff_key($symbols, $processedSymbols);
|
||||||
|
if ($newSymbols === []) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$processedSymbols += $newSymbols;
|
||||||
|
$changed = false;
|
||||||
|
|
||||||
|
foreach ($scopeFiles as $file) {
|
||||||
|
$file = $this->normalizePath($file);
|
||||||
|
if (isset($expanded[$file]) || !file_exists($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dependencies = $this->parseFileDependencies($file);
|
||||||
|
if (array_intersect_key($dependencies, $symbols) === []) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expanded[$file] = true;
|
||||||
|
foreach ($this->parseDeclaredSymbols($file) as $class => $kind) {
|
||||||
|
if (!isset($symbols[$class])) {
|
||||||
|
$symbols[$class] = $kind;
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while ($changed);
|
||||||
|
|
||||||
|
return array_keys($expanded);
|
||||||
|
}
|
||||||
private function scanDirectory(string $path, int $depth = 0): void
|
private function scanDirectory(string $path, int $depth = 0): void
|
||||||
{
|
{
|
||||||
if ($depth > $this->config['max_depth']) {
|
if ($depth > $this->config['max_depth']) {
|
||||||
@@ -185,7 +251,7 @@ class Scanner extends Component
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processFile(string $path): void
|
private function processFile(string $path, bool $force = false): void
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $this->config['extensions'], true)) {
|
if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $this->config['extensions'], true)) {
|
||||||
@@ -193,7 +259,7 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->visitedFiles[$path] = true;
|
$this->visitedFiles[$path] = true;
|
||||||
if (!$this->shouldProcessFile($path)) {
|
if (!$force && !$this->shouldProcessFile($path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,22 +306,44 @@ class Scanner extends Component
|
|||||||
{
|
{
|
||||||
$this->optimizeWithOpcache($path);
|
$this->optimizeWithOpcache($path);
|
||||||
|
|
||||||
$before = get_declared_classes();
|
self::$currentFile = $path;
|
||||||
require_once $path;
|
$classes = array_keys($this->parseDeclaredSymbols($path));
|
||||||
$after = get_declared_classes();
|
$loadFile = $classes === [] ? (bool)$this->config['require_files_without_classes'] : false;
|
||||||
|
|
||||||
$classes = array_values(array_diff($after, $before));
|
|
||||||
foreach ($classes as $class) {
|
foreach ($classes as $class) {
|
||||||
if (class_exists($class)) {
|
if (!$this->isDeclaredSymbol($class)) {
|
||||||
$this->analyzeClass($class);
|
$loadFile = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $classes;
|
try {
|
||||||
}
|
if ($loadFile) {
|
||||||
|
$before = $this->getDeclaredSymbols();
|
||||||
|
require_once $path;
|
||||||
|
if ($classes === []) {
|
||||||
|
$after = $this->getDeclaredSymbols();
|
||||||
|
$classes = array_values(array_diff($after, $before));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
if (class_exists($class)) {
|
||||||
|
$this->analyzeClass($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
} finally {
|
||||||
|
self::$currentFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
private function optimizeWithOpcache(string $path): void
|
private function optimizeWithOpcache(string $path): void
|
||||||
{
|
{
|
||||||
|
if (!$this->config['opcache_compile']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->hasOpcache === null) {
|
if ($this->hasOpcache === null) {
|
||||||
$this->hasOpcache = function_exists('opcache_invalidate') && function_exists('opcache_compile_file');
|
$this->hasOpcache = function_exists('opcache_invalidate') && function_exists('opcache_compile_file');
|
||||||
}
|
}
|
||||||
@@ -277,6 +365,307 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function parseDeclaredSymbols(string $path): array
|
||||||
|
{
|
||||||
|
$tokens = token_get_all(file_get_contents($path) ?: '');
|
||||||
|
$namespace = '';
|
||||||
|
$classes = [];
|
||||||
|
$count = count($tokens);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if (!is_array($token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token[0] === T_NAMESPACE) {
|
||||||
|
$namespace = $this->parseNamespace($tokens, $i + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->isClassLikeToken($token[0])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token[0] === T_CLASS && ($this->isAnonymousClass($tokens, $i) || $this->isClassConstant($tokens, $i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->parseClassLikeName($tokens, $i + 1);
|
||||||
|
if ($name === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes[ltrim($namespace . '\\' . $name, '\\')] = $this->getClassLikeKind($tokens, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseNamespace(array $tokens, int $offset): string
|
||||||
|
{
|
||||||
|
$namespace = '';
|
||||||
|
$count = count($tokens);
|
||||||
|
for ($i = $offset; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if ($token === ';' || $token === '{') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (is_array($token) && in_array($token[0], [T_STRING, T_NAME_QUALIFIED, T_NS_SEPARATOR], true)) {
|
||||||
|
$namespace .= $token[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($namespace, '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseClassLikeName(array $tokens, int $offset): ?string
|
||||||
|
{
|
||||||
|
$count = count($tokens);
|
||||||
|
for ($i = $offset; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if (!is_array($token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($token[0] === T_STRING) {
|
||||||
|
return $token[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isClassLikeToken(int $token): bool
|
||||||
|
{
|
||||||
|
return $token === T_CLASS
|
||||||
|
|| $token === T_INTERFACE
|
||||||
|
|| $token === T_TRAIT
|
||||||
|
|| (defined('T_ENUM') && $token === T_ENUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isAnonymousClass(array $tokens, int $offset): bool
|
||||||
|
{
|
||||||
|
for ($i = $offset - 1; $i >= 0; $i--) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if (is_array($token) && in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($token) && $token[0] === T_NEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isClassConstant(array $tokens, int $offset): bool
|
||||||
|
{
|
||||||
|
for ($i = $offset - 1; $i >= 0; $i--) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if (is_array($token) && in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($token) && $token[0] === T_DOUBLE_COLON;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private function parseFileDependencies(string $path): array
|
||||||
|
{
|
||||||
|
$tokens = token_get_all(file_get_contents($path) ?: '');
|
||||||
|
$namespace = '';
|
||||||
|
$imports = [];
|
||||||
|
$dependencies = [];
|
||||||
|
$count = count($tokens);
|
||||||
|
$braceDepth = 0;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if ($token === '{') {
|
||||||
|
$braceDepth++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($token === '}') {
|
||||||
|
$braceDepth = max(0, $braceDepth - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!is_array($token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token[0] === T_NAMESPACE) {
|
||||||
|
$namespace = $this->parseNamespace($tokens, $i + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token[0] === T_USE) {
|
||||||
|
if ($braceDepth === 0) {
|
||||||
|
foreach ($this->parseUseImports($tokens, $i + 1, $namespace) as $alias => $class) {
|
||||||
|
$imports[$alias] = $class;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->parseNameList($tokens, $i + 1, [';', '{']) as $name) {
|
||||||
|
$dependencies[$this->resolveClassName($name, $namespace, $imports)] = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token[0] === T_EXTENDS || $token[0] === T_IMPLEMENTS) {
|
||||||
|
foreach ($this->parseNameList($tokens, $i + 1, ['{']) as $name) {
|
||||||
|
$dependencies[$this->resolveClassName($name, $namespace, $imports)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseUseImports(array $tokens, int $offset, string $namespace): array
|
||||||
|
{
|
||||||
|
$imports = [];
|
||||||
|
$count = count($tokens);
|
||||||
|
for ($i = $offset; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if ($token === ';') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (is_array($token) && in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_array($token) && in_array($token[0], [T_FUNCTION, T_CONST], true)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->collectQualifiedName($tokens, $i);
|
||||||
|
if ($name === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alias = null;
|
||||||
|
$j = $i + 1;
|
||||||
|
while ($j < $count) {
|
||||||
|
$next = $tokens[$j];
|
||||||
|
if ($next === ',' || $next === ';') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (is_array($next) && $next[0] === T_AS) {
|
||||||
|
$aliasOffset = $j + 1;
|
||||||
|
$alias = $this->collectQualifiedName($tokens, $aliasOffset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = trim($name, '\\');
|
||||||
|
$short = $alias ?: basename(str_replace('\\', '/', $class));
|
||||||
|
$imports[strtolower($short)] = $class;
|
||||||
|
$i = $j;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $imports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseNameList(array $tokens, int $offset, array $stoppers): array
|
||||||
|
{
|
||||||
|
$names = [];
|
||||||
|
$count = count($tokens);
|
||||||
|
for ($i = $offset; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if (is_string($token) && in_array($token, $stoppers, true)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->collectQualifiedName($tokens, $i);
|
||||||
|
if ($name !== null) {
|
||||||
|
$names[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function collectQualifiedName(array $tokens, int &$offset): ?string
|
||||||
|
{
|
||||||
|
$count = count($tokens);
|
||||||
|
$name = '';
|
||||||
|
$started = false;
|
||||||
|
for ($i = $offset; $i < $count; $i++) {
|
||||||
|
$token = $tokens[$i];
|
||||||
|
if (!$started && is_array($token) && in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($token) && in_array($token[0], [T_STRING, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED, T_NS_SEPARATOR], true)) {
|
||||||
|
$name .= $token[1];
|
||||||
|
$started = true;
|
||||||
|
$offset = $i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name === '' ? null : $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveClassName(string $name, string $namespace, array $imports): string
|
||||||
|
{
|
||||||
|
$name = trim($name, '\\');
|
||||||
|
if ($name === '') {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode('\\', $name);
|
||||||
|
$first = strtolower($parts[0]);
|
||||||
|
if (isset($imports[$first])) {
|
||||||
|
array_shift($parts);
|
||||||
|
return $imports[$first] . ($parts === [] ? '' : '\\' . implode('\\', $parts));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $namespace === '' ? $name : $namespace . '\\' . $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClassLikeKind(array $tokens, int $offset): string
|
||||||
|
{
|
||||||
|
$token = $tokens[$offset];
|
||||||
|
if (!is_array($token)) {
|
||||||
|
return 'class';
|
||||||
|
}
|
||||||
|
if ($token[0] === T_TRAIT) {
|
||||||
|
return 'trait';
|
||||||
|
}
|
||||||
|
if ($token[0] === T_INTERFACE) {
|
||||||
|
return 'interface';
|
||||||
|
}
|
||||||
|
if (defined('T_ENUM') && $token[0] === T_ENUM) {
|
||||||
|
return 'enum';
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = $offset - 1; $i >= 0; $i--) {
|
||||||
|
$previous = $tokens[$i];
|
||||||
|
if (is_array($previous) && in_array($previous[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($previous) && $previous[0] === T_ABSTRACT ? 'abstract_class' : 'class';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'class';
|
||||||
|
}
|
||||||
|
private function getDeclaredSymbols(): array
|
||||||
|
{
|
||||||
|
return array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isDeclaredSymbol(string $class): bool
|
||||||
|
{
|
||||||
|
return class_exists($class, false)
|
||||||
|
|| interface_exists($class, false)
|
||||||
|
|| trait_exists($class, false)
|
||||||
|
|| (function_exists('enum_exists') && enum_exists($class, false));
|
||||||
|
}
|
||||||
private function analyzeClass(string $class): void
|
private function analyzeClass(string $class): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -309,7 +698,8 @@ class Scanner extends Component
|
|||||||
private function analyzeClassMethods(ReflectionClass $reflect, string $class): void
|
private function analyzeClassMethods(ReflectionClass $reflect, string $class): void
|
||||||
{
|
{
|
||||||
foreach ($reflect->getMethods() as $method) {
|
foreach ($reflect->getMethods() as $method) {
|
||||||
if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) {
|
$declaringClass = $method->getDeclaringClass();
|
||||||
|
if ($method->isStatic() || ($declaringClass->getName() !== $class && !$declaringClass->isTrait())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,16 +856,26 @@ class Scanner extends Component
|
|||||||
return array_map(fn($attr) => $attr->getName(), $reflect->getAttributes());
|
return array_map(fn($attr) => $attr->getName(), $reflect->getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStats(): array
|
public function getStats(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'total_files' => count($this->files),
|
'total_files' => count($this->files),
|
||||||
'cached_mtimes' => count($this->fileMtimes),
|
'cached_mtimes' => count($this->fileMtimes),
|
||||||
'base_path' => $this->basePath,
|
'base_path' => $this->basePath,
|
||||||
'config' => $this->config,
|
'config' => $this->config,
|
||||||
'manifest_entries' => count($this->manifest->all()),
|
'manifest_entries' => count($this->manifest->all()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 Master 扫描产生的完整清单数据,供 Worker 轻量重建注解路由
|
||||||
|
* @return array{string: array{mtime: int, classes: string[]}}
|
||||||
|
*/
|
||||||
|
public function getManifestClasses(): array
|
||||||
|
{
|
||||||
|
return $this->manifest->all();
|
||||||
|
}
|
||||||
|
|
||||||
public function reset(): void
|
public function reset(): void
|
||||||
{
|
{
|
||||||
@@ -487,6 +887,11 @@ class Scanner extends Component
|
|||||||
$this->visitedFiles = [];
|
$this->visitedFiles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getCurrentFile(): ?string
|
||||||
|
{
|
||||||
|
return self::$currentFile;
|
||||||
|
}
|
||||||
|
|
||||||
private function detectRemovedFiles(string $path): void
|
private function detectRemovedFiles(string $path): void
|
||||||
{
|
{
|
||||||
$prefix = rtrim($path, '/\\') . '/';
|
$prefix = rtrim($path, '/\\') . '/';
|
||||||
@@ -589,6 +994,9 @@ class Scanner extends Component
|
|||||||
if (method_exists($this->container, 'forgetClass')) {
|
if (method_exists($this->container, 'forgetClass')) {
|
||||||
$this->container->forgetClass($class);
|
$this->container->forgetClass($class);
|
||||||
}
|
}
|
||||||
|
if (class_exists(\Kiri\Router\Defer\DeferRegistry::class)) {
|
||||||
|
\Kiri\Router\Defer\DeferRegistry::removeClass($class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user