SystemRealitaet.php
Pfad: src/Infrastructure/Reconcile/SystemRealitaet.php
Ext: php
Größe: 4301 Bytes
Geändert: 2026-07-16 01:15:14+02
<?php
declare(strict_types=1);
namespace Demo\Infrastructure\Reconcile;
use Demo\Domain\Abgleich\Abweichung;
use Demo\Domain\Abgleich\RealitaetsPruefung;
use Demo\Infrastructure\Http\Routen;
use Demo\Infrastructure\Persistence\PdoConnectionFactory;
use PDO;
/**
* Prueft Artefakte gegen die reale Umgebung: DB-Schema, DB-Funktionen (pg_proc),
* Dateisystem, geladene Klassen und die Routen-Registry.
*/
final class SystemRealitaet implements RealitaetsPruefung
{
/** @var PdoConnectionFactory Verbindungs-Fabrik. */
private PdoConnectionFactory $factory;
/** @var Routen Registry der bekannten Routen. */
private Routen $routen;
/** @var PDO|null Zwischengespeicherte Verbindung. */
private ?PDO $pdo = null;
/**
* @param PdoConnectionFactory $factory Injizierte Verbindungs-Fabrik.
* @param Routen $routen Routen-Registry.
*/
public function __construct(PdoConnectionFactory $factory, Routen $routen)
{
$this->factory = $factory;
$this->routen = $routen;
}
/**
* @inheritDoc
*/
public function pruefe(string $typ, string $bezeichner): string
{
$vorhanden = match ($typ) {
'datenbank' => $this->skalar('SELECT 1 WHERE current_database() = ?', [$bezeichner]),
'tabelle' => $this->schemaObjekt($bezeichner, 2),
'spalte' => $this->schemaObjekt($bezeichner, 3),
'datei' => is_file($bezeichner),
'code_symbol' => $this->codeSymbol($bezeichner),
'db_funktion' => $this->dbFunktion($bezeichner),
'schnittstelle' => in_array($bezeichner, $this->routen->alle(), true),
default => null,
};
if ($vorhanden === null) {
return Abweichung::NICHT_PRUEFBAR;
}
return $vorhanden ? Abweichung::VORHANDEN : Abweichung::FEHLT_REAL;
}
/**
* Prueft ein Schema-Objekt (Tabelle schema.tabelle oder Spalte schema.tabelle.spalte).
*
* @param string $bezeichner Punkt-getrennter Bezeichner.
* @param int $teile Erwartete Anzahl Segmente (2=Tabelle, 3=Spalte).
* @return bool Ob das Objekt existiert.
*/
private function schemaObjekt(string $bezeichner, int $teile): bool
{
$t = explode('.', $bezeichner);
if (count($t) !== $teile) {
return false;
}
if ($teile === 2) {
return $this->skalar(
'SELECT 1 FROM information_schema.tables WHERE table_schema = ? AND table_name = ?',
$t
);
}
return $this->skalar(
'SELECT 1 FROM information_schema.columns '
. 'WHERE table_schema = ? AND table_name = ? AND column_name = ?',
$t
);
}
/**
* Prueft eine DB-Funktion (Bezeichner schema.funktion) gegen pg_proc.
*
* @param string $bezeichner schema.funktion.
* @return bool Ob die Funktion existiert.
*/
private function dbFunktion(string $bezeichner): bool
{
$t = explode('.', $bezeichner);
if (count($t) !== 2) {
return false;
}
return $this->skalar(
'SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace '
. 'WHERE n.nspname = ? AND p.proname = ?',
$t
);
}
/**
* Prueft ein Code-Symbol (Klasse oder Klasse::methode).
*
* @param string $bezeichner Voll qualifizierter Name.
* @return bool Ob Klasse (und ggf. Methode) existiert.
*/
private function codeSymbol(string $bezeichner): bool
{
$t = explode('::', $bezeichner, 2);
if (!class_exists($t[0])) {
return false;
}
return count($t) === 1 || method_exists($t[0], $t[1]);
}
/**
* Fuehrt eine parametrisierte Existenzabfrage aus.
*
* @param string $sql SQL mit Platzhaltern.
* @param list<string> $params Parameter.
* @return bool Ob mindestens eine Zeile zurueckkommt.
*/
private function skalar(string $sql, array $params): bool
{
$this->pdo ??= $this->factory->create();
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn() !== false;
}
}