← Code-Übersicht

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;
    }
}