Backup #968
| ID | 968 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Infrastructure/CodeAnalysis/CodeScanner.php |
| Version | 10 |
| Typ |
modified |
| Größe | 8.7 KB |
| Hash | 5f38abe98b747122dff4ae4616cc96ed7c12b28cf1115dacf16a3914c7f8de7f |
| Datum | 2025-12-23 22:31:26 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Edit-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
declare(strict_types=1);
namespace Infrastructure\CodeAnalysis;
// @responsibility: Rekursives Verzeichnis-Scanning für Code-Analyse
use Domain\Service\CodeScannerInterface;
use Infrastructure\Persistence\CodeAnalysisRepository;
final class CodeScanner implements CodeScannerInterface
{
public function __construct(
private CodeAnalysisRepository $repository,
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->repository->saveBatch($items, $scanId);
$this->repository->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'],
'quality_avg_grade' => $qualityStats['avg_grade'],
];
}
/**
* @return array<string>
*/
private function getEnabledDirectories(): array
{
$configs = $this->repository->getConfiguredDirectories();
return array_column($configs, 'directory');
}
/**
* @return array<string, mixed>
*/
private function getConfigForDirectory(string $directory): array
{
$configs = $this->repository->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) ?: '';
return [
'file_path' => $filePath,
'file_name' => basename($filePath),
'extension' => pathinfo($filePath, PATHINFO_EXTENSION),
'directory' => dirname($filePath),
'file_size' => $stat ? $stat['size'] : 0,
'line_count' => $this->countLines($filePath),
'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 countLines(string $filePath): int
{
$handle = fopen($filePath, 'r');
if ($handle === false) {
return 0;
}
$count = 0;
while (!feof($handle)) {
fgets($handle);
$count++;
}
fclose($handle);
return $count;
}
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 Quality-Analyse für alle Dateien des Scans aus.
*
* @return array{total_issues: int, avg_grade: string}
*/
private function runQualityAnalysis(string $scanId): array
{
$files = $this->repository->findByScanId($scanId);
$totalIssues = 0;
$gradeValues = [];
$gradeMap = ['A' => 5, 'B' => 4, 'C' => 3, 'D' => 2, 'F' => 1];
foreach ($files as $file) {
$qualityResult = $this->qualityChecker->analyze($file);
$this->repository->saveQuality((int) $file['id'], $qualityResult);
$totalIssues += $qualityResult['issues_count'];
$gradeValues[] = $gradeMap[$qualityResult['quality_grade']] ?? 3;
}
$avgGradeValue = count($gradeValues) > 0 ? array_sum($gradeValues) / count($gradeValues) : 3;
$avgGrade = match (true) {
$avgGradeValue >= 4.5 => 'A',
$avgGradeValue >= 3.5 => 'B',
$avgGradeValue >= 2.5 => 'C',
$avgGradeValue >= 1.5 => 'D',
default => 'F',
};
return [
'total_issues' => $totalIssues,
'avg_grade' => $avgGrade,
];
}
}
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 1327 |
20 |
modified |
8.6 KB |
2025-12-25 16:25 |
| 1326 |
19 |
modified |
8.7 KB |
2025-12-25 16:25 |
| 1311 |
18 |
modified |
8.6 KB |
2025-12-25 16:15 |
| 1310 |
17 |
modified |
8.6 KB |
2025-12-25 16:15 |
| 1248 |
16 |
modified |
8.6 KB |
2025-12-25 12:44 |
| 1247 |
15 |
modified |
8.6 KB |
2025-12-25 12:44 |
| 1246 |
14 |
modified |
8.6 KB |
2025-12-25 12:44 |
| 1245 |
13 |
modified |
8.5 KB |
2025-12-25 12:44 |
| 1244 |
12 |
modified |
8.4 KB |
2025-12-25 12:44 |
| 969 |
11 |
modified |
8.7 KB |
2025-12-23 22:31 |
| 968 |
10 |
modified |
8.7 KB |
2025-12-23 22:31 |
| 949 |
9 |
modified |
8.6 KB |
2025-12-23 22:07 |
| 931 |
8 |
modified |
7.4 KB |
2025-12-23 21:45 |
| 930 |
7 |
modified |
7.2 KB |
2025-12-23 21:44 |
| 929 |
6 |
modified |
7.1 KB |
2025-12-23 21:44 |
| 918 |
5 |
modified |
6.8 KB |
2025-12-23 21:04 |
| 917 |
4 |
modified |
6.7 KB |
2025-12-23 21:04 |
| 916 |
3 |
modified |
6.6 KB |
2025-12-23 21:04 |
| 879 |
2 |
modified |
6.3 KB |
2025-12-23 15:17 |
| 870 |
1 |
modified |
6.3 KB |
2025-12-23 09:28 |
← Zurück zur Übersicht