, * functions: array, * error: string|null * } */ public function parse(string $filePath): array { $result = [ 'namespace' => null, 'classes' => [], 'functions' => [], 'error' => null, ]; if (!file_exists($filePath) || !is_readable($filePath)) { $result['error'] = 'Datei nicht lesbar'; return $result; } $content = file_get_contents($filePath); if ($content === false) { $result['error'] = 'Datei konnte nicht gelesen werden'; return $result; } try { $tokens = @token_get_all($content); } catch (\Throwable $e) { $result['error'] = 'Token-Fehler: ' . $e->getMessage(); return $result; } $result['namespace'] = $this->extractNamespace($tokens); $result['classes'] = $this->extractClasses($tokens); $result['functions'] = $this->extractFunctions($tokens); return $result; } /** * @param array $tokens */ private function extractNamespace(array $tokens): ?string { $namespace = ''; $capturing = false; foreach ($tokens as $token) { if (is_array($token)) { if ($token[0] === T_NAMESPACE) { $capturing = true; continue; } if ($capturing) { if ($token[0] === T_NAME_QUALIFIED || $token[0] === T_STRING) { $namespace .= $token[1]; } elseif ($token[0] === T_NS_SEPARATOR) { $namespace .= '\\'; } } } elseif ($capturing && ($token === ';' || $token === '{')) { break; } } return $namespace !== '' ? $namespace : null; } /** * @param array $tokens * @return array */ private function extractClasses(array $tokens): array { $classes = []; $count = count($tokens); for ($i = 0; $i < $count; $i++) { $token = $tokens[$i]; if (!is_array($token)) { continue; } $type = match ($token[0]) { T_CLASS => 'class', T_INTERFACE => 'interface', T_TRAIT => 'trait', T_ENUM => 'enum', default => null, }; if ($type === null) { continue; } // Prüfen ob es kein anonymes Class-Statement ist $prevIndex = $this->findPrevNonWhitespace($tokens, $i); if ($prevIndex !== null && is_array($tokens[$prevIndex]) && $tokens[$prevIndex][0] === T_NEW) { continue; } // Klassennamen finden for ($j = $i + 1; $j < $count; $j++) { if (is_array($tokens[$j]) && $tokens[$j][0] === T_STRING) { $classes[] = [ 'name' => $tokens[$j][1], 'type' => $type, 'line' => $token[2], ]; break; } } } return $classes; } /** * @param array $tokens * @return array */ private function extractFunctions(array $tokens): array { $functions = []; $count = count($tokens); $braceDepth = 0; $inClass = false; for ($i = 0; $i < $count; $i++) { $token = $tokens[$i]; if (!is_array($token)) { if ($token === '{') { $braceDepth++; } elseif ($token === '}') { $braceDepth--; if ($braceDepth === 0) { $inClass = false; } } continue; } // Klasse/Interface/Trait betreten if (in_array($token[0], [T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], true)) { $inClass = true; } if ($token[0] !== T_FUNCTION) { continue; } // Visibility ermitteln $visibility = null; if ($inClass) { for ($j = $i - 1; $j >= 0; $j--) { if (!is_array($tokens[$j])) { break; } if ($tokens[$j][0] === T_PUBLIC) { $visibility = 'public'; break; } if ($tokens[$j][0] === T_PROTECTED) { $visibility = 'protected'; break; } if ($tokens[$j][0] === T_PRIVATE) { $visibility = 'private'; break; } if ($tokens[$j][0] !== T_WHITESPACE && $tokens[$j][0] !== T_STATIC && $tokens[$j][0] !== T_FINAL && $tokens[$j][0] !== T_ABSTRACT) { break; } } } // Funktionsnamen finden for ($j = $i + 1; $j < $count; $j++) { if (is_array($tokens[$j]) && $tokens[$j][0] === T_STRING) { $functions[] = [ 'name' => $tokens[$j][1], 'visibility' => $visibility, 'line' => $token[2], ]; break; } // Anonyme Funktion (kein Name) if ($tokens[$j] === '(') { break; } } } return $functions; } /** * @param array $tokens */ private function findPrevNonWhitespace(array $tokens, int $index): ?int { for ($i = $index - 1; $i >= 0; $i--) { if (!is_array($tokens[$i]) || $tokens[$i][0] !== T_WHITESPACE) { return $i; } } return null; } }