eee
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Kiri\Di;
|
||||||
|
|
||||||
|
class ChangeSet
|
||||||
|
{
|
||||||
|
private array $changedFiles = [];
|
||||||
|
|
||||||
|
private array $removedFiles = [];
|
||||||
|
|
||||||
|
private array $changedClasses = [];
|
||||||
|
|
||||||
|
private array $removedClasses = [];
|
||||||
|
|
||||||
|
public function addChangedFile(string $file): void
|
||||||
|
{
|
||||||
|
$this->changedFiles[$file] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRemovedFile(string $file): void
|
||||||
|
{
|
||||||
|
$this->removedFiles[$file] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addChangedClass(string $class): void
|
||||||
|
{
|
||||||
|
$this->changedClasses[$class] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRemovedClass(string $class): void
|
||||||
|
{
|
||||||
|
$this->removedClasses[$class] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function merge(ChangeSet $changeSet): self
|
||||||
|
{
|
||||||
|
foreach ($changeSet->getChangedFiles() as $file) {
|
||||||
|
$this->addChangedFile($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($changeSet->getRemovedFiles() as $file) {
|
||||||
|
$this->addRemovedFile($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($changeSet->getChangedClasses() as $class) {
|
||||||
|
$this->addChangedClass($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($changeSet->getRemovedClasses() as $class) {
|
||||||
|
$this->addRemovedClass($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChangedFiles(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->changedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemovedFiles(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->removedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChangedClasses(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->changedClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemovedClasses(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->removedClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasChanges(): bool
|
||||||
|
{
|
||||||
|
return $this->changedFiles !== []
|
||||||
|
|| $this->removedFiles !== []
|
||||||
|
|| $this->changedClasses !== []
|
||||||
|
|| $this->removedClasses !== [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'changed_files' => $this->getChangedFiles(),
|
||||||
|
'removed_files' => $this->getRemovedFiles(),
|
||||||
|
'changed_classes' => $this->getChangedClasses(),
|
||||||
|
'removed_classes' => $this->getRemovedClasses(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,6 +153,60 @@ class Container implements ContainerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function forgetReflection(string $className): void
|
||||||
|
{
|
||||||
|
unset($this->_reflection[$className]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function forgetMethodParams(string $className, ?string $method = null): void
|
||||||
|
{
|
||||||
|
if (!isset($this->_parameters[$className])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($method === null) {
|
||||||
|
unset($this->_parameters[$className]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->_parameters[$className][$method]);
|
||||||
|
if ($this->_parameters[$className] === []) {
|
||||||
|
unset($this->_parameters[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function forgetClass(string $className): void
|
||||||
|
{
|
||||||
|
unset($this->_singletons[$className]);
|
||||||
|
$this->forgetReflection($className);
|
||||||
|
$this->forgetMethodParams($className);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function forgetNamespace(string $prefix): void
|
||||||
|
{
|
||||||
|
foreach (array_keys($this->_singletons) as $className) {
|
||||||
|
if ($className !== ContainerInterface::class && str_starts_with($className, $prefix)) {
|
||||||
|
unset($this->_singletons[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($this->_reflection) as $className) {
|
||||||
|
if (str_starts_with($className, $prefix)) {
|
||||||
|
unset($this->_reflection[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($this->_parameters) as $className) {
|
||||||
|
if (str_starts_with($className, $prefix)) {
|
||||||
|
unset($this->_parameters[$className]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $className
|
* @param string $className
|
||||||
* @param array $construct
|
* @param array $construct
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Kiri\Di;
|
||||||
|
|
||||||
|
class HotReloadState
|
||||||
|
{
|
||||||
|
private const MAX_AGE_SECONDS = 30;
|
||||||
|
|
||||||
|
public function store(array $changedFiles): void
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'timestamp' => time(),
|
||||||
|
'changed_files' => array_values(array_unique(array_map([$this, 'normalizePath'], array_filter($changedFiles)))),
|
||||||
|
];
|
||||||
|
|
||||||
|
$directory = dirname($this->getFilePath());
|
||||||
|
if (!is_dir($directory)) {
|
||||||
|
mkdir($directory, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($this->getFilePath(), json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function consume(): array
|
||||||
|
{
|
||||||
|
return $this->peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function peek(): array
|
||||||
|
{
|
||||||
|
$file = $this->getFilePath();
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode((string)file_get_contents($file), true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = (int)($data['timestamp'] ?? 0);
|
||||||
|
if ($timestamp < 1 || (time() - $timestamp) > self::MAX_AGE_SECONDS) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = is_array($data['changed_files'] ?? null) ? $data['changed_files'] : [];
|
||||||
|
return array_values(array_unique(array_map([$this, 'normalizePath'], $files)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFilePath(): string
|
||||||
|
{
|
||||||
|
$basePath = $this->normalizePath($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
|
||||||
|
$runtimePath = defined('APP_PATH')
|
||||||
|
? rtrim(str_replace('\\', '/', APP_PATH), '/') . '/storage/.kiri-hot-reload/'
|
||||||
|
: sys_get_temp_dir() . '/kiri-hot-reload/';
|
||||||
|
|
||||||
|
return $runtimePath . md5($basePath) . '.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizePath(string $path): string
|
||||||
|
{
|
||||||
|
$resolved = realpath($path) ?: $path;
|
||||||
|
return str_replace('\\', '/', $resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Kiri\Di;
|
||||||
|
|
||||||
|
class ScanManifest
|
||||||
|
{
|
||||||
|
private array $entries = [];
|
||||||
|
|
||||||
|
public function set(string $path, int $mtime, array $classes): void
|
||||||
|
{
|
||||||
|
$this->entries[$path] = [
|
||||||
|
'mtime' => $mtime,
|
||||||
|
'classes' => array_values(array_unique($classes)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has(string $path): bool
|
||||||
|
{
|
||||||
|
return isset($this->entries[$path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMtime(string $path): ?int
|
||||||
|
{
|
||||||
|
return $this->entries[$path]['mtime'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClasses(string $path): array
|
||||||
|
{
|
||||||
|
return $this->entries[$path]['classes'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(string $path): array
|
||||||
|
{
|
||||||
|
$classes = $this->getClasses($path);
|
||||||
|
unset($this->entries[$path]);
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all(): array
|
||||||
|
{
|
||||||
|
return $this->entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paths(?string $prefix = null): array
|
||||||
|
{
|
||||||
|
if ($prefix === null) {
|
||||||
|
return array_keys($this->entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_filter(array_keys($this->entries), fn(string $path) => str_starts_with($path, $prefix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromArray(array $entries): void
|
||||||
|
{
|
||||||
|
$this->entries = [];
|
||||||
|
foreach ($entries as $path => $entry) {
|
||||||
|
$mtime = (int)($entry['mtime'] ?? 0);
|
||||||
|
$classes = is_array($entry['classes'] ?? null) ? $entry['classes'] : [];
|
||||||
|
$this->set($path, $mtime, $classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+320
-183
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Kiri\Di;
|
namespace Kiri\Di;
|
||||||
|
|
||||||
use Kiri\Di\Inject\Container;
|
use DirectoryIterator;
|
||||||
use Kiri\Abstracts\Component;
|
use Kiri\Abstracts\Component;
|
||||||
|
use Kiri\Di\Inject\Container;
|
||||||
use Kiri\Di\Inject\Skip;
|
use Kiri\Di\Inject\Skip;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
use DirectoryIterator;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class Scanner extends Component
|
class Scanner extends Component
|
||||||
@@ -20,25 +20,34 @@ class Scanner extends Component
|
|||||||
|
|
||||||
public array $files = [];
|
public array $files = [];
|
||||||
|
|
||||||
private array $fileMtimes = [];
|
private array $fileMtimes = [];
|
||||||
private ?bool $hasOpcache = null;
|
|
||||||
|
private ?bool $hasOpcache = null;
|
||||||
|
|
||||||
private string $basePath;
|
private string $basePath;
|
||||||
|
|
||||||
|
private ScanManifest $manifest;
|
||||||
|
|
||||||
|
private ?ChangeSet $changeSet = null;
|
||||||
|
|
||||||
|
private array $visitedFiles = [];
|
||||||
|
|
||||||
private array $config = [
|
private array $config = [
|
||||||
'skip_patterns' => ['/vendor/', '/tests/', '/cache/', '/node_modules/'],
|
'skip_patterns' => ['/vendor/', '/tests/', '/cache/', '/node_modules/'],
|
||||||
'skip_directories' => [],
|
'skip_directories' => [],
|
||||||
'extensions' => ['php'],
|
'extensions' => ['php'],
|
||||||
'max_depth' => 20,
|
'max_depth' => 20,
|
||||||
'follow_links' => false,
|
'follow_links' => false,
|
||||||
'cache_enabled' => false,
|
'cache_enabled' => false,
|
||||||
'cache_ttl' => 3600,
|
'cache_ttl' => 3600,
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
'class_name_strategy' => 'auto', // 'auto', 'extract', 'rename', or 'both'
|
'class_name_strategy' => 'auto',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->basePath = $_SERVER['PWD'] ?? APP_PATH ?? getcwd();
|
$this->basePath = $this->normalizePath($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
|
||||||
|
$this->manifest = new ScanManifest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setConfig(array $config): void
|
public function setConfig(array $config): void
|
||||||
@@ -46,21 +55,70 @@ class Scanner extends Component
|
|||||||
$this->config = array_merge($this->config, $config);
|
$this->config = array_merge($this->config, $config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scan(string $path, ?string $cacheFile = null): void
|
public function scan(string $path, ?string $cacheFile = null): ChangeSet
|
||||||
{
|
{
|
||||||
|
$path = $this->normalizePath($path);
|
||||||
|
$this->changeSet = new ChangeSet();
|
||||||
|
$this->visitedFiles = [];
|
||||||
|
$cacheLoaded = false;
|
||||||
|
|
||||||
if ($this->config['cache_enabled']) {
|
if ($this->config['cache_enabled']) {
|
||||||
$cacheFile = $cacheFile ?? $this->getDefaultCacheFile();
|
$cacheFile = $cacheFile ?? $this->getDefaultCacheFile();
|
||||||
if ($this->loadFromCache($cacheFile, $path)) {
|
if ($this->loadFromCache($cacheFile, $path)) {
|
||||||
$this->processCachedFiles();
|
$this->replayManifest($path);
|
||||||
return;
|
return $this->changeSet;
|
||||||
}
|
}
|
||||||
|
$cacheLoaded = $this->manifest->all() !== [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->scanDirectory($path);
|
$this->scanDirectory($path);
|
||||||
|
$this->detectRemovedFiles($path);
|
||||||
|
|
||||||
|
if ($cacheLoaded) {
|
||||||
|
$this->replayManifest($path, $this->changeSet->getChangedFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->syncLegacyState();
|
||||||
|
|
||||||
if ($this->config['cache_enabled']) {
|
if ($this->config['cache_enabled']) {
|
||||||
$this->saveToCache($cacheFile);
|
$this->saveToCache($cacheFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->changeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scanFiles(array $files, ?string $scopePath = null, ?string $cacheFile = null): ChangeSet
|
||||||
|
{
|
||||||
|
$this->changeSet = new ChangeSet();
|
||||||
|
$this->visitedFiles = [];
|
||||||
|
$cacheLoaded = false;
|
||||||
|
|
||||||
|
if ($scopePath !== null) {
|
||||||
|
$scopePath = $this->normalizePath($scopePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->config['cache_enabled'] && $scopePath !== null) {
|
||||||
|
$cacheFile = $cacheFile ?? $this->getDefaultCacheFile();
|
||||||
|
$this->loadFromCache($cacheFile, $scopePath);
|
||||||
|
$cacheLoaded = $this->manifest->all() !== [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_values(array_unique($files)) as $file) {
|
||||||
|
$path = $this->normalizePath($file);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
$this->processFile($path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->markRemovedFile($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cacheLoaded && $scopePath !== null) {
|
||||||
|
$this->replayManifest($scopePath, $this->changeSet->getChangedFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->syncLegacyState();
|
||||||
|
return $this->changeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function scanDirectory(string $path, int $depth = 0): void
|
private function scanDirectory(string $path, int $depth = 0): void
|
||||||
@@ -81,8 +139,7 @@ class Scanner extends Component
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$realPath = $item->getRealPath();
|
$realPath = $this->normalizePath($item->getRealPath() ?: $item->getPathname());
|
||||||
|
|
||||||
if ($item->isDir()) {
|
if ($item->isDir()) {
|
||||||
if ($this->shouldSkipDirectory($realPath)) {
|
if ($this->shouldSkipDirectory($realPath)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -93,7 +150,10 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->scanDirectory($realPath, $depth + 1);
|
$this->scanDirectory($realPath, $depth + 1);
|
||||||
} elseif ($item->isFile()) {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($item->isFile()) {
|
||||||
$this->processFile($realPath);
|
$this->processFile($realPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,14 +166,13 @@ class Scanner extends Component
|
|||||||
|
|
||||||
private function shouldSkipDirectory(string $path): bool
|
private function shouldSkipDirectory(string $path): bool
|
||||||
{
|
{
|
||||||
$path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
$path = rtrim($this->normalizePath($path), '/') . '/';
|
||||||
|
|
||||||
$skipDirs = array_merge(
|
$skipDirs = array_merge(
|
||||||
$this->config['skip_directories'],
|
$this->config['skip_directories'],
|
||||||
config('site.scanner.skip', [])
|
config('site.scanner.skip', [])
|
||||||
);
|
);
|
||||||
|
|
||||||
if (in_array($path, $skipDirs)) {
|
if (in_array($path, $skipDirs, true)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,25 +187,34 @@ class Scanner extends Component
|
|||||||
|
|
||||||
private function processFile(string $path): void
|
private function processFile(string $path): void
|
||||||
{
|
{
|
||||||
if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $this->config['extensions'])) {
|
$path = $this->normalizePath($path);
|
||||||
return;
|
if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $this->config['extensions'], true)) {
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array($path, $this->files)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->visitedFiles[$path] = true;
|
||||||
if (!$this->shouldProcessFile($path)) {
|
if (!$this->shouldProcessFile($path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->files[] = $path;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->loadAndParseFile($path);
|
$oldClasses = $this->manifest->getClasses($path);
|
||||||
|
$this->invalidateClasses($oldClasses);
|
||||||
|
$newClasses = $this->loadAndParseFile($path);
|
||||||
|
|
||||||
|
$this->manifest->set($path, (int)filemtime($path), $newClasses);
|
||||||
|
$this->changeSet?->addChangedFile($path);
|
||||||
|
|
||||||
|
foreach (array_diff($oldClasses, $newClasses) as $class) {
|
||||||
|
$this->changeSet?->addRemovedClass($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($newClasses as $class) {
|
||||||
|
$this->changeSet?->addChangedClass($class);
|
||||||
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logError($e, [
|
$this->logError($e, [
|
||||||
'file' => $path,
|
'file' => $path,
|
||||||
'action' => 'process_file',
|
'action' => 'process_file',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -162,55 +230,51 @@ class Scanner extends Component
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mtime = filemtime($path);
|
$mtime = (int)filemtime($path);
|
||||||
|
$cachedMtime = $this->manifest->getMtime($path);
|
||||||
|
|
||||||
if (!isset($this->fileMtimes[$path]) || $this->fileMtimes[$path] < $mtime) {
|
return $cachedMtime === null || $cachedMtime < $mtime;
|
||||||
$this->fileMtimes[$path] = $mtime;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadAndParseFile(string $path): void
|
private function loadAndParseFile(string $path): array
|
||||||
{
|
{
|
||||||
$this->optimizeWithOpcache($path);
|
$this->optimizeWithOpcache($path);
|
||||||
|
|
||||||
require_once $path;
|
require_once $path;
|
||||||
|
|
||||||
// 尝试获取类名
|
$classes = $this->getClassNamesForFile($path);
|
||||||
$class = $this->getClassNameFromPath($path);
|
foreach ($classes as $class) {
|
||||||
if ($class && class_exists($class)) {
|
if (class_exists($class)) {
|
||||||
$this->analyzeClass($class);
|
$this->analyzeClass($class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getClassNamesForFile(string $path): array
|
||||||
* 从文件路径获取类名(支持多种策略)
|
|
||||||
*/
|
|
||||||
private function getClassNameFromPath(string $path): ?string
|
|
||||||
{
|
{
|
||||||
$strategy = $this->config['class_name_strategy'];
|
$strategy = $this->config['class_name_strategy'];
|
||||||
|
$classes = [];
|
||||||
|
|
||||||
// 移除基础路径前缀,类似原代码逻辑
|
if (in_array($strategy, ['auto', 'extract', 'both'], true)) {
|
||||||
$relativePath = str_replace($this->basePath, '', $path);
|
$class = $this->extractClassNameFromFile($path);
|
||||||
$relativePath = str_replace('.php', '', $relativePath);
|
if ($class !== null) {
|
||||||
|
$classes[] = $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->renamePathToClassName($relativePath);
|
if ($classes === [] || in_array($strategy, ['rename', 'both'], true)) {
|
||||||
|
$classes[] = $this->renamePathToClassName($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter($classes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否可以安全地从文件内容提取类名
|
|
||||||
*/
|
|
||||||
private function canExtractClassName(): bool
|
private function canExtractClassName(): bool
|
||||||
{
|
{
|
||||||
// 如果有tokenizer扩展,优先使用它
|
|
||||||
return function_exists('token_get_all');
|
return function_exists('token_get_all');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从文件内容提取类名(原代码没有的功能)
|
|
||||||
*/
|
|
||||||
private function extractClassNameFromFile(string $path): ?string
|
private function extractClassNameFromFile(string $path): ?string
|
||||||
{
|
{
|
||||||
if (!$this->canExtractClassName()) {
|
if (!$this->canExtractClassName()) {
|
||||||
@@ -218,7 +282,7 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$content = @file_get_contents($path);
|
$content = @file_get_contents($path);
|
||||||
if (!$content) {
|
if ($content === false || $content === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,64 +291,59 @@ class Scanner extends Component
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$namespace = '';
|
$namespace = '';
|
||||||
$class = '';
|
$class = '';
|
||||||
$collectingNamespace = false;
|
$collectingNamespace = false;
|
||||||
$collectingClass = false;
|
$collectingClass = false;
|
||||||
|
$previousToken = null;
|
||||||
|
|
||||||
var_dump($tokens[1]);
|
|
||||||
foreach ($tokens as $token) {
|
foreach ($tokens as $token) {
|
||||||
if (is_array($token)) {
|
if (is_array($token)) {
|
||||||
|
$text = $token[1];
|
||||||
|
|
||||||
if ($token[0] === T_NAMESPACE) {
|
if ($token[0] === T_NAMESPACE) {
|
||||||
$namespace = '';
|
$namespace = '';
|
||||||
$collectingNamespace = true;
|
$collectingNamespace = true;
|
||||||
} elseif ($collectingNamespace && $token[0] === T_STRING) {
|
} elseif ($collectingNamespace && in_array($token[0], [T_STRING, T_NAME_QUALIFIED, T_NS_SEPARATOR], true)) {
|
||||||
$namespace .= $token[1];
|
$namespace .= $text;
|
||||||
} elseif ($collectingNamespace && $token[0] === T_NS_SEPARATOR) {
|
|
||||||
$namespace .= '\\';
|
|
||||||
} elseif ($collectingNamespace && $token[0] === T_WHITESPACE) {
|
} elseif ($collectingNamespace && $token[0] === T_WHITESPACE) {
|
||||||
continue;
|
} elseif (in_array($token[0], [T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], true) && $previousToken !== T_DOUBLE_COLON && $previousToken !== T_NEW) {
|
||||||
} elseif ($token[0] === T_CLASS) {
|
|
||||||
$collectingClass = true;
|
$collectingClass = true;
|
||||||
} elseif ($collectingClass && $token[0] === T_STRING) {
|
} elseif ($collectingClass && $token[0] === T_STRING) {
|
||||||
$class = $token[1];
|
$class = $text;
|
||||||
break;
|
break;
|
||||||
} elseif ($collectingNamespace) {
|
} elseif ($collectingNamespace) {
|
||||||
$collectingNamespace = false;
|
$collectingNamespace = false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if ($collectingNamespace && ($token === ';' || $token === '{')) {
|
if ($token[0] !== T_WHITESPACE) {
|
||||||
$collectingNamespace = false;
|
$previousToken = $token[0];
|
||||||
}
|
|
||||||
if ($token === '{') {
|
|
||||||
$collectingClass = false;
|
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($collectingNamespace && ($token === ';' || $token === '{')) {
|
||||||
|
$collectingNamespace = false;
|
||||||
|
}
|
||||||
|
if ($token === '{') {
|
||||||
|
$collectingClass = false;
|
||||||
|
}
|
||||||
|
$previousToken = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$class) {
|
if ($class === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $namespace ? $namespace . '\\' . $class : $class;
|
return $namespace !== '' ? $namespace . '\\' . $class : $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将路径重命名为类名(原代码的逻辑)
|
|
||||||
*/
|
|
||||||
private function renamePathToClassName(string $path): string
|
private function renamePathToClassName(string $path): string
|
||||||
{
|
{
|
||||||
// 清理路径,移除开头和结尾的斜杠
|
$relativePath = str_replace($this->basePath, '', $this->normalizePath($path));
|
||||||
$path = trim($path, '/\\');
|
$relativePath = str_replace('.php', '', $relativePath);
|
||||||
|
$parts = explode('/', trim($relativePath, '/\\'));
|
||||||
// 分割路径部分
|
$parts = array_values(array_filter($parts, fn(string $part) => $part !== ''));
|
||||||
$parts = explode('/', str_replace('\\', '/', $path));
|
|
||||||
|
|
||||||
// 过滤空值并对每个部分应用ucfirst
|
|
||||||
$parts = array_filter($parts, function ($part) {
|
|
||||||
return !empty($part);
|
|
||||||
});
|
|
||||||
|
|
||||||
$parts = array_map('ucfirst', $parts);
|
$parts = array_map('ucfirst', $parts);
|
||||||
|
|
||||||
return implode('\\', $parts);
|
return implode('\\', $parts);
|
||||||
@@ -293,21 +352,22 @@ class Scanner extends Component
|
|||||||
private function optimizeWithOpcache(string $path): void
|
private function optimizeWithOpcache(string $path): void
|
||||||
{
|
{
|
||||||
if ($this->hasOpcache === null) {
|
if ($this->hasOpcache === null) {
|
||||||
$this->hasOpcache = function_exists('opcache_invalidate')
|
$this->hasOpcache = function_exists('opcache_invalidate') && function_exists('opcache_compile_file');
|
||||||
&& function_exists('opcache_compile_file');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->hasOpcache) {
|
if (!$this->hasOpcache) {
|
||||||
try {
|
return;
|
||||||
opcache_invalidate($path, true);
|
}
|
||||||
opcache_compile_file($path);
|
|
||||||
} catch (Throwable $e) {
|
try {
|
||||||
if ($this->config['debug']) {
|
opcache_invalidate($path, true);
|
||||||
$this->logError($e, [
|
opcache_compile_file($path);
|
||||||
'file' => $path,
|
} catch (Throwable $e) {
|
||||||
'action' => 'opcache_optimize',
|
if ($this->config['debug']) {
|
||||||
]);
|
$this->logError($e, [
|
||||||
}
|
'file' => $path,
|
||||||
|
'action' => 'opcache_optimize',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,11 +376,7 @@ class Scanner extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$reflect = $this->container->getReflectionClass($class);
|
$reflect = $this->container->getReflectionClass($class);
|
||||||
|
if (!$reflect->isInstantiable() || $reflect->isTrait() || $reflect->isEnum() || $reflect->isInterface()) {
|
||||||
if (!$reflect->isInstantiable() ||
|
|
||||||
$reflect->isTrait() ||
|
|
||||||
$reflect->isEnum() ||
|
|
||||||
$reflect->isInterface()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,10 +385,9 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->analyzeClassMethods($reflect, $class);
|
$this->analyzeClassMethods($reflect, $class);
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logError($e, [
|
$this->logError($e, [
|
||||||
'class' => $class,
|
'class' => $class,
|
||||||
'action' => 'analyze_class',
|
'action' => 'analyze_class',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -340,13 +395,10 @@ class Scanner extends Component
|
|||||||
|
|
||||||
private function shouldSkipClass(ReflectionClass $reflect): bool
|
private function shouldSkipClass(ReflectionClass $reflect): bool
|
||||||
{
|
{
|
||||||
$attributes = array_map(
|
$attributes = array_map(fn($attr) => $attr->getName(), $reflect->getAttributes());
|
||||||
fn($attr) => $attr->getName(),
|
|
||||||
$reflect->getAttributes()
|
|
||||||
);
|
|
||||||
|
|
||||||
return in_array(Skip::class, $attributes, true) ||
|
return in_array(Skip::class, $attributes, true)
|
||||||
in_array(\Attribute::class, $attributes, true);
|
|| in_array(\Attribute::class, $attributes, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function analyzeClassMethods(ReflectionClass $reflect, string $class): void
|
private function analyzeClassMethods(ReflectionClass $reflect, string $class): void
|
||||||
@@ -364,23 +416,21 @@ class Scanner extends Component
|
|||||||
{
|
{
|
||||||
foreach ($method->getAttributes() as $attribute) {
|
foreach ($method->getAttributes() as $attribute) {
|
||||||
$attributeName = $attribute->getName();
|
$attributeName = $attribute->getName();
|
||||||
|
|
||||||
if (!class_exists($attributeName)) {
|
if (!class_exists($attributeName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$instance = $attribute->newInstance();
|
$instance = $attribute->newInstance();
|
||||||
|
|
||||||
if (method_exists($instance, 'dispatch')) {
|
if (method_exists($instance, 'dispatch')) {
|
||||||
$instance->dispatch($class, $method->getName());
|
$instance->dispatch($class, $method->getName());
|
||||||
}
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$this->logError($e, [
|
$this->logError($e, [
|
||||||
'class' => $class,
|
'class' => $class,
|
||||||
'method' => $method->getName(),
|
'method' => $method->getName(),
|
||||||
'attribute' => $attributeName,
|
'attribute' => $attributeName,
|
||||||
'action' => 'process_attribute',
|
'action' => 'process_attribute',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +438,9 @@ class Scanner extends Component
|
|||||||
|
|
||||||
private function getDefaultCacheFile(): string
|
private function getDefaultCacheFile(): string
|
||||||
{
|
{
|
||||||
$cacheDir = sys_get_temp_dir() . '/kiri-scanner-cache/';
|
$cacheDir = defined('APP_PATH')
|
||||||
|
? rtrim(str_replace('\\', '/', APP_PATH), '/') . '/storage/.kiri-scanner-cache/'
|
||||||
|
: sys_get_temp_dir() . '/kiri-scanner-cache/';
|
||||||
if (!is_dir($cacheDir)) {
|
if (!is_dir($cacheDir)) {
|
||||||
mkdir($cacheDir, 0755, true);
|
mkdir($cacheDir, 0755, true);
|
||||||
}
|
}
|
||||||
@@ -407,33 +459,34 @@ class Scanner extends Component
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->hasDirectoryChanged($path, $cacheFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents($cacheFile), true);
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
if (!is_array($data) || !isset($data['files'], $data['mtimes'])) {
|
if (!is_array($data) || !isset($data['manifest'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->files = $data['files'];
|
$this->manifest->fromArray($data['manifest']);
|
||||||
$this->fileMtimes = $data['mtimes'];
|
$this->syncLegacyState();
|
||||||
|
|
||||||
return true;
|
return !$this->hasDirectoryChanged($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function hasDirectoryChanged(string $path, string $cacheFile): bool
|
private function hasDirectoryChanged(string $path): bool
|
||||||
{
|
{
|
||||||
$cacheMtime = filemtime($cacheFile);
|
$currentFiles = $this->collectFiles($path);
|
||||||
|
$cachedFiles = $this->manifest->paths(rtrim($path, '/\\') . '/');
|
||||||
|
|
||||||
$dirIterator = new DirectoryIterator($path);
|
sort($currentFiles);
|
||||||
foreach ($dirIterator as $item) {
|
sort($cachedFiles);
|
||||||
if ($item->isDot()) {
|
if ($currentFiles !== $cachedFiles) {
|
||||||
continue;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($currentFiles as $file) {
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$itemPath = $item->getRealPath();
|
if ($this->manifest->getMtime($file) !== (int)filemtime($file)) {
|
||||||
if (filemtime($itemPath) > $cacheMtime) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,21 +497,19 @@ class Scanner extends Component
|
|||||||
private function saveToCache(string $cacheFile): void
|
private function saveToCache(string $cacheFile): void
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'files' => $this->files,
|
'manifest' => $this->manifest->all(),
|
||||||
'mtimes' => $this->fileMtimes,
|
|
||||||
'timestamp' => time(),
|
'timestamp' => time(),
|
||||||
'version' => '1.1',
|
'version' => '2.0',
|
||||||
];
|
];
|
||||||
|
|
||||||
file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT));
|
file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
$this->cleanupOldCacheFiles(dirname($cacheFile));
|
$this->cleanupOldCacheFiles(dirname($cacheFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanupOldCacheFiles(string $cacheDir): void
|
private function cleanupOldCacheFiles(string $cacheDir): void
|
||||||
{
|
{
|
||||||
$files = glob($cacheDir . '/scanner_*.json');
|
$files = glob($cacheDir . '/scanner_*.json');
|
||||||
$now = time();
|
$now = time();
|
||||||
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
if (filemtime($file) + (7 * 24 * 3600) < $now) {
|
if (filemtime($file) + (7 * 24 * 3600) < $now) {
|
||||||
@@ -467,24 +518,15 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processCachedFiles(): void
|
|
||||||
{
|
|
||||||
foreach ($this->files as $file) {
|
|
||||||
if ($this->shouldProcessFile($file)) {
|
|
||||||
$this->loadAndParseFile($file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function logError(Throwable $e, array $context = []): void
|
private function logError(Throwable $e, array $context = []): void
|
||||||
{
|
{
|
||||||
$fullContext = array_merge($context, [
|
$fullContext = array_merge($context, [
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
'code' => $e->getCode(),
|
'code' => $e->getCode(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'trace' => $e->getTraceAsString(),
|
'trace' => $e->getTraceAsString(),
|
||||||
'time' => date('Y-m-d H:i:s'),
|
'time' => date('Y-m-d H:i:s'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (function_exists('error')) {
|
if (function_exists('error')) {
|
||||||
@@ -495,58 +537,153 @@ class Scanner extends Component
|
|||||||
|
|
||||||
if ($this->config['debug'] && php_sapi_name() === 'cli') {
|
if ($this->config['debug'] && php_sapi_name() === 'cli') {
|
||||||
echo "Scanner Error: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}\n";
|
echo "Scanner Error: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}\n";
|
||||||
if (!empty($context)) {
|
if ($context !== []) {
|
||||||
echo 'Context: ' . json_encode($context) . "\n";
|
echo 'Context: ' . json_encode($context) . "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 原代码的load_directory方法(保持兼容性)
|
|
||||||
*/
|
|
||||||
public function load_directory(string $path): void
|
public function load_directory(string $path): void
|
||||||
{
|
{
|
||||||
$this->scan($path);
|
$this->scan($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 原代码的parseFile方法(保持兼容性)
|
|
||||||
*/
|
|
||||||
protected function parseFile($file): void
|
protected function parseFile($file): void
|
||||||
{
|
{
|
||||||
// 这里$file是相对路径,需要转换为绝对路径
|
$absolutePath = $this->normalizePath($this->basePath . '/' . ltrim((string)$file, '/')) . '.php';
|
||||||
$absolutePath = $this->basePath . ltrim($file, '/') . '.php';
|
|
||||||
|
|
||||||
if (file_exists($absolutePath)) {
|
if (file_exists($absolutePath)) {
|
||||||
$this->processFile($absolutePath);
|
$this->processFile($absolutePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 原代码的skipNames方法(保持兼容性)
|
|
||||||
*/
|
|
||||||
protected function skipNames(ReflectionClass $reflect): array
|
protected function skipNames(ReflectionClass $reflect): array
|
||||||
{
|
{
|
||||||
return array_map(
|
return array_map(fn($attr) => $attr->getName(), $reflect->getAttributes());
|
||||||
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()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reset(): void
|
public function reset(): void
|
||||||
{
|
{
|
||||||
$this->files = [];
|
$this->files = [];
|
||||||
$this->fileMtimes = [];
|
$this->fileMtimes = [];
|
||||||
$this->hasOpcache = null;
|
$this->hasOpcache = null;
|
||||||
|
$this->manifest = new ScanManifest();
|
||||||
|
$this->changeSet = null;
|
||||||
|
$this->visitedFiles = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function detectRemovedFiles(string $path): void
|
||||||
|
{
|
||||||
|
$prefix = rtrim($path, '/\\') . '/';
|
||||||
|
foreach ($this->manifest->paths($prefix) as $file) {
|
||||||
|
if (!isset($this->visitedFiles[$file])) {
|
||||||
|
$this->markRemovedFile($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function markRemovedFile(string $path): void
|
||||||
|
{
|
||||||
|
if (!$this->manifest->has($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = $this->manifest->remove($path);
|
||||||
|
$this->invalidateClasses($classes);
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
$this->changeSet?->addRemovedClass($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->changeSet?->addRemovedFile($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function syncLegacyState(): void
|
||||||
|
{
|
||||||
|
$this->files = array_keys($this->manifest->all());
|
||||||
|
$this->fileMtimes = [];
|
||||||
|
foreach ($this->manifest->all() as $path => $entry) {
|
||||||
|
$this->fileMtimes[$path] = (int)$entry['mtime'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function collectFiles(string $path, int $depth = 0): array
|
||||||
|
{
|
||||||
|
if ($depth > $this->config['max_depth']) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
try {
|
||||||
|
$dir = new DirectoryIterator($path);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($dir as $item) {
|
||||||
|
if ($this->shouldSkipItem($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$realPath = $this->normalizePath($item->getRealPath() ?: $item->getPathname());
|
||||||
|
if ($item->isDir()) {
|
||||||
|
if ($this->shouldSkipDirectory($realPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item->isLink() && !$this->config['follow_links']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$files = array_merge($files, $this->collectFiles($realPath, $depth + 1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($item->isFile() && in_array(pathinfo($realPath, PATHINFO_EXTENSION), $this->config['extensions'], true)) {
|
||||||
|
$files[] = $realPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizePath(string $path): string
|
||||||
|
{
|
||||||
|
$resolved = realpath($path) ?: $path;
|
||||||
|
return str_replace('\\', '/', $resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function replayManifest(string $path, array $skipPaths = []): void
|
||||||
|
{
|
||||||
|
$skip = array_fill_keys(array_map([$this, 'normalizePath'], $skipPaths), true);
|
||||||
|
$prefix = rtrim($path, '/\\') . '/';
|
||||||
|
|
||||||
|
foreach ($this->manifest->paths($prefix) as $file) {
|
||||||
|
if (isset($skip[$file])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->manifest->getClasses($file) as $class) {
|
||||||
|
if (class_exists($class)) {
|
||||||
|
$this->analyzeClass($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function invalidateClasses(array $classes): void
|
||||||
|
{
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
if (method_exists($this->container, 'forgetClass')) {
|
||||||
|
$this->container->forgetClass($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user