- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
221 lines
6.1 KiB
PHP
221 lines
6.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core;
|
|
|
|
/**
|
|
* Parser für PHP-Klassendateien ohne externe Abhängigkeiten
|
|
*/
|
|
final class ClassParser
|
|
{
|
|
private static array $classCache = [];
|
|
|
|
private static array $tokenCache = [];
|
|
|
|
/**
|
|
* Gibt alle Klassen, Interfaces und Traits in einer Datei zurück
|
|
*
|
|
* @param string $file Pfad zur PHP-Datei
|
|
* @return array Namen der in der Datei enthaltenen Klassen
|
|
*/
|
|
public static function getClassesInFile(string $file): array
|
|
{
|
|
// Prüfe Cache
|
|
$cacheKey = md5($file . filemtime($file));
|
|
if (isset(self::$classCache[$cacheKey])) {
|
|
return self::$classCache[$cacheKey];
|
|
}
|
|
|
|
$classes = [];
|
|
$namespace = '';
|
|
$tokens = self::getTokens($file);
|
|
|
|
if ($tokens === null) {
|
|
return [];
|
|
}
|
|
|
|
$count = count($tokens);
|
|
$i = 0;
|
|
|
|
// Durchlaufe alle Tokens
|
|
while ($i < $count) {
|
|
if ($tokens[$i][0] === T_NAMESPACE) {
|
|
// Namespace finden
|
|
$namespace = self::parseNamespace($tokens, $i);
|
|
#debug("Found namespace: '$namespace' in file: $file");
|
|
} elseif ($tokens[$i][0] === T_CLASS || $tokens[$i][0] === T_INTERFACE || $tokens[$i][0] === T_TRAIT) {
|
|
// Klasse/Interface/Trait finden
|
|
$className = self::parseClassName($tokens, $i);
|
|
if ($className !== null) {
|
|
#debug("Found class: '$className', current namespace: '$namespace'");
|
|
|
|
if (empty($namespace)) {
|
|
// Fallback: Versuche Namespace aus Dateiinhalt zu extrahieren
|
|
$namespace = self::extractNamespaceFromContent($file);
|
|
#debug("Fallback namespace from content: '$namespace'");
|
|
}
|
|
|
|
if (! empty($namespace)) {
|
|
$fullClassName = '\\' . trim($namespace, '\\') . '\\' . $className;
|
|
} else {
|
|
$fullClassName = '\\' . $className;
|
|
}
|
|
|
|
#debug("Final class name: '$fullClassName'");
|
|
$classes[] = $fullClassName;
|
|
}
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
// Ergebnis cachen
|
|
self::$classCache[$cacheKey] = $classes;
|
|
|
|
#debug($classes);
|
|
|
|
return $classes;
|
|
}
|
|
|
|
/**
|
|
* Parst den Namespace aus den Tokens
|
|
*/
|
|
private static function parseNamespace(array $tokens, int &$i): string
|
|
{
|
|
$namespace = '';
|
|
$originalI = $i;
|
|
$i++; // T_NAMESPACE Token überspringen
|
|
|
|
#debug("Starting namespace parse at token $i, T_NAMESPACE was at $originalI");
|
|
|
|
// Debug: Zeige die nächsten paar Tokens
|
|
for ($debugI = $i; $debugI < min($i + 10, count($tokens)); $debugI++) {
|
|
$token = $tokens[$debugI];
|
|
}
|
|
|
|
while ($i < count($tokens)) {
|
|
$token = $tokens[$i];
|
|
|
|
if (is_array($token)) {
|
|
|
|
if ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || $token[0] === T_NAME_QUALIFIED) {
|
|
$namespace .= $token[1];
|
|
} elseif ($token[0] === T_WHITESPACE) {
|
|
// Whitespace ignorieren, aber weitermachen
|
|
} else {
|
|
#debug("Breaking on token: " . token_name($token[0]));
|
|
break;
|
|
}
|
|
} else {
|
|
if ($token === ';' || $token === '{') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
$result = trim($namespace, '\\');
|
|
|
|
#debug("Final parsed namespace: '$result'");
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Parst den Klassennamen aus den Tokens
|
|
*/
|
|
private static function parseClassName(array $tokens, int &$i): ?string
|
|
{
|
|
$i += 1; // T_CLASS/T_INTERFACE/T_TRAIT Token überspringen
|
|
|
|
// Suche nach dem Namen (nächstes T_STRING Token)
|
|
while ($i < count($tokens)) {
|
|
if ($tokens[$i][0] === T_STRING) {
|
|
return $tokens[$i][1];
|
|
} elseif ($tokens[$i] === '{' || $tokens[$i] === ';') {
|
|
// Unerwartetes Ende ohne Klassennamen
|
|
return null;
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Liest Tokens aus einer Datei mit Caching
|
|
*/
|
|
private static function getTokens(string $file): ?array
|
|
{
|
|
if (! file_exists($file)) {
|
|
return null;
|
|
}
|
|
|
|
$cacheKey = md5($file . filemtime($file));
|
|
if (isset(self::$tokenCache[$cacheKey])) {
|
|
return self::$tokenCache[$cacheKey];
|
|
}
|
|
|
|
$code = file_get_contents($file);
|
|
if ($code === false) {
|
|
return null;
|
|
}
|
|
|
|
$tokens = token_get_all($code);
|
|
self::$tokenCache[$cacheKey] = $tokens;
|
|
|
|
// Cache-Größe begrenzen
|
|
if (count(self::$tokenCache) > 100) {
|
|
// Entferne ältesten Eintrag
|
|
array_shift(self::$tokenCache);
|
|
}
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* Extrahiert Namespace aus Dateiinhalt als Fallback
|
|
*/
|
|
private static function extractNamespaceFromContent(string $file): string
|
|
{
|
|
$content = file_get_contents($file);
|
|
if ($content === false) {
|
|
return '';
|
|
}
|
|
|
|
// Regex für namespace-Deklaration
|
|
if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
|
|
return trim($matches[1]);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Leert den internen Cache
|
|
*/
|
|
public static function clearCache(): void
|
|
{
|
|
self::$classCache = [];
|
|
self::$tokenCache = [];
|
|
}
|
|
|
|
/**
|
|
* Extrahiert den Klassennamen (mit Namespace) aus einer PHP-Datei
|
|
*/
|
|
public function getClassNameFromFile(string $file): ?string
|
|
{
|
|
$contents = file_get_contents($file);
|
|
if (
|
|
preg_match('#namespace\s+([^;]+);#', $contents, $nsMatch)
|
|
&& preg_match('/class\s+(\w+)/', $contents, $classMatch)
|
|
) {
|
|
return trim($nsMatch[1]) . '\\' . trim($classMatch[1]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|