src/Foo/Bar.php --- spl_autoload_register(static function (string $class): void { $prefix = 'Deploymentcenter\\'; $len = strlen($prefix); if (strncmp($class, $prefix, $len) !== 0) { return; } $relative = substr($class, $len); $path = DC_SRC . '/' . str_replace('\\', '/', $relative) . '.php'; if (is_file($path)) { require_once $path; } }); // --- Konfiguration laden --- $dcConfigFile = DC_ROOT . '/config/config.php'; if (!is_file($dcConfigFile)) { http_response_code(500); header('Content-Type: text/plain; charset=utf-8'); echo "Konfiguration fehlt.\n\n" . "Bitte config/config.example.php nach config/config.php kopieren und ausfuellen.\n"; exit(1); } /** @var array $dcConfig */ $dcConfig = require $dcConfigFile; if (!is_array($dcConfig)) { http_response_code(500); header('Content-Type: text/plain; charset=utf-8'); echo "config/config.php muss ein Array zurueckgeben.\n"; exit(1); } // --- Fassung: aus dem Changelog, nicht aus der Konfiguration --- // Die ausgelieferte Fassung ist eine Eigenschaft des Standes, keine // Einstellung der Installation. In config/config.php stand sie als // Zeichenkette - und blieb dort auf 2.0.0 stehen, waehrend laengst 2.7.0 lief. // Das war keine Schoenheitsfrage: /api/health meldete 2.0.0, Fehlermeldungen // trugen eine falsche build_version, und wer die Fassung gegen eine // Mindestanforderung prueft (dc-testkit doctor), bekam auf einem aktuellen // Server eine Absage. public/docs/changelog.json wird bei jedem Release // ohnehin gepflegt und ist damit die belastbarere Quelle. Der Wert aus der // Konfiguration bleibt Rueckfallebene, falls die Datei fehlt. // // Gelesen wird nur der Kopf der Datei: sie ist rund 55 KB gross, und dafuer // bei jedem Aufruf einen json_decode zu bezahlen, waere fuer eine einzelne // Zeichenkette zu teuer. Findet sich der Schluessel dort nicht - etwa weil // jemand die Datei umsortiert hat -, wird sie doch vollstaendig gelesen. $dcChangelogFile = DC_ROOT . '/public/docs/changelog.json'; if (is_file($dcChangelogFile)) { $dcVersion = null; $dcHead = (string)file_get_contents($dcChangelogFile, false, null, 0, 4096); if (preg_match('/"current"\s*:\s*"([^"]+)"/', $dcHead, $dcMatch) === 1) { $dcVersion = $dcMatch[1]; } else { $dcMeta = json_decode((string)file_get_contents($dcChangelogFile), true); if (is_array($dcMeta) && is_string($dcMeta['current'] ?? null)) { $dcVersion = $dcMeta['current']; } } if (is_string($dcVersion) && $dcVersion !== '') { if (!isset($dcConfig['app']) || !is_array($dcConfig['app'])) { $dcConfig['app'] = []; } $dcConfig['app']['version'] = $dcVersion; } } Deploymentcenter\Core\Config::load($dcConfig); date_default_timezone_set((string)Deploymentcenter\Core\Config::get('app.timezone', 'UTC')); // --- Fehleranzeige: niemals an den Client, immer ins Log --- $dcDebug = (bool)Deploymentcenter\Core\Config::get('app.debug', false); ini_set('display_errors', $dcDebug ? '1' : '0'); ini_set('log_errors', '1'); error_reporting(E_ALL); set_exception_handler(static function (\Throwable $e): void { // Http::fail() protokolliert die Exception bereits - hier nur die // Selbstmeldung in den eigenen Bugtracker anstossen. Deploymentcenter\Core\ErrorReporter::report($e); if (!headers_sent()) { http_response_code(500); } Deploymentcenter\Core\Http::fail(500, 'internal_error', 'Interner Serverfehler.', $e); }); register_shutdown_function(static function (): void { $err = error_get_last(); if ($err === null) { return; } if (!in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR], true)) { return; } Deploymentcenter\Core\Logger::error( sprintf('Fatal: %s in %s:%d', $err['message'], $err['file'], $err['line']) ); Deploymentcenter\Core\ErrorReporter::reportFatal($err); }); unset($dcConfigFile, $dcConfig, $dcDebug);