Files
breadcrumb-the-shire/docs/ARCHITECTURE.md
2026-02-11 19:28:12 +01:00

720 lines
20 KiB
Markdown

# IMVS - Architektur und Grundprinzipien
## Inhaltsverzeichnis
1. [Projektübersicht](#1-projektübersicht)
2. [Framework: MintyPHP](#2-framework-mintyphp)
3. [Verzeichnisstruktur](#3-verzeichnisstruktur)
4. [Datenbankarchitektur](#4-datenbankarchitektur)
5. [PHP-Architektur](#5-php-architektur)
6. [Frontend-Architektur](#6-frontend-architektur)
7. [Template-System](#7-template-system)
8. [Authentifizierung und Autorisierung](#8-authentifizierung-und-autorisierung)
9. [Internationalisierung](#9-internationalisierung)
10. [Best Practices](#10-best-practices)
---
## 1. Projektübersicht
**Projektname:** IMVS
**Framework:** MintyPHP v3+
**PHP-Version:** 8.5+
**Datenbank:** MySQL 5.7+
**Lizenz:** MIT
### Kernfeatures
- Multi-Tenant-Architektur
- Rollen- und Berechtigungssystem
- Mehrsprachigkeit (DE/EN)
- Dark/Light Theme
- E-Mail-Integration mit Templates
---
## 2. Framework: MintyPHP
### Philosophie
MintyPHP ist ein leichtgewichtiges PHP-Framework mit folgenden Prinzipien:
- **Easy to learn** - Einfach für PHP-Entwickler zu erlernen
- **Secure by design** - Sicherheit als Grundprinzip
- **Lightweight** - Minimaler Overhead
- **Single variable scope** - Eine Variable-Scope für alle Layer
- **SQL required** - Direktes SQL statt ORM
- **PHP as templating** - PHP selbst als Template-Sprache
### Kern-Komponenten
| Komponente | Beschreibung |
|------------|--------------|
| `Router` | URL-Routing zu Pages |
| `DB` | Datenbankzugriff mit Prepared Statements |
| `Auth` | Basis-Authentifizierung |
| `Session` | Session-Management |
| `Buffer` | Output-Buffering für Templates |
| `I18n` | Internationalisierung |
| `Cache` | Memcached-Integration |
| `Firewall` | DDoS-Schutz |
### Router-Konvention
```
URL: /admin/users/edit/abc-123
Datei: pages/admin/users/edit($id).php
Variable: $id = 'abc-123'
```
---
## 3. Verzeichnisstruktur
```
ImmobilienMaklerVerkaufssoftware/
├── config/ # Konfigurationsdateien
│ ├── config.php # Hauptkonfiguration
│ ├── router.php # Route-Definitionen
│ └── settings.php # App-Einstellungen (generiert)
├── db/ # Datenbank
│ └── init/init.sql # Schema und Seeds
├── docker/ # Docker-Konfiguration
├── docs/ # Dokumentation
├── i18n/ # Sprachdateien
│ ├── default_de.json
│ └── default_en.json
├── lib/ # Geschäftslogik
│ ├── Http/ # HTTP-Utilities
│ ├── Repository/ # Datenbankschicht
│ ├── Service/ # Business-Logic
│ └── Support/ # Helper und Guards
├── pages/ # Seiten (Controller)
│ ├── admin/ # Geschützte Admin-Seiten
│ ├── auth/ # Authentifizierung
│ ├── error/ # Fehlerseiten
│ └── page/ # CMS-Seiten
├── storage/ # Dateiablage
│ ├── branding/ # Logos, Favicons
│ ├── tenants/ # Tenant-Dateien
│ └── users/ # User-Avatare
├── templates/ # View-Templates
│ ├── partials/ # Wiederverwendbare Teile
│ └── emails/ # E-Mail-Templates
├── tests/ # PHPUnit Tests
├── vendor/ # Composer Dependencies
└── web/ # Public Root
├── css/ # Stylesheets
├── js/ # JavaScript-Module
├── vendor/ # Frontend-Libraries
└── index.php # Entry Point
```
---
## 4. Datenbankarchitektur
### Entity-Relationship-Diagramm
```
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ USERS │────<│ USER_TENANTS │>────│ TENANTS │
└─────────────┘ └─────────────────┘ └─────────────┘
│ │
│ ┌─────────────────┐ │
└────────────<│ USER_ROLES │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ ROLES │ │
│ └─────────────┘ │
│ │ │
│ ┌─────────────────┐ │
│ │ROLE_PERMISSIONS │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ PERMISSIONS │ │
│ └─────────────┘ │
│ │
│ ┌─────────────────┐ ┌──────┴──────┐
└────────────<│USER_DEPARTMENTS │>────│ DEPARTMENTS │
└─────────────────┘ └─────────────┘
┌──────┴──────┐
│TENANT_DEPTS │
└─────────────┘
```
### Haupttabellen
#### users
```sql
id INT PRIMARY KEY AUTO_INCREMENT
uuid VARCHAR(36) UNIQUE
first_name VARCHAR(100)
last_name VARCHAR(100)
email VARCHAR(255) UNIQUE
password VARCHAR(255) -- bcrypt hash
locale VARCHAR(10) -- 'de', 'en'
theme VARCHAR(20) -- 'light', 'dark'
active TINYINT DEFAULT 1
primary_tenant_id INT FK -> tenants.id
current_tenant_id INT FK -> tenants.id
created_by INT FK -> users.id
modified_by INT FK -> users.id
created DATETIME
modified DATETIME
```
#### tenants
```sql
id INT PRIMARY KEY AUTO_INCREMENT
uuid VARCHAR(36) UNIQUE
description VARCHAR(255) -- Name/Firma
address VARCHAR(255)
postal_code VARCHAR(20)
city VARCHAR(100)
country VARCHAR(100)
vat_id VARCHAR(50) -- MwSt-ID
tax_number VARCHAR(50)
phone VARCHAR(50)
email VARCHAR(255)
website VARCHAR(255)
created_by INT FK -> users.id
modified_by INT FK -> users.id
created DATETIME
modified DATETIME
```
#### roles
```sql
id INT PRIMARY KEY AUTO_INCREMENT
uuid VARCHAR(36) UNIQUE
description VARCHAR(255) -- Rollenname
created_by INT FK -> users.id
modified_by INT FK -> users.id
created DATETIME
modified DATETIME
```
#### permissions
```sql
id INT PRIMARY KEY AUTO_INCREMENT
key VARCHAR(100) UNIQUE -- z.B. 'users.view'
description VARCHAR(255)
created DATETIME
```
#### departments
```sql
id INT PRIMARY KEY AUTO_INCREMENT
uuid VARCHAR(36) UNIQUE
description VARCHAR(255) -- Abteilungsname
created_by INT FK -> users.id
modified_by INT FK -> users.id
created DATETIME
modified DATETIME
```
### Verknüpfungstabellen
| Tabelle | Beschreibung |
|---------|--------------|
| `user_tenants` | User ↔ Tenant (M:N) |
| `user_roles` | User ↔ Role (M:N) |
| `user_departments` | User ↔ Department (M:N) |
| `role_permissions` | Role ↔ Permission (M:N) |
| `tenant_departments` | Tenant ↔ Department (M:N) |
### Vordefinierte Permissions
```
users.view, users.create, users.update, users.delete
users.self_update, users.update_assignments
tenants.view, tenants.create, tenants.update, tenants.delete
departments.view, departments.create, departments.update, departments.delete
roles.view, roles.create, roles.update, roles.delete
permissions.view, permissions.create, permissions.update, permissions.delete
settings.view, settings.update
mail_log.view
stats.view
```
---
## 5. PHP-Architektur
### Schichten-Architektur
```
┌─────────────────────────────────────────────┐
│ Pages │
│ (Controller/Actions) │
├─────────────────────────────────────────────┤
│ Services │
│ (Business Logic, Validation) │
├─────────────────────────────────────────────┤
│ Repositories │
│ (Data Access, SQL) │
├─────────────────────────────────────────────┤
│ DB │
│ (MintyPHP Database) │
└─────────────────────────────────────────────┘
```
### Service-Layer (`lib/Service/`)
Services implementieren die Geschäftslogik:
```php
namespace MintyPHP\Service;
class UserService
{
// Listen
public static function list(): array
public static function listPaged(array $options): array
// Finder
public static function findByUuid(string $uuid): ?array
public static function findById(int $id): ?array
public static function findByEmail(string $email): ?array
// CRUD
public static function createFromAdmin(array $input, int $currentUserId): array
public static function updateFromAdmin(int $userId, array $input, int $currentUserId): array
public static function deleteByUuid(string $uuid, int $currentUserId): array
// Bulk-Operationen
public static function deleteByUuids(array $uuids, int $currentUserId): array
public static function setActiveByUuids(array $uuids, bool $active, int $currentUserId): array
}
```
**Wichtige Services:**
| Service | Verantwortung |
|---------|---------------|
| `AuthService` | Login, Logout, Session-Management |
| `UserService` | Benutzerverwaltung |
| `TenantService` | Mandantenverwaltung |
| `RoleService` | Rollenverwaltung |
| `PermissionService` | Berechtigungsprüfung und -verwaltung |
| `DepartmentService` | Abteilungsverwaltung |
| `MailService` | E-Mail-Versand |
| `SettingService` | App-Einstellungen |
### Repository-Layer (`lib/Repository/`)
Repositories kapseln Datenbankzugriffe:
```php
namespace MintyPHP\Repository;
class UserRepository
{
public static function list(): array
{
return DB::select('SELECT * FROM users ORDER BY last_name, first_name');
}
public static function find(int $id): ?array
{
return DB::selectOne('SELECT * FROM users WHERE id = ?', [$id]);
}
public static function create(array $data): int
{
return DB::insert('users', $data);
}
public static function update(int $id, array $data): bool
{
return DB::update('users', $data, ['id' => $id]) > 0;
}
}
```
### Support-Layer (`lib/Support/`)
#### Guard - Zugriffskontrolle
```php
namespace MintyPHP\Support;
class Guard
{
// Nur für angemeldete Benutzer
public static function requireLogin(string $redirect = 'login'): void
// Spezifische Berechtigung erforderlich
public static function requirePermission(string $permissionKey, string $redirect = 'admin'): void
// Berechtigung oder 403 (für AJAX)
public static function requirePermissionOrForbidden(string $permissionKey): void
}
```
#### Flash - Session-Nachrichten
```php
namespace MintyPHP\Support;
class Flash
{
public static function success(string $message, ?string $scope = null, ?string $key = null): string
public static function error(string $message, ?string $scope = null, ?string $key = null): string
public static function info(string $message, ?string $scope = null, ?string $key = null): string
public static function peek(?string $scope = null): array
public static function has(): bool
}
```
### Helper-Funktionen (`lib/Support/helpers/`)
```php
// HTML-Escape
e($string);
// Asset-URL
asset('css/style.css');
// Lokalisierte URL
lurl('admin/users');
// Übersetzung
t('Hello');
t('Welcome %s', $name);
// App-Einstellung
appSetting('app_title');
// Berechtigung prüfen
can('users.view');
```
---
## 6. Frontend-Architektur
### CSS-Struktur (`web/css/`)
```
web/css/
├── base/
│ ├── variables.base.css # CSS-Variablen (Basis + Light/Dark)
│ ├── variables.theme-dark-green.css # Theme-Overrides
│ └── variables.contrast.css # High-Contrast Overrides
├── layout/
│ ├── app-shell.css # Haupt-Layout
│ ├── app-topbar.css # Header
│ ├── app-sidebar.css # Navigation
│ └── app-aside-icon-bar.css
├── components/
│ ├── app-buttons.css
│ ├── app-forms.css
│ ├── app-list-table.css
│ ├── app-badges.css
│ ├── app-tabs.css
│ ├── app-flash.css
│ └── vendor-*.css # Third-Party
└── pages/
└── app-login.css
```
### JavaScript-Module (`web/js/`)
ES-Module-Architektur:
```
web/js/
├── app-init.js # Haupt-Initialisierung
├── core/
│ └── app-grid-factory.js # Grid/Tabellen-Generator
├── components/
│ ├── app-tenant-switcher.js
│ ├── app-theme-toggle.js
│ ├── app-bulk-selection.js
│ ├── app-flash-auto-dismiss.js
│ └── ...
└── pages/
├── app-users-list.js
└── app-list-utils.js
```
### Modul-Pattern
```javascript
// web/js/components/app-tenant-switcher.js
const switchTenant = async (link) => {
const tenantId = link.dataset.switchTenant;
// ...
};
const initTenantSwitcher = () => {
const tenantLinks = document.querySelectorAll('[data-switch-tenant]');
tenantLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
switchTenant(link);
});
});
};
// Auto-Init bei DOM Ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTenantSwitcher);
} else {
initTenantSwitcher();
}
```
### Import in app-init.js
```javascript
// web/js/app-init.js
import './components/app-tenant-switcher.js';
import './components/app-theme-toggle.js';
import './components/app-flash-auto-dismiss.js';
// ...
```
---
## 7. Template-System
### Struktur
```
templates/
├── default.phtml # Haupt-Layout
├── login.phtml # Login-Layout
├── error.phtml # Fehler-Layout
├── partials/
│ ├── app-topbar.phtml
│ ├── app-sidebar.phtml
│ ├── app-aside.phtml
│ ├── app-flash.phtml
│ └── app-footer.phtml
└── emails/
├── de/
│ ├── access_info.html
│ └── reset_code.html
└── en/
└── ...
```
### Template-Rendering
```php
<!-- templates/default.phtml -->
<!DOCTYPE html>
<html lang="<?php e(I18n::$locale); ?>">
<head>
<title><?php e(Buffer::get('title') ?? appTitle()); ?></title>
<link rel="stylesheet" href="<?php e(asset('css/app.css')); ?>">
</head>
<body>
<?php require 'partials/app-topbar.phtml'; ?>
<main>
<?php require 'partials/app-flash.phtml'; ?>
<?php echo Buffer::get('html'); ?>
</main>
<script type="module" src="<?php e(asset('js/app-init.js')); ?>"></script>
</body>
</html>
```
### Page mit View
```php
// pages/admin/users/index().php
use MintyPHP\Buffer;
use MintyPHP\Support\Guard;
Guard::requirePermissionOrForbidden('users.view');
$users = UserService::list();
Buffer::set('title', t('Users'));
// View wird automatisch geladen: pages/admin/users/index(default).phtml
```
---
## 8. Authentifizierung und Autorisierung
### Login-Ablauf
```
1. POST /login
├─> AuthService::login(email, password)
│ ├─> Auth::login() - Passwort-Verifikation
│ ├─> $_SESSION['user'] setzen
│ ├─> PermissionService::getUserPermissions()
│ │ └─> $_SESSION['permissions'] setzen
│ └─> AuthService::loadTenantDataIntoSession()
│ ├─> $_SESSION['available_tenants']
│ └─> $_SESSION['current_tenant']
└─> Router::redirect('admin')
```
### Berechtigungsprüfung
```php
// In Page
Guard::requirePermissionOrForbidden(PermissionService::USERS_VIEW);
// Im Template
<?php if (can('users.create')): ?>
<a href="admin/users/create">Neuer Benutzer</a>
<?php endif; ?>
```
### Session-Struktur
```php
$_SESSION = [
'user' => [
'id' => 1,
'uuid' => 'abc-123',
'email' => 'user@example.com',
'first_name' => 'Max',
'last_name' => 'Mustermann',
'locale' => 'de',
'theme' => 'dark',
],
'permissions' => [
'user_id' => 1,
'keys' => ['users.view', 'users.create', ...],
],
'available_tenants' => [
['id' => 1, 'uuid' => '...', 'description' => 'Tenant A'],
['id' => 2, 'uuid' => '...', 'description' => 'Tenant B'],
],
'current_tenant' => [
'id' => 1,
'uuid' => '...',
'description' => 'Tenant A',
],
];
```
---
## 9. Internationalisierung
### Sprachdateien
```json
// i18n/default_de.json
{
"Login": "Anmelden",
"Users": "Benutzer",
"At least %d characters": "Mindestens %d Zeichen"
}
// i18n/default_en.json
{
"Login": "Login",
"Users": "Users",
"At least %d characters": "At least %d characters"
}
```
### Verwendung
```php
// Einfache Übersetzung
t('Login')
// Mit Parametern
t('At least %d characters', 12)
// Im Template
<?php e(t('Users')); ?>
```
### Sprachauflösung
Priorität:
1. URL-Präfix (`/de/admin`, `/en/admin`)
2. Session (`$_SESSION['user']['locale']`)
3. Cookie (`locale=de`)
4. Default (`APP_LOCALE`)
---
## 10. Best Practices
### Service-Rückgabewerte
```php
// Erfolg
return [
'ok' => true,
'uuid' => $uuid,
'id' => $id,
];
// Fehler
return [
'ok' => false,
'error' => 'validation_error',
'errors' => ['email' => 'Email ist erforderlich'],
'form' => $sanitizedInput,
];
```
### Datenbankzugriff
```php
// RICHTIG - Prepared Statements
$user = DB::selectOne('SELECT * FROM users WHERE email = ?', [$email]);
// FALSCH - SQL-Injection
$user = DB::selectOne("SELECT * FROM users WHERE email = '$email'");
```
### Template-Ausgabe
```php
// RICHTIG - Escape
<?php e($user['email']); ?>
// FALSCH - XSS-Risiko
<?php echo $user['email']; ?>
```
### Passwort-Anforderungen
- Mindestens 12 Zeichen
- Mindestens 1 Großbuchstabe
- Mindestens 1 Kleinbuchstabe
- Mindestens 1 Ziffer
- Mindestens 1 Sonderzeichen
### Code-Organisation
1. **Services** - Geschäftslogik und Validierung
2. **Repositories** - Nur Datenbankzugriff, keine Logik
3. **Guards** - Zugriffskontrolle
4. **Helpers** - Wiederverwendbare Funktionen
5. **Pages** - Koordination, keine Logik
---
## Weiterführende Dokumentation
- [MintyPHP Framework](https://github.com/mevdschee/php-crud-api)
- [Docker Setup](../docker-compose.yml)
- [Database Schema](../db/init/init.sql)