Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik, Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und ausschaltbares Debug-Log mit Maskierung von Zugangsdaten. Layout nach Vorbild des FinanzManagers: feste Navigation links, Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System. Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.
76 lines
2.2 KiB
PHP
76 lines
2.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Finanzmanager;
|
|
|
|
class Router
|
|
{
|
|
private array $routen = [];
|
|
|
|
public function add(string $methode, string $muster, callable $handler): void
|
|
{
|
|
$this->routen[] = [
|
|
'methode' => strtoupper($methode),
|
|
'muster' => $muster,
|
|
'handler' => $handler,
|
|
];
|
|
}
|
|
|
|
public function get(string $muster, callable $handler): void
|
|
{
|
|
$this->add('GET', $muster, $handler);
|
|
}
|
|
|
|
public function post(string $muster, callable $handler): void
|
|
{
|
|
$this->add('POST', $muster, $handler);
|
|
}
|
|
|
|
public function dispatch(): void
|
|
{
|
|
$methode = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
|
|
|
// Formulare können die Methode über ein verstecktes Feld überschreiben.
|
|
if ($methode === 'POST') {
|
|
$override = $_POST['_method'] ?? $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? null;
|
|
if ($override) {
|
|
$methode = strtoupper((string)$override);
|
|
}
|
|
}
|
|
|
|
$pfad = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
|
$pfad = '/' . trim($pfad, '/');
|
|
|
|
// Jeder verändernde Request braucht ein gültiges CSRF-Token.
|
|
if (in_array($methode, ['POST', 'PUT', 'DELETE', 'PATCH'], true)) {
|
|
Auth::pruefeCsrfToken((string)($_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''));
|
|
}
|
|
|
|
foreach ($this->routen as $route) {
|
|
if ($route['methode'] !== $methode) {
|
|
continue;
|
|
}
|
|
$params = self::passt($route['muster'], $pfad);
|
|
if ($params !== null) {
|
|
($route['handler'])($params);
|
|
return;
|
|
}
|
|
}
|
|
|
|
http_response_code(404);
|
|
(new View())->render('layout/404.php', ['pfad' => $pfad]);
|
|
}
|
|
|
|
/** Übersetzt `/konten/{id}` in einen regulären Ausdruck mit benannten Gruppen. */
|
|
public static function passt(string $muster, string $pfad): ?array
|
|
{
|
|
$regex = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $muster);
|
|
|
|
if (preg_match('#^' . $regex . '$#', $pfad, $treffer)) {
|
|
return array_filter($treffer, 'is_string', ARRAY_FILTER_USE_KEY);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|