← Code-Übersicht

PostgresCodeDateiRepository.php

Pfad: src/Infrastructure/Persistence/PostgresCodeDateiRepository.php

Ext: php

Größe: 5579 Bytes

Geändert: 2026-07-16T10:06:48+02:00

Frühere Version vom 2026-07-16T10:06:48+02:00 · zur aktuellen Fassung

<?php
declare(strict_types=1);
namespace Demo\Infrastructure\Persistence;

use Demo\Domain\Code\CodeDatei;
use Demo\Domain\Code\CodeDateiRepository;
use PDO;
use PDOStatement;
use Throwable;

final class PostgresCodeDateiRepository implements CodeDateiRepository
{
    private PdoConnectionFactory $factory;
    private ?PDO $pdo = null;
    public function __construct(PdoConnectionFactory $factory)
    {
        $this->factory = $factory;
    }

    public function liste(): array
    {
        $sql = 'SELECT id, relativer_pfad, datei_name, datei_erweiterung, code, code_hash,'
            . ' datei_groesse, datei_mtime, created_at, updated_at FROM code_datei ORDER BY relativer_pfad';
        $stmt = $this->pdo()->query($sql);
        $ausgabe = [];
        foreach ($stmt === false ? [] : $stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $ausgabe[] = $this->hydrate($row);
        }
        return $ausgabe;
    }

    public function byId(int $id): ?CodeDatei
    {
        $sql = 'SELECT id, relativer_pfad, datei_name, datei_erweiterung, code, code_hash,'
            . ' datei_groesse, datei_mtime, created_at, updated_at FROM code_datei WHERE id = :id';
        $stmt = $this->pdo()->prepare($sql);
        $stmt->execute([':id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row === false ? null : $this->hydrate((array) $row);
    }

    public function synchronisiere(array $dateien): void
    {
        $pdo = $this->pdo();
        $pdo->beginTransaction();
        try {
            $bestehende = $this->bestehendeZeilen();
            $einsetzen = $pdo->prepare(
                'INSERT INTO code_datei (relativer_pfad, datei_name, datei_erweiterung, code, code_hash,'
                . ' datei_groesse, datei_mtime)'
                . ' VALUES (:relativer_pfad, :datei_name, :datei_erweiterung, :code, :code_hash,'
                . ' :datei_groesse, :datei_mtime)'
            );
            $aktualisieren = $pdo->prepare(
                'UPDATE code_datei'
                . ' SET datei_name = :datei_name, datei_erweiterung = :datei_erweiterung,'
                . ' code = :code, code_hash = :code_hash, datei_groesse = :datei_groesse,'
                . ' datei_mtime = :datei_mtime WHERE id = :id'
            );
            $entfernen = $pdo->prepare('DELETE FROM code_datei WHERE relativer_pfad = :relativer_pfad');

            foreach ($dateien as $datei) {
                if (!$datei instanceof CodeDatei) {
                    continue;
                }
                $this->importiereDatei($datei, $bestehende, $einsetzen, $aktualisieren);
                unset($bestehende[$datei->relativerPfad]);
            }
            foreach (array_keys($bestehende) as $zuLoeschen) {
                $entfernen->execute([':relativer_pfad' => $zuLoeschen]);
            }
            $pdo->commit();
        } catch (Throwable $throwable) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $throwable;
        }
    }

    private function importiereDatei(CodeDatei $datei, array &$bestehende, PDOStatement $einsetzen, PDOStatement $aktualisieren): void
    {
        $pfad = $datei->relativerPfad;
        if (!isset($bestehende[$pfad])) {
            $this->speichereNeu($einsetzen, $datei);
            return;
        }
        if ($bestehende[$pfad]['hash'] !== $datei->codeHash) {
            $aktualisieren->execute([
                ':id' => $bestehende[$pfad]['id'],
                ':datei_name' => $datei->dateiName,
                ':datei_erweiterung' => $datei->dateiErweiterung,
                ':code' => $datei->code,
                ':code_hash' => $datei->codeHash,
                ':datei_groesse' => $datei->dateiGroesse,
                ':datei_mtime' => $this->zeitstempelFromDatei($datei->geaendert),
            ]);
        }
    }

    private function speichereNeu(PDOStatement $einsetzen, CodeDatei $datei): void
    {
        $einsetzen->execute([
            ':relativer_pfad' => $datei->relativerPfad,
            ':datei_name' => $datei->dateiName,
            ':datei_erweiterung' => $datei->dateiErweiterung,
            ':code' => $datei->code,
            ':code_hash' => $datei->codeHash,
            ':datei_groesse' => $datei->dateiGroesse,
            ':datei_mtime' => $this->zeitstempelFromDatei($datei->geaendert),
        ]);
    }

    private function bestehendeZeilen(): array
    {
        $bestehende = [];
        $stmt = $this->pdo()->query('SELECT id, relativer_pfad, code_hash FROM code_datei');
        foreach ($stmt === false ? [] : $stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $bestehende[(string) $row['relativer_pfad']] = ['id' => (int) $row['id'], 'hash' => (string) $row['code_hash']];
        }
        return $bestehende;
    }

    private function hydrate(array $row): CodeDatei
    {
        return new CodeDatei(
            (int) $row['id'],
            (string) $row['relativer_pfad'],
            (string) $row['datei_name'],
            (string) $row['datei_erweiterung'],
            (string) $row['code'],
            (string) $row['code_hash'],
            (int) $row['datei_groesse'],
            (string) $row['datei_mtime'],
            (string) $row['updated_at'],
            (string) $row['created_at']
        );
    }

    private function zeitstempelFromDatei(string $iso): string
    {
        return gmdate('Y-m-d H:i:s', strtotime($iso) ?: time());
    }

    private function pdo(): PDO
    {
        return $this->pdo ??= $this->factory->create();
    }
}