This commit is contained in:
2026-06-12 23:57:19 +08:00
parent 0ff2cd7a06
commit 7827b8d5b1
6 changed files with 147 additions and 116 deletions
+66 -4
View File
@@ -6,34 +6,75 @@ namespace Kiri\Di;
class ChangeSet class ChangeSet
{ {
/**
* @var array
*/
private array $changedFiles = []; private array $changedFiles = [];
/**
* @var array
*/
private array $removedFiles = []; private array $removedFiles = [];
/**
* @var array
*/
private array $changedClasses = []; private array $changedClasses = [];
/**
* @var array
*/
private array $removedClasses = []; private array $removedClasses = [];
/**
* @param string $file
* @return void
*/
public function addChangedFile(string $file): void public function addChangedFile(string $file): void
{ {
$this->changedFiles[$file] = true; $this->changedFiles[$file] = true;
} }
/**
* @param string $file
* @return void
*/
public function addRemovedFile(string $file): void public function addRemovedFile(string $file): void
{ {
$this->removedFiles[$file] = true; $this->removedFiles[$file] = true;
} }
/**
* @param string $class
* @return void
*/
public function addChangedClass(string $class): void public function addChangedClass(string $class): void
{ {
$this->changedClasses[$class] = true; $this->changedClasses[$class] = true;
} }
/**
* @param string $class
* @return void
*/
public function addRemovedClass(string $class): void public function addRemovedClass(string $class): void
{ {
$this->removedClasses[$class] = true; $this->removedClasses[$class] = true;
} }
/**
* @param ChangeSet $changeSet
* @return $this
*/
public function merge(ChangeSet $changeSet): self public function merge(ChangeSet $changeSet): self
{ {
foreach ($changeSet->getChangedFiles() as $file) { foreach ($changeSet->getChangedFiles() as $file) {
@@ -55,34 +96,55 @@ class ChangeSet
return $this; return $this;
} }
/**
* @return array
*/
public function getChangedFiles(): array public function getChangedFiles(): array
{ {
return array_keys($this->changedFiles); return array_keys($this->changedFiles);
} }
/**
* @return array
*/
public function getRemovedFiles(): array public function getRemovedFiles(): array
{ {
return array_keys($this->removedFiles); return array_keys($this->removedFiles);
} }
/**
* @return array
*/
public function getChangedClasses(): array public function getChangedClasses(): array
{ {
return array_keys($this->changedClasses); return array_keys($this->changedClasses);
} }
/**
* @return array
*/
public function getRemovedClasses(): array public function getRemovedClasses(): array
{ {
return array_keys($this->removedClasses); return array_keys($this->removedClasses);
} }
/**
* @return bool
*/
public function hasChanges(): bool public function hasChanges(): bool
{ {
return $this->changedFiles !== [] return $this->changedFiles !== [] || $this->removedFiles !== [] || $this->changedClasses !== [] || $this->removedClasses !== [];
|| $this->removedFiles !== []
|| $this->changedClasses !== []
|| $this->removedClasses !== [];
} }
/**
* @return array
*/
public function toArray(): array public function toArray(): array
{ {
return [ return [
+14
View File
@@ -225,7 +225,14 @@ 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);
$needsProxy = !$isController && class_exists(\Kiri\Router\Annotate\DeferRegistry::class) && \Kiri\Router\Annotate\DeferRegistry::hasAny($className);
if ($needsProxy) {
$newInstance = \Kiri\Router\Annotate\DeferProxyGenerator::create($className, $construct);
} else {
$newInstance = $reflect->newInstanceArgs($construct); $newInstance = $reflect->newInstanceArgs($construct);
}
return $this->runInit($reflect, static::configure($newInstance, $config)); return $this->runInit($reflect, static::configure($newInstance, $config));
} }
@@ -252,7 +259,14 @@ class Container implements ContainerInterface
if (empty($construct) && ($handler = $reflect->getConstructor()) !== null) { if (empty($construct) && ($handler = $reflect->getConstructor()) !== null) {
$construct = $this->getMethodParams($handler); $construct = $this->getMethodParams($handler);
} }
$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());
if ($needsProxy) {
$newInstance = \Kiri\Router\Annotate\DeferProxyGenerator::create($reflect->getName(), $construct);
} else {
$newInstance = $reflect->newInstanceArgs($construct); $newInstance = $reflect->newInstanceArgs($construct);
}
return $this->runInit($reflect, static::configure($newInstance, $config)); return $this->runInit($reflect, static::configure($newInstance, $config));
} }
+9 -3
View File
@@ -6,13 +6,17 @@ namespace Kiri\Di;
class HotReloadState class HotReloadState
{ {
private const MAX_AGE_SECONDS = 30; private const int MAX_AGE_SECONDS = 30;
public function store(array $changedFiles): void public function store(array $changedFiles): void
{ {
$payload = [ $payload = [
'timestamp' => time(), 'timestamp' => time(),
'changed_files' => array_values(array_unique(array_map([$this, 'normalizePath'], array_filter($changedFiles)))), 'changed_files' => $changedFiles
|> array_filter(...)
|> (fn($x) => array_map([$this, 'normalizePath'], $x))
|> array_unique(...)
|> array_values(...),
]; ];
$directory = dirname($this->getFilePath()); $directory = dirname($this->getFilePath());
@@ -46,7 +50,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_values(array_unique(array_map([$this, 'normalizePath'], $files))); return array_map([$this, 'normalizePath'], $files)
|> array_unique(...)
|> array_values(...);
} }
private function getFilePath(): string private function getFilePath(): string
+45 -1
View File
@@ -8,6 +8,13 @@ class ScanManifest
{ {
private array $entries = []; private array $entries = [];
/**
* @param string $path
* @param int $mtime
* @param array $classes
* @return void
*/
public function set(string $path, int $mtime, array $classes): void public function set(string $path, int $mtime, array $classes): void
{ {
$this->entries[$path] = [ $this->entries[$path] = [
@@ -16,21 +23,41 @@ class ScanManifest
]; ];
} }
/**
* @param string $path
* @return bool
*/
public function has(string $path): bool public function has(string $path): bool
{ {
return isset($this->entries[$path]); return isset($this->entries[$path]);
} }
/**
* @param string $path
* @return int|null
*/
public function getMtime(string $path): ?int public function getMtime(string $path): ?int
{ {
return $this->entries[$path]['mtime'] ?? null; return $this->entries[$path]['mtime'] ?? null;
} }
/**
* @param string $path
* @return array
*/
public function getClasses(string $path): array public function getClasses(string $path): array
{ {
return $this->entries[$path]['classes'] ?? []; return $this->entries[$path]['classes'] ?? [];
} }
/**
* @param string $path
* @return array
*/
public function remove(string $path): array public function remove(string $path): array
{ {
$classes = $this->getClasses($path); $classes = $this->getClasses($path);
@@ -38,20 +65,37 @@ class ScanManifest
return $classes; return $classes;
} }
/**
* @return array
*/
public function all(): array public function all(): array
{ {
return $this->entries; return $this->entries;
} }
/**
* @param string|null $prefix
* @return array
*/
public function paths(?string $prefix = null): array public function paths(?string $prefix = null): array
{ {
if ($prefix === null) { if ($prefix === null) {
return array_keys($this->entries); return array_keys($this->entries);
} }
return array_values(array_filter(array_keys($this->entries), fn(string $path) => str_starts_with($path, $prefix))); return $this->entries
|> array_keys(...)
|> (fn($x) => array_filter($x, fn(string $path) => str_starts_with($path, $prefix)))
|> array_values(...);
} }
/**
* @param array $entries
* @return void
*/
public function fromArray(array $entries): void public function fromArray(array $entries): void
{ {
$this->entries = []; $this->entries = [];
+8 -103
View File
@@ -8,6 +8,7 @@ use DirectoryIterator;
use Kiri\Abstracts\Component; use Kiri\Abstracts\Component;
use Kiri\Di\Inject\Container; use Kiri\Di\Inject\Container;
use Kiri\Di\Inject\Skip; use Kiri\Di\Inject\Skip;
use Kiri\Di\Interface\InjectMethodInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use ReflectionClass; use ReflectionClass;
use ReflectionMethod; use ReflectionMethod;
@@ -41,7 +42,6 @@ class Scanner extends Component
'cache_enabled' => false, 'cache_enabled' => false,
'cache_ttl' => 3600, 'cache_ttl' => 3600,
'debug' => false, 'debug' => false,
'class_name_strategy' => 'auto',
]; ];
public function __construct() public function __construct()
@@ -239,9 +239,12 @@ class Scanner extends Component
private function loadAndParseFile(string $path): array private function loadAndParseFile(string $path): array
{ {
$this->optimizeWithOpcache($path); $this->optimizeWithOpcache($path);
require_once $path;
$classes = $this->getClassNamesForFile($path); $before = get_declared_classes();
require_once $path;
$after = get_declared_classes();
$classes = array_values(array_diff($after, $before));
foreach ($classes as $class) { foreach ($classes as $class) {
if (class_exists($class)) { if (class_exists($class)) {
$this->analyzeClass($class); $this->analyzeClass($class);
@@ -251,104 +254,6 @@ class Scanner extends Component
return $classes; return $classes;
} }
private function getClassNamesForFile(string $path): array
{
$strategy = $this->config['class_name_strategy'];
$classes = [];
if (in_array($strategy, ['auto', 'extract', 'both'], true)) {
$class = $this->extractClassNameFromFile($path);
if ($class !== null) {
$classes[] = $class;
}
}
if ($classes === [] || in_array($strategy, ['rename', 'both'], true)) {
$classes[] = $this->renamePathToClassName($path);
}
return array_values(array_unique(array_filter($classes)));
}
private function canExtractClassName(): bool
{
return function_exists('token_get_all');
}
private function extractClassNameFromFile(string $path): ?string
{
if (!$this->canExtractClassName()) {
return null;
}
$content = @file_get_contents($path);
if ($content === false || $content === '') {
return null;
}
$tokens = @token_get_all($content);
if (!$tokens) {
return null;
}
$namespace = '';
$class = '';
$collectingNamespace = false;
$collectingClass = false;
$previousToken = null;
foreach ($tokens as $token) {
if (is_array($token)) {
$text = $token[1];
if ($token[0] === T_NAMESPACE) {
$namespace = '';
$collectingNamespace = true;
} elseif ($collectingNamespace && in_array($token[0], [T_STRING, T_NAME_QUALIFIED, T_NS_SEPARATOR], true)) {
$namespace .= $text;
} elseif ($collectingNamespace && $token[0] === T_WHITESPACE) {
} elseif (in_array($token[0], [T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], true) && $previousToken !== T_DOUBLE_COLON && $previousToken !== T_NEW) {
$collectingClass = true;
} elseif ($collectingClass && $token[0] === T_STRING) {
$class = $text;
break;
} elseif ($collectingNamespace) {
$collectingNamespace = false;
}
if ($token[0] !== T_WHITESPACE) {
$previousToken = $token[0];
}
continue;
}
if ($collectingNamespace && ($token === ';' || $token === '{')) {
$collectingNamespace = false;
}
if ($token === '{') {
$collectingClass = false;
}
$previousToken = $token;
}
if ($class === '') {
return null;
}
return $namespace !== '' ? $namespace . '\\' . $class : $class;
}
private function renamePathToClassName(string $path): string
{
$relativePath = str_replace($this->basePath, '', $this->normalizePath($path));
$relativePath = str_replace('.php', '', $relativePath);
$parts = explode('/', trim($relativePath, '/\\'));
$parts = array_values(array_filter($parts, fn(string $part) => $part !== ''));
$parts = array_map('ucfirst', $parts);
return implode('\\', $parts);
}
private function optimizeWithOpcache(string $path): void private function optimizeWithOpcache(string $path): void
{ {
if ($this->hasOpcache === null) { if ($this->hasOpcache === null) {
@@ -403,7 +308,7 @@ class Scanner extends Component
private function analyzeClassMethods(ReflectionClass $reflect, string $class): void private function analyzeClassMethods(ReflectionClass $reflect, string $class): void
{ {
foreach ($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { foreach ($reflect->getMethods() as $method) {
if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) { if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) {
continue; continue;
} }
@@ -422,7 +327,7 @@ class Scanner extends Component
try { try {
$instance = $attribute->newInstance(); $instance = $attribute->newInstance();
if (method_exists($instance, 'dispatch')) { if ($instance instanceof InjectMethodInterface) {
$instance->dispatch($class, $method->getName()); $instance->dispatch($class, $method->getName());
} }
} catch (Throwable $e) { } catch (Throwable $e) {
+1 -1
View File
@@ -9,7 +9,7 @@
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": ">=8.4", "php": ">=8.5",
"psr/container": "^2.0" "psr/container": "^2.0"
}, },
"autoload": { "autoload": {