Backup #968

ID968
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Infrastructure/CodeAnalysis/CodeScanner.php
Version10
Typ modified
Größe8.7 KB
Hash5f38abe98b747122dff4ae4616cc96ed7c12b28cf1115dacf16a3914c7f8de7f
Datum2025-12-23 22:31:26
Geändert vonclaude-code-hook
GrundClaude 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

Herunterladen

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