446 lines
12 KiB
Markdown
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.
|