Files
michaelschiemer/src/Framework/Core/ClassParser.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

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;
}
}