Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
/**
* Deckt die Erstinstallation ab: was der Assistent in die `.env` schreibt, welche
* Voraussetzungen er prüft und dass die Anleitungen ihn nicht aussperren.
*/
return static function (): void {
$wurzel = dirname(__DIR__);
/** Der Quelltext des Installers. */
$installer = static fn (): string => (string)file_get_contents($wurzel . '/public/install.php');
/** Der Inhalt der geschriebenen .env – der Heredoc-Block aus createEnvFile(). */
$envVorlage = static function () use ($installer): string {
$src = $installer();
$von = strpos($src, '$env = <<<ENV');
$bis = strpos($src, "\nENV;", (int)$von);
return $von === false || $bis === false ? '' : substr($src, $von, $bis - $von);
};
// ----------------------------------------------------------------
// Inhalt der erzeugten .env
// ----------------------------------------------------------------
test('Die erzeugte .env enthält keine Anmeldedaten des Administrators', function () use ($envVorlage, $installer) {
// Der Administrator wird gehasht in der Datenbank angelegt; ein zusätzlicher
// Klartext auf der Platte hätte keinen Leser und nur ein Risiko.
$env = $envVorlage();
assertFalse(str_contains($env, 'LOCAL_ADMIN_PASSWORD'), 'Passwort im Klartext in der .env');
assertFalse(str_contains($env, 'LOCAL_ADMIN_EMAIL'), 'Adresse ohne Leser in der .env');
assertFalse(str_contains($installer(), '$adminPassQ'), 'Der Wert wird weiterhin aufbereitet');
});
test('Der Administrator wird trotzdem angelegt', function () use ($installer) {
// Die Werte fallen nur aus der .env, nicht aus der Installation.
assertTrue(str_contains($installer(), 'createLocalAdmin($app[\'admin_email\'], $app[\'admin_password\']'));
});
test('Die .env trägt nur Schlüssel, die auch gelesen werden', function () use ($envVorlage, $wurzel) {
$env = $envVorlage();
preg_match_all('/^([A-Z][A-Z0-9_]+)=/m', $env, $treffer);
assertTrue($treffer[1] !== [], 'Keine Schlüssel gefunden');
// Gelesen wird über $_ENV in config/, bootstrap.php und im Quellbaum.
$gelesen = '';
foreach (['/config', '/src'] as $ordner) {
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($wurzel . $ordner));
foreach ($it as $datei) {
if ($datei->isFile() && $datei->getExtension() === 'php') {
$gelesen .= (string)file_get_contents($datei->getPathname());
}
}
}
$gelesen .= (string)file_get_contents($wurzel . '/public/index.php');
$gelesen .= (string)file_get_contents($wurzel . '/bootstrap.php');
foreach ($treffer[1] as $schluessel) {
assertTrue(
str_contains($gelesen, "'" . $schluessel . "'"),
'Ohne Leser in der .env: ' . $schluessel
);
}
});
// ----------------------------------------------------------------
// Systemvoraussetzungen
// ----------------------------------------------------------------
test('Der Installer prüft genau die Extensions aus composer.json', function () use ($installer, $wurzel) {
// Weicht die Liste ab, meldet der Assistent ein installierbares System,
// auf dem "composer install" scheitert – oder umgekehrt.
$composer = json_decode((string)file_get_contents($wurzel . '/composer.json'), true);
$gefordert = [];
foreach (array_keys($composer['require'] ?? []) as $paket) {
if (str_starts_with($paket, 'ext-')) {
$gefordert[] = substr($paket, 4);
}
}
sort($gefordert);
$von = strpos($installer(), '$requiredExtensions = [');
$bis = strpos($installer(), '];', (int)$von);
preg_match_all("/'([a-z_]+)'/", substr($installer(), (int)$von, (int)$bis - (int)$von), $treffer);
$geprueft = $treffer[1];
sort($geprueft);
assertSame($gefordert, $geprueft, 'Installer und composer.json fordern Unterschiedliches');
});
test('Die geforderte PHP-Version folgt composer.json', function () use ($installer, $wurzel) {
$composer = json_decode((string)file_get_contents($wurzel . '/composer.json'), true);
assertSame('>=8.2', $composer['require']['php'] ?? '');
assertTrue(str_contains($installer(), "version_compare(PHP_VERSION, '8.2.0', '>=')"));
});
test('Ohne vendor/ gilt die Voraussetzung als nicht erfüllt', function () use ($installer) {
// public/index.php lädt den Autoloader als Erstes – fehlt er, endet jeder
// spätere Aufruf im Serverfehler statt in einer Meldung.
assertTrue(str_contains($installer(), "is_file(BASE_PATH . '/vendor/autoload.php')"));
});
// ----------------------------------------------------------------
// Sperre und Anleitungen
// ----------------------------------------------------------------
test('Eine vorhandene .env mit APP_KEY sperrt den Installer', function () use ($installer) {
$src = $installer();
assertTrue(str_contains($src, "storage/.installed"), 'Kein Marker-Zweig');
assertTrue((bool)preg_match('/APP_KEY\\\\s\*=/', $src), 'Die .env wird nicht auf APP_KEY geprüft');
});
test('Die Sperrmeldung nennt die von Hand angelegte .env', function () use ($installer) {
// Häufigster Fall in der Praxis: gar nicht installiert, nur eine .env zu früh
// angelegt. Ohne den Hinweis wirkt die Meldung wie ein Defekt.
$src = $installer();
$treffer = substr_count($src, 'von Hand');
assertTrue($treffer >= 2, 'Der Hinweis fehlt im Web- oder im CLI-Zweig');
});
test('Keine Anleitung legt die .env vor dem Assistenten an', function () use ($wurzel) {
// Wer "cp .env.example .env" plus APP_KEY ausführt und danach install.php
// aufruft, läuft in die Sperre.
foreach (['/README.md', '/docs/INSTALLATION.md'] as $datei) {
$text = (string)file_get_contents($wurzel . $datei);
assertFalse(str_contains($text, 'cp .env.example .env'), 'Anleitung legt .env an: ' . $datei);
}
});
test('Die Anleitungen benennen, wer die .env schreibt', function () use ($wurzel) {
foreach (['/README.md', '/docs/INSTALLATION.md', '/.env.example'] as $datei) {
$text = (string)file_get_contents($wurzel . $datei);
assertTrue(
str_contains($text, 'legt die .env')
|| str_contains($text, 'legt die `.env`')
|| str_contains($text, 'schreibt die .env'),
'Ohne Hinweis auf den Assistenten: ' . $datei
);
}
});
};