eee
This commit is contained in:
+147
-5
@@ -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,6 +83,9 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->syncLegacyState();
|
$this->syncLegacyState();
|
||||||
|
if ($this->config['cache_enabled'] && $cacheFile !== null) {
|
||||||
|
$this->saveToCache($cacheFile);
|
||||||
|
}
|
||||||
return $this->changeSet;
|
return $this->changeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +120,9 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->syncLegacyState();
|
$this->syncLegacyState();
|
||||||
|
if ($this->config['cache_enabled'] && $scopePath !== null && $cacheFile !== null) {
|
||||||
|
$this->saveToCache($cacheFile);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->changeSet;
|
return $this->changeSet;
|
||||||
}
|
}
|
||||||
@@ -236,11 +246,28 @@ class Scanner extends Component
|
|||||||
{
|
{
|
||||||
$this->optimizeWithOpcache($path);
|
$this->optimizeWithOpcache($path);
|
||||||
|
|
||||||
$before = get_declared_classes();
|
self::$currentFile = $path;
|
||||||
require_once $path;
|
$classes = $this->parseDeclaredClasses($path);
|
||||||
$after = get_declared_classes();
|
$loadFile = $classes === [] ? (bool)$this->config['require_files_without_classes'] : false;
|
||||||
|
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
if (!$this->isDeclaredSymbol($class)) {
|
||||||
|
$loadFile = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($loadFile) {
|
||||||
|
$before = $this->getDeclaredSymbols();
|
||||||
|
require_once $path;
|
||||||
|
$after = $this->getDeclaredSymbols();
|
||||||
|
$classes = array_values(array_unique(array_merge(
|
||||||
|
$classes,
|
||||||
|
array_diff($after, $before)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
$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);
|
||||||
@@ -248,10 +275,16 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $classes;
|
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');
|
||||||
}
|
}
|
||||||
@@ -273,6 +306,110 @@ class Scanner extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function parseDeclaredClasses(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)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->parseClassLikeName($tokens, $i + 1);
|
||||||
|
if ($name === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes[] = ltrim($namespace . '\\' . $name, '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($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 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 {
|
||||||
@@ -493,6 +630,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, '/\\') . '/';
|
||||||
|
|||||||
Reference in New Issue
Block a user