12 KiB
Basisdateien fuer neue Projekte
Dieses Dokument enthaelt die Basisdateien, die bei einem neuen Projekt direkt angelegt werden sollen. Der Schwerpunkt liegt auf den Config-Dateien, weil diese fuer den ersten lauffaehigen Stand zwingend benoetigt werden.
Dieses Dokument ist dafuer gedacht, zusammen mit GENERAL.md in ein neues GitLab-Projekt kopiert zu werden. Danach kann die Projektstruktur inklusive Basisdateien direkt erstellt werden.
Wichtige Regel vor der Erstellung
Ein neues Projekt darf nur erstellt werden, wenn beide Domains bekannt sind:
- Live-Domain
- Staging-Domain
Wenn eine oder beide Angaben fehlen, muss die Erstellung gestoppt werden. Vor dem Anlegen der Dateien ist dann explizit nach beiden Domains zu fragen.
Ohne beide Domains duerfen insbesondere diese Dateien nicht final erzeugt werden:
config/prod/domaindata.phpconfig/prod/settings.phpconfig/staging/domaindata.phpconfig/staging/settings.php
Vor der Neuerstellung ist das Repository ausserdem auf den neuen Projektstand zurueckzusetzen:
- alle bestehenden Dateien und Ordner loeschen
.gitlab-ci.ymlausdruecklich behalten.gitlab-ci.ymlanschliessend auf alte Domainreferenzen, Umgebungs-URLs und projektspezifische Angaben pruefen- gefundene Domainreferenzen in
.gitlab-ci.ymlauf die neue Live- und Staging-Domain anpassen
Platzhalter fuer neue Projekte
In den folgenden Vorlagen werden diese Platzhalter verwendet:
<LIVE_DOMAIN>fuer die Produktiv-Domain<STAGING_DOMAIN>fuer die Staging-Domain<APP_PREFIX>fuer den Cookie- und App-Prefix<APP_NAME>fuer einen allgemeinen Projektnamen oder OIDC-Client-Namen
Datei: config/fileload.php
<?php
declare(strict_types=1);
spl_autoload_register(function ($class) {
if (str_starts_with($class, 'App\\Repository\\')) {
$prefix = 'App\\Repository\\';
$baseDir = __DIR__ . '/../src/Repository/';
} elseif (str_starts_with($class, 'App\\')) {
$prefix = 'App\\';
$baseDir = __DIR__ . '/../src/App/';
} else {
return;
}
$len = strlen($prefix);
$relativeClass = substr($class, $len);
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
if (file_exists($file)) {
require $file;
}
});
require_once __DIR__ . '/../src/App/functions.php';
$domainFile = __DIR__ . '/domaindata.php';
$settingsFile = __DIR__ . '/settings.php';
$configFile = __DIR__ . '/db.php';
$baseConfigFile = __DIR__ . '/base_db.php';
$fallbackBaseConfigStaging = __DIR__ . '/staging/db_settings_basic.php';
$fallbackBaseConfigProd = __DIR__ . '/prod/db_settings_basic.php';
if (file_exists($domainFile)) {
require_once $domainFile;
}
if (file_exists($settingsFile)) {
require_once $settingsFile;
}
$dbConfig = [];
if (file_exists($configFile)) {
$dbConfig = require $configFile;
}
$baseDbConfig = [];
if (file_exists($baseConfigFile)) {
$baseDbConfig = require $baseConfigFile;
}
if (empty($baseDbConfig) && file_exists($fallbackBaseConfigStaging)) {
$baseDbConfig = require $fallbackBaseConfigStaging;
}
if (empty($baseDbConfig) && file_exists($fallbackBaseConfigProd)) {
$baseDbConfig = require $fallbackBaseConfigProd;
}
global $appConfig;
$dbEnabled = defined('APP_DB_ENABLED') ? APP_DB_ENABLED : true;
$baseDbEnabled = defined('APP_BASE_DB_ENABLED') ? APP_BASE_DB_ENABLED : false;
$appConfig = new \App\Config($dbConfig, $dbEnabled, $baseDbConfig, $baseDbEnabled);
\App\App::init($appConfig);
Datei: config/config.php
<?php
declare(strict_types=1);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
foreach (['domaindata.php', 'settings.php'] as $cfgFile) {
$rootPath = __DIR__ . '/' . $cfgFile;
if (file_exists($rootPath)) {
require_once $rootPath;
} else {
throw new \RuntimeException("Missing required config file: $cfgFile (expected $rootPath)");
}
}
if (!defined('ASSET_VERSION')) {
define('ASSET_VERSION', 'dev-' . date('Ymd-His'));
}
if (!defined('APP_DOMAIN_PRIMARY')) {
define('APP_DOMAIN_PRIMARY', APP_DOMAIN_NAME);
}
if (!defined('APP_URL_PRIMARY')) {
define('APP_URL_PRIMARY', 'https://' . APP_DOMAIN_PRIMARY);
}
if (!defined('APP_API_BASE')) {
define('APP_API_BASE', 'https://api.' . APP_DOMAIN_PRIMARY);
}
if (!defined('APP_DB_ENABLED')) {
define('APP_DB_ENABLED', false);
}
Datei: config/base_db.php
<?php
declare(strict_types=1);
$path = __DIR__ . '/db_settings_basic.php';
if (!file_exists($path)) {
throw new RuntimeException('Missing base DB config: expected config/db_settings_basic.php');
}
return require $path;
Datei: config/staging/domaindata.php
<?php
declare(strict_types=1);
if (!defined('APP_DOMAIN_NAME')) {
define('APP_DOMAIN_NAME', '<STAGING_DOMAIN>');
}
if (!defined('APP_PREFIX')) {
define('APP_PREFIX', '<APP_PREFIX>');
}
Datei: config/prod/domaindata.php
<?php
declare(strict_types=1);
if (!defined('APP_DOMAIN_NAME')) {
define('APP_DOMAIN_NAME', '<LIVE_DOMAIN>');
}
if (!defined('APP_PREFIX')) {
define('APP_PREFIX', '<APP_PREFIX>');
}
Datei: config/staging/settings.php
<?php
define('ASSET_VERSION', 'dev-' . date('Ymd-His'));
define('APP_DOMAIN_PRIMARY', APP_DOMAIN_NAME);
define('APP_URL_PRIMARY', 'https://' . APP_DOMAIN_PRIMARY);
define('APP_API_BASE', 'https://api.' . APP_DOMAIN_PRIMARY);
define('APP_DB_ENABLED', false);
define('APP_DB_DEBUG', true);
define('APP_DB_AUTO_INIT', true);
define('APP_BASE_DB_ENABLED', true);
define('APP_AUTH_ENABLED', false);
define('APP_DEBUG_TOOL', true);
define('APP_AUTH_DEBUG', true);
/*
Optional fuer Projekte mit OIDC:
define('APP_OIDC_ISSUER', 'https://auth.example.tld/realms/<APP_NAME>');
define('APP_OIDC_AUTH_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/auth');
define('APP_OIDC_TOKEN_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/token');
define('APP_OIDC_USERINFO_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/userinfo');
define('APP_OIDC_LOGOUT_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/logout');
define('APP_OIDC_CLIENT_ID', '<APP_NAME>');
define('APP_OIDC_CLIENT_SECRET', 'CHANGE_ME');
define('APP_OIDC_REDIRECT_URI', 'https://<STAGING_DOMAIN>/auth/callback');
define('APP_OIDC_POST_LOGOUT_REDIRECT_URI', 'https://<STAGING_DOMAIN>/');
define('APP_OIDC_GROUP_CLAIM', 'groups');
define('APP_OIDC_ADMIN_GROUP', 'appadmin');
define('APP_OIDC_USER_GROUP', 'user');
*/
Datei: config/prod/settings.php
<?php
define('ASSET_VERSION', 'dev-' . date('Ymd-His'));
define('APP_DOMAIN_PRIMARY', APP_DOMAIN_NAME);
define('APP_URL_PRIMARY', 'https://' . APP_DOMAIN_PRIMARY);
define('APP_API_BASE', 'https://api.' . APP_DOMAIN_PRIMARY);
define('APP_DB_ENABLED', false);
define('APP_DB_DEBUG', false);
define('APP_DB_AUTO_INIT', true);
define('APP_BASE_DB_ENABLED', true);
define('APP_AUTH_ENABLED', false);
define('APP_DEBUG_TOOL', false);
define('APP_AUTH_DEBUG', false);
/*
Optional fuer Projekte mit OIDC:
define('APP_OIDC_ISSUER', 'https://auth.example.tld/realms/<APP_NAME>');
define('APP_OIDC_AUTH_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/auth');
define('APP_OIDC_TOKEN_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/token');
define('APP_OIDC_USERINFO_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/userinfo');
define('APP_OIDC_LOGOUT_ENDPOINT', 'https://auth.example.tld/realms/<APP_NAME>/protocol/openid-connect/logout');
define('APP_OIDC_CLIENT_ID', '<APP_NAME>');
define('APP_OIDC_CLIENT_SECRET', 'CHANGE_ME');
define('APP_OIDC_REDIRECT_URI', 'https://<LIVE_DOMAIN>/auth/callback');
define('APP_OIDC_POST_LOGOUT_REDIRECT_URI', 'https://<LIVE_DOMAIN>/');
define('APP_OIDC_GROUP_CLAIM', 'groups');
define('APP_OIDC_ADMIN_GROUP', 'appadmin');
define('APP_OIDC_USER_GROUP', 'user');
*/
Datei: config/staging/db_settings_basic.php
<?php
declare(strict_types=1);
return [
'dsn' => 'sqlite:' . __DIR__ . '/../../data/app_staging.sqlite',
'user' => null,
'password' => null,
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
],
];
Datei: config/prod/db_settings_basic.php
<?php
declare(strict_types=1);
return [
'dsn' => 'sqlite:' . __DIR__ . '/../../data/app_prod.sqlite',
'user' => null,
'password' => null,
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
],
];
Datei: public/index.php
Minimaler Einstiegspunkt fuer den Webzugriff:
<?php
declare(strict_types=1);
require_once __DIR__ . '/../config/fileload.php';
$uriPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
$uriPath = preg_replace('~/{2,}~', '/', $uriPath);
$uriPath = trim($uriPath, '/');
if (str_contains($uriPath, '..')) {
http_response_code(400);
exit('Bad request');
}
$pagesBase = realpath(__DIR__ . '/../partials/landingpages') ?: (__DIR__ . '/../partials/landingpages');
$page404 = $pagesBase . '/404.php';
if (preg_match('~^module/([a-zA-Z0-9_-]+)(?:/(.+))?$~', $uriPath, $m)) {
$module = $m[1];
$page = isset($m[2]) && $m[2] !== '' ? trim($m[2], '/') : 'index';
$modulePage = app()->modules()->resolvePage($module, $page);
$target = $modulePage ?: $page404;
if (!$modulePage) {
http_response_code(404);
}
} elseif ($uriPath === '' || $uriPath === 'index' || $uriPath === 'index.php') {
$target = $pagesBase . '/index.php';
} else {
$base = $pagesBase . '/' . $uriPath;
if (is_dir($base) && is_file($base . '/index.php')) {
$target = $base . '/index.php';
} elseif (is_file($base . '.php')) {
$target = $base . '.php';
} else {
http_response_code(404);
$target = $page404;
}
}
ob_start();
require $target;
$content = ob_get_clean();
tpl('layout_start', 'structure');
echo $content;
tpl('layout_end', 'structure');
Datei: partials/landingpages/index.php
<div class="card">
<h1>Projektstart</h1>
<p>Die Grundstruktur wurde erfolgreich erstellt.</p>
</div>
Datei: partials/landingpages/404.php
<div class="card">
<h1>404</h1>
<p>Die angeforderte Seite wurde nicht gefunden.</p>
</div>
Datei: partials/structure/layout_start.php
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Projektbasis</title>
<link rel="stylesheet" href="/assets/css/app.css?v=<?= urlencode(app()->config()->assetVersion) ?>">
</head>
<body>
<main class="main-content">
Datei: partials/structure/layout_end.php
</main>
<script src="/assets/js/app.js?v=<?= urlencode(app()->config()->assetVersion) ?>" defer></script>
</body>
</html>
Datei: public/assets/css/app.css
body {
margin: 0;
font-family: sans-serif;
background: #f5f5f5;
color: #111;
}
.main-content {
max-width: 960px;
margin: 0 auto;
padding: 32px 16px;
}
.card {
background: #fff;
border: 1px solid #ddd;
border-radius: 12px;
padding: 24px;
}
Datei: public/assets/js/app.js
document.documentElement.classList.add('js');
Datei: public/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
Empfohlene Startreihenfolge in einem neuen Projekt
- Neues GitLab-Projekt anlegen.
- Repository lokal mit VS Code verknuepfen.
- Alle bestehenden Dateien und Ordner entfernen,
.gitlab-ci.ymljedoch behalten. GENERAL.mdundBASE_FILES.mdin das neue Repository kopieren..gitlab-ci.ymlauf alte Domain- und Projektreferenzen pruefen und anpassen.- Pflichtstruktur aus
GENERAL.mdanlegen. - Basisdateien aus
BASE_FILES.mdanlegen. - Live- und Staging-Domain einsetzen.
- Danach erst die eigentliche Projekterstellung oder Modulentwicklung starten.
Kurzregel
Ohne GENERAL.md, ohne BASE_FILES.md oder ohne beide Domains ist die saubere Erstellung eines neuen Projekts auf dieser Basis nicht vollstaendig und soll nicht ausgefuehrt werden.