PostgresCodeDateiRepository.php
Pfad: src/Infrastructure/Persistence/PostgresCodeDateiRepository.php
Ext: php
Größe: 7417 Bytes
Geändert: 2026-07-16 10:49:14+02
<?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
{
/**
* @var PdoConnectionFactory Verbindungsfabrik für die Datenbank.
*/
private PdoConnectionFactory $factory;
/** @var PDO|null Lazy-loaded PDO-Verbindung. */
private ?PDO $pdo = null;
/**
* @param PdoConnectionFactory $factory Verbindungsfabrik.
*/
public function __construct(PdoConnectionFactory $factory)
{
$this->factory = $factory;
}
/**
* @return array<int, CodeDatei> Alle aktuell bekannten Dateien.
*/
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()->prepare($sql);
$stmt->execute();
$ausgabe = [];
foreach ($stmt === false ? [] : $stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$ausgabe[] = $this->hydrate($row);
}
return $ausgabe;
}
/**
* @param int $id Primärschlüssel der gewünschten Datei.
* @return CodeDatei|null Gefundene Entität oder null.
*/
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);
}
/**
* Synchronisiert aktuellen Scanstand mit den gespeicherten Datensätzen.
*
* @param array<int,mixed> $dateien Eingelesene Dateien aus dem Scanner.
* @return void
*/
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;
}
}
/**
* Entscheidet Insert/Update anhand vorhandener Datei und Hash.
*
* @param CodeDatei $datei Zu verarbeitende Datei.
* @param array<string,array{id:int,hash:string}> &$bestehende Bereits bekannte Dateien nach Pfad.
* @param PDOStatement $einsetzen Prepared INSERT.
* @param PDOStatement $aktualisieren Prepared UPDATE.
* @return void
*/
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),
]);
}
}
/**
* Fügt eine neue Datei persistiert ein.
*
* @param PDOStatement $einsetzen Prepared INSERT.
* @param CodeDatei $datei Neue Datei.
* @return void
*/
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),
]);
}
/**
* Lädt den aktuellen Datenbestand nach Pfad mit id/hash.
*
* @return array<string,array{id:int,hash:string}> Vorhandene Datensätze.
*/
private function bestehendeZeilen(): array
{
$bestehende = [];
$stmt = $this->pdo()->prepare('SELECT id, relativer_pfad, code_hash FROM code_datei');
$stmt->execute();
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;
}
/**
* Hydriert eine Datenzeile in ein Domain-Objekt.
*
* @param array<string,mixed> $row Datenbankzeile.
* @return CodeDatei
*/
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['created_at']
);
}
/**
* Wandelt Datumsstring in UTC-Format für MySQL/Postgres-kompatible Speicherung.
*
* @param string $iso ISO Datumszeichenkette.
* @return string
*/
private function zeitstempelFromDatei(string $iso): string
{
return gmdate('Y-m-d H:i:s', strtotime($iso) ?: time());
}
/**
* Liefert die Lazy-PDO-Verbindung.
*
* @return PDO
*/
private function pdo(): PDO
{
return $this->pdo ??= $this->factory->create();
}
}
Frühere Versionen
| Version | Zeitpunkt | Operation |
|---|---|---|
| 5 | 2026-07-16 12:49:36.486735+02 | UPDATE |