PhpFileParser.php
- Pfad:
src/Infrastructure/CodeAnalysis/PhpFileParser.php - Namespace: Infrastructure\CodeAnalysis
- Zeilen: 291 | Größe: 8,655 Bytes
- Geändert: 2025-12-28 23:20:04 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 94
- Dependencies: 100 (25%)
- LOC: 69 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 4
- trait Infrastructure\CodeAnalysis\TokenNavigatorTrait
- trait Infrastructure\CodeAnalysis\UseStatementExtractorTrait
- trait Infrastructure\CodeAnalysis\InheritanceExtractorTrait
- trait Infrastructure\CodeAnalysis\ClassFunctionExtractorTrait
Klassen 1
-
PhpFileParserclass Zeile 21
Funktionen 5
-
parse()public Zeile 49 -
extractNamespace()private Zeile 115 -
extractConstructorDeps()private Zeile 149 -
resolveFqcn()private Zeile 252 -
isBuiltInType()private Zeile 280
Verwendet von 2
- CodeScanner.php constructor
- InfrastructureServiceProvider.php use
Versionen 12
-
v12
2025-12-28 23:20 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v11
2025-12-25 01:51 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v10
2025-12-25 01:51 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v9
2025-12-25 01:50 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v8
2025-12-25 01:50 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v7
2025-12-25 01:48 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v6
2025-12-25 01:48 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v5
2025-12-25 01:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v4
2025-12-25 01:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v3
2025-12-25 00:18 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v2
2025-12-25 00:17 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v1
2025-12-23 14:14 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Write-Operation
Code
<?php
declare(strict_types=1);
namespace Infrastructure\CodeAnalysis;
/**
* @responsibility PHP-Datei-Strukturanalyse via Tokenizer
*
* @composition Nutzt Mixin-Pattern für Separation of Concerns:
* - TokenNavigatorTrait: Token-Stream Navigation und Positionierung
* - UseStatementExtractorTrait: use-Statement und Import-Map Extraktion
* - InheritanceExtractorTrait: extends/implements/use-Trait Analyse
* - ClassFunctionExtractorTrait: Methoden, Properties und Constructor-Dependencies
*
* @see TokenNavigatorTrait
* @see UseStatementExtractorTrait
* @see InheritanceExtractorTrait
* @see ClassFunctionExtractorTrait
*/
final class PhpFileParser
{
use TokenNavigatorTrait;
use UseStatementExtractorTrait;
use InheritanceExtractorTrait;
use ClassFunctionExtractorTrait;
/** @var array<string, string> Maps short name to FQCN from use statements */
private array $importMap = [];
/** @var string|null Current namespace */
private ?string $currentNamespace = null;
/**
* Parst eine PHP-Datei und extrahiert Metadaten inkl. Dependencies.
*
* @return array{
* namespace: string|null,
* classes: array<array{name: string, type: string, line: int}>,
* functions: array<array{name: string, visibility: string|null, line: int}>,
* uses: array<string>,
* extends_class: string|null,
* implements_interfaces: array<string>,
* traits_used: array<string>,
* constructor_deps: array<string>,
* error: string|null
* }
*/
public function parse(string $filePath): array
{
$this->importMap = [];
$this->currentNamespace = null;
$result = [
'namespace' => null,
'classes' => [],
'functions' => [],
'uses' => [],
'extends_class' => null,
'implements_interfaces' => [],
'traits_used' => [],
'constructor_deps' => [],
'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;
}
// First pass: Extract namespace and use statements for resolution
$result['namespace'] = $this->extractNamespace($tokens);
$this->currentNamespace = $result['namespace'];
$result['uses'] = $this->extractUseStatements($tokens);
// Build import map for FQCN resolution
foreach ($result['uses'] as $fqcn) {
$parts = explode('\\', $fqcn);
$shortName = end($parts);
$this->importMap[$shortName] = $fqcn;
}
// Second pass: Extract structural elements
$result['classes'] = $this->extractClasses($tokens);
$result['functions'] = $this->extractFunctions($tokens);
// Third pass: Extract dependencies requiring resolution
$result['extends_class'] = $this->extractExtends($tokens);
$result['implements_interfaces'] = $this->extractImplements($tokens);
$result['traits_used'] = $this->extractTraits($tokens);
$result['constructor_deps'] = $this->extractConstructorDeps($tokens);
return $result;
}
/**
* @param array<mixed> $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;
}
/**
* Extract constructor parameter type hints (DI dependencies).
*
* @param array<mixed> $tokens
* @return array<string>
*/
private function extractConstructorDeps(array $tokens): array
{
$deps = [];
$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;
}
if (in_array($token[0], [T_CLASS, T_TRAIT, T_ENUM], true)) {
$inClass = true;
continue;
}
// Look for T_FUNCTION with name __construct
if ($token[0] !== T_FUNCTION) {
continue;
}
// Check if it's __construct
$funcName = $this->findNextString($tokens, $i);
if ($funcName !== '__construct') {
continue;
}
// Find opening parenthesis
$parenStart = null;
for ($j = $i + 1; $j < $count; $j++) {
if ($tokens[$j] === '(') {
$parenStart = $j;
break;
}
}
if ($parenStart === null) {
continue;
}
// Extract type hints from parameters
$parenDepth = 1;
$lastType = null;
for ($j = $parenStart + 1; $j < $count && $parenDepth > 0; $j++) {
$paramToken = $tokens[$j];
if ($paramToken === '(') {
$parenDepth++;
continue;
}
if ($paramToken === ')') {
$parenDepth--;
continue;
}
if (!is_array($paramToken)) {
continue;
}
// Type hint tokens (skip built-in types)
if (in_array($paramToken[0], [T_STRING, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED], true)) {
$typeName = $paramToken[1];
// Skip built-in types
if (!$this->isBuiltInType($typeName)) {
$lastType = $typeName;
}
}
// Variable = end of parameter, save type if found
if ($paramToken[0] === T_VARIABLE && $lastType !== null) {
$deps[] = $this->resolveFqcn($lastType);
$lastType = null;
}
}
// Only process first constructor found
break;
}
return array_unique($deps);
}
/**
* Resolve a short or qualified name to FQCN.
*/
private function resolveFqcn(string $name): string
{
// Already fully qualified
if (str_starts_with($name, '\\')) {
return ltrim($name, '\\');
}
// Already contains namespace separator (qualified name)
if (str_contains($name, '\\')) {
return $name;
}
// Check import map
if (isset($this->importMap[$name])) {
return $this->importMap[$name];
}
// Default to current namespace + name
if ($this->currentNamespace !== null) {
return $this->currentNamespace . '\\' . $name;
}
return $name;
}
/**
* Check if a type name is a PHP built-in type.
*/
private function isBuiltInType(string $type): bool
{
$builtIn = [
'int', 'float', 'bool', 'string', 'array', 'object', 'callable',
'iterable', 'void', 'null', 'mixed', 'never', 'true', 'false',
'self', 'static', 'parent',
];
return in_array(strtolower($type), $builtIn, true);
}
}