Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
/**
* Deckt ab, dass Controller nur eigene Methoden aufrufen, die es auch gibt.
*
* `php -l` prüft die Syntax, nicht die Auflösung von Methoden: Ein Aufruf auf
* eine nicht vorhandene Methode fällt erst beim Seitenaufruf auf und endet dort
* als interner Fehler. Diese Prüfung zieht den Befund auf die Testzeit vor.
*/
return static function (): void {
/** @return list<string> Alle Controller-Dateien */
$dateien = static function (): array {
$liste = [];
$lauf = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
dirname(__DIR__) . '/src/Controllers',
FilesystemIterator::SKIP_DOTS
)
);
foreach ($lauf as $datei) {
if ($datei->isFile() && $datei->getExtension() === 'php') {
$liste[] = $datei->getPathname();
}
}
sort($liste);
return $liste;
};
/** Voll qualifizierter Klassenname einer Datei. */
$klasseVon = static function (string $datei): ?string {
$inhalt = (string)file_get_contents($datei);
if (!preg_match('/^namespace\s+([^;]+);/m', $inhalt, $ns)) {
return null;
}
if (!preg_match('/^(?:final\s+|abstract\s+)?class\s+(\w+)/m', $inhalt, $cl)) {
return null;
}
return trim($ns[1]) . '\\' . $cl[1];
};
/**
* Alle auf $this aufgerufenen Methodennamen einer Datei.
* Aufrufe auf andere Objekte bleiben außen vor – deren Klasse steht hier
* nicht fest.
*
* @return list<string>
*/
$aufrufe = static function (string $datei): array {
$inhalt = (string)file_get_contents($datei);
preg_match_all('/\$this->([a-zA-Z_]\w*)\s*\(/', $inhalt, $m);
return array_values(array_unique($m[1]));
};
// ----------------------------------------------------------------
// Die Prüfung
// ----------------------------------------------------------------
test('Jeder Controller-Aufruf auf $this trifft eine vorhandene Methode', function () use ($dateien, $klasseVon, $aufrufe) {
$geprueft = 0;
$fehlend = [];
foreach ($dateien() as $datei) {
$klasse = $klasseVon($datei);
if ($klasse === null || !class_exists($klasse)) {
continue;
}
$reflex = new ReflectionClass($klasse);
if ($reflex->isAbstract()) {
continue;
}
// Klassen mit __call lösen Methoden zur Laufzeit auf.
if ($reflex->hasMethod('__call')) {
continue;
}
foreach ($aufrufe($datei) as $name) {
$geprueft++;
if (!$reflex->hasMethod($name)) {
$fehlend[] = $reflex->getShortName() . '::' . $name . '()';
}
}
}
assertTrue($geprueft > 200, 'Zu wenige Aufrufe geprüft – die Sammlung greift nicht');
assertSame([], $fehlend, 'Nicht vorhandene Methoden: ' . implode(', ', $fehlend));
});
// ----------------------------------------------------------------
// Der konkrete Fall
// ----------------------------------------------------------------
test('Die Budget-Einstellungen können ihren Wert lesen und schreiben', function () {
// Die Seite liest den Standard-Steuersatz und schreibt ihn zurück;
// beides läuft über system_settings.
$klasse = new ReflectionClass(\OpenNIT\Controllers\Admin\BudgetAdminController::class);
foreach (['getSetting', 'saveSetting'] as $name) {
assertTrue($klasse->hasMethod($name), 'Fehlt: ' . $name);
}
$src = (string)file_get_contents(
dirname(__DIR__) . '/src/Controllers/Admin/BudgetAdminController.php'
);
assertTrue(str_contains($src, 'SystemSetting($this->db())'), 'Die Ablage weicht vom üblichen Weg ab');
});
};