{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/opt\/scripts\/di-check.php",
"content": "#!\/usr\/bin\/env php\n<?php\n\/**\n * DI Dependency Resolution Check\n *\n * Validates that all UseCase constructor dependencies can be resolved\n * by the DI Container (either via explicit registration or autowiring).\n *\n * Usage: php di-check.php [project-path]\n *\n * Exit codes:\n * 0 = All dependencies resolvable\n * 1 = Unresolvable dependencies found\n *\/\n\n\/\/ @responsibility: Prüft ob alle UseCase-Dependencies via DI auflösbar sind\n\ndeclare(strict_types=1);\n\n$projectDir = $argv[1] ?? '\/var\/www\/dev.campus.systemische-tools.de';\n$srcDir = $projectDir . '\/src';\n$servicesFile = $projectDir . '\/src\/services.php';\n\n\/\/ Colors for terminal output\n$RED = \"\\033[0;31m\";\n$GREEN = \"\\033[0;32m\";\n$YELLOW = \"\\033[1;33m\";\n$NC = \"\\033[0m\";\n\necho \"=== DI Dependency Resolution Check ===\\n\";\necho \"Project: $projectDir\\n\\n\";\n\n\/\/ Load autoloader\nrequire_once $projectDir . '\/vendor\/autoload.php';\n\n\/\/ Parse services.php to find registered services\nfunction getRegisteredServices(string $servicesFile): array {\n $content = file_get_contents($servicesFile);\n $registered = [];\n\n \/\/ Match: $container->set(ClassName::class, ...)\n preg_match_all('\/\\$container->set\\(([^:]+)::class\/', $content, $matches);\n\n foreach ($matches[1] as $match) {\n \/\/ Resolve the full class name from use statements\n $className = trim($match);\n $registered[$className] = true;\n }\n\n \/\/ Also parse use statements to get full class names\n preg_match_all('\/^use\\s+([^;]+);\/m', $content, $useMatches);\n $useMap = [];\n foreach ($useMatches[1] as $use) {\n $parts = explode('\\\\', $use);\n $shortName = end($parts);\n $useMap[$shortName] = $use;\n }\n\n \/\/ Build full class name map\n $fullRegistered = [];\n foreach ($registered as $short => $_) {\n if (isset($useMap[$short])) {\n $fullRegistered[$useMap[$short]] = true;\n } else {\n $fullRegistered[$short] = true;\n }\n }\n\n return $fullRegistered;\n}\n\n\/\/ Get all UseCase files\nfunction getUseCaseFiles(string $srcDir): array {\n $files = [];\n $iterator = new RecursiveIteratorIterator(\n new RecursiveDirectoryIterator($srcDir . '\/UseCases')\n );\n\n foreach ($iterator as $file) {\n if ($file->isFile() && $file->getExtension() === 'php') {\n $files[] = $file->getPathname();\n }\n }\n\n return $files;\n}\n\n\/\/ Check if a dependency can be resolved\nfunction canResolve(string $dependency, array $registered): array {\n \/\/ Check if explicitly registered\n if (isset($registered[$dependency])) {\n return ['resolvable' => true, 'method' => 'registered'];\n }\n\n \/\/ Check if it's an interface (must be registered)\n if (str_ends_with($dependency, 'Interface')) {\n return ['resolvable' => false, 'method' => 'interface_not_registered'];\n }\n\n \/\/ Check if class exists (can be autowired)\n if (class_exists($dependency)) {\n return ['resolvable' => true, 'method' => 'autowire'];\n }\n\n return ['resolvable' => false, 'method' => 'not_found'];\n}\n\n\/\/ Analyze a UseCase class\nfunction analyzeUseCase(string $file, array $registered): array {\n $issues = [];\n\n \/\/ Get class name from file\n $content = file_get_contents($file);\n\n \/\/ Extract namespace\n preg_match('\/namespace\\s+([^;]+);\/', $content, $nsMatch);\n $namespace = $nsMatch[1] ?? '';\n\n \/\/ Extract class name\n preg_match('\/class\\s+(\\w+)\/', $content, $classMatch);\n $className = $classMatch[1] ?? '';\n\n if (!$className) {\n return [];\n }\n\n $fullClassName = $namespace . '\\\\' . $className;\n\n \/\/ Skip if class doesn't exist (syntax error or not autoloadable)\n if (!class_exists($fullClassName)) {\n return [];\n }\n\n try {\n $reflection = new ReflectionClass($fullClassName);\n $constructor = $reflection->getConstructor();\n\n if ($constructor === null) {\n return [];\n }\n\n foreach ($constructor->getParameters() as $param) {\n $type = $param->getType();\n\n \/\/ Skip optional parameters with defaults\n if ($param->isDefaultValueAvailable()) {\n continue;\n }\n\n \/\/ Skip if no type hint\n if ($type === null || !$type instanceof ReflectionNamedType) {\n continue;\n }\n\n \/\/ Skip built-in types\n if ($type->isBuiltin()) {\n continue;\n }\n\n $typeName = $type->getName();\n $result = canResolve($typeName, $registered);\n\n if (!$result['resolvable']) {\n $issues[] = [\n 'class' => $fullClassName,\n 'parameter' => $param->getName(),\n 'type' => $typeName,\n 'reason' => $result['method'],\n ];\n }\n }\n } catch (ReflectionException $e) {\n \/\/ Skip if reflection fails\n }\n\n return $issues;\n}\n\n\/\/ Main execution\n$registered = getRegisteredServices($servicesFile);\n$useCaseFiles = getUseCaseFiles($srcDir);\n$allIssues = [];\n\necho \"Checking \" . count($useCaseFiles) . \" UseCase files...\\n\\n\";\n\nforeach ($useCaseFiles as $file) {\n $issues = analyzeUseCase($file, $registered);\n if (!empty($issues)) {\n $allIssues = array_merge($allIssues, $issues);\n }\n}\n\nif (empty($allIssues)) {\n echo \"{$GREEN}✓ All UseCase dependencies are resolvable{$NC}\\n\";\n echo \"\\nRegistered services: \" . count($registered) . \"\\n\";\n echo \"UseCases checked: \" . count($useCaseFiles) . \"\\n\";\n exit(0);\n} else {\n echo \"{$RED}✗ Found \" . count($allIssues) . \" unresolvable dependencies:{$NC}\\n\\n\";\n\n foreach ($allIssues as $issue) {\n $shortClass = basename(str_replace('\\\\', '\/', $issue['class']));\n echo \" {$YELLOW}{$shortClass}{$NC}\\n\";\n echo \" Parameter: \\${$issue['parameter']}\\n\";\n echo \" Type: {$issue['type']}\\n\";\n echo \" Reason: {$issue['reason']}\\n\\n\";\n }\n\n echo \"Fix: Register missing interfaces in src\/services.php\\n\";\n exit(1);\n}\n"
}
}