CodeScanner.php
- Pfad:
src/Infrastructure/CodeAnalysis/CodeScanner.php - Namespace: Infrastructure\CodeAnalysis
- Zeilen: 270 | Größe: 8,510 Bytes
- Geändert: 2025-12-25 16:25:16 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 91
- Dependencies: 100 (25%)
- LOC: 76 (20%)
- Methods: 80 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 10
- implements Domain\Service\CodeScannerInterface
- constructor Domain\Repository\FileAnalysisRepositoryInterface
- constructor Domain\Repository\CodeQualityRepositoryInterface
- constructor Infrastructure\CodeAnalysis\PhpFileParser
- constructor Infrastructure\CodeAnalysis\PythonFileParser
- constructor Infrastructure\CodeAnalysis\JsFileParser
- constructor Infrastructure\CodeAnalysis\CodeQualityChecker
- use Domain\Repository\CodeQualityRepositoryInterface
- use Domain\Repository\FileAnalysisRepositoryInterface
- use Domain\Service\CodeScannerInterface
Klassen 1
-
CodeScannerclass Zeile 13
Funktionen 12
-
__construct()public Zeile 15 -
scan()public Zeile 31 -
getEnabledDirectories()private Zeile 84 -
getConfigForDirectory()private Zeile 94 -
parseExtensions()private Zeile 109 -
parseExcludePatterns()private Zeile 117 -
scanDirectory()private Zeile 131 -
isExcluded()private Zeile 168 -
analyzeFile()private Zeile 184 -
getParserForExtension()private Zeile 217 -
generateUuid()private Zeile 226 -
runQualityAnalysis()private Zeile 246
Verwendet von 1
Versionen 20
-
v20
2025-12-25 16:25 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v19
2025-12-25 16:25 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v18
2025-12-25 16:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v17
2025-12-25 16:15 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v16
2025-12-25 12:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v15
2025-12-25 12:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v14
2025-12-25 12:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v13
2025-12-25 12:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v12
2025-12-25 12:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v11
2025-12-23 22:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v10
2025-12-23 22:31 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v9
2025-12-23 22:07 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v8
2025-12-23 21:45 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v7
2025-12-23 21:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v6
2025-12-23 21:44 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v5
2025-12-23 21:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v4
2025-12-23 21:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v3
2025-12-23 21:04 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v2
2025-12-23 15:17 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v1
2025-12-23 09:28 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
Code
<?php
declare(strict_types=1);
namespace Infrastructure\CodeAnalysis;
// @responsibility: Rekursives Verzeichnis-Scanning für Code-Analyse
use Domain\Repository\CodeQualityRepositoryInterface;
use Domain\Repository\FileAnalysisRepositoryInterface;
use Domain\Service\CodeScannerInterface;
final class CodeScanner implements CodeScannerInterface
{
public function __construct(
private FileAnalysisRepositoryInterface $fileRepository,
private CodeQualityRepositoryInterface $qualityRepository,
private PhpFileParser $phpParser,
private PythonFileParser $pythonParser,
private JsFileParser $jsParser,
private CodeQualityChecker $qualityChecker
) {
}
/**
* Scannt konfigurierte oder übergebene Verzeichnisse.
*
* @param array<string>|null $directories Null = konfigurierte Verzeichnisse
* @return array{scan_id: string, files_scanned: int, files_with_errors: int, total_classes: int, total_functions: int, duration_ms: int}
*/
public function scan(?array $directories = null, string $triggeredBy = 'web'): array
{
$start = hrtime(true);
$scanId = $this->generateUuid();
$dirs = $directories ?? $this->getEnabledDirectories();
$items = [];
$totalClasses = 0;
$totalFunctions = 0;
$filesWithErrors = 0;
foreach ($dirs as $directory) {
$config = $this->getConfigForDirectory($directory);
$extensions = $this->parseExtensions($config['extensions'] ?? 'php');
$excludePatterns = $this->parseExcludePatterns($config['exclude_patterns'] ?? '');
$files = $this->scanDirectory($directory, $extensions, $excludePatterns);
foreach ($files as $filePath) {
$item = $this->analyzeFile($filePath, $triggeredBy);
$items[] = $item;
if ($item['parse_error'] !== null) {
$filesWithErrors++;
}
$totalClasses += count($item['classes']);
$totalFunctions += count($item['functions']);
}
}
$this->fileRepository->saveBatch($items, $scanId);
$this->fileRepository->deleteByNotScanId($scanId);
// Quality-Analyse ausführen
$qualityStats = $this->runQualityAnalysis($scanId);
$durationMs = (int) ((hrtime(true) - $start) / 1_000_000);
return [
'scan_id' => $scanId,
'files_scanned' => count($items),
'files_with_errors' => $filesWithErrors,
'total_classes' => $totalClasses,
'total_functions' => $totalFunctions,
'duration_ms' => $durationMs,
'quality_issues' => $qualityStats['total_issues'],
'avg_hygiene_score' => $qualityStats['avg_hygiene_score'],
];
}
/**
* @return array<string>
*/
private function getEnabledDirectories(): array
{
$configs = $this->fileRepository->getConfiguredDirectories();
return array_column($configs, 'directory');
}
/**
* @return array<string, mixed>
*/
private function getConfigForDirectory(string $directory): array
{
$configs = $this->fileRepository->getConfiguredDirectories();
foreach ($configs as $config) {
if ($config['directory'] === $directory) {
return $config;
}
}
return ['extensions' => 'php', 'exclude_patterns' => ''];
}
/**
* @return array<string>
*/
private function parseExtensions(string $extensions): array
{
return array_map('trim', explode(',', $extensions));
}
/**
* @return array<string>
*/
private function parseExcludePatterns(string $patterns): array
{
if ($patterns === '') {
return [];
}
return array_map('trim', explode(',', $patterns));
}
/**
* @param array<string> $extensions
* @param array<string> $excludePatterns
* @return array<string>
*/
private function scanDirectory(string $directory, array $extensions, array $excludePatterns): array
{
$files = [];
if (!is_dir($directory)) {
return $files;
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (!$file->isFile()) {
continue;
}
$filePath = $file->getPathname();
$ext = $file->getExtension();
if (!in_array($ext, $extensions, true)) {
continue;
}
if ($this->isExcluded($filePath, $directory, $excludePatterns)) {
continue;
}
$files[] = $filePath;
}
return $files;
}
/**
* @param array<string> $patterns
*/
private function isExcluded(string $filePath, string $rootDir, array $patterns): bool
{
$relativePath = str_replace($rootDir . '/', '', $filePath);
foreach ($patterns as $pattern) {
if (fnmatch($pattern, $relativePath)) {
return true;
}
}
return false;
}
/**
* @return array<string, mixed>
*/
private function analyzeFile(string $filePath, string $triggeredBy): array
{
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
$parsed = $this->getParserForExtension($extension)->parse($filePath);
$stat = @stat($filePath);
$fileContent = @file_get_contents($filePath) ?: '';
$lineCount = $fileContent !== '' ? substr_count($fileContent, "\n") + 1 : 0;
return [
'file_path' => $filePath,
'file_name' => basename($filePath),
'extension' => pathinfo($filePath, PATHINFO_EXTENSION),
'directory' => dirname($filePath),
'file_size' => $stat ? $stat['size'] : 0,
'line_count' => $lineCount,
'file_content' => $fileContent,
'modified_at' => $stat ? date('Y-m-d H:i:s', $stat['mtime']) : date('Y-m-d H:i:s'),
'namespace' => $parsed['namespace'],
'classes' => $parsed['classes'],
'functions' => $parsed['functions'],
'uses' => $parsed['uses'],
'extends_class' => $parsed['extends_class'],
'implements_interfaces' => $parsed['implements_interfaces'],
'traits_used' => $parsed['traits_used'],
'constructor_deps' => $parsed['constructor_deps'],
'parse_error' => $parsed['error'],
'triggered_by' => $triggeredBy,
];
}
/**
* Wählt den passenden Parser für die Dateiendung.
*/
private function getParserForExtension(string $extension): PhpFileParser|PythonFileParser|JsFileParser
{
return match ($extension) {
'py' => $this->pythonParser,
'js' => $this->jsParser,
default => $this->phpParser,
};
}
private function generateUuid(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0x0fff) | 0x4000,
random_int(0, 0x3fff) | 0x8000,
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff)
);
}
/**
* Führt Hygiene-Analyse für alle Dateien des Scans aus.
*
* @return array{total_issues: int, avg_hygiene_score: int}
*/
private function runQualityAnalysis(string $scanId): array
{
$files = $this->fileRepository->findByScanId($scanId);
$totalIssues = 0;
$hygieneScores = [];
foreach ($files as $file) {
$qualityResult = $this->qualityChecker->analyze($file);
$this->qualityRepository->saveQuality((int) $file['id'], $qualityResult);
$totalIssues += $qualityResult['issues_count'];
$hygieneScores[] = $qualityResult['hygiene_score'];
}
$avgScore = count($hygieneScores) > 0
? (int) round(array_sum($hygieneScores) / count($hygieneScores))
: 50;
return [
'total_issues' => $totalIssues,
'avg_hygiene_score' => $avgScore,
];
}
}