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('/(?])([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; } }