FileAnalysisRepository.php
- Pfad:
src/Infrastructure/Persistence/FileAnalysisRepository.php - Namespace: Infrastructure\Persistence
- Zeilen: 318 | Größe: 11,219 Bytes
- Geändert: 2025-12-27 23:50:02 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 87
- Dependencies: 100 (25%)
- LOC: 54 (20%)
- Methods: 80 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 5
- implements Domain\Repository\FileAnalysisRepositoryInterface
- constructor PDO
- use Domain\Constants
- use Domain\Repository\FileAnalysisRepositoryInterface
- use PDO
Klassen 1
-
FileAnalysisRepositoryclass Zeile 13
Funktionen 12
-
__construct()public Zeile 17 -
findAll()public Zeile 26 -
findById()public Zeile 97 -
saveBatch()public Zeile 117 -
saveDependencies()private Zeile 187 -
deleteByNotScanId()public Zeile 215 -
getLatestScanId()public Zeile 223 -
findByScanId()public Zeile 234 -
getStatistics()public Zeile 247 -
getDistinctDirectories()Zeile 291 -
getDistinctNamespaces()Zeile 301 -
getConfiguredDirectories()Zeile 311
Verwendet von 1
Versionen 8
-
v8
2025-12-27 23:50 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v7
2025-12-27 23:49 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v6
2025-12-25 16:26 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v5
2025-12-25 16:26 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v4
2025-12-25 16:26 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v3
2025-12-25 16:16 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v2
2025-12-25 16:16 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v1
2025-12-25 16:16 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
Code
<?php
declare(strict_types=1);
namespace Infrastructure\Persistence;
// @responsibility: Persistenz für Datei-Analyse (code_analysis Tabelle)
use Domain\Constants;
use Domain\Repository\FileAnalysisRepositoryInterface;
use PDO;
class FileAnalysisRepository implements FileAnalysisRepositoryInterface
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* @param array<string, mixed> $filters
* @return array<array<string, mixed>>
*/
public function findAll(array $filters = [], int $limit = Constants::DEFAULT_LIMIT, int $offset = 0): array
{
$sql = '
SELECT ca.*,
cq.dependency_score, cq.hygiene_score,
cq.factor_scores, cq.hardcoded_count, cq.issues_count,
cq.warnings_count, cq.issues_json
FROM code_analysis ca
LEFT JOIN code_quality cq ON ca.id = cq.analysis_id
WHERE 1=1
';
$params = [];
if (!empty($filters['scan_id'])) {
$sql .= ' AND ca.scan_id = :scan_id';
$params['scan_id'] = $filters['scan_id'];
}
if (!empty($filters['directory'])) {
$sql .= ' AND ca.directory = :directory';
$params['directory'] = $filters['directory'];
}
if (!empty($filters['namespace'])) {
$sql .= ' AND ca.namespace = :namespace';
$params['namespace'] = $filters['namespace'];
}
if (!empty($filters['extension'])) {
$sql .= ' AND ca.extension = :extension';
$params['extension'] = $filters['extension'];
}
if (!empty($filters['search'])) {
$sql .= ' AND (ca.file_name LIKE :search OR ca.namespace LIKE :search2)';
$params['search'] = '%' . $filters['search'] . '%';
$params['search2'] = '%' . $filters['search'] . '%';
}
if (isset($filters['has_classes']) && $filters['has_classes'] !== '') {
if ($filters['has_classes'] === '1') {
$sql .= " AND ca.classes IS NOT NULL AND ca.classes != '[]'";
} else {
$sql .= " AND (ca.classes IS NULL OR ca.classes = '[]')";
}
}
if (isset($filters['has_error']) && $filters['has_error'] === '1') {
$sql .= ' AND ca.parse_error IS NOT NULL';
}
if (!empty($filters['has_issues'])) {
$sql .= ' AND cq.issues_count > 0';
}
$sql .= ' ORDER BY ca.directory, ca.file_name LIMIT :limit OFFSET :offset';
$stmt = $this->pdo->prepare($sql);
foreach ($params as $key => $value) {
$stmt->bindValue(':' . $key, $value);
}
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* @return array<string, mixed>|null
*/
public function findById(int $id): ?array
{
$stmt = $this->pdo->prepare('
SELECT ca.*,
cq.dependency_score, cq.hygiene_score,
cq.factor_scores, cq.hardcoded_count, cq.issues_count,
cq.warnings_count, cq.issues_json
FROM code_analysis ca
LEFT JOIN code_quality cq ON ca.id = cq.analysis_id
WHERE ca.id = :id
');
$stmt->execute(['id' => $id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}
/**
* @param array<array<string, mixed>> $items
*/
public function saveBatch(array $items, string $scanId): int
{
if (empty($items)) {
return 0;
}
$this->pdo->beginTransaction();
try {
$analysisStmt = $this->pdo->prepare('
INSERT INTO code_analysis
(scan_id, file_path, file_name, extension, directory, file_size, line_count,
file_content, modified_at, namespace, classes, functions, uses, extends_class,
implements_interfaces, traits_used, constructor_deps, parse_error, triggered_by)
VALUES
(:scan_id, :file_path, :file_name, :extension, :directory, :file_size, :line_count,
:file_content, :modified_at, :namespace, :classes, :functions, :uses, :extends_class,
:implements_interfaces, :traits_used, :constructor_deps, :parse_error, :triggered_by)
');
$depStmt = $this->pdo->prepare('
INSERT INTO code_dependencies (analysis_id, dependency_type, target_fqcn)
VALUES (:analysis_id, :dep_type, :target_fqcn)
');
$count = 0;
foreach ($items as $item) {
$analysisStmt->execute([
'scan_id' => $scanId,
'file_path' => $item['file_path'],
'file_name' => $item['file_name'],
'extension' => $item['extension'],
'directory' => $item['directory'],
'file_size' => $item['file_size'],
'line_count' => $item['line_count'],
'file_content' => $item['file_content'] ?? '',
'modified_at' => $item['modified_at'],
'namespace' => $item['namespace'],
'classes' => json_encode($item['classes'] ?? [], JSON_UNESCAPED_UNICODE),
'functions' => json_encode($item['functions'] ?? [], JSON_UNESCAPED_UNICODE),
'uses' => json_encode($item['uses'] ?? [], JSON_UNESCAPED_UNICODE),
'extends_class' => $item['extends_class'],
'implements_interfaces' => json_encode($item['implements_interfaces'] ?? [], JSON_UNESCAPED_UNICODE),
'traits_used' => json_encode($item['traits_used'] ?? [], JSON_UNESCAPED_UNICODE),
'constructor_deps' => json_encode($item['constructor_deps'] ?? [], JSON_UNESCAPED_UNICODE),
'parse_error' => $item['parse_error'],
'triggered_by' => $item['triggered_by'] ?? 'web',
]);
$analysisId = (int) $this->pdo->lastInsertId();
// Save dependencies to normalized table
$this->saveDependencies($depStmt, $analysisId, $item);
$count++;
}
$this->pdo->commit();
return $count;
} catch (\Throwable $e) {
$this->pdo->rollBack();
throw $e;
}
}
/**
* @param array<string, mixed> $item
*/
private function saveDependencies(\PDOStatement $stmt, int $analysisId, array $item): void
{
// Use statements
foreach ($item['uses'] ?? [] as $fqcn) {
$stmt->execute(['analysis_id' => $analysisId, 'dep_type' => 'use', 'target_fqcn' => $fqcn]);
}
// Extends
if (!empty($item['extends_class'])) {
$stmt->execute(['analysis_id' => $analysisId, 'dep_type' => 'extends', 'target_fqcn' => $item['extends_class']]);
}
// Implements
foreach ($item['implements_interfaces'] ?? [] as $fqcn) {
$stmt->execute(['analysis_id' => $analysisId, 'dep_type' => 'implements', 'target_fqcn' => $fqcn]);
}
// Traits
foreach ($item['traits_used'] ?? [] as $fqcn) {
$stmt->execute(['analysis_id' => $analysisId, 'dep_type' => 'trait', 'target_fqcn' => $fqcn]);
}
// Constructor dependencies
foreach ($item['constructor_deps'] ?? [] as $fqcn) {
$stmt->execute(['analysis_id' => $analysisId, 'dep_type' => 'constructor', 'target_fqcn' => $fqcn]);
}
}
public function deleteByNotScanId(string $currentScanId): int
{
$stmt = $this->pdo->prepare('DELETE FROM code_analysis WHERE scan_id != :scan_id');
$stmt->execute(['scan_id' => $currentScanId]);
return $stmt->rowCount();
}
public function getLatestScanId(): ?string
{
$stmt = $this->pdo->query('SELECT scan_id FROM code_analysis ORDER BY scanned_at DESC LIMIT 1');
$result = $stmt->fetchColumn();
return $result ?: null;
}
/**
* @return array<array<string, mixed>>
*/
public function findByScanId(string $scanId): array
{
$stmt = $this->pdo->prepare('
SELECT * FROM code_analysis WHERE scan_id = :scan_id ORDER BY file_path
');
$stmt->execute(['scan_id' => $scanId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* @return array<string, mixed>
*/
public function getStatistics(?string $scanId = null): array
{
$where = $scanId ? 'WHERE scan_id = :scan_id' : '';
$params = $scanId ? ['scan_id' => $scanId] : [];
$stmt = $this->pdo->prepare("
SELECT
COUNT(*) as total_files,
SUM(line_count) as total_lines,
SUM(file_size) as total_size,
COUNT(CASE WHEN parse_error IS NOT NULL THEN 1 END) as files_with_errors,
MAX(scanned_at) as last_scan
FROM code_analysis {$where}
");
$stmt->execute($params);
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $this->pdo->prepare("SELECT classes, functions FROM code_analysis {$where}");
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$totalClasses = 0;
$totalFunctions = 0;
foreach ($rows as $row) {
$classes = json_decode($row['classes'] ?? '[]', true);
$functions = json_decode($row['functions'] ?? '[]', true);
$totalClasses += count($classes);
$totalFunctions += count($functions);
}
return [
'total_files' => (int) ($stats['total_files'] ?? 0),
'total_lines' => (int) ($stats['total_lines'] ?? 0),
'total_size' => (int) ($stats['total_size'] ?? 0),
'total_classes' => $totalClasses,
'total_functions' => $totalFunctions,
'files_with_errors' => (int) ($stats['files_with_errors'] ?? 0),
'last_scan' => $stats['last_scan'],
];
}
/**
* @return array<string>
*/
public function getDistinctDirectories(): array
{
$stmt = $this->pdo->query('SELECT DISTINCT directory FROM code_analysis ORDER BY directory');
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
/**
* @return array<string>
*/
public function getDistinctNamespaces(): array
{
$stmt = $this->pdo->query('SELECT DISTINCT namespace FROM code_analysis WHERE namespace IS NOT NULL ORDER BY namespace');
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
/**
* @return array<array<string, mixed>>
*/
public function getConfiguredDirectories(): array
{
$stmt = $this->pdo->query('SELECT * FROM code_scan_config WHERE enabled = 1 ORDER BY label');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}