96 lines
3.4 KiB
PHP
96 lines
3.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Test 99: Klassen-Importe in den Views (Fix für /suppliers, HTTP 500)
|
||
*
|
||
* Views werden per include eingebunden und laufen dadurch im globalen
|
||
* Namespace – das `use Steuerkram\View;` des einbindenden Controllers gilt
|
||
* dort nicht. Fehlt der Import in der View selbst, wird `View::formatDate()`
|
||
* zu `\View::formatDate()` und die Seite stirbt mit "Class View not found".
|
||
*
|
||
* Der Fehler bleibt so lange unsichtbar, wie die betroffene Zeile nicht
|
||
* ausgeführt wird (in suppliers/list.php stand sie in einer foreach-Schleife,
|
||
* schlug also erst mit dem ersten Lieferanten zu). Dieser Test prüft alle
|
||
* Views statisch, damit ein fehlender Import nicht erst im Betrieb auffällt.
|
||
*/
|
||
class ViewKlassenImportTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 99: Klassen-Importe in den Views';
|
||
|
||
public function run(): void
|
||
{
|
||
$viewDir = realpath(__DIR__ . '/../src/Views');
|
||
$this->assert($viewDir !== false, 'src/Views/ existiert');
|
||
|
||
$dateien = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($viewDir));
|
||
$geprueft = 0;
|
||
$fehler = [];
|
||
|
||
foreach ($dateien as $datei) {
|
||
if (!$datei->isFile() || $datei->getExtension() !== 'php') {
|
||
continue;
|
||
}
|
||
|
||
$inhalt = file_get_contents($datei->getPathname());
|
||
$relativ = str_replace($viewDir . '/', '', $datei->getPathname());
|
||
$geprueft++;
|
||
|
||
foreach ($this->unaufgeloesteKlassen($inhalt) as $klasse) {
|
||
$fehler[] = "$relativ verwendet $klasse:: ohne Import";
|
||
}
|
||
}
|
||
|
||
$this->assertGreaterThan(20, $geprueft, 'Es wurden Views eingelesen');
|
||
$this->assert(
|
||
$fehler === [],
|
||
'Jede View importiert die Klassen, die sie statisch aufruft'
|
||
. ($fehler === [] ? '' : ': ' . implode('; ', $fehler))
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Liefert die in $inhalt statisch aufgerufenen Klassennamen, die weder
|
||
* global existieren noch per use-Statement importiert sind.
|
||
*
|
||
* @return string[]
|
||
*/
|
||
private function unaufgeloesteKlassen(string $inhalt): array
|
||
{
|
||
preg_match_all(
|
||
'/\buse\s+([A-Za-z0-9_\\\\]+)(?:\s+as\s+([A-Za-z0-9_]+))?\s*;/i',
|
||
$inhalt,
|
||
$useTreffer,
|
||
PREG_SET_ORDER
|
||
);
|
||
|
||
$importiert = [];
|
||
foreach ($useTreffer as $treffer) {
|
||
$teile = explode('\\', $treffer[1]);
|
||
$name = $treffer[2] ?? end($teile);
|
||
$importiert[$name] = true;
|
||
}
|
||
|
||
// Aufrufe wie View::formatDate(), aber nicht \Steuerkram\View::,
|
||
// $obj::, self::/static::/parent::
|
||
preg_match_all('/(?<![\\\\$\w>])([A-Z][A-Za-z0-9_]*)\s*::/', $inhalt, $aufrufe);
|
||
|
||
$offen = [];
|
||
foreach (array_unique($aufrufe[1]) as $klasse) {
|
||
if (isset($importiert[$klasse])) {
|
||
continue;
|
||
}
|
||
// Global existierende Klassen (DateTime, PDO, …) sind unkritisch.
|
||
if (class_exists($klasse, false) || interface_exists($klasse, false) || enum_exists($klasse, false)) {
|
||
continue;
|
||
}
|
||
if (class_exists('\\' . $klasse) || interface_exists('\\' . $klasse)) {
|
||
continue;
|
||
}
|
||
$offen[] = $klasse;
|
||
}
|
||
|
||
return $offen;
|
||
}
|
||
}
|