Files
nexus/BASE_FILES.md
2026-03-19 02:01:54 +01:00

446 lines
12 KiB
Markdown

# 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.php`
- `config/prod/settings.php`
- `config/staging/domaindata.php`
- `config/staging/settings.php`
Vor der Neuerstellung ist das Repository ausserdem auf den neuen Projektstand zurueckzusetzen:
- alle bestehenden Dateien und Ordner loeschen
- `.gitlab-ci.yml` ausdruecklich behalten
- `.gitlab-ci.yml` anschliessend auf alte Domainreferenzen, Umgebungs-URLs und projektspezifische Angaben pruefen
- gefundene Domainreferenzen in `.gitlab-ci.yml` auf 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
<?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
<?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
<?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
<?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
<?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
<?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_BASIC_AUTH', 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
<?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_BASIC_AUTH', false);
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
<?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
<?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
<?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`
```php
<div class="card">
<h1>Projektstart</h1>
<p>Die Grundstruktur wurde erfolgreich erstellt.</p>
</div>
```
## Datei: `partials/landingpages/404.php`
```php
<div class="card">
<h1>404</h1>
<p>Die angeforderte Seite wurde nicht gefunden.</p>
</div>
```
## Datei: `partials/structure/layout_start.php`
```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`
```php
</main>
<script src="/assets/js/app.js?v=<?= urlencode(app()->config()->assetVersion) ?>" defer></script>
</body>
</html>
```
## Datei: `public/assets/css/app.css`
```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`
```js
document.documentElement.classList.add('js');
```
## Datei: `public/.htaccess`
```apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
```
## Empfohlene Startreihenfolge in einem neuen Projekt
1. Neues GitLab-Projekt anlegen.
2. Repository lokal mit VS Code verknuepfen.
3. Alle bestehenden Dateien und Ordner entfernen, `.gitlab-ci.yml` jedoch behalten.
4. `GENERAL.md` und `BASE_FILES.md` in das neue Repository kopieren.
5. `.gitlab-ci.yml` auf alte Domain- und Projektreferenzen pruefen und anpassen.
6. Pflichtstruktur aus `GENERAL.md` anlegen.
7. Basisdateien aus `BASE_FILES.md` anlegen.
8. Live- und Staging-Domain einsetzen.
9. 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.