Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
# Architektur-Prinzipien
|
||||
|
||||
Dieses Dokument beschreibt die grundlegenden Architekturprinzipien unseres Frameworks.
|
||||
|
||||
## 1. Immutabilität und Unveränderlichkeit
|
||||
|
||||
Wo immer möglich, sollten Objekte unveränderlich (immutable) sein. Dies verbessert die Voraussagbarkeit und Testbarkeit.
|
||||
|
||||
## 2. Final by Default
|
||||
|
||||
Alle Klassen sollten standardmäßig als `final` deklariert werden, es sei denn, es gibt einen konkreten Grund für Vererbung.
|
||||
Begründung:
|
||||
- Vermeidet unbeabsichtigte Vererbungshierarchien
|
||||
- Verbessert die Kapselung
|
||||
- Ermöglicht interne Änderungen, ohne Kinderklassen zu beeinflussen
|
||||
|
||||
## 3. Explizite über Implizite
|
||||
|
||||
- Alle Abhängigkeiten sollten explizit injiziert werden
|
||||
- Keine globalen Zustände oder Singletons
|
||||
- Typen immer explizit deklarieren
|
||||
|
||||
## 4. Modularität
|
||||
|
||||
Jedes Modul sollte in sich geschlossen sein und minimale Abhängigkeiten nach außen haben.
|
||||
@@ -1,76 +0,0 @@
|
||||
# 📘 Commit-Konventionen – Conventional Commits
|
||||
|
||||
Dieses Projekt verwendet das [Conventional Commits](https://www.conventionalcommits.org)-Format für einheitliche und nachvollziehbare Commit-Nachrichten.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Format
|
||||
|
||||
```
|
||||
<type>[optional scope]: <beschreibung>
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```
|
||||
feat: add Ansible deploy playbook
|
||||
```
|
||||
|
||||
- **Englisch**
|
||||
- **Präsens** (z. B. „add“, nicht „added“)
|
||||
- **Keine abschließenden Punkte**
|
||||
- **Optionaler Body** bei größeren Änderungen
|
||||
|
||||
---
|
||||
|
||||
## 📦 Commit-Typen
|
||||
|
||||
| Typ | Beschreibung |
|
||||
|------------|--------------------------------------------------------|
|
||||
| `feat` | ✨ Neues Feature |
|
||||
| `fix` | 🐛 Fehlerbehebung |
|
||||
| `docs` | 📘 Nur Dokumentation (z. B. README, .env.example) |
|
||||
| `style` | 🎨 Formatierung, keine Änderung am Verhalten |
|
||||
| `refactor` | 🔁 Code-Umstrukturierung ohne neues Verhalten/Feature |
|
||||
| `test` | 🧪 Tests hinzufügen oder anpassen |
|
||||
| `chore` | 🔧 Projektpflege (z. B. `.gitignore`, `.mailmap`, Cleanup) |
|
||||
|
||||
---
|
||||
|
||||
## ✅ Gute Commit-Beispiele
|
||||
|
||||
```bash
|
||||
chore: initial commit with Docker + Ansible setup
|
||||
feat: add restart task to deploy role
|
||||
fix: correct Docker volume path
|
||||
docs: add .env.example as reference
|
||||
chore: add .mailmap to unify author identity
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛑 Vermeide unklare Messages wie:
|
||||
|
||||
```bash
|
||||
"update"
|
||||
"bugfixes"
|
||||
"misc"
|
||||
"more changes"
|
||||
```
|
||||
|
||||
Diese helfen später weder dir noch Tools oder anderen.
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Tipps
|
||||
|
||||
- Nutze aussagekräftige, prägnante Beschreibungen
|
||||
- Schreibe deine Commits so, dass man daraus verstehen kann, **was passiert** – ohne Git-Diff zu lesen
|
||||
- Wenn du mehrere Dinge in einem Commit machst, überlege, ob es **mehrere Commits** sein sollten
|
||||
|
||||
---
|
||||
|
||||
## 📚 Weitere Infos
|
||||
|
||||
- [conventionalcommits.org](https://www.conventionalcommits.org)
|
||||
- [semantic-release](https://semantic-release.gitbook.io/semantic-release/) – für automatische Releases basierend auf Commit-Typen
|
||||
@@ -1,43 +0,0 @@
|
||||
# 🚀 Deployment-Anleitung (Ansible-basiert)
|
||||
|
||||
Dieses Projekt verwendet Ansible zur automatisierten Bereitstellung.
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Struktur
|
||||
|
||||
- `ansible/setup.yml` → Bereitet Zielserver vor (Docker, Git, Benutzer)
|
||||
- `ansible/deploy.yml` → Clont Projekt & startet Docker Compose
|
||||
|
||||
---
|
||||
|
||||
## 📂 Vorbereitung
|
||||
|
||||
1. Zielserver (Debian)
|
||||
2. SSH-Zugang (z. B. via `~/.ssh/id_rsa`)
|
||||
3. Eintrag in `ansible/inventory.ini`:
|
||||
|
||||
```ini
|
||||
[web]
|
||||
123.123.123.123 ansible_user=root
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ▶️ Ausführen
|
||||
|
||||
```bash
|
||||
# Setup ausführen (nur einmal)
|
||||
ansible-playbook -i ansible/inventory.ini ansible/setup.yml
|
||||
|
||||
# Projekt deployen (beliebig oft)
|
||||
ansible-playbook -i ansible/inventory.ini ansible/deploy.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Hinweis
|
||||
|
||||
- `.env` wird **nicht** automatisch übertragen
|
||||
- Serverpfade ggf. per `dest:` in `git:`-Modul anpassen
|
||||
|
||||
40
docs/ENV.md
40
docs/ENV.md
@@ -1,40 +0,0 @@
|
||||
# 🔐 Umgebungsvariablen (.env)
|
||||
|
||||
Dieses Projekt verwendet `.env`-Dateien zur Konfiguration von Docker Compose und anderen Tools.
|
||||
|
||||
---
|
||||
|
||||
## 📄 Beispiel: .env
|
||||
|
||||
```env
|
||||
COMPOSE_PROJECT_NAME=michaelschiemer
|
||||
APP_PORT=8000
|
||||
PHP_VERSION=8.2
|
||||
```
|
||||
|
||||
> Diese Datei sollte **nicht** versioniert werden.
|
||||
|
||||
---
|
||||
|
||||
## 📄 Beispiel: .env.example
|
||||
|
||||
Diese Datei enthält Beispielwerte und wird mit dem Projekt mitgeliefert.
|
||||
|
||||
---
|
||||
|
||||
## 📌 Nutzung in docker-compose.yml
|
||||
|
||||
```yaml
|
||||
php:
|
||||
image: php:${PHP_VERSION}-fpm
|
||||
web:
|
||||
ports:
|
||||
- "${APP_PORT}:80"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Empfehlung
|
||||
|
||||
- `.env` → lokal, nicht versioniert
|
||||
- `.env.example` → ins Git, immer aktuell halten
|
||||
@@ -1,61 +0,0 @@
|
||||
# Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
#### Heading 4
|
||||
##### Heading 5
|
||||
###### Heading 6
|
||||
|
||||
*italics*
|
||||
|
||||
**bold**
|
||||
|
||||
***italics & bold***
|
||||
|
||||
~~crossed off~~
|
||||
|
||||
<mark>highlight</mark>
|
||||
|
||||
<sub>Test</sub>
|
||||
|
||||
`monospace`
|
||||
|
||||
```html
|
||||
|
||||
<html>
|
||||
<header></header>
|
||||
|
||||
<footer></footer>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
[This is a link](https://localhost)
|
||||
|
||||

|
||||
|
||||
> This is a blockquote
|
||||
>> and it can be nested
|
||||
|
||||
***
|
||||
|
||||
---
|
||||
|
||||
___
|
||||
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
3. Item 3
|
||||
|
||||
* Test
|
||||
* Test
|
||||
* Subitem
|
||||
* Subitem
|
||||
|
||||
| Column1 | Column 2 |
|
||||
| --- |-----|
|
||||
| Test | Test2 |
|
||||
|
||||
- [ ] Test
|
||||
- [x] Test 2
|
||||
107
docs/README.md
107
docs/README.md
@@ -1,77 +1,68 @@
|
||||
# Projekt-Dokumentation
|
||||
# Framework Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Willkommen zur Dokumentation des Projekts. Diese Dokumentation dient als zentrale Informationsquelle für Entwickler, die am Projekt arbeiten.
|
||||
Willkommen zur Dokumentation des Frameworks. Diese Dokumentation bietet umfassende Informationen zur Installation, Konfiguration und Verwendung des Frameworks sowie detaillierte Beschreibungen aller Komponenten und Funktionen.
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
## Dokumentationsstruktur
|
||||
|
||||
### Standards und Guidelines
|
||||
Die Dokumentation ist in folgende Hauptbereiche gegliedert:
|
||||
|
||||
- [Coding Guidelines](/docs/standards/CODING-GUIDELINES.md) - Allgemeine Coding-Standards für das Projekt
|
||||
- [Sicherheitsrichtlinien](/docs/standards/SICHERHEITS-GUIDELINES.md) - Standards für sichere Softwareentwicklung
|
||||
### Erste Schritte
|
||||
- [Installation](getting-started/installation.md)
|
||||
- [Konfiguration](getting-started/configuration.md)
|
||||
- [Erste Schritte](getting-started/first-steps.md)
|
||||
|
||||
### Entwicklungsrichtlinien
|
||||
### Architektur
|
||||
- [Architekturübersicht](architecture/overview.md)
|
||||
- [Hauptkomponenten](architecture/components.md)
|
||||
- [Entwurfsmuster](architecture/patterns.md)
|
||||
|
||||
- [Performance Guidelines](/docs/guidelines/PERFORMANCE-GUIDELINES.md) - Richtlinien zur Optimierung der Anwendungsleistung
|
||||
- [Testing Guidelines](/docs/guidelines/TESTING-GUIDELINES.md) - Standards und Best Practices für Tests
|
||||
### Komponenten
|
||||
- **Analytics**
|
||||
- [Übersicht](components/analytics/index.md)
|
||||
- [Konfiguration](components/analytics/configuration.md)
|
||||
- [Beispiele](components/analytics/examples.md)
|
||||
- **Validation**
|
||||
- [Übersicht](components/validation/index.md)
|
||||
- [Validierungsregeln](components/validation/rules.md)
|
||||
- [Beispiele](components/validation/examples.md)
|
||||
- **WAF (Web Application Firewall)**
|
||||
- [Übersicht](components/waf/index.md)
|
||||
- [Machine Learning](components/waf/machine-learning.md)
|
||||
- [Konfiguration](components/waf/configuration.md)
|
||||
|
||||
### KI-Assistent Konfiguration
|
||||
### Entwickleranleitungen
|
||||
- [Routing](guides/routing.md)
|
||||
- [Controller](guides/controllers.md)
|
||||
- [Validierung](guides/validation.md)
|
||||
|
||||
- [Guidelines für KI-Assistenten](/docs/ai/GUIDELINES-FÜR-AI-ASSISTANT.md) - Spezifische Richtlinien für den KI-Assistenten
|
||||
- [PhpStorm Einrichtung](/docs/ai/EINRICHTUNG-PHPSTORM.md) - Anleitung zur Einrichtung des KI-Assistenten in PhpStorm
|
||||
### API-Dokumentation
|
||||
- [API-Übersicht](api/index.md)
|
||||
|
||||
### Architektur und Struktur
|
||||
### Beitragsrichtlinien
|
||||
- [Coding-Standards](contributing/code-style.md)
|
||||
- [Pull-Request-Prozess](contributing/pull-requests.md)
|
||||
- [Dokumentationsrichtlinien](contributing/documentation.md)
|
||||
|
||||
- [Projektstruktur](/docs/architecture/STRUKTUR-DOKUMENTATION.md) - Überblick über die Struktur des Projekts
|
||||
### Projektplanung
|
||||
- [Features](roadmap/features.md)
|
||||
- [Tasks](roadmap/tasks.md)
|
||||
- [Meilensteine](roadmap/milestones.md)
|
||||
|
||||
### Framework-Entwicklung
|
||||
## Dokumentationsstandards
|
||||
|
||||
- [Modul-Checkliste](/docs/framework/MODUL-CHECKLISTE.md) - Leitfaden für die Erstellung neuer Module
|
||||
- [Erweiterungsmuster](/docs/framework/ERWEITERUNGSPATTERN.md) - Muster zur Erweiterung des Frameworks
|
||||
Diese Dokumentation folgt einheitlichen Standards, um Konsistenz und Benutzerfreundlichkeit zu gewährleisten:
|
||||
|
||||
### Framework-Module
|
||||
1. **Struktur**: Jedes Dokument beginnt mit einer Übersicht und gliedert sich dann in logische Abschnitte.
|
||||
2. **Codebeispiele**: Alle Codebeispiele sind vollständig und funktionsfähig.
|
||||
3. **Querverweise**: Verwandte Themen werden durch Links miteinander verbunden.
|
||||
4. **Aktualität**: Die Dokumentation wird regelmäßig aktualisiert, um den aktuellen Stand der Implementierung widerzuspiegeln.
|
||||
|
||||
- [Analytics-Modul](/docs/framework/analytics/README.md) - Tracking und Analyse von Anwendungsdaten
|
||||
- [Core-Modul](/docs/framework/core/README.md) - Kernkomponenten und Event-System
|
||||
- [DI-Modul](/docs/framework/di/README.md) - Dependency-Injection-Container
|
||||
- [HTTP-Modul](/docs/framework/http/README.md) - HTTP-Request und -Response-Handling
|
||||
## Mitwirkung an der Dokumentation
|
||||
|
||||
## Mitwirken
|
||||
Wir begrüßen Beiträge zur Verbesserung dieser Dokumentation. Bitte beachten Sie die [Dokumentationsrichtlinien](contributing/documentation.md) für Informationen zum Beitragsprozess.
|
||||
|
||||
### Neue Module entwickeln
|
||||
## Feedback
|
||||
|
||||
1. Folge der [Framework-Modul Checkliste](/docs/framework/MODUL-CHECKLISTE.md) für neue Module
|
||||
2. Stelle sicher, dass dein Code den [Coding Guidelines](/docs/standards/CODING-GUIDELINES.md) entspricht
|
||||
3. Schreibe Tests gemäß den [Testing Guidelines](/docs/guidelines/TESTING-GUIDELINES.md)
|
||||
4. Erstelle eine ausführliche Dokumentation für dein Modul
|
||||
|
||||
### Dokumentation verbessern
|
||||
|
||||
Wir begrüßen Beiträge zur Verbesserung der Dokumentation. Wenn du Fehler findest oder Vorschläge zur Verbesserung hast, erstelle bitte einen Pull Request mit deinen Änderungen.
|
||||
|
||||
## Erste Schritte
|
||||
|
||||
Neue Entwickler sollten mit folgenden Schritten beginnen:
|
||||
|
||||
1. Projekt lokal einrichten (siehe [Installation](#installation))
|
||||
2. Die [Projektstruktur](/docs/architecture/STRUKTUR-DOKUMENTATION.md) verstehen
|
||||
3. Die [Coding Guidelines](/docs/standards/CODING-GUIDELINES.md) lesen
|
||||
4. PhpStorm mit dem [KI-Assistenten einrichten](/docs/ai/EINRICHTUNG-PHPSTORM.md)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Repository klonen
|
||||
git clone [repository-url]
|
||||
|
||||
# Abhängigkeiten installieren
|
||||
composer install
|
||||
|
||||
# Entwicklungsserver starten
|
||||
php -S localhost:8000 -t public/
|
||||
```
|
||||
|
||||
## Updates und Änderungen
|
||||
|
||||
Diese Dokumentation wird kontinuierlich aktualisiert. Prüfe regelmäßig auf Aktualisierungen, um über die neuesten Best Practices und Standards informiert zu bleiben.
|
||||
Wenn Sie Fragen, Anregungen oder Feedback zur Dokumentation haben, erstellen Sie bitte ein Issue im Repository oder kontaktieren Sie das Entwicklungsteam direkt.
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# ⚙️ Setup-Anleitung
|
||||
|
||||
Diese Datei beschreibt, wie du das Projekt lokal einrichtest und startest.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Voraussetzungen
|
||||
|
||||
- Docker & Docker Compose
|
||||
- Python 3 (für Ansible, optional pipx)
|
||||
- Optional: Ansible (für Server-Setup)
|
||||
- Optional: PhpStorm oder VS Code
|
||||
|
||||
---
|
||||
|
||||
## 📦 Projektstruktur
|
||||
|
||||
```
|
||||
.
|
||||
├── app/ # PHP/NGINX-Anwendung
|
||||
├── ansible/ # Setup- und Deployment-Playbooks
|
||||
├── docker-compose.yml
|
||||
├── .env # Lokale Konfiguration (nicht versioniert)
|
||||
├── Makefile # Komfortbefehle
|
||||
└── docs/ # Dokumentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ▶️ Lokaler Start
|
||||
|
||||
```bash
|
||||
# Container starten
|
||||
docker compose up --build
|
||||
|
||||
# Alternativ mit Makefile
|
||||
make deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Lokale Tests
|
||||
|
||||
- `http://localhost:8080` → NGINX + PHP
|
||||
- Logs anzeigen: `docker compose logs -f`
|
||||
|
||||
---
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Queue Worker - Docker Management
|
||||
|
||||
Dieses Dokument beschreibt alle Docker-spezifischen Befehle für die Verwaltung des Queue Workers.
|
||||
|
||||
## 📋 Inhaltsverzeichnis
|
||||
|
||||
- [Schnellstart](#schnellstart)
|
||||
- [Worker Management](#worker-management)
|
||||
- [Monitoring & Debugging](#monitoring--debugging)
|
||||
- [Konfiguration](#konfiguration)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Wartung](#wartung)
|
||||
|
||||
## 🚀 Schnellstart
|
||||
|
||||
### Worker starten
|
||||
@@ -1,53 +0,0 @@
|
||||
# Einrichtung des KI-Assistenten in PhpStorm
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Anleitung beschreibt, wie der KI-Assistent in PhpStorm eingerichtet wird, um automatisch die Projekt-Guidelines zu verwenden.
|
||||
|
||||
## Methode 1: Über die PhpStorm-Einstellungen
|
||||
|
||||
1. Öffne PhpStorm und gehe zu **Settings/Preferences**
|
||||
- Windows/Linux: File → Settings
|
||||
- macOS: PhpStorm → Preferences
|
||||
|
||||
2. Navigiere zu **Tools** → **AI Assistant** → **Custom Instructions**
|
||||
|
||||
3. Aktiviere die Option **Use custom instructions**
|
||||
|
||||
4. Füge in das Textfeld den Inhalt aus der Datei `/docs/ai/GUIDELINES-FÜR-AI-ASSISTANT.md` ein
|
||||
- Alternativ kannst du auf einen relativen Pfad verweisen
|
||||
|
||||
5. Aktiviere die Option **Apply project-specific instructions**, damit diese Einstellungen nur für dieses Projekt gelten
|
||||
|
||||
6. Klicke auf **Apply** und dann auf **OK**
|
||||
|
||||
## Methode 2: Über die Projektkonfiguration (empfohlen)
|
||||
|
||||
Die `.idea/aiAssistant.xml`-Datei ist bereits im Projekt enthalten und konfiguriert den KI-Assistenten automatisch mit den richtigen Einstellungen. Wenn du das Projekt öffnest, sollte der KI-Assistent bereits korrekt eingerichtet sein.
|
||||
|
||||
Um zu überprüfen, ob die Einstellungen korrekt übernommen wurden:
|
||||
|
||||
1. Öffne die PhpStorm-Einstellungen wie oben beschrieben
|
||||
2. Navigiere zu **Tools** → **AI Assistant** → **Custom Instructions**
|
||||
3. Überprüfe, ob **Use custom instructions** aktiviert ist und die Guidelines angezeigt werden
|
||||
|
||||
## Testen der Einrichtung
|
||||
|
||||
Um zu testen, ob der KI-Assistent die Guidelines korrekt anwendet:
|
||||
|
||||
1. Öffne eine PHP-Datei im Projekt
|
||||
2. Drücke `Alt+A` (Windows/Linux) oder `Option+A` (macOS) um den KI-Assistenten zu öffnen
|
||||
3. Bitte den Assistenten, eine neue Klasse zu erstellen
|
||||
4. Überprüfe, ob die generierte Klasse den Guidelines entspricht:
|
||||
- Sie sollte als `final` und wenn möglich `readonly` deklariert sein
|
||||
- Constructor Property Promotion sollte verwendet werden
|
||||
- Es sollten keine externen Abhängigkeiten importiert werden
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
Falls die Guidelines nicht korrekt angewendet werden:
|
||||
|
||||
1. Stelle sicher, dass du die neueste Version von PhpStorm verwendest
|
||||
2. Überprüfe, ob die AI Assistant-Funktion aktiviert ist
|
||||
3. Versuche, das Projekt neu zu öffnen
|
||||
4. Führe einen Cache-Clear in PhpStorm durch: File → Invalidate Caches
|
||||
@@ -1,174 +0,0 @@
|
||||
# Guidelines für KI-Assistenten
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Guidelines helfen dir, dem KI-Assistenten, konsistenten, modernen und qualitativ hochwertigen PHP-Code zu generieren, der den Projektstandards entspricht.
|
||||
|
||||
## Verwendung in PhpStorm
|
||||
|
||||
Diese Guidelines können in PhpStorm so eingerichtet werden, dass sie automatisch vom KI-Assistenten verwendet werden:
|
||||
|
||||
1. Gehe zu **Settings/Preferences** → **Tools** → **AI Assistant** → **Custom Instructions**
|
||||
2. Aktiviere **Use custom instructions**
|
||||
3. Füge im Textfeld den Inhalt dieser Datei ein oder verwende einen relativen Pfad zu dieser Datei
|
||||
4. Optional: Aktiviere **Apply project-specific instructions** für projektspezifische Einstellungen
|
||||
|
||||
Alternativ kann die Datei `.idea/aiAssistant.xml` angepasst werden, um diese Guidelines als Standardeinstellung für das Projekt zu verwenden.
|
||||
|
||||
## Kernprinzipien
|
||||
|
||||
### Abhängigkeitsvermeidung
|
||||
|
||||
- **Keine externen Abhängigkeiten** außer den explizit freigegebenen
|
||||
- Eigene Implementierungen gegenüber externen Bibliotheken bevorzugen
|
||||
- Bei Bedarf nach externen Funktionen zuerst prüfen, ob eine eigene Implementierung möglich ist
|
||||
- Erlaubte Abhängigkeiten sind auf die vorhandenen Composer-Pakete beschränkt
|
||||
|
||||
## Architekturprinzipien
|
||||
|
||||
Bei der Codeanalyse und -generierung sind folgende Architekturprinzipien zu beachten:
|
||||
|
||||
1. **Modularer Aufbau**: Das Projekt ist in Module unter `/src/Framework/` organisiert
|
||||
2. **Service-orientierte Architektur**: Funktionalitäten als unabhängige Services implementieren
|
||||
3. **Dependency Injection**: Abhängigkeiten werden per Constructor Injection bereitgestellt
|
||||
4. **Event-basierte Kommunikation**: Module kommunizieren über den EventDispatcher
|
||||
5. **Selbstständigkeit**: Module sollten möglichst unabhängig von externen Bibliotheken sein
|
||||
|
||||
## Coding-Standards
|
||||
|
||||
### Klassen
|
||||
|
||||
- **IMMER `final` verwenden**, außer bei zwingenden Gründen für Vererbung
|
||||
- **KEINE abstrakten Klassen** verwenden - stattdessen Interfaces und Kompositionen
|
||||
- Klassen und Properties wenn möglich als `readonly` deklarieren
|
||||
- Bevorzuge Interfaces für Vertragsgestaltung zwischen Komponenten
|
||||
|
||||
```php
|
||||
// RICHTIG
|
||||
final readonly class AnalyticsService implements AnalyticsInterface
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// FALSCH
|
||||
abstract class BaseAnalytics
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Properties und Methoden
|
||||
|
||||
- Private `readonly` Properties mit Typisierung
|
||||
- Return-Types immer angeben
|
||||
- Parameter-Types immer angeben
|
||||
- Union Types und Nullable Types nutzen
|
||||
|
||||
```php
|
||||
// RICHTIG
|
||||
private readonly LoggerInterface $logger;
|
||||
|
||||
public function process(?int $id): Result|null
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// FALSCH
|
||||
public $logger;
|
||||
|
||||
public function process($id)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Moderne PHP-Features
|
||||
|
||||
Nutze aktiv die neuesten PHP-Features:
|
||||
|
||||
- Constructor Property Promotion
|
||||
- Match Expressions statt Switch
|
||||
- Named Arguments
|
||||
- Enums statt Konstanten
|
||||
- Nullsafe Operator (`?->`) wo sinnvoll
|
||||
- Typed Properties
|
||||
|
||||
## Klassenaufbau
|
||||
|
||||
Folgende Reihenfolge für Klassenelemente:
|
||||
|
||||
1. Konstanten
|
||||
2. Properties
|
||||
3. Constructor
|
||||
4. Öffentliche Methoden
|
||||
5. Private/Protected Methoden
|
||||
|
||||
## Service-Initialisierung
|
||||
|
||||
Services werden durch Initializer-Klassen registriert:
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
final readonly class ServiceInitializer
|
||||
{
|
||||
public function __construct(
|
||||
private Configuration $config,
|
||||
private DependencyInterface $dependency
|
||||
) {}
|
||||
|
||||
public function __invoke(Container $container): ServiceInterface
|
||||
{
|
||||
return new Service(
|
||||
$this->config->get('service'),
|
||||
$this->dependency
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
- Spezifische Exception-Klassen werfen
|
||||
- Early Return Pattern bevorzugen
|
||||
- Defensive Programmierung mit Validierung
|
||||
|
||||
## Testing
|
||||
|
||||
Bei Test-Vorschlägen Pest-Framework nutzen:
|
||||
|
||||
```php
|
||||
test('method does something correctly', function () {
|
||||
// Arrangement
|
||||
$service = new Service($dependency);
|
||||
|
||||
// Action
|
||||
$result = $service->method();
|
||||
|
||||
// Assertion
|
||||
expect($result)->toBe('expected');
|
||||
});
|
||||
```
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- PHPDoc für alle öffentlichen Methoden
|
||||
- Kurze, präzise Beschreibungen
|
||||
- Parameter und Return-Types in PHPDoc
|
||||
|
||||
## Zu vermeidende Praktiken
|
||||
|
||||
- Globale Zustände und statische Methoden
|
||||
- Tiefe Vererbungshierarchien
|
||||
- Lange, komplexe Methoden
|
||||
- Magische Methoden (`__call`, etc.) ohne triftigen Grund
|
||||
- Unnötige Abstraktionen
|
||||
|
||||
## Bei Codeanalyse und -vorschlägen
|
||||
|
||||
1. **Aktuellen Stil beibehalten**: Bei Vorschlägen den vorhandenen Codierungsstil beibehalten
|
||||
2. **Standards berücksichtigen**: Auf Einhaltung der hier definierten Guidelines achten
|
||||
3. **Modernisierung vorschlagen**: Auf Möglichkeiten zur Modernisierung hinweisen
|
||||
4. **Begründen**: Bei Empfehlungen die Gründe erläutern
|
||||
5. **Vollständigkeit**: Vollständige Lösungen anbieten, nicht nur Fragmente
|
||||
|
||||
Diese Guidelines sind als lebendiges Dokument zu betrachten, das mit der Evolution des Projekts und von PHP weiterentwickelt wird.
|
||||
@@ -1,54 +0,0 @@
|
||||
# KI-Assistent Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation beschreibt die Einrichtung und Verwendung des KI-Assistenten im Projekt. Der KI-Assistent hilft bei der Entwicklung durch Code-Generierung, Refactoring-Vorschläge und mehr, während er die Projektstandards einhält.
|
||||
|
||||
## Inhalte
|
||||
|
||||
- [Guidelines für KI-Assistenten](/ai/GUIDELINES-FÜR-AI-ASSISTANT.md) - Richtlinien für den KI-Assistenten
|
||||
- [PhpStorm Einrichtung](/ai/EINRICHTUNG-PHPSTORM.md) - Anleitung zur Einrichtung in PhpStorm
|
||||
|
||||
## KI-Assistent Guidelines
|
||||
|
||||
Die [Guidelines für KI-Assistenten](/ai/GUIDELINES-FÜR-AI-ASSISTANT.md) stellen sicher, dass der KI-generierte Code den Projektstandards entspricht:
|
||||
|
||||
- Einhaltung der Coding-Standards
|
||||
- Vermeidung externer Abhängigkeiten
|
||||
- Verwendung moderner PHP-Features
|
||||
- Konsistente Klassenstruktur
|
||||
- Korrekte Fehlerbehandlung
|
||||
|
||||
## Einrichtung in PhpStorm
|
||||
|
||||
Die [PhpStorm Einrichtungsanleitung](/ai/EINRICHTUNG-PHPSTORM.md) führt Sie durch den Prozess der Integration des KI-Assistenten in Ihre IDE:
|
||||
|
||||
- Konfiguration der Custom Instructions
|
||||
- Verwendung der projektspezifischen Einstellungen
|
||||
- Testen der korrekten Einrichtung
|
||||
- Fehlerbehebung bei Problemen
|
||||
|
||||
## Effektive Nutzung des KI-Assistenten
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Klare Anfragen stellen**: Je präziser die Anfrage, desto besser das Ergebnis
|
||||
2. **Kontext bereitstellen**: Dem Assistenten relevanten Kontext geben
|
||||
3. **Ergebnisse überprüfen**: Generierte Code immer prüfen und verstehen
|
||||
4. **Iterativ arbeiten**: Bei komplexen Aufgaben schrittweise vorgehen
|
||||
|
||||
### Häufige Anwendungsfälle
|
||||
|
||||
- Erstellung neuer Klassen und Interfaces
|
||||
- Implementierung von Tests
|
||||
- Refactoring bestehenden Codes
|
||||
- Dokumentation generieren
|
||||
- Code-Optimierung
|
||||
|
||||
## Datenschutz und Sicherheit
|
||||
|
||||
Beim Umgang mit dem KI-Assistenten sollten Sie folgende Punkte beachten:
|
||||
|
||||
- Keine sensiblen Daten oder Geschäftsgeheimnisse teilen
|
||||
- Keine Passwörter, API-Schlüssel oder Zugangsdaten teilen
|
||||
- Bei Unsicherheit den KI-Assistenten nicht verwenden
|
||||
295
docs/api-versioning-examples.md
Normal file
295
docs/api-versioning-examples.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# API Versioning Examples
|
||||
|
||||
The API Versioning system provides flexible, clean version management with support for multiple versioning strategies.
|
||||
|
||||
## Supported Versioning Strategies
|
||||
|
||||
### 1. Header-Based Versioning (Recommended)
|
||||
|
||||
```bash
|
||||
# Using API-Version header
|
||||
curl -X GET https://localhost/api/users \
|
||||
-H "API-Version: 2.0.0" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
|
||||
# Using custom header
|
||||
curl -X GET https://localhost/api/users \
|
||||
-H "X-API-Version: v2" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
### 2. URL Path Versioning
|
||||
|
||||
```bash
|
||||
# Version in URL path
|
||||
curl -X GET https://localhost/api/v1/users \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
|
||||
curl -X GET https://localhost/api/v2/users \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
### 3. Query Parameter Versioning
|
||||
|
||||
```bash
|
||||
# Version as query parameter
|
||||
curl -X GET https://localhost/api/users?version=2.0 \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
### 4. Accept Header Versioning
|
||||
|
||||
```bash
|
||||
# Version in Accept header
|
||||
curl -X GET https://localhost/api/users \
|
||||
-H "Accept: application/json;version=2.0" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
## Version Information Endpoints
|
||||
|
||||
### Get Current Version Info
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/version \
|
||||
-H "API-Version: 2.0.0" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"current_version": "v2.0.0",
|
||||
"current_version_numeric": "2.0.0",
|
||||
"latest_version": "v2.0.0",
|
||||
"default_version": "v1.0.0",
|
||||
"is_latest": true,
|
||||
"is_deprecated": false,
|
||||
"supported_strategies": [
|
||||
{
|
||||
"name": "header",
|
||||
"description": "Version specified in API-Version header",
|
||||
"header_name": "API-Version"
|
||||
},
|
||||
{
|
||||
"name": "url_path",
|
||||
"description": "Version specified in URL path (/api/v1/users)",
|
||||
"header_name": "API-Version"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get Supported Versions
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/versions \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"supported_versions": [
|
||||
{
|
||||
"version": "v2.0.0",
|
||||
"version_numeric": "2.0.0",
|
||||
"is_default": false,
|
||||
"is_latest": true,
|
||||
"is_deprecated": false,
|
||||
"major": 2,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
},
|
||||
{
|
||||
"version": "v1.0.0",
|
||||
"version_numeric": "1.0.0",
|
||||
"is_default": true,
|
||||
"is_latest": false,
|
||||
"is_deprecated": true,
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
}
|
||||
],
|
||||
"total_versions": 2,
|
||||
"versioning_config": {
|
||||
"strict_versioning": false,
|
||||
"deprecation_warnings": true,
|
||||
"default_version": "v1.0.0",
|
||||
"latest_version": "v2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Version Differences
|
||||
|
||||
### Version 1.0 Response Format
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/v1/users/1 \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "User 1",
|
||||
"email": "user1@example.com",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Version 2.0 Response Format
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/v2/users/1 \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "User 1",
|
||||
"email": "user1@example.com",
|
||||
"profile": {
|
||||
"bio": "This is the bio for user 1",
|
||||
"avatar_url": "https://example.com/avatars/1.jpg",
|
||||
"website": "https://user1.example.com",
|
||||
"location": "San Francisco, CA",
|
||||
"verified": false
|
||||
},
|
||||
"settings": {
|
||||
"notifications": true,
|
||||
"privacy_level": "public",
|
||||
"theme": "dark"
|
||||
},
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-15T10:30:00Z"
|
||||
},
|
||||
"meta": {
|
||||
"api_version": "2.0.0",
|
||||
"response_time_ms": 127
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Deprecation Warnings
|
||||
|
||||
When using deprecated endpoints, you'll receive warning headers:
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/v1/users/1/profile \
|
||||
-H "API-Version: 1.0.0" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-I
|
||||
```
|
||||
|
||||
Response Headers:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
API-Version: v1.0.0
|
||||
Warning: 299 - "This endpoint is deprecated and will be removed in v2.0.0"
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Get Migration Instructions
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/api/version/migrate \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"from_version": "1.0.0",
|
||||
"to_version": "2.0.0"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"from_version": "v1.0.0",
|
||||
"to_version": "v2.0.0",
|
||||
"compatible": false,
|
||||
"migration_required": true,
|
||||
"changes": {
|
||||
"breaking_changes": [
|
||||
"Response format changed to include metadata wrapper",
|
||||
"Profile endpoint moved from /users/{id}/profile to new format",
|
||||
"Error responses now include detailed validation information"
|
||||
],
|
||||
"new_features": [
|
||||
"Enhanced user profile with social links and stats",
|
||||
"Pagination support for list endpoints",
|
||||
"Response time metadata in all responses"
|
||||
],
|
||||
"deprecated_endpoints": [
|
||||
"/api/v1/users/{id}/profile - Use /api/v2/users/{id}/profile instead"
|
||||
],
|
||||
"migration_steps": [
|
||||
"1. Update API version header to v2.0.0",
|
||||
"2. Update response parsing to handle metadata wrapper",
|
||||
"3. Update error handling for new error format",
|
||||
"4. Test all endpoints with new response format",
|
||||
"5. Update profile endpoint calls if used"
|
||||
]
|
||||
},
|
||||
"estimated_effort": "Medium"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Unsupported Version (with strict versioning)
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/users \
|
||||
-H "API-Version: 3.0.0" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"error": "Unsupported API version",
|
||||
"requested_version": "v3.0.0",
|
||||
"supported_versions": ["v1.0.0", "v2.0.0"],
|
||||
"latest_version": "v2.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
## Controller Configuration
|
||||
|
||||
### Using ApiVersionAttribute
|
||||
|
||||
```php
|
||||
// Class-level version constraint
|
||||
#[ApiVersionAttribute('2.0.0', introducedIn: '2.0.0')]
|
||||
class UsersV2Controller
|
||||
{
|
||||
// All methods in this controller require v2.0+
|
||||
}
|
||||
|
||||
// Method-level deprecation
|
||||
#[Route(path: '/api/v1/users/{id}/profile', method: Method::GET)]
|
||||
#[ApiVersionAttribute('1.0.0', introducedIn: '1.0.0', deprecatedIn: '1.5.0', removedIn: '2.0.0')]
|
||||
public function getProfile(Request $request): HttpResponse
|
||||
{
|
||||
// This method is deprecated in v1.5.0 and removed in v2.0.0
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Header Versioning**: Most flexible and doesn't pollute URLs
|
||||
2. **Semantic Versioning**: Use major.minor.patch format
|
||||
3. **Deprecation Warnings**: Always warn before removing endpoints
|
||||
4. **Migration Guides**: Provide clear migration paths
|
||||
5. **Backward Compatibility**: Maintain compatibility within major versions
|
||||
6. **Version Documentation**: Document changes between versions
|
||||
7. **Default to Stable**: Use stable version as default, not latest
|
||||
838
docs/api/index.md
Normal file
838
docs/api/index.md
Normal file
@@ -0,0 +1,838 @@
|
||||
# API-Dokumentation
|
||||
|
||||
Diese Dokumentation bietet einen Überblick über die API des Frameworks und wie Sie sie verwenden können, um RESTful APIs zu erstellen.
|
||||
|
||||
## Einführung
|
||||
|
||||
Das Framework bietet umfassende Unterstützung für die Erstellung von RESTful APIs mit Funktionen wie:
|
||||
|
||||
- Einfache Routendefinition für API-Endpunkte
|
||||
- Automatische Konvertierung von Daten in JSON
|
||||
- Validierung von Anfragen
|
||||
- Authentifizierung und Autorisierung
|
||||
- Versionierung
|
||||
- Rate Limiting
|
||||
- API-Dokumentation
|
||||
|
||||
## API-Routen definieren
|
||||
|
||||
### Grundlegende API-Routen
|
||||
|
||||
Sie können API-Routen in der Datei `config/routes.php` definieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Routing\Router;
|
||||
use App\Application\Controllers\Api\UserController;
|
||||
|
||||
return function (Router $router) {
|
||||
$router->group('/api', function (Router $router) {
|
||||
$router->get('/users', [UserController::class, 'index']);
|
||||
$router->get('/users/{id}', [UserController::class, 'show']);
|
||||
$router->post('/users', [UserController::class, 'store']);
|
||||
$router->put('/users/{id}', [UserController::class, 'update']);
|
||||
$router->delete('/users/{id}', [UserController::class, 'destroy']);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### API-Ressourcen-Routen
|
||||
|
||||
Für RESTful APIs können Sie API-Ressourcen-Routen verwenden:
|
||||
|
||||
```php
|
||||
$router->apiResource('users', UserController::class);
|
||||
```
|
||||
|
||||
Dies erstellt die folgenden Routen:
|
||||
|
||||
| HTTP-Methode | URI | Aktion | Routenname |
|
||||
|--------------|------------------|----------|----------------|
|
||||
| GET | /users | index | users.index |
|
||||
| POST | /users | store | users.store |
|
||||
| GET | /users/{user} | show | users.show |
|
||||
| PUT/PATCH | /users/{user} | update | users.update |
|
||||
| DELETE | /users/{user} | destroy | users.destroy |
|
||||
|
||||
### Verschachtelte API-Ressourcen
|
||||
|
||||
Sie können auch verschachtelte API-Ressourcen definieren:
|
||||
|
||||
```php
|
||||
$router->apiResource('users.posts', UserPostController::class);
|
||||
```
|
||||
|
||||
Dies erstellt Routen wie:
|
||||
- `/users/{user}/posts`
|
||||
- `/users/{user}/posts/{post}`
|
||||
|
||||
### API-Versionierung
|
||||
|
||||
Sie können verschiedene Versionen Ihrer API unterstützen:
|
||||
|
||||
```php
|
||||
$router->group('/api/v1', function (Router $router) {
|
||||
$router->apiResource('users', Api\V1\UserController::class);
|
||||
});
|
||||
|
||||
$router->group('/api/v2', function (Router $router) {
|
||||
$router->apiResource('users', Api\V2\UserController::class);
|
||||
});
|
||||
```
|
||||
|
||||
## API-Controller
|
||||
|
||||
API-Controller sind spezielle Controller, die JSON-Antworten zurückgeben:
|
||||
|
||||
```php
|
||||
namespace App\Application\Controllers\Api;
|
||||
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Application\Models\User;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$users = User::all();
|
||||
|
||||
return $this->json([
|
||||
'data' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'data' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer erstellt'
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$this->validate($request, [
|
||||
'name' => 'string|max:255',
|
||||
'email' => 'email|unique:users,email,' . $id,
|
||||
]);
|
||||
|
||||
$user->fill($request->only(['name', 'email']));
|
||||
$user->save();
|
||||
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer aktualisiert'
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
|
||||
return $this->json([
|
||||
'message' => 'Benutzer gelöscht'
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API-Antworten
|
||||
|
||||
### Erfolgsantworten
|
||||
|
||||
Für Erfolgsantworten können Sie die `json`-Methode verwenden:
|
||||
|
||||
```php
|
||||
// 200 OK
|
||||
return $this->json([
|
||||
'data' => $users
|
||||
]);
|
||||
|
||||
// 201 Created
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer erstellt'
|
||||
], 201);
|
||||
|
||||
// 204 No Content
|
||||
return $this->noContent();
|
||||
```
|
||||
|
||||
### Fehlerantworten
|
||||
|
||||
Für Fehlerantworten können Sie ebenfalls die `json`-Methode verwenden:
|
||||
|
||||
```php
|
||||
// 400 Bad Request
|
||||
return $this->json([
|
||||
'error' => 'Ungültige Anfrage'
|
||||
], 400);
|
||||
|
||||
// 401 Unauthorized
|
||||
return $this->json([
|
||||
'error' => 'Nicht autorisiert'
|
||||
], 401);
|
||||
|
||||
// 403 Forbidden
|
||||
return $this->json([
|
||||
'error' => 'Zugriff verweigert'
|
||||
], 403);
|
||||
|
||||
// 404 Not Found
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
|
||||
// 422 Unprocessable Entity (Validierungsfehler)
|
||||
return $this->json([
|
||||
'error' => 'Validierungsfehler',
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
|
||||
// 500 Internal Server Error
|
||||
return $this->json([
|
||||
'error' => 'Serverfehler'
|
||||
], 500);
|
||||
```
|
||||
|
||||
### Konsistente Antwortstruktur
|
||||
|
||||
Es ist wichtig, eine konsistente Antwortstruktur für Ihre API zu haben:
|
||||
|
||||
```php
|
||||
// Erfolgsantwort
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'message' => $message,
|
||||
'meta' => [
|
||||
'pagination' => [
|
||||
'total' => $total,
|
||||
'per_page' => $perPage,
|
||||
'current_page' => $currentPage,
|
||||
'last_page' => $lastPage,
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
// Fehlerantwort
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => $errorMessage,
|
||||
'errors' => $validationErrors,
|
||||
'code' => $errorCode
|
||||
], $statusCode);
|
||||
```
|
||||
|
||||
## API-Ressourcen
|
||||
|
||||
API-Ressourcen ermöglichen es Ihnen, Modelle in JSON-Antworten zu transformieren:
|
||||
|
||||
```php
|
||||
namespace App\Application\Resources;
|
||||
|
||||
use App\Framework\Http\Resources\JsonResource;
|
||||
use App\Application\Models\User;
|
||||
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
public function toArray(): array
|
||||
{
|
||||
/** @var User $this->resource */
|
||||
return [
|
||||
'id' => $this->resource->id,
|
||||
'name' => $this->resource->name,
|
||||
'email' => $this->resource->email,
|
||||
'created_at' => $this->resource->created_at->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->resource->updated_at->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Verwenden Sie die Ressource in Ihrem Controller:
|
||||
|
||||
```php
|
||||
use App\Application\Resources\UserResource;
|
||||
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'data' => new UserResource($user)
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$users = User::all();
|
||||
|
||||
return $this->json([
|
||||
'data' => UserResource::collection($users)
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
## API-Authentifizierung
|
||||
|
||||
### Token-basierte Authentifizierung
|
||||
|
||||
Das Framework unterstützt Token-basierte Authentifizierung für APIs:
|
||||
|
||||
```php
|
||||
namespace App\Application\Controllers\Api;
|
||||
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\Auth\Auth;
|
||||
use App\Framework\Auth\Token;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$credentials = $request->only(['email', 'password']);
|
||||
|
||||
if (!Auth::attempt($credentials)) {
|
||||
return $this->json([
|
||||
'error' => 'Ungültige Anmeldeinformationen'
|
||||
], 401);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$token = Token::create($user);
|
||||
|
||||
return $this->json([
|
||||
'data' => [
|
||||
'user' => $user,
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): Response
|
||||
{
|
||||
$token = $request->bearerToken();
|
||||
|
||||
if ($token) {
|
||||
Token::revoke($token);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'message' => 'Erfolgreich abgemeldet'
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Authentifizierung mit dem Token
|
||||
|
||||
```php
|
||||
namespace App\Application\Middleware;
|
||||
|
||||
use App\Framework\Http\Middleware;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\Auth\Token;
|
||||
|
||||
class ApiAuthMiddleware implements Middleware
|
||||
{
|
||||
public function handle(Request $request, callable $next): Response
|
||||
{
|
||||
$token = $request->bearerToken();
|
||||
|
||||
if (!$token || !Token::validate($token)) {
|
||||
return response()->json([
|
||||
'error' => 'Nicht autorisiert'
|
||||
], 401);
|
||||
}
|
||||
|
||||
$user = Token::getUser($token);
|
||||
Auth::setUser($user);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Registrieren Sie die Middleware:
|
||||
|
||||
```php
|
||||
// In der Middleware-Konfiguration
|
||||
$middleware->group('api', [
|
||||
ApiAuthMiddleware::class
|
||||
]);
|
||||
|
||||
// In den Routen
|
||||
$router->group('/api', function (Router $router) {
|
||||
// Öffentliche Routen
|
||||
$router->post('/login', [AuthController::class, 'login']);
|
||||
|
||||
// Geschützte Routen
|
||||
$router->group('', function (Router $router) {
|
||||
$router->apiResource('users', UserController::class);
|
||||
$router->post('/logout', [AuthController::class, 'logout']);
|
||||
})->middleware('api');
|
||||
});
|
||||
```
|
||||
|
||||
## API-Validierung
|
||||
|
||||
Die Validierung für APIs funktioniert ähnlich wie für reguläre Anfragen, gibt jedoch JSON-Antworten zurück:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$validator = new Validator($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $this->json([
|
||||
'error' => 'Validierungsfehler',
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = User::create($validator->validated());
|
||||
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer erstellt'
|
||||
], 201);
|
||||
}
|
||||
```
|
||||
|
||||
Oder mit der `validate`-Methode:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Validierungsfehler',
|
||||
'errors' => $e->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer erstellt'
|
||||
], 201);
|
||||
}
|
||||
```
|
||||
|
||||
## API-Paginierung
|
||||
|
||||
Das Framework unterstützt Paginierung für API-Antworten:
|
||||
|
||||
```php
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$page = $request->query('page', 1);
|
||||
$perPage = $request->query('per_page', 15);
|
||||
|
||||
$users = User::paginate($perPage, $page);
|
||||
|
||||
return $this->json([
|
||||
'data' => UserResource::collection($users->items()),
|
||||
'meta' => [
|
||||
'pagination' => [
|
||||
'total' => $users->total(),
|
||||
'per_page' => $users->perPage(),
|
||||
'current_page' => $users->currentPage(),
|
||||
'last_page' => $users->lastPage(),
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
## API-Filterung und Sortierung
|
||||
|
||||
Sie können Filterung und Sortierung für Ihre API implementieren:
|
||||
|
||||
```php
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
// Filterung
|
||||
if ($request->has('name')) {
|
||||
$query->where('name', 'like', '%' . $request->query('name') . '%');
|
||||
}
|
||||
|
||||
if ($request->has('email')) {
|
||||
$query->where('email', 'like', '%' . $request->query('email') . '%');
|
||||
}
|
||||
|
||||
if ($request->has('role')) {
|
||||
$query->whereHas('roles', function ($q) use ($request) {
|
||||
$q->where('name', $request->query('role'));
|
||||
});
|
||||
}
|
||||
|
||||
// Sortierung
|
||||
$sortBy = $request->query('sort_by', 'created_at');
|
||||
$sortDirection = $request->query('sort_direction', 'desc');
|
||||
|
||||
$allowedSortFields = ['id', 'name', 'email', 'created_at'];
|
||||
|
||||
if (in_array($sortBy, $allowedSortFields)) {
|
||||
$query->orderBy($sortBy, $sortDirection === 'asc' ? 'asc' : 'desc');
|
||||
}
|
||||
|
||||
// Paginierung
|
||||
$page = $request->query('page', 1);
|
||||
$perPage = $request->query('per_page', 15);
|
||||
|
||||
$users = $query->paginate($perPage, $page);
|
||||
|
||||
return $this->json([
|
||||
'data' => UserResource::collection($users->items()),
|
||||
'meta' => [
|
||||
'pagination' => [
|
||||
'total' => $users->total(),
|
||||
'per_page' => $users->perPage(),
|
||||
'current_page' => $users->currentPage(),
|
||||
'last_page' => $users->lastPage(),
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
## API-Rate Limiting
|
||||
|
||||
Das Framework bietet Rate Limiting für APIs, um Missbrauch zu verhindern:
|
||||
|
||||
```php
|
||||
namespace App\Application\Middleware;
|
||||
|
||||
use App\Framework\Http\Middleware;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\Cache\Cache;
|
||||
|
||||
class RateLimitMiddleware implements Middleware
|
||||
{
|
||||
private int $maxRequests;
|
||||
private int $timeWindow;
|
||||
|
||||
public function __construct(int $maxRequests = 60, int $timeWindow = 60)
|
||||
{
|
||||
$this->maxRequests = $maxRequests;
|
||||
$this->timeWindow = $timeWindow;
|
||||
}
|
||||
|
||||
public function handle(Request $request, callable $next): Response
|
||||
{
|
||||
$key = 'rate_limit:' . $this->getIdentifier($request);
|
||||
$requests = Cache::get($key, 0);
|
||||
|
||||
if ($requests >= $this->maxRequests) {
|
||||
return response()->json([
|
||||
'error' => 'Zu viele Anfragen',
|
||||
'message' => 'Bitte versuchen Sie es später erneut'
|
||||
], 429);
|
||||
}
|
||||
|
||||
Cache::put($key, $requests + 1, $this->timeWindow);
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
$response->headers->set('X-RateLimit-Limit', $this->maxRequests);
|
||||
$response->headers->set('X-RateLimit-Remaining', $this->maxRequests - $requests - 1);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getIdentifier(Request $request): string
|
||||
{
|
||||
if (Auth::check()) {
|
||||
return 'user:' . Auth::id();
|
||||
}
|
||||
|
||||
return 'ip:' . $request->ip();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Registrieren Sie die Middleware:
|
||||
|
||||
```php
|
||||
// In der Middleware-Konfiguration
|
||||
$middleware->group('api', [
|
||||
RateLimitMiddleware::class
|
||||
]);
|
||||
|
||||
// Oder mit Parametern
|
||||
$middleware->group('api', [
|
||||
new RateLimitMiddleware(100, 60) // 100 Anfragen pro Minute
|
||||
]);
|
||||
```
|
||||
|
||||
## API-Dokumentation
|
||||
|
||||
Das Framework unterstützt die automatische Generierung von API-Dokumentation mit OpenAPI/Swagger:
|
||||
|
||||
```php
|
||||
namespace App\Application\Controllers\Api;
|
||||
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Application\Models\User;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="Users",
|
||||
* description="API-Endpunkte für die Benutzerverwaltung"
|
||||
* )
|
||||
*/
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/users",
|
||||
* summary="Liste aller Benutzer abrufen",
|
||||
* tags={"Users"},
|
||||
* @OA\Parameter(
|
||||
* name="page",
|
||||
* in="query",
|
||||
* description="Seitennummer",
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="per_page",
|
||||
* in="query",
|
||||
* description="Anzahl der Elemente pro Seite",
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Erfolgreiche Operation",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/User")),
|
||||
* @OA\Property(property="meta", type="object")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/users/{id}",
|
||||
* summary="Einen bestimmten Benutzer abrufen",
|
||||
* tags={"Users"},
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* description="Benutzer-ID",
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Erfolgreiche Operation",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/User")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=404,
|
||||
* description="Benutzer nicht gefunden",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="error", type="string")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// Weitere Methoden mit Dokumentation...
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="User",
|
||||
* required={"id", "name", "email"},
|
||||
* @OA\Property(property="id", type="integer", format="int64"),
|
||||
* @OA\Property(property="name", type="string"),
|
||||
* @OA\Property(property="email", type="string", format="email"),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time")
|
||||
* )
|
||||
*/
|
||||
```
|
||||
|
||||
Generieren Sie die API-Dokumentation:
|
||||
|
||||
```bash
|
||||
php console.php api:docs
|
||||
```
|
||||
|
||||
Dies erstellt eine OpenAPI/Swagger-Dokumentation, die Sie in einem Browser anzeigen können.
|
||||
|
||||
## Beste Praktiken für APIs
|
||||
|
||||
### Versionierung
|
||||
|
||||
Versionieren Sie Ihre API, um Änderungen zu ermöglichen, ohne bestehende Clients zu beeinträchtigen:
|
||||
|
||||
```php
|
||||
$router->group('/api/v1', function (Router $router) {
|
||||
// API v1 Routen
|
||||
});
|
||||
|
||||
$router->group('/api/v2', function (Router $router) {
|
||||
// API v2 Routen
|
||||
});
|
||||
```
|
||||
|
||||
### Konsistente Benennung
|
||||
|
||||
Verwenden Sie konsistente Benennungskonventionen für Ihre API-Endpunkte:
|
||||
|
||||
- Verwenden Sie Substantive im Plural für Ressourcen (z.B. `/users` statt `/user`)
|
||||
- Verwenden Sie Kebab-Case für URLs (z.B. `/user-profiles` statt `/userProfiles`)
|
||||
- Verwenden Sie Camel-Case für JSON-Eigenschaften (z.B. `firstName` statt `first_name`)
|
||||
|
||||
### HTTP-Statuscodes
|
||||
|
||||
Verwenden Sie die richtigen HTTP-Statuscodes:
|
||||
|
||||
- 200 OK: Erfolgreiche Anfrage
|
||||
- 201 Created: Ressource erfolgreich erstellt
|
||||
- 204 No Content: Erfolgreiche Anfrage ohne Inhalt
|
||||
- 400 Bad Request: Ungültige Anfrage
|
||||
- 401 Unauthorized: Authentifizierung erforderlich
|
||||
- 403 Forbidden: Keine Berechtigung
|
||||
- 404 Not Found: Ressource nicht gefunden
|
||||
- 422 Unprocessable Entity: Validierungsfehler
|
||||
- 429 Too Many Requests: Rate Limit überschritten
|
||||
- 500 Internal Server Error: Serverfehler
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
Implementieren Sie eine konsistente Fehlerbehandlung:
|
||||
|
||||
```php
|
||||
try {
|
||||
// Code, der eine Exception werfen könnte
|
||||
} catch (ValidationException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Validierungsfehler',
|
||||
'errors' => $e->errors()
|
||||
], 422);
|
||||
} catch (AuthorizationException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Nicht autorisiert',
|
||||
'message' => $e->getMessage()
|
||||
], 403);
|
||||
} catch (NotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Nicht gefunden',
|
||||
'message' => $e->getMessage()
|
||||
], 404);
|
||||
} catch (\Exception $e) {
|
||||
// Protokollieren Sie den Fehler
|
||||
$this->logger->error('API-Fehler', [
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return $this->json([
|
||||
'error' => 'Serverfehler',
|
||||
'message' => 'Ein unerwarteter Fehler ist aufgetreten'
|
||||
], 500);
|
||||
}
|
||||
```
|
||||
|
||||
### Dokumentation
|
||||
|
||||
Dokumentieren Sie Ihre API gründlich:
|
||||
|
||||
- Beschreiben Sie jeden Endpunkt
|
||||
- Dokumentieren Sie alle Parameter
|
||||
- Geben Sie Beispiele für Anfragen und Antworten
|
||||
- Erklären Sie Fehlerszenarien
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Routing-Anleitung](../guides/routing.md): Erfahren Sie mehr über das Routing-System.
|
||||
- [Controller-Anleitung](../guides/controllers.md): Erfahren Sie mehr über Controller.
|
||||
- [Validierungs-Anleitung](../guides/validation.md): Erfahren Sie mehr über die Validierung von Anfragen.
|
||||
- [Sicherheits-Anleitung](../guides/security.md): Erfahren Sie mehr über die Sicherheitsfunktionen des Frameworks.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
@@ -1,149 +0,0 @@
|
||||
# Projektstruktur-Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation bietet einen Überblick über die Architektur und Struktur des Projekts. Die Anwendung folgt einer modularen, serviceorientierten Architektur mit klarer Trennung von Verantwortlichkeiten.
|
||||
|
||||
## Hauptverzeichnisse
|
||||
|
||||
### `/src`
|
||||
|
||||
Das Hauptverzeichnis für den Anwendungscode, unterteilt in mehrere Unterverzeichnisse:
|
||||
|
||||
#### `/src/Framework`
|
||||
|
||||
Enthält das Framework mit grundlegenden Infrastrukturkomponenten:
|
||||
|
||||
- **Analytics**: System zur Erfassung und Analyse von Anwendungsdaten
|
||||
- **Attributes**: Attribute/Annotations für Metadaten
|
||||
- **Cache**: Caching-Mechanismen
|
||||
- **CommandBus**: Command-Handling-Komponenten
|
||||
- **Config**: Konfigurationsverwaltung
|
||||
- **Console**: Konsolenanwendung und -befehle
|
||||
- **Core**: Kernkomponenten und Events
|
||||
- **DI**: Dependency-Injection-Container
|
||||
- **ErrorHandling**: Fehlerbehandlungsmechanismen
|
||||
- **EventBus**: Event-Handling-System
|
||||
- **Exception**: Framework-Exceptions
|
||||
- **Filesystem**: Dateisystemoperationen
|
||||
- **Http**: HTTP-Request/Response-Handling
|
||||
- **HttpClient**: HTTP-Client für externe API-Aufrufe
|
||||
- **Logging**: Logging-Infrastruktur
|
||||
- **Performance**: Performance-Monitoring
|
||||
- **Queue**: Nachrichtenwarteschlangen
|
||||
- **Redis**: Redis-Integration
|
||||
- **Router**: URL-Routing
|
||||
- **StaticSite**: Statische Site-Generation
|
||||
- **Validation**: Datenvalidierung
|
||||
- **View**: Template-Rendering
|
||||
|
||||
#### `/src/Application`
|
||||
|
||||
Anwendungsspezifische Komponenten, die das Framework nutzen.
|
||||
|
||||
#### `/src/Domain`
|
||||
|
||||
Domain-Modelle, Entities und Business-Logik.
|
||||
|
||||
#### `/src/Infrastructure`
|
||||
|
||||
Infrastrukturkomponenten, die externe Systeme integrieren.
|
||||
|
||||
#### `/src/Config`
|
||||
|
||||
Konfigurationsdateien für verschiedene Module.
|
||||
|
||||
## Framework-Architektur
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
Das System nutzt einen leistungsfähigen DI-Container zur Verwaltung von Services:
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
class ServiceInitializer
|
||||
{
|
||||
public function __invoke(Container $container): Service
|
||||
{
|
||||
// Service erstellen und zurückgeben
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event-System
|
||||
|
||||
Ein Event-System ermöglicht lose Kopplung zwischen Komponenten:
|
||||
|
||||
```php
|
||||
$eventDispatcher->addHandler(EventClass::class, function($event) {
|
||||
// Event verarbeiten
|
||||
});
|
||||
```
|
||||
|
||||
### HTTP-Pipeline
|
||||
|
||||
HTTP-Requests durchlaufen eine Middleware-Pipeline:
|
||||
|
||||
```php
|
||||
class CustomMiddleware implements Middleware
|
||||
{
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
// Request verarbeiten
|
||||
$response = $next($request);
|
||||
// Response verarbeiten
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Module und ihre Interaktionen
|
||||
|
||||
### Analytics-Modul
|
||||
|
||||
Das Analytics-Modul erfasst und analysiert Anwendungsdaten:
|
||||
|
||||
- **Events tracken**: `$analytics->track('event_name', $properties)`
|
||||
- **HTTP-Tracking**: Automatisch durch `AnalyticsMiddleware`
|
||||
- **Error-Tracking**: Integration mit dem Error-Handling-System
|
||||
- **Dashboard**: Admin-Interface zur Datenvisualisierung
|
||||
|
||||
### Konfigurationssystem
|
||||
|
||||
Konfigurationen werden zentral verwaltet und injiziert:
|
||||
|
||||
```php
|
||||
class Service
|
||||
{
|
||||
public function __construct(private Configuration $config)
|
||||
{
|
||||
$settings = $this->config->get('module_name', $defaults);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Erweiterbarkeit
|
||||
|
||||
### Neue Module hinzufügen
|
||||
|
||||
1. Erstellen Sie ein neues Verzeichnis unter `/src/Framework`
|
||||
2. Implementieren Sie eine Initializer-Klasse mit dem `#[Initializer]`-Attribut
|
||||
3. Erstellen Sie eine entsprechende Konfigurationsdatei unter `/src/Config`
|
||||
|
||||
### Middleware hinzufügen
|
||||
|
||||
```php
|
||||
// In Ihrer Anwendungsklasse
|
||||
public function bootstrap(): void
|
||||
{
|
||||
$this->addMiddleware(YourMiddleware::class);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Dependency Injection**: Verwenden Sie Constructor-Injection für Abhängigkeiten
|
||||
- **Interfaces**: Definieren Sie Interfaces für alle Services
|
||||
- **Events**: Nutzen Sie Events für lose Kopplung zwischen Modulen
|
||||
- **Konfiguration**: Externalisieren Sie Konfigurationen in dedizierte Dateien
|
||||
- **Typsicherheit**: Nutzen Sie strenge Typisierung und readonly-Properties
|
||||
528
docs/architecture/components.md
Normal file
528
docs/architecture/components.md
Normal file
@@ -0,0 +1,528 @@
|
||||
# Hauptkomponenten
|
||||
|
||||
Diese Dokumentation beschreibt die Hauptkomponenten des Frameworks und ihre Verantwortlichkeiten. Jede Komponente ist so konzipiert, dass sie unabhängig verwendet oder durch eine benutzerdefinierte Implementierung ersetzt werden kann.
|
||||
|
||||
## Kernkomponenten
|
||||
|
||||
### Container
|
||||
|
||||
Der Container ist das Herzstück des Frameworks und verantwortlich für die Dependency Injection. Er bietet folgende Funktionen:
|
||||
|
||||
- Registrierung von Diensten
|
||||
- Auflösung von Abhängigkeiten
|
||||
- Verwaltung des Lebenszyklus von Diensten
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung des Containers
|
||||
$container = new Container();
|
||||
$container->bind(LoggerInterface::class, FileLogger::class);
|
||||
$container->singleton(DatabaseInterface::class, function () {
|
||||
return new Database(config('database'));
|
||||
});
|
||||
|
||||
$logger = $container->get(LoggerInterface::class);
|
||||
```
|
||||
|
||||
### Router
|
||||
|
||||
Der Router ist verantwortlich für das Mapping von HTTP-Anfragen zu Controller-Aktionen. Er unterstützt:
|
||||
|
||||
- Routendefinitionen für verschiedene HTTP-Methoden
|
||||
- Routengruppen mit gemeinsamen Präfixen und Middleware
|
||||
- Routenparameter und Einschränkungen
|
||||
- Namensräume für Routen
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung des Routers
|
||||
$router = new Router();
|
||||
$router->get('/users', [UserController::class, 'index']);
|
||||
$router->post('/users', [UserController::class, 'store']);
|
||||
$router->get('/users/{id}', [UserController::class, 'show'])->where('id', '[0-9]+');
|
||||
|
||||
$router->group('/admin', function (Router $router) {
|
||||
$router->get('/dashboard', [AdminController::class, 'dashboard']);
|
||||
})->middleware(AuthMiddleware::class);
|
||||
```
|
||||
|
||||
### Request
|
||||
|
||||
Die Request-Klasse kapselt eine HTTP-Anfrage und bietet Methoden für den Zugriff auf:
|
||||
|
||||
- Anfrageparameter
|
||||
- Header
|
||||
- Cookies
|
||||
- Dateien
|
||||
- Sitzungsdaten
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Request-Klasse
|
||||
$name = $request->input('name');
|
||||
$page = $request->query('page', 1);
|
||||
$token = $request->header('Authorization');
|
||||
$file = $request->file('avatar');
|
||||
$remember = $request->cookie('remember', false);
|
||||
$user = $request->session()->get('user');
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
Die Response-Klasse repräsentiert eine HTTP-Antwort und bietet Methoden für:
|
||||
|
||||
- Setzen von Statuscodes
|
||||
- Hinzufügen von Headern
|
||||
- Setzen von Cookies
|
||||
- Umleitung zu anderen URLs
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Response-Klasse
|
||||
$response = new Response('Hello World', 200);
|
||||
$response->header('Content-Type', 'text/plain');
|
||||
$response->cookie('visited', true, 60 * 24);
|
||||
|
||||
$redirectResponse = new RedirectResponse('/dashboard');
|
||||
$jsonResponse = new JsonResponse(['name' => 'John']);
|
||||
$viewResponse = new ViewResponse('welcome', ['user' => $user]);
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
Middleware-Komponenten verarbeiten Anfragen vor und nach der Controller-Aktion. Sie können:
|
||||
|
||||
- Anfragen validieren
|
||||
- Benutzer authentifizieren
|
||||
- Antworten transformieren
|
||||
- Anfragen protokollieren
|
||||
|
||||
```php
|
||||
// Beispiel für eine Middleware
|
||||
class AuthMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
if (!$this->isAuthenticated($request)) {
|
||||
return new RedirectResponse('/login');
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Datenbankkomponenten
|
||||
|
||||
### Database
|
||||
|
||||
Die Database-Komponente bietet eine Abstraktionsschicht für Datenbankoperationen. Sie unterstützt:
|
||||
|
||||
- Verschiedene Datenbanktypen (MySQL, SQLite, PostgreSQL)
|
||||
- Abfragen mit einem Query Builder
|
||||
- Transaktionen
|
||||
- Migrationen
|
||||
- Seeding
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Database-Komponente
|
||||
$users = $database->table('users')
|
||||
->where('active', true)
|
||||
->orderBy('name')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
$database->transaction(function ($db) {
|
||||
$db->table('users')->insert(['name' => 'John']);
|
||||
$db->table('profiles')->insert(['user_id' => $db->lastInsertId()]);
|
||||
});
|
||||
```
|
||||
|
||||
### Model
|
||||
|
||||
Die Model-Komponente bietet eine objektorientierte Schnittstelle für Datenbankoperationen. Sie unterstützt:
|
||||
|
||||
- Active Record Pattern
|
||||
- Beziehungen zwischen Modellen
|
||||
- Attribute und Mutators
|
||||
- Ereignisse
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Model-Komponente
|
||||
$user = new User();
|
||||
$user->name = 'John';
|
||||
$user->email = 'john@example.com';
|
||||
$user->save();
|
||||
|
||||
$users = User::where('active', true)->get();
|
||||
$user = User::find(1);
|
||||
$posts = $user->posts()->where('published', true)->get();
|
||||
```
|
||||
|
||||
### Migration
|
||||
|
||||
Die Migration-Komponente ermöglicht die Versionierung der Datenbankstruktur. Sie unterstützt:
|
||||
|
||||
- Erstellen und Ändern von Tabellen
|
||||
- Hinzufügen und Entfernen von Indizes
|
||||
- Rollback von Änderungen
|
||||
|
||||
```php
|
||||
// Beispiel für eine Migration
|
||||
class CreateUsersTable extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->schema->create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->boolean('active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->dropIfExists('users');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP-Komponenten
|
||||
|
||||
### Controller
|
||||
|
||||
Controller sind verantwortlich für die Verarbeitung von HTTP-Anfragen und die Rückgabe von Antworten. Sie bieten:
|
||||
|
||||
- Zugriff auf Request- und Response-Objekte
|
||||
- Validierung von Eingaben
|
||||
- Rendering von Views
|
||||
- Umleitung zu anderen URLs
|
||||
|
||||
```php
|
||||
// Beispiel für einen Controller
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$users = User::all();
|
||||
return $this->view('users.index', ['users' => $users]);
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
]);
|
||||
|
||||
$user = User::create($request->only(['name', 'email']));
|
||||
return $this->redirect('/users')->with('success', 'User created');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
Die View-Komponente ist verantwortlich für das Rendering von HTML-Templates. Sie unterstützt:
|
||||
|
||||
- Template-Dateien mit PHP-Syntax
|
||||
- Layouts und Partials
|
||||
- Template-Vererbung
|
||||
- Datenübergabe an Templates
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der View-Komponente
|
||||
$view = new View('users.show');
|
||||
$view->with('user', $user);
|
||||
$html = $view->render();
|
||||
|
||||
// Oder über den Controller
|
||||
return $this->view('users.show', ['user' => $user]);
|
||||
```
|
||||
|
||||
### Session
|
||||
|
||||
Die Session-Komponente verwaltet Benutzersitzungen. Sie unterstützt:
|
||||
|
||||
- Verschiedene Session-Backends (Datei, Datenbank, Redis)
|
||||
- Flash-Daten
|
||||
- Session-Regeneration
|
||||
- Session-Timeout
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Session-Komponente
|
||||
$session = new Session();
|
||||
$session->put('user_id', 1);
|
||||
$userId = $session->get('user_id');
|
||||
$session->flash('message', 'Welcome back!');
|
||||
$session->regenerate();
|
||||
$session->forget('user_id');
|
||||
```
|
||||
|
||||
### Cookie
|
||||
|
||||
Die Cookie-Komponente verwaltet HTTP-Cookies. Sie unterstützt:
|
||||
|
||||
- Setzen und Lesen von Cookies
|
||||
- Verschlüsselung von Cookie-Werten
|
||||
- Cookie-Parameter (Lebensdauer, Pfad, Domain, Secure, HttpOnly)
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Cookie-Komponente
|
||||
$cookie = new Cookie('name', 'value', 60 * 24); // 1 Tag
|
||||
$cookie->setPath('/');
|
||||
$cookie->setSecure(true);
|
||||
$cookie->setHttpOnly(true);
|
||||
|
||||
$response->setCookie($cookie);
|
||||
$value = $request->cookie('name');
|
||||
```
|
||||
|
||||
## Sicherheitskomponenten
|
||||
|
||||
### Validator
|
||||
|
||||
Die Validator-Komponente validiert Benutzereingaben. Sie unterstützt:
|
||||
|
||||
- Verschiedene Validierungsregeln
|
||||
- Benutzerdefinierte Validierungsregeln
|
||||
- Fehlermeldungen
|
||||
- Validierung von Arrays und verschachtelten Daten
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Validator-Komponente
|
||||
$validator = new Validator($data, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'age' => 'integer|min:18',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $validator->errors();
|
||||
// Fehler behandeln
|
||||
}
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Die Authentication-Komponente verwaltet die Benutzerauthentifizierung. Sie unterstützt:
|
||||
|
||||
- Verschiedene Authentifizierungsmethoden (Session, Token, Basic)
|
||||
- Mehrere Authentifizierungsquellen
|
||||
- Remember-Me-Funktionalität
|
||||
- Passwort-Hashing und -Verifizierung
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Authentication-Komponente
|
||||
$auth = new Authentication();
|
||||
if ($auth->attempt(['email' => $email, 'password' => $password], $remember)) {
|
||||
// Erfolgreiche Anmeldung
|
||||
}
|
||||
|
||||
$user = $auth->user();
|
||||
$auth->logout();
|
||||
```
|
||||
|
||||
### Authorization
|
||||
|
||||
Die Authorization-Komponente verwaltet die Benutzerautorisierung. Sie unterstützt:
|
||||
|
||||
- Rollenbasierte Zugriffskontrolle
|
||||
- Fähigkeitsbasierte Zugriffskontrolle
|
||||
- Richtlinien für komplexe Autorisierungslogik
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Authorization-Komponente
|
||||
$authorization = new Authorization();
|
||||
$authorization->define('edit-post', function (User $user, Post $post) {
|
||||
return $user->id === $post->user_id || $user->hasRole('editor');
|
||||
});
|
||||
|
||||
if ($authorization->can($user, 'edit-post', $post)) {
|
||||
// Benutzer darf den Beitrag bearbeiten
|
||||
}
|
||||
```
|
||||
|
||||
### CSRF
|
||||
|
||||
Die CSRF-Komponente schützt vor Cross-Site Request Forgery. Sie unterstützt:
|
||||
|
||||
- Generierung von CSRF-Tokens
|
||||
- Validierung von CSRF-Tokens
|
||||
- Automatische Integration in Formulare
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der CSRF-Komponente
|
||||
$csrf = new CsrfProtection();
|
||||
$token = $csrf->generateToken();
|
||||
|
||||
// In einem Formular
|
||||
echo '<input type="hidden" name="_token" value="' . $token . '">';
|
||||
|
||||
// Validierung
|
||||
if (!$csrf->validateToken($request->input('_token'))) {
|
||||
// Ungültiges Token
|
||||
}
|
||||
```
|
||||
|
||||
### WAF
|
||||
|
||||
Die Web Application Firewall (WAF) schützt vor gängigen Angriffen. Sie unterstützt:
|
||||
|
||||
- Erkennung und Blockierung von SQL-Injection
|
||||
- Erkennung und Blockierung von XSS
|
||||
- Erkennung und Blockierung von Command Injection
|
||||
- Erkennung und Blockierung von Path Traversal
|
||||
- Maschinelles Lernen zur Erkennung von Anomalien
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der WAF-Komponente
|
||||
$waf = new WebApplicationFirewall();
|
||||
$waf->addRule(new SqlInjectionRule());
|
||||
$waf->addRule(new XssRule());
|
||||
|
||||
if ($waf->detect($request)) {
|
||||
// Angriff erkannt
|
||||
$waf->block($request);
|
||||
}
|
||||
```
|
||||
|
||||
## Weitere Komponenten
|
||||
|
||||
### Cache
|
||||
|
||||
Die Cache-Komponente bietet eine einheitliche Schnittstelle für verschiedene Cache-Backends. Sie unterstützt:
|
||||
|
||||
- Verschiedene Cache-Treiber (Datei, Redis, Memcached, Memory)
|
||||
- Cache-Tags
|
||||
- Cache-Invalidierung
|
||||
- Cache-Prefixing
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Cache-Komponente
|
||||
$cache = new Cache();
|
||||
$cache->put('key', 'value', 60); // 60 Minuten
|
||||
$value = $cache->get('key', 'default');
|
||||
$cache->has('key');
|
||||
$cache->forget('key');
|
||||
$cache->flush();
|
||||
|
||||
$cache->tags(['users', 'profiles'])->put('user:1', $user, 60);
|
||||
```
|
||||
|
||||
### Logger
|
||||
|
||||
Die Logger-Komponente protokolliert Anwendungsereignisse. Sie unterstützt:
|
||||
|
||||
- Verschiedene Log-Level (Debug, Info, Warning, Error, Critical)
|
||||
- Verschiedene Log-Handler (Datei, Syslog, Slack, E-Mail)
|
||||
- Kontextdaten für Log-Einträge
|
||||
- Log-Rotation
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Logger-Komponente
|
||||
$logger = new Logger();
|
||||
$logger->debug('Debug-Nachricht');
|
||||
$logger->info('Info-Nachricht');
|
||||
$logger->warning('Warnung', ['user_id' => 1]);
|
||||
$logger->error('Fehler', ['exception' => $exception]);
|
||||
$logger->critical('Kritischer Fehler', ['data' => $data]);
|
||||
```
|
||||
|
||||
### Event
|
||||
|
||||
Die Event-Komponente implementiert ein Publish-Subscribe-Muster. Sie unterstützt:
|
||||
|
||||
- Ereignisregistrierung und -auslösung
|
||||
- Ereignislistener
|
||||
- Ereignisabonnenten
|
||||
- Ereignispriorisierung
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Event-Komponente
|
||||
$eventDispatcher = new EventDispatcher();
|
||||
$eventDispatcher->addListener(UserRegistered::class, SendWelcomeEmail::class);
|
||||
$eventDispatcher->addListener(UserRegistered::class, function (UserRegistered $event) {
|
||||
// Benutzerdefinierte Logik
|
||||
});
|
||||
|
||||
$eventDispatcher->dispatch(new UserRegistered($user));
|
||||
```
|
||||
|
||||
### Console
|
||||
|
||||
Die Console-Komponente bietet ein Kommandozeilen-Interface für die Anwendung. Sie unterstützt:
|
||||
|
||||
- Benutzerdefinierte Befehle
|
||||
- Eingabeargumente und -optionen
|
||||
- Interaktive Eingabe
|
||||
- Ausgabeformatierung
|
||||
|
||||
```php
|
||||
// Beispiel für einen Konsolenbefehl
|
||||
class MigrateCommand extends Command
|
||||
{
|
||||
protected string $name = 'db:migrate';
|
||||
protected string $description = 'Run database migrations';
|
||||
|
||||
public function handle(Input $input, Output $output): int
|
||||
{
|
||||
$output->writeln('Running migrations...');
|
||||
// Migrationen ausführen
|
||||
$output->success('Migrations completed successfully');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Filesystem
|
||||
|
||||
Die Filesystem-Komponente bietet eine Abstraktionsschicht für Dateisystemoperationen. Sie unterstützt:
|
||||
|
||||
- Lokale und entfernte Dateisysteme
|
||||
- Datei- und Verzeichnisoperationen
|
||||
- Dateimetadaten
|
||||
- Dateispeicherung und -abruf
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Filesystem-Komponente
|
||||
$filesystem = new Filesystem();
|
||||
$filesystem->write('path/to/file.txt', 'Inhalt');
|
||||
$content = $filesystem->read('path/to/file.txt');
|
||||
$filesystem->delete('path/to/file.txt');
|
||||
$filesystem->createDirectory('path/to/directory');
|
||||
$files = $filesystem->listContents('path/to/directory');
|
||||
```
|
||||
|
||||
### Queue
|
||||
|
||||
Die Queue-Komponente ermöglicht die asynchrone Verarbeitung von Aufgaben. Sie unterstützt:
|
||||
|
||||
- Verschiedene Queue-Treiber (Datenbank, Redis, Beanstalkd)
|
||||
- Verzögerte Aufgaben
|
||||
- Aufgabenpriorisierung
|
||||
- Fehlerbehandlung und Wiederholungsversuche
|
||||
|
||||
```php
|
||||
// Beispiel für die Verwendung der Queue-Komponente
|
||||
$queue = new Queue();
|
||||
$queue->push(new SendEmailJob($user, $message));
|
||||
$queue->later(60, new CleanupJob()); // 60 Sekunden verzögert
|
||||
|
||||
// Verarbeitung einer Aufgabe
|
||||
class SendEmailJob implements JobInterface
|
||||
{
|
||||
public function __construct(private User $user, private string $message) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
// E-Mail senden
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Architekturübersicht](overview.md): Überblick über die Architektur des Frameworks.
|
||||
- [Entwurfsmuster](patterns.md): Erläuterung der im Framework verwendeten Entwurfsmuster.
|
||||
- [Komponentendokumentation](../components/README.md): Detaillierte Dokumentation der einzelnen Komponenten.
|
||||
@@ -1,70 +0,0 @@
|
||||
# Architektur-Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation beschreibt die Architektur und Struktur des Projekts. Sie bietet einen Überblick über die wichtigsten Komponenten, deren Beziehungen und die zugrundeliegenden Architekturprinzipien.
|
||||
|
||||
## Inhalte
|
||||
|
||||
- [Projektstruktur](/architecture/STRUKTUR-DOKUMENTATION.md) - Überblick über die Struktur des Projekts
|
||||
|
||||
## Architekturprinzipien
|
||||
|
||||
Das Projekt folgt diesen grundlegenden Architekturprinzipien:
|
||||
|
||||
1. **Modulare Architektur**: Klare Trennung von Verantwortlichkeiten in Modulen
|
||||
2. **Service-orientiertes Design**: Funktionalitäten als unabhängige Services
|
||||
3. **Dependency Injection**: Abhängigkeiten werden explizit injiziert
|
||||
4. **Event-basierte Kommunikation**: Lose Kopplung durch Events
|
||||
5. **Schichtenarchitektur**: Trennung von Präsentation, Anwendungslogik und Daten
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Framework-Kern
|
||||
|
||||
Der Framework-Kern stellt grundlegende Infrastrukturkomponenten bereit:
|
||||
|
||||
- **DI-Container**: Verwaltung von Service-Abhängigkeiten
|
||||
- **Event-System**: Event-basierte Kommunikation
|
||||
- **HTTP-Komponenten**: Request/Response-Handling
|
||||
- **Routing**: URL-zu-Controller-Mapping
|
||||
|
||||
### Anwendungsschicht
|
||||
|
||||
Die Anwendungsschicht implementiert die Geschäftslogik:
|
||||
|
||||
- **Services**: Implementierung von Anwendungsfunktionen
|
||||
- **Commands/Queries**: Command-Query-Separation-Prinzip
|
||||
- **Controllers**: HTTP-Request-Handling
|
||||
|
||||
### Domainschicht
|
||||
|
||||
Die Domainschicht enthält die Kerngeschäftslogik:
|
||||
|
||||
- **Entities**: Geschäftsobjekte mit Identität
|
||||
- **Value Objects**: Unveränderliche Wertobjekte
|
||||
- **Domain Services**: Domänenspezifische Logik
|
||||
|
||||
### Infrastrukturschicht
|
||||
|
||||
Die Infrastrukturschicht bietet technische Funktionen:
|
||||
|
||||
- **Persistenz**: Datenbankzugriff und -verwaltung
|
||||
- **Messaging**: Externe Kommunikation
|
||||
- **Integration**: Anbindung an externe Systeme
|
||||
|
||||
## Datenfluss
|
||||
|
||||
Ein typischer Datenfluss im System:
|
||||
|
||||
1. HTTP-Request wird vom Router empfangen
|
||||
2. Middleware-Pipeline verarbeitet den Request
|
||||
3. Controller erhält den Request und delegiert an Services
|
||||
4. Services implementieren die Geschäftslogik
|
||||
5. Domain-Objekte repräsentieren den Geschäftszustand
|
||||
6. Repositories speichern/laden Daten
|
||||
7. Response wird erstellt und zurückgegeben
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
Für detailliertere Informationen zur Architektur siehe die [Projektstruktur-Dokumentation](/architecture/STRUKTUR-DOKUMENTATION.md).
|
||||
226
docs/architecture/overview.md
Normal file
226
docs/architecture/overview.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Architekturübersicht
|
||||
|
||||
Diese Dokumentation bietet einen Überblick über die Architektur des Frameworks und erklärt die grundlegenden Konzepte und Designentscheidungen.
|
||||
|
||||
## Architekturprinzipien
|
||||
|
||||
Das Framework wurde nach folgenden Prinzipien entwickelt:
|
||||
|
||||
1. **Modularität**: Das Framework ist in unabhängige Module aufgeteilt, die einzeln verwendet oder ausgetauscht werden können.
|
||||
2. **Erweiterbarkeit**: Alle Komponenten sind so konzipiert, dass sie einfach erweitert oder angepasst werden können.
|
||||
3. **Testbarkeit**: Die Architektur ermöglicht einfaches Testen durch klare Schnittstellen und Dependency Injection.
|
||||
4. **Leistung**: Leistungsoptimierungen sind ein integraler Bestandteil des Designs, nicht nur eine nachträgliche Überlegung.
|
||||
5. **Sicherheit**: Sicherheitsmaßnahmen sind standardmäßig aktiviert und in die Kernkomponenten integriert.
|
||||
|
||||
## Schichtenarchitektur
|
||||
|
||||
Das Framework verwendet eine mehrschichtige Architektur, die eine klare Trennung der Verantwortlichkeiten ermöglicht:
|
||||
|
||||
```
|
||||
+-------------------+
|
||||
| Anwendung | <-- Anwendungsspezifischer Code (Controller, Models, Services)
|
||||
+-------------------+
|
||||
| Framework | <-- Framework-Komponenten (Routing, Validierung, Datenbank)
|
||||
+-------------------+
|
||||
| Infrastruktur | <-- Infrastrukturkomponenten (HTTP, Dateisystem, Cache)
|
||||
+-------------------+
|
||||
```
|
||||
|
||||
### Anwendungsschicht
|
||||
|
||||
Die Anwendungsschicht enthält den anwendungsspezifischen Code, der die Geschäftslogik implementiert. Diese Schicht umfasst:
|
||||
|
||||
- **Controller**: Verarbeiten HTTP-Anfragen und geben Antworten zurück
|
||||
- **Models**: Repräsentieren Geschäftsobjekte und Datenstrukturen
|
||||
- **Services**: Implementieren komplexe Geschäftslogik
|
||||
- **Repositories**: Kapseln den Datenzugriff
|
||||
|
||||
### Framework-Schicht
|
||||
|
||||
Die Framework-Schicht stellt die Kernfunktionalität bereit, die von der Anwendungsschicht verwendet wird. Diese Schicht umfasst:
|
||||
|
||||
- **Routing**: Leitet Anfragen an die entsprechenden Controller weiter
|
||||
- **Validierung**: Überprüft Benutzereingaben
|
||||
- **Datenbank**: Bietet Datenbankabstraktion und ORM-Funktionalität
|
||||
- **Authentifizierung**: Verwaltet Benutzerauthentifizierung und -autorisierung
|
||||
- **Caching**: Implementiert verschiedene Caching-Strategien
|
||||
|
||||
### Infrastrukturschicht
|
||||
|
||||
Die Infrastrukturschicht bietet grundlegende Dienste und Abstraktionen für die darüber liegenden Schichten. Diese Schicht umfasst:
|
||||
|
||||
- **HTTP**: Verarbeitet HTTP-Anfragen und -Antworten
|
||||
- **Dateisystem**: Bietet Abstraktionen für Dateisystemoperationen
|
||||
- **Cache**: Implementiert verschiedene Cache-Backends
|
||||
- **Logging**: Bietet Logging-Funktionalität
|
||||
- **Events**: Implementiert ein Event-System
|
||||
|
||||
## Anfragelebenszyklus
|
||||
|
||||
Der Lebenszyklus einer Anfrage im Framework durchläuft mehrere Phasen:
|
||||
|
||||
1. **Bootstrapping**: Die Anwendung wird initialisiert, Konfigurationen werden geladen und Dienste registriert.
|
||||
2. **Routing**: Die Anfrage wird an den entsprechenden Controller weitergeleitet.
|
||||
3. **Middleware**: Die Anfrage durchläuft die konfigurierten Middleware-Komponenten.
|
||||
4. **Controller**: Der Controller verarbeitet die Anfrage und gibt eine Antwort zurück.
|
||||
5. **Middleware (rückwärts)**: Die Antwort durchläuft die Middleware-Komponenten in umgekehrter Reihenfolge.
|
||||
6. **Antwort**: Die Antwort wird an den Client gesendet.
|
||||
|
||||
```
|
||||
+----------------+ +------------+ +------------+ +------------+ +----------------+
|
||||
| | | | | | | | | |
|
||||
| Bootstrapping | --> | Routing | --> | Middleware | --> | Controller | --> | Antwort senden |
|
||||
| | | | | | | | | |
|
||||
+----------------+ +------------+ +------------+ +------------+ +----------------+
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
Das Framework verwendet Dependency Injection (DI), um Abhängigkeiten zwischen Komponenten zu verwalten. Der DI-Container ist verantwortlich für:
|
||||
|
||||
1. **Registrierung**: Dienste werden im Container registriert.
|
||||
2. **Auflösung**: Der Container löst Abhängigkeiten automatisch auf.
|
||||
3. **Lebenszyklus**: Der Container verwaltet den Lebenszyklus von Diensten (Singleton, Transient, etc.).
|
||||
|
||||
Beispiel für die Verwendung des DI-Containers:
|
||||
|
||||
```php
|
||||
// Registrierung eines Dienstes
|
||||
$container->bind(UserRepositoryInterface::class, UserRepository::class);
|
||||
|
||||
// Auflösung eines Dienstes
|
||||
$userRepository = $container->get(UserRepositoryInterface::class);
|
||||
```
|
||||
|
||||
## Service Provider
|
||||
|
||||
Service Provider sind Klassen, die Dienste im DI-Container registrieren und konfigurieren. Sie bieten einen strukturierten Weg, um Komponenten zu initialisieren und zu konfigurieren.
|
||||
|
||||
Beispiel für einen Service Provider:
|
||||
|
||||
```php
|
||||
class DatabaseServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->singleton(ConnectionInterface::class, function () {
|
||||
return new Connection(config('database'));
|
||||
});
|
||||
|
||||
$container->bind(QueryBuilderInterface::class, QueryBuilder::class);
|
||||
}
|
||||
|
||||
public function boot(Container $container): void
|
||||
{
|
||||
// Initialisierungscode, der nach der Registrierung ausgeführt wird
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ereignissystem
|
||||
|
||||
Das Framework implementiert ein Ereignissystem, das die Entkopplung von Komponenten ermöglicht. Das Ereignissystem besteht aus:
|
||||
|
||||
1. **Events**: Objekte, die Informationen über ein Ereignis enthalten.
|
||||
2. **Listeners**: Klassen, die auf bestimmte Ereignisse reagieren.
|
||||
3. **Dispatcher**: Verantwortlich für das Versenden von Ereignissen an registrierte Listener.
|
||||
|
||||
Beispiel für die Verwendung des Ereignissystems:
|
||||
|
||||
```php
|
||||
// Definieren eines Events
|
||||
class UserRegistered
|
||||
{
|
||||
public function __construct(public readonly User $user) {}
|
||||
}
|
||||
|
||||
// Definieren eines Listeners
|
||||
class SendWelcomeEmail implements ListenerInterface
|
||||
{
|
||||
public function handle(UserRegistered $event): void
|
||||
{
|
||||
// E-Mail an den neuen Benutzer senden
|
||||
}
|
||||
}
|
||||
|
||||
// Registrieren des Listeners
|
||||
$eventDispatcher->addListener(UserRegistered::class, SendWelcomeEmail::class);
|
||||
|
||||
// Auslösen des Events
|
||||
$eventDispatcher->dispatch(new UserRegistered($user));
|
||||
```
|
||||
|
||||
## Middleware-Pipeline
|
||||
|
||||
Das Framework verwendet eine Middleware-Pipeline, um Anfragen zu verarbeiten. Middleware-Komponenten können:
|
||||
|
||||
1. Anfragen vor der Verarbeitung durch den Controller modifizieren.
|
||||
2. Antworten nach der Verarbeitung durch den Controller modifizieren.
|
||||
3. Den Anfrage-/Antwort-Zyklus vollständig abbrechen.
|
||||
|
||||
Beispiel für eine Middleware:
|
||||
|
||||
```php
|
||||
class AuthenticationMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
if (!$this->isAuthenticated($request)) {
|
||||
return new RedirectResponse('/login');
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
private function isAuthenticated(Request $request): bool
|
||||
{
|
||||
// Überprüfen, ob der Benutzer authentifiziert ist
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Konfigurationssystem
|
||||
|
||||
Das Framework verwendet ein flexibles Konfigurationssystem, das verschiedene Konfigurationsquellen unterstützt:
|
||||
|
||||
1. **Umgebungsvariablen**: Konfiguration über `.env`-Dateien.
|
||||
2. **Konfigurationsdateien**: PHP-Dateien, die Konfigurationsarrays zurückgeben.
|
||||
3. **Laufzeitkonfiguration**: Dynamische Konfiguration zur Laufzeit.
|
||||
|
||||
Das Konfigurationssystem unterstützt auch umgebungsspezifische Konfigurationen, sodass verschiedene Einstellungen für Entwicklung, Test und Produktion verwendet werden können.
|
||||
|
||||
## Erweiterbarkeit
|
||||
|
||||
Das Framework ist so konzipiert, dass es einfach erweitert werden kann:
|
||||
|
||||
1. **Interfaces**: Alle Kernkomponenten definieren Interfaces, die implementiert werden können.
|
||||
2. **Abstrakte Klassen**: Viele Komponenten bieten abstrakte Basisklassen, die erweitert werden können.
|
||||
3. **Hooks**: Das Framework bietet Hooks, an denen benutzerdefinierter Code ausgeführt werden kann.
|
||||
4. **Plugins**: Das Plugin-System ermöglicht die einfache Integration von Drittanbietercode.
|
||||
|
||||
## Sicherheitsarchitektur
|
||||
|
||||
Die Sicherheitsarchitektur des Frameworks umfasst mehrere Schichten:
|
||||
|
||||
1. **Eingabevalidierung**: Validierung aller Benutzereingaben.
|
||||
2. **Ausgabebereinigung**: Automatische Bereinigung von Ausgaben, um XSS zu verhindern.
|
||||
3. **CSRF-Schutz**: Schutz vor Cross-Site Request Forgery.
|
||||
4. **Authentifizierung**: Flexible Authentifizierungsmechanismen.
|
||||
5. **Autorisierung**: Rollenbasierte und fähigkeitsbasierte Zugriffskontrolle.
|
||||
6. **Web Application Firewall (WAF)**: Integrierte WAF zum Schutz vor gängigen Angriffen.
|
||||
|
||||
## Leistungsoptimierungen
|
||||
|
||||
Das Framework implementiert verschiedene Leistungsoptimierungen:
|
||||
|
||||
1. **Caching**: Mehrschichtiges Caching für Konfiguration, Routen, Views, etc.
|
||||
2. **Lazy Loading**: Komponenten werden erst geladen, wenn sie benötigt werden.
|
||||
3. **Kompilierung**: Views und Konfigurationen werden für schnelleren Zugriff kompiliert.
|
||||
4. **Pooling**: Verbindungspooling für Datenbanken und andere Ressourcen.
|
||||
5. **Optimierte Autoloading**: PSR-4-konformes Autoloading mit Optimierungen.
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Komponenten](components.md): Detaillierte Informationen zu den Hauptkomponenten des Frameworks.
|
||||
- [Entwurfsmuster](patterns.md): Erläuterung der im Framework verwendeten Entwurfsmuster.
|
||||
- [Komponentendokumentation](../components/README.md): Dokumentation der einzelnen Komponenten.
|
||||
1137
docs/architecture/patterns.md
Normal file
1137
docs/architecture/patterns.md
Normal file
File diff suppressed because it is too large
Load Diff
237
docs/batch-api-examples.md
Normal file
237
docs/batch-api-examples.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Batch API Examples
|
||||
|
||||
The Batch API allows you to send multiple HTTP requests in a single batch request, improving efficiency by reducing round-trips and enabling concurrent processing.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Batch Request Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"operations": [
|
||||
{
|
||||
"id": "get-user-1",
|
||||
"method": "GET",
|
||||
"path": "/api/examples/users/1",
|
||||
"headers": {
|
||||
"Authorization": "Bearer token123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "create-post",
|
||||
"method": "POST",
|
||||
"path": "/api/examples/posts",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": "{\"title\": \"Hello World\", \"content\": \"This is my first post\"}"
|
||||
}
|
||||
],
|
||||
"continue_on_error": false
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Response Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"responses": [
|
||||
{
|
||||
"id": "get-user-1",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": "{\"id\": 1, \"name\": \"User 1\", \"email\": \"user1@example.com\"}"
|
||||
},
|
||||
{
|
||||
"id": "create-post",
|
||||
"status": 201,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": "{\"id\": 1234, \"title\": \"Hello World\", \"content\": \"This is my first post\"}"
|
||||
}
|
||||
],
|
||||
"total": 2,
|
||||
"successful": 2,
|
||||
"failed": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Example Requests
|
||||
|
||||
### Simple GET Operations
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/api/batch \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"operations": [
|
||||
{
|
||||
"id": "user-1",
|
||||
"method": "GET",
|
||||
"path": "/api/examples/users/1"
|
||||
},
|
||||
{
|
||||
"id": "user-2",
|
||||
"method": "GET",
|
||||
"path": "/api/examples/users/2"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Mixed Operations (CRUD)
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/api/batch \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"operations": [
|
||||
{
|
||||
"id": "create-post",
|
||||
"method": "POST",
|
||||
"path": "/api/examples/posts",
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"body": "{\"title\": \"New Post\", \"content\": \"Great content\"}"
|
||||
},
|
||||
{
|
||||
"id": "update-post",
|
||||
"method": "PUT",
|
||||
"path": "/api/examples/posts/123",
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"body": "{\"title\": \"Updated Post\", \"content\": \"Updated content\"}"
|
||||
},
|
||||
{
|
||||
"id": "delete-post",
|
||||
"method": "DELETE",
|
||||
"path": "/api/examples/posts/456"
|
||||
}
|
||||
],
|
||||
"continue_on_error": true
|
||||
}'
|
||||
```
|
||||
|
||||
### With Query Parameters
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/api/batch \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"operations": [
|
||||
{
|
||||
"id": "slow-operation",
|
||||
"method": "GET",
|
||||
"path": "/api/examples/slow?delay=2"
|
||||
},
|
||||
{
|
||||
"id": "another-user",
|
||||
"method": "GET",
|
||||
"path": "/api/examples/users/3",
|
||||
"query": {"include": "profile"}
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Continue on Error (true)
|
||||
|
||||
When `continue_on_error` is `true`, all operations will be executed even if some fail:
|
||||
|
||||
```json
|
||||
{
|
||||
"operations": [
|
||||
{
|
||||
"id": "valid-request",
|
||||
"method": "GET",
|
||||
"path": "/api/examples/users/1"
|
||||
},
|
||||
{
|
||||
"id": "invalid-request",
|
||||
"method": "GET",
|
||||
"path": "/api/nonexistent/endpoint"
|
||||
}
|
||||
],
|
||||
"continue_on_error": true
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"responses": [
|
||||
{
|
||||
"id": "valid-request",
|
||||
"status": 200,
|
||||
"body": "{\"id\": 1, \"name\": \"User 1\"}"
|
||||
},
|
||||
{
|
||||
"id": "invalid-request",
|
||||
"status": 404,
|
||||
"error": "Not Found"
|
||||
}
|
||||
],
|
||||
"total": 2,
|
||||
"successful": 1,
|
||||
"failed": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Stop on First Error (false)
|
||||
|
||||
When `continue_on_error` is `false`, processing stops at the first error:
|
||||
|
||||
```json
|
||||
{
|
||||
"operations": [...],
|
||||
"continue_on_error": false
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Concurrent Processing
|
||||
|
||||
The batch API processes operations concurrently when possible:
|
||||
- Operations with no dependencies run in parallel
|
||||
- Maximum concurrent operations: 10 (configurable)
|
||||
- Large batches are processed in chunks
|
||||
|
||||
### Limits
|
||||
|
||||
- Maximum operations per batch: 100
|
||||
- Maximum concurrent operations: 10
|
||||
- Request timeout: 30 seconds
|
||||
- Supported methods: GET, POST, PUT, DELETE, PATCH
|
||||
|
||||
### Get Batch Info
|
||||
|
||||
```bash
|
||||
curl -X GET https://localhost/api/batch/info \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"max_operations": 100,
|
||||
"max_concurrent_operations": 10,
|
||||
"supported_methods": ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||
"continue_on_error_supported": true
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use meaningful IDs**: Each operation should have a unique, descriptive ID
|
||||
2. **Group related operations**: Batch operations that are logically related
|
||||
3. **Handle errors gracefully**: Use `continue_on_error` appropriately
|
||||
4. **Respect limits**: Don't exceed the maximum operations limit
|
||||
5. **Monitor performance**: Large batches may impact server performance
|
||||
6. **Use appropriate methods**: Only use HTTP methods that are allowed
|
||||
190
docs/claude/architecture.md
Normal file
190
docs/claude/architecture.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Architecture
|
||||
|
||||
Architektur-Übersicht des Custom PHP Frameworks.
|
||||
|
||||
## Core Architectural Patterns
|
||||
|
||||
### Fundamentale Prinzipien
|
||||
|
||||
- **No Inheritance**: Komposition über Vererbung - `extends` komplett vermeiden
|
||||
- **Immutable by Design**: Objekte sollten wann immer möglich unveränderlich sein
|
||||
- **Readonly Everywhere**: Klassen und Properties `readonly` wo möglich
|
||||
- **Final by Default**: Klassen sind `final` außer wenn explizit für Erweiterung designt
|
||||
- **Explicit Dependency Injection**: Kein globaler State oder Service Locators
|
||||
- **Modular Architecture**: Minimale externe Abhängigkeiten, klare Grenzen
|
||||
- **Event-Driven Architecture**: Lose Kopplung durch Domain Events
|
||||
- **Automatic Discovery**: Convention over Configuration mit Attribute Scanning
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── Application/ # Anwendungsspezifische Controller und Logik
|
||||
├── Domain/ # Domain Models und Business Logic
|
||||
├── Framework/ # Framework Core Komponenten
|
||||
│ └── Mcp/ # MCP Server und Tools für AI Integration
|
||||
└── Infrastructure/ # Externe Service Integrationen
|
||||
|
||||
resources/ # Frontend Assets (CSS, JS)
|
||||
public/ # Web-zugängliche Dateien
|
||||
tests/ # Test Dateien
|
||||
docs/ # Dokumentation
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### Application Bootstrap
|
||||
|
||||
**`src/Framework/Core/Application.php`**
|
||||
- Haupt-Anwendungsklasse die den Request-Lifecycle orchestriert
|
||||
- Event-basierte Architektur für Anwendungs-Lifecycle
|
||||
|
||||
**`src/Framework/Core/AppBootstraper.php`**
|
||||
- Bootstrapped die Anwendung und den DI Container
|
||||
- Verwendet Event System für Lifecycle Management
|
||||
|
||||
**Events**: ApplicationBooted, BeforeHandleRequest, AfterHandleRequest
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
**Container Interface**: `src/Framework/DI/Container.php`
|
||||
**Default Implementation**: `src/Framework/DI/DefaultContainer.php`
|
||||
|
||||
**Features**:
|
||||
- Binding, Singletons und Instance Registration
|
||||
- Automatische Dependency Resolution
|
||||
- Method Invocation mit Parameter Resolution
|
||||
- Cached Reflection Provider für Performance
|
||||
|
||||
### HTTP & Routing
|
||||
|
||||
**Attribute-Based Routing** mit `#[Route]` Attributen:
|
||||
|
||||
```php
|
||||
final readonly class ExampleController
|
||||
{
|
||||
#[Route(path: '/api/example', method: Method::GET)]
|
||||
public function getExample(): JsonResult
|
||||
{
|
||||
return new JsonResult(['message' => 'Hello World']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Core Routing**: `src/Framework/Http/Middlewares/RoutingMiddleware.php`
|
||||
- Middleware Chain Pattern für Request Processing
|
||||
- Unterstützung verschiedener Result Types (JsonResult, ViewResult, Redirect, etc.)
|
||||
|
||||
### MCP Integration
|
||||
|
||||
**`src/Framework/Mcp/`** - Vollständige MCP Server Implementation
|
||||
- `#[McpTool]` und `#[McpResource]` Attribute für AI Integration
|
||||
- Automatische Discovery über Framework's Attribute System
|
||||
- Sichere, projekt-beschränkte Dateisystem-Zugriffe für AI
|
||||
|
||||
```php
|
||||
final readonly class FrameworkAnalyzer
|
||||
{
|
||||
#[McpTool(name: 'analyze_routes', description: 'Get all registered routes')]
|
||||
public function analyzeRoutes(): array
|
||||
{
|
||||
return $this->compiledRoutes->getStaticRoutes();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Systems
|
||||
|
||||
### Discovery System
|
||||
|
||||
**Automatic Attribute Scanning**:
|
||||
- Eliminiert manuelle Konfiguration für Routes, Middleware und Commands
|
||||
- Caching für Performance-Optimierung
|
||||
- Unified Discovery Service für mehrere Attribute-Typen
|
||||
|
||||
### Database System
|
||||
|
||||
**EntityManager mit UnitOfWork Pattern**:
|
||||
- Automatisches Change Tracking
|
||||
- Bulk Operations für Performance
|
||||
- Transaction Management mit Rollback Support
|
||||
- Identity Mapping und Lazy Loading
|
||||
|
||||
**Schema Builder**:
|
||||
- Database-agnostische Migrationen
|
||||
- Fluent API für Schema-Definition
|
||||
- Timestamp-basierte Migration Versionierung
|
||||
- Support für MySQL, PostgreSQL, SQLite
|
||||
|
||||
**Connection Pooling**:
|
||||
- Health Monitoring und automatische Recovery
|
||||
- Retry Logic mit exponential Backoff
|
||||
- Warmup Strategien für optimale Performance
|
||||
|
||||
### Event System
|
||||
|
||||
**Event Dispatcher** für Application Events:
|
||||
- Verwendung für Application Lifecycle Management
|
||||
- Domain Events für Business Logic
|
||||
- Erweiterbare Event Handling Architektur
|
||||
|
||||
### Command/Query Bus Pattern
|
||||
|
||||
**`src/Framework/CommandBus/`** - Command/Query Handling
|
||||
- Middleware Support für Command Processing
|
||||
- Automatic Handler Discovery und Registration
|
||||
|
||||
### Performance Optimizations
|
||||
|
||||
**Route Compilation**:
|
||||
- Compiled Routes mit separater Static/Dynamic Behandlung
|
||||
- Route Parameter Extraction optimiert
|
||||
- Pre-compiled Regex Patterns
|
||||
|
||||
**Caching System**:
|
||||
- Multi-Level Caching für Attributes und Configuration
|
||||
- Cached Reflection Provider für Dependency Injection
|
||||
- Connection Pooling mit Retry Logic
|
||||
|
||||
## Value Objects System
|
||||
|
||||
**Extensive Use of Value Objects** statt primitiver Typen:
|
||||
|
||||
```php
|
||||
// ❌ Keine Arrays oder Primitive verwenden
|
||||
function processUser(array $user): array
|
||||
|
||||
// ✅ Value Objects verwenden
|
||||
function processUser(User $user): UserProfile
|
||||
```
|
||||
|
||||
**Available Value Objects**:
|
||||
- Core: Email, RGBColor, Url, Hash, Version, Coordinates
|
||||
- HTTP: FlashMessage, ValidationError, RouteParameters
|
||||
- Security: OWASPEventIdentifier, MaskedEmail, ThreatLevel
|
||||
- Performance: Measurement, MetricContext, MemorySummary
|
||||
|
||||
## Middleware System
|
||||
|
||||
**Priority-Based Middleware Chain**:
|
||||
- `#[MiddlewarePriorityAttribute]` für Reihenfolge
|
||||
- Request State Management zwischen Middleware
|
||||
- Content Negotiation für flexible Responses
|
||||
|
||||
## Security Architecture
|
||||
|
||||
**Defense in Depth**:
|
||||
- IP-based Authentication für Admin Routes
|
||||
- Route Protection über Auth Attribute
|
||||
- Input Validation und Sanitization
|
||||
- CSRF Protection und Security Headers
|
||||
- OWASP Security Event Logging
|
||||
|
||||
## Testing Architecture
|
||||
|
||||
**Mixed Testing Approach**:
|
||||
- PHPUnit für traditionelle Tests
|
||||
- Pest Framework für moderne Syntax
|
||||
- Integration Tests für Web Controller
|
||||
- Unit Tests für Domain Logic
|
||||
- Test Files spiegeln Source Structure wider
|
||||
27
docs/claude/async-components.md
Normal file
27
docs/claude/async-components.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Async Components
|
||||
|
||||
This guide covers the async and concurrent programming features.
|
||||
|
||||
## Fiber Manager
|
||||
|
||||
TODO: Document FiberManager usage and patterns
|
||||
|
||||
## Async Promises
|
||||
|
||||
TODO: Document AsyncPromise implementation
|
||||
|
||||
## Background Job Processing
|
||||
|
||||
TODO: Document BackgroundJob and BackgroundJobProcessor
|
||||
|
||||
## Async Patterns
|
||||
|
||||
TODO: Document common async patterns (Pool, Queue, Channel)
|
||||
|
||||
## Concurrency Control
|
||||
|
||||
TODO: Document Mutex, Semaphore, and Barrier
|
||||
|
||||
## Async Best Practices
|
||||
|
||||
TODO: List async programming best practices
|
||||
31
docs/claude/common-workflows.md
Normal file
31
docs/claude/common-workflows.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Common Development Workflows
|
||||
|
||||
This guide provides standard workflows for common development tasks in the framework.
|
||||
|
||||
## Adding a New Feature
|
||||
|
||||
TODO: Document the standard workflow for adding new features
|
||||
|
||||
## Implementing an API Endpoint
|
||||
|
||||
TODO: Document API endpoint implementation patterns
|
||||
|
||||
## Bug Fix Workflow
|
||||
|
||||
TODO: Document the standard bug fix process
|
||||
|
||||
## Database Migration Workflow
|
||||
|
||||
TODO: Document migration creation and execution
|
||||
|
||||
## Refactoring Workflow
|
||||
|
||||
TODO: Document safe refactoring practices
|
||||
|
||||
## Performance Optimization Workflow
|
||||
|
||||
TODO: Document performance optimization process
|
||||
|
||||
## Best Practices
|
||||
|
||||
TODO: List general workflow best practices
|
||||
31
docs/claude/console-commands.md
Normal file
31
docs/claude/console-commands.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Console Commands
|
||||
|
||||
This guide covers creating and working with console commands.
|
||||
|
||||
## Creating Console Commands
|
||||
|
||||
TODO: Document console command creation with attributes
|
||||
|
||||
## Command Arguments and Options
|
||||
|
||||
TODO: Document argument and option handling
|
||||
|
||||
## Command Bus Integration
|
||||
|
||||
TODO: Document using CommandBus in console commands
|
||||
|
||||
## Interactive Commands
|
||||
|
||||
TODO: Document interactive menus and prompts
|
||||
|
||||
## Output Formatting
|
||||
|
||||
TODO: Document tables, progress bars, and styling
|
||||
|
||||
## Testing Console Commands
|
||||
|
||||
TODO: Document console command testing patterns
|
||||
|
||||
## Best Practices
|
||||
|
||||
TODO: List console command best practices
|
||||
31
docs/claude/database-patterns.md
Normal file
31
docs/claude/database-patterns.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Database Patterns
|
||||
|
||||
This guide covers database best practices and patterns.
|
||||
|
||||
## EntityManager Usage
|
||||
|
||||
TODO: Document EntityManager and UnitOfWork pattern
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
TODO: Document repository implementation and usage
|
||||
|
||||
## Migration Best Practices
|
||||
|
||||
TODO: Document migration creation and versioning
|
||||
|
||||
## Query Optimization
|
||||
|
||||
TODO: Document N+1 prevention and batch loading
|
||||
|
||||
## Connection Pooling
|
||||
|
||||
TODO: Document connection pool configuration
|
||||
|
||||
## Transaction Management
|
||||
|
||||
TODO: Document transaction patterns and best practices
|
||||
|
||||
## Database Testing
|
||||
|
||||
TODO: Document database testing strategies
|
||||
158
docs/claude/development-commands.md
Normal file
158
docs/claude/development-commands.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Development Commands
|
||||
|
||||
Entwicklungskommandos für das Custom PHP Framework.
|
||||
|
||||
## PHP Development
|
||||
|
||||
```bash
|
||||
# Abhängigkeiten installieren
|
||||
composer install
|
||||
|
||||
# Code Style prüfen (dry-run)
|
||||
composer cs
|
||||
|
||||
# Code Style automatisch korrigieren
|
||||
composer cs-fix
|
||||
|
||||
# Autoloader optimiert neu generieren
|
||||
composer reload
|
||||
|
||||
# PHP Tests ausführen (Pest Framework)
|
||||
./vendor/bin/pest
|
||||
```
|
||||
|
||||
## Frontend Development
|
||||
|
||||
```bash
|
||||
# Node.js Abhängigkeiten installieren
|
||||
npm install
|
||||
|
||||
# Vite Development Server mit HTTPS starten
|
||||
npm run dev
|
||||
|
||||
# Production Assets builden
|
||||
npm run build
|
||||
|
||||
# Production Build vorschauen
|
||||
npm run preview
|
||||
|
||||
# Jest Tests ausführen
|
||||
npm run test
|
||||
|
||||
# Assets builden und nach public/ deployen
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
## Docker & Environment
|
||||
|
||||
```bash
|
||||
# Alle Docker Container starten
|
||||
make up
|
||||
|
||||
# Alle Container stoppen
|
||||
make down
|
||||
|
||||
# Docker Images builden
|
||||
make build
|
||||
|
||||
# Docker Logs anzeigen
|
||||
make logs
|
||||
|
||||
# Autoloader neu laden und PHP Container neu starten
|
||||
make reload
|
||||
|
||||
# Console Commands in Docker PHP Container ausführen
|
||||
make console
|
||||
|
||||
# Code Style Checks in Docker ausführen
|
||||
make cs
|
||||
|
||||
# Code Style in Docker korrigieren
|
||||
make cs-fix
|
||||
|
||||
# Code Style für spezifische Datei korrigieren
|
||||
make cs-fix-file FILE=path/to/file.php
|
||||
|
||||
# Dateiberechtigungen korrigieren
|
||||
make fix-perms
|
||||
|
||||
# Projekt-Gesundheitscheck
|
||||
make doctor
|
||||
```
|
||||
|
||||
## Console Commands
|
||||
|
||||
```bash
|
||||
# Console Anwendung ausführen
|
||||
php console.php
|
||||
|
||||
# Console in Docker ausführen
|
||||
docker exec php php console.php
|
||||
|
||||
# MCP Server für AI-Integration starten
|
||||
php console.php mcp:server
|
||||
```
|
||||
|
||||
## Database & Migration Commands
|
||||
|
||||
```bash
|
||||
# Neue Migration erstellen
|
||||
php console.php make:migration CreateUsersTable [Domain]
|
||||
|
||||
# Alle ausstehenden Migrationen anwenden
|
||||
php console.php db:migrate
|
||||
|
||||
# Migrationen rückgängig machen (Standard: 1 Schritt)
|
||||
php console.php db:rollback [steps]
|
||||
|
||||
# Migration Status anzeigen
|
||||
php console.php db:status
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
- Tests befinden sich im `tests/` Verzeichnis
|
||||
- PHPUnit Konfiguration in `phpunit.xml`
|
||||
- Verwende Pest Framework für neue Tests
|
||||
- Tests spiegeln die Source-Verzeichnisstruktur wider
|
||||
|
||||
## Performance & Monitoring
|
||||
|
||||
```bash
|
||||
# Performance Logs verarbeiten
|
||||
php scripts/process-performance-logs.php
|
||||
|
||||
# Worker Berechtigungen korrigieren
|
||||
./scripts/fix-worker-permissions.sh
|
||||
```
|
||||
|
||||
## Asset Management
|
||||
|
||||
- **CSS**: `resources/css/` - Strukturiertes CSS mit ITCSS Methodologie
|
||||
- **JavaScript**: `resources/js/` - Modular JavaScript mit Core/Module System
|
||||
- **Build**: Vite für Asset-Kompilierung mit Tree Shaking
|
||||
- **PWA**: Service Worker für Offline-Funktionalität
|
||||
- **SSL**: HTTPS Development Server mit lokalen SSL-Zertifikaten
|
||||
|
||||
## Code Quality
|
||||
|
||||
```bash
|
||||
# PHPStan statische Analyse
|
||||
./vendor/bin/phpstan analyse
|
||||
|
||||
# PHP-CS-Fixer für Code Style
|
||||
./vendor/bin/php-cs-fixer fix --dry-run # Vorschau
|
||||
./vendor/bin/php-cs-fixer fix # Anwenden
|
||||
|
||||
# Pest Tests mit Coverage
|
||||
./vendor/bin/pest --coverage
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Setup**: `make up && composer install && npm install`
|
||||
2. **Development**: `npm run dev` für Frontend, `make console` für Backend
|
||||
3. **Testing**: `./vendor/bin/pest` für PHP, `npm run test` für JavaScript
|
||||
4. **Code Quality**: `composer cs` vor Commits
|
||||
5. **Database**: `php console.php db:migrate` für Schema-Änderungen
|
||||
6. **Deployment**: `npm run build` für Production Assets
|
||||
218
docs/claude/error-handling.md
Normal file
218
docs/claude/error-handling.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Error Handling & Debugging
|
||||
|
||||
This guide covers error handling patterns and debugging strategies in the framework.
|
||||
|
||||
## Exception Handling
|
||||
|
||||
All custom exceptions in the framework must extend `FrameworkException` to ensure consistent error handling, logging, and recovery mechanisms.
|
||||
|
||||
### The FrameworkException System
|
||||
|
||||
The framework provides a sophisticated exception system with:
|
||||
- **ExceptionContext**: Rich context information for debugging
|
||||
- **ErrorCode**: Categorized error codes with recovery hints
|
||||
- **RetryAfter**: Support for recoverable operations
|
||||
- **Fluent Interface**: Easy context building
|
||||
|
||||
### Creating Custom Exceptions
|
||||
|
||||
```php
|
||||
namespace App\Domain\User\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
use App\Framework\Exception\ErrorCode;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
|
||||
final class UserNotFoundException extends FrameworkException
|
||||
{
|
||||
public static function byId(UserId $id): self
|
||||
{
|
||||
return self::create(
|
||||
ErrorCode::ENTITY_NOT_FOUND,
|
||||
"User with ID '{$id->toString()}' not found"
|
||||
)->withData([
|
||||
'user_id' => $id->toString(),
|
||||
'search_type' => 'by_id'
|
||||
]);
|
||||
}
|
||||
|
||||
public static function byEmail(Email $email): self
|
||||
{
|
||||
$context = ExceptionContext::forOperation('user.lookup', 'UserRepository')
|
||||
->withData(['email' => $email->getMasked()]);
|
||||
|
||||
return self::fromContext(
|
||||
"User with email not found",
|
||||
$context,
|
||||
ErrorCode::ENTITY_NOT_FOUND
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using ErrorCode Enum
|
||||
|
||||
```php
|
||||
// The framework provides predefined error codes:
|
||||
ErrorCode::DB_CONNECTION_FAILED // Database errors
|
||||
ErrorCode::AUTH_TOKEN_EXPIRED // Authentication errors
|
||||
ErrorCode::VAL_BUSINESS_RULE_VIOLATION // Validation errors
|
||||
ErrorCode::HTTP_RATE_LIMIT_EXCEEDED // HTTP errors
|
||||
ErrorCode::SEC_CSRF_TOKEN_INVALID // Security errors
|
||||
|
||||
// Using error codes in exceptions:
|
||||
throw FrameworkException::create(
|
||||
ErrorCode::DB_QUERY_FAILED,
|
||||
"Failed to execute user query"
|
||||
)->withContext(
|
||||
ExceptionContext::forOperation('user.find', 'UserRepository')
|
||||
->withData(['query' => 'SELECT * FROM users WHERE id = ?'])
|
||||
->withDebug(['bind_params' => [$userId]])
|
||||
);
|
||||
```
|
||||
|
||||
### Exception Context Building
|
||||
|
||||
```php
|
||||
// Method 1: Using factory methods
|
||||
$exception = FrameworkException::forOperation(
|
||||
'payment.process',
|
||||
'PaymentService',
|
||||
'Payment processing failed',
|
||||
ErrorCode::PAYMENT_GATEWAY_ERROR
|
||||
)->withData([
|
||||
'amount' => $amount->toArray(),
|
||||
'gateway' => 'stripe',
|
||||
'customer_id' => $customerId
|
||||
])->withMetadata([
|
||||
'attempt' => 1,
|
||||
'idempotency_key' => $idempotencyKey
|
||||
]);
|
||||
|
||||
// Method 2: Building context separately
|
||||
$context = ExceptionContext::empty()
|
||||
->withOperation('order.validate', 'OrderService')
|
||||
->withData([
|
||||
'order_id' => $orderId,
|
||||
'total' => $total->toDecimal()
|
||||
])
|
||||
->withDebug([
|
||||
'validation_rules' => ['min_amount', 'max_items'],
|
||||
'failed_rule' => 'min_amount'
|
||||
]);
|
||||
|
||||
throw FrameworkException::fromContext(
|
||||
'Order validation failed',
|
||||
$context,
|
||||
ErrorCode::VAL_BUSINESS_RULE_VIOLATION
|
||||
);
|
||||
```
|
||||
|
||||
### Recoverable Exceptions
|
||||
|
||||
```php
|
||||
// Creating recoverable exceptions with retry hints
|
||||
final class RateLimitException extends FrameworkException
|
||||
{
|
||||
public static function exceeded(int $retryAfter): self
|
||||
{
|
||||
return self::create(
|
||||
ErrorCode::HTTP_RATE_LIMIT_EXCEEDED,
|
||||
'API rate limit exceeded'
|
||||
)->withRetryAfter($retryAfter)
|
||||
->withData(['retry_after_seconds' => $retryAfter]);
|
||||
}
|
||||
}
|
||||
|
||||
// Using in code
|
||||
try {
|
||||
$response = $apiClient->request($endpoint);
|
||||
} catch (RateLimitException $e) {
|
||||
if ($e->isRecoverable()) {
|
||||
$waitTime = $e->getRetryAfter();
|
||||
// Schedule retry after $waitTime seconds
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Categories
|
||||
|
||||
```php
|
||||
// Check exception category for handling strategies
|
||||
try {
|
||||
$result = $operation->execute();
|
||||
} catch (FrameworkException $e) {
|
||||
if ($e->isCategory('AUTH')) {
|
||||
// Handle authentication errors
|
||||
return $this->redirectToLogin();
|
||||
}
|
||||
|
||||
if ($e->isCategory('VAL')) {
|
||||
// Handle validation errors
|
||||
return $this->validationErrorResponse($e);
|
||||
}
|
||||
|
||||
if ($e->isErrorCode(ErrorCode::DB_CONNECTION_FAILED)) {
|
||||
// Handle specific database connection errors
|
||||
$this->notifyOps($e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Exceptions for Quick Use
|
||||
|
||||
```php
|
||||
// When you don't need the full context system
|
||||
throw FrameworkException::simple('Quick error message');
|
||||
|
||||
// With previous exception
|
||||
} catch (\PDOException $e) {
|
||||
throw FrameworkException::simple(
|
||||
'Database operation failed',
|
||||
$e,
|
||||
500
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Data Sanitization
|
||||
|
||||
The framework automatically sanitizes sensitive data in exceptions:
|
||||
|
||||
```php
|
||||
// Sensitive keys are automatically redacted
|
||||
$exception->withData([
|
||||
'username' => 'john@example.com',
|
||||
'password' => 'secret123', // Will be logged as '[REDACTED]'
|
||||
'api_key' => 'sk_live_...' // Will be logged as '[REDACTED]'
|
||||
]);
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always extend FrameworkException** for custom exceptions
|
||||
2. **Use ErrorCode enum** for categorizable errors
|
||||
3. **Provide rich context** with operation, component, and data
|
||||
4. **Use factory methods** for consistent exception creation
|
||||
5. **Sanitize sensitive data** (automatic for common keys)
|
||||
6. **Make exceptions domain-specific** (UserNotFoundException vs generic NotFoundException)
|
||||
7. **Include recovery hints** for recoverable errors
|
||||
|
||||
## Logging Best Practices
|
||||
|
||||
TODO: Document logging patterns and levels
|
||||
|
||||
## Debug Strategies
|
||||
|
||||
TODO: Document debugging approaches and tools
|
||||
|
||||
## Error Recovery Patterns
|
||||
|
||||
TODO: Document error recovery and graceful degradation
|
||||
|
||||
## Common Error Scenarios
|
||||
|
||||
TODO: List common errors and solutions
|
||||
27
docs/claude/event-system.md
Normal file
27
docs/claude/event-system.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Event System
|
||||
|
||||
This guide covers the event-driven architecture of the framework.
|
||||
|
||||
## EventBus vs EventDispatcher
|
||||
|
||||
TODO: Document the differences and when to use each
|
||||
|
||||
## Domain Events
|
||||
|
||||
TODO: Document domain event creation and handling
|
||||
|
||||
## Event Handler Registration
|
||||
|
||||
TODO: Document event handler attributes and registration
|
||||
|
||||
## Event Middleware
|
||||
|
||||
TODO: Document event middleware system
|
||||
|
||||
## Async Event Processing
|
||||
|
||||
TODO: Document async event handling patterns
|
||||
|
||||
## Event Best Practices
|
||||
|
||||
TODO: List event system best practices
|
||||
325
docs/claude/framework-personas.md
Normal file
325
docs/claude/framework-personas.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# Framework-Specific Personas
|
||||
|
||||
Framework-spezifische Subagent-Personas für das Custom PHP Framework.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Personas ergänzen das Standard SuperClaude System mit framework-spezifischen Experten, die die einzigartigen Patterns und Architektur-Entscheidungen des Custom PHP Frameworks perfekt verstehen.
|
||||
|
||||
## Framework-Spezifische Personas
|
||||
|
||||
### `--persona-framework-core`
|
||||
|
||||
**Identity**: Framework-Core Spezialist für Custom PHP Framework Architektur
|
||||
|
||||
**Priority Hierarchy**: Framework-Patterns > Performance > Standard PHP Practices
|
||||
|
||||
**Core Principles**:
|
||||
1. **No Inheritance**: Komposition über Vererbung - `extends` komplett vermeiden
|
||||
2. **Immutable by Design**: `readonly` Classes und Properties bevorzugen
|
||||
3. **Explicit DI**: Kein globaler State, nur Constructor Injection
|
||||
4. **Attribute-Driven**: Convention over Configuration mit Attribute Scanning
|
||||
|
||||
**Framework-Spezifische Patterns**:
|
||||
- **Final by Default**: Alle Klassen sind `final` außer explizit für Extension designt
|
||||
- **Readonly Everywhere**: Classes und Properties `readonly` wo technisch möglich
|
||||
- **Value Objects over Primitives**: Niemals primitive Arrays oder Strings für Domain-Konzepte
|
||||
- **Event-Driven Architecture**: Domain Events für lose Kopplung verwenden
|
||||
|
||||
**MCP Server Preferences**:
|
||||
- **Primary**: Custom Framework MCP - Für Framework-interne Analyse
|
||||
- **Secondary**: Sequential - Für komplexe Framework-Entscheidungen
|
||||
- **Avoided**: Magic - Generische UI-Generation passt nicht zu Framework-Patterns
|
||||
|
||||
**Optimized Commands**:
|
||||
- `/analyze --framework` - Framework-spezifische Architektur-Analyse
|
||||
- `/implement --framework-pattern` - Framework-Pattern-konforme Implementation
|
||||
- `/improve --framework-compliance` - Framework-Konformitäts-Verbesserungen
|
||||
|
||||
**Auto-Activation Triggers**:
|
||||
- Keywords: "readonly", "final", "composition", "attribute", "framework"
|
||||
- Framework-spezifische Klassen oder Patterns
|
||||
- Dependency Injection oder Container-Arbeit
|
||||
|
||||
**Framework-Specific Code Patterns**:
|
||||
```php
|
||||
// ✅ Framework-konform
|
||||
final readonly class UserService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $repository,
|
||||
private readonly EventDispatcher $events
|
||||
) {}
|
||||
|
||||
public function createUser(Email $email, UserName $name): User
|
||||
{
|
||||
$user = User::create($email, $name);
|
||||
$this->repository->save($user);
|
||||
$this->events->dispatch(new UserCreatedEvent($user));
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Attribute-basierte Konfiguration
|
||||
#[Route(path: '/api/users/{id}', method: Method::GET)]
|
||||
#[Auth(strategy: 'session')]
|
||||
public function getUser(UserId $id): JsonResult
|
||||
{
|
||||
return new JsonResult($this->userService->findById($id));
|
||||
}
|
||||
```
|
||||
|
||||
**Quality Standards**:
|
||||
- **Framework Compliance**: 100% Adherence zu Framework-Patterns
|
||||
- **Immutability**: Bevorzuge readonly/final wo technisch möglich
|
||||
- **Type Safety**: Value Objects statt Primitives für Domain-Konzepte
|
||||
|
||||
### `--persona-mcp-specialist`
|
||||
|
||||
**Identity**: MCP-Integration Spezialist für Framework AI-Integration
|
||||
|
||||
**Priority Hierarchy**: MCP-Framework-Integration > AI-Safety > Standard MCP Practices
|
||||
|
||||
**Core Principles**:
|
||||
1. **Framework-Aware MCP**: Nutze Framework's MCP-Server für interne Analyse
|
||||
2. **Safe Sandbox Operations**: Respektiere projekt-beschränkte Dateizugriffe
|
||||
3. **Attribute-Driven Discovery**: Verstehe #[McpTool] und #[McpResource] Patterns
|
||||
|
||||
**MCP-Framework Integration**:
|
||||
- **Framework MCP Tools**: `analyze_routes`, `analyze_container_bindings`, `discover_attributes`
|
||||
- **Health Monitoring**: `framework_health_check`, `list_framework_modules`
|
||||
- **Safe File Operations**: `list_directory`, `read_file`, `find_files` (projekt-beschränkt)
|
||||
|
||||
**MCP Server Preferences**:
|
||||
- **Primary**: Custom Framework MCP - Für Framework-interne Operationen
|
||||
- **Secondary**: Sequential - Für MCP-Koordination und Planung
|
||||
- **Integration**: Alle Standard MCP Server für erweiterte Funktionalität
|
||||
|
||||
**Optimized Commands**:
|
||||
- `/analyze --mcp-integration` - MCP-System-Analyse mit Framework-Tools
|
||||
- `/troubleshoot --mcp` - MCP-Integration Debugging
|
||||
- `/implement --mcp-tool` - Neue MCP-Tools nach Framework-Patterns
|
||||
|
||||
**Auto-Activation Triggers**:
|
||||
- Keywords: "mcp", "ai-integration", "framework-analysis"
|
||||
- MCP-Tool oder Resource Entwicklung
|
||||
- Framework-interne Analyse-Anfragen
|
||||
|
||||
**MCP-Framework Patterns**:
|
||||
```php
|
||||
// ✅ Framework MCP Tool Implementation
|
||||
final readonly class DomainAnalyzer
|
||||
{
|
||||
#[McpTool(name: 'analyze_domain_structure', description: 'Analyze domain architecture')]
|
||||
public function analyzeDomainStructure(string $domainPath): array
|
||||
{
|
||||
return $this->domainScanner->scanDomainModels($domainPath);
|
||||
}
|
||||
|
||||
#[McpResource(uri: 'framework://domain/{domain}')]
|
||||
public function getDomainInfo(string $domain): array
|
||||
{
|
||||
return $this->domainRegistry->getDomainInfo($domain);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Quality Standards**:
|
||||
- **Framework Integration**: Nutze Framework MCP-Tools optimal
|
||||
- **Safety First**: Respektiere Sandbox-Limitierungen
|
||||
- **Discovery Compliance**: Folge Framework's Attribute-Discovery-Patterns
|
||||
|
||||
### `--persona-value-object-architect`
|
||||
|
||||
**Identity**: Value Object Spezialist für Framework's "No Primitives" Philosophie
|
||||
|
||||
**Priority Hierarchy**: Type Safety > Domain Modeling > Performance > Convenience
|
||||
|
||||
**Core Principles**:
|
||||
1. **No Primitive Obsession**: Niemals primitive Arrays oder Strings für Domain-Konzepte
|
||||
2. **Immutable Value Objects**: Alle VOs sind readonly mit Transformation-Methoden
|
||||
3. **Rich Domain Modeling**: Value Objects enthalten Domain-spezifische Validation und Logic
|
||||
|
||||
**Value Object Categories**:
|
||||
- **Core VOs**: Email, RGBColor, Url, Hash, Version, Coordinates
|
||||
- **HTTP VOs**: FlashMessage, ValidationError, RouteParameters
|
||||
- **Security VOs**: OWASPEventIdentifier, MaskedEmail, ThreatLevel
|
||||
- **Performance VOs**: Measurement, MetricContext, MemorySummary
|
||||
|
||||
**MCP Server Preferences**:
|
||||
- **Primary**: Custom Framework MCP - Für bestehende VO-Analyse
|
||||
- **Secondary**: Context7 - Für VO-Pattern-Recherche
|
||||
- **Avoided**: Magic - Fokus auf Domain-Modeling, nicht UI
|
||||
|
||||
**Optimized Commands**:
|
||||
- `/implement --value-object` - Neue Value Objects nach Framework-Standards
|
||||
- `/refactor --primitives-to-vos` - Primitive Obsession eliminieren
|
||||
- `/analyze --domain-modeling` - Domain-Model-Analyse mit VOs
|
||||
|
||||
**Auto-Activation Triggers**:
|
||||
- Keywords: "value object", "domain modeling", "primitive", "type safety"
|
||||
- Array/String-Parameter in Domain-Methoden
|
||||
- Neue Domain-Konzepte ohne entsprechende VOs
|
||||
|
||||
**Value Object Patterns**:
|
||||
```php
|
||||
// ✅ Framework Value Object Pattern
|
||||
final readonly class Price
|
||||
{
|
||||
public function __construct(
|
||||
public int $cents,
|
||||
public Currency $currency
|
||||
) {
|
||||
if ($cents < 0) {
|
||||
throw new \InvalidArgumentException('Price cannot be negative');
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromEuros(float $euros, Currency $currency = null): self
|
||||
{
|
||||
return new self((int) round($euros * 100), $currency ?? Currency::EUR);
|
||||
}
|
||||
|
||||
public function add(self $other): self
|
||||
{
|
||||
if (!$this->currency->equals($other->currency)) {
|
||||
throw new \InvalidArgumentException('Currencies must match');
|
||||
}
|
||||
return new self($this->cents + $other->cents, $this->currency);
|
||||
}
|
||||
|
||||
public function toDecimal(): string
|
||||
{
|
||||
return number_format($this->cents / 100, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Verwendung statt Primitives
|
||||
// ❌ function calculateTotal(array $items, string $currency): float
|
||||
// ✅ function calculateTotal(OrderItems $items): Price
|
||||
```
|
||||
|
||||
**Quality Standards**:
|
||||
- **Type Safety**: 100% - keine Primitive für Domain-Konzepte
|
||||
- **Immutability**: Alle VOs readonly mit Transformation-Methoden
|
||||
- **Domain Richness**: VOs enthalten relevante Business Logic
|
||||
|
||||
### `--persona-discovery-expert`
|
||||
|
||||
**Identity**: Attribute-Discovery Spezialist für Framework's Convention-over-Configuration
|
||||
|
||||
**Priority Hierarchy**: Attribute Patterns > Performance > Manual Configuration
|
||||
|
||||
**Core Principles**:
|
||||
1. **Attribute-Driven Everything**: Routes, Middleware, Commands, MCP-Tools via Attributes
|
||||
2. **Convention over Configuration**: Minimiere manuelle Konfiguration durch Discovery
|
||||
3. **Performance-Aware Caching**: Discovery-Results für Performance cachen
|
||||
|
||||
**Attribute System Expertise**:
|
||||
- **Routing**: `#[Route]`, `#[Auth]`, `#[MiddlewarePriority]`
|
||||
- **MCP Integration**: `#[McpTool]`, `#[McpResource]`
|
||||
- **Commands**: `#[ConsoleCommand]`, `#[CommandHandler]`
|
||||
- **Events**: `#[EventHandler]`, `#[DomainEvent]`
|
||||
|
||||
**Discovery Components**:
|
||||
- **Unified Discovery Service**: Für mehrere Attribute-Typen
|
||||
- **Cached Reflection Provider**: Performance-optimierte Attribute-Scanning
|
||||
- **Automatic Registration**: Eliminiert manuelle Konfiguration
|
||||
|
||||
**MCP Server Preferences**:
|
||||
- **Primary**: Custom Framework MCP - Für Attribute-Discovery-Analyse
|
||||
- **Secondary**: Sequential - Für komplexe Discovery-Pattern-Analyse
|
||||
- **Avoided**: Magic - Fokus auf Framework-interne Discovery
|
||||
|
||||
**Optimized Commands**:
|
||||
- `/analyze --discovery-system` - Attribute-Discovery System analysieren
|
||||
- `/implement --attribute-pattern` - Neue Attribute nach Framework-Standards
|
||||
- `/optimize --discovery-performance` - Discovery-System Performance optimieren
|
||||
|
||||
**Auto-Activation Triggers**:
|
||||
- Keywords: "attribute", "discovery", "convention", "configuration"
|
||||
- Neue Controller, Commands oder MCP-Tools
|
||||
- Performance-Issues im Discovery-System
|
||||
|
||||
**Attribute Patterns**:
|
||||
```php
|
||||
// ✅ Framework Attribute Patterns
|
||||
final readonly class UserController
|
||||
{
|
||||
#[Route(path: '/api/users', method: Method::POST)]
|
||||
#[Auth(strategy: 'session', roles: ['admin'])]
|
||||
#[MiddlewarePriority(100)]
|
||||
public function createUser(CreateUserRequest $request): JsonResult
|
||||
{
|
||||
return new JsonResult($this->userService->create($request));
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MCP Tool Discovery
|
||||
final readonly class FrameworkAnalyzer
|
||||
{
|
||||
#[McpTool(name: 'scan_controllers', description: 'Scan for controller attributes')]
|
||||
public function scanControllers(): array
|
||||
{
|
||||
return $this->attributeScanner->findClassesWithAttribute(Route::class);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Command Discovery
|
||||
final readonly class UserCommands
|
||||
{
|
||||
#[ConsoleCommand(name: 'user:create', description: 'Create a new user')]
|
||||
public function createUser(string $email, string $name): void
|
||||
{
|
||||
$this->userService->create(new Email($email), new UserName($name));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Quality Standards**:
|
||||
- **Discovery Coverage**: 100% - alle relevanten Komponenten via Attributes
|
||||
- **Performance**: Cached Discovery-Results für Production
|
||||
- **Convention Compliance**: Strikte Einhaltung der Framework-Attribute-Patterns
|
||||
|
||||
## Integration mit Standard SuperClaude System
|
||||
|
||||
Diese Framework-Personas erweitern das bestehende SuperClaude System und können kombiniert werden:
|
||||
|
||||
```bash
|
||||
# Framework-Core mit Standard-Architect
|
||||
--persona-framework-core --persona-architect
|
||||
|
||||
# MCP-Specialist mit Standard-Analyzer
|
||||
--persona-mcp-specialist --persona-analyzer
|
||||
|
||||
# Value-Object mit Standard-Refactorer
|
||||
--persona-value-object-architect --persona-refactorer
|
||||
|
||||
# Discovery-Expert mit Standard-Performance
|
||||
--persona-discovery-expert --persona-performance
|
||||
```
|
||||
|
||||
## Auto-Activation Integration
|
||||
|
||||
Framework-Personas haben Vorrang vor Standard-Personas bei Framework-spezifischen Triggern:
|
||||
|
||||
- **Framework-Code erkannt** → Framework-Core aktiviert
|
||||
- **MCP-Integration detected** → MCP-Specialist aktiviert
|
||||
- **Primitive Obsession** → Value-Object-Architect aktiviert
|
||||
- **Attribute-Arbeit** → Discovery-Expert aktiviert
|
||||
|
||||
## Verwendung
|
||||
|
||||
Diese Framework-Personas werden automatisch verfügbar, wenn das SuperClaude System diese Datei über die `CLAUDE.md` Referenz lädt. Sie ergänzen die Standard-Personas mit framework-spezifischem Expertenwissen.
|
||||
|
||||
**Beispiel-Usage**:
|
||||
```bash
|
||||
# Automatische Aktivierung bei Framework-Arbeit
|
||||
/analyze --framework-compliance
|
||||
|
||||
# Manuelle Aktivierung
|
||||
/implement --persona-framework-core --value-object UserProfile
|
||||
|
||||
# Kombination mit Standard-Personas
|
||||
/improve --persona-value-object-architect --persona-refactorer
|
||||
```
|
||||
576
docs/claude/guidelines.md
Normal file
576
docs/claude/guidelines.md
Normal file
@@ -0,0 +1,576 @@
|
||||
# Development Guidelines
|
||||
|
||||
Entwicklungsrichtlinien für das Custom PHP Framework.
|
||||
|
||||
## Code Style Principles
|
||||
|
||||
### Fundamental Code Standards
|
||||
|
||||
- **PSR-12 Coding Standards**: Befolge PSR-12 für einheitliche Code-Formatierung
|
||||
- **PHP-CS-Fixer**: Automatische Code-Formatierung verwenden
|
||||
- **Strict Types**: `declare(strict_types=1)` in allen PHP-Dateien
|
||||
|
||||
### Framework-Specific Principles
|
||||
|
||||
**No Inheritance Principle**:
|
||||
```php
|
||||
// ❌ Avoid extends - problematisch für Wartbarkeit
|
||||
class UserController extends BaseController
|
||||
{
|
||||
// Problematische Vererbung
|
||||
}
|
||||
|
||||
// ✅ Use composition - flexibler und testbarer
|
||||
final readonly class UserController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AuthService $auth,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly Logger $logger
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
**Immutable by Design**:
|
||||
```php
|
||||
// ✅ Unveränderliche Objekte bevorzugen
|
||||
final readonly class User
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public Email $email,
|
||||
public string $name
|
||||
) {}
|
||||
|
||||
// Neue Instanz für Änderungen
|
||||
public function changeName(string $newName): self
|
||||
{
|
||||
return new self($this->id, $this->email, $newName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Readonly Everywhere**:
|
||||
```php
|
||||
// ✅ Classes und Properties readonly wo möglich
|
||||
final readonly class ProductService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepository $repository,
|
||||
private readonly PriceCalculator $calculator
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
**Final by Default**:
|
||||
```php
|
||||
// ✅ Klassen sind final außer wenn explizit für Erweiterung designt
|
||||
final readonly class OrderProcessor
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// Nur wenn bewusst erweiterbar
|
||||
readonly class BaseValidator
|
||||
{
|
||||
// Designed for extension
|
||||
}
|
||||
```
|
||||
|
||||
**Property Hooks Usage**:
|
||||
```php
|
||||
// ❌ Property Hooks in readonly Klassen - TECHNISCH NICHT MÖGLICH
|
||||
final readonly class User
|
||||
{
|
||||
public string $fullName {
|
||||
get => $this->firstName . ' ' . $this->lastName; // PHP Fehler!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Normale Methoden in readonly Klassen verwenden
|
||||
final readonly class User
|
||||
{
|
||||
public function getFullName(): string
|
||||
{
|
||||
return $this->firstName . ' ' . $this->lastName;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Property Hooks nur in mutable Klassen verwenden
|
||||
final class ConfigManager
|
||||
{
|
||||
private array $cache = [];
|
||||
|
||||
public string $apiKey {
|
||||
set (string $value) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidArgumentException('API key cannot be empty');
|
||||
}
|
||||
$this->apiKey = $value;
|
||||
$this->cache = []; // Clear cache on change
|
||||
}
|
||||
}
|
||||
|
||||
public array $settings {
|
||||
get => $this->cache ?: $this->cache = $this->loadSettings();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ private(set) für kontrollierte Mutation
|
||||
final class EventStore
|
||||
{
|
||||
public private(set) array $events = [];
|
||||
|
||||
public function addEvent(DomainEvent $event): void
|
||||
{
|
||||
$this->events[] = $event;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Property Hooks Richtlinien**:
|
||||
- **TECHNISCH NICHT MÖGLICH** in `readonly` Klassen - PHP verbietet Property Hooks in readonly Klassen
|
||||
- **Nur in mutable Klassen** verwenden
|
||||
- **Alternative**: Normale Methoden in `readonly` Klassen für computed values
|
||||
- **Use Cases**: Validation beim Setzen, Lazy Loading, Cache Invalidation
|
||||
- **private(set)** für kontrollierte Array-Mutation in mutable Klassen
|
||||
|
||||
## Value Objects over Primitives
|
||||
|
||||
**Verwende Value Objects statt Arrays oder Primitives**:
|
||||
|
||||
```php
|
||||
// ❌ Primitive Obsession vermeiden
|
||||
function createUser(string $email, array $preferences): array
|
||||
{
|
||||
// Problematisch: keine Typsicherheit, versteckte Struktur
|
||||
}
|
||||
|
||||
// ✅ Value Objects für Domain-Konzepte
|
||||
function createUser(Email $email, UserPreferences $preferences): User
|
||||
{
|
||||
// Typsicher, selbstdokumentierend, validiert
|
||||
}
|
||||
```
|
||||
|
||||
**Domain Modeling mit Value Objects**:
|
||||
```php
|
||||
final readonly class Price
|
||||
{
|
||||
public function __construct(
|
||||
public int $cents,
|
||||
public Currency $currency
|
||||
) {
|
||||
if ($cents < 0) {
|
||||
throw new \InvalidArgumentException('Preis kann nicht negativ sein');
|
||||
}
|
||||
}
|
||||
|
||||
public function toEuros(): float
|
||||
{
|
||||
return $this->cents / 100.0;
|
||||
}
|
||||
|
||||
public function add(self $other): self
|
||||
{
|
||||
if (!$this->currency->equals($other->currency)) {
|
||||
throw new \InvalidArgumentException('Currencies must match');
|
||||
}
|
||||
|
||||
return new self($this->cents + $other->cents, $this->currency);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Test Organization
|
||||
|
||||
**Mixed Testing Approach**:
|
||||
- **Pest Framework**: Bevorzugt für neue Tests (moderne Syntax)
|
||||
- **PHPUnit**: Traditionelle Tests beibehalten
|
||||
- **Test Structure**: Tests spiegeln Source-Verzeichnisstruktur wider
|
||||
|
||||
```php
|
||||
// ✅ Pest Test Beispiel
|
||||
it('can calculate order total with tax', function () {
|
||||
$order = new Order([
|
||||
new OrderItem(new Price(1000, Currency::EUR), quantity: 2),
|
||||
new OrderItem(new Price(500, Currency::EUR), quantity: 1)
|
||||
]);
|
||||
|
||||
$calculator = new OrderCalculator(new TaxRate(0.19));
|
||||
$total = $calculator->calculateTotal($order);
|
||||
|
||||
expect($total->cents)->toBe(2975); // 25€ + 19% tax
|
||||
});
|
||||
```
|
||||
|
||||
### Test Categories
|
||||
|
||||
- **Unit Tests**: Domain Logic, Value Objects, Services
|
||||
- **Integration Tests**: Web Controller, Database Operations
|
||||
- **Feature Tests**: End-to-End User Workflows
|
||||
- **Performance Tests**: Critical Path Performance
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
**IP-based Authentication** für Admin Routes:
|
||||
```php
|
||||
#[Route(path: '/admin/dashboard', method: Method::GET)]
|
||||
#[Auth(strategy: 'ip', allowedIps: ['127.0.0.1', '::1'])]
|
||||
public function dashboard(HttpRequest $request): ViewResult
|
||||
{
|
||||
// Nur von erlaubten IP-Adressen erreichbar
|
||||
}
|
||||
```
|
||||
|
||||
**Route Protection**:
|
||||
```php
|
||||
#[Route(path: '/api/users', method: Method::POST)]
|
||||
#[Auth(strategy: 'session', roles: ['admin'])]
|
||||
public function createUser(CreateUserRequest $request): JsonResult
|
||||
{
|
||||
// Authentifizierung und Autorisierung erforderlich
|
||||
}
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
|
||||
**Request Objects** für Validation:
|
||||
```php
|
||||
final readonly class CreateUserRequest implements ControllerRequest
|
||||
{
|
||||
public function __construct(
|
||||
public Email $email,
|
||||
public string $name,
|
||||
public ?string $company = null
|
||||
) {}
|
||||
|
||||
public static function fromHttpRequest(HttpRequest $request): self
|
||||
{
|
||||
$data = $request->parsedBody->toArray();
|
||||
|
||||
return new self(
|
||||
email: new Email($data['email'] ?? ''),
|
||||
name: trim($data['name'] ?? ''),
|
||||
company: !empty($data['company']) ? trim($data['company']) : null
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Server Data Access
|
||||
|
||||
**Verwende Request::server statt Superglobals**:
|
||||
```php
|
||||
// ❌ Keine Superglobals verwenden
|
||||
public function handleRequest(): JsonResult
|
||||
{
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT']; // Schlecht
|
||||
$clientIp = $_SERVER['REMOTE_ADDR']; // Schlecht
|
||||
}
|
||||
|
||||
// ✅ Request::server verwenden
|
||||
public function handleRequest(HttpRequest $request): JsonResult
|
||||
{
|
||||
$userAgent = $request->server->getUserAgent();
|
||||
$clientIp = $request->server->getClientIp();
|
||||
$referer = $request->server->getSafeRefererUrl('/dashboard');
|
||||
|
||||
return new JsonResult([
|
||||
'user_agent' => $userAgent->toString(),
|
||||
'client_ip' => (string) $clientIp,
|
||||
'safe_referer' => $referer
|
||||
]);
|
||||
}
|
||||
|
||||
### OWASP Security Events
|
||||
|
||||
**Security Event Logging**:
|
||||
```php
|
||||
// Automatisches Security Event Logging
|
||||
final readonly class AuthenticationGuard
|
||||
{
|
||||
public function authenticate(LoginAttempt $attempt): AuthResult
|
||||
{
|
||||
if ($attempt->isRateLimited()) {
|
||||
$this->eventLogger->logSecurityEvent(
|
||||
new AuthenticationFailedEvent(
|
||||
reason: 'Rate limit exceeded',
|
||||
ipAddress: $attempt->ipAddress,
|
||||
userAgent: $attempt->userAgent
|
||||
)
|
||||
);
|
||||
|
||||
throw new AuthenticationException('Too many attempts');
|
||||
}
|
||||
|
||||
// Authentication logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Guidelines
|
||||
|
||||
### Database Optimization
|
||||
|
||||
**EntityManager Usage**:
|
||||
```php
|
||||
// ✅ Bulk Operations verwenden
|
||||
public function updateMultipleUsers(array $userUpdates): void
|
||||
{
|
||||
$this->entityManager->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($userUpdates as $update) {
|
||||
$user = $this->entityManager->find(User::class, $update->userId);
|
||||
$user->updateProfile($update->profileData);
|
||||
// Keine sofortige Persistierung
|
||||
}
|
||||
|
||||
$this->entityManager->flush(); // Bulk flush
|
||||
$this->entityManager->commit();
|
||||
} catch (\Exception $e) {
|
||||
$this->entityManager->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**N+1 Query Prevention**:
|
||||
```php
|
||||
// ✅ Eager Loading verwenden
|
||||
$users = $this->userRepository->findWithProfiles($userIds);
|
||||
|
||||
// Statt lazy loading in Schleife
|
||||
// foreach ($users as $user) {
|
||||
// $profile = $user->getProfile(); // N+1 Problem
|
||||
// }
|
||||
```
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
**Framework Cache Interface mit Value Objects**:
|
||||
```php
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\Cache\CacheKey;
|
||||
use App\Framework\Cache\CacheItem;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
|
||||
final readonly class UserService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Cache $cache, // SmartCache ist Standard-Implementation
|
||||
private readonly UserRepository $repository
|
||||
) {}
|
||||
|
||||
public function getUser(string $userId): User
|
||||
{
|
||||
$cacheKey = CacheKey::fromString("user_{$userId}");
|
||||
$ttl = Duration::fromHours(1);
|
||||
|
||||
// Remember Pattern mit Value Objects
|
||||
$cacheItem = $this->cache->remember(
|
||||
key: $cacheKey,
|
||||
callback: fn() => $this->repository->find($userId),
|
||||
ttl: $ttl
|
||||
);
|
||||
|
||||
return $cacheItem->value;
|
||||
}
|
||||
|
||||
public function cacheMultipleUsers(array $users): bool
|
||||
{
|
||||
$cacheItems = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$cacheItems[] = CacheItem::forSetting(
|
||||
key: CacheKey::fromString("user_{$user->id}"),
|
||||
value: $user,
|
||||
ttl: Duration::fromHours(1)
|
||||
);
|
||||
}
|
||||
|
||||
// Batch-Operation mit SmartCache
|
||||
return $this->cache->set(...$cacheItems);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Advanced Cache Patterns**:
|
||||
```php
|
||||
// Cache mit Tags für gruppierte Invalidierung
|
||||
$userKey = CacheKey::fromString("user_{$userId}");
|
||||
$teamTag = CacheTag::fromString("team_{$teamId}");
|
||||
|
||||
$cacheItem = CacheItem::forSetting(
|
||||
key: $userKey,
|
||||
value: $user,
|
||||
ttl: Duration::fromHours(2),
|
||||
tags: [$teamTag]
|
||||
);
|
||||
|
||||
$this->cache->set($cacheItem);
|
||||
|
||||
// Alle Team-bezogenen Caches invalidieren
|
||||
$this->cache->forget($teamTag);
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Typed Configuration mit Environment Klasse
|
||||
|
||||
**Framework Environment Klasse verwenden**:
|
||||
```php
|
||||
final readonly class DatabaseConfig
|
||||
{
|
||||
public function __construct(
|
||||
public string $host,
|
||||
public int $port,
|
||||
public string $database,
|
||||
public string $username,
|
||||
public string $password,
|
||||
public string $driver = 'mysql'
|
||||
) {}
|
||||
|
||||
public static function fromEnvironment(Environment $env): self
|
||||
{
|
||||
return new self(
|
||||
host: $env->get(EnvKey::DB_HOST, 'localhost'),
|
||||
port: $env->getInt(EnvKey::DB_PORT, 3306),
|
||||
database: $env->require(EnvKey::DB_NAME),
|
||||
username: $env->require(EnvKey::DB_USER),
|
||||
password: $env->require(EnvKey::DB_PASS),
|
||||
driver: $env->get(EnvKey::DB_DRIVER, 'mysql')
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**EnvKey Enum für Type Safety**:
|
||||
```php
|
||||
// Environment Keys als Enum definieren
|
||||
enum EnvKey: string
|
||||
{
|
||||
case DB_HOST = 'DB_HOST';
|
||||
case DB_PORT = 'DB_PORT';
|
||||
case DB_NAME = 'DB_NAME';
|
||||
case DB_USER = 'DB_USER';
|
||||
case DB_PASS = 'DB_PASS';
|
||||
case DB_DRIVER = 'DB_DRIVER';
|
||||
case APP_ENV = 'APP_ENV';
|
||||
}
|
||||
```
|
||||
|
||||
**Configuration Initializer Pattern**:
|
||||
```php
|
||||
final readonly class DatabaseConfigInitializer implements Initializer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Environment $environment
|
||||
) {}
|
||||
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
$config = DatabaseConfig::fromEnvironment($this->environment);
|
||||
$container->singleton(DatabaseConfig::class, $config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### Code Documentation
|
||||
|
||||
**Self-Documenting Code bevorzugen**:
|
||||
```php
|
||||
// ✅ Code, der sich selbst erklärt
|
||||
final readonly class OrderTotalCalculator
|
||||
{
|
||||
public function calculateTotalWithTax(
|
||||
Order $order,
|
||||
TaxRate $taxRate
|
||||
): Money {
|
||||
$subtotal = $this->calculateSubtotal($order);
|
||||
$taxAmount = $subtotal->multiply($taxRate->asDecimal());
|
||||
|
||||
return $subtotal->add($taxAmount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PHPDoc nur wenn notwendig**:
|
||||
```php
|
||||
/**
|
||||
* Berechnet Gesamtsumme nur für komplexe Business Logic
|
||||
*
|
||||
* @param Order $order - Customer order with line items
|
||||
* @param TaxRate $taxRate - Applicable tax rate (0.0-1.0)
|
||||
* @throws InvalidOrderException wenn Order leer ist
|
||||
*/
|
||||
public function calculateComplexTotal(Order $order, TaxRate $taxRate): Money
|
||||
```
|
||||
|
||||
## Dependency Injection Best Practices
|
||||
|
||||
### Constructor Injection
|
||||
|
||||
**Explizite Dependencies**:
|
||||
```php
|
||||
// ✅ Alle Dependencies im Constructor
|
||||
final readonly class OrderProcessor
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PaymentGateway $paymentGateway,
|
||||
private readonly InventoryService $inventory,
|
||||
private readonly EmailService $emailService,
|
||||
private readonly Logger $logger
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
**Service Locator Anti-Pattern vermeiden**:
|
||||
```php
|
||||
// ❌ Service Locator Pattern
|
||||
public function processOrder(Order $order): void
|
||||
{
|
||||
$gateway = ServiceLocator::get(PaymentGateway::class); // Schlecht
|
||||
}
|
||||
|
||||
// ✅ Dependency Injection
|
||||
public function processOrder(Order $order): void
|
||||
{
|
||||
$this->paymentGateway->charge($order->getTotal()); // Gut
|
||||
}
|
||||
```
|
||||
|
||||
## Framework Integration Patterns
|
||||
|
||||
### Environment-Aware Services
|
||||
|
||||
**Environment in Service Initialization**:
|
||||
```php
|
||||
final readonly class EmailServiceInitializer implements Initializer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Environment $environment
|
||||
) {}
|
||||
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
$service = match ($this->environment->get(EnvKey::APP_ENV)) {
|
||||
'production' => new SmtpEmailService(
|
||||
host: $this->environment->require(EnvKey::SMTP_HOST),
|
||||
username: $this->environment->require(EnvKey::SMTP_USER),
|
||||
password: $this->environment->require(EnvKey::SMTP_PASS)
|
||||
),
|
||||
'development' => new LogEmailService(),
|
||||
default => new NullEmailService()
|
||||
};
|
||||
|
||||
$container->singleton(EmailService::class, $service);
|
||||
}
|
||||
}
|
||||
```
|
||||
74
docs/claude/mcp-integration.md
Normal file
74
docs/claude/mcp-integration.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# MCP Integration
|
||||
|
||||
**Model Context Protocol (MCP) Server Integration** für das Custom PHP Framework.
|
||||
|
||||
## Server Status
|
||||
|
||||
**✅ FULLY FUNCTIONAL**
|
||||
- Server getestet und funktionsfähig mit JSON-RPC Protokoll
|
||||
- Tools und Ressourcen bereit für AI-Integration
|
||||
- Automatische Erkennung von Framework-Komponenten
|
||||
- Sichere, sandbox-basierte Dateisystem-Zugriffe
|
||||
|
||||
## Quick Access Commands
|
||||
|
||||
```bash
|
||||
# MCP Server starten
|
||||
docker exec -i php php console.php mcp:server
|
||||
|
||||
# MCP Server testen
|
||||
echo '{"jsonrpc": "2.0", "method": "initialize", "params": {}}' | docker exec -i php php console.php mcp:server
|
||||
```
|
||||
|
||||
## Verfügbare MCP Tools
|
||||
|
||||
| Tool | Beschreibung | Verwendung |
|
||||
|------|--------------|------------|
|
||||
| `analyze_routes` | Alle registrierten Routen abrufen | Framework-Routing-Analyse |
|
||||
| `analyze_container_bindings` | DI Container Bindings analysieren | Dependency Injection Debugging |
|
||||
| `discover_attributes` | Attribute nach Typ entdecken | Framework-Pattern-Erkennung |
|
||||
| `framework_health_check` | Health Check der Framework-Komponenten | System-Status-Überprüfung |
|
||||
| `list_framework_modules` | Alle Framework-Module auflisten | Architektur-Übersicht |
|
||||
| `list_directory` | Verzeichnisinhalte auflisten (projekt-beschränkt) | Dateisystem-Navigation |
|
||||
| `read_file` | Dateiinhalte mit Zeilenlimits lesen | Code-Analyse |
|
||||
| `find_files` | Dateien nach Pattern finden | Pattern-basierte Suche |
|
||||
|
||||
## MCP Resources
|
||||
|
||||
- `framework://config`: Framework-Konfiguration und Umgebung
|
||||
|
||||
## Claude Desktop Konfiguration
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"custom-php-framework": {
|
||||
"command": "docker",
|
||||
"args": ["exec", "-i", "php", "php", "console.php", "mcp:server"],
|
||||
"cwd": "/home/michael/dev/michaelschiemer"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Framework-Analyse-Capabilities
|
||||
|
||||
- **Route Discovery**: Automatische Erkennung aller registrierten Routen
|
||||
- **Container Binding Inspection**: Analyse der Dependency Injection Bindings
|
||||
- **Modul- und Komponenten-Discovery**: Erkennung aller Framework-Module
|
||||
- **Health Monitoring**: Überwachung des Framework-Status
|
||||
- **File System Operations**: Projekt-beschränkte Dateisystem-Operationen
|
||||
|
||||
## Best Practices für AI-Interaktion
|
||||
|
||||
1. **MCP Tools verwenden**: Nutze MCP Tools für Framework-Analyse anstatt manueller Datei-Lesung
|
||||
2. **Attribute Discovery nutzen**: Verwende Attribute-Discovery zum Verstehen der Framework-Patterns
|
||||
3. **Health Checks**: Führe Framework Health Checks vor Änderungen durch
|
||||
4. **Projekt-Scope beachten**: Respektiere projekt-beschränkte Dateizugriff-Limitierungen
|
||||
|
||||
## Sicherheitsfeatures
|
||||
|
||||
- **Sandboxed File Access**: Alle Dateizugriffe sind auf das Projekt beschränkt
|
||||
- **Safe Operations**: Nur lesende Operationen für Framework-Analyse
|
||||
- **Validation**: Eingabe-Validierung für alle MCP-Tool-Parameter
|
||||
- **Error Handling**: Robuste Fehlerbehandlung mit aussagekräftigen Meldungen
|
||||
31
docs/claude/performance-monitoring.md
Normal file
31
docs/claude/performance-monitoring.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Performance Monitoring
|
||||
|
||||
This guide covers performance monitoring and optimization features.
|
||||
|
||||
## Performance Collector
|
||||
|
||||
TODO: Document PerformanceCollector usage
|
||||
|
||||
## Metrics System
|
||||
|
||||
TODO: Document metrics collection and reporting
|
||||
|
||||
## Circuit Breaker Pattern
|
||||
|
||||
TODO: Document circuit breaker implementation
|
||||
|
||||
## Cache Performance
|
||||
|
||||
TODO: Document cache metrics and optimization
|
||||
|
||||
## Database Performance
|
||||
|
||||
TODO: Document query analysis and optimization
|
||||
|
||||
## Performance Testing
|
||||
|
||||
TODO: Document performance testing strategies
|
||||
|
||||
## Optimization Techniques
|
||||
|
||||
TODO: List performance optimization techniques
|
||||
31
docs/claude/queue-system.md
Normal file
31
docs/claude/queue-system.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Queue System
|
||||
|
||||
This guide covers the queue system and background job processing.
|
||||
|
||||
## Queue Interface
|
||||
|
||||
TODO: Document Queue interface and implementations
|
||||
|
||||
## Background Jobs
|
||||
|
||||
TODO: Document background job creation and processing
|
||||
|
||||
## Queue Drivers
|
||||
|
||||
TODO: Document available queue drivers (Redis, File, etc.)
|
||||
|
||||
## Retry Mechanisms
|
||||
|
||||
TODO: Document retry strategies and configuration
|
||||
|
||||
## Failed Jobs
|
||||
|
||||
TODO: Document failed job handling
|
||||
|
||||
## Queue Monitoring
|
||||
|
||||
TODO: Document queue monitoring and metrics
|
||||
|
||||
## Best Practices
|
||||
|
||||
TODO: List queue system best practices
|
||||
31
docs/claude/security-patterns.md
Normal file
31
docs/claude/security-patterns.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Security Patterns
|
||||
|
||||
This guide covers security best practices and patterns used in the framework.
|
||||
|
||||
## OWASP Event Integration
|
||||
|
||||
TODO: Document OWASP security event handling
|
||||
|
||||
## Web Application Firewall (WAF)
|
||||
|
||||
TODO: Document WAF usage and configuration
|
||||
|
||||
## Authentication & Authorization
|
||||
|
||||
TODO: Document auth patterns and IP-based authentication
|
||||
|
||||
## CSRF Protection
|
||||
|
||||
TODO: Document CSRF token handling
|
||||
|
||||
## Security Headers
|
||||
|
||||
TODO: Document security headers configuration
|
||||
|
||||
## Input Validation
|
||||
|
||||
TODO: Document input validation and sanitization
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
TODO: List general security guidelines
|
||||
31
docs/claude/troubleshooting.md
Normal file
31
docs/claude/troubleshooting.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Troubleshooting
|
||||
|
||||
This guide helps diagnose and fix common issues.
|
||||
|
||||
## Common Errors
|
||||
|
||||
TODO: Document frequent errors and solutions
|
||||
|
||||
## Container/DI Issues
|
||||
|
||||
TODO: Document dependency injection problems
|
||||
|
||||
## Routing Problems
|
||||
|
||||
TODO: Document route conflicts and resolution
|
||||
|
||||
## Performance Issues
|
||||
|
||||
TODO: Document performance problem diagnosis
|
||||
|
||||
## Database Connection Issues
|
||||
|
||||
TODO: Document database troubleshooting
|
||||
|
||||
## Cache Problems
|
||||
|
||||
TODO: Document cache-related issues
|
||||
|
||||
## Debug Tools
|
||||
|
||||
TODO: Document available debugging tools and techniques
|
||||
477
docs/components/analytics/configuration.md
Normal file
477
docs/components/analytics/configuration.md
Normal file
@@ -0,0 +1,477 @@
|
||||
# Analytics Konfiguration
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Analytics-Konfiguration korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Analytics Framework bietet umfangreiche Konfigurationsmöglichkeiten, um das Verhalten und die Leistung an die Anforderungen Ihrer Anwendung anzupassen. Diese Dokumentation beschreibt die verfügbaren Konfigurationsoptionen und wie Sie diese anwenden können.
|
||||
|
||||
## Konfigurationsklasse
|
||||
|
||||
Die zentrale Klasse für die Analytics-Konfiguration ist `AnalyticsConfig`. Diese Klasse enthält alle Einstellungen, die das Verhalten des Analytics Frameworks steuern:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsConfig;
|
||||
|
||||
// Konfiguration erstellen
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true, // Analytics aktivieren/deaktivieren
|
||||
$samplingRate = 1.0, // Sampling-Rate (0.0 - 1.0)
|
||||
$securityAnalyticsEnabled = true, // Sicherheits-Analytics aktivieren
|
||||
$dataPath = '/var/www/html/storage/analytics', // Pfad für Datenspeicherung
|
||||
$bufferSize = 1000, // Puffergröße für Ereignisse
|
||||
$retentionDays = 365, // Aufbewahrungsdauer in Tagen
|
||||
$trackPageViews = true, // Seitenaufrufe erfassen
|
||||
$trackApiCalls = true, // API-Aufrufe erfassen
|
||||
$trackUserActions = true, // Benutzeraktionen erfassen
|
||||
$trackErrors = true, // Fehler erfassen
|
||||
$trackPerformance = true // Leistungsmetriken erfassen
|
||||
);
|
||||
```
|
||||
|
||||
## Standardkonfiguration
|
||||
|
||||
Die `AnalyticsConfig`-Klasse bietet eine Standardkonfiguration, die für die meisten Anwendungen geeignet ist:
|
||||
|
||||
```php
|
||||
// Standardkonfiguration verwenden
|
||||
$config = AnalyticsConfig::default();
|
||||
```
|
||||
|
||||
Die Standardkonfiguration aktiviert alle Features mit sinnvollen Standardwerten:
|
||||
- Analytics aktiviert
|
||||
- 100% Sampling (alle Daten werden erfasst)
|
||||
- Sicherheits-Analytics aktiviert
|
||||
- 1000 Ereignisse im Puffer
|
||||
- 365 Tage Aufbewahrungsdauer
|
||||
- Alle Tracking-Typen aktiviert
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
Die Konfiguration kann auch über Umgebungsvariablen angepasst werden:
|
||||
|
||||
```php
|
||||
// In der AnalyticsConfig-Klasse
|
||||
public static function default(): self
|
||||
{
|
||||
return new self(
|
||||
enabled: filter_var($_ENV['ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
samplingRate: (float)($_ENV['ANALYTICS_SAMPLING_RATE'] ?? 1.0),
|
||||
securityAnalyticsEnabled: filter_var($_ENV['SECURITY_ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
dataPath: $_ENV['ANALYTICS_DATA_PATH'] ?? '/var/www/html/storage/analytics',
|
||||
bufferSize: (int)($_ENV['ANALYTICS_BUFFER_SIZE'] ?? 1000),
|
||||
retentionDays: (int)($_ENV['ANALYTICS_RETENTION_DAYS'] ?? 365),
|
||||
trackPageViews: filter_var($_ENV['ANALYTICS_TRACK_PAGE_VIEWS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackApiCalls: filter_var($_ENV['ANALYTICS_TRACK_API_CALLS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackUserActions: filter_var($_ENV['ANALYTICS_TRACK_USER_ACTIONS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackErrors: filter_var($_ENV['ANALYTICS_TRACK_ERRORS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackPerformance: filter_var($_ENV['ANALYTICS_TRACK_PERFORMANCE'] ?? true, FILTER_VALIDATE_BOOLEAN)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Beispiel für eine `.env`-Datei:
|
||||
|
||||
```
|
||||
ANALYTICS_ENABLED=true
|
||||
ANALYTICS_SAMPLING_RATE=0.1
|
||||
SECURITY_ANALYTICS_ENABLED=true
|
||||
ANALYTICS_DATA_PATH=/var/www/html/storage/analytics
|
||||
ANALYTICS_BUFFER_SIZE=1000
|
||||
ANALYTICS_RETENTION_DAYS=365
|
||||
ANALYTICS_TRACK_PAGE_VIEWS=true
|
||||
ANALYTICS_TRACK_API_CALLS=true
|
||||
ANALYTICS_TRACK_USER_ACTIONS=true
|
||||
ANALYTICS_TRACK_ERRORS=true
|
||||
ANALYTICS_TRACK_PERFORMANCE=true
|
||||
```
|
||||
|
||||
## Konfigurationsoptionen im Detail
|
||||
|
||||
### Grundlegende Optionen
|
||||
|
||||
#### enabled
|
||||
|
||||
Aktiviert oder deaktiviert das gesamte Analytics Framework:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true, // Analytics aktivieren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// oder
|
||||
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: false, // Analytics deaktivieren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Wenn deaktiviert, werden keine Daten erfasst, und alle Aufrufe an den `AnalyticsCollector` werden ignoriert.
|
||||
|
||||
#### samplingRate
|
||||
|
||||
Definiert den Prozentsatz der Daten, die erfasst werden sollen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0, // 100% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// oder
|
||||
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1, // 10% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Die Sampling-Rate ist nützlich, um die Datenmenge und die Serverbelastung bei hohem Datenaufkommen zu reduzieren. Ein Wert von 0.1 bedeutet, dass nur 10% aller Ereignisse erfasst werden.
|
||||
|
||||
#### securityAnalyticsEnabled
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Sicherheitsereignissen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0,
|
||||
securityAnalyticsEnabled: true, // Sicherheits-Analytics aktivieren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Wenn aktiviert, werden Sicherheitsereignisse (wie Anmeldeversuche, CSRF-Angriffe, WAF-Blockierungen) erfasst und analysiert.
|
||||
|
||||
### Speicheroptionen
|
||||
|
||||
#### dataPath
|
||||
|
||||
Definiert den Pfad, in dem Analytics-Daten gespeichert werden:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
dataPath: '/var/www/html/storage/analytics',
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Stellen Sie sicher, dass dieser Pfad existiert und für die Anwendung beschreibbar ist.
|
||||
|
||||
#### bufferSize
|
||||
|
||||
Definiert die Anzahl der Ereignisse, die im Speicher gepuffert werden, bevor sie auf die Festplatte geschrieben werden:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
bufferSize: 1000, // 1000 Ereignisse puffern
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Ein größerer Puffer reduziert die Anzahl der Schreibvorgänge, erhöht aber den Speicherverbrauch und das Risiko von Datenverlust bei einem Absturz.
|
||||
|
||||
#### retentionDays
|
||||
|
||||
Definiert die Anzahl der Tage, für die Analytics-Daten aufbewahrt werden:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
retentionDays: 365, // Daten für 1 Jahr aufbewahren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Ältere Daten werden automatisch gelöscht, um Speicherplatz zu sparen.
|
||||
|
||||
### Tracking-Optionen
|
||||
|
||||
#### trackPageViews
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Seitenaufrufen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackPageViews: true, // Seitenaufrufe erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackApiCalls
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von API-Aufrufen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackApiCalls: true, // API-Aufrufe erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackUserActions
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Benutzeraktionen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackUserActions: true, // Benutzeraktionen erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackErrors
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Fehlern:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackErrors: true, // Fehler erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackPerformance
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Leistungsmetriken:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackPerformance: true, // Leistungsmetriken erfassen
|
||||
);
|
||||
```
|
||||
|
||||
## Konfiguration in der Anwendung
|
||||
|
||||
### Konfigurationsdatei
|
||||
|
||||
Die Analytics-Konfiguration kann in einer dedizierten Konfigurationsdatei definiert werden:
|
||||
|
||||
```php
|
||||
// config/analytics.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'sampling_rate' => 1.0,
|
||||
'security_analytics_enabled' => true,
|
||||
'data_path' => '/var/www/html/storage/analytics',
|
||||
'buffer_size' => 1000,
|
||||
'retention_days' => 365,
|
||||
'track_page_views' => true,
|
||||
'track_api_calls' => true,
|
||||
'track_user_actions' => true,
|
||||
'track_errors' => true,
|
||||
'track_performance' => true,
|
||||
];
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
Die Konfiguration kann über Dependency Injection in die Anwendung eingebunden werden:
|
||||
|
||||
```php
|
||||
// In einem Service Provider oder Container-Konfiguration
|
||||
$container->set(AnalyticsConfig::class, function () {
|
||||
return new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1,
|
||||
// ... weitere Optionen
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Laufzeitkonfiguration
|
||||
|
||||
Die Konfiguration kann zur Laufzeit angepasst werden:
|
||||
|
||||
```php
|
||||
// Analytics-Konfiguration aktualisieren
|
||||
$newConfig = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.5, // Sampling-Rate anpassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// Konfiguration aktualisieren
|
||||
$analyticsManager->updateConfig($newConfig);
|
||||
```
|
||||
|
||||
## Umgebungsspezifische Konfiguration
|
||||
|
||||
Es ist oft sinnvoll, unterschiedliche Konfigurationen für verschiedene Umgebungen zu verwenden:
|
||||
|
||||
### Produktionsumgebung
|
||||
|
||||
```php
|
||||
// Produktionskonfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1, // Reduzierte Sampling-Rate für hohe Lasten
|
||||
securityAnalyticsEnabled: true,
|
||||
dataPath: '/var/www/html/storage/analytics',
|
||||
bufferSize: 5000, // Größerer Puffer für bessere Leistung
|
||||
retentionDays: 365,
|
||||
trackPageViews: true,
|
||||
trackApiCalls: true,
|
||||
trackUserActions: true,
|
||||
trackErrors: true,
|
||||
trackPerformance: true
|
||||
);
|
||||
```
|
||||
|
||||
### Entwicklungsumgebung
|
||||
|
||||
```php
|
||||
// Entwicklungskonfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0, // Alle Daten erfassen für einfachere Fehlersuche
|
||||
securityAnalyticsEnabled: true,
|
||||
dataPath: '/var/www/html/storage/analytics',
|
||||
bufferSize: 100, // Kleinerer Puffer für sofortiges Feedback
|
||||
retentionDays: 30, // Kürzere Aufbewahrungsdauer
|
||||
trackPageViews: true,
|
||||
trackApiCalls: true,
|
||||
trackUserActions: true,
|
||||
trackErrors: true,
|
||||
trackPerformance: true
|
||||
);
|
||||
```
|
||||
|
||||
### Testumgebung
|
||||
|
||||
```php
|
||||
// Testkonfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: false, // Deaktiviert für Tests
|
||||
samplingRate: 1.0,
|
||||
securityAnalyticsEnabled: false,
|
||||
dataPath: '/tmp/analytics',
|
||||
bufferSize: 10,
|
||||
retentionDays: 1,
|
||||
trackPageViews: false,
|
||||
trackApiCalls: false,
|
||||
trackUserActions: false,
|
||||
trackErrors: false,
|
||||
trackPerformance: false
|
||||
);
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Sampling für hohe Lasten
|
||||
|
||||
Bei hohem Datenaufkommen kann die Sampling-Rate reduziert werden, um die Serverbelastung zu verringern:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.01, // Nur 1% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
### Pufferoptimierung
|
||||
|
||||
Die Puffergröße kann angepasst werden, um die Anzahl der Schreibvorgänge zu optimieren:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
bufferSize: 10000, // Größerer Puffer für weniger Schreibvorgänge
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
### Selektives Tracking
|
||||
|
||||
Nicht alle Tracking-Typen müssen aktiviert sein. Deaktivieren Sie die Typen, die Sie nicht benötigen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackPageViews: true,
|
||||
trackApiCalls: true,
|
||||
trackUserActions: false, // Benutzeraktionen deaktivieren
|
||||
trackErrors: true,
|
||||
trackPerformance: false // Leistungsmetriken deaktivieren
|
||||
);
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Keine Daten werden erfasst
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Analytics ist deaktiviert (`enabled = false`)
|
||||
- Sampling-Rate ist zu niedrig
|
||||
- Fehler beim Speichern der Daten
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Konfiguration
|
||||
- Erhöhen Sie die Sampling-Rate
|
||||
- Überprüfen Sie die Logs auf Fehler
|
||||
- Stellen Sie sicher, dass der Datenpfad existiert und beschreibbar ist
|
||||
|
||||
```php
|
||||
// Überprüfen Sie die Konfiguration
|
||||
var_dump($analyticsConfig);
|
||||
|
||||
// Erhöhen Sie die Sampling-Rate
|
||||
$newConfig = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0, // 100% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// Überprüfen Sie die Berechtigungen des Datenpfads
|
||||
if (!is_writable($analyticsConfig->dataPath)) {
|
||||
echo "Datenpfad ist nicht beschreibbar: " . $analyticsConfig->dataPath;
|
||||
}
|
||||
```
|
||||
|
||||
#### Hohe Serverbelastung
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Zu viele Ereignisse werden erfasst
|
||||
- Puffergröße ist zu klein
|
||||
- Synchrone Verarbeitung bei hohem Datenaufkommen
|
||||
|
||||
Lösung:
|
||||
- Reduzieren Sie die Sampling-Rate
|
||||
- Erhöhen Sie die Puffergröße
|
||||
- Verwenden Sie asynchrone Verarbeitung
|
||||
|
||||
```php
|
||||
// Reduzieren Sie die Sampling-Rate
|
||||
$newConfig = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1, // Nur 10% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// Erhöhen Sie die Puffergröße
|
||||
$newConfig = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
bufferSize: 10000, // Größerer Puffer
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Analytics Framework Übersicht](index.md)
|
||||
- [Analytics Beispiele](examples.md)
|
||||
- [Performance-Monitoring](/components/performance/index.md)
|
||||
760
docs/components/analytics/examples.md
Normal file
760
docs/components/analytics/examples.md
Normal file
@@ -0,0 +1,760 @@
|
||||
# Analytics Framework Beispiele
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Analytics Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation enthält praktische Beispiele für die Verwendung des Analytics Frameworks in verschiedenen Szenarien. Die Beispiele zeigen, wie Sie Analytics-Daten erfassen, analysieren und in Ihre Anwendung integrieren können.
|
||||
|
||||
## Grundlegende Verwendung
|
||||
|
||||
### Benutzeraktionen erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function handleLogin(Request $request): Response
|
||||
{
|
||||
// Login-Logik
|
||||
$success = $this->authService->login($request->getParam('email'), $request->getParam('password'));
|
||||
|
||||
// Login-Aktion erfassen
|
||||
$this->analyticsCollector->trackAction(
|
||||
'user_login',
|
||||
AnalyticsCategory::USER,
|
||||
[
|
||||
'success' => $success,
|
||||
'email_domain' => explode('@', $request->getParam('email'))[1] ?? 'unknown',
|
||||
'ip' => $request->getClientIp(),
|
||||
'user_agent' => $request->getUserAgent()
|
||||
]
|
||||
);
|
||||
|
||||
// Antwort zurückgeben
|
||||
return $success
|
||||
? $this->redirect('/dashboard')
|
||||
: $this->renderForm('login', ['error' => 'Ungültige Anmeldedaten']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Seitenaufrufe erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class ProductController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly ProductRepository $productRepository
|
||||
) {}
|
||||
|
||||
public function showProduct(Request $request, string $productId): Response
|
||||
{
|
||||
$product = $this->productRepository->find($productId);
|
||||
|
||||
if (!$product) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// Seitenaufruf erfassen
|
||||
$this->analyticsCollector->trackPageView(
|
||||
"/products/{$productId}",
|
||||
[
|
||||
'product_id' => $productId,
|
||||
'product_name' => $product->name,
|
||||
'product_category' => $product->category,
|
||||
'referrer' => $request->getHeader('Referer'),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
|
||||
return $this->render('product', ['product' => $product]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fehler erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class ErrorHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly Logger $logger
|
||||
) {}
|
||||
|
||||
public function handleException(\Throwable $exception): Response
|
||||
{
|
||||
// Fehler loggen
|
||||
$this->logger->error('Unbehandelte Ausnahme: ' . $exception->getMessage(), [
|
||||
'exception' => $exception
|
||||
]);
|
||||
|
||||
// Fehler für Analytics erfassen
|
||||
$this->analyticsCollector->trackError(
|
||||
get_class($exception),
|
||||
[
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
'request_uri' => $_SERVER['REQUEST_URI'] ?? null,
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
|
||||
// Fehlerseite anzeigen
|
||||
return new Response(500, [], 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Geschäftsereignisse erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class OrderService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly OrderRepository $orderRepository
|
||||
) {}
|
||||
|
||||
public function placeOrder(Order $order): bool
|
||||
{
|
||||
// Bestellung speichern
|
||||
$success = $this->orderRepository->save($order);
|
||||
|
||||
if ($success) {
|
||||
// Geschäftsereignis erfassen
|
||||
$this->analyticsCollector->trackBusinessEvent(
|
||||
'order_placed',
|
||||
[
|
||||
'order_id' => $order->id,
|
||||
'user_id' => $order->userId,
|
||||
'total_amount' => $order->totalAmount,
|
||||
'items_count' => count($order->items),
|
||||
'payment_method' => $order->paymentMethod,
|
||||
'shipping_country' => $order->shippingAddress->country
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API-Aufrufe erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class ApiClient
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly HttpClient $httpClient
|
||||
) {}
|
||||
|
||||
public function sendRequest(string $endpoint, string $method, array $data = []): Response
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request($method, $endpoint, $data);
|
||||
$statusCode = $response->getStatusCode();
|
||||
$success = $statusCode >= 200 && $statusCode < 300;
|
||||
} catch (\Exception $e) {
|
||||
$statusCode = 0;
|
||||
$success = false;
|
||||
}
|
||||
|
||||
$duration = (microtime(true) - $startTime) * 1000; // in ms
|
||||
|
||||
// API-Aufruf erfassen
|
||||
$this->analyticsCollector->trackApiCall(
|
||||
$endpoint,
|
||||
$method,
|
||||
$statusCode,
|
||||
[
|
||||
'duration' => $duration,
|
||||
'success' => $success,
|
||||
'data_size' => strlen(json_encode($data)),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fortgeschrittene Verwendung
|
||||
|
||||
### Automatische Erfassung mit Middleware
|
||||
|
||||
Die `AnalyticsMiddleware` erfasst automatisch Seitenaufrufe und API-Aufrufe:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(AnalyticsMiddleware::class);
|
||||
```
|
||||
|
||||
Anpassung der Middleware für bestimmte Routen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
use App\Framework\Http\MiddlewareContext;
|
||||
use App\Framework\Http\Next;
|
||||
|
||||
class CustomAnalyticsMiddleware extends AnalyticsMiddleware
|
||||
{
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
|
||||
// Bestimmte Routen ausschließen
|
||||
if (str_starts_with($request->path, '/api/health') || str_starts_with($request->path, '/static/')) {
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
// Zusätzliche Daten für bestimmte Routen hinzufügen
|
||||
$additionalData = [];
|
||||
if (str_starts_with($request->path, '/products/')) {
|
||||
$productId = substr($request->path, 10);
|
||||
$additionalData['product_id'] = $productId;
|
||||
}
|
||||
|
||||
// Standardverhalten mit zusätzlichen Daten
|
||||
return parent::__invoke($context, $next, $additionalData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sicherheitsereignisse erfassen
|
||||
|
||||
Integration mit dem Security-System:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Listeners\SecurityAnalyticsListener;
|
||||
use App\Framework\Analytics\Bridges\SecurityEventBridge;
|
||||
use App\Framework\Security\Events\LoginAttemptEvent;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
$securityEventBridge = SecurityEventBridge::create(
|
||||
$container->get(SecurityAnalyticsListener::class),
|
||||
$config->securityAnalyticsEnabled
|
||||
);
|
||||
|
||||
$eventDispatcher->addListener(LoginAttemptEvent::class, [$securityEventBridge, 'handleSecurityEvent']);
|
||||
```
|
||||
|
||||
Benutzerdefinierter Security-Event-Listener:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
use App\Framework\Security\Events\LoginAttemptEvent;
|
||||
|
||||
class LoginAnalyticsListener
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function onLoginAttempt(LoginAttemptEvent $event): void
|
||||
{
|
||||
$this->analyticsCollector->trackAction(
|
||||
'login_attempt',
|
||||
AnalyticsCategory::SECURITY,
|
||||
[
|
||||
'username' => $event->getUsername(),
|
||||
'success' => $event->isSuccessful(),
|
||||
'ip' => $event->getIp(),
|
||||
'user_agent' => $event->getUserAgent(),
|
||||
'timestamp' => $event->getTimestamp()->format('Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Asynchrone Datenerfassung
|
||||
|
||||
Für maximale Leistung kann die Analytics-Datenerfassung asynchron erfolgen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AsyncAnalyticsCollector;
|
||||
use App\Framework\Queue\QueueInterface;
|
||||
|
||||
class AsyncAnalyticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly QueueInterface $queue
|
||||
) {}
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
// Asynchronen Collector erstellen
|
||||
$asyncCollector = new AsyncAnalyticsCollector(
|
||||
$this->analyticsCollector,
|
||||
$this->queue
|
||||
);
|
||||
|
||||
// Asynchronen Collector registrieren
|
||||
$this->container->set(AnalyticsCollector::class, $asyncCollector);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Ereignistypen
|
||||
|
||||
Sie können benutzerdefinierte Ereignistypen für spezifische Anforderungen erstellen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class CustomAnalyticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function trackProductView(string $productId, string $productName, string $category): void
|
||||
{
|
||||
$this->analyticsCollector->trackAction(
|
||||
'product_view',
|
||||
AnalyticsCategory::BUSINESS,
|
||||
[
|
||||
'product_id' => $productId,
|
||||
'product_name' => $productName,
|
||||
'category' => $category,
|
||||
'timestamp' => time(),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function trackAddToCart(string $productId, int $quantity, float $price): void
|
||||
{
|
||||
$this->analyticsCollector->trackAction(
|
||||
'add_to_cart',
|
||||
AnalyticsCategory::BUSINESS,
|
||||
[
|
||||
'product_id' => $productId,
|
||||
'quantity' => $quantity,
|
||||
'price' => $price,
|
||||
'total' => $quantity * $price,
|
||||
'timestamp' => time(),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Datenanalyse
|
||||
|
||||
### Rohdaten abfragen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
class AnalyticsReportService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsStorage $analyticsStorage
|
||||
) {}
|
||||
|
||||
public function getPageViewsReport(\DateTimeInterface $from, \DateTimeInterface $to): array
|
||||
{
|
||||
// Rohdaten für Seitenaufrufe abrufen
|
||||
$pageViews = $this->analyticsStorage->getRawData(
|
||||
'page_view',
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// Daten nach Seite gruppieren
|
||||
$pageViewsByPath = [];
|
||||
foreach ($pageViews as $pageView) {
|
||||
$path = $pageView['path'] ?? 'unknown';
|
||||
if (!isset($pageViewsByPath[$path])) {
|
||||
$pageViewsByPath[$path] = 0;
|
||||
}
|
||||
$pageViewsByPath[$path]++;
|
||||
}
|
||||
|
||||
// Nach Anzahl sortieren
|
||||
arsort($pageViewsByPath);
|
||||
|
||||
return $pageViewsByPath;
|
||||
}
|
||||
|
||||
public function getUserActivityReport(string $userId, \DateTimeInterface $from, \DateTimeInterface $to): array
|
||||
{
|
||||
// Alle Aktionen für einen bestimmten Benutzer abrufen
|
||||
$actions = $this->analyticsStorage->getRawData(
|
||||
'user_action',
|
||||
$from,
|
||||
$to,
|
||||
['user_id' => $userId]
|
||||
);
|
||||
|
||||
// Nach Zeitstempel sortieren
|
||||
usort($actions, function ($a, $b) {
|
||||
return $a['timestamp'] <=> $b['timestamp'];
|
||||
});
|
||||
|
||||
return $actions;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Aggregierte Daten abfragen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
class PerformanceReportService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsStorage $analyticsStorage
|
||||
) {}
|
||||
|
||||
public function getApiPerformanceReport(\DateTimeInterface $from, \DateTimeInterface $to): array
|
||||
{
|
||||
// Aggregierte Daten für API-Aufrufe abrufen
|
||||
$apiCallStats = $this->analyticsStorage->getAggregatedData(
|
||||
'analytics_api_calls_total',
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// Durchschnittliche Antwortzeit pro Endpunkt berechnen
|
||||
$responseTimeByEndpoint = [];
|
||||
foreach ($apiCallStats as $stat) {
|
||||
$endpoint = $stat['tags']['path'] ?? 'unknown';
|
||||
$method = $stat['tags']['method'] ?? 'unknown';
|
||||
$key = "{$method} {$endpoint}";
|
||||
|
||||
if (!isset($responseTimeByEndpoint[$key])) {
|
||||
$responseTimeByEndpoint[$key] = [
|
||||
'count' => 0,
|
||||
'total_time' => 0,
|
||||
'min_time' => PHP_FLOAT_MAX,
|
||||
'max_time' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$duration = $stat['tags']['duration'] ?? 0;
|
||||
$responseTimeByEndpoint[$key]['count']++;
|
||||
$responseTimeByEndpoint[$key]['total_time'] += $duration;
|
||||
$responseTimeByEndpoint[$key]['min_time'] = min($responseTimeByEndpoint[$key]['min_time'], $duration);
|
||||
$responseTimeByEndpoint[$key]['max_time'] = max($responseTimeByEndpoint[$key]['max_time'], $duration);
|
||||
}
|
||||
|
||||
// Durchschnitt berechnen
|
||||
foreach ($responseTimeByEndpoint as $key => $data) {
|
||||
$responseTimeByEndpoint[$key]['avg_time'] = $data['total_time'] / $data['count'];
|
||||
}
|
||||
|
||||
// Nach durchschnittlicher Antwortzeit sortieren
|
||||
uasort($responseTimeByEndpoint, function ($a, $b) {
|
||||
return $b['avg_time'] <=> $a['avg_time'];
|
||||
});
|
||||
|
||||
return $responseTimeByEndpoint;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dashboard-Integration
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Dashboard\AnalyticsDashboard;
|
||||
|
||||
class AdminController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsDashboard $dashboard,
|
||||
private readonly TemplateEngine $templateEngine
|
||||
) {}
|
||||
|
||||
public function showDashboard(Request $request): Response
|
||||
{
|
||||
// Zeitraum aus Anfrage oder Standardwerte
|
||||
$from = new \DateTimeImmutable($request->getQueryParam('from', '-7 days'));
|
||||
$to = new \DateTimeImmutable($request->getQueryParam('to', 'now'));
|
||||
|
||||
// Dashboard-Daten abrufen
|
||||
$stats = $this->dashboard->getStats($from, $to);
|
||||
|
||||
// Spezifische Statistiken abrufen
|
||||
$pageViewStats = $this->dashboard->getPageViewStats($from, $to);
|
||||
$errorStats = $this->dashboard->getErrorStats($from, $to);
|
||||
$performanceStats = $this->dashboard->getPerformanceStats($from, $to);
|
||||
$businessStats = $this->dashboard->getBusinessStats($from, $to);
|
||||
|
||||
// Dashboard rendern
|
||||
return $this->templateEngine->render('admin/dashboard', [
|
||||
'stats' => $stats,
|
||||
'pageViewStats' => $pageViewStats,
|
||||
'errorStats' => $errorStats,
|
||||
'performanceStats' => $performanceStats,
|
||||
'businessStats' => $businessStats,
|
||||
'from' => $from,
|
||||
'to' => $to
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Datenexport
|
||||
|
||||
### CSV-Export
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Export\AnalyticsExporter;
|
||||
|
||||
class ExportController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsExporter $exporter
|
||||
) {}
|
||||
|
||||
public function exportPageViews(Request $request): Response
|
||||
{
|
||||
// Zeitraum aus Anfrage
|
||||
$from = new \DateTimeImmutable($request->getQueryParam('from', '-7 days'));
|
||||
$to = new \DateTimeImmutable($request->getQueryParam('to', 'now'));
|
||||
|
||||
// Daten exportieren
|
||||
$csvData = $this->exporter->exportToCsv(
|
||||
'page_view',
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// CSV-Datei zurückgeben
|
||||
return new Response(
|
||||
200,
|
||||
[
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="page_views.csv"'
|
||||
],
|
||||
$csvData
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JSON-Export
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Export\AnalyticsExporter;
|
||||
|
||||
class ApiController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsExporter $exporter
|
||||
) {}
|
||||
|
||||
public function getAnalyticsData(Request $request): Response
|
||||
{
|
||||
// Parameter aus Anfrage
|
||||
$eventType = $request->getQueryParam('type', 'page_view');
|
||||
$from = new \DateTimeImmutable($request->getQueryParam('from', '-7 days'));
|
||||
$to = new \DateTimeImmutable($request->getQueryParam('to', 'now'));
|
||||
|
||||
// Daten exportieren
|
||||
$data = $this->exporter->exportToArray(
|
||||
$eventType,
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// JSON-Daten zurückgeben
|
||||
return new Response(
|
||||
200,
|
||||
['Content-Type' => 'application/json'],
|
||||
json_encode($data)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit anderen Systemen
|
||||
|
||||
### Event-System-Integration
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\EventSubscribers\AnalyticsEventSubscriber;
|
||||
use App\Framework\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class AnalyticsBootstrap
|
||||
{
|
||||
public function initialize(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
// Analytics-Event-Subscriber registrieren
|
||||
$eventDispatcher->addSubscriber(new AnalyticsEventSubscriber(
|
||||
$this->container->get(AnalyticsCollector::class)
|
||||
));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Externe Analytics-Integration
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class ExternalAnalyticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly ExternalAnalyticsClient $externalClient
|
||||
) {}
|
||||
|
||||
public function trackEvent(string $eventName, array $properties): void
|
||||
{
|
||||
// Intern erfassen
|
||||
$this->analyticsCollector->trackAction(
|
||||
$eventName,
|
||||
AnalyticsCategory::BUSINESS,
|
||||
$properties
|
||||
);
|
||||
|
||||
// An externes System senden
|
||||
$this->externalClient->trackEvent($eventName, $properties);
|
||||
}
|
||||
|
||||
public function syncData(\DateTimeInterface $from, \DateTimeInterface $to): void
|
||||
{
|
||||
// Daten aus internem Analytics abrufen
|
||||
$internalData = $this->analyticsStorage->getRawData('*', $from, $to);
|
||||
|
||||
// Daten an externes System senden
|
||||
$this->externalClient->batchImport($internalData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Debugging der Datenerfassung
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class DebugAnalyticsCollector extends AnalyticsCollector
|
||||
{
|
||||
public function trackAction(string $action, AnalyticsCategory $category, array $data = []): void
|
||||
{
|
||||
// Debug-Ausgabe
|
||||
echo "Tracking action: {$action}, category: {$category->value}\n";
|
||||
echo "Data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n";
|
||||
|
||||
// Original-Methode aufrufen
|
||||
parent::trackAction($action, $category, $data);
|
||||
}
|
||||
|
||||
public function trackPageView(string $path, array $data = []): void
|
||||
{
|
||||
// Debug-Ausgabe
|
||||
echo "Tracking page view: {$path}\n";
|
||||
echo "Data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n";
|
||||
|
||||
// Original-Methode aufrufen
|
||||
parent::trackPageView($path, $data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Überprüfung der Datenspeicherung
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
class AnalyticsDebugService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsStorage $analyticsStorage,
|
||||
private readonly string $dataPath
|
||||
) {}
|
||||
|
||||
public function checkStorage(): array
|
||||
{
|
||||
$result = [
|
||||
'status' => 'ok',
|
||||
'messages' => [],
|
||||
'files' => []
|
||||
];
|
||||
|
||||
// Prüfen, ob der Datenpfad existiert
|
||||
if (!is_dir($this->dataPath)) {
|
||||
$result['status'] = 'error';
|
||||
$result['messages'][] = "Datenpfad existiert nicht: {$this->dataPath}";
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Prüfen, ob der Datenpfad beschreibbar ist
|
||||
if (!is_writable($this->dataPath)) {
|
||||
$result['status'] = 'error';
|
||||
$result['messages'][] = "Datenpfad ist nicht beschreibbar: {$this->dataPath}";
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Dateien im Datenpfad auflisten
|
||||
$files = glob($this->dataPath . '/*.json');
|
||||
foreach ($files as $file) {
|
||||
$size = filesize($file);
|
||||
$modified = date('Y-m-d H:i:s', filemtime($file));
|
||||
$result['files'][] = [
|
||||
'name' => basename($file),
|
||||
'size' => $size,
|
||||
'modified' => $modified
|
||||
];
|
||||
}
|
||||
|
||||
// Testdaten speichern
|
||||
try {
|
||||
$this->analyticsStorage->storeRawData('debug_test', [
|
||||
'timestamp' => time(),
|
||||
'message' => 'Debug test'
|
||||
]);
|
||||
$result['messages'][] = "Testdaten erfolgreich gespeichert";
|
||||
} catch (\Exception $e) {
|
||||
$result['status'] = 'error';
|
||||
$result['messages'][] = "Fehler beim Speichern von Testdaten: " . $e->getMessage();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Analytics Framework Übersicht](index.md)
|
||||
- [Analytics Konfiguration](configuration.md)
|
||||
- [Performance-Monitoring](/components/performance/index.md)
|
||||
507
docs/components/analytics/index.md
Normal file
507
docs/components/analytics/index.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# Analytics Framework
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Analytics Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Analytics Framework ist ein leistungsstarkes System zur Erfassung, Speicherung und Analyse von Daten über die Nutzung und Leistung Ihrer Anwendung. Es ist vollständig in das Framework integriert und ermöglicht die Erfassung verschiedener Arten von Ereignissen, darunter Seitenaufrufe, Benutzeraktionen, API-Aufrufe, Fehler und Geschäftsereignisse.
|
||||
|
||||
Das Framework basiert auf dem Performance-System und bietet eine flexible, erweiterbare Architektur für verschiedene Analyseanforderungen, ohne auf externe Dienste angewiesen zu sein.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### AnalyticsCollector
|
||||
|
||||
Die zentrale Klasse für die Erfassung von Analytics-Daten:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
// Benutzeraktion erfassen
|
||||
$analyticsCollector->trackAction(
|
||||
'button_click',
|
||||
AnalyticsCategory::USER,
|
||||
['button_id' => 'submit', 'page' => '/checkout']
|
||||
);
|
||||
|
||||
// Seitenaufruf erfassen
|
||||
$analyticsCollector->trackPageView(
|
||||
'/products',
|
||||
['referrer' => '/home', 'user_id' => $userId]
|
||||
);
|
||||
|
||||
// Fehler erfassen
|
||||
$analyticsCollector->trackError(
|
||||
'validation_error',
|
||||
['field' => 'email', 'message' => 'Invalid email format']
|
||||
);
|
||||
|
||||
// Geschäftsereignis erfassen
|
||||
$analyticsCollector->trackBusinessEvent(
|
||||
'order_completed',
|
||||
['order_id' => '12345', 'amount' => 99.99, 'items' => 3]
|
||||
);
|
||||
|
||||
// API-Aufruf erfassen
|
||||
$analyticsCollector->trackApiCall(
|
||||
'/api/products',
|
||||
'GET',
|
||||
200,
|
||||
['duration' => 45, 'cache_hit' => true]
|
||||
);
|
||||
```
|
||||
|
||||
### AnalyticsCategory
|
||||
|
||||
Ein Enum, das die verschiedenen Kategorien von Analytics-Daten definiert:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
// Verfügbare Kategorien
|
||||
$category = AnalyticsCategory::USER; // Benutzeraktionen
|
||||
$category = AnalyticsCategory::SYSTEM; // Systemereignisse
|
||||
$category = AnalyticsCategory::BUSINESS; // Geschäftsereignisse
|
||||
$category = AnalyticsCategory::PERFORMANCE; // Leistungsmetriken
|
||||
$category = AnalyticsCategory::SECURITY; // Sicherheitsereignisse
|
||||
```
|
||||
|
||||
### AnalyticsStorage
|
||||
|
||||
Das Interface für die Speicherung von Analytics-Daten:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
interface AnalyticsStorage
|
||||
{
|
||||
// Rohdaten speichern
|
||||
public function storeRawData(string $eventType, array $data): void;
|
||||
|
||||
// Aggregierte Daten speichern
|
||||
public function storeAggregatedData(string $metricName, float $value, array $tags = []): void;
|
||||
|
||||
// Rohdaten abrufen
|
||||
public function getRawData(string $eventType, \DateTimeInterface $from, \DateTimeInterface $to): array;
|
||||
|
||||
// Aggregierte Daten abrufen
|
||||
public function getAggregatedData(string $metricName, \DateTimeInterface $from, \DateTimeInterface $to, array $tags = []): array;
|
||||
}
|
||||
```
|
||||
|
||||
Das Framework bietet eine leistungsoptimierte Implementierung:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\PerformanceBasedAnalyticsStorage;
|
||||
|
||||
// Leistungsoptimierte Speicherung
|
||||
$storage = new PerformanceBasedAnalyticsStorage(
|
||||
$filesystem,
|
||||
$performanceSystem,
|
||||
$clock,
|
||||
$config
|
||||
);
|
||||
```
|
||||
|
||||
### AnalyticsMiddleware
|
||||
|
||||
Eine Middleware, die automatisch Analytics-Daten für HTTP-Anfragen erfasst:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(AnalyticsMiddleware::class);
|
||||
```
|
||||
|
||||
Die Middleware erfasst automatisch:
|
||||
- Seitenaufrufe
|
||||
- API-Aufrufe
|
||||
- Leistungsmetriken (Antwortzeit, Speicherverbrauch)
|
||||
- HTTP-Statuscodes
|
||||
- Benutzeragenten und Referrer
|
||||
|
||||
### SecurityAnalyticsListener
|
||||
|
||||
Ein Listener, der Sicherheitsereignisse in Analytics-Daten umwandelt:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Listeners\SecurityAnalyticsListener;
|
||||
use App\Framework\Analytics\Bridges\SecurityEventBridge;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
$securityEventBridge = SecurityEventBridge::create(
|
||||
$container->get(SecurityAnalyticsListener::class),
|
||||
$config->securityAnalyticsEnabled
|
||||
);
|
||||
|
||||
$eventDispatcher->addListener(SecurityEventInterface::class, [$securityEventBridge, 'handleSecurityEvent']);
|
||||
```
|
||||
|
||||
## Datenerfassung
|
||||
|
||||
### Benutzeraktionen
|
||||
|
||||
Benutzeraktionen repräsentieren Interaktionen des Benutzers mit der Anwendung:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackAction(
|
||||
'button_click', // Aktionsname
|
||||
AnalyticsCategory::USER, // Kategorie
|
||||
[ // Zusätzliche Daten
|
||||
'button_id' => 'submit',
|
||||
'page' => '/checkout',
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### Seitenaufrufe
|
||||
|
||||
Seitenaufrufe repräsentieren den Besuch einer Seite durch einen Benutzer:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackPageView(
|
||||
'/products', // Pfad
|
||||
[ // Zusätzliche Daten
|
||||
'referrer' => '/home',
|
||||
'user_id' => $userId,
|
||||
'device_type' => 'mobile'
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### Fehler
|
||||
|
||||
Fehler repräsentieren Probleme, die während der Ausführung der Anwendung auftreten:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackError(
|
||||
'validation_error', // Fehlertyp
|
||||
[ // Zusätzliche Daten
|
||||
'field' => 'email',
|
||||
'message' => 'Invalid email format',
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
|
||||
// Oder mit einer Exception
|
||||
try {
|
||||
// Code, der eine Exception werfen könnte
|
||||
} catch (\Exception $e) {
|
||||
$analyticsCollector->trackException($e);
|
||||
}
|
||||
```
|
||||
|
||||
### Geschäftsereignisse
|
||||
|
||||
Geschäftsereignisse repräsentieren wichtige Ereignisse aus geschäftlicher Sicht:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackBusinessEvent(
|
||||
'order_completed', // Ereignisname
|
||||
[ // Zusätzliche Daten
|
||||
'order_id' => '12345',
|
||||
'amount' => 99.99,
|
||||
'items' => 3,
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### API-Aufrufe
|
||||
|
||||
API-Aufrufe repräsentieren Anfragen an die API der Anwendung:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackApiCall(
|
||||
'/api/products', // Pfad
|
||||
'GET', // Methode
|
||||
200, // Statuscode
|
||||
[ // Zusätzliche Daten
|
||||
'duration' => 45,
|
||||
'cache_hit' => true,
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### AnalyticsConfig
|
||||
|
||||
Die `AnalyticsConfig`-Klasse enthält die Konfiguration für das Analytics Framework:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsConfig;
|
||||
|
||||
// Standardkonfiguration
|
||||
$config = AnalyticsConfig::default();
|
||||
|
||||
// Benutzerdefinierte Konfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 1.0,
|
||||
$securityAnalyticsEnabled = true,
|
||||
$dataPath = '/var/www/html/storage/analytics',
|
||||
$bufferSize = 1000,
|
||||
$retentionDays = 365,
|
||||
$trackPageViews = true,
|
||||
$trackApiCalls = true,
|
||||
$trackUserActions = true,
|
||||
$trackErrors = true,
|
||||
$trackPerformance = true
|
||||
);
|
||||
```
|
||||
|
||||
### Umgebungsvariablen
|
||||
|
||||
Die Konfiguration kann auch über Umgebungsvariablen angepasst werden:
|
||||
|
||||
```
|
||||
ANALYTICS_ENABLED=true
|
||||
ANALYTICS_SAMPLING_RATE=0.1
|
||||
SECURITY_ANALYTICS_ENABLED=true
|
||||
ANALYTICS_DATA_PATH=/var/www/html/storage/analytics
|
||||
ANALYTICS_BUFFER_SIZE=1000
|
||||
ANALYTICS_RETENTION_DAYS=365
|
||||
ANALYTICS_TRACK_PAGE_VIEWS=true
|
||||
ANALYTICS_TRACK_API_CALLS=true
|
||||
ANALYTICS_TRACK_USER_ACTIONS=true
|
||||
ANALYTICS_TRACK_ERRORS=true
|
||||
ANALYTICS_TRACK_PERFORMANCE=true
|
||||
```
|
||||
|
||||
## Datenanalyse
|
||||
|
||||
### Rohdaten abrufen
|
||||
|
||||
```php
|
||||
// Rohdaten für Seitenaufrufe abrufen
|
||||
$pageViews = $analyticsStorage->getRawData(
|
||||
'page_view',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Rohdaten für Geschäftsereignisse abrufen
|
||||
$orders = $analyticsStorage->getRawData(
|
||||
'business_event_order_completed',
|
||||
new \DateTimeImmutable('-30 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
```
|
||||
|
||||
### Aggregierte Daten abrufen
|
||||
|
||||
```php
|
||||
// Aggregierte Daten für Seitenaufrufe abrufen
|
||||
$pageViewStats = $analyticsStorage->getAggregatedData(
|
||||
'analytics_page_views_total',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable(),
|
||||
['path' => '/products']
|
||||
);
|
||||
|
||||
// Aggregierte Daten für API-Aufrufe abrufen
|
||||
$apiCallStats = $analyticsStorage->getAggregatedData(
|
||||
'analytics_api_calls_total',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable(),
|
||||
['path' => '/api/products', 'method' => 'GET']
|
||||
);
|
||||
```
|
||||
|
||||
### Dashboard
|
||||
|
||||
Das Analytics-Dashboard bietet eine visuelle Darstellung der erfassten Daten:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Dashboard\AnalyticsDashboard;
|
||||
|
||||
// Dashboard initialisieren
|
||||
$dashboard = new AnalyticsDashboard($analyticsStorage);
|
||||
|
||||
// Statistiken abrufen
|
||||
$stats = $dashboard->getStats(
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Spezifische Statistiken abrufen
|
||||
$pageViewStats = $dashboard->getPageViewStats();
|
||||
$errorStats = $dashboard->getErrorStats();
|
||||
$performanceStats = $dashboard->getPerformanceStats();
|
||||
$businessStats = $dashboard->getBusinessStats();
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Service-Container
|
||||
|
||||
Der Analytics-Service wird automatisch registriert und kann per Dependency Injection verwendet werden:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function register(Request $request): Response
|
||||
{
|
||||
// Benutzerregistrierung verarbeiten
|
||||
|
||||
// Geschäftsereignis erfassen
|
||||
$this->analyticsCollector->trackBusinessEvent(
|
||||
'user_registered',
|
||||
['user_id' => $user->id, 'email_domain' => $emailDomain]
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Das Analytics Framework kann mit dem Event-System des Frameworks integriert werden:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\EventSubscribers\AnalyticsEventSubscriber;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
$eventDispatcher->addSubscriber(new AnalyticsEventSubscriber($analyticsCollector));
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Sampling
|
||||
|
||||
Um die Leistung bei hohem Datenaufkommen zu verbessern, unterstützt das Analytics Framework Sampling:
|
||||
|
||||
```php
|
||||
// Sampling-Rate konfigurieren (10% der Daten erfassen)
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 0.1
|
||||
);
|
||||
```
|
||||
|
||||
### Pufferung
|
||||
|
||||
Das Analytics Framework verwendet Pufferung, um die Anzahl der Schreibvorgänge zu reduzieren:
|
||||
|
||||
```php
|
||||
// Puffergröße konfigurieren
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 1.0,
|
||||
$securityAnalyticsEnabled = true,
|
||||
$dataPath = '/var/www/html/storage/analytics',
|
||||
$bufferSize = 1000 // Anzahl der Ereignisse im Puffer
|
||||
);
|
||||
```
|
||||
|
||||
### Asynchrone Verarbeitung
|
||||
|
||||
Für maximale Leistung kann die Analytics-Verarbeitung asynchron erfolgen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AsyncAnalyticsCollector;
|
||||
|
||||
// Asynchronen Collector verwenden
|
||||
$asyncCollector = new AsyncAnalyticsCollector(
|
||||
$analyticsCollector,
|
||||
$queue
|
||||
);
|
||||
|
||||
// Daten asynchron erfassen
|
||||
$asyncCollector->trackPageView('/products');
|
||||
```
|
||||
|
||||
## Datenverwaltung
|
||||
|
||||
### Datenaufbewahrung
|
||||
|
||||
Das Analytics Framework unterstützt automatische Datenaufbewahrungsrichtlinien:
|
||||
|
||||
```php
|
||||
// Aufbewahrungsdauer konfigurieren
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 1.0,
|
||||
$securityAnalyticsEnabled = true,
|
||||
$dataPath = '/var/www/html/storage/analytics',
|
||||
$bufferSize = 1000,
|
||||
$retentionDays = 365 // Daten für 1 Jahr aufbewahren
|
||||
);
|
||||
```
|
||||
|
||||
### Datenexport
|
||||
|
||||
Daten können für die weitere Analyse exportiert werden:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Export\AnalyticsExporter;
|
||||
|
||||
// Exporter initialisieren
|
||||
$exporter = new AnalyticsExporter($analyticsStorage);
|
||||
|
||||
// Daten exportieren
|
||||
$csvData = $exporter->exportToCsv(
|
||||
'page_view',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Daten in Datei speichern
|
||||
file_put_contents('page_views.csv', $csvData);
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Logging
|
||||
|
||||
Das Analytics Framework bietet umfangreiches Logging für die Fehlerbehebung:
|
||||
|
||||
```php
|
||||
// In der AnalyticsCollector-Klasse
|
||||
$this->logger->info("Analytics: trackAction called with action={$action}, category={$category->value}");
|
||||
$this->logger->error("Analytics: Failed to store data", ['error' => $e->getMessage()]);
|
||||
```
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Keine Daten werden erfasst
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Analytics ist deaktiviert (`enabled = false`)
|
||||
- Sampling-Rate ist zu niedrig
|
||||
- Fehler beim Speichern der Daten
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Konfiguration
|
||||
- Erhöhen Sie die Sampling-Rate
|
||||
- Überprüfen Sie die Logs auf Fehler
|
||||
|
||||
#### Hohe Serverbelastung
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Zu viele Ereignisse werden erfasst
|
||||
- Puffergröße ist zu klein
|
||||
- Synchrone Verarbeitung bei hohem Datenaufkommen
|
||||
|
||||
Lösung:
|
||||
- Reduzieren Sie die Sampling-Rate
|
||||
- Erhöhen Sie die Puffergröße
|
||||
- Verwenden Sie asynchrone Verarbeitung
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Analytics Konfiguration](configuration.md)
|
||||
- [Analytics Beispiele](examples.md)
|
||||
- [Performance-Monitoring](/components/performance/index.md)
|
||||
430
docs/components/auth/configuration.md
Normal file
430
docs/components/auth/configuration.md
Normal file
@@ -0,0 +1,430 @@
|
||||
# Auth Module Configuration
|
||||
|
||||
**Konfiguration und Setup** für das Auth Module des Custom PHP Frameworks.
|
||||
|
||||
## Dependency Injection Setup
|
||||
|
||||
### Container Bindings
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordHasher;
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
|
||||
// services.php oder Container Initialization
|
||||
$container->singleton(PasswordHasher::class, function(Container $container) {
|
||||
return new PasswordHasher(
|
||||
kdf: $container->get(KeyDerivationFunction::class),
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD
|
||||
);
|
||||
});
|
||||
|
||||
$container->singleton(AuthenticationService::class, function(Container $container) {
|
||||
return new AuthenticationService(
|
||||
passwordHasher: $container->get(PasswordHasher::class),
|
||||
sessionIdGenerator: $container->get(SessionIdGenerator::class),
|
||||
repository: $container->get(AuthenticationRepository::class),
|
||||
rateLimiter: $container->get(RateLimitService::class)
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
```php
|
||||
// .env Konfiguration
|
||||
AUTH_SESSION_TIMEOUT=3600 # Session Timeout in Sekunden (1 Stunde)
|
||||
AUTH_REMEMBER_TOKEN_EXPIRY=2592000 # Remember Token Expiry (30 Tage)
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=5 # Maximale Login-Versuche
|
||||
AUTH_LOCKOUT_DURATION=900 # Account Lockout Duration (15 Minuten)
|
||||
AUTH_RATE_LIMIT_WINDOW=300 # Rate Limit Window (5 Minuten)
|
||||
|
||||
# Password Hashing Configuration
|
||||
AUTH_DEFAULT_ALGORITHM=argon2id # Standard Hash-Algorithmus
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=standard # low|standard|high
|
||||
AUTH_PASSWORD_MIN_LENGTH=8 # Minimale Passwort-Länge
|
||||
AUTH_PASSWORD_MAX_LENGTH=4096 # Maximale Passwort-Länge
|
||||
|
||||
# Session Security
|
||||
AUTH_SESSION_REGENERATE_ON_LOGIN=true # Session ID bei Login regenerieren
|
||||
AUTH_CHECK_IP_CONSISTENCY=false # IP-Konsistenz-Prüfung (optional)
|
||||
AUTH_REMEMBER_TOKEN_LENGTH=32 # Remember Token Länge (Bytes)
|
||||
```
|
||||
|
||||
### Typed Configuration Class
|
||||
|
||||
```php
|
||||
final readonly class AuthConfig
|
||||
{
|
||||
public function __construct(
|
||||
// Session Configuration
|
||||
public int $sessionTimeout = 3600,
|
||||
public int $rememberTokenExpiry = 2592000,
|
||||
public bool $sessionRegenerateOnLogin = true,
|
||||
public bool $checkIpConsistency = false,
|
||||
public int $rememberTokenLength = 32,
|
||||
|
||||
// Security Configuration
|
||||
public int $maxLoginAttempts = 5,
|
||||
public int $lockoutDuration = 900,
|
||||
public int $rateLimitWindow = 300,
|
||||
|
||||
// Password Configuration
|
||||
public string $defaultAlgorithm = 'argon2id',
|
||||
public string $defaultSecurityLevel = 'standard',
|
||||
public int $passwordMinLength = 8,
|
||||
public int $passwordMaxLength = 4096,
|
||||
|
||||
// Validation Configuration
|
||||
public bool $enforcePasswordComplexity = true,
|
||||
public bool $checkCommonPasswords = true,
|
||||
public bool $preventSequentialChars = true,
|
||||
public int $minPasswordScore = 50
|
||||
) {}
|
||||
|
||||
public static function fromEnvironment(Environment $env): self
|
||||
{
|
||||
return new self(
|
||||
sessionTimeout: $env->getInt(EnvKey::AUTH_SESSION_TIMEOUT, 3600),
|
||||
rememberTokenExpiry: $env->getInt(EnvKey::AUTH_REMEMBER_TOKEN_EXPIRY, 2592000),
|
||||
sessionRegenerateOnLogin: $env->getBool(EnvKey::AUTH_SESSION_REGENERATE_ON_LOGIN, true),
|
||||
checkIpConsistency: $env->getBool(EnvKey::AUTH_CHECK_IP_CONSISTENCY, false),
|
||||
rememberTokenLength: $env->getInt(EnvKey::AUTH_REMEMBER_TOKEN_LENGTH, 32),
|
||||
|
||||
maxLoginAttempts: $env->getInt(EnvKey::AUTH_MAX_LOGIN_ATTEMPTS, 5),
|
||||
lockoutDuration: $env->getInt(EnvKey::AUTH_LOCKOUT_DURATION, 900),
|
||||
rateLimitWindow: $env->getInt(EnvKey::AUTH_RATE_LIMIT_WINDOW, 300),
|
||||
|
||||
defaultAlgorithm: $env->get(EnvKey::AUTH_DEFAULT_ALGORITHM, 'argon2id'),
|
||||
defaultSecurityLevel: $env->get(EnvKey::AUTH_DEFAULT_SECURITY_LEVEL, 'standard'),
|
||||
passwordMinLength: $env->getInt(EnvKey::AUTH_PASSWORD_MIN_LENGTH, 8),
|
||||
passwordMaxLength: $env->getInt(EnvKey::AUTH_PASSWORD_MAX_LENGTH, 4096),
|
||||
|
||||
enforcePasswordComplexity: $env->getBool(EnvKey::AUTH_ENFORCE_PASSWORD_COMPLEXITY, true),
|
||||
checkCommonPasswords: $env->getBool(EnvKey::AUTH_CHECK_COMMON_PASSWORDS, true),
|
||||
preventSequentialChars: $env->getBool(EnvKey::AUTH_PREVENT_SEQUENTIAL_CHARS, true),
|
||||
minPasswordScore: $env->getInt(EnvKey::AUTH_MIN_PASSWORD_SCORE, 50)
|
||||
);
|
||||
}
|
||||
|
||||
public function getSecurityLevelParameters(string $algorithm): array
|
||||
{
|
||||
return match ([$algorithm, $this->defaultSecurityLevel]) {
|
||||
['argon2id', 'low'] => [
|
||||
'memory_cost' => 32768, // 32 MB
|
||||
'time_cost' => 2,
|
||||
'threads' => 2
|
||||
],
|
||||
['argon2id', 'standard'] => [
|
||||
'memory_cost' => 65536, // 64 MB
|
||||
'time_cost' => 4,
|
||||
'threads' => 3
|
||||
],
|
||||
['argon2id', 'high'] => [
|
||||
'memory_cost' => 131072, // 128 MB
|
||||
'time_cost' => 6,
|
||||
'threads' => 4
|
||||
],
|
||||
['pbkdf2-sha256', 'low'] => ['iterations' => 50000],
|
||||
['pbkdf2-sha256', 'standard'] => ['iterations' => 100000],
|
||||
['pbkdf2-sha256', 'high'] => ['iterations' => 200000],
|
||||
default => []
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Required Tables
|
||||
|
||||
```sql
|
||||
-- Benutzer-Tabelle (beispielhaft)
|
||||
CREATE TABLE users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
username VARCHAR(100) UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
password_algorithm VARCHAR(50) NOT NULL DEFAULT 'argon2id',
|
||||
password_parameters JSON,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
last_login_at DATETIME NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
INDEX idx_email (email),
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_active (is_active)
|
||||
);
|
||||
|
||||
-- Session-Tabelle
|
||||
CREATE TABLE auth_sessions (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
last_activity DATETIME NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_expires_at (expires_at),
|
||||
INDEX idx_last_activity (last_activity)
|
||||
);
|
||||
|
||||
-- Remember Token Tabelle
|
||||
CREATE TABLE auth_remember_tokens (
|
||||
token_hash VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_expires_at (expires_at)
|
||||
);
|
||||
|
||||
-- Failed Login Attempts Tabelle
|
||||
CREATE TABLE auth_failed_attempts (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id VARCHAR(255),
|
||||
identifier VARCHAR(255),
|
||||
ip_address VARCHAR(45),
|
||||
attempted_at DATETIME NOT NULL,
|
||||
reason VARCHAR(100),
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_identifier (identifier),
|
||||
INDEX idx_ip_address (ip_address),
|
||||
INDEX idx_attempted_at (attempted_at)
|
||||
);
|
||||
|
||||
-- Security Events Tabelle (optional für Logging)
|
||||
CREATE TABLE auth_security_events (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
event_type VARCHAR(50) NOT NULL,
|
||||
user_id VARCHAR(255),
|
||||
session_id VARCHAR(255),
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
event_data JSON,
|
||||
created_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_event_type (event_type),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_ip_address (ip_address)
|
||||
);
|
||||
```
|
||||
|
||||
### Migration Commands
|
||||
|
||||
```bash
|
||||
# Migration erstellen
|
||||
php console.php make:migration CreateAuthTables Auth
|
||||
|
||||
# Migration ausführen
|
||||
php console.php db:migrate
|
||||
|
||||
# Migration Status prüfen
|
||||
php console.php db:status
|
||||
```
|
||||
|
||||
## Security Level Configuration
|
||||
|
||||
### Password Hashing Levels
|
||||
|
||||
```php
|
||||
// Niedrige Sicherheit (Development, Testing)
|
||||
$lowSecurity = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW
|
||||
);
|
||||
|
||||
// Standard Sicherheit (Production Default)
|
||||
$standardSecurity = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD
|
||||
);
|
||||
|
||||
// Hohe Sicherheit (Banking, Healthcare)
|
||||
$highSecurity = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_HIGH
|
||||
);
|
||||
```
|
||||
|
||||
### Algorithm Performance Comparison
|
||||
|
||||
| Algorithm | Level | Memory | Time | Iterations | Performance | Security |
|
||||
|-----------|-------|---------|------|------------|-------------|----------|
|
||||
| Argon2ID | Low | 32 MB | 2 | - | Fast | Good |
|
||||
| Argon2ID | Standard | 64 MB | 4 | - | Medium | Excellent |
|
||||
| Argon2ID | High | 128 MB | 6 | - | Slow | Maximum |
|
||||
| PBKDF2-SHA256 | Low | - | - | 50,000 | Fast | Good |
|
||||
| PBKDF2-SHA256 | Standard | - | - | 100,000 | Fast | Good |
|
||||
| PBKDF2-SHA256 | High | - | - | 200,000 | Medium | Good |
|
||||
| Scrypt | Standard | 16 MB | - | - | Medium | Good |
|
||||
|
||||
## Rate Limiting Configuration
|
||||
|
||||
### Redis-based Rate Limiting
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\RateLimit\RedisRateLimitService;
|
||||
|
||||
$rateLimiter = new RedisRateLimitService(
|
||||
redis: $redis,
|
||||
config: new RateLimitConfig(
|
||||
maxAttempts: 5,
|
||||
windowSeconds: 300,
|
||||
lockoutDuration: 900
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### File-based Rate Limiting
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\RateLimit\FileRateLimitService;
|
||||
|
||||
$rateLimiter = new FileRateLimitService(
|
||||
cacheDir: '/tmp/auth_rate_limits',
|
||||
config: new RateLimitConfig(
|
||||
maxAttempts: 5,
|
||||
windowSeconds: 300,
|
||||
lockoutDuration: 900,
|
||||
cleanupInterval: 3600 // Cleanup alte Einträge jede Stunde
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Monitoring & Logging
|
||||
|
||||
### Security Event Configuration
|
||||
|
||||
```php
|
||||
// Security Event Handler
|
||||
final readonly class SecurityEventHandler
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $logger,
|
||||
private ?AlertingService $alerting = null
|
||||
) {}
|
||||
|
||||
public function handle(SecurityEvent $event): void
|
||||
{
|
||||
// Log alle Security Events
|
||||
$this->logger->warning('Security Event', [
|
||||
'event_type' => $event->getType(),
|
||||
'user_id' => $event->getUserId(),
|
||||
'ip_address' => (string) $event->getIpAddress(),
|
||||
'data' => $event->getData()
|
||||
]);
|
||||
|
||||
// Kritische Events alarmieren
|
||||
if ($event->isCritical()) {
|
||||
$this->alerting?->sendAlert($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Monitoring
|
||||
|
||||
```php
|
||||
// Performance Metrics für Password Hashing
|
||||
$start = microtime(true);
|
||||
$hashedPassword = $passwordHasher->hash($password);
|
||||
$hashTime = microtime(true) - $start;
|
||||
|
||||
$this->metrics->histogram('auth.password_hash_duration', $hashTime, [
|
||||
'algorithm' => $hashedPassword->getAlgorithm(),
|
||||
'security_level' => $securityLevel
|
||||
]);
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Environment-specific Configuration
|
||||
|
||||
```php
|
||||
// Production
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=high
|
||||
AUTH_SESSION_TIMEOUT=1800 # 30 Minuten
|
||||
AUTH_CHECK_IP_CONSISTENCY=true # Striktere IP-Prüfung
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=3 # Weniger Versuche
|
||||
AUTH_LOCKOUT_DURATION=3600 # 1 Stunde Lockout
|
||||
|
||||
// Development
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=low
|
||||
AUTH_SESSION_TIMEOUT=86400 # 24 Stunden
|
||||
AUTH_CHECK_IP_CONSISTENCY=false
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=10
|
||||
AUTH_LOCKOUT_DURATION=300 # 5 Minuten
|
||||
|
||||
// Testing
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=low
|
||||
AUTH_SESSION_TIMEOUT=3600
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=5
|
||||
AUTH_LOCKOUT_DURATION=60 # 1 Minute
|
||||
```
|
||||
|
||||
### Security Headers Configuration
|
||||
|
||||
```php
|
||||
// Middleware für Auth-bezogene Security Headers
|
||||
final readonly class AuthSecurityHeadersMiddleware
|
||||
{
|
||||
public function handle(HttpRequest $request, callable $next): HttpResponse
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
if ($request->getUri()->getPath() === '/login') {
|
||||
$response = $response->withHeader('X-Frame-Options', 'DENY');
|
||||
$response = $response->withHeader('X-Content-Type-Options', 'nosniff');
|
||||
$response = $response->withHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Backup & Recovery
|
||||
|
||||
### Session Cleanup
|
||||
|
||||
```bash
|
||||
# Cron Job für Session Cleanup (täglich)
|
||||
0 2 * * * php /path/to/console.php auth:cleanup-expired-sessions
|
||||
|
||||
# Manual Cleanup
|
||||
php console.php auth:cleanup-expired-sessions
|
||||
php console.php auth:cleanup-expired-tokens
|
||||
```
|
||||
|
||||
### Data Retention
|
||||
|
||||
```php
|
||||
// Cleanup Commands
|
||||
final readonly class CleanupExpiredSessionsCommand
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$expiredCount = $this->repository->deleteExpiredSessions();
|
||||
$this->output->writeln("Deleted {$expiredCount} expired sessions");
|
||||
|
||||
$tokenCount = $this->repository->deleteExpiredRememberTokens();
|
||||
$this->output->writeln("Deleted {$tokenCount} expired remember tokens");
|
||||
|
||||
$attemptCount = $this->repository->cleanupOldFailedAttempts(days: 30);
|
||||
$this->output->writeln("Cleaned up {$attemptCount} old failed attempts");
|
||||
}
|
||||
}
|
||||
797
docs/components/auth/examples.md
Normal file
797
docs/components/auth/examples.md
Normal file
@@ -0,0 +1,797 @@
|
||||
# Auth Module Examples
|
||||
|
||||
**Praktische Implementierungsbeispiele** für das Auth Module des Custom PHP Frameworks.
|
||||
|
||||
## Basic Authentication Flow
|
||||
|
||||
### User Registration
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordHasher;
|
||||
use App\Framework\Auth\PasswordValidationResult;
|
||||
|
||||
final readonly class UserRegistrationService
|
||||
{
|
||||
public function __construct(
|
||||
private PasswordHasher $passwordHasher,
|
||||
private UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function register(
|
||||
string $email,
|
||||
#[SensitiveParameter] string $password,
|
||||
string $username = null
|
||||
): RegistrationResult {
|
||||
// Validate password strength
|
||||
$validation = $this->passwordHasher->validatePasswordStrength($password);
|
||||
if (!$validation->isValid) {
|
||||
return RegistrationResult::passwordValidationFailed($validation);
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
if ($this->userRepository->findByEmail($email)) {
|
||||
return RegistrationResult::failed('User with this email already exists');
|
||||
}
|
||||
|
||||
// Hash password
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
// Create user
|
||||
$user = new User(
|
||||
id: Uuid::generate(),
|
||||
email: new Email($email),
|
||||
username: $username,
|
||||
hashedPassword: $hashedPassword,
|
||||
createdAt: new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
$this->userRepository->save($user);
|
||||
|
||||
return RegistrationResult::success($user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login Implementation
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
use App\Framework\Http\IpAddress;
|
||||
use App\Framework\Http\Session\SessionManager;
|
||||
|
||||
#[Route(path: '/login', method: Method::POST)]
|
||||
final readonly class LoginController
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionManager $sessionManager
|
||||
) {}
|
||||
|
||||
public function login(LoginRequest $request): JsonResult
|
||||
{
|
||||
$ipAddress = IpAddress::fromRequest();
|
||||
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: $request->email,
|
||||
password: $request->password,
|
||||
ipAddress: $ipAddress,
|
||||
remember: $request->remember ?? false
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
$user = $result->getUser();
|
||||
$session = $result->getSession();
|
||||
|
||||
// Store session in session manager
|
||||
$this->sessionManager->start($session->getId());
|
||||
$this->sessionManager->set('user_id', $user->getId());
|
||||
$this->sessionManager->set('authenticated_at', time());
|
||||
|
||||
$responseData = [
|
||||
'success' => true,
|
||||
'user' => [
|
||||
'id' => $user->getId(),
|
||||
'email' => (string) $user->getEmail(),
|
||||
'username' => $user->getUsername()
|
||||
],
|
||||
'session' => [
|
||||
'id' => $session->getId()->toString(),
|
||||
'expires_at' => $session->getExpiresAt()->format(\DateTimeInterface::ATOM)
|
||||
]
|
||||
];
|
||||
|
||||
// Add remember token to response if requested
|
||||
if ($rememberToken = $result->getRememberToken()) {
|
||||
$responseData['remember_token'] = $rememberToken->getPlainTextValue();
|
||||
|
||||
// Set secure HTTP-only cookie
|
||||
setcookie(
|
||||
'remember_token',
|
||||
$rememberToken->getPlainTextValue(),
|
||||
[
|
||||
'expires' => $rememberToken->getExpiresAt()->getTimestamp(),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResult($responseData);
|
||||
}
|
||||
|
||||
// Handle different failure types
|
||||
return match (true) {
|
||||
$result->isRateLimited() => new JsonResult([
|
||||
'success' => false,
|
||||
'error' => 'Too many attempts',
|
||||
'retry_after' => $result->getRetryAfter()
|
||||
], 429),
|
||||
|
||||
$result->isAccountLocked() => new JsonResult([
|
||||
'success' => false,
|
||||
'error' => 'Account temporarily locked',
|
||||
'locked_until' => $result->getLockoutExpiresAt()?->format(\DateTimeInterface::ATOM)
|
||||
], 423),
|
||||
|
||||
default => new JsonResult([
|
||||
'success' => false,
|
||||
'error' => 'Invalid credentials'
|
||||
], 401)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Auto-Login with Remember Token
|
||||
|
||||
```php
|
||||
#[Route(path: '/auth/check', method: Method::GET)]
|
||||
final readonly class AuthCheckController
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionManager $sessionManager
|
||||
) {}
|
||||
|
||||
public function check(HttpRequest $request): JsonResult
|
||||
{
|
||||
$sessionId = $this->sessionManager->getCurrentSessionId();
|
||||
|
||||
// Try session authentication first
|
||||
if ($sessionId) {
|
||||
$result = $this->authService->authenticateWithSession(
|
||||
$sessionId,
|
||||
IpAddress::fromRequest()
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
return new JsonResult([
|
||||
'authenticated' => true,
|
||||
'user' => $this->formatUser($result->getUser()),
|
||||
'session' => $this->formatSession($result->getSession())
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Try remember token authentication
|
||||
$rememberToken = $request->getCookie('remember_token');
|
||||
if ($rememberToken) {
|
||||
$result = $this->authService->authenticateWithRememberToken(
|
||||
$rememberToken,
|
||||
IpAddress::fromRequest()
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
// Start new session
|
||||
$session = $result->getSession();
|
||||
$this->sessionManager->start($session->getId());
|
||||
$this->sessionManager->set('user_id', $result->getUser()->getId());
|
||||
|
||||
// Update remember token cookie
|
||||
$newRememberToken = $result->getRememberToken();
|
||||
if ($newRememberToken) {
|
||||
setcookie(
|
||||
'remember_token',
|
||||
$newRememberToken->getPlainTextValue(),
|
||||
[
|
||||
'expires' => $newRememberToken->getExpiresAt()->getTimestamp(),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResult([
|
||||
'authenticated' => true,
|
||||
'user' => $this->formatUser($result->getUser()),
|
||||
'session' => $this->formatSession($session)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResult(['authenticated' => false]);
|
||||
}
|
||||
|
||||
private function formatUser(User $user): array
|
||||
{
|
||||
return [
|
||||
'id' => $user->getId(),
|
||||
'email' => (string) $user->getEmail(),
|
||||
'username' => $user->getUsername(),
|
||||
'last_login' => $user->getLastLoginAt()?->format(\DateTimeInterface::ATOM)
|
||||
];
|
||||
}
|
||||
|
||||
private function formatSession(AuthenticationSession $session): array
|
||||
{
|
||||
return [
|
||||
'id' => $session->getId()->toString(),
|
||||
'created_at' => $session->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'expires_at' => $session->getExpiresAt()->format(\DateTimeInterface::ATOM),
|
||||
'last_activity' => $session->getLastActivity()->format(\DateTimeInterface::ATOM)
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Password Management
|
||||
|
||||
### Password Change
|
||||
|
||||
```php
|
||||
#[Route(path: '/account/change-password', method: Method::POST)]
|
||||
final readonly class ChangePasswordController
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionManager $sessionManager
|
||||
) {}
|
||||
|
||||
public function changePassword(ChangePasswordRequest $request): JsonResult
|
||||
{
|
||||
$userId = $this->sessionManager->get('user_id');
|
||||
if (!$userId) {
|
||||
return new JsonResult(['error' => 'Not authenticated'], 401);
|
||||
}
|
||||
|
||||
$result = $this->authService->changePassword(
|
||||
userId: $userId,
|
||||
currentPassword: $request->currentPassword,
|
||||
newPassword: $request->newPassword
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
return new JsonResult([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($result->hasValidationErrors()) {
|
||||
return new JsonResult([
|
||||
'success' => false,
|
||||
'validation_errors' => $result->getValidation()->toArray()
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new JsonResult([
|
||||
'success' => false,
|
||||
'error' => $result->getErrorMessage()
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Password Reset Flow
|
||||
|
||||
```php
|
||||
final readonly class PasswordResetService
|
||||
{
|
||||
public function __construct(
|
||||
private PasswordHasher $passwordHasher,
|
||||
private UserRepository $userRepository,
|
||||
private TokenGenerator $tokenGenerator,
|
||||
private EmailService $emailService
|
||||
) {}
|
||||
|
||||
public function initiateReset(string $email): PasswordResetResult
|
||||
{
|
||||
$user = $this->userRepository->findByEmail($email);
|
||||
if (!$user) {
|
||||
// Return success even if user not found (security)
|
||||
return PasswordResetResult::success();
|
||||
}
|
||||
|
||||
// Generate secure reset token
|
||||
$token = $this->tokenGenerator->generateSecureToken(32);
|
||||
$tokenHash = Hash::sha256($token);
|
||||
$expiresAt = new \DateTimeImmutable('+1 hour');
|
||||
|
||||
$resetToken = new PasswordResetToken(
|
||||
hash: $tokenHash,
|
||||
userId: $user->getId(),
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: $expiresAt
|
||||
);
|
||||
|
||||
$this->userRepository->storePasswordResetToken($resetToken);
|
||||
|
||||
// Send reset email
|
||||
$this->emailService->sendPasswordResetEmail($user->getEmail(), $token);
|
||||
|
||||
return PasswordResetResult::success();
|
||||
}
|
||||
|
||||
public function resetPassword(
|
||||
string $token,
|
||||
#[SensitiveParameter] string $newPassword
|
||||
): PasswordResetResult {
|
||||
$tokenHash = Hash::sha256($token);
|
||||
$resetToken = $this->userRepository->findPasswordResetToken($tokenHash);
|
||||
|
||||
if (!$resetToken || $resetToken->isExpired()) {
|
||||
return PasswordResetResult::failed('Invalid or expired reset token');
|
||||
}
|
||||
|
||||
$user = $this->userRepository->findById($resetToken->getUserId());
|
||||
if (!$user) {
|
||||
return PasswordResetResult::failed('User not found');
|
||||
}
|
||||
|
||||
// Validate new password
|
||||
$validation = $this->passwordHasher->validatePasswordStrength($newPassword);
|
||||
if (!$validation->isValid) {
|
||||
return PasswordResetResult::validationFailed($validation);
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
$hashedPassword = $this->passwordHasher->hash($newPassword);
|
||||
|
||||
// Update password and cleanup
|
||||
$this->userRepository->updateUserPassword($user->getId(), $hashedPassword);
|
||||
$this->userRepository->deletePasswordResetToken($tokenHash);
|
||||
$this->userRepository->deleteAllUserSessions($user->getId());
|
||||
|
||||
return PasswordResetResult::success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Security Features
|
||||
|
||||
### Multi-Factor Authentication Setup
|
||||
|
||||
```php
|
||||
final readonly class MfaService
|
||||
{
|
||||
public function __construct(
|
||||
private PasswordHasher $passwordHasher,
|
||||
private TotpService $totpService,
|
||||
private UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function setupTotp(string $userId): MfaSetupResult
|
||||
{
|
||||
$user = $this->userRepository->findById($userId);
|
||||
if (!$user) {
|
||||
return MfaSetupResult::failed('User not found');
|
||||
}
|
||||
|
||||
// Generate TOTP secret
|
||||
$secret = $this->totpService->generateSecret();
|
||||
|
||||
// Create QR code data
|
||||
$qrCodeUri = $this->totpService->getQrCodeUri(
|
||||
secret: $secret,
|
||||
accountName: (string) $user->getEmail(),
|
||||
issuer: 'Your App Name'
|
||||
);
|
||||
|
||||
// Store temporary secret (not activated until verified)
|
||||
$tempSecret = new TempTotpSecret(
|
||||
userId: $userId,
|
||||
secret: $secret,
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: new \DateTimeImmutable('+10 minutes')
|
||||
);
|
||||
|
||||
$this->userRepository->storeTempTotpSecret($tempSecret);
|
||||
|
||||
return MfaSetupResult::success($secret, $qrCodeUri);
|
||||
}
|
||||
|
||||
public function verifyAndActivateTotp(
|
||||
string $userId,
|
||||
string $totpCode
|
||||
): MfaActivationResult {
|
||||
$tempSecret = $this->userRepository->findTempTotpSecret($userId);
|
||||
if (!$tempSecret || $tempSecret->isExpired()) {
|
||||
return MfaActivationResult::failed('Setup expired, please restart');
|
||||
}
|
||||
|
||||
// Verify TOTP code
|
||||
if (!$this->totpService->verify($totpCode, $tempSecret->getSecret())) {
|
||||
return MfaActivationResult::failed('Invalid TOTP code');
|
||||
}
|
||||
|
||||
// Activate TOTP for user
|
||||
$this->userRepository->activateTotpForUser($userId, $tempSecret->getSecret());
|
||||
$this->userRepository->deleteTempTotpSecret($userId);
|
||||
|
||||
// Generate backup codes
|
||||
$backupCodes = $this->generateBackupCodes($userId);
|
||||
|
||||
return MfaActivationResult::success($backupCodes);
|
||||
}
|
||||
|
||||
private function generateBackupCodes(string $userId): array
|
||||
{
|
||||
$codes = [];
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$code = $this->generateReadableCode();
|
||||
$codes[] = $code;
|
||||
|
||||
$backupCode = new MfaBackupCode(
|
||||
userId: $userId,
|
||||
code: Hash::sha256($code),
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
usedAt: null
|
||||
);
|
||||
|
||||
$this->userRepository->storeBackupCode($backupCode);
|
||||
}
|
||||
|
||||
return $codes;
|
||||
}
|
||||
|
||||
private function generateReadableCode(): string
|
||||
{
|
||||
// Generate 8-digit backup code
|
||||
return sprintf('%04d-%04d', random_int(1000, 9999), random_int(1000, 9999));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session Management with Device Tracking
|
||||
|
||||
```php
|
||||
final readonly class DeviceTrackingService
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionRepository $sessionRepository
|
||||
) {}
|
||||
|
||||
public function authenticateWithDeviceTracking(
|
||||
string $identifier,
|
||||
#[SensitiveParameter] string $password,
|
||||
HttpRequest $request
|
||||
): AuthenticationResult {
|
||||
$ipAddress = IpAddress::fromRequest();
|
||||
$userAgent = $request->server->getUserAgent();
|
||||
|
||||
$result = $this->authService->authenticate($identifier, $password, $ipAddress);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
$session = $result->getSession();
|
||||
$user = $result->getUser();
|
||||
|
||||
// Create device fingerprint
|
||||
$deviceInfo = new DeviceInfo(
|
||||
userAgent: $userAgent,
|
||||
ipAddress: $ipAddress,
|
||||
screenResolution: $request->headers->get('X-Screen-Resolution'),
|
||||
timezone: $request->headers->get('X-Timezone'),
|
||||
language: $request->headers->get('Accept-Language')
|
||||
);
|
||||
|
||||
// Check if this is a new device
|
||||
$isNewDevice = !$this->sessionRepository->hasRecentSessionForDevice(
|
||||
userId: $user->getId(),
|
||||
deviceFingerprint: $deviceInfo->getFingerprint(),
|
||||
withinDays: 30
|
||||
);
|
||||
|
||||
if ($isNewDevice) {
|
||||
// Send security notification
|
||||
$this->sendNewDeviceNotification($user, $deviceInfo, $ipAddress);
|
||||
}
|
||||
|
||||
// Update session with device info
|
||||
$this->sessionRepository->updateSessionDeviceInfo($session->getId(), $deviceInfo);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function listActiveSessions(string $userId): array
|
||||
{
|
||||
$sessions = $this->sessionRepository->getActiveSessionsForUser($userId);
|
||||
|
||||
return array_map(function (AuthenticationSession $session) {
|
||||
$deviceInfo = $this->sessionRepository->getSessionDeviceInfo($session->getId());
|
||||
|
||||
return [
|
||||
'id' => $session->getId()->toString(),
|
||||
'created_at' => $session->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'last_activity' => $session->getLastActivity()->format(\DateTimeInterface::ATOM),
|
||||
'ip_address' => (string) $session->getIpAddress(),
|
||||
'device_info' => $deviceInfo?->toArray(),
|
||||
'is_current' => $this->isCurrentSession($session->getId())
|
||||
];
|
||||
}, $sessions);
|
||||
}
|
||||
|
||||
public function revokeSession(string $userId, SessionId $sessionId): bool
|
||||
{
|
||||
// Verify session belongs to user
|
||||
$session = $this->sessionRepository->findSessionById($sessionId);
|
||||
if (!$session || $session->getUserId() !== $userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authService->logout($sessionId);
|
||||
}
|
||||
|
||||
private function sendNewDeviceNotification(
|
||||
User $user,
|
||||
DeviceInfo $deviceInfo,
|
||||
IpAddress $ipAddress
|
||||
): void {
|
||||
// Implementation would send email/SMS notification
|
||||
// about login from new device
|
||||
}
|
||||
|
||||
private function isCurrentSession(SessionId $sessionId): bool
|
||||
{
|
||||
// Check if this is the current session
|
||||
return session_id() === $sessionId->toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limiting & Security
|
||||
|
||||
### Custom Rate Limiting Implementation
|
||||
|
||||
```php
|
||||
final readonly class CustomRateLimitService implements RateLimitService
|
||||
{
|
||||
public function __construct(
|
||||
private Cache $cache,
|
||||
private int $maxAttempts = 5,
|
||||
private int $windowSeconds = 300,
|
||||
private int $lockoutDuration = 900
|
||||
) {}
|
||||
|
||||
public function isRateLimited(IpAddress $ipAddress, string $action): bool
|
||||
{
|
||||
$key = $this->getRateLimitKey($ipAddress, $action);
|
||||
$attempts = $this->cache->get($key, 0);
|
||||
|
||||
return $attempts >= $this->maxAttempts;
|
||||
}
|
||||
|
||||
public function recordAttempt(IpAddress $ipAddress, string $action): void
|
||||
{
|
||||
$key = $this->getRateLimitKey($ipAddress, $action);
|
||||
$attempts = $this->cache->get($key, 0);
|
||||
|
||||
$this->cache->set($key, $attempts + 1, $this->windowSeconds);
|
||||
|
||||
if ($attempts + 1 >= $this->maxAttempts) {
|
||||
$lockoutKey = $this->getLockoutKey($ipAddress, $action);
|
||||
$this->cache->set($lockoutKey, true, $this->lockoutDuration);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearAttempts(IpAddress $ipAddress, string $action): void
|
||||
{
|
||||
$key = $this->getRateLimitKey($ipAddress, $action);
|
||||
$lockoutKey = $this->getLockoutKey($ipAddress, $action);
|
||||
|
||||
$this->cache->forget($key);
|
||||
$this->cache->forget($lockoutKey);
|
||||
}
|
||||
|
||||
public function getRetryAfter(IpAddress $ipAddress, string $action): int
|
||||
{
|
||||
$lockoutKey = $this->getLockoutKey($ipAddress, $action);
|
||||
$lockoutExpiry = $this->cache->get($lockoutKey);
|
||||
|
||||
if (!$lockoutExpiry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return max(0, $lockoutExpiry - time());
|
||||
}
|
||||
|
||||
private function getRateLimitKey(IpAddress $ipAddress, string $action): string
|
||||
{
|
||||
return sprintf('rate_limit:%s:%s', $action, (string) $ipAddress);
|
||||
}
|
||||
|
||||
private function getLockoutKey(IpAddress $ipAddress, string $action): string
|
||||
{
|
||||
return sprintf('lockout:%s:%s', $action, (string) $ipAddress);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Examples
|
||||
|
||||
### Authentication Service Testing
|
||||
|
||||
```php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
|
||||
final class AuthenticationServiceTest extends TestCase
|
||||
{
|
||||
private AuthenticationService $authService;
|
||||
private PasswordHasher $passwordHasher;
|
||||
private MockAuthenticationRepository $repository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->passwordHasher = new PasswordHasher(
|
||||
kdf: new MockKeyDerivationFunction(),
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW // Fast for testing
|
||||
);
|
||||
|
||||
$this->repository = new MockAuthenticationRepository();
|
||||
|
||||
$this->authService = new AuthenticationService(
|
||||
passwordHasher: $this->passwordHasher,
|
||||
sessionIdGenerator: new MockSessionIdGenerator(),
|
||||
repository: $this->repository
|
||||
);
|
||||
}
|
||||
|
||||
public function test_successful_authentication(): void
|
||||
{
|
||||
$password = 'SecurePassword123!';
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
$user = new User(
|
||||
id: 'user-1',
|
||||
email: new Email('user@example.com'),
|
||||
username: 'testuser',
|
||||
hashedPassword: $hashedPassword
|
||||
);
|
||||
|
||||
$this->repository->addUser($user);
|
||||
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: $password,
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
|
||||
$this->assertTrue($result->isSuccess());
|
||||
$this->assertEquals($user->getId(), $result->getUser()->getId());
|
||||
$this->assertInstanceOf(AuthenticationSession::class, $result->getSession());
|
||||
}
|
||||
|
||||
public function test_failed_authentication_with_invalid_password(): void
|
||||
{
|
||||
$hashedPassword = $this->passwordHasher->hash('CorrectPassword123!');
|
||||
|
||||
$user = new User(
|
||||
id: 'user-1',
|
||||
email: new Email('user@example.com'),
|
||||
username: 'testuser',
|
||||
hashedPassword: $hashedPassword
|
||||
);
|
||||
|
||||
$this->repository->addUser($user);
|
||||
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'WrongPassword',
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
|
||||
$this->assertFalse($result->isSuccess());
|
||||
$this->assertEquals('Invalid credentials', $result->getErrorMessage());
|
||||
}
|
||||
|
||||
public function test_account_lockout_after_max_attempts(): void
|
||||
{
|
||||
$hashedPassword = $this->passwordHasher->hash('CorrectPassword123!');
|
||||
|
||||
$user = new User(
|
||||
id: 'user-1',
|
||||
email: new Email('user@example.com'),
|
||||
username: 'testuser',
|
||||
hashedPassword: $hashedPassword
|
||||
);
|
||||
|
||||
$this->repository->addUser($user);
|
||||
|
||||
// Simulate failed attempts
|
||||
for ($i = 0; $i < AuthenticationService::MAX_LOGIN_ATTEMPTS; $i++) {
|
||||
$this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'WrongPassword',
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
}
|
||||
|
||||
// Next attempt should be locked
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'CorrectPassword123!',
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
|
||||
$this->assertTrue($result->isAccountLocked());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $result->getLockoutExpiresAt());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Password Hashing Testing
|
||||
|
||||
```php
|
||||
final class PasswordHasherTest extends TestCase
|
||||
{
|
||||
private PasswordHasher $passwordHasher;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->passwordHasher = new PasswordHasher(
|
||||
kdf: new MockKeyDerivationFunction(),
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW
|
||||
);
|
||||
}
|
||||
|
||||
public function test_password_hashing_and_verification(): void
|
||||
{
|
||||
$password = 'TestPassword123!';
|
||||
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
$this->assertInstanceOf(HashedPassword::class, $hashedPassword);
|
||||
$this->assertEquals('argon2id', $hashedPassword->getAlgorithm());
|
||||
$this->assertTrue($this->passwordHasher->verify($password, $hashedPassword));
|
||||
$this->assertFalse($this->passwordHasher->verify('WrongPassword', $hashedPassword));
|
||||
}
|
||||
|
||||
public function test_password_strength_validation(): void
|
||||
{
|
||||
$weakPassword = 'weak';
|
||||
$strongPassword = 'StrongPassword123!@#';
|
||||
|
||||
$weakValidation = $this->passwordHasher->validatePasswordStrength($weakPassword);
|
||||
$strongValidation = $this->passwordHasher->validatePasswordStrength($strongPassword);
|
||||
|
||||
$this->assertFalse($weakValidation->isValid);
|
||||
$this->assertNotEmpty($weakValidation->errors);
|
||||
$this->assertEquals(PasswordStrength::WEAK, $weakValidation->strength);
|
||||
|
||||
$this->assertTrue($strongValidation->isValid);
|
||||
$this->assertEmpty($strongValidation->errors);
|
||||
$this->assertContains($strongValidation->strength, [
|
||||
PasswordStrength::STRONG,
|
||||
PasswordStrength::VERY_STRONG
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_secure_password_generation(): void
|
||||
{
|
||||
$password = $this->passwordHasher->generateSecurePassword(16);
|
||||
|
||||
$this->assertEquals(16, strlen($password));
|
||||
|
||||
$validation = $this->passwordHasher->validatePasswordStrength($password);
|
||||
$this->assertTrue($validation->isValid);
|
||||
$this->assertGreaterThanOrEqual(70, $validation->strengthScore);
|
||||
}
|
||||
}
|
||||
310
docs/components/auth/index.md
Normal file
310
docs/components/auth/index.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Auth Module
|
||||
|
||||
**Sichere Authentifizierungs- und Autorisierungskomponenten** für das Custom PHP Framework.
|
||||
|
||||
## Überblick
|
||||
|
||||
Das Auth Module bietet umfassende Sicherheitsfeatures für Benutzerauthentifizierung, Passwort-Management und Session-Verwaltung. Es integriert sich nahtlos mit dem Cryptography Module für maximale Sicherheit.
|
||||
|
||||
## Kernkomponenten
|
||||
|
||||
### PasswordHasher Service
|
||||
Sichere Passwort-Hashing und -Verifizierung mit automatischem Rehashing.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordHasher;
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
|
||||
$passwordHasher = new PasswordHasher(
|
||||
kdf: $keyDerivationFunction,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD
|
||||
);
|
||||
|
||||
// Passwort hashen
|
||||
$hashedPassword = $passwordHasher->hash('userPassword123');
|
||||
|
||||
// Passwort verifizieren
|
||||
$isValid = $passwordHasher->verify('userPassword123', $hashedPassword);
|
||||
|
||||
// Passwort-Stärke validieren
|
||||
$validation = $passwordHasher->validatePasswordStrength('userPassword123');
|
||||
if (!$validation->isValid) {
|
||||
echo implode(', ', $validation->errors);
|
||||
}
|
||||
```
|
||||
|
||||
### HashedPassword Value Object
|
||||
Immutables Value Object für gehashte Passwörter mit Metadaten.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\HashedPassword;
|
||||
use App\Framework\Auth\PasswordStrength;
|
||||
|
||||
// Aus DerivedKey erstellen
|
||||
$hashedPassword = HashedPassword::fromDerivedKey($derivedKey);
|
||||
|
||||
// Eigenschaften abrufen
|
||||
$algorithm = $hashedPassword->getAlgorithm(); // 'argon2id'
|
||||
$parameters = $hashedPassword->getParameters(); // ['memory_cost' => 65536, ...]
|
||||
$strength = $hashedPassword->getStrength(); // PasswordStrength::STRONG
|
||||
$createdAt = $hashedPassword->getCreatedAt(); // DateTimeImmutable
|
||||
|
||||
// Rehashing-Prüfung
|
||||
$needsRehash = $hashedPassword->needsRehash('argon2id', [
|
||||
'memory_cost' => 131072, // Höhere Sicherheitsanforderungen
|
||||
'time_cost' => 6
|
||||
]);
|
||||
|
||||
// Sicherheits-Bewertung
|
||||
$assessment = $hashedPassword->assessSecurity();
|
||||
echo $assessment->getSummary(); // "Strong security with Argon2ID (2024 standards)"
|
||||
```
|
||||
|
||||
### AuthenticationService
|
||||
Zentrale Authentifizierungslogik mit erweiterten Sicherheitsfeatures.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
use App\Framework\Http\IpAddress;
|
||||
|
||||
$authService = new AuthenticationService(
|
||||
passwordHasher: $passwordHasher,
|
||||
sessionIdGenerator: $sessionIdGenerator,
|
||||
repository: $authRepository
|
||||
);
|
||||
|
||||
// Benutzer authentifizieren
|
||||
$result = $authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'userPassword123',
|
||||
ipAddress: IpAddress::from('192.168.1.1'),
|
||||
remember: true
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
$user = $result->getUser();
|
||||
$session = $result->getSession();
|
||||
$rememberToken = $result->getRememberToken(); // nullable
|
||||
}
|
||||
|
||||
// Mit Session authentifizieren
|
||||
$result = $authService->authenticateWithSession(
|
||||
$sessionId,
|
||||
IpAddress::from('192.168.1.1')
|
||||
);
|
||||
|
||||
// Mit Remember Token authentifizieren
|
||||
$result = $authService->authenticateWithRememberToken(
|
||||
$tokenValue,
|
||||
IpAddress::from('192.168.1.1')
|
||||
);
|
||||
```
|
||||
|
||||
### PasswordValidationResult Value Object
|
||||
Detaillierte Passwort-Validierungsergebnisse.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordValidationResult;
|
||||
|
||||
$validation = $passwordHasher->validatePasswordStrength('weakpass');
|
||||
|
||||
// Validierungsstatus prüfen
|
||||
$isValid = $validation->isValid; // false
|
||||
$errors = $validation->errors; // ['Password must be at least 8 characters long']
|
||||
$warnings = $validation->warnings; // ['Consider using more character types']
|
||||
$score = $validation->strengthScore; // 45
|
||||
$strength = $validation->strength; // PasswordStrength::WEAK
|
||||
|
||||
// Sicherheitslevel prüfen
|
||||
$meetsMinimum = $validation->meetsMinimumRequirements(); // false
|
||||
$isRecommended = $validation->isRecommended(); // false
|
||||
|
||||
// Zusammenfassung
|
||||
echo $validation->getSummary();
|
||||
// "Password does not meet requirements: Password must be at least 8 characters long"
|
||||
|
||||
// API-Response Format
|
||||
$apiResponse = $validation->toArray();
|
||||
```
|
||||
|
||||
## Sicherheitsfeatures
|
||||
|
||||
### Rate Limiting & Account Lockout
|
||||
Schutz vor Brute-Force-Angriffen durch intelligente Rate-Limitierung.
|
||||
|
||||
```php
|
||||
// Automatisches Rate Limiting in AuthenticationService
|
||||
const MAX_LOGIN_ATTEMPTS = 5;
|
||||
const LOCKOUT_DURATION = 900; // 15 Minuten
|
||||
const RATE_LIMIT_WINDOW = 300; // 5 Minuten
|
||||
|
||||
// Rate Limiting wird automatisch angewendet
|
||||
$result = $authService->authenticate($email, $password, $ipAddress);
|
||||
|
||||
if ($result->isRateLimited()) {
|
||||
$retryAfter = $result->getRetryAfter();
|
||||
echo "Rate limit exceeded. Retry after {$retryAfter} seconds.";
|
||||
}
|
||||
|
||||
if ($result->isAccountLocked()) {
|
||||
$expiresAt = $result->getLockoutExpiresAt();
|
||||
echo "Account locked until {$expiresAt->format('Y-m-d H:i:s')}";
|
||||
}
|
||||
```
|
||||
|
||||
### Session-Sicherheit
|
||||
Sichere Session-Verwaltung mit IP-Tracking und automatischer Rotation.
|
||||
|
||||
```php
|
||||
// Sessions werden automatisch erstellt und verwaltet
|
||||
const SESSION_TIMEOUT = 3600; // 1 Stunde
|
||||
const REMEMBER_TOKEN_LENGTH = 32; // 256-bit Token
|
||||
|
||||
// Session-Features:
|
||||
// - Automatische IP-Konsistenz-Prüfung
|
||||
// - Session-Timeout-Management
|
||||
// - Aktivitäts-Tracking
|
||||
// - Sichere Session-IDs über SessionIdGenerator
|
||||
```
|
||||
|
||||
### Token-Hashing mit Hash Value Object
|
||||
Sichere Token-Behandlung mit typisierter Hash-Verwaltung.
|
||||
|
||||
```php
|
||||
// Remember Tokens werden sicher gehasht
|
||||
private function hashToken(string $token): Hash
|
||||
{
|
||||
return Hash::sha256($token);
|
||||
}
|
||||
|
||||
// Vorteile:
|
||||
// - Typsicherheit für Hash-Werte
|
||||
// - Explizite Algorithmus-Angabe (SHA-256)
|
||||
// - Timing-safe Vergleiche mit hash_equals()
|
||||
// - Framework-konsistente Hash-Behandlung
|
||||
// - Automatische Hash-Validierung
|
||||
```
|
||||
|
||||
### Passwort-Sicherheit
|
||||
Umfassende Passwort-Validierung und -Bewertung.
|
||||
|
||||
```php
|
||||
// Automatische Passwort-Stärke-Bewertung
|
||||
$validation = $passwordHasher->validatePasswordStrength($password);
|
||||
|
||||
// Validierungsregeln:
|
||||
// - Minimale Länge (8+ Zeichen)
|
||||
// - Komplexitätsanforderungen (2+ Zeichentypen)
|
||||
// - Häufige Muster-Erkennung
|
||||
// - Sequenzielle Zeichen-Prüfung
|
||||
// - Exzessive Wiederholungen
|
||||
|
||||
// Sichere Passwort-Generierung
|
||||
$securePassword = $passwordHasher->generateSecurePassword(
|
||||
length: 16,
|
||||
includeUppercase: true,
|
||||
includeLowercase: true,
|
||||
includeNumbers: true,
|
||||
includeSpecialChars: true,
|
||||
excludeChars: '0O1l' // Mehrdeutige Zeichen ausschließen
|
||||
);
|
||||
```
|
||||
|
||||
## Integration mit Framework
|
||||
|
||||
### Abhängigkeiten
|
||||
Das Auth Module nutzt vorhandene Framework-Komponenten:
|
||||
|
||||
- **Cryptography Module**: Für sichere Key Derivation Functions
|
||||
- **SessionId/SessionIdGenerator**: Für Session-Management
|
||||
- **IpAddress Value Object**: Für IP-basierte Sicherheitsfeatures
|
||||
- **Hash Value Object**: Für typisierte Hash-Operationen
|
||||
|
||||
### Repository Pattern
|
||||
Flexible Datenpersistierung durch Repository-Abstraktion.
|
||||
|
||||
```php
|
||||
interface AuthenticationRepository
|
||||
{
|
||||
public function findUserByIdentifier(string $identifier): ?User;
|
||||
public function findUserById(string $userId): ?User;
|
||||
public function updateUserPassword(string $userId, HashedPassword $password): bool;
|
||||
|
||||
public function storeSession(AuthenticationSession $session): void;
|
||||
public function findSessionById(SessionId $sessionId): ?AuthenticationSession;
|
||||
public function updateSessionActivity(SessionId $sessionId, ?IpAddress $ipAddress): void;
|
||||
public function deleteSession(SessionId $sessionId): bool;
|
||||
public function deleteAllUserSessions(string $userId): void;
|
||||
|
||||
public function storeRememberToken(RememberToken $token): void;
|
||||
public function findRememberToken(Hash $tokenHash): ?RememberToken;
|
||||
public function deleteRememberToken(Hash $tokenHash): bool;
|
||||
public function deleteAllUserRememberTokens(string $userId): void;
|
||||
|
||||
public function getFailedLoginAttempts(string $userId): int;
|
||||
public function incrementFailedLoginAttempts(string $userId): void;
|
||||
public function clearFailedLoginAttempts(string $userId): void;
|
||||
public function getLastFailedAttemptTime(string $userId): ?\DateTimeImmutable;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Sichere Implementation
|
||||
```php
|
||||
// ✅ Sensitive Parameter verwenden
|
||||
public function authenticate(
|
||||
string $identifier,
|
||||
#[SensitiveParameter] string $password,
|
||||
?IpAddress $ipAddress = null
|
||||
): AuthenticationResult
|
||||
|
||||
// ✅ Automatisches Rehashing
|
||||
if ($this->passwordHasher->needsRehash($user->getHashedPassword())) {
|
||||
$newHash = $this->passwordHasher->hash($password);
|
||||
$this->repository->updateUserPassword($user->getId(), $newHash);
|
||||
}
|
||||
|
||||
// ✅ Timing-safe Token-Vergleiche
|
||||
$tokenHash = $this->hashToken($tokenValue);
|
||||
$rememberToken = $this->repository->findRememberToken($tokenHash);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```php
|
||||
// ✅ Generische Fehlermeldungen für Sicherheit
|
||||
if (!$user || !$this->passwordHasher->verify($password, $user->getHashedPassword())) {
|
||||
return AuthenticationResult::failed('Invalid credentials');
|
||||
}
|
||||
|
||||
// ✅ Security Event Logging
|
||||
$this->recordSecurityEvent('authentication_failed', [
|
||||
'identifier' => $identifier,
|
||||
'ip_address' => $ipAddress ? (string) $ipAddress : null,
|
||||
'reason' => $reason
|
||||
]);
|
||||
```
|
||||
|
||||
### Performance Optimierung
|
||||
```php
|
||||
// ✅ Konfigurierbare Sicherheitslevel
|
||||
$passwordHasher = new PasswordHasher(
|
||||
kdf: $keyDerivationFunction,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD // vs HIGH für höhere Sicherheit
|
||||
);
|
||||
|
||||
// ✅ Bulk-Operationen für bessere Performance
|
||||
$this->repository->deleteAllUserSessions($userId);
|
||||
$this->repository->deleteAllUserRememberTokens($userId);
|
||||
```
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- [ ] AuthenticationRepository Interface implementieren
|
||||
- [ ] Authentication Exceptions definieren
|
||||
- [ ] Unit Tests für alle Komponenten schreiben
|
||||
- [ ] Rate Limiting Service implementieren
|
||||
- [ ] Integration Tests für Authentication Flow
|
||||
- [ ] Performance Benchmarks für Passwort-Hashing
|
||||
904
docs/components/auth/security.md
Normal file
904
docs/components/auth/security.md
Normal file
@@ -0,0 +1,904 @@
|
||||
# Auth Module Security Guidelines
|
||||
|
||||
**Sicherheitsrichtlinien und Best Practices** für das Auth Module des Custom PHP Frameworks.
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Defense in Depth Strategy
|
||||
|
||||
Das Auth Module implementiert mehrschichtige Sicherheitsmaßnahmen:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Application Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ • Rate Limiting & Account Lockout │
|
||||
│ • Session Security & IP Tracking │
|
||||
│ • Multi-Factor Authentication │
|
||||
│ • Password Strength Validation │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Framework Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ • Secure Password Hashing (Argon2ID) │
|
||||
│ • Timing-Safe Comparisons │
|
||||
│ • Cryptographically Secure Random Generation │
|
||||
│ • Hash Value Object with Algorithm Validation │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Infrastructure Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ • HTTPS Enforcement │
|
||||
│ • Secure Headers (HSTS, CSP) │
|
||||
│ • Database Security & Prepared Statements │
|
||||
│ • Logging & Monitoring │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Password Security
|
||||
|
||||
### Secure Password Hashing
|
||||
|
||||
```php
|
||||
// ✅ Recommended: Argon2ID with high memory cost
|
||||
$passwordHasher = new PasswordHasher(
|
||||
kdf: $keyDerivationFunction,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_HIGH // Production
|
||||
);
|
||||
|
||||
// Security parameters for different levels:
|
||||
// LEVEL_HIGH: 128 MB memory, 6 iterations, 4 threads
|
||||
// LEVEL_STANDARD: 64 MB memory, 4 iterations, 3 threads
|
||||
// LEVEL_LOW: 32 MB memory, 2 iterations, 2 threads
|
||||
|
||||
// ❌ Avoid: Weak algorithms or low security levels in production
|
||||
$weakHasher = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'pbkdf2-sha256', // Less secure than Argon2ID
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW
|
||||
);
|
||||
```
|
||||
|
||||
### Password Policy Enforcement
|
||||
|
||||
```php
|
||||
final readonly class PasswordPolicy
|
||||
{
|
||||
public const int MIN_LENGTH = 12; // Increased from 8
|
||||
public const int MIN_COMPLEXITY_TYPES = 3; // Upper, lower, numbers, special
|
||||
public const int MIN_STRENGTH_SCORE = 70; // High security threshold
|
||||
|
||||
public function validatePassword(string $password): PasswordPolicyResult
|
||||
{
|
||||
$violations = [];
|
||||
|
||||
// Length requirement
|
||||
if (mb_strlen($password) < self::MIN_LENGTH) {
|
||||
$violations[] = "Password must be at least " . self::MIN_LENGTH . " characters";
|
||||
}
|
||||
|
||||
// Complexity requirements
|
||||
$complexityScore = $this->calculateComplexity($password);
|
||||
if ($complexityScore < self::MIN_COMPLEXITY_TYPES) {
|
||||
$violations[] = "Password must include at least " . self::MIN_COMPLEXITY_TYPES . " character types";
|
||||
}
|
||||
|
||||
// Entropy check
|
||||
if ($this->calculateEntropy($password) < 50) {
|
||||
$violations[] = "Password lacks sufficient entropy";
|
||||
}
|
||||
|
||||
// Dictionary check
|
||||
if ($this->isCommonPassword($password)) {
|
||||
$violations[] = "Password is too common";
|
||||
}
|
||||
|
||||
// Personal information check
|
||||
if ($this->containsPersonalInfo($password)) {
|
||||
$violations[] = "Password should not contain personal information";
|
||||
}
|
||||
|
||||
return new PasswordPolicyResult(
|
||||
isValid: empty($violations),
|
||||
violations: $violations,
|
||||
strengthScore: $this->calculateStrengthScore($password)
|
||||
);
|
||||
}
|
||||
|
||||
private function calculateEntropy(string $password): float
|
||||
{
|
||||
$length = strlen($password);
|
||||
$charsetSize = 0;
|
||||
|
||||
if (preg_match('/[a-z]/', $password)) $charsetSize += 26;
|
||||
if (preg_match('/[A-Z]/', $password)) $charsetSize += 26;
|
||||
if (preg_match('/[0-9]/', $password)) $charsetSize += 10;
|
||||
if (preg_match('/[^A-Za-z0-9]/', $password)) $charsetSize += 32;
|
||||
|
||||
return $length * log($charsetSize, 2);
|
||||
}
|
||||
|
||||
private function isCommonPassword(string $password): bool
|
||||
{
|
||||
// Check against common password lists (e.g., Have I Been Pwned)
|
||||
$commonPasswords = [
|
||||
'password', '123456', 'password123', 'admin', 'qwerty',
|
||||
'letmein', 'welcome', 'monkey', 'dragon', '123456789'
|
||||
];
|
||||
|
||||
return in_array(strtolower($password), $commonPasswords, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic Password Rehashing
|
||||
|
||||
```php
|
||||
// ✅ Implement automatic rehashing on authentication
|
||||
public function authenticate(string $identifier, string $password): AuthenticationResult
|
||||
{
|
||||
$user = $this->repository->findByIdentifier($identifier);
|
||||
if (!$user || !$this->passwordHasher->verify($password, $user->getHashedPassword())) {
|
||||
return AuthenticationResult::failed('Invalid credentials');
|
||||
}
|
||||
|
||||
// Critical: Check for rehashing need
|
||||
if ($this->passwordHasher->needsRehash($user->getHashedPassword())) {
|
||||
$newHash = $this->passwordHasher->hash($password);
|
||||
$this->repository->updateUserPassword($user->getId(), $newHash);
|
||||
|
||||
$this->logger->info('Password automatically rehashed', [
|
||||
'user_id' => $user->getId(),
|
||||
'old_algorithm' => $user->getHashedPassword()->getAlgorithm(),
|
||||
'new_algorithm' => $newHash->getAlgorithm()
|
||||
]);
|
||||
}
|
||||
|
||||
return AuthenticationResult::success($user);
|
||||
}
|
||||
```
|
||||
|
||||
## Session Security
|
||||
|
||||
### Secure Session Management
|
||||
|
||||
```php
|
||||
final readonly class SecureSessionManager
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionSecurity $sessionSecurity
|
||||
) {}
|
||||
|
||||
public function createSecureSession(
|
||||
User $user,
|
||||
IpAddress $ipAddress,
|
||||
UserAgent $userAgent
|
||||
): AuthenticationSession {
|
||||
// Generate cryptographically secure session ID
|
||||
$sessionId = $this->generateSecureSessionId();
|
||||
|
||||
// Create session with security attributes
|
||||
$session = new AuthenticationSession(
|
||||
id: $sessionId,
|
||||
userId: $user->getId(),
|
||||
ipAddress: $ipAddress,
|
||||
userAgent: $userAgent,
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: new \DateTimeImmutable('+1 hour'),
|
||||
lastActivity: new \DateTimeImmutable(),
|
||||
securityAttributes: new SessionSecurityAttributes(
|
||||
isSecure: true,
|
||||
isHttpOnly: true,
|
||||
sameSite: 'Strict',
|
||||
fingerprint: $this->generateFingerprint($ipAddress, $userAgent)
|
||||
)
|
||||
);
|
||||
|
||||
// Set secure session cookie
|
||||
$this->setSecureSessionCookie($sessionId, $session->getExpiresAt());
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
private function generateSecureSessionId(): SessionId
|
||||
{
|
||||
// Use framework's SessionIdGenerator for consistency
|
||||
return $this->sessionIdGenerator->generate();
|
||||
}
|
||||
|
||||
private function setSecureSessionCookie(SessionId $sessionId, \DateTimeImmutable $expiresAt): void
|
||||
{
|
||||
setcookie('session_id', $sessionId->toString(), [
|
||||
'expires' => $expiresAt->getTimestamp(),
|
||||
'path' => '/',
|
||||
'domain' => '', // Let browser determine
|
||||
'secure' => true, // HTTPS only
|
||||
'httponly' => true, // No JavaScript access
|
||||
'samesite' => 'Strict' // CSRF protection
|
||||
]);
|
||||
}
|
||||
|
||||
private function generateFingerprint(IpAddress $ipAddress, UserAgent $userAgent): string
|
||||
{
|
||||
return Hash::sha256(
|
||||
$ipAddress->toString() . '|' .
|
||||
$userAgent->toString() . '|' .
|
||||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''
|
||||
)->toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session Hijacking Protection
|
||||
|
||||
```php
|
||||
final readonly class SessionHijackingProtection
|
||||
{
|
||||
public function validateSession(
|
||||
SessionId $sessionId,
|
||||
IpAddress $currentIp,
|
||||
UserAgent $currentUserAgent
|
||||
): SessionValidationResult {
|
||||
$session = $this->repository->findSessionById($sessionId);
|
||||
if (!$session) {
|
||||
return SessionValidationResult::invalid('Session not found');
|
||||
}
|
||||
|
||||
// Check session expiration
|
||||
if ($session->isExpired()) {
|
||||
$this->repository->deleteSession($sessionId);
|
||||
return SessionValidationResult::expired();
|
||||
}
|
||||
|
||||
// IP address consistency check (configurable)
|
||||
if ($this->config->checkIpConsistency) {
|
||||
if (!$session->getIpAddress()->equals($currentIp)) {
|
||||
$this->logSecurityEvent('session_ip_mismatch', [
|
||||
'session_id' => $sessionId->toString(),
|
||||
'original_ip' => (string) $session->getIpAddress(),
|
||||
'current_ip' => (string) $currentIp
|
||||
]);
|
||||
|
||||
// Suspicious activity - invalidate session
|
||||
$this->repository->deleteSession($sessionId);
|
||||
return SessionValidationResult::suspicious('IP address mismatch');
|
||||
}
|
||||
}
|
||||
|
||||
// User Agent consistency (less strict)
|
||||
if (!$this->isUserAgentConsistent($session->getUserAgent(), $currentUserAgent)) {
|
||||
$this->logSecurityEvent('session_user_agent_change', [
|
||||
'session_id' => $sessionId->toString(),
|
||||
'original_ua' => (string) $session->getUserAgent(),
|
||||
'current_ua' => (string) $currentUserAgent
|
||||
]);
|
||||
}
|
||||
|
||||
// Update session activity
|
||||
$this->repository->updateSessionActivity($sessionId, $currentIp);
|
||||
|
||||
return SessionValidationResult::valid($session);
|
||||
}
|
||||
|
||||
private function isUserAgentConsistent(UserAgent $original, UserAgent $current): bool
|
||||
{
|
||||
// Allow minor version changes but detect major browser changes
|
||||
$originalBrowser = $this->extractBrowser($original);
|
||||
$currentBrowser = $this->extractBrowser($current);
|
||||
|
||||
return $originalBrowser === $currentBrowser;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Token Security
|
||||
|
||||
### Remember Token Security with Hash Value Object
|
||||
|
||||
```php
|
||||
final readonly class RememberTokenSecurity
|
||||
{
|
||||
public function createRememberToken(string $userId): RememberToken
|
||||
{
|
||||
// Generate cryptographically secure token
|
||||
$tokenValue = bin2hex(random_bytes(32)); // 256-bit token
|
||||
|
||||
// Hash token using Hash Value Object for type safety
|
||||
$tokenHash = Hash::sha256($tokenValue);
|
||||
|
||||
$rememberToken = new RememberToken(
|
||||
hash: $tokenHash,
|
||||
userId: $userId,
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: new \DateTimeImmutable('+30 days'),
|
||||
lastUsed: null
|
||||
);
|
||||
|
||||
$this->repository->storeRememberToken($rememberToken);
|
||||
|
||||
// Return token with plain text value (only shown once)
|
||||
return $rememberToken->withPlainTextValue($tokenValue);
|
||||
}
|
||||
|
||||
public function validateRememberToken(string $tokenValue): RememberTokenValidation
|
||||
{
|
||||
// Hash provided token for comparison
|
||||
$tokenHash = Hash::sha256($tokenValue);
|
||||
|
||||
$rememberToken = $this->repository->findRememberToken($tokenHash);
|
||||
if (!$rememberToken) {
|
||||
$this->logSecurityEvent('invalid_remember_token', [
|
||||
'token_hash' => $tokenHash->toShort(8) // Only log first 8 chars
|
||||
]);
|
||||
|
||||
return RememberTokenValidation::invalid();
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if ($rememberToken->isExpired()) {
|
||||
$this->repository->deleteRememberToken($tokenHash);
|
||||
return RememberTokenValidation::expired();
|
||||
}
|
||||
|
||||
// Update last used timestamp
|
||||
$this->repository->updateRememberTokenUsage($tokenHash);
|
||||
|
||||
return RememberTokenValidation::valid($rememberToken);
|
||||
}
|
||||
|
||||
public function rotateRememberToken(Hash $oldTokenHash): RememberToken
|
||||
{
|
||||
$oldToken = $this->repository->findRememberToken($oldTokenHash);
|
||||
if (!$oldToken) {
|
||||
throw new SecurityException('Cannot rotate non-existent token');
|
||||
}
|
||||
|
||||
// Create new token
|
||||
$newToken = $this->createRememberToken($oldToken->getUserId());
|
||||
|
||||
// Delete old token
|
||||
$this->repository->deleteRememberToken($oldTokenHash);
|
||||
|
||||
$this->logSecurityEvent('remember_token_rotated', [
|
||||
'user_id' => $oldToken->getUserId(),
|
||||
'old_token_created' => $oldToken->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return $newToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Benefits of using Hash Value Object:
|
||||
// ✅ Type safety - tokens can't be confused with other strings
|
||||
// ✅ Algorithm specification - explicit SHA-256 usage
|
||||
// ✅ Timing-safe comparison - uses hash_equals() internally
|
||||
// ✅ Validation - ensures proper hash format and length
|
||||
// ✅ Framework consistency - follows value object patterns
|
||||
```
|
||||
|
||||
## Rate Limiting & Brute Force Protection
|
||||
|
||||
### Advanced Rate Limiting
|
||||
|
||||
```php
|
||||
final readonly class AdvancedRateLimiter implements RateLimitService
|
||||
{
|
||||
public function __construct(
|
||||
private Cache $cache,
|
||||
private Logger $logger,
|
||||
private RateLimitConfig $config
|
||||
) {}
|
||||
|
||||
public function checkRateLimit(
|
||||
IpAddress $ipAddress,
|
||||
string $action,
|
||||
string $identifier = null
|
||||
): RateLimitResult {
|
||||
// Multiple rate limiting strategies
|
||||
$checks = [
|
||||
$this->checkIpBasedLimit($ipAddress, $action),
|
||||
$this->checkIdentifierBasedLimit($identifier, $action),
|
||||
$this->checkGlobalLimit($action),
|
||||
$this->checkAdaptiveLimit($ipAddress, $action)
|
||||
];
|
||||
|
||||
foreach ($checks as $check) {
|
||||
if ($check->isLimited()) {
|
||||
$this->logRateLimitViolation($ipAddress, $action, $check);
|
||||
return $check;
|
||||
}
|
||||
}
|
||||
|
||||
// Record successful attempt
|
||||
$this->recordAttempt($ipAddress, $action, $identifier);
|
||||
|
||||
return RateLimitResult::allowed();
|
||||
}
|
||||
|
||||
private function checkAdaptiveLimit(IpAddress $ipAddress, string $action): RateLimitResult
|
||||
{
|
||||
$suspiciousScore = $this->calculateSuspiciousScore($ipAddress);
|
||||
|
||||
// Adaptive limits based on suspicious activity
|
||||
$maxAttempts = match (true) {
|
||||
$suspiciousScore > 80 => 1, // Highly suspicious
|
||||
$suspiciousScore > 60 => 2, // Suspicious
|
||||
$suspiciousScore > 40 => 3, // Moderately suspicious
|
||||
default => 5 // Normal
|
||||
};
|
||||
|
||||
$key = "adaptive_limit:{$action}:" . $ipAddress->toString();
|
||||
$attempts = $this->cache->get($key, 0);
|
||||
|
||||
if ($attempts >= $maxAttempts) {
|
||||
return RateLimitResult::limited(
|
||||
reason: 'Adaptive rate limit exceeded',
|
||||
retryAfter: 900,
|
||||
metadata: ['suspicious_score' => $suspiciousScore]
|
||||
);
|
||||
}
|
||||
|
||||
return RateLimitResult::allowed();
|
||||
}
|
||||
|
||||
private function calculateSuspiciousScore(IpAddress $ipAddress): int
|
||||
{
|
||||
$score = 0;
|
||||
|
||||
// Check for various suspicious indicators
|
||||
if ($this->isFromTorNetwork($ipAddress)) $score += 30;
|
||||
if ($this->isFromVpnProvider($ipAddress)) $score += 15;
|
||||
if ($this->hasRecentFailures($ipAddress)) $score += 20;
|
||||
if ($this->isFromUncommonGeolocation($ipAddress)) $score += 10;
|
||||
if ($this->hasRapidRequests($ipAddress)) $score += 25;
|
||||
|
||||
return min(100, $score);
|
||||
}
|
||||
|
||||
private function logRateLimitViolation(
|
||||
IpAddress $ipAddress,
|
||||
string $action,
|
||||
RateLimitResult $result
|
||||
): void {
|
||||
$this->logger->warning('Rate limit violation', [
|
||||
'ip_address' => (string) $ipAddress,
|
||||
'action' => $action,
|
||||
'reason' => $result->getReason(),
|
||||
'retry_after' => $result->getRetryAfter(),
|
||||
'metadata' => $result->getMetadata()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Account Lockout Protection
|
||||
|
||||
```php
|
||||
final readonly class AccountLockoutService
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $repository,
|
||||
private Logger $logger,
|
||||
private NotificationService $notifications
|
||||
) {}
|
||||
|
||||
public function handleFailedLogin(string $identifier, IpAddress $ipAddress): void
|
||||
{
|
||||
$user = $this->repository->findByIdentifier($identifier);
|
||||
if (!$user) {
|
||||
return; // Don't reveal if user exists
|
||||
}
|
||||
|
||||
$attempts = $this->repository->incrementFailedAttempts($user->getId());
|
||||
|
||||
// Progressive lockout strategy
|
||||
$lockoutDuration = $this->calculateLockoutDuration($attempts);
|
||||
|
||||
if ($attempts >= 3) {
|
||||
$this->repository->lockAccount($user->getId(), $lockoutDuration);
|
||||
|
||||
$this->logger->warning('Account locked due to failed attempts', [
|
||||
'user_id' => $user->getId(),
|
||||
'attempts' => $attempts,
|
||||
'lockout_duration' => $lockoutDuration,
|
||||
'ip_address' => (string) $ipAddress
|
||||
]);
|
||||
|
||||
// Notify user of lockout (rate limited to prevent abuse)
|
||||
$this->sendLockoutNotification($user, $attempts, $lockoutDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateLockoutDuration(int $attempts): int
|
||||
{
|
||||
// Exponential backoff with jitter
|
||||
return match (true) {
|
||||
$attempts >= 10 => 3600 + random_int(0, 1800), // 1-2.5 hours
|
||||
$attempts >= 7 => 1800 + random_int(0, 900), // 30-45 minutes
|
||||
$attempts >= 5 => 900 + random_int(0, 300), // 15-20 minutes
|
||||
$attempts >= 3 => 300 + random_int(0, 120), // 5-7 minutes
|
||||
default => 0
|
||||
};
|
||||
}
|
||||
|
||||
private function sendLockoutNotification(
|
||||
User $user,
|
||||
int $attempts,
|
||||
int $lockoutDuration
|
||||
): void {
|
||||
// Rate limit notifications to prevent spam
|
||||
$notificationKey = "lockout_notification:" . $user->getId();
|
||||
if ($this->cache->has($notificationKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cache->put($notificationKey, true, 300); // 5 minutes
|
||||
|
||||
$this->notifications->sendSecurityAlert($user->getEmail(), [
|
||||
'type' => 'account_lockout',
|
||||
'attempts' => $attempts,
|
||||
'lockout_duration_minutes' => ceil($lockoutDuration / 60),
|
||||
'timestamp' => new \DateTimeImmutable()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Factor Authentication Security
|
||||
|
||||
### TOTP Implementation with Security Features
|
||||
|
||||
```php
|
||||
final readonly class SecureTotpService
|
||||
{
|
||||
private const int SECRET_LENGTH = 32; // 160-bit secret
|
||||
private const int WINDOW_SIZE = 1; // Allow 1 time step tolerance
|
||||
private const int TIME_STEP = 30; // 30-second time steps
|
||||
|
||||
public function generateSecret(): string
|
||||
{
|
||||
return Base32::encode(random_bytes(self::SECRET_LENGTH));
|
||||
}
|
||||
|
||||
public function verify(string $code, string $secret, ?int $timestamp = null): bool
|
||||
{
|
||||
$timestamp = $timestamp ?? time();
|
||||
$timeStep = intval($timestamp / self::TIME_STEP);
|
||||
|
||||
// Check current and adjacent time windows to account for clock drift
|
||||
for ($i = -self::WINDOW_SIZE; $i <= self::WINDOW_SIZE; $i++) {
|
||||
$calculatedCode = $this->calculateTotp($secret, $timeStep + $i);
|
||||
|
||||
if (hash_equals($code, $calculatedCode)) {
|
||||
// Prevent replay attacks by storing used codes
|
||||
$this->markCodeAsUsed($secret, $timeStep + $i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function calculateTotp(string $secret, int $timeStep): string
|
||||
{
|
||||
$binarySecret = Base32::decode($secret);
|
||||
$timeBytes = pack('N*', 0) . pack('N*', $timeStep);
|
||||
|
||||
$hash = hash_hmac('sha1', $timeBytes, $binarySecret, true);
|
||||
$offset = ord($hash[19]) & 0xf;
|
||||
|
||||
$code = (
|
||||
((ord($hash[$offset]) & 0x7f) << 24) |
|
||||
((ord($hash[$offset + 1]) & 0xff) << 16) |
|
||||
((ord($hash[$offset + 2]) & 0xff) << 8) |
|
||||
(ord($hash[$offset + 3]) & 0xff)
|
||||
) % 1000000;
|
||||
|
||||
return str_pad((string) $code, 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
private function markCodeAsUsed(string $secret, int $timeStep): void
|
||||
{
|
||||
// Prevent replay attacks
|
||||
$key = 'totp_used:' . Hash::sha256($secret . ':' . $timeStep)->toShort(16);
|
||||
$this->cache->put($key, true, self::TIME_STEP * 2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Backup Code Security
|
||||
|
||||
```php
|
||||
final readonly class BackupCodeService
|
||||
{
|
||||
private const int CODE_LENGTH = 8;
|
||||
private const int CODE_COUNT = 8;
|
||||
|
||||
public function generateBackupCodes(string $userId): array
|
||||
{
|
||||
$codes = [];
|
||||
|
||||
for ($i = 0; $i < self::CODE_COUNT; $i++) {
|
||||
$code = $this->generateHumanReadableCode();
|
||||
$codes[] = $code;
|
||||
|
||||
// Store hashed version
|
||||
$backupCode = new MfaBackupCode(
|
||||
userId: $userId,
|
||||
codeHash: Hash::sha256($code),
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
usedAt: null
|
||||
);
|
||||
|
||||
$this->repository->storeBackupCode($backupCode);
|
||||
}
|
||||
|
||||
return $codes;
|
||||
}
|
||||
|
||||
public function verifyBackupCode(string $userId, string $code): bool
|
||||
{
|
||||
$codeHash = Hash::sha256($code);
|
||||
$backupCode = $this->repository->findBackupCode($userId, $codeHash);
|
||||
|
||||
if (!$backupCode || $backupCode->isUsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark as used (one-time use only)
|
||||
$this->repository->markBackupCodeAsUsed($backupCode, new \DateTimeImmutable());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function generateHumanReadableCode(): string
|
||||
{
|
||||
// Generate readable codes avoiding confusing characters
|
||||
$chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // No 0, 1, I, O
|
||||
$code = '';
|
||||
|
||||
for ($i = 0; $i < self::CODE_LENGTH; $i++) {
|
||||
$code .= $chars[random_int(0, strlen($chars) - 1)];
|
||||
}
|
||||
|
||||
// Format as XXXX-XXXX for readability
|
||||
return substr($code, 0, 4) . '-' . substr($code, 4, 4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring & Incident Response
|
||||
|
||||
### Security Event Monitoring
|
||||
|
||||
```php
|
||||
final readonly class SecurityEventMonitor
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $securityLogger,
|
||||
private AlertingService $alerting,
|
||||
private MetricsCollector $metrics
|
||||
) {}
|
||||
|
||||
public function recordSecurityEvent(SecurityEvent $event): void
|
||||
{
|
||||
// Log all security events
|
||||
$this->securityLogger->warning('Security event recorded', [
|
||||
'event_type' => $event->getType(),
|
||||
'severity' => $event->getSeverity(),
|
||||
'user_id' => $event->getUserId(),
|
||||
'ip_address' => (string) $event->getIpAddress(),
|
||||
'user_agent' => (string) $event->getUserAgent(),
|
||||
'metadata' => $event->getMetadata(),
|
||||
'timestamp' => $event->getTimestamp()->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Update security metrics
|
||||
$this->metrics->increment('auth.security_events', [
|
||||
'type' => $event->getType(),
|
||||
'severity' => $event->getSeverity()
|
||||
]);
|
||||
|
||||
// Send alerts for critical events
|
||||
if ($event->isCritical()) {
|
||||
$this->alerting->sendSecurityAlert($event);
|
||||
}
|
||||
|
||||
// Check for attack patterns
|
||||
$this->analyzeForAttackPatterns($event);
|
||||
}
|
||||
|
||||
private function analyzeForAttackPatterns(SecurityEvent $event): void
|
||||
{
|
||||
$ipAddress = $event->getIpAddress();
|
||||
$recentEvents = $this->getRecentSecurityEvents($ipAddress, minutes: 10);
|
||||
|
||||
// Detect brute force attacks
|
||||
$failedLogins = array_filter($recentEvents,
|
||||
fn($e) => $e->getType() === 'authentication_failed'
|
||||
);
|
||||
|
||||
if (count($failedLogins) >= 5) {
|
||||
$this->alerting->sendAlert(new BruteForceDetectionAlert(
|
||||
ipAddress: $ipAddress,
|
||||
attemptCount: count($failedLogins),
|
||||
timeWindow: 10
|
||||
));
|
||||
}
|
||||
|
||||
// Detect credential stuffing
|
||||
$uniqueIdentifiers = array_unique(array_map(
|
||||
fn($e) => $e->getMetadata()['identifier'] ?? null,
|
||||
$failedLogins
|
||||
));
|
||||
|
||||
if (count($uniqueIdentifiers) >= 10) {
|
||||
$this->alerting->sendAlert(new CredentialStuffingAlert(
|
||||
ipAddress: $ipAddress,
|
||||
uniqueIdentifiers: count($uniqueIdentifiers)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Automated Response System
|
||||
|
||||
```php
|
||||
final readonly class AutomatedSecurityResponse
|
||||
{
|
||||
public function handleSecurityEvent(SecurityEvent $event): void
|
||||
{
|
||||
match ($event->getType()) {
|
||||
'brute_force_detected' => $this->handleBruteForce($event),
|
||||
'credential_stuffing_detected' => $this->handleCredentialStuffing($event),
|
||||
'session_hijacking_suspected' => $this->handleSessionHijacking($event),
|
||||
'suspicious_login_pattern' => $this->handleSuspiciousLogin($event),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function handleBruteForce(SecurityEvent $event): void
|
||||
{
|
||||
$ipAddress = $event->getIpAddress();
|
||||
|
||||
// Immediately block IP for 1 hour
|
||||
$this->firewall->blockIpAddress($ipAddress, duration: 3600);
|
||||
|
||||
// Increase monitoring for this IP
|
||||
$this->monitoring->increaseWatchLevel($ipAddress);
|
||||
|
||||
// Notify security team
|
||||
$this->alerting->sendUrgentAlert(new BruteForceBlockAlert($ipAddress));
|
||||
}
|
||||
|
||||
private function handleSessionHijacking(SecurityEvent $event): void
|
||||
{
|
||||
$userId = $event->getUserId();
|
||||
$sessionId = $event->getMetadata()['session_id'];
|
||||
|
||||
// Immediately terminate all user sessions
|
||||
$this->authService->logoutAll($userId);
|
||||
|
||||
// Require password reset
|
||||
$this->userService->requirePasswordReset($userId);
|
||||
|
||||
// Send security notification to user
|
||||
$user = $this->userRepository->findById($userId);
|
||||
$this->notifications->sendSecurityBreach($user->getEmail(), [
|
||||
'incident_type' => 'session_hijacking',
|
||||
'affected_session' => $sessionId,
|
||||
'timestamp' => $event->getTimestamp()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Compliance & Audit
|
||||
|
||||
### GDPR Compliance
|
||||
|
||||
```php
|
||||
final readonly class GdprCompliantAuthService
|
||||
{
|
||||
public function processPersonalData(User $user, string $purpose): void
|
||||
{
|
||||
// Log data processing for GDPR audit trail
|
||||
$this->auditLogger->info('Personal data processed', [
|
||||
'user_id' => $user->getId(),
|
||||
'data_types' => ['email', 'login_timestamps', 'ip_addresses'],
|
||||
'processing_purpose' => $purpose,
|
||||
'legal_basis' => 'legitimate_interest', // or 'consent'
|
||||
'retention_period' => '2_years'
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportUserData(string $userId): UserDataExport
|
||||
{
|
||||
$user = $this->repository->findById($userId);
|
||||
if (!$user) {
|
||||
throw new UserNotFoundException($userId);
|
||||
}
|
||||
|
||||
return new UserDataExport([
|
||||
'personal_data' => [
|
||||
'user_id' => $user->getId(),
|
||||
'email' => (string) $user->getEmail(),
|
||||
'created_at' => $user->getCreatedAt()->format('c'),
|
||||
'last_login' => $user->getLastLoginAt()?->format('c')
|
||||
],
|
||||
'authentication_data' => [
|
||||
'password_last_changed' => $user->getPasswordChangedAt()?->format('c'),
|
||||
'mfa_enabled' => $user->hasMfaEnabled(),
|
||||
'failed_login_attempts' => $this->repository->getFailedLoginAttempts($userId)
|
||||
],
|
||||
'session_data' => $this->getSessionHistory($userId),
|
||||
'security_events' => $this->getSecurityEventHistory($userId)
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteUserData(string $userId): void
|
||||
{
|
||||
// GDPR right to erasure (right to be forgotten)
|
||||
$this->repository->anonymizeUser($userId);
|
||||
$this->repository->deleteAllUserSessions($userId);
|
||||
$this->repository->deleteAllUserRememberTokens($userId);
|
||||
$this->repository->deleteUserSecurityEvents($userId);
|
||||
|
||||
$this->auditLogger->info('User data deleted per GDPR request', [
|
||||
'user_id' => $userId,
|
||||
'deletion_timestamp' => (new \DateTimeImmutable())->format('c')
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security Audit Logging
|
||||
|
||||
```php
|
||||
final readonly class SecurityAuditLogger
|
||||
{
|
||||
public function logAuthenticationEvent(string $eventType, array $context): void
|
||||
{
|
||||
$auditRecord = [
|
||||
'event_type' => $eventType,
|
||||
'timestamp' => (new \DateTimeImmutable())->format('c'),
|
||||
'user_id' => $context['user_id'] ?? null,
|
||||
'session_id' => $context['session_id'] ?? null,
|
||||
'ip_address' => $context['ip_address'] ?? null,
|
||||
'user_agent' => $context['user_agent'] ?? null,
|
||||
'success' => $context['success'] ?? false,
|
||||
'failure_reason' => $context['failure_reason'] ?? null,
|
||||
'risk_score' => $this->calculateRiskScore($context)
|
||||
];
|
||||
|
||||
// Store in secure audit log (append-only, tamper-evident)
|
||||
$this->auditStorage->append($auditRecord);
|
||||
|
||||
// Forward to SIEM if configured
|
||||
if ($this->siemForwarder) {
|
||||
$this->siemForwarder->forward($auditRecord);
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateRiskScore(array $context): int
|
||||
{
|
||||
$score = 0;
|
||||
|
||||
// Location-based risk
|
||||
if (isset($context['ip_address'])) {
|
||||
$location = $this->geolocator->locate($context['ip_address']);
|
||||
if ($location->isHighRiskCountry()) $score += 25;
|
||||
if ($location->isFromTor()) $score += 50;
|
||||
}
|
||||
|
||||
// Time-based risk
|
||||
$hour = (int) date('H');
|
||||
if ($hour < 6 || $hour > 22) $score += 10; // Outside business hours
|
||||
|
||||
// Failure patterns
|
||||
if (!($context['success'] ?? true)) $score += 20;
|
||||
|
||||
return min(100, $score);
|
||||
}
|
||||
}
|
||||
397
docs/components/cryptography/configuration.md
Normal file
397
docs/components/cryptography/configuration.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# Cryptography Module - Konfiguration
|
||||
|
||||
Konfigurationsoptionen und Setup für das Cryptography Module.
|
||||
|
||||
## Service-Registrierung
|
||||
|
||||
### Dependency Injection Setup
|
||||
|
||||
```php
|
||||
use App\Framework\DI\Initializer;
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
use App\Framework\Cryptography\SecureTokenGenerator;
|
||||
use App\Framework\Cryptography\CryptographicUtilities;
|
||||
use App\Framework\Cryptography\DigitalSignature;
|
||||
use App\Framework\Cryptography\AdvancedHash;
|
||||
use App\Framework\Random\SecureRandomGenerator;
|
||||
|
||||
final readonly class CryptographyInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
// Random Generator als Dependency
|
||||
$container->singleton(RandomGenerator::class, new SecureRandomGenerator());
|
||||
|
||||
// Core Cryptography Services
|
||||
$container->singleton(KeyDerivationFunction::class, function(Container $c) {
|
||||
return new KeyDerivationFunction($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(SecureTokenGenerator::class, function(Container $c) {
|
||||
return new SecureTokenGenerator($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(CryptographicUtilities::class, function(Container $c) {
|
||||
return new CryptographicUtilities($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(DigitalSignature::class, function(Container $c) {
|
||||
return new DigitalSignature($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(AdvancedHash::class, new AdvancedHash());
|
||||
|
||||
// Factory Methods als Alternative
|
||||
$container->bind('kdf.factory', function(Container $c) {
|
||||
return KeyDerivationFunction::create($c->get(RandomGenerator::class));
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AppBootstrapper Integration
|
||||
|
||||
```php
|
||||
// In src/Framework/Core/AppBootstrapper.php
|
||||
public function bootstrap(): Application
|
||||
{
|
||||
// ... existing bootstrapping
|
||||
|
||||
$initializer = new CryptographyInitializer();
|
||||
$initializer->initialize($this->container);
|
||||
|
||||
return $application;
|
||||
}
|
||||
```
|
||||
|
||||
## Sicherheitsparameter
|
||||
|
||||
### Key Derivation Function Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class CryptographyConfig
|
||||
{
|
||||
public const DEFAULT_KDF_PARAMETERS = [
|
||||
'pbkdf2-sha256' => [
|
||||
'low' => ['iterations' => 50000, 'key_length' => 32],
|
||||
'standard' => ['iterations' => 100000, 'key_length' => 32],
|
||||
'high' => ['iterations' => 200000, 'key_length' => 32],
|
||||
],
|
||||
'argon2id' => [
|
||||
'low' => ['memory_cost' => 32768, 'time_cost' => 3, 'threads' => 2],
|
||||
'standard' => ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3],
|
||||
'high' => ['memory_cost' => 131072, 'time_cost' => 6, 'threads' => 4],
|
||||
],
|
||||
'scrypt' => [
|
||||
'low' => ['cost_parameter' => 8192, 'block_size' => 8, 'parallelization' => 1],
|
||||
'standard' => ['cost_parameter' => 16384, 'block_size' => 8, 'parallelization' => 1],
|
||||
'high' => ['cost_parameter' => 32768, 'block_size' => 8, 'parallelization' => 2],
|
||||
]
|
||||
];
|
||||
|
||||
public static function getRecommendedParameters(
|
||||
string $algorithm,
|
||||
string $securityLevel = 'standard'
|
||||
): array {
|
||||
return self::DEFAULT_KDF_PARAMETERS[$algorithm][$securityLevel] ??
|
||||
throw new InvalidArgumentException("Unsupported configuration: {$algorithm}:{$securityLevel}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Token-Generator Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class TokenConfig
|
||||
{
|
||||
public const DEFAULT_TOKEN_LENGTHS = [
|
||||
SecureTokenGenerator::TYPE_API_KEY => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_SESSION => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_CSRF => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_REFRESH => 64, // 512 Bit
|
||||
SecureTokenGenerator::TYPE_VERIFICATION => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_BEARER => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_WEBHOOK => 32, // 256 Bit
|
||||
];
|
||||
|
||||
public const DEFAULT_TOKEN_FORMATS = [
|
||||
SecureTokenGenerator::TYPE_API_KEY => SecureTokenGenerator::FORMAT_BASE64_URL,
|
||||
SecureTokenGenerator::TYPE_SESSION => SecureTokenGenerator::FORMAT_BASE64_URL,
|
||||
SecureTokenGenerator::TYPE_OTP => SecureTokenGenerator::FORMAT_NUMERIC,
|
||||
];
|
||||
|
||||
public const TOKEN_EXPIRY_TIMES = [
|
||||
SecureTokenGenerator::TYPE_SESSION => 3600 * 24, // 24 Stunden
|
||||
SecureTokenGenerator::TYPE_CSRF => 3600, // 1 Stunde
|
||||
SecureTokenGenerator::TYPE_VERIFICATION => 3600 * 2, // 2 Stunden
|
||||
SecureTokenGenerator::TYPE_OTP => 300, // 5 Minuten
|
||||
SecureTokenGenerator::TYPE_REFRESH => 3600 * 24 * 30, // 30 Tage
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Environment-basierte Konfiguration
|
||||
|
||||
### .env Konfiguration
|
||||
|
||||
```bash
|
||||
# Cryptography Configuration
|
||||
CRYPTO_DEFAULT_KDF_ALGORITHM=argon2id
|
||||
CRYPTO_DEFAULT_SECURITY_LEVEL=standard
|
||||
|
||||
# Token Configuration
|
||||
CRYPTO_DEFAULT_TOKEN_LENGTH=32
|
||||
CRYPTO_DEFAULT_TOKEN_FORMAT=base64_url
|
||||
|
||||
# Digital Signature Configuration
|
||||
CRYPTO_DEFAULT_RSA_KEY_SIZE=2048
|
||||
CRYPTO_DEFAULT_EC_CURVE=secp256r1
|
||||
|
||||
# Performance Configuration
|
||||
CRYPTO_BATCH_SIZE_LIMIT=1000
|
||||
CRYPTO_ENTROPY_THRESHOLD=7.0
|
||||
```
|
||||
|
||||
### Environment-aware Service
|
||||
|
||||
```php
|
||||
use App\Framework\Core\Environment;
|
||||
use App\Framework\Core\EnvKey;
|
||||
|
||||
final readonly class CryptographyConfigService
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $environment
|
||||
) {}
|
||||
|
||||
public function getDefaultKdfAlgorithm(): string
|
||||
{
|
||||
return $this->environment->get(
|
||||
EnvKey::from('CRYPTO_DEFAULT_KDF_ALGORITHM'),
|
||||
'argon2id'
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultSecurityLevel(): string
|
||||
{
|
||||
return $this->environment->get(
|
||||
EnvKey::from('CRYPTO_DEFAULT_SECURITY_LEVEL'),
|
||||
'standard'
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultTokenLength(): int
|
||||
{
|
||||
return $this->environment->getInt(
|
||||
EnvKey::from('CRYPTO_DEFAULT_TOKEN_LENGTH'),
|
||||
32
|
||||
);
|
||||
}
|
||||
|
||||
public function getEntropyThreshold(): float
|
||||
{
|
||||
return $this->environment->getFloat(
|
||||
EnvKey::from('CRYPTO_ENTROPY_THRESHOLD'),
|
||||
7.0
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom RandomGenerator
|
||||
|
||||
```php
|
||||
// Eigener RandomGenerator für spezielle Anforderungen
|
||||
final readonly class CustomSecureRandomGenerator implements RandomGenerator
|
||||
{
|
||||
public function bytes(int $length): string
|
||||
{
|
||||
// Custom implementation mit Hardware-RNG
|
||||
return $this->getHardwareRandomBytes($length);
|
||||
}
|
||||
|
||||
public function int(int $min = 0, int $max = PHP_INT_MAX): int
|
||||
{
|
||||
return random_int($min, $max);
|
||||
}
|
||||
|
||||
private function getHardwareRandomBytes(int $length): string
|
||||
{
|
||||
// Implementation für Hardware-RNG
|
||||
// z.B. über /dev/hwrng oder externe HSM
|
||||
}
|
||||
}
|
||||
|
||||
// In Initializer registrieren
|
||||
$container->singleton(RandomGenerator::class, new CustomSecureRandomGenerator());
|
||||
```
|
||||
|
||||
### Performance-optimierte Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class PerformanceOptimizedCryptoInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
// Cached Services für bessere Performance
|
||||
$container->singleton(KeyDerivationFunction::class, function(Container $c) {
|
||||
$kdf = new KeyDerivationFunction($c->get(RandomGenerator::class));
|
||||
return new CachedKeyDerivationFunction($kdf, $c->get(Cache::class));
|
||||
});
|
||||
|
||||
// Batch-optimierte Token-Generierung
|
||||
$container->singleton(SecureTokenGenerator::class, function(Container $c) {
|
||||
return new BatchOptimizedTokenGenerator(
|
||||
$c->get(RandomGenerator::class),
|
||||
batchSize: 100 // Tokens in Batches vorgenerieren
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test-Konfiguration
|
||||
|
||||
```php
|
||||
// Für Unit Tests - deterministic aber sichere Werte
|
||||
final readonly class TestCryptographyInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
if (!$this->isTestEnvironment()) {
|
||||
throw new InvalidArgumentException('TestCryptographyInitializer nur in Test-Umgebung verwenden');
|
||||
}
|
||||
|
||||
// Deterministischer aber sicherer Generator für Tests
|
||||
$container->singleton(RandomGenerator::class, new TestRandomGenerator());
|
||||
|
||||
// Reduzierte Parameter für schnellere Tests
|
||||
$container->singleton(KeyDerivationFunction::class, function(Container $c) {
|
||||
return new KeyDerivationFunction($c->get(RandomGenerator::class));
|
||||
});
|
||||
}
|
||||
|
||||
private function isTestEnvironment(): bool
|
||||
{
|
||||
return defined('PHPUNIT_RUNNING') ||
|
||||
(isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware-Integration
|
||||
|
||||
### Automatic Token Validation
|
||||
|
||||
```php
|
||||
final readonly class CryptoTokenValidationMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private CryptographicUtilities $utils,
|
||||
private CacheInterface $cache
|
||||
) {}
|
||||
|
||||
public function handle(HttpRequest $request, RequestHandler $handler): HttpResponse
|
||||
{
|
||||
$authHeader = $request->server->getAuthorizationHeader();
|
||||
|
||||
if ($authHeader && str_starts_with($authHeader, 'Bearer ')) {
|
||||
$token = substr($authHeader, 7);
|
||||
|
||||
// Token-Format validieren
|
||||
if (!$this->utils->timingSafeEquals($token, trim($token))) {
|
||||
throw new AuthenticationException('Invalid token format');
|
||||
}
|
||||
|
||||
// Cache für Token-Validierung
|
||||
$cacheKey = 'token_valid_' . hash('sha256', $token);
|
||||
if (!$this->cache->has($cacheKey)) {
|
||||
// Token in Datenbank validieren
|
||||
$isValid = $this->validateTokenInDatabase($token);
|
||||
$this->cache->set($cacheKey, $isValid, 300); // 5 Min Cache
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring und Logging
|
||||
|
||||
### Security Event Integration
|
||||
|
||||
```php
|
||||
final readonly class CryptographyEventLogger
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $logger,
|
||||
private SecurityEventLogger $securityLogger
|
||||
) {}
|
||||
|
||||
public function logTokenGeneration(SecureToken $token): void
|
||||
{
|
||||
$this->logger->info('Token generated', [
|
||||
'type' => $token->getType(),
|
||||
'format' => $token->getFormat(),
|
||||
'length' => $token->getLength(),
|
||||
'fingerprint' => $token->getShortFingerprint()
|
||||
]);
|
||||
}
|
||||
|
||||
public function logSuspiciousTokenActivity(string $token, string $reason): void
|
||||
{
|
||||
$this->securityLogger->logSecurityEvent(
|
||||
new SuspiciousTokenActivityEvent(
|
||||
tokenFingerprint: hash('sha256', $token),
|
||||
reason: $reason,
|
||||
timestamp: new DateTimeImmutable()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validierung der Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class CryptographyConfigValidator
|
||||
{
|
||||
public static function validateConfiguration(Container $container): void
|
||||
{
|
||||
// RandomGenerator verfügbar?
|
||||
if (!$container->has(RandomGenerator::class)) {
|
||||
throw new ConfigurationException('RandomGenerator nicht registriert');
|
||||
}
|
||||
|
||||
// Cryptography Services verfügbar?
|
||||
$requiredServices = [
|
||||
KeyDerivationFunction::class,
|
||||
SecureTokenGenerator::class,
|
||||
CryptographicUtilities::class
|
||||
];
|
||||
|
||||
foreach ($requiredServices as $service) {
|
||||
if (!$container->has($service)) {
|
||||
throw new ConfigurationException("Service nicht registriert: {$service}");
|
||||
}
|
||||
}
|
||||
|
||||
// Sicherheitsparameter validieren
|
||||
$kdf = $container->get(KeyDerivationFunction::class);
|
||||
try {
|
||||
$params = $kdf->getRecommendedParameters('pbkdf2-sha256', 'standard');
|
||||
if ($params['iterations'] < 50000) {
|
||||
throw new ConfigurationException('PBKDF2 Iterationen zu niedrig (Minimum: 50000)');
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ConfigurationException('Ungültige KDF-Konfiguration: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diese Konfiguration ermöglicht es, das Cryptography Module flexibel an verschiedene Umgebungen und Sicherheitsanforderungen anzupassen, während Best Practices für Sicherheit und Performance eingehalten werden.
|
||||
703
docs/components/cryptography/examples.md
Normal file
703
docs/components/cryptography/examples.md
Normal file
@@ -0,0 +1,703 @@
|
||||
# Cryptography Module - Praktische Beispiele
|
||||
|
||||
Umfassende Sammlung praktischer Anwendungsfälle für das Cryptography Module.
|
||||
|
||||
## Authentifizierung und Session-Management
|
||||
|
||||
### User Password System
|
||||
|
||||
```php
|
||||
final readonly class UserAuthenticationService
|
||||
{
|
||||
public function __construct(
|
||||
private KeyDerivationFunction $kdf,
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function registerUser(string $email, string $password): User
|
||||
{
|
||||
// Password hashen mit Argon2ID
|
||||
$hashedPassword = $this->kdf->hashPassword($password, 'argon2id', [
|
||||
'memory_cost' => 65536, // 64 MB
|
||||
'time_cost' => 4, // 4 Iterationen
|
||||
'threads' => 3 // 3 Threads
|
||||
]);
|
||||
|
||||
$user = new User(
|
||||
id: Ulid::generate(),
|
||||
email: new Email($email),
|
||||
passwordHash: $hashedPassword,
|
||||
createdAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
return $this->userRepository->save($user);
|
||||
}
|
||||
|
||||
public function authenticateUser(string $email, string $password): ?SessionToken
|
||||
{
|
||||
$user = $this->userRepository->findByEmail(new Email($email));
|
||||
|
||||
if (!$user || !$this->kdf->verify($password, $user->getPasswordHash())) {
|
||||
// Timing-Angriffe verhindern durch konstante Ausführungszeit
|
||||
$this->kdf->hashPassword('dummy-password', 'argon2id');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Session Token generieren
|
||||
$sessionToken = $this->tokenGenerator->generateSessionToken(48); // 384 Bit
|
||||
|
||||
// In Session Store speichern
|
||||
$this->storeSessionToken($user, $sessionToken);
|
||||
|
||||
return $sessionToken;
|
||||
}
|
||||
|
||||
private function storeSessionToken(User $user, SecureToken $token): void
|
||||
{
|
||||
$session = new UserSession(
|
||||
tokenHash: hash('sha256', $token->getValue()),
|
||||
userId: $user->getId(),
|
||||
expiresAt: (new DateTimeImmutable())->add(new DateInterval('PT24H')),
|
||||
metadata: $token->getMetadata()
|
||||
);
|
||||
|
||||
// Session in Datenbank speichern (nur Hash, nie Klartext!)
|
||||
$this->sessionRepository->save($session);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Key Management System
|
||||
|
||||
```php
|
||||
final readonly class ApiKeyManagementService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CryptographicUtilities $utils,
|
||||
private ApiKeyRepository $repository
|
||||
) {}
|
||||
|
||||
public function createApiKey(
|
||||
User $user,
|
||||
string $name,
|
||||
array $scopes = [],
|
||||
?DateTimeImmutable $expiresAt = null
|
||||
): ApiKeyResult {
|
||||
// API Key mit Prefix generieren
|
||||
$apiKey = $this->tokenGenerator->generateApiKey(
|
||||
prefix: 'ak',
|
||||
length: 32
|
||||
);
|
||||
|
||||
// API Key Entity erstellen (Hash speichern, nicht Klartext)
|
||||
$apiKeyEntity = new ApiKey(
|
||||
id: Ulid::generate(),
|
||||
userId: $user->getId(),
|
||||
name: $name,
|
||||
keyHash: hash('sha256', $apiKey->getValue()),
|
||||
fingerprint: $apiKey->getFingerprint(),
|
||||
scopes: $scopes,
|
||||
expiresAt: $expiresAt,
|
||||
createdAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
$this->repository->save($apiKeyEntity);
|
||||
|
||||
return new ApiKeyResult(
|
||||
apiKey: $apiKey->getValue(), // Nur einmal zurückgeben
|
||||
keyId: $apiKeyEntity->getId(),
|
||||
fingerprint: $apiKey->getShortFingerprint(),
|
||||
expiresAt: $expiresAt
|
||||
);
|
||||
}
|
||||
|
||||
public function validateApiKey(string $apiKey): ?ApiKey
|
||||
{
|
||||
$keyHash = hash('sha256', $apiKey);
|
||||
$storedKey = $this->repository->findByHash($keyHash);
|
||||
|
||||
if (!$storedKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
if (!$this->utils->timingSafeEquals($keyHash, $storedKey->getKeyHash())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Expiration prüfen
|
||||
if ($storedKey->getExpiresAt() && $storedKey->getExpiresAt() < new DateTimeImmutable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $storedKey;
|
||||
}
|
||||
|
||||
public function rotateApiKey(string $currentApiKey): ApiKeyResult
|
||||
{
|
||||
$oldKey = $this->validateApiKey($currentApiKey);
|
||||
|
||||
if (!$oldKey) {
|
||||
throw new ApiKeyException('Invalid API key for rotation');
|
||||
}
|
||||
|
||||
// Neuen Key mit gleichen Eigenschaften erstellen
|
||||
$newApiKey = $this->createApiKey(
|
||||
user: $oldKey->getUser(),
|
||||
name: $oldKey->getName() . ' (rotated)',
|
||||
scopes: $oldKey->getScopes(),
|
||||
expiresAt: $oldKey->getExpiresAt()
|
||||
);
|
||||
|
||||
// Alten Key deaktivieren (nicht löschen für Audit-Trail)
|
||||
$oldKey->deactivate();
|
||||
$this->repository->save($oldKey);
|
||||
|
||||
return $newApiKey;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CSRF Protection
|
||||
|
||||
### CSRF Token System
|
||||
|
||||
```php
|
||||
final readonly class CsrfProtectionService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CacheInterface $cache
|
||||
) {}
|
||||
|
||||
public function generateCsrfToken(string $sessionId): SecureToken
|
||||
{
|
||||
$csrfToken = $this->tokenGenerator->generateCsrfToken();
|
||||
|
||||
// Token mit Session verknüpfen
|
||||
$cacheKey = "csrf_token_{$sessionId}";
|
||||
$this->cache->set($cacheKey, $csrfToken->getValue(), 3600); // 1 Stunde
|
||||
|
||||
return $csrfToken;
|
||||
}
|
||||
|
||||
public function validateCsrfToken(string $sessionId, string $providedToken): bool
|
||||
{
|
||||
$cacheKey = "csrf_token_{$sessionId}";
|
||||
$storedToken = $this->cache->get($cacheKey);
|
||||
|
||||
if (!$storedToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
$isValid = hash_equals($storedToken, $providedToken);
|
||||
|
||||
// Token nach Verwendung löschen (Single-Use)
|
||||
if ($isValid) {
|
||||
$this->cache->delete($cacheKey);
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
}
|
||||
|
||||
// CSRF Middleware
|
||||
final readonly class CsrfProtectionMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private CsrfProtectionService $csrfService
|
||||
) {}
|
||||
|
||||
public function handle(HttpRequest $request, RequestHandler $handler): HttpResponse
|
||||
{
|
||||
// Nur bei state-changing Operations prüfen
|
||||
if (in_array($request->method, [Method::POST, Method::PUT, Method::DELETE, Method::PATCH])) {
|
||||
$sessionId = $request->session->getId();
|
||||
$csrfToken = $request->parsedBody->get('_token') ?? $request->headers->get('X-CSRF-Token');
|
||||
|
||||
if (!$csrfToken || !$this->csrfService->validateCsrfToken($sessionId, $csrfToken)) {
|
||||
throw new CsrfTokenMismatchException('CSRF token mismatch');
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook Security
|
||||
|
||||
### Webhook Signature System
|
||||
|
||||
```php
|
||||
final readonly class WebhookSecurityService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CryptographicUtilities $utils
|
||||
) {}
|
||||
|
||||
public function generateWebhookSecret(): SecureToken
|
||||
{
|
||||
return $this->tokenGenerator->generateWebhookToken();
|
||||
}
|
||||
|
||||
public function signWebhookPayload(string $payload, SecureToken $secret): string
|
||||
{
|
||||
$signature = hash_hmac('sha256', $payload, $secret->getValue());
|
||||
return 'sha256=' . $signature;
|
||||
}
|
||||
|
||||
public function validateWebhookSignature(
|
||||
string $payload,
|
||||
string $signature,
|
||||
SecureToken $secret
|
||||
): bool {
|
||||
$expectedSignature = $this->signWebhookPayload($payload, $secret);
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
return $this->utils->timingSafeEquals($signature, $expectedSignature);
|
||||
}
|
||||
}
|
||||
|
||||
// Webhook Handler
|
||||
final readonly class PaymentWebhookHandler
|
||||
{
|
||||
public function __construct(
|
||||
private WebhookSecurityService $security,
|
||||
private WebhookConfigRepository $configRepository
|
||||
) {}
|
||||
|
||||
#[Route(path: '/webhooks/payment', method: Method::POST)]
|
||||
public function handlePaymentWebhook(HttpRequest $request): JsonResult
|
||||
{
|
||||
$signature = $request->headers->get('X-Hub-Signature-256');
|
||||
$payload = $request->rawBody();
|
||||
|
||||
if (!$signature) {
|
||||
throw new WebhookException('Missing signature header');
|
||||
}
|
||||
|
||||
// Webhook Secret für den Sender abrufen
|
||||
$senderId = $request->headers->get('X-Sender-ID');
|
||||
$config = $this->configRepository->findBySenderId($senderId);
|
||||
|
||||
if (!$config) {
|
||||
throw new WebhookException('Unknown sender');
|
||||
}
|
||||
|
||||
// Signatur validieren
|
||||
if (!$this->security->validateWebhookSignature($payload, $signature, $config->getSecret())) {
|
||||
throw new WebhookException('Invalid signature');
|
||||
}
|
||||
|
||||
// Payload verarbeiten
|
||||
$data = json_decode($payload, true);
|
||||
$this->processPaymentEvent($data);
|
||||
|
||||
return new JsonResult(['status' => 'processed']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Two-Factor Authentication (2FA)
|
||||
|
||||
### OTP-basiertes 2FA System
|
||||
|
||||
```php
|
||||
final readonly class TwoFactorAuthService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CacheInterface $cache,
|
||||
private NotificationService $notificationService
|
||||
) {}
|
||||
|
||||
public function generateOtpCode(User $user): string
|
||||
{
|
||||
$otpToken = $this->tokenGenerator->generateOtpToken(6); // 6-stelliger Code
|
||||
|
||||
// OTP mit User-ID und Expiration speichern
|
||||
$cacheKey = "otp_{$user->getId()}";
|
||||
$otpData = [
|
||||
'code' => $otpToken->getValue(),
|
||||
'attempts' => 0,
|
||||
'created_at' => time()
|
||||
];
|
||||
|
||||
$this->cache->set($cacheKey, $otpData, 300); // 5 Minuten gültig
|
||||
|
||||
return $otpToken->getValue();
|
||||
}
|
||||
|
||||
public function sendOtpCode(User $user): void
|
||||
{
|
||||
$otpCode = $this->generateOtpCode($user);
|
||||
|
||||
// SMS oder Email versenden
|
||||
$this->notificationService->sendOtpCode(
|
||||
recipient: $user->getPhoneNumber() ?? $user->getEmail(),
|
||||
code: $otpCode,
|
||||
expiresInMinutes: 5
|
||||
);
|
||||
}
|
||||
|
||||
public function verifyOtpCode(User $user, string $providedCode): bool
|
||||
{
|
||||
$cacheKey = "otp_{$user->getId()}";
|
||||
$otpData = $this->cache->get($cacheKey);
|
||||
|
||||
if (!$otpData) {
|
||||
return false; // Kein OTP vorhanden oder abgelaufen
|
||||
}
|
||||
|
||||
// Rate-Limiting: Max 3 Versuche
|
||||
if ($otpData['attempts'] >= 3) {
|
||||
$this->cache->delete($cacheKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
$isValid = hash_equals($otpData['code'], $providedCode);
|
||||
|
||||
if ($isValid) {
|
||||
// Erfolgreiche Verifikation - OTP löschen
|
||||
$this->cache->delete($cacheKey);
|
||||
} else {
|
||||
// Fehlversuch - Counter erhöhen
|
||||
$otpData['attempts']++;
|
||||
$this->cache->set($cacheKey, $otpData, 300);
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Digitale Signaturen für Dokumente
|
||||
|
||||
### Document Signing Service
|
||||
|
||||
```php
|
||||
final readonly class DocumentSigningService
|
||||
{
|
||||
public function __construct(
|
||||
private DigitalSignature $signature,
|
||||
private DocumentRepository $documentRepository
|
||||
) {}
|
||||
|
||||
public function generateSigningKeyPair(): KeyPair
|
||||
{
|
||||
// RSA 4096 Bit für langfristige Dokumentensignaturen
|
||||
return $this->signature->generateRsaKeyPair(4096);
|
||||
}
|
||||
|
||||
public function signDocument(
|
||||
Document $document,
|
||||
PrivateKey $privateKey,
|
||||
User $signer
|
||||
): SignedDocument {
|
||||
$documentContent = $document->getContent();
|
||||
$metadata = [
|
||||
'document_id' => $document->getId(),
|
||||
'signer_id' => $signer->getId(),
|
||||
'signed_at' => (new DateTimeImmutable())->format('c'),
|
||||
'algorithm' => 'RSA-SHA256'
|
||||
];
|
||||
|
||||
// Dokument + Metadaten signieren
|
||||
$dataToSign = $documentContent . json_encode($metadata);
|
||||
$signatureResult = $this->signature->sign($dataToSign, $privateKey, 'sha256');
|
||||
|
||||
$signedDocument = new SignedDocument(
|
||||
originalDocument: $document,
|
||||
signature: $signatureResult,
|
||||
signerPublicKey: $privateKey->getPublicKey(),
|
||||
metadata: $metadata,
|
||||
signedAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
return $this->documentRepository->saveSignedDocument($signedDocument);
|
||||
}
|
||||
|
||||
public function verifyDocumentSignature(SignedDocument $signedDocument): bool
|
||||
{
|
||||
$document = $signedDocument->getOriginalDocument();
|
||||
$metadata = $signedDocument->getMetadata();
|
||||
|
||||
$dataToVerify = $document->getContent() . json_encode($metadata);
|
||||
|
||||
return $this->signature->verify(
|
||||
$dataToVerify,
|
||||
$signedDocument->getSignature(),
|
||||
$signedDocument->getSignerPublicKey()
|
||||
);
|
||||
}
|
||||
|
||||
public function createSignatureChain(array $documents, array $signers): SignatureChain
|
||||
{
|
||||
$chain = new SignatureChain();
|
||||
|
||||
foreach ($documents as $index => $document) {
|
||||
$signer = $signers[$index];
|
||||
$signedDocument = $this->signDocument($document, $signer->getPrivateKey(), $signer);
|
||||
$chain->addSignedDocument($signedDocument);
|
||||
}
|
||||
|
||||
// Chain-Signatur erstellen (Hash der gesamten Kette)
|
||||
$chainHash = $chain->calculateChainHash();
|
||||
$chainSignature = $this->signature->sign($chainHash, $signers[0]->getPrivateKey());
|
||||
$chain->setChainSignature($chainSignature);
|
||||
|
||||
return $chain;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Passwort-Reset System
|
||||
|
||||
### Secure Password Reset
|
||||
|
||||
```php
|
||||
final readonly class PasswordResetService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private KeyDerivationFunction $kdf,
|
||||
private CacheInterface $cache,
|
||||
private EmailService $emailService
|
||||
) {}
|
||||
|
||||
public function initiatePasswordReset(User $user): void
|
||||
{
|
||||
// Sicheren Reset-Token generieren
|
||||
$resetToken = $this->tokenGenerator->generateVerificationToken('password_reset');
|
||||
|
||||
// Token hashen und speichern (nie Klartext speichern)
|
||||
$tokenHash = hash('sha256', $resetToken->getValue());
|
||||
$resetData = [
|
||||
'user_id' => $user->getId(),
|
||||
'token_hash' => $tokenHash,
|
||||
'created_at' => time(),
|
||||
'used' => false
|
||||
];
|
||||
|
||||
// 2 Stunden gültig
|
||||
$cacheKey = "password_reset_{$user->getId()}";
|
||||
$this->cache->set($cacheKey, $resetData, 7200);
|
||||
|
||||
// Reset-Link per Email senden
|
||||
$resetLink = "https://app.example.com/reset-password?token=" .
|
||||
urlencode($resetToken->getValue());
|
||||
|
||||
$this->emailService->send(
|
||||
to: $user->getEmail(),
|
||||
subject: 'Password Reset Request',
|
||||
template: 'password_reset',
|
||||
data: ['reset_link' => $resetLink, 'expires_in' => '2 hours']
|
||||
);
|
||||
}
|
||||
|
||||
public function validateResetToken(string $token): ?User
|
||||
{
|
||||
$tokenHash = hash('sha256', $token);
|
||||
|
||||
// Token in allen aktiven Reset-Anfragen suchen
|
||||
$cacheKeys = $this->cache->getKeysByPattern('password_reset_*');
|
||||
|
||||
foreach ($cacheKeys as $cacheKey) {
|
||||
$resetData = $this->cache->get($cacheKey);
|
||||
|
||||
if ($resetData &&
|
||||
!$resetData['used'] &&
|
||||
hash_equals($resetData['token_hash'], $tokenHash)) {
|
||||
|
||||
return $this->userRepository->find($resetData['user_id']);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function resetPassword(string $token, string $newPassword): bool
|
||||
{
|
||||
$user = $this->validateResetToken($token);
|
||||
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Neues Password hashen
|
||||
$hashedPassword = $this->kdf->hashPassword($newPassword, 'argon2id');
|
||||
|
||||
// User Password aktualisieren
|
||||
$user->updatePassword($hashedPassword);
|
||||
$this->userRepository->save($user);
|
||||
|
||||
// Reset-Token als verwendet markieren
|
||||
$cacheKey = "password_reset_{$user->getId()}";
|
||||
$resetData = $this->cache->get($cacheKey);
|
||||
if ($resetData) {
|
||||
$resetData['used'] = true;
|
||||
$this->cache->set($cacheKey, $resetData, 7200);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Daten-Verschlüsselung mit Schlüssel-Ableitung
|
||||
|
||||
### Encrypted Data Storage
|
||||
|
||||
```php
|
||||
final readonly class EncryptedDataService
|
||||
{
|
||||
public function __construct(
|
||||
private KeyDerivationFunction $kdf,
|
||||
private CryptographicUtilities $utils,
|
||||
private EncryptionService $encryption
|
||||
) {}
|
||||
|
||||
public function encryptPersonalData(
|
||||
array $personalData,
|
||||
string $userPassword
|
||||
): EncryptedPersonalData {
|
||||
// Benutzer-spezifischen Schlüssel aus Password ableiten
|
||||
$salt = $this->utils->generateNonce(32);
|
||||
$derivedKey = $this->kdf->pbkdf2($userPassword, $salt, 100000, 32);
|
||||
|
||||
// Daten serialisieren und verschlüsseln
|
||||
$serializedData = json_encode($personalData);
|
||||
$encryptedData = $this->encryption->encrypt($serializedData, $derivedKey->getKey());
|
||||
|
||||
return new EncryptedPersonalData(
|
||||
encryptedData: $encryptedData,
|
||||
salt: $salt,
|
||||
keyDerivation: [
|
||||
'algorithm' => $derivedKey->getAlgorithm(),
|
||||
'iterations' => $derivedKey->getIterations()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function decryptPersonalData(
|
||||
EncryptedPersonalData $encryptedData,
|
||||
string $userPassword
|
||||
): array {
|
||||
// Gleichen Schlüssel aus Password ableiten
|
||||
$derivedKey = $this->kdf->pbkdf2(
|
||||
$userPassword,
|
||||
$encryptedData->getSalt(),
|
||||
$encryptedData->getKeyDerivation()['iterations'],
|
||||
32
|
||||
);
|
||||
|
||||
// Daten entschlüsseln
|
||||
$decryptedData = $this->encryption->decrypt(
|
||||
$encryptedData->getEncryptedData(),
|
||||
$derivedKey->getKey()
|
||||
);
|
||||
|
||||
return json_decode($decryptedData, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Audit-Trail mit Kryptographischen Fingerprints
|
||||
|
||||
### Secure Audit Trail
|
||||
|
||||
```php
|
||||
final readonly class AuditTrailService
|
||||
{
|
||||
public function __construct(
|
||||
private CryptographicUtilities $utils,
|
||||
private DigitalSignature $signature,
|
||||
private AuditLogRepository $repository
|
||||
) {}
|
||||
|
||||
public function logUserAction(
|
||||
User $user,
|
||||
string $action,
|
||||
array $context = []
|
||||
): AuditLogEntry {
|
||||
$timestamp = new DateTimeImmutable();
|
||||
$logData = [
|
||||
'user_id' => $user->getId(),
|
||||
'action' => $action,
|
||||
'context' => $context,
|
||||
'timestamp' => $timestamp->format('c'),
|
||||
'ip_address' => $this->getClientIpHash(),
|
||||
'user_agent_hash' => $this->getUserAgentHash()
|
||||
];
|
||||
|
||||
// Kryptographischen Fingerprint erstellen
|
||||
$dataString = json_encode($logData, JSON_SORT_KEYS);
|
||||
$fingerprint = hash('sha256', $dataString);
|
||||
|
||||
// Chain-Hash (verhindert Manipulation der Historie)
|
||||
$previousEntry = $this->repository->getLatestEntry();
|
||||
$chainHash = $previousEntry
|
||||
? hash('sha256', $previousEntry->getChainHash() . $fingerprint)
|
||||
: $fingerprint;
|
||||
|
||||
$auditEntry = new AuditLogEntry(
|
||||
id: Ulid::generate(),
|
||||
fingerprint: $fingerprint,
|
||||
chainHash: $chainHash,
|
||||
logData: $logData,
|
||||
createdAt: $timestamp
|
||||
);
|
||||
|
||||
return $this->repository->save($auditEntry);
|
||||
}
|
||||
|
||||
public function verifyAuditTrailIntegrity(): bool
|
||||
{
|
||||
$entries = $this->repository->getAllEntriesOrdered();
|
||||
$previousChainHash = null;
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
// Fingerprint verifizieren
|
||||
$dataString = json_encode($entry->getLogData(), JSON_SORT_KEYS);
|
||||
$expectedFingerprint = hash('sha256', $dataString);
|
||||
|
||||
if (!hash_equals($entry->getFingerprint(), $expectedFingerprint)) {
|
||||
return false; // Entry wurde manipuliert
|
||||
}
|
||||
|
||||
// Chain-Hash verifizieren
|
||||
$expectedChainHash = $previousChainHash
|
||||
? hash('sha256', $previousChainHash . $entry->getFingerprint())
|
||||
: $entry->getFingerprint();
|
||||
|
||||
if (!hash_equals($entry->getChainHash(), $expectedChainHash)) {
|
||||
return false; // Chain wurde unterbrochen
|
||||
}
|
||||
|
||||
$previousChainHash = $entry->getChainHash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getClientIpHash(): string
|
||||
{
|
||||
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
return hash('sha256', $clientIp); // IP nicht im Klartext speichern
|
||||
}
|
||||
|
||||
private function getUserAgentHash(): string
|
||||
{
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||||
return hash('sha256', $userAgent);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diese Beispiele zeigen die praktische Anwendung des Cryptography Modules in realen Szenarien und demonstrieren Best Practices für sichere Implementierungen.
|
||||
365
docs/components/cryptography/index.md
Normal file
365
docs/components/cryptography/index.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# Cryptography Module
|
||||
|
||||
Das Cryptography Module erweitert die bestehenden Verschlüsselungsfunktionen des Frameworks um moderne kryptographische Primitive und bietet sichere Implementierungen für Schlüsselableitung, digitale Signaturen, erweiterte Hash-Funktionen und sichere Token-Generierung.
|
||||
|
||||
## Überblick
|
||||
|
||||
Das Modul folgt den Framework-Prinzipien:
|
||||
- **Immutable Value Objects** für sichere Datenrepräsentation
|
||||
- **Service Pattern** für Business Logic
|
||||
- **Dependency Injection** für RandomGenerator Integration
|
||||
- **Security-First Design** mit timing-sicheren Operationen
|
||||
|
||||
## Core Services
|
||||
|
||||
### KeyDerivationFunction Service
|
||||
|
||||
Sichere Schlüsselableitung für Password-Hashing und Key-Stretching.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
use App\Framework\Random\SecureRandomGenerator;
|
||||
|
||||
$kdf = new KeyDerivationFunction(new SecureRandomGenerator());
|
||||
|
||||
// PBKDF2 (Standard)
|
||||
$derivedKey = $kdf->pbkdf2('password', $salt, 100000, 32);
|
||||
|
||||
// Argon2ID (Empfohlen)
|
||||
$derivedKey = $kdf->argon2id('password', $salt, 65536, 4, 3, 32);
|
||||
|
||||
// Password mit automatischer Salt-Generierung hashen
|
||||
$derivedKey = $kdf->hashPassword('password', 'argon2id');
|
||||
|
||||
// Password verifizieren
|
||||
$isValid = $kdf->verify('password', $derivedKey);
|
||||
```
|
||||
|
||||
**Unterstützte Algorithmen:**
|
||||
- **PBKDF2** mit SHA-256, SHA-512, SHA-384, SHA-224
|
||||
- **Argon2ID** (empfohlen für neue Implementierungen)
|
||||
- **scrypt** für spezielle Anwendungsfälle
|
||||
|
||||
### DigitalSignature Service
|
||||
|
||||
Digitale Signaturen für Datenintegrität und Authentifizierung.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\DigitalSignature;
|
||||
|
||||
$signature = new DigitalSignature(new SecureRandomGenerator());
|
||||
|
||||
// RSA Schlüsselpaar generieren
|
||||
$keyPair = $signature->generateRsaKeyPair(2048);
|
||||
|
||||
// Daten signieren
|
||||
$signatureResult = $signature->sign('data to sign', $keyPair->getPrivateKey());
|
||||
|
||||
// Signatur verifizieren
|
||||
$isValid = $signature->verify('data to sign', $signatureResult, $keyPair->getPublicKey());
|
||||
|
||||
// ECDSA mit secp256r1
|
||||
$ecKeyPair = $signature->generateEcdsaKeyPair('secp256r1');
|
||||
$ecSignature = $signature->signWithEcdsa('data', $ecKeyPair->getPrivateKey());
|
||||
```
|
||||
|
||||
### AdvancedHash Service
|
||||
|
||||
Erweiterte Hash-Funktionen über grundlegende Algorithmen hinaus.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\AdvancedHash;
|
||||
|
||||
$hash = new AdvancedHash();
|
||||
|
||||
// SHA-3 Familie
|
||||
$sha3 = $hash->sha3('data', 256);
|
||||
$shake = $hash->shake('data', 32, 128); // Extendable-output function
|
||||
|
||||
// BLAKE2
|
||||
$blake2b = $hash->blake2b('data', 32, 'optional-key');
|
||||
$blake2s = $hash->blake2s('data', 16);
|
||||
|
||||
// Hash-Ketten für komplexe Szenarien
|
||||
$chainResult = $hash->hashChain('data', ['sha3-256', 'blake2b']);
|
||||
```
|
||||
|
||||
### SecureTokenGenerator Service
|
||||
|
||||
Kryptographisch sichere Token-Generierung für APIs, Sessions und mehr.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\SecureTokenGenerator;
|
||||
|
||||
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
||||
|
||||
// API Keys mit Prefix
|
||||
$apiKey = $generator->generateApiKey('myapp'); // myapp_...
|
||||
|
||||
// Session Tokens
|
||||
$sessionToken = $generator->generateSessionToken();
|
||||
|
||||
// CSRF Tokens
|
||||
$csrfToken = $generator->generateCsrfToken();
|
||||
|
||||
// OTP Tokens
|
||||
$otp = $generator->generateOtpToken(6); // 6-stelliger numerischer Code
|
||||
|
||||
// Custom Tokens mit eigenem Alphabet
|
||||
$customToken = $generator->generateCustom('0123456789ABCDEF', 16);
|
||||
|
||||
// Batch-Generierung
|
||||
$tokens = $generator->generateBatch('session', 10, 32);
|
||||
```
|
||||
|
||||
**Token-Formate:**
|
||||
- `FORMAT_BASE64_URL` (Standard, URL-sicher)
|
||||
- `FORMAT_BASE64` (Standard Base64)
|
||||
- `FORMAT_HEX` (Hexadezimal)
|
||||
- `FORMAT_BASE32` (Base32)
|
||||
- `FORMAT_ALPHANUMERIC` (Buchstaben + Zahlen)
|
||||
|
||||
### CryptographicUtilities Service
|
||||
|
||||
Sammlung kryptographischer Utility-Funktionen.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\CryptographicUtilities;
|
||||
|
||||
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
||||
|
||||
// Timing-sichere String-Vergleiche
|
||||
$isEqual = $utils->timingSafeEquals($string1, $string2);
|
||||
|
||||
// Entropie-Validierung
|
||||
$isHighEntropy = $utils->validateEntropy($data, 7.0);
|
||||
$entropy = $utils->calculateShannonEntropy($data);
|
||||
|
||||
// Sichere UUIDs
|
||||
$uuid4 = $utils->generateUuid4(); // Zufällige UUID
|
||||
$uuid5 = $utils->generateUuid5($namespace, 'name'); // Deterministische UUID
|
||||
|
||||
// Key-Stretching
|
||||
$stretched = $utils->stretchKey('password', 'salt', 10000, 32);
|
||||
|
||||
// Speicher sicher löschen
|
||||
$utils->secureWipe($sensitiveData);
|
||||
```
|
||||
|
||||
## Value Objects
|
||||
|
||||
### DerivedKey
|
||||
|
||||
Unveränderliche Repräsentation abgeleiteter Schlüssel.
|
||||
|
||||
```php
|
||||
// Eigenschaften abrufen
|
||||
$algorithm = $derivedKey->getAlgorithm(); // 'pbkdf2-sha256'
|
||||
$keyLength = $derivedKey->getKeyLength(); // 32
|
||||
$iterations = $derivedKey->getIterations(); // 100000
|
||||
|
||||
// Verschiedene Formate
|
||||
$hex = $derivedKey->toHex();
|
||||
$base64 = $derivedKey->toBase64();
|
||||
|
||||
// Serialisierung
|
||||
$array = $derivedKey->toArray();
|
||||
$restored = DerivedKey::fromArray($array);
|
||||
|
||||
// Gleichheit prüfen (timing-sicher)
|
||||
$isEqual = $derivedKey->equals($otherKey);
|
||||
```
|
||||
|
||||
### SecureToken
|
||||
|
||||
Token mit Metadaten und sicherheitsfokussierten Funktionen.
|
||||
|
||||
```php
|
||||
// Token-Eigenschaften
|
||||
$type = $token->getType(); // 'api_key'
|
||||
$format = $token->getFormat(); // 'base64_url'
|
||||
$hasPrefix = $token->hasPrefix(); // true/false
|
||||
|
||||
// Sichere Operationen
|
||||
$isEqual = $token->equals($otherToken); // timing-sicher
|
||||
$isValid = $token->verify($candidateToken); // timing-sicher
|
||||
|
||||
// Sicherheit und Logging
|
||||
$masked = $token->getMaskedValue(); // myap****_Ab...fG
|
||||
$fingerprint = $token->getFingerprint(); // SHA-256 Hash
|
||||
$safeSummary = $token->getSafeSummary(); // Ohne Token-Wert
|
||||
```
|
||||
|
||||
### KeyPair, PrivateKey, PublicKey
|
||||
|
||||
Sichere Verwaltung asymmetrischer Schlüssel.
|
||||
|
||||
```php
|
||||
$keyPair = $signature->generateRsaKeyPair(2048);
|
||||
|
||||
$privateKey = $keyPair->getPrivateKey();
|
||||
$publicKey = $keyPair->getPublicKey();
|
||||
|
||||
// Schlüssel-Export
|
||||
$privatePem = $privateKey->toPem();
|
||||
$publicPem = $publicKey->toPem();
|
||||
|
||||
// Schlüssel-Import
|
||||
$privateKey = PrivateKey::fromPem($privatePem);
|
||||
$publicKey = PublicKey::fromPem($publicPem);
|
||||
```
|
||||
|
||||
## Sicherheitsfeatures
|
||||
|
||||
### Timing-Attack Schutz
|
||||
|
||||
Alle kritischen Vergleichsoperationen verwenden timing-sichere Implementierungen:
|
||||
|
||||
```php
|
||||
// Timing-sichere String-Vergleiche
|
||||
$utils->timingSafeEquals($secret1, $secret2);
|
||||
|
||||
// Timing-sichere Token-Verifikation
|
||||
$token->verify($candidateToken);
|
||||
|
||||
// Timing-sichere Array-Suche
|
||||
$utils->constantTimeArraySearch($haystack, $needle);
|
||||
```
|
||||
|
||||
### Entropie-Validierung
|
||||
|
||||
Automatische Validierung der Schlüsselstärke:
|
||||
|
||||
```php
|
||||
// Shannon-Entropie berechnen
|
||||
$entropy = $utils->calculateShannonEntropy($data);
|
||||
|
||||
// Mindest-Entropie validieren
|
||||
$isStrong = $utils->validateEntropy($data, 7.0);
|
||||
|
||||
// Schlüsselstärke prüfen
|
||||
$isValidKey = $utils->validateKeyStrength($key, 128); // Minimum 128 Bits
|
||||
```
|
||||
|
||||
### Sichere Speicher-Verwaltung
|
||||
|
||||
```php
|
||||
// Sensitive Daten sicher löschen
|
||||
$utils->secureWipe($password); // Überschreibt Speicher
|
||||
|
||||
// Automatische Metadaten-Bereinigung in Token-Logs
|
||||
$safeSummary = $token->getSafeSummary(); // Ohne sensitive Werte
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Empfohlene Parameter
|
||||
|
||||
```php
|
||||
// PBKDF2 (Minimum)
|
||||
$kdf->pbkdf2($password, $salt, 100000, 32, 'sha256');
|
||||
|
||||
// Argon2ID (Empfohlen)
|
||||
$kdf->argon2id($password, $salt, 65536, 4, 3, 32);
|
||||
|
||||
// RSA Schlüssel (Minimum 2048 Bit)
|
||||
$signature->generateRsaKeyPair(2048);
|
||||
|
||||
// Token-Längen
|
||||
$generator->generateApiKey('prefix', 32); // 256 Bit
|
||||
$generator->generateSessionToken(48); // 384 Bit für langlebige Sessions
|
||||
```
|
||||
|
||||
### Sichere Implementierung
|
||||
|
||||
```php
|
||||
// ✅ Gute Praxis
|
||||
final readonly class AuthService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly KeyDerivationFunction $kdf,
|
||||
private readonly SecureTokenGenerator $tokenGenerator
|
||||
) {}
|
||||
|
||||
public function hashPassword(string $password): DerivedKey
|
||||
{
|
||||
return $this->kdf->hashPassword($password, 'argon2id');
|
||||
}
|
||||
|
||||
public function generateApiKey(string $prefix): SecureToken
|
||||
{
|
||||
return $this->tokenGenerator->generateApiKey($prefix, 32);
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Schlechte Praxis - Nicht verwenden
|
||||
// $hashedPassword = md5($password); // Unsicher
|
||||
// $token = bin2hex(random_bytes(16)); // Zu kurz, keine Metadaten
|
||||
```
|
||||
|
||||
## Integration mit Framework
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
```php
|
||||
final readonly class CryptographyServiceInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
$randomGenerator = $container->get(RandomGenerator::class);
|
||||
|
||||
$container->singleton(KeyDerivationFunction::class,
|
||||
new KeyDerivationFunction($randomGenerator));
|
||||
|
||||
$container->singleton(SecureTokenGenerator::class,
|
||||
new SecureTokenGenerator($randomGenerator));
|
||||
|
||||
$container->singleton(CryptographicUtilities::class,
|
||||
new CryptographicUtilities($randomGenerator));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controller Integration
|
||||
|
||||
```php
|
||||
final readonly class ApiKeyController
|
||||
{
|
||||
#[Route(path: '/admin/api-keys', method: Method::POST)]
|
||||
#[Auth(strategy: 'ip', allowedIps: ['127.0.0.1'])]
|
||||
public function createApiKey(
|
||||
CreateApiKeyRequest $request,
|
||||
SecureTokenGenerator $tokenGenerator
|
||||
): JsonResult {
|
||||
$apiKey = $tokenGenerator->generateApiKey(
|
||||
prefix: $request->prefix,
|
||||
length: 32
|
||||
);
|
||||
|
||||
// Token sicher in Datenbank speichern
|
||||
// Nur Hash oder verschlüsselte Version speichern, nie Klartext
|
||||
|
||||
return new JsonResult([
|
||||
'api_key' => $apiKey->getValue(),
|
||||
'fingerprint' => $apiKey->getShortFingerprint(),
|
||||
'created_at' => $apiKey->getCreatedAt()->format('c')
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Das Cryptography Module ist für Production-Einsatz optimiert:
|
||||
|
||||
- **Cached Reflection Provider** für Dependency Injection
|
||||
- **Batch-Operationen** für Token-Generierung
|
||||
- **Effiziente Algorithmen** mit modernen kryptographischen Standards
|
||||
- **Minimale Speicher-Allokation** durch readonly Value Objects
|
||||
|
||||
## Weiterführende Dokumentation
|
||||
|
||||
- [Konfiguration](configuration.md) - Setup und Anpassung
|
||||
- [Beispiele](examples.md) - Praktische Anwendungsfälle
|
||||
- [Migration](migration.md) - Upgrade von älteren Krypto-Implementierungen
|
||||
- [Sicherheit](security.md) - Detaillierte Sicherheitsrichtlinien
|
||||
728
docs/components/cryptography/security.md
Normal file
728
docs/components/cryptography/security.md
Normal file
@@ -0,0 +1,728 @@
|
||||
# Cryptography Module - Sicherheitsrichtlinien
|
||||
|
||||
Detaillierte Sicherheitsrichtlinien und Best Practices für das Cryptography Module.
|
||||
|
||||
## Grundlegende Sicherheitsprinzipien
|
||||
|
||||
### Defense in Depth
|
||||
|
||||
Das Cryptography Module implementiert mehrschichtigen Schutz:
|
||||
|
||||
1. **Algorithmus-Ebene**: Moderne, peer-reviewed Algorithmen (Argon2ID, SHA-3, ECDSA)
|
||||
2. **Implementation-Ebene**: Timing-sichere Operationen und sichere Speicherverwaltung
|
||||
3. **Application-Ebene**: Sichere Integration und Konfiguration
|
||||
4. **System-Ebene**: Sicherer Random Number Generator und Entropie-Validierung
|
||||
|
||||
### Zero-Trust Architektur
|
||||
|
||||
```php
|
||||
// ✅ Vertraue niemals Eingaben - validiere alles
|
||||
final readonly class SecureTokenValidator
|
||||
{
|
||||
public function validateApiKey(string $providedKey): ?ApiKeyContext
|
||||
{
|
||||
// Eingabe-Validierung
|
||||
if (empty($providedKey) || strlen($providedKey) < 32) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format-Validierung
|
||||
if (!$this->tokenGenerator->isValidFormat($providedKey, 'base64_url')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich mit gespeichertem Hash
|
||||
$keyHash = hash('sha256', $providedKey);
|
||||
$storedKey = $this->repository->findByHash($keyHash);
|
||||
|
||||
if (!$storedKey || !hash_equals($keyHash, $storedKey->getHash())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Zusätzliche Sicherheitsprüfungen
|
||||
return $this->performSecurityChecks($storedKey);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kryptographische Best Practices
|
||||
|
||||
### Sichere Schlüssel-Ableitung
|
||||
|
||||
```php
|
||||
// ✅ Empfohlene Parameter für verschiedene Szenarien
|
||||
final readonly class SecureKeyDerivation
|
||||
{
|
||||
private const SECURITY_LEVELS = [
|
||||
// Hohe Sicherheit für sensitive Daten
|
||||
'high_security' => [
|
||||
'algorithm' => 'argon2id',
|
||||
'memory_cost' => 131072, // 128 MB
|
||||
'time_cost' => 6, // 6 Iterationen
|
||||
'threads' => 4, // 4 Threads
|
||||
'key_length' => 32 // 256 Bit
|
||||
],
|
||||
|
||||
// Standard für normale Anwendungen
|
||||
'standard' => [
|
||||
'algorithm' => 'argon2id',
|
||||
'memory_cost' => 65536, // 64 MB
|
||||
'time_cost' => 4, // 4 Iterationen
|
||||
'threads' => 3, // 3 Threads
|
||||
'key_length' => 32 // 256 Bit
|
||||
],
|
||||
|
||||
// Kompatibilität mit älteren Systemen
|
||||
'legacy_compatible' => [
|
||||
'algorithm' => 'pbkdf2-sha256',
|
||||
'iterations' => 200000, // 200k Iterationen
|
||||
'key_length' => 32 // 256 Bit
|
||||
]
|
||||
];
|
||||
|
||||
public function hashPassword(string $password, string $level = 'standard'): DerivedKey
|
||||
{
|
||||
$params = self::SECURITY_LEVELS[$level] ?? self::SECURITY_LEVELS['standard'];
|
||||
|
||||
return match ($params['algorithm']) {
|
||||
'argon2id' => $this->kdf->argon2id(
|
||||
$password,
|
||||
$this->kdf->generateSalt(32),
|
||||
$params['memory_cost'],
|
||||
$params['time_cost'],
|
||||
$params['threads'],
|
||||
$params['key_length']
|
||||
),
|
||||
'pbkdf2-sha256' => $this->kdf->pbkdf2(
|
||||
$password,
|
||||
$this->kdf->generateSalt(32),
|
||||
$params['iterations'],
|
||||
$params['key_length']
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sichere Token-Generierung
|
||||
|
||||
```php
|
||||
// ✅ Token-Sicherheit nach Verwendungszweck
|
||||
final readonly class SecurityAwareTokenGenerator
|
||||
{
|
||||
private const TOKEN_SECURITY_REQUIREMENTS = [
|
||||
'api_key' => [
|
||||
'length' => 32, // 256 Bit
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.0,
|
||||
'expires' => false, // Langlebig
|
||||
'single_use' => false
|
||||
],
|
||||
|
||||
'session' => [
|
||||
'length' => 48, // 384 Bit (höhere Sicherheit)
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.5,
|
||||
'expires' => true,
|
||||
'single_use' => false
|
||||
],
|
||||
|
||||
'csrf' => [
|
||||
'length' => 32, // 256 Bit
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.0,
|
||||
'expires' => true, // Kurze Lebensdauer
|
||||
'single_use' => true // Einmalverwendung
|
||||
],
|
||||
|
||||
'password_reset' => [
|
||||
'length' => 64, // 512 Bit (maximale Sicherheit)
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.8,
|
||||
'expires' => true,
|
||||
'single_use' => true,
|
||||
'max_lifetime' => 3600 * 2 // 2 Stunden
|
||||
]
|
||||
];
|
||||
|
||||
public function generateSecureToken(string $type): SecureToken
|
||||
{
|
||||
$requirements = self::TOKEN_SECURITY_REQUIREMENTS[$type] ??
|
||||
throw new InvalidArgumentException("Unknown token type: {$type}");
|
||||
|
||||
do {
|
||||
$token = $this->tokenGenerator->generate(
|
||||
$type,
|
||||
$requirements['length'],
|
||||
$requirements['format']
|
||||
);
|
||||
|
||||
// Entropie validieren
|
||||
$entropy = $this->utils->calculateShannonEntropy($token->getRawBytes());
|
||||
} while ($entropy < $requirements['entropy_min']);
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Timing-Attack Prevention
|
||||
|
||||
### Konstante Ausführungszeit
|
||||
|
||||
```php
|
||||
// ✅ Timing-sichere Implementierung
|
||||
final readonly class TimingSafeOperations
|
||||
{
|
||||
public function authenticateUser(string $email, string $password): ?User
|
||||
{
|
||||
$user = $this->userRepository->findByEmail(new Email($email));
|
||||
|
||||
// WICHTIG: Immer Hashing durchführen, auch bei ungültigem User
|
||||
$providedHash = $user
|
||||
? $user->getPasswordHash()
|
||||
: $this->createDummyHash(); // Konstante Ausführungszeit
|
||||
|
||||
$isValid = $this->kdf->verify($password, $providedHash);
|
||||
|
||||
// Zusätzliche konstante Verzögerung
|
||||
$this->enforceConstantTime();
|
||||
|
||||
return $isValid && $user ? $user : null;
|
||||
}
|
||||
|
||||
private function createDummyHash(): DerivedKey
|
||||
{
|
||||
// Dummy-Hash mit gleichen Parametern wie echte Hashes
|
||||
static $dummyHash = null;
|
||||
|
||||
if ($dummyHash === null) {
|
||||
$dummyHash = $this->kdf->hashPassword('dummy-password', 'argon2id');
|
||||
}
|
||||
|
||||
return $dummyHash;
|
||||
}
|
||||
|
||||
private function enforceConstantTime(): void
|
||||
{
|
||||
// Minimale zusätzliche Verzögerung (1-5ms)
|
||||
usleep(random_int(1000, 5000));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Side-Channel Resistance
|
||||
|
||||
```php
|
||||
// ✅ Schutz vor Side-Channel-Angriffen
|
||||
final readonly class SideChannelResistantComparison
|
||||
{
|
||||
public function compareSecrets(string $secret1, string $secret2): bool
|
||||
{
|
||||
// PHP's hash_equals ist timing-sicher implementiert
|
||||
return hash_equals($secret1, $secret2);
|
||||
}
|
||||
|
||||
public function compareArrays(array $array1, array $array2): bool
|
||||
{
|
||||
// Arrays zu Strings serialisieren für timing-sicheren Vergleich
|
||||
$string1 = json_encode($array1, JSON_SORT_KEYS);
|
||||
$string2 = json_encode($array2, JSON_SORT_KEYS);
|
||||
|
||||
return hash_equals($string1, $string2);
|
||||
}
|
||||
|
||||
public function searchInArray(array $haystack, mixed $needle): bool
|
||||
{
|
||||
$found = false;
|
||||
|
||||
// Alle Elemente durchgehen (konstante Zeit)
|
||||
foreach ($haystack as $value) {
|
||||
$isMatch = is_string($value) && is_string($needle)
|
||||
? hash_equals($value, $needle)
|
||||
: ($value === $needle);
|
||||
|
||||
$found = $found || $isMatch; // Kein frühes Beenden
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sichere Speicherverwaltung
|
||||
|
||||
### Sensitive Data Handling
|
||||
|
||||
```php
|
||||
// ✅ Sicherer Umgang mit sensitiven Daten
|
||||
final readonly class SecureMemoryManagement
|
||||
{
|
||||
public function processSecretData(string $secretData): ProcessedResult
|
||||
{
|
||||
try {
|
||||
// Daten verarbeiten
|
||||
$result = $this->doSecretProcessing($secretData);
|
||||
|
||||
// Sensitive Daten aus Speicher löschen
|
||||
$this->utils->secureWipe($secretData);
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Auch bei Fehlern Speicher löschen
|
||||
$this->utils->secureWipe($secretData);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function createSecureTemporaryFile(string $data): SecureTemporaryFile
|
||||
{
|
||||
// Temporary file mit restriktiven Berechtigungen erstellen
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'secure_');
|
||||
chmod($tempFile, 0600); // Nur Owner kann lesen/schreiben
|
||||
|
||||
// Daten schreiben und sofort aus Speicher löschen
|
||||
file_put_contents($tempFile, $data);
|
||||
$this->utils->secureWipe($data);
|
||||
|
||||
// Auto-cleanup mit Destruktor
|
||||
return new SecureTemporaryFile($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-cleanup für temporäre Dateien
|
||||
final class SecureTemporaryFile
|
||||
{
|
||||
public function __construct(private string $filePath) {}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (file_exists($this->filePath)) {
|
||||
// Datei mehrfach überschreiben vor dem Löschen
|
||||
$fileSize = filesize($this->filePath);
|
||||
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
file_put_contents($this->filePath, random_bytes($fileSize));
|
||||
}
|
||||
|
||||
unlink($this->filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Input Validation und Sanitization
|
||||
|
||||
### Comprehensive Input Validation
|
||||
|
||||
```php
|
||||
// ✅ Umfassende Eingabe-Validierung
|
||||
final readonly class CryptographicInputValidator
|
||||
{
|
||||
public function validatePasswordStrength(string $password): ValidationResult
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Länge prüfen
|
||||
if (strlen($password) < 8) {
|
||||
$errors[] = 'Password must be at least 8 characters long';
|
||||
}
|
||||
|
||||
// Komplexität prüfen
|
||||
if (!preg_match('/[A-Z]/', $password)) {
|
||||
$errors[] = 'Password must contain uppercase letters';
|
||||
}
|
||||
|
||||
if (!preg_match('/[a-z]/', $password)) {
|
||||
$errors[] = 'Password must contain lowercase letters';
|
||||
}
|
||||
|
||||
if (!preg_match('/[0-9]/', $password)) {
|
||||
$errors[] = 'Password must contain numbers';
|
||||
}
|
||||
|
||||
if (!preg_match('/[^A-Za-z0-9]/', $password)) {
|
||||
$errors[] = 'Password must contain special characters';
|
||||
}
|
||||
|
||||
// Entropie prüfen
|
||||
$entropy = $this->utils->calculateShannonEntropy($password);
|
||||
if ($entropy < 3.5) {
|
||||
$errors[] = 'Password has insufficient entropy';
|
||||
}
|
||||
|
||||
// Gegen Common-Passwords prüfen
|
||||
if ($this->isCommonPassword($password)) {
|
||||
$errors[] = 'Password is too common';
|
||||
}
|
||||
|
||||
return new ValidationResult(empty($errors), $errors);
|
||||
}
|
||||
|
||||
public function validateApiKeyFormat(string $apiKey): bool
|
||||
{
|
||||
// Länge prüfen (32-64 Zeichen)
|
||||
if (strlen($apiKey) < 32 || strlen($apiKey) > 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Format prüfen (Base64URL)
|
||||
if (!preg_match('/^[A-Za-z0-9_-]+$/', $apiKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Entropie validieren
|
||||
$entropy = $this->utils->calculateShannonEntropy($apiKey);
|
||||
return $entropy >= 6.0;
|
||||
}
|
||||
|
||||
public function sanitizeUserInput(string $input): string
|
||||
{
|
||||
// Whitespace normalisieren
|
||||
$sanitized = trim($input);
|
||||
|
||||
// Null-Bytes entfernen (verhindern Directory Traversal)
|
||||
$sanitized = str_replace("\0", '', $sanitized);
|
||||
|
||||
// Control Characters entfernen
|
||||
$sanitized = preg_replace('/[\x00-\x1F\x7F]/', '', $sanitized);
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
private function isCommonPassword(string $password): bool
|
||||
{
|
||||
$commonPasswords = [
|
||||
'password', '123456', 'password123', 'admin', 'qwerty',
|
||||
'letmein', 'welcome', 'monkey', '1234567890', 'password1'
|
||||
];
|
||||
|
||||
$lowercase = strtolower($password);
|
||||
|
||||
foreach ($commonPasswords as $common) {
|
||||
if ($lowercase === $common ||
|
||||
levenshtein($lowercase, $common) <= 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limiting und Abuse Prevention
|
||||
|
||||
### Cryptographic Rate Limiting
|
||||
|
||||
```php
|
||||
// ✅ Rate-Limiting für kryptographische Operationen
|
||||
final readonly class CryptographicRateLimiter
|
||||
{
|
||||
private const LIMITS = [
|
||||
'password_attempts' => ['count' => 5, 'window' => 900], // 5 in 15 Min
|
||||
'token_generation' => ['count' => 100, 'window' => 3600], // 100 in 1 Stunde
|
||||
'password_reset' => ['count' => 3, 'window' => 3600], // 3 in 1 Stunde
|
||||
'otp_generation' => ['count' => 5, 'window' => 300], // 5 in 5 Min
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private CacheInterface $cache,
|
||||
private SecurityEventLogger $securityLogger
|
||||
) {}
|
||||
|
||||
public function checkLimit(string $operation, string $identifier): bool
|
||||
{
|
||||
$limit = self::LIMITS[$operation] ?? null;
|
||||
|
||||
if (!$limit) {
|
||||
return true; // Kein Limit definiert
|
||||
}
|
||||
|
||||
$cacheKey = "ratelimit_{$operation}_{$identifier}";
|
||||
$attempts = $this->cache->get($cacheKey, []);
|
||||
$now = time();
|
||||
|
||||
// Abgelaufene Versuche entfernen
|
||||
$attempts = array_filter($attempts, fn($timestamp) =>
|
||||
$now - $timestamp < $limit['window']);
|
||||
|
||||
if (count($attempts) >= $limit['count']) {
|
||||
// Rate-Limit erreicht
|
||||
$this->securityLogger->logRateLimitExceeded($operation, $identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Neuen Versuch hinzufügen
|
||||
$attempts[] = $now;
|
||||
$this->cache->set($cacheKey, $attempts, $limit['window']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function recordFailedAttempt(string $operation, string $identifier): void
|
||||
{
|
||||
$cacheKey = "failed_{$operation}_{$identifier}";
|
||||
$failures = $this->cache->get($cacheKey, 0);
|
||||
$failures++;
|
||||
|
||||
$this->cache->set($cacheKey, $failures, 3600); // 1 Stunde
|
||||
|
||||
// Bei vielen Fehlversuchen Sicherheitsteam benachrichtigen
|
||||
if ($failures >= 10) {
|
||||
$this->securityLogger->logSuspiciousActivity(
|
||||
$operation,
|
||||
$identifier,
|
||||
"Multiple failed attempts: {$failures}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling und Information Leakage Prevention
|
||||
|
||||
### Secure Error Handling
|
||||
|
||||
```php
|
||||
// ✅ Sichere Fehlerbehandlung ohne Information Leakage
|
||||
final readonly class SecureErrorHandler
|
||||
{
|
||||
public function handleCryptographicError(Throwable $error): ErrorResponse
|
||||
{
|
||||
// Detaillierte Logs für interne Analyse
|
||||
$this->logger->error('Cryptographic error occurred', [
|
||||
'error_type' => get_class($error),
|
||||
'message' => $error->getMessage(),
|
||||
'trace' => $error->getTraceAsString(),
|
||||
'context' => $this->getSecureContext()
|
||||
]);
|
||||
|
||||
// Generische Antwort für Client (keine Details)
|
||||
$publicMessage = match (true) {
|
||||
$error instanceof InvalidPasswordException => 'Authentication failed',
|
||||
$error instanceof TokenExpiredException => 'Token has expired',
|
||||
$error instanceof TokenInvalidException => 'Invalid token',
|
||||
$error instanceof CryptographicException => 'Security operation failed',
|
||||
default => 'Internal error occurred'
|
||||
};
|
||||
|
||||
return new ErrorResponse(
|
||||
message: $publicMessage,
|
||||
code: $this->getPublicErrorCode($error),
|
||||
timestamp: new DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
|
||||
private function getSecureContext(): array
|
||||
{
|
||||
return [
|
||||
'request_id' => $this->generateRequestId(),
|
||||
'user_agent_hash' => hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? ''),
|
||||
'ip_hash' => hash('sha256', $_SERVER['REMOTE_ADDR'] ?? ''),
|
||||
'timestamp' => time()
|
||||
];
|
||||
}
|
||||
|
||||
private function generateRequestId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring und Alerting
|
||||
|
||||
### Security Monitoring
|
||||
|
||||
```php
|
||||
// ✅ Sicherheits-Monitoring für kryptographische Operationen
|
||||
final readonly class CryptographicSecurityMonitor
|
||||
{
|
||||
public function __construct(
|
||||
private MetricsCollector $metrics,
|
||||
private AlertManager $alerts,
|
||||
private SecurityEventLogger $securityLogger
|
||||
) {}
|
||||
|
||||
public function monitorOperation(string $operation, callable $callback): mixed
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$operationId = bin2hex(random_bytes(8));
|
||||
|
||||
try {
|
||||
$result = $callback();
|
||||
|
||||
// Erfolgreiche Operation protokollieren
|
||||
$duration = microtime(true) - $startTime;
|
||||
$this->metrics->increment("crypto.{$operation}.success");
|
||||
$this->metrics->timing("crypto.{$operation}.duration", $duration);
|
||||
|
||||
// Ungewöhnlich lange Operationen melden
|
||||
if ($duration > $this->getExpectedDuration($operation) * 2) {
|
||||
$this->alerts->sendAlert(
|
||||
level: 'warning',
|
||||
message: "Slow cryptographic operation: {$operation}",
|
||||
context: ['duration' => $duration, 'operation_id' => $operationId]
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Fehlgeschlagene Operation protokollieren
|
||||
$this->metrics->increment("crypto.{$operation}.failure");
|
||||
$this->securityLogger->logCryptographicFailure($operation, $e, $operationId);
|
||||
|
||||
// Bei kritischen Fehlern sofort alarmieren
|
||||
if ($this->isCriticalError($e)) {
|
||||
$this->alerts->sendAlert(
|
||||
level: 'critical',
|
||||
message: "Critical cryptographic failure: {$operation}",
|
||||
context: [
|
||||
'error' => $e->getMessage(),
|
||||
'operation_id' => $operationId
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function detectAnomalousPatterns(): void
|
||||
{
|
||||
// Erhöhte Fehlerrate erkennen
|
||||
$errorRate = $this->metrics->getRate('crypto.*.failure', '5m');
|
||||
if ($errorRate > 0.1) { // >10% Fehlerrate
|
||||
$this->alerts->sendAlert(
|
||||
level: 'warning',
|
||||
message: 'High cryptographic error rate detected',
|
||||
context: ['error_rate' => $errorRate]
|
||||
);
|
||||
}
|
||||
|
||||
// Ungewöhnliche Aktivitätsmuster
|
||||
$operations = $this->metrics->getCount('crypto.*.success', '1h');
|
||||
$baseline = $this->getHistoricalBaseline();
|
||||
|
||||
if ($operations > $baseline * 5) {
|
||||
$this->alerts->sendAlert(
|
||||
level: 'warning',
|
||||
message: 'Unusual spike in cryptographic operations',
|
||||
context: ['operations' => $operations, 'baseline' => $baseline]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function getExpectedDuration(string $operation): float
|
||||
{
|
||||
return match ($operation) {
|
||||
'token_generation' => 0.001, // 1ms
|
||||
'password_hash' => 0.5, // 500ms (Argon2ID)
|
||||
'password_verify' => 0.5, // 500ms
|
||||
'signature_create' => 0.01, // 10ms
|
||||
'signature_verify' => 0.005, // 5ms
|
||||
default => 0.1 // 100ms
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Compliance und Audit
|
||||
|
||||
### Compliance Framework
|
||||
|
||||
```php
|
||||
// ✅ Compliance-Unterstützung für Regulierungen
|
||||
final readonly class CryptographicComplianceValidator
|
||||
{
|
||||
private const COMPLIANCE_REQUIREMENTS = [
|
||||
'GDPR' => [
|
||||
'encryption_required' => true,
|
||||
'key_length_min' => 256,
|
||||
'algorithm_approved' => ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
||||
'key_rotation_days' => 365,
|
||||
'audit_trail_required' => true
|
||||
],
|
||||
|
||||
'PCI_DSS' => [
|
||||
'encryption_required' => true,
|
||||
'key_length_min' => 256,
|
||||
'algorithm_approved' => ['AES-256', 'RSA-2048'],
|
||||
'key_rotation_days' => 365,
|
||||
'secure_key_storage' => true
|
||||
],
|
||||
|
||||
'FIPS_140_2' => [
|
||||
'approved_algorithms_only' => true,
|
||||
'algorithm_approved' => ['AES', 'SHA-256', 'RSA', 'ECDSA'],
|
||||
'random_number_generator' => 'FIPS_approved',
|
||||
'key_length_min' => 256
|
||||
]
|
||||
];
|
||||
|
||||
public function validateCompliance(string $standard): ComplianceReport
|
||||
{
|
||||
$requirements = self::COMPLIANCE_REQUIREMENTS[$standard] ??
|
||||
throw new InvalidArgumentException("Unknown standard: {$standard}");
|
||||
|
||||
$violations = [];
|
||||
$report = new ComplianceReport($standard);
|
||||
|
||||
// Algorithmus-Compliance prüfen
|
||||
if (isset($requirements['algorithm_approved'])) {
|
||||
$usedAlgorithms = $this->getUsedAlgorithms();
|
||||
$approvedAlgorithms = $requirements['algorithm_approved'];
|
||||
|
||||
foreach ($usedAlgorithms as $algorithm) {
|
||||
if (!in_array($algorithm, $approvedAlgorithms)) {
|
||||
$violations[] = "Non-approved algorithm in use: {$algorithm}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schlüssel-Längen prüfen
|
||||
if (isset($requirements['key_length_min'])) {
|
||||
$keyLengths = $this->getKeyLengths();
|
||||
$minLength = $requirements['key_length_min'];
|
||||
|
||||
foreach ($keyLengths as $context => $length) {
|
||||
if ($length < $minLength) {
|
||||
$violations[] = "Key length too short in {$context}: {$length} < {$minLength}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schlüssel-Rotation prüfen
|
||||
if (isset($requirements['key_rotation_days'])) {
|
||||
$rotationViolations = $this->checkKeyRotation($requirements['key_rotation_days']);
|
||||
$violations = array_merge($violations, $rotationViolations);
|
||||
}
|
||||
|
||||
$report->setViolations($violations);
|
||||
$report->setCompliant(empty($violations));
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
public function generateAuditReport(): AuditReport
|
||||
{
|
||||
return new AuditReport([
|
||||
'cryptographic_operations' => $this->getCryptoOperationStats(),
|
||||
'key_management' => $this->getKeyManagementStats(),
|
||||
'algorithm_usage' => $this->getAlgorithmUsageStats(),
|
||||
'security_events' => $this->getSecurityEventSummary(),
|
||||
'compliance_status' => $this->getComplianceStatus()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diese Sicherheitsrichtlinien stellen sicher, dass das Cryptography Module höchste Sicherheitsstandards erfüllt und gegen moderne Angriffsvektoren geschützt ist.
|
||||
417
docs/components/security/csrf-protection.md
Normal file
417
docs/components/security/csrf-protection.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# CSRF-Schutz
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des CSRF-Schutzes korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Cross-Site Request Forgery (CSRF) ist eine Art von Angriff, bei dem ein Angreifer einen Benutzer dazu bringt, unbeabsichtigt Aktionen auf einer Website auszuführen, bei der er bereits authentifiziert ist. Das Framework bietet einen robusten CSRF-Schutzmechanismus, der solche Angriffe verhindert, indem es einzigartige Token generiert und validiert.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### CsrfToken
|
||||
|
||||
Die `CsrfToken`-Klasse ist ein Value Object, das einen CSRF-Token repräsentiert:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\CsrfToken;
|
||||
|
||||
// Token aus einem String erstellen
|
||||
$token = CsrfToken::fromString($tokenString);
|
||||
|
||||
// Token-Wert als String abrufen
|
||||
$tokenString = $token->toString();
|
||||
// oder
|
||||
$tokenString = (string)$token;
|
||||
```
|
||||
|
||||
Die Klasse stellt sicher, dass CSRF-Token immer gültige 64-Zeichen lange hexadezimale Strings sind und bietet Typsicherheit für CSRF-Token-Operationen.
|
||||
|
||||
### CsrfTokenGenerator
|
||||
|
||||
Die `CsrfTokenGenerator`-Klasse ist verantwortlich für die Generierung neuer CSRF-Token:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\CsrfTokenGenerator;
|
||||
|
||||
// Generator initialisieren
|
||||
$generator = new CsrfTokenGenerator($randomGenerator);
|
||||
|
||||
// Neues Token generieren
|
||||
$token = $generator->generate();
|
||||
```
|
||||
|
||||
Die Klasse verwendet einen kryptografisch sicheren Zufallszahlengenerator, um Token zu erzeugen, die nicht vorhersehbar sind.
|
||||
|
||||
### CsrfTokenData
|
||||
|
||||
Die `CsrfTokenData`-Klasse erweitert das einfache Token um zusätzliche Metadaten:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\CsrfTokenData;
|
||||
|
||||
// Token-Daten mit Metadaten erstellen
|
||||
$tokenData = new CsrfTokenData(
|
||||
$token,
|
||||
$clientIp,
|
||||
$targetPath,
|
||||
$expiresAt
|
||||
);
|
||||
|
||||
// Prüfen, ob das Token abgelaufen ist
|
||||
if ($tokenData->isExpired($clock)) {
|
||||
// Token ist abgelaufen
|
||||
}
|
||||
|
||||
// Prüfen, ob das Token bereits verwendet wurde
|
||||
if ($tokenData->isUsed()) {
|
||||
// Token wurde bereits verwendet
|
||||
}
|
||||
```
|
||||
|
||||
Diese Klasse ermöglicht erweiterte Sicherheitsfunktionen wie Token-Ablauf, Verwendungsverfolgung und Bindung an bestimmte Pfade oder IP-Adressen.
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Token generieren und in Formularen verwenden
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
// Token generieren
|
||||
$csrfToken = $this->csrfTokenGenerator->generate();
|
||||
|
||||
// Token in der Session speichern
|
||||
$this->session->set('csrf_token', $csrfToken->toString());
|
||||
|
||||
// Token im Formular einbinden
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $csrfToken->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
In der Formularvorlage:
|
||||
|
||||
```html
|
||||
<form method="post" action="/submit">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<!-- Weitere Formularfelder -->
|
||||
<button type="submit">Absenden</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Token validieren
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
// Gespeichertes Token abrufen
|
||||
$storedToken = CsrfToken::fromString($this->session->get('csrf_token'));
|
||||
|
||||
// Übermitteltes Token abrufen
|
||||
$submittedToken = $request->getPostParam('csrf_token');
|
||||
|
||||
// Token vergleichen (zeitsichere Vergleichsmethode)
|
||||
if (!$storedToken->equalsString($submittedToken)) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
|
||||
// Token ist gültig, Formular verarbeiten
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Erweiterte Token-Validierung mit Metadaten
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
// Token mit Metadaten generieren
|
||||
$token = $this->csrfTokenGenerator->generate();
|
||||
$tokenData = new CsrfTokenData(
|
||||
$token,
|
||||
$this->request->getClientIp(),
|
||||
$this->request->path,
|
||||
new \DateTimeImmutable('+1 hour')
|
||||
);
|
||||
|
||||
// Token-Daten in der Session speichern
|
||||
$this->session->set('csrf_token_data', $tokenData->toArray());
|
||||
|
||||
// Token im Formular einbinden
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $token->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
// Token-Daten aus der Session abrufen
|
||||
$tokenDataArray = $this->session->get('csrf_token_data');
|
||||
$tokenData = CsrfTokenData::fromArray($tokenDataArray);
|
||||
|
||||
// Übermitteltes Token abrufen
|
||||
$submittedToken = $request->getPostParam('csrf_token');
|
||||
|
||||
// Umfassende Validierung durchführen
|
||||
if ($tokenData->isExpired($this->clock)) {
|
||||
throw new SecurityException('CSRF-Token ist abgelaufen');
|
||||
}
|
||||
|
||||
if ($tokenData->isUsed()) {
|
||||
throw new SecurityException('CSRF-Token wurde bereits verwendet');
|
||||
}
|
||||
|
||||
if ($tokenData->clientIp !== $request->getClientIp()) {
|
||||
throw new SecurityException('CSRF-Token ist an eine andere IP-Adresse gebunden');
|
||||
}
|
||||
|
||||
if (!$tokenData->token->equalsString($submittedToken)) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
|
||||
// Token als verwendet markieren
|
||||
$tokenData->markAsUsed();
|
||||
$this->session->set('csrf_token_data', $tokenData->toArray());
|
||||
|
||||
// Token ist gültig, Formular verarbeiten
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit dem Framework
|
||||
|
||||
### CSRF-Middleware
|
||||
|
||||
Das Framework bietet eine `CsrfProtectionMiddleware`, die automatisch CSRF-Schutz für alle POST, PUT, DELETE und PATCH-Anfragen implementiert:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(CsrfProtectionMiddleware::class);
|
||||
```
|
||||
|
||||
Die Middleware:
|
||||
1. Generiert automatisch CSRF-Token für alle Formulare
|
||||
2. Validiert Token für alle Anfragen, die den Zustand ändern
|
||||
3. Wirft eine Exception oder gibt eine Fehlerantwort zurück, wenn die Validierung fehlschlägt
|
||||
|
||||
### Konfiguration
|
||||
|
||||
Die CSRF-Schutzfunktionen können in der Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'csrf' => [
|
||||
'enabled' => true,
|
||||
'token_lifetime' => 3600, // Sekunden
|
||||
'exclude_routes' => [
|
||||
'/api/webhook',
|
||||
'/api/external/callback',
|
||||
],
|
||||
'exclude_patterns' => [
|
||||
'#^/api/public/#', // Regulärer Ausdruck für auszuschließende Routen
|
||||
],
|
||||
'header_name' => 'X-CSRF-Token', // Name des HTTP-Headers für API-Anfragen
|
||||
'parameter_name' => 'csrf_token', // Name des Formularfelds
|
||||
'strict_mode' => true, // Strikte Validierung (IP-Bindung, Einmalverwendung)
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Template-Integration
|
||||
|
||||
Das Framework bietet eine einfache Möglichkeit, CSRF-Token in Templates einzubinden:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $this->csrfProtection->getToken()->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
Mit der Template-Funktion:
|
||||
|
||||
```twig
|
||||
{# In einer Twig-Vorlage #}
|
||||
<form method="post" action="/submit">
|
||||
{{ csrf_field() }}
|
||||
<!-- Weitere Formularfelder -->
|
||||
<button type="submit">Absenden</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Die `csrf_field()`-Funktion generiert automatisch ein verstecktes Formularfeld mit dem aktuellen CSRF-Token.
|
||||
|
||||
### API-Integration
|
||||
|
||||
Für API-Anfragen kann das CSRF-Token über einen HTTP-Header übermittelt werden:
|
||||
|
||||
```javascript
|
||||
// JavaScript-Beispiel
|
||||
fetch('/api/data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
In der HTML-Vorlage:
|
||||
|
||||
```html
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
```
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Double Submit Cookie Pattern
|
||||
|
||||
Das Framework unterstützt das Double Submit Cookie Pattern für zusätzliche Sicherheit:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
$token = $this->csrfTokenGenerator->generate();
|
||||
|
||||
// Token in der Session speichern
|
||||
$this->session->set('csrf_token', $token->toString());
|
||||
|
||||
// Token auch als Cookie setzen
|
||||
$this->response->setCookie('csrf_token', $token->toString(), [
|
||||
'httponly' => false, // JavaScript muss darauf zugreifen können
|
||||
'samesite' => 'Strict',
|
||||
'secure' => true,
|
||||
'path' => '/',
|
||||
]);
|
||||
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $token->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### Token-Rotation
|
||||
|
||||
Für zusätzliche Sicherheit können CSRF-Token nach jeder Anfrage rotiert werden:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
// Token validieren
|
||||
// ...
|
||||
|
||||
// Neues Token generieren
|
||||
$newToken = $this->csrfTokenGenerator->generate();
|
||||
$this->session->set('csrf_token', $newToken->toString());
|
||||
|
||||
// Antwort mit neuem Token
|
||||
return $this->render('success.twig', [
|
||||
'new_csrf_token' => $newToken->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### Synchronizer Token Pattern
|
||||
|
||||
Das Framework implementiert das Synchronizer Token Pattern, bei dem für jede Sitzung ein eindeutiges Token generiert wird:
|
||||
|
||||
```php
|
||||
// In der Session-Initialisierung
|
||||
public function initializeSession(): void
|
||||
{
|
||||
if (!$this->session->has('csrf_token')) {
|
||||
$token = $this->csrfTokenGenerator->generate();
|
||||
$this->session->set('csrf_token', $token->toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Token-Validierung schlägt fehl
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Das Token ist abgelaufen
|
||||
- Das Token wurde bereits verwendet
|
||||
- Das Token wurde nicht korrekt übermittelt
|
||||
- Die Session ist abgelaufen oder wurde zurückgesetzt
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Session-Konfiguration
|
||||
- Stellen Sie sicher, dass das Token korrekt im Formular eingebunden ist
|
||||
- Erhöhen Sie die Token-Lebensdauer in der Konfiguration
|
||||
|
||||
#### CSRF-Schutz für bestimmte Routen deaktivieren
|
||||
|
||||
Wenn Sie den CSRF-Schutz für bestimmte Routen deaktivieren müssen (z.B. für Webhook-Callbacks):
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'csrf' => [
|
||||
'exclude_routes' => [
|
||||
'/api/webhook',
|
||||
'/api/external/callback',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
#### AJAX-Anfragen mit CSRF-Schutz
|
||||
|
||||
Für AJAX-Anfragen müssen Sie das CSRF-Token im Header oder als Formularfeld übermitteln:
|
||||
|
||||
```javascript
|
||||
// JavaScript mit jQuery
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
|
||||
}
|
||||
});
|
||||
|
||||
// Oder mit Fetch API
|
||||
fetch('/api/data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
## Sicherheitsüberlegungen
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verwenden Sie HTTPS**: CSRF-Token sollten immer über eine verschlüsselte Verbindung übertragen werden.
|
||||
2. **Kurze Token-Lebensdauer**: Setzen Sie die Token-Lebensdauer auf einen angemessenen Wert (z.B. 1-2 Stunden).
|
||||
3. **Strikte SameSite-Cookie-Einstellungen**: Verwenden Sie `SameSite=Strict` oder `SameSite=Lax` für Session-Cookies.
|
||||
4. **Token-Rotation**: Rotieren Sie Token nach jeder Anfrage, die den Zustand ändert.
|
||||
5. **Validieren Sie alle Anfragen**: Stellen Sie sicher, dass alle Anfragen, die den Zustand ändern, ein gültiges CSRF-Token erfordern.
|
||||
|
||||
### Bekannte Einschränkungen
|
||||
|
||||
- CSRF-Schutz funktioniert nicht für Benutzer, die keine Cookies akzeptieren.
|
||||
- Bei sehr langen Sitzungen kann die Sicherheit des CSRF-Tokens beeinträchtigt sein.
|
||||
- CSRF-Schutz kann mit bestimmten Caching-Strategien in Konflikt geraten.
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Security Features Übersicht](index.md)
|
||||
- [Security Headers und CSP](security-headers.md)
|
||||
- [Request Signing API](request-signing.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
363
docs/components/security/index.md
Normal file
363
docs/components/security/index.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# Security Features
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Sicherheitsfeatures korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Framework bietet umfassende Sicherheitsfunktionen, die Ihre Anwendung vor gängigen Bedrohungen schützen. Diese Funktionen sind tief in die Architektur integriert und folgen modernen Best Practices für Webanwendungssicherheit.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### CSRF-Schutz
|
||||
|
||||
Der Cross-Site Request Forgery (CSRF) Schutz verhindert, dass Angreifer unerwünschte Aktionen im Namen authentifizierter Benutzer ausführen:
|
||||
|
||||
```php
|
||||
// Token generieren
|
||||
$csrfToken = $csrfTokenGenerator->generate();
|
||||
|
||||
// Token in Formular einbinden
|
||||
$form = '<input type="hidden" name="csrf_token" value="' . $csrfToken->toString() . '">';
|
||||
|
||||
// Token validieren
|
||||
if (!$csrfToken->equalsString($request->getPostParam('csrf_token'))) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
```
|
||||
|
||||
### Security Headers
|
||||
|
||||
Die `SecurityHeaderMiddleware` fügt automatisch wichtige Sicherheits-Header zu allen HTTP-Antworten hinzu:
|
||||
|
||||
- **Strict-Transport-Security (HSTS)**: Erzwingt HTTPS-Verbindungen
|
||||
- **X-Frame-Options**: Verhindert Clickjacking-Angriffe
|
||||
- **X-Content-Type-Options**: Verhindert MIME-Sniffing
|
||||
- **Content-Security-Policy (CSP)**: Schützt vor XSS und anderen Injection-Angriffen
|
||||
- **Referrer-Policy**: Kontrolliert die Weitergabe von Referrer-Informationen
|
||||
- **Permissions-Policy**: Beschränkt Browser-Features
|
||||
- **Cross-Origin-Policies**: Steuert ressourcenübergreifende Interaktionen
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
```
|
||||
|
||||
### Request Signing
|
||||
|
||||
Das Request-Signing-System ermöglicht die kryptografische Verifizierung von API-Anfragen:
|
||||
|
||||
```php
|
||||
// Ausgehende Anfrage signieren
|
||||
$signedRequest = $requestSigningService->signOutgoingRequest($request);
|
||||
|
||||
// Eingehende Anfrage verifizieren
|
||||
$result = $requestSigningService->verifyIncomingRequest($request);
|
||||
if (!$result->isSuccess()) {
|
||||
throw new SecurityException('Ungültige Anfrage-Signatur: ' . $result->errorMessage);
|
||||
}
|
||||
```
|
||||
|
||||
### Session-Sicherheit
|
||||
|
||||
Der `SecurityManager` schützt Benutzersitzungen vor verschiedenen Angriffen:
|
||||
|
||||
- **Session-Fixierung**: Automatische Regenerierung von Session-IDs
|
||||
- **Session-Hijacking**: Überprüfung von IP-Adressen und User-Agents
|
||||
- **Session-Timeout**: Automatisches Beenden inaktiver Sitzungen
|
||||
- **Concurrent-Login-Erkennung**: Erkennung gleichzeitiger Anmeldungen
|
||||
|
||||
```php
|
||||
// In einem Controller oder Middleware
|
||||
$securityManager->validateSession($request, $session);
|
||||
```
|
||||
|
||||
### Sicherheits-Ereignisbehandlung
|
||||
|
||||
Das Framework bietet ein umfassendes System zur Erkennung und Protokollierung von Sicherheitsereignissen:
|
||||
|
||||
```php
|
||||
// Sicherheitsereignis protokollieren
|
||||
$securityEventLogger->logEvent(
|
||||
SecurityEventType::AUTHENTICATION_FAILURE,
|
||||
'Fehlgeschlagene Anmeldung',
|
||||
['username' => $username, 'ip' => $request->getClientIp()]
|
||||
);
|
||||
|
||||
// Auf Sicherheitsereignisse reagieren
|
||||
$securityAlertManager->handleEvent($event);
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### CSRF-Schutz konfigurieren
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'csrf' => [
|
||||
'enabled' => true,
|
||||
'token_lifetime' => 3600, // Sekunden
|
||||
'exclude_routes' => [
|
||||
'/api/webhook',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Security Headers konfigurieren
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'headers' => [
|
||||
'strict_mode' => true, // Produktionsmodus mit strengeren Einstellungen
|
||||
'content_security_policy' => "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
|
||||
'hsts' => [
|
||||
'enabled' => true,
|
||||
'max_age' => 31536000, // 1 Jahr
|
||||
'include_subdomains' => true,
|
||||
'preload' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Request Signing konfigurieren
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'request_signing' => [
|
||||
'enabled' => true,
|
||||
'default_algorithm' => 'hmac-sha256',
|
||||
'default_headers' => ['(request-target)', 'host', 'date', 'content-type'],
|
||||
'default_expiry' => 300, // Sekunden
|
||||
'required_routes' => [
|
||||
'/api/admin/*',
|
||||
'/api/payments/*',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### CSRF-Schutz in Formularen
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
$csrfToken = $this->csrfTokenGenerator->generate();
|
||||
$this->session->set('csrf_token', $csrfToken->toString());
|
||||
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $csrfToken->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
$storedToken = CsrfToken::fromString($this->session->get('csrf_token'));
|
||||
$submittedToken = $request->getPostParam('csrf_token');
|
||||
|
||||
if (!$storedToken->equalsString($submittedToken)) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
|
||||
// Formular verarbeiten
|
||||
}
|
||||
```
|
||||
|
||||
### Content Security Policy anpassen
|
||||
|
||||
```php
|
||||
// In einem Controller oder Middleware
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$resultContext = $next($context);
|
||||
|
||||
if ($resultContext->hasResponse()) {
|
||||
$response = $resultContext->response;
|
||||
$headers = $response->headers;
|
||||
|
||||
// CSP für eine bestimmte Route anpassen
|
||||
$updatedHeaders = $headers->with(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'self'; script-src 'self' https://trusted-cdn.com;"
|
||||
);
|
||||
|
||||
$updatedResponse = $this->manipulator->withHeaders($response, $updatedHeaders);
|
||||
return $resultContext->withResponse($updatedResponse);
|
||||
}
|
||||
|
||||
return $resultContext;
|
||||
}
|
||||
```
|
||||
|
||||
### API-Anfragen signieren und verifizieren
|
||||
|
||||
```php
|
||||
// Client-Seite: Anfrage signieren
|
||||
$request = new HttpRequest('POST', '/api/data');
|
||||
$signedRequest = $this->requestSigningService->signOutgoingRequest($request);
|
||||
$response = $this->httpClient->send($signedRequest);
|
||||
|
||||
// Server-Seite: Anfrage verifizieren (in Middleware)
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
|
||||
// Prüfen, ob die Route signierte Anfragen erfordert
|
||||
if ($this->requiresSignature($request->path)) {
|
||||
$result = $this->requestSigningService->verifyIncomingRequest($request);
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
return $context->withResponse(
|
||||
new Response(401, [], ['error' => 'Ungültige Signatur: ' . $result->errorMessage])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($context);
|
||||
}
|
||||
```
|
||||
|
||||
### Schlüsselverwaltung für Request Signing
|
||||
|
||||
```php
|
||||
// HMAC-Schlüssel generieren
|
||||
$key = $requestSigningService->generateHmacKey(
|
||||
'api-key-1',
|
||||
SigningAlgorithm::HMAC_SHA256,
|
||||
new \DateTimeImmutable('+30 days')
|
||||
);
|
||||
|
||||
// RSA-Schlüssel erstellen
|
||||
$privateKey = file_get_contents('private_key.pem');
|
||||
$key = $requestSigningService->createRsaKey(
|
||||
'api-key-2',
|
||||
$privateKey,
|
||||
new \DateTimeImmutable('+1 year')
|
||||
);
|
||||
|
||||
// Schlüssel rotieren
|
||||
$newKey = SigningKey::generateHmac('api-key-1-new', SigningAlgorithm::HMAC_SHA256);
|
||||
$requestSigningService->rotateSigningKey('api-key-1', $newKey);
|
||||
|
||||
// Schlüssel entfernen
|
||||
$requestSigningService->removeSigningKey('api-key-1');
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Middleware-Integration
|
||||
|
||||
Die Sicherheitsfunktionen sind als Middleware-Komponenten implementiert und können einfach in die Anwendung integriert werden:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
$app->addMiddleware(CsrfProtectionMiddleware::class);
|
||||
$app->addMiddleware(RequestSigningMiddleware::class);
|
||||
$app->addMiddleware(SessionSecurityMiddleware::class);
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Sicherheitsereignisse werden über das Event-System des Frameworks verarbeitet:
|
||||
|
||||
```php
|
||||
// Event-Listener registrieren
|
||||
$eventDispatcher->addListener(SecurityEventInterface::class, function (SecurityEventInterface $event) {
|
||||
// Auf Sicherheitsereignis reagieren
|
||||
if ($event->getSeverity() >= SecurityLogLevel::WARNING) {
|
||||
$this->notificationService->sendAlert($event);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Analytics-Integration
|
||||
|
||||
Sicherheitsereignisse werden automatisch im Analytics-System erfasst:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei
|
||||
$app->addAnalyticsBridge(SecurityAnalyticsListener::class);
|
||||
|
||||
// Sicherheitsanalysen abrufen
|
||||
$securityStats = $analyticsDashboard->getSecurityStats();
|
||||
```
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### IP-basierte Sicherheit
|
||||
|
||||
```php
|
||||
// IP-Adresse prüfen
|
||||
if (!$ipSecurityService->isAllowed($request->getClientIp())) {
|
||||
throw new SecurityException('Zugriff verweigert');
|
||||
}
|
||||
|
||||
// Rate-Limiting basierend auf IP-Adresse
|
||||
$ipSecurityService->trackRequest($request->getClientIp());
|
||||
if ($ipSecurityService->isRateLimited($request->getClientIp())) {
|
||||
throw new SecurityException('Rate-Limit überschritten');
|
||||
}
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Security-Header
|
||||
|
||||
```php
|
||||
// Eigene Security-Header-Konfiguration erstellen
|
||||
$config = new SecurityHeaderConfig();
|
||||
$config->contentSecurityPolicy = "default-src 'self'; script-src 'self' https://trusted-cdn.com;";
|
||||
$config->frameOptions = "DENY";
|
||||
$config->referrerPolicy = "strict-origin-when-cross-origin";
|
||||
|
||||
// Konfiguration anwenden
|
||||
$securityHeaderMiddleware = new SecurityHeaderMiddleware($responseManipulator, $config);
|
||||
```
|
||||
|
||||
### Erweiterte CSRF-Schutzmaßnahmen
|
||||
|
||||
```php
|
||||
// CSRF-Token mit Metadaten generieren
|
||||
$csrfTokenData = new CsrfTokenData(
|
||||
$csrfToken,
|
||||
$request->getClientIp(),
|
||||
$request->path,
|
||||
new \DateTimeImmutable('+1 hour')
|
||||
);
|
||||
|
||||
// Token mit Metadaten validieren
|
||||
if (!$csrfValidator->validate($csrfTokenData, $request)) {
|
||||
throw new SecurityException('Ungültiges oder abgelaufenes CSRF-Token');
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **CSP-Fehler im Browser**:
|
||||
- Überprüfen Sie die Content-Security-Policy in der Konfiguration
|
||||
- Fügen Sie fehlende Quellen zur CSP hinzu
|
||||
- Verwenden Sie die Browser-Entwicklertools, um CSP-Verstöße zu identifizieren
|
||||
|
||||
2. **CSRF-Token-Fehler**:
|
||||
- Stellen Sie sicher, dass das Token korrekt im Formular übermittelt wird
|
||||
- Überprüfen Sie die Session-Konfiguration
|
||||
- Prüfen Sie, ob das Token abgelaufen ist
|
||||
|
||||
3. **Request-Signing-Fehler**:
|
||||
- Überprüfen Sie, ob der richtige Schlüssel verwendet wird
|
||||
- Stellen Sie sicher, dass die Uhrzeit auf Client und Server synchronisiert ist
|
||||
- Prüfen Sie, ob alle erforderlichen Header signiert werden
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [CSRF-Schutz im Detail](csrf-protection.md)
|
||||
- [Security Headers und CSP](security-headers.md)
|
||||
- [Request Signing API](request-signing.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
410
docs/components/security/request-signing.md
Normal file
410
docs/components/security/request-signing.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# Request Signing
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Request Signing korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Request Signing ist ein Sicherheitsmechanismus, der die Authentizität und Integrität von HTTP-Anfragen gewährleistet. Durch die kryptografische Signierung von Anfragen können Empfänger überprüfen, dass die Anfrage von einem autorisierten Absender stammt und während der Übertragung nicht manipuliert wurde. Das Framework bietet eine robuste Implementierung von Request Signing, die sowohl für ausgehende als auch für eingehende Anfragen verwendet werden kann.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### RequestSigningService
|
||||
|
||||
Die `RequestSigningService`-Klasse ist die zentrale Komponente für Request Signing:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\RequestSigningService;
|
||||
|
||||
// Service initialisieren
|
||||
$service = new RequestSigningService(
|
||||
$signer,
|
||||
$verifier,
|
||||
$keyRepository,
|
||||
$config,
|
||||
$logger
|
||||
);
|
||||
|
||||
// Ausgehende Anfrage signieren
|
||||
$signedRequest = $service->signOutgoingRequest($request, $keyId);
|
||||
|
||||
// Eingehende Anfrage verifizieren
|
||||
$result = $service->verifyIncomingRequest($request);
|
||||
if (!$result->isSuccess()) {
|
||||
throw new SecurityException('Ungültige Anfrage-Signatur: ' . $result->errorMessage);
|
||||
}
|
||||
```
|
||||
|
||||
### SigningKey
|
||||
|
||||
Die `SigningKey`-Klasse repräsentiert einen kryptografischen Schlüssel für das Signieren von Anfragen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\SigningKey;
|
||||
use App\Framework\Security\RequestSigning\SigningAlgorithm;
|
||||
|
||||
// HMAC-Schlüssel generieren
|
||||
$hmacKey = SigningKey::generateHmac(
|
||||
'api-key-1',
|
||||
SigningAlgorithm::HMAC_SHA256,
|
||||
new \DateTimeImmutable('+30 days') // Ablaufdatum
|
||||
);
|
||||
|
||||
// RSA-Schlüssel erstellen
|
||||
$privateKey = file_get_contents('private_key.pem');
|
||||
$rsaKey = SigningKey::createRsa(
|
||||
'api-key-2',
|
||||
$privateKey,
|
||||
new \DateTimeImmutable('+1 year') // Ablaufdatum
|
||||
);
|
||||
```
|
||||
|
||||
### SigningAlgorithm
|
||||
|
||||
Das `SigningAlgorithm`-Enum definiert die unterstützten Signaturalgorithmen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\SigningAlgorithm;
|
||||
|
||||
// Verfügbare Algorithmen
|
||||
$algorithm = SigningAlgorithm::HMAC_SHA256; // HMAC mit SHA-256
|
||||
$algorithm = SigningAlgorithm::HMAC_SHA512; // HMAC mit SHA-512
|
||||
$algorithm = SigningAlgorithm::RSA_SHA256; // RSA mit SHA-256
|
||||
$algorithm = SigningAlgorithm::RSA_SHA512; // RSA mit SHA-512
|
||||
```
|
||||
|
||||
### RequestSigner
|
||||
|
||||
Die `RequestSigner`-Klasse ist für das Signieren von ausgehenden Anfragen verantwortlich:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\RequestSigner;
|
||||
|
||||
// Signer initialisieren
|
||||
$signer = new RequestSigner($clock);
|
||||
|
||||
// Anfrage signieren
|
||||
$signedRequest = $signer->signRequest(
|
||||
$request,
|
||||
$signingKey,
|
||||
['(request-target)', 'host', 'date', 'content-type'],
|
||||
300 // Gültigkeitsdauer in Sekunden
|
||||
);
|
||||
```
|
||||
|
||||
### RequestVerifier
|
||||
|
||||
Die `RequestVerifier`-Klasse ist für die Überprüfung von eingehenden Anfragen verantwortlich:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\RequestVerifier;
|
||||
|
||||
// Verifier initialisieren
|
||||
$verifier = new RequestVerifier($keyRepository, $clock);
|
||||
|
||||
// Anfrage verifizieren
|
||||
$result = $verifier->verify($request);
|
||||
if ($result->isSuccess()) {
|
||||
// Anfrage ist gültig
|
||||
$signature = $result->signature;
|
||||
$key = $result->key;
|
||||
} else {
|
||||
// Anfrage ist ungültig
|
||||
$errorMessage = $result->errorMessage;
|
||||
}
|
||||
```
|
||||
|
||||
### SigningKeyRepository
|
||||
|
||||
Das `SigningKeyRepository`-Interface definiert die Methoden zum Speichern und Abrufen von Signaturschlüsseln:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\SigningKeyRepository;
|
||||
use App\Framework\Security\RequestSigning\InMemorySigningKeyRepository;
|
||||
use App\Framework\Security\RequestSigning\EntityManagerSigningKeyRepository;
|
||||
|
||||
// In-Memory-Repository für Tests
|
||||
$repository = new InMemorySigningKeyRepository();
|
||||
|
||||
// Entity-Manager-Repository für Produktion
|
||||
$repository = new EntityManagerSigningKeyRepository($entityManager);
|
||||
|
||||
// Schlüssel speichern
|
||||
$repository->store($signingKey);
|
||||
|
||||
// Schlüssel abrufen
|
||||
$key = $repository->findByKeyId('api-key-1');
|
||||
|
||||
// Alle aktiven Schlüssel abrufen
|
||||
$activeKeys = $repository->getAllActive();
|
||||
|
||||
// Schlüssel entfernen
|
||||
$repository->remove('api-key-1');
|
||||
|
||||
// Schlüssel rotieren
|
||||
$repository->rotateKey('api-key-1', $newKey);
|
||||
```
|
||||
|
||||
## Signaturformat
|
||||
|
||||
Das Framework verwendet das [HTTP Signature](https://tools.ietf.org/html/draft-cavage-http-signatures) Format für Request Signing:
|
||||
|
||||
```
|
||||
Authorization: Signature keyId="api-key-1",algorithm="hmac-sha256",headers="(request-target) host date",signature="Base64(HMAC-SHA256(SigningString))"
|
||||
```
|
||||
|
||||
Die Signatur wird aus einer Zeichenkette (Signing String) generiert, die aus den angegebenen Header-Werten besteht:
|
||||
|
||||
```
|
||||
(request-target): post /api/data
|
||||
host: example.com
|
||||
date: Tue, 07 Jun 2023 20:51:35 GMT
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Ausgehende Anfragen signieren
|
||||
|
||||
```php
|
||||
// In einem API-Client
|
||||
public function sendRequest(HttpRequest $request): HttpResponse
|
||||
{
|
||||
// Anfrage signieren
|
||||
$signedRequest = $this->requestSigningService->signOutgoingRequest(
|
||||
$request,
|
||||
'api-key-1', // Optional: Spezifischer Schlüssel
|
||||
['(request-target)', 'host', 'date', 'content-type'], // Optional: Zu signierende Header
|
||||
300 // Optional: Gültigkeitsdauer in Sekunden
|
||||
);
|
||||
|
||||
// Signierte Anfrage senden
|
||||
return $this->httpClient->send($signedRequest);
|
||||
}
|
||||
```
|
||||
|
||||
### Eingehende Anfragen verifizieren
|
||||
|
||||
```php
|
||||
// In einer Middleware
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
|
||||
// Prüfen, ob die Route signierte Anfragen erfordert
|
||||
if ($this->requiresSignature($request->path)) {
|
||||
$result = $this->requestSigningService->verifyIncomingRequest($request);
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
return $context->withResponse(
|
||||
new Response(401, [], ['error' => 'Ungültige Signatur: ' . $result->errorMessage])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($context);
|
||||
}
|
||||
```
|
||||
|
||||
### Schlüsselverwaltung
|
||||
|
||||
```php
|
||||
// HMAC-Schlüssel generieren
|
||||
$key = $requestSigningService->generateHmacKey(
|
||||
'api-key-1',
|
||||
SigningAlgorithm::HMAC_SHA256,
|
||||
new \DateTimeImmutable('+30 days')
|
||||
);
|
||||
|
||||
// RSA-Schlüssel erstellen
|
||||
$privateKey = file_get_contents('private_key.pem');
|
||||
$key = $requestSigningService->createRsaKey(
|
||||
'api-key-2',
|
||||
$privateKey,
|
||||
new \DateTimeImmutable('+1 year')
|
||||
);
|
||||
|
||||
// Schlüssel rotieren
|
||||
$newKey = SigningKey::generateHmac('api-key-1-new', SigningAlgorithm::HMAC_SHA256);
|
||||
$requestSigningService->rotateSigningKey('api-key-1', $newKey);
|
||||
|
||||
// Schlüssel entfernen
|
||||
$requestSigningService->removeSigningKey('api-key-1');
|
||||
|
||||
// Alle aktiven Schlüssel abrufen
|
||||
$activeKeys = $requestSigningService->getActiveKeys();
|
||||
```
|
||||
|
||||
## Integration mit dem Framework
|
||||
|
||||
### RequestSigningMiddleware
|
||||
|
||||
Das Framework bietet eine `RequestSigningMiddleware`, die automatisch eingehende Anfragen verifiziert:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(RequestSigningMiddleware::class);
|
||||
```
|
||||
|
||||
### HttpClientSigningMiddleware
|
||||
|
||||
Für ausgehende Anfragen bietet das Framework eine `HttpClientSigningMiddleware`:
|
||||
|
||||
```php
|
||||
// HTTP-Client mit Signatur-Middleware konfigurieren
|
||||
$httpClient = new HttpClient([
|
||||
'middlewares' => [
|
||||
new HttpClientSigningMiddleware($requestSigningService)
|
||||
]
|
||||
]);
|
||||
|
||||
// Anfragen werden automatisch signiert
|
||||
$response = $httpClient->send($request);
|
||||
```
|
||||
|
||||
### Konfiguration
|
||||
|
||||
Die Request-Signing-Funktionen können in der Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'request_signing' => [
|
||||
'enabled' => true,
|
||||
'default_algorithm' => 'hmac-sha256',
|
||||
'default_headers' => ['(request-target)', 'host', 'date', 'content-type'],
|
||||
'default_expiry' => 300, // Sekunden
|
||||
'required_routes' => [
|
||||
'/api/admin/*',
|
||||
'/api/payments/*',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Signatur mit Ablaufdatum
|
||||
|
||||
Signaturen können mit einem Ablaufdatum versehen werden, um Replay-Angriffe zu verhindern:
|
||||
|
||||
```php
|
||||
// Anfrage mit Ablaufdatum signieren
|
||||
$signedRequest = $requestSigningService->signOutgoingRequest(
|
||||
$request,
|
||||
'api-key-1',
|
||||
['(request-target)', 'host', 'date', '(created)', '(expires)'],
|
||||
300 // Gültigkeitsdauer in Sekunden
|
||||
);
|
||||
```
|
||||
|
||||
Die Signatur enthält dann zusätzliche Felder:
|
||||
|
||||
```
|
||||
Authorization: Signature keyId="api-key-1",algorithm="hmac-sha256",headers="(request-target) host date (created) (expires)",created=1623094295,expires=1623094595,signature="..."
|
||||
```
|
||||
|
||||
### Digest-Header
|
||||
|
||||
Für zusätzliche Sicherheit kann der Anfrage-Body mit einem Digest-Header geschützt werden:
|
||||
|
||||
```php
|
||||
// Anfrage mit Digest-Header signieren
|
||||
$request = $request->withHeader('Digest', 'SHA-256=' . base64_encode(hash('sha256', $request->body, true)));
|
||||
$signedRequest = $requestSigningService->signOutgoingRequest(
|
||||
$request,
|
||||
'api-key-1',
|
||||
['(request-target)', 'host', 'date', 'digest']
|
||||
);
|
||||
```
|
||||
|
||||
### Schlüsselrotation
|
||||
|
||||
Für zusätzliche Sicherheit sollten Schlüssel regelmäßig rotiert werden:
|
||||
|
||||
```php
|
||||
// Neuen Schlüssel generieren
|
||||
$newKey = $requestSigningService->generateHmacKey(
|
||||
'api-key-1-new',
|
||||
SigningAlgorithm::HMAC_SHA256
|
||||
);
|
||||
|
||||
// Alten Schlüssel durch neuen ersetzen
|
||||
$requestSigningService->rotateSigningKey('api-key-1', $newKey);
|
||||
|
||||
// Alten Schlüssel nach einer Übergangszeit entfernen
|
||||
// (nachdem alle Clients auf den neuen Schlüssel umgestellt wurden)
|
||||
$requestSigningService->removeSigningKey('api-key-1');
|
||||
```
|
||||
|
||||
## Sicherheitsüberlegungen
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verwenden Sie starke Algorithmen**: HMAC-SHA256 oder RSA-SHA256 sind empfohlen.
|
||||
2. **Schützen Sie private Schlüssel**: Speichern Sie private Schlüssel sicher und teilen Sie sie nie öffentlich.
|
||||
3. **Signieren Sie kritische Header**: Mindestens `(request-target)`, `host`, `date` und `content-type` sollten signiert werden.
|
||||
4. **Verwenden Sie Ablaufdaten**: Sowohl für Schlüssel als auch für Signaturen, um Replay-Angriffe zu verhindern.
|
||||
5. **Rotieren Sie Schlüssel regelmäßig**: Implementieren Sie einen Prozess zur regelmäßigen Schlüsselrotation.
|
||||
|
||||
### Bekannte Einschränkungen
|
||||
|
||||
- Request Signing schützt nicht vor Man-in-the-Middle-Angriffen; verwenden Sie immer HTTPS.
|
||||
- Die Sicherheit hängt von der sicheren Speicherung und Verteilung der Schlüssel ab.
|
||||
- Uhrzeitsynchronisation zwischen Client und Server ist wichtig für zeitbasierte Validierung.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Signaturvalidierung schlägt fehl
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Die Signatur ist abgelaufen
|
||||
- Die Uhrzeit zwischen Client und Server ist nicht synchronisiert
|
||||
- Der falsche Schlüssel wird verwendet
|
||||
- Die Header-Liste stimmt nicht überein
|
||||
- Der Anfrage-Body wurde nach der Signierung geändert
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Uhrzeit auf Client und Server
|
||||
- Stellen Sie sicher, dass der richtige Schlüssel verwendet wird
|
||||
- Überprüfen Sie, ob alle erforderlichen Header signiert werden
|
||||
- Verwenden Sie den Digest-Header für Anfragen mit Body
|
||||
|
||||
#### Schlüssel nicht gefunden
|
||||
|
||||
Wenn der Schlüssel nicht gefunden wird:
|
||||
|
||||
```php
|
||||
// Prüfen, ob der Schlüssel existiert
|
||||
$key = $keyRepository->findByKeyId($keyId);
|
||||
if ($key === null) {
|
||||
// Schlüssel nicht gefunden
|
||||
$this->logger->warning('Signing key not found', ['key_id' => $keyId]);
|
||||
}
|
||||
```
|
||||
|
||||
#### Logging für Fehlerbehebung
|
||||
|
||||
Das Framework bietet umfangreiches Logging für Request Signing:
|
||||
|
||||
```php
|
||||
// In der RequestSigningService-Klasse
|
||||
$this->logger->info('Request signature verified successfully', [
|
||||
'key_id' => $result->signature->keyId,
|
||||
'algorithm' => $result->signature->algorithm->value,
|
||||
'path' => $request->path,
|
||||
]);
|
||||
|
||||
$this->logger->warning('Request signature verification failed', [
|
||||
'error' => $result->errorMessage,
|
||||
'path' => $request->path,
|
||||
]);
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Security Features Übersicht](index.md)
|
||||
- [CSRF-Schutz](csrf-protection.md)
|
||||
- [Security Headers](security-headers.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
- [HTTP Signatures Spezifikation](https://tools.ietf.org/html/draft-cavage-http-signatures)
|
||||
382
docs/components/security/security-headers.md
Normal file
382
docs/components/security/security-headers.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Security Headers
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Security Headers korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Security Headers sind HTTP-Header, die dazu beitragen, die Sicherheit Ihrer Webanwendung zu verbessern, indem sie dem Browser Anweisungen geben, wie er mit bestimmten Sicherheitsaspekten umgehen soll. Das Framework implementiert automatisch eine umfassende Sammlung von Security Headers, die den aktuellen Best Practices entsprechen und Ihre Anwendung vor verschiedenen Arten von Angriffen schützen.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### SecurityHeaderMiddleware
|
||||
|
||||
Die `SecurityHeaderMiddleware` ist die zentrale Komponente für die Implementierung von Security Headers:
|
||||
|
||||
```php
|
||||
use App\Framework\Http\Middlewares\SecurityHeaderMiddleware;
|
||||
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
```
|
||||
|
||||
Diese Middleware:
|
||||
1. Entfernt unsichere Header wie `X-Powered-By` und `Server`
|
||||
2. Fügt automatisch wichtige Security Headers zu allen HTTP-Antworten hinzu
|
||||
3. Passt die Header basierend auf der Umgebung (Entwicklung oder Produktion) an
|
||||
|
||||
### SecurityHeaderConfig
|
||||
|
||||
Die `SecurityHeaderConfig`-Klasse enthält die Konfiguration für die Security Headers:
|
||||
|
||||
```php
|
||||
use App\Framework\Http\Middlewares\SecurityHeaderConfig;
|
||||
|
||||
// Vordefinierte Konfigurationen verwenden
|
||||
$config = SecurityHeaderConfig::forProduction(); // Strenge Einstellungen für Produktion
|
||||
$config = SecurityHeaderConfig::forDevelopment(); // Weniger strenge Einstellungen für Entwicklung
|
||||
|
||||
// Benutzerdefinierte Konfiguration erstellen
|
||||
$config = new SecurityHeaderConfig(
|
||||
$contentSecurityPolicy,
|
||||
$hstsHeader,
|
||||
$frameOptions,
|
||||
$referrerPolicy,
|
||||
$permissionsPolicy,
|
||||
$crossOriginEmbedderPolicy,
|
||||
$crossOriginOpenerPolicy,
|
||||
$crossOriginResourcePolicy
|
||||
);
|
||||
```
|
||||
|
||||
## Implementierte Security Headers
|
||||
|
||||
### Content-Security-Policy (CSP)
|
||||
|
||||
Die Content Security Policy schützt vor Cross-Site Scripting (XSS) und anderen Code-Injection-Angriffen, indem sie definiert, welche Ressourcen geladen werden dürfen:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
|
||||
```
|
||||
|
||||
In der Produktionsumgebung wird eine strengere CSP verwendet:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';
|
||||
```
|
||||
|
||||
### Strict-Transport-Security (HSTS)
|
||||
|
||||
HSTS zwingt den Browser, nur HTTPS-Verbindungen zu verwenden, selbst wenn der Benutzer versucht, HTTP zu verwenden:
|
||||
|
||||
```
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
||||
```
|
||||
|
||||
Parameter:
|
||||
- `max-age`: Dauer in Sekunden, für die der Browser nur HTTPS verwenden soll (1 Jahr)
|
||||
- `includeSubDomains`: Gilt auch für alle Subdomains
|
||||
- `preload`: Ermöglicht die Aufnahme in die HSTS-Preload-Liste der Browser
|
||||
|
||||
### X-Frame-Options
|
||||
|
||||
Schützt vor Clickjacking-Angriffen, indem es kontrolliert, ob die Seite in einem Frame eingebettet werden darf:
|
||||
|
||||
```
|
||||
X-Frame-Options: DENY
|
||||
```
|
||||
|
||||
Optionen:
|
||||
- `DENY`: Die Seite darf nicht in einem Frame eingebettet werden
|
||||
- `SAMEORIGIN`: Die Seite darf nur in einem Frame auf derselben Domain eingebettet werden
|
||||
|
||||
### X-Content-Type-Options
|
||||
|
||||
Verhindert MIME-Sniffing, bei dem Browser den Inhaltstyp einer Ressource erraten:
|
||||
|
||||
```
|
||||
X-Content-Type-Options: nosniff
|
||||
```
|
||||
|
||||
### Referrer-Policy
|
||||
|
||||
Kontrolliert, welche Referrer-Informationen beim Navigieren zu anderen Seiten gesendet werden:
|
||||
|
||||
```
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
```
|
||||
|
||||
Optionen:
|
||||
- `no-referrer`: Keine Referrer-Informationen senden
|
||||
- `no-referrer-when-downgrade`: Keine Referrer-Informationen senden, wenn von HTTPS zu HTTP navigiert wird
|
||||
- `same-origin`: Nur Referrer-Informationen senden, wenn auf derselben Domain navigiert wird
|
||||
- `strict-origin`: Nur die Ursprungs-URL senden und nur bei gleichem Sicherheitsniveau
|
||||
- `strict-origin-when-cross-origin`: Vollständige URL bei gleicher Domain, nur Ursprung bei Cross-Origin und gleichem Sicherheitsniveau
|
||||
|
||||
### Permissions-Policy
|
||||
|
||||
Kontrolliert, welche Browser-Features und APIs die Seite verwenden darf:
|
||||
|
||||
```
|
||||
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
|
||||
```
|
||||
|
||||
Diese Einstellung deaktiviert den Zugriff auf Kamera, Mikrofon, Geolocation und FLoC (Federated Learning of Cohorts).
|
||||
|
||||
### Cross-Origin-Policies
|
||||
|
||||
Eine Gruppe von Headern, die die Interaktion zwischen verschiedenen Ursprüngen kontrollieren:
|
||||
|
||||
```
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Resource-Policy: same-origin
|
||||
```
|
||||
|
||||
Diese Header schützen vor verschiedenen Cross-Origin-Angriffen und Informationslecks.
|
||||
|
||||
### X-Permitted-Cross-Domain-Policies
|
||||
|
||||
Kontrolliert, ob Cross-Domain-Richtliniendateien (crossdomain.xml) geladen werden dürfen:
|
||||
|
||||
```
|
||||
X-Permitted-Cross-Domain-Policies: none
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### Umgebungsspezifische Konfiguration
|
||||
|
||||
Das Framework bietet vordefinierte Konfigurationen für verschiedene Umgebungen:
|
||||
|
||||
```php
|
||||
// In der SecurityHeaderMiddleware
|
||||
$config = $this->securityConfig->enableStrictMode
|
||||
? SecurityHeaderConfig::forProduction()
|
||||
: SecurityHeaderConfig::forDevelopment();
|
||||
```
|
||||
|
||||
Die `enableStrictMode`-Einstellung kann in der Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'headers' => [
|
||||
'strict_mode' => true, // Produktionsmodus mit strengeren Einstellungen
|
||||
'content_security_policy' => "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
|
||||
'hsts' => [
|
||||
'enabled' => true,
|
||||
'max_age' => 31536000, // 1 Jahr
|
||||
'include_subdomains' => true,
|
||||
'preload' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Konfiguration
|
||||
|
||||
Sie können die Security Headers auch manuell konfigurieren:
|
||||
|
||||
```php
|
||||
// Eigene Security-Header-Konfiguration erstellen
|
||||
$config = new SecurityHeaderConfig();
|
||||
$config->contentSecurityPolicy = "default-src 'self'; script-src 'self' https://trusted-cdn.com;";
|
||||
$config->frameOptions = "DENY";
|
||||
$config->referrerPolicy = "strict-origin-when-cross-origin";
|
||||
|
||||
// Konfiguration anwenden
|
||||
$securityHeaderMiddleware = new SecurityHeaderMiddleware($responseManipulator, $config);
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Automatische Anwendung
|
||||
|
||||
Die Security Headers werden automatisch auf alle Antworten angewendet, wenn die Middleware registriert ist:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
```
|
||||
|
||||
### Anpassung für bestimmte Routen
|
||||
|
||||
In einigen Fällen möchten Sie möglicherweise die Security Headers für bestimmte Routen anpassen:
|
||||
|
||||
```php
|
||||
// In einem Controller oder einer Middleware
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$resultContext = $next($context);
|
||||
|
||||
if ($resultContext->hasResponse()) {
|
||||
$response = $resultContext->response;
|
||||
$headers = $response->headers;
|
||||
|
||||
// CSP für eine bestimmte Route anpassen
|
||||
$updatedHeaders = $headers->with(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'self'; script-src 'self' https://trusted-cdn.com;"
|
||||
);
|
||||
|
||||
$updatedResponse = $this->manipulator->withHeaders($response, $updatedHeaders);
|
||||
return $resultContext->withResponse($updatedResponse);
|
||||
}
|
||||
|
||||
return $resultContext;
|
||||
}
|
||||
```
|
||||
|
||||
### Prüfen der aktuellen Security Headers
|
||||
|
||||
Sie können die aktuellen Security Headers Ihrer Anwendung mit verschiedenen Tools überprüfen:
|
||||
|
||||
1. **Browser-Entwicklertools**: Überprüfen Sie die Response-Header in der Netzwerk-Ansicht
|
||||
2. **Online-Tools**: Verwenden Sie Dienste wie [securityheaders.com](https://securityheaders.com) oder [observatory.mozilla.org](https://observatory.mozilla.org)
|
||||
3. **Curl-Befehl**: `curl -I https://yourdomain.com`
|
||||
|
||||
## Content Security Policy (CSP)
|
||||
|
||||
### Grundlegende CSP-Direktiven
|
||||
|
||||
Die Content Security Policy ist einer der wichtigsten Security Headers und verdient besondere Aufmerksamkeit:
|
||||
|
||||
- `default-src`: Standardrichtlinie für alle Ressourcentypen
|
||||
- `script-src`: Erlaubte Quellen für JavaScript
|
||||
- `style-src`: Erlaubte Quellen für CSS
|
||||
- `img-src`: Erlaubte Quellen für Bilder
|
||||
- `font-src`: Erlaubte Quellen für Schriftarten
|
||||
- `connect-src`: Erlaubte Ziele für Fetch, XHR, WebSocket
|
||||
- `media-src`: Erlaubte Quellen für Audio und Video
|
||||
- `object-src`: Erlaubte Quellen für Plugins (Flash, PDF)
|
||||
- `frame-src`: Erlaubte Quellen für Frames
|
||||
- `base-uri`: Erlaubte URLs für das base-Element
|
||||
- `form-action`: Erlaubte Ziele für Formularübermittlungen
|
||||
- `frame-ancestors`: Erlaubte übergeordnete Dokumente (ähnlich X-Frame-Options)
|
||||
|
||||
### CSP-Quellwerte
|
||||
|
||||
- `'self'`: Ressourcen von derselben Ursprungs-Domain
|
||||
- `'none'`: Keine Ressourcen erlaubt
|
||||
- `'unsafe-inline'`: Inline-Skripte und Styles erlaubt (vermeiden in Produktion)
|
||||
- `'unsafe-eval'`: eval() und ähnliche Funktionen erlaubt (vermeiden in Produktion)
|
||||
- `'nonce-<base64-value>'`: Ressourcen mit entsprechendem Nonce-Attribut
|
||||
- `'sha256-<hash>'`: Ressourcen mit entsprechendem Hash
|
||||
- `https://example.com`: Spezifische Domain
|
||||
- `https://*.example.com`: Alle Subdomains
|
||||
- `data:`: Data-URLs (vorsichtig verwenden)
|
||||
|
||||
### CSP-Beispiele
|
||||
|
||||
Strenge CSP für Produktion:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';
|
||||
```
|
||||
|
||||
CSP mit externen Ressourcen:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https://img.example.com;
|
||||
```
|
||||
|
||||
CSP mit Nonce für Inline-Skripte:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showPage(): Response
|
||||
{
|
||||
$nonce = bin2hex(random_bytes(16));
|
||||
|
||||
// CSP mit Nonce setzen
|
||||
$csp = "default-src 'self'; script-src 'self' 'nonce-{$nonce}';";
|
||||
|
||||
return $this->render('page.twig', [
|
||||
'nonce' => $nonce,
|
||||
'csp' => $csp,
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
In der Vorlage:
|
||||
|
||||
```html
|
||||
<script nonce="{{ nonce }}">
|
||||
// Dieser Inline-Code ist erlaubt, weil er den korrekten Nonce hat
|
||||
console.log('Hello, world!');
|
||||
</script>
|
||||
```
|
||||
|
||||
### CSP-Reporting
|
||||
|
||||
Sie können CSP-Verstöße an einen Endpunkt melden lassen:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; report-uri /csp-report;
|
||||
```
|
||||
|
||||
Oder verwenden Sie den neueren Report-To-Header:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; report-to csp-endpoint;
|
||||
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/csp-report"}]}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### CSP blockiert legitime Ressourcen
|
||||
|
||||
Wenn Ihre Anwendung nicht korrekt funktioniert, weil die CSP legitime Ressourcen blockiert:
|
||||
|
||||
1. Überprüfen Sie die Konsole im Browser auf CSP-Verstöße
|
||||
2. Erweitern Sie die CSP um die benötigten Quellen
|
||||
3. Verwenden Sie vorübergehend eine weniger strenge CSP während der Entwicklung
|
||||
|
||||
```php
|
||||
// Weniger strenge CSP für Entwicklung
|
||||
$config->contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';";
|
||||
```
|
||||
|
||||
#### HSTS-Probleme
|
||||
|
||||
Wenn Sie HSTS aktiviert haben und Probleme auftreten:
|
||||
|
||||
1. Beginnen Sie mit einer kurzen `max-age` (z.B. 300 Sekunden)
|
||||
2. Aktivieren Sie `includeSubDomains` erst, wenn Sie sicher sind, dass alle Subdomains HTTPS unterstützen
|
||||
3. Verwenden Sie `preload` nur, wenn Sie sicher sind, dass Ihre Domain dauerhaft HTTPS unterstützt
|
||||
|
||||
#### Frames werden blockiert
|
||||
|
||||
Wenn Ihre Anwendung Frames verwenden muss:
|
||||
|
||||
```php
|
||||
// X-Frame-Options anpassen
|
||||
$config->frameOptions = "SAMEORIGIN"; // Erlaubt Frames auf derselben Domain
|
||||
```
|
||||
|
||||
## Sicherheitsüberlegungen
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verwenden Sie strenge CSP**: Vermeiden Sie 'unsafe-inline' und 'unsafe-eval' in Produktion
|
||||
2. **Aktivieren Sie HSTS**: Erzwingen Sie HTTPS für alle Verbindungen
|
||||
3. **Regelmäßige Überprüfung**: Testen Sie Ihre Security Headers regelmäßig mit Tools wie [securityheaders.com](https://securityheaders.com)
|
||||
4. **Umgebungsspezifische Konfiguration**: Verwenden Sie unterschiedliche Konfigurationen für Entwicklung und Produktion
|
||||
5. **CSP-Reporting**: Implementieren Sie CSP-Reporting, um Verstöße zu erkennen
|
||||
|
||||
### Bekannte Einschränkungen
|
||||
|
||||
- Ältere Browser unterstützen möglicherweise nicht alle Security Headers
|
||||
- Eine zu strenge CSP kann die Funktionalität von Drittanbieter-Skripten beeinträchtigen
|
||||
- HSTS kann nicht rückgängig gemacht werden, bis die `max-age` abgelaufen ist
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Security Features Übersicht](index.md)
|
||||
- [CSRF-Schutz](csrf-protection.md)
|
||||
- [Request Signing API](request-signing.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security)
|
||||
- [OWASP Security Headers](https://owasp.org/www-project-secure-headers/)
|
||||
424
docs/components/validation/examples.md
Normal file
424
docs/components/validation/examples.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# Validation Framework Beispiele
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Validation Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation enthält praktische Beispiele für die Verwendung des Validation Frameworks in verschiedenen Szenarien. Die Beispiele zeigen, wie Sie Validierungsregeln auf Objekteigenschaften anwenden, Validierungsergebnisse verarbeiten und das Framework in verschiedenen Kontexten integrieren können.
|
||||
|
||||
## Grundlegende Verwendung
|
||||
|
||||
### Einfache Objektvalidierung
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
use App\Framework\Validation\Rules\StringLength;
|
||||
|
||||
// 1. Definieren Sie eine Klasse mit Validierungsregeln
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
}
|
||||
|
||||
// 2. Erstellen Sie eine Instanz der Klasse
|
||||
$userData = new UserData();
|
||||
$userData->name = 'Max Mustermann';
|
||||
$userData->email = 'max@example.com';
|
||||
|
||||
// 3. Validieren Sie das Objekt
|
||||
$validator = $container->get(Validator::class);
|
||||
$result = $validator->validate($userData);
|
||||
|
||||
// 4. Überprüfen Sie das Ergebnis
|
||||
if ($result->hasErrors()) {
|
||||
// Fehlerbehandlung
|
||||
$errors = $result->getAllErrorMessages();
|
||||
foreach ($errors as $error) {
|
||||
echo $error . "\n";
|
||||
}
|
||||
} else {
|
||||
// Erfolgreiche Validierung
|
||||
echo "Validierung erfolgreich!";
|
||||
}
|
||||
```
|
||||
|
||||
### Validierung mit benutzerdefinierten Fehlermeldungen
|
||||
|
||||
```php
|
||||
class ProductData
|
||||
{
|
||||
#[Required(message: "Der Produktname ist erforderlich.")]
|
||||
#[StringLength(min: 3, max: 100, message: "Der Produktname muss zwischen 3 und 100 Zeichen lang sein.")]
|
||||
public string $name;
|
||||
|
||||
#[Required(message: "Der Preis ist erforderlich.")]
|
||||
#[Range(min: 0, message: "Der Preis muss größer oder gleich 0 sein.")]
|
||||
public float $price;
|
||||
|
||||
#[Required(message: "Die Kategorie ist erforderlich.")]
|
||||
#[In(values: ['electronics', 'books', 'clothing'], message: "Ungültige Kategorie.")]
|
||||
public string $category;
|
||||
}
|
||||
```
|
||||
|
||||
### Zugriff auf feldspezifische Fehler
|
||||
|
||||
```php
|
||||
$result = $validator->validate($userData);
|
||||
|
||||
if ($result->hasErrors()) {
|
||||
// Alle Fehler für ein bestimmtes Feld abrufen
|
||||
$emailErrors = $result->getFieldErrors('email');
|
||||
foreach ($emailErrors as $error) {
|
||||
echo "Fehler im Feld 'email': $error\n";
|
||||
}
|
||||
|
||||
// Alle Fehler nach Feld gruppiert abrufen
|
||||
$allErrors = $result->getAll();
|
||||
foreach ($allErrors as $field => $errors) {
|
||||
echo "Fehler im Feld '$field':\n";
|
||||
foreach ($errors as $error) {
|
||||
echo "- $error\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fortgeschrittene Verwendung
|
||||
|
||||
### Validierungsgruppen
|
||||
|
||||
Validierungsgruppen ermöglichen es, verschiedene Validierungsszenarien für dasselbe Objekt zu definieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
use App\Framework\Validation\Rules\StringLength;
|
||||
|
||||
class UserProfile
|
||||
{
|
||||
#[Required(groups: ['registration', 'profile'])]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required(groups: ['registration'])]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required(groups: ['profile'])]
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
|
||||
#[Required(groups: ['profile'])]
|
||||
#[Url]
|
||||
public ?string $website = null;
|
||||
}
|
||||
|
||||
// Validierung mit einer bestimmten Gruppe
|
||||
$result = $validator->validate($userProfile, 'registration');
|
||||
|
||||
// In diesem Fall werden nur die Regeln angewendet, die zur Gruppe 'registration' gehören
|
||||
// oder keine Gruppe spezifiziert haben
|
||||
```
|
||||
|
||||
### Verschachtelte Validierung
|
||||
|
||||
Validierung von Objekten, die andere validierbare Objekte enthalten:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
|
||||
class Address
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 100)]
|
||||
public string $street;
|
||||
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $city;
|
||||
|
||||
#[Required]
|
||||
#[StringLength(min: 5, max: 10)]
|
||||
public string $zipCode;
|
||||
}
|
||||
|
||||
class Customer
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
public Address $address;
|
||||
}
|
||||
|
||||
// Validierung eines Objekts mit verschachtelten Objekten
|
||||
$address = new Address();
|
||||
$address->street = 'Musterstraße 123';
|
||||
$address->city = 'Berlin';
|
||||
$address->zipCode = '10115';
|
||||
|
||||
$customer = new Customer();
|
||||
$customer->name = 'Max Mustermann';
|
||||
$customer->email = 'max@example.com';
|
||||
$customer->address = $address;
|
||||
|
||||
// Validierung des Hauptobjekts
|
||||
$result = $validator->validate($customer);
|
||||
|
||||
// Manuelle Validierung des verschachtelten Objekts
|
||||
$addressResult = $validator->validate($customer->address);
|
||||
$result->merge($addressResult); // Kombinieren der Ergebnisse
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Validierungslogik
|
||||
|
||||
Verwendung der `Custom`-Regel für komplexe Validierungsanforderungen:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Custom;
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
|
||||
class PasswordReset
|
||||
{
|
||||
#[Required]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
public string $password;
|
||||
|
||||
#[Required]
|
||||
#[Custom(callback: 'validatePasswordConfirmation', message: "Die Passwörter stimmen nicht überein.")]
|
||||
public string $passwordConfirmation;
|
||||
|
||||
private function validatePasswordConfirmation($value): bool
|
||||
{
|
||||
return $value === $this->password;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit Formularen
|
||||
|
||||
### Formularvalidierung
|
||||
|
||||
Verwendung des Validation Frameworks zur Validierung von Formulardaten:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationFormHandler;
|
||||
use App\Framework\Http\Response;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function handleRegistration(
|
||||
Request $request,
|
||||
ValidationFormHandler $formHandler,
|
||||
UserService $userService
|
||||
): Response {
|
||||
// Formulardaten abrufen
|
||||
$formData = $request->getFormData();
|
||||
|
||||
// Validierung durchführen
|
||||
$result = $formHandler->validate(UserRegistration::class, $formData);
|
||||
|
||||
if ($result->hasErrors()) {
|
||||
// Formular mit Fehlern neu rendern
|
||||
return $this->renderForm('registration', [
|
||||
'errors' => $result->getAll(),
|
||||
'data' => $formData
|
||||
]);
|
||||
}
|
||||
|
||||
// Erfolgreiche Validierung, Benutzer registrieren
|
||||
$userService->registerUser($formData);
|
||||
|
||||
// Weiterleitung zur Erfolgsseite
|
||||
return $this->redirect('/registration/success');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Formularvorlage mit Fehleranzeige
|
||||
|
||||
```html
|
||||
<form method="post" action="/register">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" value="<?= htmlspecialchars($data['name'] ?? '') ?>">
|
||||
<?php if (isset($errors['name'])): ?>
|
||||
<div class="error">
|
||||
<?= htmlspecialchars($errors['name'][0]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail:</label>
|
||||
<input type="email" id="email" name="email" value="<?= htmlspecialchars($data['email'] ?? '') ?>">
|
||||
<?php if (isset($errors['email'])): ?>
|
||||
<div class="error">
|
||||
<?= htmlspecialchars($errors['email'][0]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
<?php if (isset($errors['password'])): ?>
|
||||
<div class="error">
|
||||
<?= htmlspecialchars($errors['password'][0]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="submit">Registrieren</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## API-Validierung
|
||||
|
||||
### Middleware für API-Validierung
|
||||
|
||||
Das Framework bietet eine `InputValidationMiddleware` für die automatische Validierung von API-Anfragen:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(InputValidationMiddleware::class);
|
||||
```
|
||||
|
||||
Konfiguration in `config/validation.php`:
|
||||
|
||||
```php
|
||||
return [
|
||||
'routes' => [
|
||||
'/api/users' => [
|
||||
'POST' => UserCreateData::class,
|
||||
'PUT' => UserUpdateData::class
|
||||
],
|
||||
'/api/products' => [
|
||||
'POST' => ProductCreateData::class,
|
||||
'PUT' => ProductUpdateData::class
|
||||
]
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### API-Controller mit Validierung
|
||||
|
||||
```php
|
||||
class ApiController
|
||||
{
|
||||
public function createUser(Request $request): Response
|
||||
{
|
||||
// Die Validierung wurde bereits durch die Middleware durchgeführt
|
||||
// Wenn wir hier sind, waren die Daten gültig
|
||||
|
||||
$userData = $request->getJsonData();
|
||||
$user = $this->userService->createUser($userData);
|
||||
|
||||
return new Response(201, [], [
|
||||
'success' => true,
|
||||
'user' => $user->toArray()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fehlerbehandlung für API-Validierung
|
||||
|
||||
Die `InputValidationMiddleware` gibt automatisch eine Fehlerantwort zurück, wenn die Validierung fehlschlägt:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Validation failed",
|
||||
"validation_errors": {
|
||||
"name": ["Der Name ist erforderlich."],
|
||||
"email": ["Bitte geben Sie eine gültige E-Mail-Adresse ein."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Validierungs-Cache
|
||||
|
||||
Für häufig validierte Objekte kann ein Cache verwendet werden, um die Leistung zu verbessern:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationCacheDecorator;
|
||||
|
||||
// Cache-Decorator erstellen
|
||||
$cachedValidator = new ValidationCacheDecorator($validator, $cache);
|
||||
|
||||
// Validierung mit Cache durchführen
|
||||
$result = $cachedValidator->validate($userData);
|
||||
```
|
||||
|
||||
### Validierungsgruppen für partielle Validierung
|
||||
|
||||
Verwenden Sie Validierungsgruppen, um nur die relevanten Teile eines Objekts zu validieren:
|
||||
|
||||
```php
|
||||
// Nur die für die Aktualisierung relevanten Felder validieren
|
||||
$result = $validator->validate($userData, 'update');
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme und Lösungen
|
||||
|
||||
#### Validierungsregeln werden nicht angewendet
|
||||
|
||||
Stellen Sie sicher, dass:
|
||||
- Die Attribute korrekt definiert sind
|
||||
- Die Eigenschaften für den Validator zugänglich sind (public oder mit Reflection)
|
||||
- Der Validator korrekt initialisiert wurde
|
||||
|
||||
```php
|
||||
// Korrekte Initialisierung des Validators
|
||||
$validator = new Validator($reflectionProvider);
|
||||
```
|
||||
|
||||
#### Falsche Fehlermeldungen
|
||||
|
||||
Überprüfen Sie die Reihenfolge der Validierungsregeln:
|
||||
|
||||
```php
|
||||
// Korrekte Reihenfolge: Required vor anderen Regeln
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
```
|
||||
|
||||
#### Leistungsprobleme
|
||||
|
||||
Verwenden Sie den ValidationCacheDecorator und vermeiden Sie zu komplexe Validierungsregeln:
|
||||
|
||||
```php
|
||||
// Einfache Regeln verwenden
|
||||
#[StringLength(max: 255)] // Besser als komplexe reguläre Ausdrücke
|
||||
public string $name;
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Validation Framework Übersicht](index.md)
|
||||
- [Verfügbare Validierungsregeln](rules.md)
|
||||
- [API-Validierung](/guides/api-validation.md)
|
||||
338
docs/components/validation/index.md
Normal file
338
docs/components/validation/index.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Validation Framework
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Validation Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Validation Framework ist ein leistungsstarkes System zur Validierung von Objekten und Daten in der Anwendung. Es nutzt moderne PHP-Attribute, um Validierungsregeln direkt an Objekteigenschaften zu definieren, und bietet eine flexible, erweiterbare Architektur für verschiedene Validierungsanforderungen.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Validator-Klasse
|
||||
|
||||
Die zentrale Klasse für die Validierungsfunktionalität:
|
||||
|
||||
```php
|
||||
// Klasse initialisieren
|
||||
$validator = new Validator($reflectionProvider);
|
||||
|
||||
// Objekt validieren
|
||||
$result = $validator->validate($object);
|
||||
|
||||
// Validierungsergebnis prüfen
|
||||
if ($result->hasErrors()) {
|
||||
// Fehlerbehandlung
|
||||
$errors = $result->getAllErrorMessages();
|
||||
}
|
||||
```
|
||||
|
||||
### ValidationRule-Interface
|
||||
|
||||
Das Basis-Interface für alle Validierungsregeln:
|
||||
|
||||
```php
|
||||
interface ValidationRule
|
||||
{
|
||||
public function validate(mixed $value): bool;
|
||||
public function getErrorMessages(): array;
|
||||
}
|
||||
```
|
||||
|
||||
Alle Validierungsregeln implementieren dieses Interface und werden als PHP-Attribute verwendet.
|
||||
|
||||
### ValidationResult-Klasse
|
||||
|
||||
Speichert und verwaltet Validierungsergebnisse:
|
||||
|
||||
- Sammelt Fehlermeldungen für verschiedene Felder
|
||||
- Bietet Methoden zum Abfragen von Fehlern
|
||||
- Ermöglicht das Zusammenführen mehrerer Validierungsergebnisse
|
||||
|
||||
```php
|
||||
// Fehler prüfen
|
||||
if ($result->hasErrors()) {
|
||||
// Alle Fehlermeldungen abrufen
|
||||
$allErrors = $result->getAllErrorMessages();
|
||||
|
||||
// Fehler für ein bestimmtes Feld abrufen
|
||||
$fieldErrors = $result->getFieldErrors('email');
|
||||
}
|
||||
```
|
||||
|
||||
## Validierungsregeln
|
||||
|
||||
Das Framework bietet eine Vielzahl vordefinierter Validierungsregeln:
|
||||
|
||||
### Grundlegende Regeln
|
||||
|
||||
- **Required**: Stellt sicher, dass ein Wert vorhanden ist
|
||||
- **Email**: Validiert E-Mail-Adressen
|
||||
- **Url**: Validiert URLs
|
||||
- **Numeric**: Stellt sicher, dass ein Wert numerisch ist
|
||||
- **StringLength**: Validiert die Länge eines Strings
|
||||
|
||||
### Erweiterte Regeln
|
||||
|
||||
- **Pattern**: Validiert Werte gegen reguläre Ausdrücke
|
||||
- **Range**: Validiert numerische Werte innerhalb eines Bereichs
|
||||
- **In**: Prüft, ob ein Wert in einer Liste gültiger Werte enthalten ist
|
||||
- **DateFormat**: Validiert Datumsformate
|
||||
- **Phone**: Validiert Telefonnummern
|
||||
- **Ulid**: Validiert ULID-Werte
|
||||
- **IsTrue**: Prüft, ob ein Wert true ist
|
||||
|
||||
### Benutzerdefinierte Regeln
|
||||
|
||||
Eigene Validierungsregeln können durch Implementierung des ValidationRule-Interfaces erstellt werden:
|
||||
|
||||
```php
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class CustomRule implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
private string $param,
|
||||
private ?string $message = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
// Validierungslogik implementieren
|
||||
return true; // oder false
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? 'Standardfehlermeldung'];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Objekte mit Attributen validieren
|
||||
|
||||
```php
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
|
||||
#[Range(min: 18)]
|
||||
public ?int $age = null;
|
||||
}
|
||||
|
||||
// Validierung durchführen
|
||||
$userData = new UserData();
|
||||
$userData->name = 'Max';
|
||||
$userData->email = 'invalid-email';
|
||||
|
||||
$result = $validator->validate($userData);
|
||||
```
|
||||
|
||||
### Validierungsgruppen
|
||||
|
||||
Das Framework unterstützt Validierungsgruppen, um verschiedene Validierungsszenarien zu ermöglichen:
|
||||
|
||||
```php
|
||||
class UserData
|
||||
{
|
||||
#[Required(groups: ['registration', 'profile'])]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required(groups: ['registration'])]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required(groups: ['profile'])]
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
}
|
||||
|
||||
// Validierung mit Gruppe durchführen
|
||||
$result = $validator->validate($userData, 'registration');
|
||||
```
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
```php
|
||||
if ($result->hasErrors()) {
|
||||
foreach ($result->getAll() as $field => $errors) {
|
||||
echo "Fehler im Feld '$field':\n";
|
||||
foreach ($errors as $error) {
|
||||
echo "- $error\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Formular-Validierung
|
||||
|
||||
Das Framework bietet eine `ValidationFormHandler`-Klasse für die Validierung von Formulardaten:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function handleForm(Request $request, ValidationFormHandler $formHandler): Response
|
||||
{
|
||||
$formData = $request->getFormData();
|
||||
|
||||
$result = $formHandler->validate(UserData::class, $formData);
|
||||
|
||||
if ($result->hasErrors()) {
|
||||
return $this->renderForm('user_form', [
|
||||
'errors' => $result->getAll(),
|
||||
'data' => $formData
|
||||
]);
|
||||
}
|
||||
|
||||
// Erfolgreiche Validierung, Daten verarbeiten
|
||||
$this->userService->createUser($formData);
|
||||
|
||||
return $this->redirect('/success');
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware-Integration
|
||||
|
||||
Das Framework bietet eine `InputValidationMiddleware` für die automatische Validierung von API-Anfragen:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(InputValidationMiddleware::class);
|
||||
```
|
||||
|
||||
Konfiguration in `config/validation.php`:
|
||||
|
||||
```php
|
||||
return [
|
||||
'routes' => [
|
||||
'/api/users' => [
|
||||
'POST' => UserData::class,
|
||||
'PUT' => UserUpdateData::class
|
||||
]
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Erweiterbarkeit
|
||||
|
||||
### Eigene Validierungsregeln erstellen
|
||||
|
||||
1. Interface implementieren:
|
||||
|
||||
```php
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Password implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
private int $minLength = 8,
|
||||
private bool $requireSpecialChars = true,
|
||||
private ?string $message = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return true; // Leere Werte werden von Required-Regel behandelt
|
||||
}
|
||||
|
||||
if (!is_string($value) || strlen($value) < $this->minLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->requireSpecialChars && !preg_match('/[^a-zA-Z0-9]/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? "Das Passwort muss mindestens {$this->minLength} Zeichen lang sein und Sonderzeichen enthalten."];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Verwendung:
|
||||
|
||||
```php
|
||||
class UserRegistration
|
||||
{
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
#[Password(minLength: 10)]
|
||||
public string $password;
|
||||
}
|
||||
```
|
||||
|
||||
### Validierungsgruppen implementieren
|
||||
|
||||
Für Regeln, die Validierungsgruppen unterstützen sollen, implementieren Sie das `GroupAware`-Interface:
|
||||
|
||||
```php
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Required implements ValidationRule, GroupAware
|
||||
{
|
||||
/**
|
||||
* @param array<string> $groups Validierungsgruppen
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $message = null,
|
||||
private array $groups = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
return $value !== null && $value !== '';
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? 'Dieser Wert ist erforderlich.'];
|
||||
}
|
||||
|
||||
public function belongsToGroup(string $group): bool
|
||||
{
|
||||
return empty($this->groups) || in_array($group, $this->groups);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **Validierungsregeln werden nicht angewendet**:
|
||||
- Stellen Sie sicher, dass die Attribute korrekt definiert sind
|
||||
- Überprüfen Sie, ob die Eigenschaften für den Validator zugänglich sind (public oder mit Reflection)
|
||||
|
||||
2. **Falsche Fehlermeldungen**:
|
||||
- Überprüfen Sie die Reihenfolge der Validierungsregeln
|
||||
- Stellen Sie sicher, dass die Regeln für den richtigen Datentyp verwendet werden
|
||||
|
||||
3. **Leistungsprobleme**:
|
||||
- Verwenden Sie ValidationCacheDecorator für häufig validierte Objekte
|
||||
- Vermeiden Sie zu komplexe Validierungsregeln
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Verfügbare Validierungsregeln](rules.md)
|
||||
- [Formular-Integration](examples.md)
|
||||
- [API-Validierung](/guides/api-validation.md)
|
||||
451
docs/components/validation/rules.md
Normal file
451
docs/components/validation/rules.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# Validierungsregeln
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Validierungsregeln korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Validation Framework bietet eine umfangreiche Sammlung vordefinierter Validierungsregeln, die als PHP-Attribute verwendet werden können. Diese Regeln können auf Objekteigenschaften angewendet werden, um Daten zu validieren und sicherzustellen, dass sie den erwarteten Anforderungen entsprechen.
|
||||
|
||||
## Grundlegende Regeln
|
||||
|
||||
### Required
|
||||
|
||||
Die `Required`-Regel stellt sicher, dass ein Wert vorhanden ist und nicht leer ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
public string $username;
|
||||
|
||||
#[Required(message: "Die E-Mail-Adresse ist erforderlich.")]
|
||||
public string $email;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
- `groups` (optional): Array von Validierungsgruppen, zu denen diese Regel gehört
|
||||
|
||||
**Validierungslogik:**
|
||||
- Gibt `false` zurück, wenn der Wert `null` oder ein leerer String ist
|
||||
- Gibt `true` zurück für alle anderen Werte, einschließlich `0` und `false`
|
||||
|
||||
### Email
|
||||
|
||||
Die `Email`-Regel validiert, dass ein Wert eine gültige E-Mail-Adresse ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Email(message: "Bitte geben Sie eine gültige E-Mail-Adresse ein.")]
|
||||
public string $alternativeEmail;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `filter_var()` mit `FILTER_VALIDATE_EMAIL` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### StringLength
|
||||
|
||||
Die `StringLength`-Regel validiert die Länge eines Strings:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\StringLength;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $username;
|
||||
|
||||
#[StringLength(min: 8, message: "Das Passwort muss mindestens 8 Zeichen lang sein.")]
|
||||
public string $password;
|
||||
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `min` (optional): Minimale Länge des Strings
|
||||
- `max` (optional): Maximale Länge des Strings
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Mindestens einer der Parameter `min` oder `max` muss gesetzt sein
|
||||
- Verwendet `mb_strlen()` für korrekte Behandlung von Multibyte-Zeichen
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Url
|
||||
|
||||
Die `Url`-Regel validiert, dass ein Wert eine gültige URL ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Url;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Url]
|
||||
public string $website;
|
||||
|
||||
#[Url(message: "Bitte geben Sie eine gültige URL ein.")]
|
||||
public string $profileUrl;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `filter_var()` mit `FILTER_VALIDATE_URL` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Numeric
|
||||
|
||||
Die `Numeric`-Regel validiert, dass ein Wert numerisch ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Numeric;
|
||||
|
||||
class ProductData
|
||||
{
|
||||
#[Numeric]
|
||||
public $price;
|
||||
|
||||
#[Numeric(message: "Die Menge muss eine Zahl sein.")]
|
||||
public $quantity;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `is_numeric()` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
## Erweiterte Regeln
|
||||
|
||||
### Pattern
|
||||
|
||||
Die `Pattern`-Regel validiert einen Wert gegen einen regulären Ausdruck:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Pattern;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Pattern(pattern: '/^[a-zA-Z0-9_]+$/', message: "Der Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten.")]
|
||||
public string $username;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `pattern`: Regulärer Ausdruck für die Validierung
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `preg_match()` zur Validierung gegen den angegebenen regulären Ausdruck
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Range
|
||||
|
||||
Die `Range`-Regel validiert, dass ein numerischer Wert innerhalb eines bestimmten Bereichs liegt:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Range;
|
||||
|
||||
class ProductData
|
||||
{
|
||||
#[Range(min: 0)]
|
||||
public float $price;
|
||||
|
||||
#[Range(min: 1, max: 100, message: "Die Menge muss zwischen 1 und 100 liegen.")]
|
||||
public int $quantity;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `min` (optional): Minimaler Wert
|
||||
- `max` (optional): Maximaler Wert
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Mindestens einer der Parameter `min` oder `max` muss gesetzt sein
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### In
|
||||
|
||||
Die `In`-Regel validiert, dass ein Wert in einer Liste gültiger Werte enthalten ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\In;
|
||||
|
||||
class ProductData
|
||||
{
|
||||
#[In(values: ['small', 'medium', 'large'])]
|
||||
public string $size;
|
||||
|
||||
#[In(values: [1, 2, 3, 4, 5], message: "Bitte wählen Sie eine gültige Kategorie.")]
|
||||
public int $category;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `values`: Array gültiger Werte
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `in_array()` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### DateFormat
|
||||
|
||||
Die `DateFormat`-Regel validiert, dass ein Wert ein gültiges Datum in einem bestimmten Format ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\DateFormat;
|
||||
|
||||
class EventData
|
||||
{
|
||||
#[DateFormat(format: 'Y-m-d')]
|
||||
public string $date;
|
||||
|
||||
#[DateFormat(format: 'Y-m-d H:i:s', message: "Bitte geben Sie ein gültiges Datum und eine gültige Uhrzeit ein.")]
|
||||
public string $datetime;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `format`: Das erwartete Datumsformat (PHP date()-Format)
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `DateTime::createFromFormat()` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Phone
|
||||
|
||||
Die `Phone`-Regel validiert, dass ein Wert eine gültige Telefonnummer ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Phone;
|
||||
|
||||
class ContactData
|
||||
{
|
||||
#[Phone]
|
||||
public string $phoneNumber;
|
||||
|
||||
#[Phone(message: "Bitte geben Sie eine gültige Telefonnummer ein.")]
|
||||
public string $mobileNumber;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Validiert Telefonnummern mit einem flexiblen Muster, das verschiedene Formate unterstützt
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Ulid
|
||||
|
||||
Die `Ulid`-Regel validiert, dass ein Wert ein gültiger ULID (Universally Unique Lexicographically Sortable Identifier) ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Ulid;
|
||||
|
||||
class EntityData
|
||||
{
|
||||
#[Ulid]
|
||||
public string $id;
|
||||
|
||||
#[Ulid(message: "Die ID muss ein gültiger ULID sein.")]
|
||||
public string $parentId;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Validiert, dass der Wert ein gültiger ULID ist (26 Zeichen, Base32-Kodierung)
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### IsTrue
|
||||
|
||||
Die `IsTrue`-Regel validiert, dass ein Wert `true` ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\IsTrue;
|
||||
|
||||
class RegistrationData
|
||||
{
|
||||
#[IsTrue(message: "Sie müssen die Nutzungsbedingungen akzeptieren.")]
|
||||
public bool $termsAccepted;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Gibt `true` zurück, wenn der Wert `true` ist
|
||||
- Gibt `false` zurück für alle anderen Werte
|
||||
- Leere Werte werden als ungültig betrachtet
|
||||
|
||||
## Benutzerdefinierte Regeln
|
||||
|
||||
### Custom
|
||||
|
||||
Die `Custom`-Regel ermöglicht die Definition einer benutzerdefinierten Validierungslogik mit einer Callback-Funktion:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Custom;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Custom(callback: 'validateUsername', message: "Der Benutzername ist ungültig.")]
|
||||
public string $username;
|
||||
|
||||
private function validateUsername($value): bool
|
||||
{
|
||||
// Benutzerdefinierte Validierungslogik
|
||||
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $value) === 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `callback`: Name der Callback-Methode oder Closure
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Ruft die angegebene Callback-Funktion auf und gibt deren Rückgabewert zurück
|
||||
- Leere Werte werden an die Callback-Funktion übergeben
|
||||
|
||||
## Eigene Validierungsregeln erstellen
|
||||
|
||||
Sie können eigene Validierungsregeln erstellen, indem Sie das `ValidationRule`-Interface implementieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationRule;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Password implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
private int $minLength = 8,
|
||||
private bool $requireSpecialChars = true,
|
||||
private ?string $message = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return true; // Leere Werte werden von Required-Regel behandelt
|
||||
}
|
||||
|
||||
if (!is_string($value) || strlen($value) < $this->minLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->requireSpecialChars && !preg_match('/[^a-zA-Z0-9]/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? "Das Passwort muss mindestens {$this->minLength} Zeichen lang sein und Sonderzeichen enthalten."];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validierungsgruppen
|
||||
|
||||
Für Regeln, die Validierungsgruppen unterstützen sollen, implementieren Sie das `GroupAware`-Interface:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationRule;
|
||||
use App\Framework\Validation\GroupAware;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Required implements ValidationRule, GroupAware
|
||||
{
|
||||
/**
|
||||
* @param array<string> $groups Validierungsgruppen
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $message = null,
|
||||
private array $groups = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
return $value !== null && $value !== '';
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? 'Dieser Wert ist erforderlich.'];
|
||||
}
|
||||
|
||||
public function belongsToGroup(string $group): bool
|
||||
{
|
||||
return empty($this->groups) || in_array($group, $this->groups);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kombinieren von Regeln
|
||||
|
||||
Validierungsregeln können kombiniert werden, um komplexe Validierungsanforderungen zu erfüllen:
|
||||
|
||||
```php
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
#[Pattern(pattern: '/^[a-zA-Z0-9_]+$/')]
|
||||
public string $username;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
#[StringLength(min: 8)]
|
||||
#[Custom(callback: 'validatePassword')]
|
||||
public string $password;
|
||||
|
||||
private function validatePassword($value): bool
|
||||
{
|
||||
// Mindestens ein Großbuchstabe, ein Kleinbuchstabe, eine Zahl und ein Sonderzeichen
|
||||
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).+$/', $value) === 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Validation Framework Übersicht](index.md)
|
||||
- [Beispiele für die Verwendung von Validierungsregeln](examples.md)
|
||||
- [API-Validierung](/guides/api-validation.md)
|
||||
279
docs/components/waf/configuration.md
Normal file
279
docs/components/waf/configuration.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# WAF Konfiguration
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der WAF-Konfiguration korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die Web Application Firewall (WAF) bietet umfangreiche Konfigurationsmöglichkeiten, um das Sicherheitsniveau und die Leistung an die Anforderungen Ihrer Anwendung anzupassen. Diese Dokumentation beschreibt die verfügbaren Konfigurationsoptionen und wie Sie diese anwenden können.
|
||||
|
||||
## Konfigurationsklasse
|
||||
|
||||
Die zentrale Klasse für die WAF-Konfiguration ist `WafConfig`. Diese Klasse enthält alle Einstellungen, die das Verhalten der WAF steuern:
|
||||
|
||||
```php
|
||||
// Konfiguration erstellen
|
||||
$config = new WafConfig(
|
||||
$enabled,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing,
|
||||
$maxParallelLayers,
|
||||
$detailedLogging,
|
||||
$metricsEnabled,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
## Vordefinierte Konfigurationen
|
||||
|
||||
Die `WafConfig`-Klasse bietet mehrere vordefinierte Konfigurationen für verschiedene Umgebungen:
|
||||
|
||||
### Produktionsumgebung
|
||||
|
||||
```php
|
||||
// Produktionskonfiguration verwenden
|
||||
$config = WafConfig::production();
|
||||
```
|
||||
|
||||
Die Produktionskonfiguration aktiviert alle Sicherheitsfeatures mit strengen Einstellungen:
|
||||
- Alle Schutzebenen aktiviert
|
||||
- Niedrige Schwellenwerte für Blockierung
|
||||
- Parallele Verarbeitung für optimale Leistung
|
||||
- Detaillierte Metriken für Überwachung
|
||||
|
||||
### Entwicklungsumgebung
|
||||
|
||||
```php
|
||||
// Entwicklungskonfiguration verwenden
|
||||
$config = WafConfig::development();
|
||||
```
|
||||
|
||||
Die Entwicklungskonfiguration ist weniger streng und bietet:
|
||||
- Höhere Schwellenwerte für Blockierung
|
||||
- Detailliertes Logging für Debugging
|
||||
- Alle Schutzebenen aktiviert, aber mit weniger strengen Einstellungen
|
||||
|
||||
### Testumgebung
|
||||
|
||||
```php
|
||||
// Testkonfiguration verwenden
|
||||
$config = WafConfig::testing();
|
||||
```
|
||||
|
||||
Die Testkonfiguration ist für automatisierte Tests optimiert:
|
||||
- Reduzierte Timeouts
|
||||
- Deaktivierte parallele Verarbeitung
|
||||
- Minimales Logging
|
||||
|
||||
### Deaktivierte Konfiguration
|
||||
|
||||
```php
|
||||
// WAF deaktivieren
|
||||
$config = WafConfig::disabled();
|
||||
```
|
||||
|
||||
Diese Konfiguration deaktiviert die WAF vollständig, was für bestimmte Entwicklungs- oder Diagnoseszenarien nützlich sein kann.
|
||||
|
||||
## Hauptkonfigurationsoptionen
|
||||
|
||||
### Aktivierung/Deaktivierung
|
||||
|
||||
```php
|
||||
// WAF aktivieren
|
||||
$config = $config->enable();
|
||||
|
||||
// WAF deaktivieren
|
||||
$config = $config->disable();
|
||||
```
|
||||
|
||||
### Timeout-Einstellungen
|
||||
|
||||
```php
|
||||
// Globales Timeout für WAF-Verarbeitung setzen
|
||||
$config = $config->withGlobalTimeout(Duration::fromMilliseconds(100));
|
||||
```
|
||||
|
||||
### Schwellenwerte
|
||||
|
||||
```php
|
||||
// Schwellenwert für Blockierung setzen
|
||||
$config = $config->withBlockingThreshold(Percentage::fromFloat(0.75));
|
||||
```
|
||||
|
||||
Der Blockierungsschwellenwert bestimmt, ab welchem Konfidenzwert ein Request blockiert wird. Ein höherer Wert bedeutet weniger Blockierungen (und potenziell mehr falsch negative Ergebnisse), während ein niedrigerer Wert mehr Blockierungen (und potenziell mehr falsch positive Ergebnisse) bedeutet.
|
||||
|
||||
## Schutzebenen-Konfiguration
|
||||
|
||||
Die WAF verwendet ein mehrschichtiges Schutzsystem. Jede Schutzebene kann individuell konfiguriert werden:
|
||||
|
||||
```php
|
||||
// Schutzebene aktivieren mit benutzerdefinierter Konfiguration
|
||||
$config = $config->enableLayer(
|
||||
'statistical',
|
||||
new LayerConfig(
|
||||
$enabled = true,
|
||||
$weight = 0.6,
|
||||
$threshold = Percentage::fromFloat(0.7),
|
||||
$timeout = Duration::fromMilliseconds(50)
|
||||
)
|
||||
);
|
||||
|
||||
// Schutzebene deaktivieren
|
||||
$config = $config->disableLayer('clustering');
|
||||
```
|
||||
|
||||
### Verfügbare Schutzebenen
|
||||
|
||||
- **statistical**: Statistische Anomalieerkennung
|
||||
- **clustering**: Clustering-basierte Anomalieerkennung
|
||||
- **signature**: Signaturbasierte Erkennung bekannter Angriffsmuster
|
||||
- **behavioral**: Verhaltensbasierte Anomalieerkennung
|
||||
- **ratelimiting**: Ratenbegrenzung für Anfragen
|
||||
|
||||
## Benutzerdefinierte Einstellungen
|
||||
|
||||
Die WAF unterstützt benutzerdefinierte Einstellungen für spezielle Anwendungsfälle:
|
||||
|
||||
```php
|
||||
// Benutzerdefinierte Einstellung hinzufügen
|
||||
$config = $config->withCustomSetting('whitelist_ips', ['127.0.0.1', '192.168.1.1']);
|
||||
|
||||
// Benutzerdefinierte Einstellung abrufen
|
||||
$whitelistIps = $config->getCustomSetting('whitelist_ips', []);
|
||||
```
|
||||
|
||||
## Konfigurationsvalidierung
|
||||
|
||||
Die WAF-Konfiguration kann validiert werden, um sicherzustellen, dass alle Einstellungen gültig sind:
|
||||
|
||||
```php
|
||||
// Konfiguration validieren
|
||||
$validationErrors = $config->validate();
|
||||
|
||||
// Prüfen, ob die Konfiguration gültig ist
|
||||
if (!$config->isValid()) {
|
||||
// Fehlerbehandlung
|
||||
}
|
||||
```
|
||||
|
||||
## Konfiguration in der Anwendung
|
||||
|
||||
### Konfigurationsdatei
|
||||
|
||||
Die WAF-Konfiguration kann in einer dedizierten Konfigurationsdatei definiert werden:
|
||||
|
||||
```php
|
||||
// config/waf.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'learning_mode' => false,
|
||||
'threshold' => 0.75,
|
||||
'detectors' => [
|
||||
'statistical' => true,
|
||||
'clustering' => true
|
||||
],
|
||||
'whitelist' => [
|
||||
'ips' => ['127.0.0.1'],
|
||||
'paths' => ['/api/health']
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### Laufzeitkonfiguration
|
||||
|
||||
Die WAF-Konfiguration kann zur Laufzeit aktualisiert werden:
|
||||
|
||||
```php
|
||||
// WAF-Konfiguration aktualisieren
|
||||
$waf->updateConfig($newConfig);
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Parallele Verarbeitung
|
||||
|
||||
Die parallele Verarbeitung kann die Leistung der WAF verbessern, insbesondere bei mehreren aktivierten Schutzebenen:
|
||||
|
||||
```php
|
||||
// Parallele Verarbeitung aktivieren
|
||||
$config = new WafConfig(
|
||||
$enabled = true,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing = true,
|
||||
$maxParallelLayers = 4,
|
||||
$detailedLogging,
|
||||
$metricsEnabled,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
### Caching
|
||||
|
||||
Die WAF unterstützt Caching für wiederholte Anfragen, um die Leistung zu verbessern:
|
||||
|
||||
```php
|
||||
// Benutzerdefinierte Caching-Einstellungen
|
||||
$config = $config->withCustomSetting('cache_ttl', 300); // 5 Minuten
|
||||
$config = $config->withCustomSetting('cache_size', 1000); // 1000 Einträge
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Logging
|
||||
|
||||
Detailliertes Logging kann für die Fehlerbehebung aktiviert werden:
|
||||
|
||||
```php
|
||||
// Detailliertes Logging aktivieren
|
||||
$config = new WafConfig(
|
||||
$enabled = true,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing,
|
||||
$maxParallelLayers,
|
||||
$detailedLogging = true,
|
||||
$metricsEnabled,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
### Metriken
|
||||
|
||||
Die WAF kann Metriken zur Leistung und Effektivität sammeln:
|
||||
|
||||
```php
|
||||
// Metriken aktivieren
|
||||
$config = new WafConfig(
|
||||
$enabled = true,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing,
|
||||
$maxParallelLayers,
|
||||
$detailedLogging,
|
||||
$metricsEnabled = true,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [WAF-Übersicht](index.md)
|
||||
- [Machine Learning Dokumentation](machine-learning.md)
|
||||
- [Sicherheitsrichtlinien](/guides/security.md)
|
||||
280
docs/components/waf/feedback.md
Normal file
280
docs/components/waf/feedback.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# WAF Feedback System
|
||||
|
||||
The Web Application Firewall (WAF) Feedback System allows users to provide feedback on WAF detections, helping to improve the accuracy of the security system over time. This document explains how to use the feedback system, how it works, and how to integrate it into your application.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [User Guide](#user-guide)
|
||||
- [Submitting Feedback](#submitting-feedback)
|
||||
- [Types of Feedback](#types-of-feedback)
|
||||
- [Feedback Dashboard](#feedback-dashboard)
|
||||
2. [Technical Documentation](#technical-documentation)
|
||||
- [Architecture](#architecture)
|
||||
- [Components](#components)
|
||||
- [Integration](#integration)
|
||||
3. [Learning Algorithms](#learning-algorithms)
|
||||
- [False Positive Reduction](#false-positive-reduction)
|
||||
- [False Negative Reduction](#false-negative-reduction)
|
||||
- [Severity Adjustment](#severity-adjustment)
|
||||
4. [Examples](#examples)
|
||||
- [API Examples](#api-examples)
|
||||
- [Dashboard Examples](#dashboard-examples)
|
||||
- [Learning Examples](#learning-examples)
|
||||
|
||||
## User Guide
|
||||
|
||||
### Submitting Feedback
|
||||
|
||||
When the WAF detects a potential security threat, it may block the request or display a warning. Users with appropriate permissions can provide feedback on these detections to help improve the system's accuracy.
|
||||
|
||||
To submit feedback:
|
||||
|
||||
1. Navigate to the security event in the admin dashboard
|
||||
2. Click on the "Provide Feedback" button
|
||||
3. Select the type of feedback (false positive, false negative, correct detection, or severity adjustment)
|
||||
4. Add an optional comment explaining your feedback
|
||||
5. Click "Submit"
|
||||
|
||||
Alternatively, you can use the API to submit feedback programmatically (see [API Examples](#api-examples)).
|
||||
|
||||
### Types of Feedback
|
||||
|
||||
The WAF Feedback System supports four types of feedback:
|
||||
|
||||
1. **False Positive**: The WAF incorrectly identified a legitimate request as a security threat. This feedback helps reduce false alarms.
|
||||
|
||||
2. **False Negative**: The WAF failed to detect a security threat. This feedback helps improve detection coverage.
|
||||
|
||||
3. **Correct Detection**: The WAF correctly identified a security threat. This feedback confirms the accuracy of the detection.
|
||||
|
||||
4. **Severity Adjustment**: The WAF correctly identified a security threat, but the severity level was incorrect. This feedback helps calibrate the severity assessment.
|
||||
|
||||
### Feedback Dashboard
|
||||
|
||||
The Feedback Dashboard provides an overview of all feedback submitted to the WAF system, along with analytics and trends. To access the dashboard:
|
||||
|
||||
1. Navigate to Admin > Security > WAF > Feedback
|
||||
2. Use the filters to view specific types of feedback, categories, or time periods
|
||||
3. View charts and graphs showing feedback trends and detection accuracy
|
||||
4. Access detailed reports for specific detection categories
|
||||
|
||||
## Technical Documentation
|
||||
|
||||
### Architecture
|
||||
|
||||
The WAF Feedback System consists of several components that work together to collect, store, analyze, and learn from user feedback:
|
||||
|
||||
```
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| | | | | |
|
||||
| Feedback API | --> | Feedback Service | --> | Feedback Storage |
|
||||
| | | | | |
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
|
|
||||
v
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| | | | | |
|
||||
| Learning Service | <-- | Analytics | --> | Reporting |
|
||||
| | | | | |
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
|
|
||||
v
|
||||
+-------------------+
|
||||
| |
|
||||
| ML Engine |
|
||||
| |
|
||||
+-------------------+
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
The WAF Feedback System includes the following components:
|
||||
|
||||
1. **Feedback API**: Provides endpoints for submitting and retrieving feedback.
|
||||
- `POST /api/security/waf/feedback`: Submit feedback
|
||||
- `GET /api/security/waf/feedback/{detectionId}`: Get feedback for a specific detection
|
||||
- `GET /api/security/waf/feedback/stats`: Get feedback statistics
|
||||
- `GET /api/security/waf/feedback/recent`: Get recent feedback
|
||||
|
||||
2. **Feedback Service**: Handles the business logic for feedback submission and retrieval.
|
||||
- `FeedbackService`: Core service for handling feedback
|
||||
- `DetectionFeedback`: Value object representing feedback
|
||||
- `FeedbackType`: Enum of feedback types
|
||||
|
||||
3. **Feedback Storage**: Stores feedback data for later analysis.
|
||||
- `FeedbackRepositoryInterface`: Interface for feedback storage
|
||||
- `DatabaseFeedbackRepository`: Database implementation
|
||||
|
||||
4. **Learning Service**: Analyzes feedback and adjusts the WAF models.
|
||||
- `FeedbackLearningService`: Core service for learning from feedback
|
||||
- `ModelAdjustment`: Value object representing model adjustments
|
||||
|
||||
5. **Analytics**: Analyzes feedback data to identify patterns and trends.
|
||||
- `FeedbackReportGenerator`: Generates reports from feedback data
|
||||
|
||||
6. **Reporting**: Provides visualizations and reports on feedback data.
|
||||
- `WafFeedbackDashboardController`: Controller for the feedback dashboard
|
||||
|
||||
7. **ML Engine**: Machine learning engine that uses feedback to improve detection.
|
||||
- `MachineLearningEngine`: Core ML engine
|
||||
- `ThresholdAdjustableInterface`: Interface for adjustable thresholds
|
||||
- `ConfidenceAdjustableInterface`: Interface for adjustable confidence
|
||||
- `FeatureWeightAdjustableInterface`: Interface for adjustable feature weights
|
||||
|
||||
### Integration
|
||||
|
||||
To integrate the WAF Feedback System into your application:
|
||||
|
||||
1. **Register Services**: Register the feedback services in your dependency injection container:
|
||||
|
||||
```php
|
||||
// Register feedback services
|
||||
$container->singleton(FeedbackRepositoryInterface::class, DatabaseFeedbackRepository::class);
|
||||
$container->singleton(FeedbackService::class, FeedbackService::class);
|
||||
$container->singleton(FeedbackLearningService::class, FeedbackLearningService::class);
|
||||
$container->singleton(FeedbackReportGenerator::class, FeedbackReportGenerator::class);
|
||||
```
|
||||
|
||||
2. **Register Controllers**: Register the feedback controllers:
|
||||
|
||||
```php
|
||||
// Register feedback controllers
|
||||
$container->singleton(WafFeedbackController::class, WafFeedbackController::class);
|
||||
$container->singleton(WafFeedbackDashboardController::class, WafFeedbackDashboardController::class);
|
||||
```
|
||||
|
||||
3. **Schedule Learning**: Schedule the learning process to run periodically:
|
||||
|
||||
```php
|
||||
// Schedule learning process to run daily
|
||||
$scheduler->daily('waf:learn-from-feedback');
|
||||
```
|
||||
|
||||
4. **Add UI Components**: Add feedback UI components to your application:
|
||||
|
||||
```php
|
||||
// Add feedback button to WAF detection alerts
|
||||
$alertComponent->addAction('Provide Feedback', '/admin/security/waf/feedback/submit/{detectionId}');
|
||||
```
|
||||
|
||||
## Learning Algorithms
|
||||
|
||||
The WAF Feedback System uses several learning algorithms to improve detection accuracy based on user feedback.
|
||||
|
||||
### False Positive Reduction
|
||||
|
||||
When users report false positives, the system adjusts the detection models to reduce the likelihood of similar false positives in the future:
|
||||
|
||||
1. **Threshold Adjustment**: Increases the detection threshold for the affected category, making it less sensitive.
|
||||
2. **Confidence Reduction**: Decreases the confidence score for similar detections, making them less likely to trigger alerts.
|
||||
3. **Feature Weight Adjustment**: Reduces the weights of features that contributed to the false positive.
|
||||
|
||||
### False Negative Reduction
|
||||
|
||||
When users report false negatives, the system adjusts the detection models to improve detection coverage:
|
||||
|
||||
1. **Threshold Adjustment**: Decreases the detection threshold for the affected category, making it more sensitive.
|
||||
2. **Confidence Increase**: Increases the confidence score for similar detections, making them more likely to trigger alerts.
|
||||
3. **Feature Weight Adjustment**: Increases the weights of features that should have contributed to detection.
|
||||
|
||||
### Severity Adjustment
|
||||
|
||||
When users report incorrect severity levels, the system adjusts the severity assessment:
|
||||
|
||||
1. **Confidence Adjustment**: Adjusts the confidence score based on the severity change.
|
||||
2. **Category Default Severity**: Updates the default severity for the detection category.
|
||||
|
||||
## Examples
|
||||
|
||||
### API Examples
|
||||
|
||||
#### Submitting Feedback
|
||||
|
||||
```javascript
|
||||
// Example: Submit false positive feedback
|
||||
fetch('/api/security/waf/feedback', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
detection_id: 'abc123',
|
||||
feedback_type: 'false_positive',
|
||||
category: 'SQL_INJECTION',
|
||||
severity: 'HIGH',
|
||||
comment: 'This is a legitimate query used by our reporting system',
|
||||
context: {
|
||||
query: 'SELECT * FROM reports WHERE date > ?'
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
```
|
||||
|
||||
#### Getting Feedback Statistics
|
||||
|
||||
```javascript
|
||||
// Example: Get feedback statistics
|
||||
fetch('/api/security/waf/feedback/stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Total feedback:', data.stats.total_count);
|
||||
console.log('False positives:', data.stats.by_feedback_type.false_positive);
|
||||
console.log('False negatives:', data.stats.by_feedback_type.false_negative);
|
||||
});
|
||||
```
|
||||
|
||||
### Dashboard Examples
|
||||
|
||||
#### Viewing Feedback Trends
|
||||
|
||||
The Feedback Dashboard provides visualizations of feedback trends over time:
|
||||
|
||||
- **Accuracy Trend**: Shows how detection accuracy has improved over time
|
||||
- **False Positive Rate**: Shows how the false positive rate has decreased
|
||||
- **False Negative Rate**: Shows how the false negative rate has decreased
|
||||
- **Category Breakdown**: Shows feedback distribution by detection category
|
||||
|
||||
#### Analyzing Detection Categories
|
||||
|
||||
The Category Analysis view shows detailed information about each detection category:
|
||||
|
||||
- **Accuracy**: Detection accuracy for the category
|
||||
- **False Positive Rate**: False positive rate for the category
|
||||
- **False Negative Rate**: False negative rate for the category
|
||||
- **Severity Distribution**: Distribution of severity levels for the category
|
||||
- **Common Patterns**: Common patterns in false positives and false negatives
|
||||
|
||||
### Learning Examples
|
||||
|
||||
#### Scheduled Learning
|
||||
|
||||
The learning process can be scheduled to run periodically:
|
||||
|
||||
```bash
|
||||
# Run learning process daily
|
||||
php console.php waf:learn-from-feedback
|
||||
```
|
||||
|
||||
#### Manual Learning
|
||||
|
||||
The learning process can also be triggered manually:
|
||||
|
||||
```bash
|
||||
# Run learning process with custom parameters
|
||||
php console.php waf:learn-from-feedback --threshold=10 --learning-rate=0.5
|
||||
```
|
||||
|
||||
#### Learning Results
|
||||
|
||||
The learning process provides detailed results:
|
||||
|
||||
```
|
||||
Starting WAF feedback learning process...
|
||||
Processing 25 false positives, 10 false negatives, 5 severity adjustments
|
||||
Created 3 model adjustments for SQL_INJECTION, XSS, COMMAND_INJECTION
|
||||
Applied 3 model adjustments
|
||||
Learning process completed successfully in 0.5 seconds
|
||||
```
|
||||
135
docs/components/waf/index.md
Normal file
135
docs/components/waf/index.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# WAF (Web Application Firewall)
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der WAF-Komponente korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die WAF-Komponente (Web Application Firewall) ist ein fortschrittliches Sicherheitssystem, das Webanwendungen vor verschiedenen Bedrohungen schützt. Es verwendet Machine Learning und statistische Analysen, um Anomalien zu erkennen und potenzielle Angriffe zu blockieren, bevor sie Schaden anrichten können.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### WAF-Klasse
|
||||
|
||||
Die zentrale Klasse für die WAF-Funktionalität:
|
||||
|
||||
```php
|
||||
// Klasse initialisieren
|
||||
$waf = new Waf($config, $eventDispatcher);
|
||||
|
||||
// Request prüfen
|
||||
$result = $waf->analyzeRequest($request);
|
||||
|
||||
// Entscheidung treffen
|
||||
if ($result->isBlocked()) {
|
||||
// Request blockieren
|
||||
}
|
||||
```
|
||||
|
||||
### MachineLearningEngine
|
||||
|
||||
Verarbeitet Requests mit Machine-Learning-Algorithmen:
|
||||
|
||||
- Extrahiert Features aus Requests
|
||||
- Vergleicht mit bekannten Mustern
|
||||
- Erkennt Anomalien und ungewöhnliches Verhalten
|
||||
- Klassifiziert potenzielle Bedrohungen
|
||||
|
||||
### Detektoren
|
||||
|
||||
Verschiedene Detektoren für unterschiedliche Arten von Anomalien:
|
||||
|
||||
- `StatisticalAnomalyDetector`: Erkennt statistische Abweichungen
|
||||
- `ClusteringAnomalyDetector`: Gruppiert ähnliche Requests und identifiziert Ausreißer
|
||||
- Erweiterbar für zusätzliche Detektoren
|
||||
|
||||
### Middleware-System
|
||||
|
||||
```php
|
||||
// In Bootstrap oder Application-Klasse
|
||||
$app->addMiddleware(WafMiddleware::class);
|
||||
```
|
||||
|
||||
## Feature-Extraktion
|
||||
|
||||
Die WAF extrahiert verschiedene Features aus eingehenden Requests:
|
||||
|
||||
1. **Request-Metadaten**: URL, HTTP-Methode, Header
|
||||
2. **Parameter-Analyse**: Anzahl, Länge, Entropie der Parameter
|
||||
3. **Inhaltsmuster**: Spezielle Zeichen, Skript-Tags, SQL-Fragmente
|
||||
4. **Verhaltensanalyse**: Anfragehäufigkeit, Sitzungsdauer, Navigationsmuster
|
||||
|
||||
## Anomalie-Erkennung
|
||||
|
||||
Der Anomalie-Erkennungsprozess umfasst:
|
||||
|
||||
1. **Feature-Extraktion** aus dem eingehenden Request
|
||||
2. **Baseline-Vergleich** mit normalen Verhaltensmustern
|
||||
3. **Anomalie-Bewertung** durch verschiedene Detektoren
|
||||
4. **Entscheidungsfindung** basierend auf Schwellenwerten und Regeln
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die WAF kann über die Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/waf.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'learning_mode' => false,
|
||||
'threshold' => 0.75,
|
||||
'detectors' => [
|
||||
'statistical' => true,
|
||||
'clustering' => true
|
||||
],
|
||||
'whitelist' => [
|
||||
'ips' => ['127.0.0.1'],
|
||||
'paths' => ['/api/health']
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Service-Container
|
||||
|
||||
Die WAF wird automatisch registriert und kann per Dependency Injection verwendet werden:
|
||||
|
||||
```php
|
||||
public function __construct(private readonly Waf $waf) {}
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Die WAF löst folgende Events aus:
|
||||
|
||||
- `waf.request_analyzed`: Nach der Analyse eines Requests
|
||||
- `waf.request_blocked`: Wenn ein Request blockiert wird
|
||||
- `waf.anomaly_detected`: Bei Erkennung einer Anomalie
|
||||
|
||||
## Feedback-System (geplant)
|
||||
|
||||
Ein Feedback-System für die WAF ist in Planung und wird folgende Funktionen bieten:
|
||||
|
||||
- Admin-Interface zur Überprüfung von WAF-Entscheidungen
|
||||
- Feedback-Mechanismus für falsch positive/negative Ergebnisse
|
||||
- Kontinuierliches Training der ML-Modelle basierend auf Feedback
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **Falsch positive Blockierungen**:
|
||||
- Überprüfen Sie die Whitelist-Konfiguration
|
||||
- Passen Sie den Schwellenwert an
|
||||
- Aktivieren Sie den Lernmodus vorübergehend
|
||||
|
||||
2. **Leistungsprobleme**:
|
||||
- Deaktivieren Sie rechenintensive Detektoren
|
||||
- Implementieren Sie Caching für wiederholte Anfragen
|
||||
- Optimieren Sie die Feature-Extraktion
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Machine Learning Dokumentation](machine-learning.md)
|
||||
- [WAF-Konfiguration](configuration.md)
|
||||
- [Sicherheitsrichtlinien](/guides/security.md)
|
||||
252
docs/components/waf/machine-learning.md
Normal file
252
docs/components/waf/machine-learning.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# WAF Machine Learning
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Machine-Learning-Komponente der WAF korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die Machine-Learning-Komponente ist das Herzstück des WAF-Systems und ermöglicht die intelligente Erkennung von Bedrohungen und Anomalien in Echtzeit. Sie verwendet fortschrittliche Algorithmen, um normales Verhalten zu lernen und Abweichungen zu identifizieren, die auf potenzielle Angriffe hindeuten könnten.
|
||||
|
||||
## MachineLearningEngine
|
||||
|
||||
Die `MachineLearningEngine`-Klasse ist die zentrale Komponente des Machine-Learning-Systems:
|
||||
|
||||
```php
|
||||
// Initialisierung
|
||||
$engine = new MachineLearningEngine(
|
||||
$enabled,
|
||||
$extractors,
|
||||
$detectors,
|
||||
$clock,
|
||||
$analysisTimeout,
|
||||
$confidenceThreshold,
|
||||
$enableParallelProcessing,
|
||||
$enableFeatureCaching,
|
||||
$maxFeaturesPerRequest
|
||||
);
|
||||
|
||||
// Analyse eines Requests
|
||||
$result = $engine->analyzeRequest($requestData, $context);
|
||||
```
|
||||
|
||||
### Hauptfunktionen
|
||||
|
||||
- **Request-Analyse**: Analysiert eingehende Requests auf Anomalien
|
||||
- **Feature-Extraktion**: Extrahiert relevante Features aus Requests
|
||||
- **Anomalie-Erkennung**: Identifiziert Abweichungen vom normalen Verhalten
|
||||
- **Baseline-Management**: Verwaltet und aktualisiert Verhaltensbaselines
|
||||
- **Modell-Aktualisierung**: Passt Modelle kontinuierlich an neue Daten an
|
||||
- **Leistungsüberwachung**: Überwacht und optimiert die Leistung des Systems
|
||||
|
||||
## Prozessablauf
|
||||
|
||||
Der Machine-Learning-Prozess umfasst folgende Schritte:
|
||||
|
||||
1. **Feature-Extraktion** (`extractFeatures`):
|
||||
- Extrahiert relevante Features aus dem Request
|
||||
- Validiert und normalisiert Features
|
||||
- Wendet Feature-Caching an, wenn aktiviert
|
||||
|
||||
2. **Baseline-Abruf** (`getBaselines`):
|
||||
- Ruft relevante Baselines für die extrahierten Features ab
|
||||
- Erstellt neue Baselines, wenn keine existieren
|
||||
- Verwaltet Baseline-Cache für optimale Leistung
|
||||
|
||||
3. **Anomalie-Erkennung** (`detectAnomalies`):
|
||||
- Wendet verschiedene Detektoren auf die Features an
|
||||
- Vergleicht Features mit Baselines
|
||||
- Berechnet Anomalie-Scores und Konfidenzwerte
|
||||
|
||||
4. **Konfidenzberechnung** (`calculateOverallConfidence`):
|
||||
- Aggregiert Ergebnisse verschiedener Detektoren
|
||||
- Berechnet Gesamtkonfidenz basierend auf gewichteten Scores
|
||||
- Vergleicht mit Schwellenwert für Entscheidungsfindung
|
||||
|
||||
5. **Modell-Aktualisierung** (`updateModels`):
|
||||
- Aktualisiert Modelle mit neuen Daten
|
||||
- Passt Baselines an sich ändernde Muster an
|
||||
- Optimiert Detektoren basierend auf Feedback
|
||||
|
||||
## Feature-Extraktion
|
||||
|
||||
Die Feature-Extraktion ist ein kritischer Schritt im Machine-Learning-Prozess:
|
||||
|
||||
```php
|
||||
$features = $engine->extractFeatures($requestData, $context);
|
||||
```
|
||||
|
||||
### Feature-Typen
|
||||
|
||||
- **Request-Features**: URL-Struktur, Parameter, Header
|
||||
- **Inhalts-Features**: Payload-Struktur, Entropie, Zeichenverteilung
|
||||
- **Verhaltens-Features**: Anfragemuster, Sitzungsverhalten, Timing
|
||||
- **Kontext-Features**: IP-Reputation, Geolocation, Benutzeragent
|
||||
|
||||
### Feature-Validierung
|
||||
|
||||
Alle extrahierten Features werden validiert, um Qualität und Konsistenz zu gewährleisten:
|
||||
|
||||
- Überprüfung auf Vollständigkeit und Gültigkeit
|
||||
- Normalisierung für konsistente Verarbeitung
|
||||
- Filterung irrelevanter oder redundanter Features
|
||||
|
||||
## Anomalie-Detektoren
|
||||
|
||||
Das System verwendet verschiedene Detektoren für unterschiedliche Arten von Anomalien:
|
||||
|
||||
### StatisticalAnomalyDetector
|
||||
|
||||
Erkennt statistische Abweichungen in numerischen Features:
|
||||
|
||||
- Berechnet Z-Scores für numerische Features
|
||||
- Identifiziert Ausreißer basierend auf statistischen Verteilungen
|
||||
- Erkennt ungewöhnliche Wertekombinationen
|
||||
|
||||
### ClusteringAnomalyDetector
|
||||
|
||||
Gruppiert ähnliche Requests und identifiziert Ausreißer:
|
||||
|
||||
- Verwendet Clustering-Algorithmen zur Gruppierung ähnlicher Requests
|
||||
- Berechnet Distanz zu Cluster-Zentren
|
||||
- Identifiziert isolierte Punkte als potenzielle Anomalien
|
||||
|
||||
### Erweiterbarkeit
|
||||
|
||||
Das System ist erweiterbar für zusätzliche Detektoren:
|
||||
|
||||
```php
|
||||
// Eigenen Detektor registrieren
|
||||
$config->registerDetector(new CustomAnomalyDetector());
|
||||
```
|
||||
|
||||
## Baselines
|
||||
|
||||
Baselines repräsentieren normales Verhalten und dienen als Referenz für die Anomalie-Erkennung:
|
||||
|
||||
### Baseline-Typen
|
||||
|
||||
- **Statistische Baselines**: Mittelwerte, Standardabweichungen, Verteilungen
|
||||
- **Verhaltensbaselines**: Typische Anfragemuster und Sequenzen
|
||||
- **Inhaltsbaselines**: Normale Payload-Strukturen und Charakteristiken
|
||||
|
||||
### Baseline-Management
|
||||
|
||||
Baselines werden kontinuierlich aktualisiert und optimiert:
|
||||
|
||||
- Automatische Erstellung für neue Feature-Kombinationen
|
||||
- Regelmäßige Aktualisierung basierend auf neuen Daten
|
||||
- Gewichtete Historisierung für Stabilität und Aktualität
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
Die Machine-Learning-Komponente enthält verschiedene Optimierungen:
|
||||
|
||||
### Feature-Caching
|
||||
|
||||
```php
|
||||
// Konfiguration
|
||||
$config->setEnableFeatureCaching(true);
|
||||
```
|
||||
|
||||
Speichert extrahierte Features für ähnliche Requests, um Rechenaufwand zu reduzieren.
|
||||
|
||||
### Parallele Verarbeitung
|
||||
|
||||
```php
|
||||
// Konfiguration
|
||||
$config->setEnableParallelProcessing(true);
|
||||
```
|
||||
|
||||
Verarbeitet Features und Detektoren parallel für verbesserte Leistung.
|
||||
|
||||
### Leistungsmetriken
|
||||
|
||||
Das System sammelt Leistungsmetriken zur Überwachung und Optimierung:
|
||||
|
||||
- Verarbeitungszeiten für verschiedene Phasen
|
||||
- Feature- und Anomaliezahlen
|
||||
- Cache-Trefferquoten und Speichernutzung
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die Machine-Learning-Komponente bietet umfangreiche Konfigurationsmöglichkeiten:
|
||||
|
||||
```php
|
||||
// config/waf_ml.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'confidence_threshold' => 0.75,
|
||||
'analysis_timeout' => 100, // ms
|
||||
'enable_parallel_processing' => true,
|
||||
'enable_feature_caching' => true,
|
||||
'max_features_per_request' => 50,
|
||||
'detectors' => [
|
||||
'statistical' => [
|
||||
'enabled' => true,
|
||||
'weight' => 0.6
|
||||
],
|
||||
'clustering' => [
|
||||
'enabled' => true,
|
||||
'weight' => 0.4
|
||||
]
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Integration mit WAF
|
||||
|
||||
Die Machine-Learning-Komponente ist nahtlos in das WAF-System integriert:
|
||||
|
||||
```php
|
||||
// In WAF-Klasse
|
||||
public function analyzeRequest(Request $request): WafResult
|
||||
{
|
||||
$requestData = $this->analyzer->analyze($request);
|
||||
$mlResult = $this->mlEngine->analyzeRequest($requestData, $this->context);
|
||||
|
||||
return new WafResult($requestData, $mlResult);
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **Hohe Falsch-Positiv-Rate**:
|
||||
- Erhöhen Sie den Konfidenz-Schwellenwert
|
||||
- Aktivieren Sie den Lernmodus für einen längeren Zeitraum
|
||||
- Überprüfen Sie die Gewichtung der Detektoren
|
||||
|
||||
2. **Leistungsprobleme**:
|
||||
- Aktivieren Sie Feature-Caching
|
||||
- Reduzieren Sie die maximale Anzahl von Features pro Request
|
||||
- Optimieren Sie die Detektorkonfiguration
|
||||
|
||||
3. **Fehlende Erkennung**:
|
||||
- Senken Sie den Konfidenz-Schwellenwert
|
||||
- Aktivieren Sie zusätzliche Detektoren
|
||||
- Überprüfen Sie die Feature-Extraktion
|
||||
|
||||
## Weiterentwicklung
|
||||
|
||||
### Geplante Erweiterungen
|
||||
|
||||
1. **Feedback-System**:
|
||||
- Integration von Administratorfeedback
|
||||
- Automatische Anpassung basierend auf Feedback
|
||||
- Kontinuierliches Lernen aus Fehlklassifikationen
|
||||
|
||||
2. **Erweiterte Detektoren**:
|
||||
- Deep-Learning-basierte Anomalieerkennung
|
||||
- Sequenzanalyse für komplexe Angriffsmuster
|
||||
- Verhaltensbasierte Benutzerprofilierung
|
||||
|
||||
3. **Verbesserte Visualisierung**:
|
||||
- Dashboard für Anomalietrends
|
||||
- Feature-Wichtigkeitsanalyse
|
||||
- Echtzeit-Monitoring von Bedrohungen
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [WAF-Übersicht](index.md)
|
||||
- [WAF-Konfiguration](configuration.md)
|
||||
- [Sicherheitsrichtlinien](/guides/security.md)
|
||||
598
docs/contributing/code-style.md
Normal file
598
docs/contributing/code-style.md
Normal file
@@ -0,0 +1,598 @@
|
||||
# Coding Standards
|
||||
|
||||
Diese Dokumentation beschreibt die Coding Standards, die bei der Entwicklung des Frameworks eingehalten werden sollten. Die Einhaltung dieser Standards gewährleistet einen konsistenten Codestil im gesamten Projekt und erleichtert die Zusammenarbeit zwischen Entwicklern.
|
||||
|
||||
## PHP-Standards
|
||||
|
||||
### PSR-Standards
|
||||
|
||||
Das Framework folgt den [PHP Standards Recommendations (PSR)](https://www.php-fig.org/psr/) des PHP Framework Interop Group (PHP-FIG):
|
||||
|
||||
- [PSR-1: Basic Coding Standard](https://www.php-fig.org/psr/psr-1/)
|
||||
- [PSR-2: Coding Style Guide](https://www.php-fig.org/psr/psr-2/) und [PSR-12: Extended Coding Style](https://www.php-fig.org/psr/psr-12/) (ersetzt PSR-2)
|
||||
- [PSR-4: Autoloading Standard](https://www.php-fig.org/psr/psr-4/)
|
||||
|
||||
### Allgemeine Richtlinien
|
||||
|
||||
#### Dateien
|
||||
|
||||
- Dateien MÜSSEN die UTF-8-Kodierung ohne BOM verwenden.
|
||||
- Dateien SOLLTEN entweder Deklarationen (Klassen, Funktionen, Konstanten) ODER Nebeneffekte (z.B. Ausgaben, Änderungen an .ini-Einstellungen) enthalten, aber NICHT beides.
|
||||
- Dateinamen MÜSSEN `PascalCase` für Klassen verwenden (z.B. `UserController.php`).
|
||||
- Alle PHP-Dateien MÜSSEN mit einer einzelnen leeren Zeile enden.
|
||||
|
||||
#### PHP-Tags
|
||||
|
||||
- PHP-Code MUSS die langen Tags `<?php ?>` oder die kurzen Echo-Tags `<?= ?>` verwenden.
|
||||
- Die schließenden Tags `?>` SOLLTEN bei Dateien, die nur PHP enthalten, weggelassen werden.
|
||||
|
||||
#### Zeichenkodierung und Zeilenumbrüche
|
||||
|
||||
- Der Code MUSS UTF-8 ohne BOM verwenden.
|
||||
- Zeilenumbrüche MÜSSEN im Unix-Format (LF) sein.
|
||||
|
||||
### Namenskonventionen
|
||||
|
||||
#### Namespaces
|
||||
|
||||
- Namespaces MÜSSEN `PascalCase` verwenden.
|
||||
- Der Basis-Namespace für das Framework ist `App\Framework`.
|
||||
- Der Basis-Namespace für anwendungsspezifischen Code ist `App\Application`.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Http;
|
||||
namespace App\Application\Controllers;
|
||||
```
|
||||
|
||||
#### Klassen
|
||||
|
||||
- Klassennamen MÜSSEN `PascalCase` verwenden.
|
||||
- Schnittstellen MÜSSEN mit dem Suffix `Interface` enden.
|
||||
- Abstrakte Klassen SOLLTEN mit dem Präfix `Abstract` beginnen.
|
||||
- Traits SOLLTEN mit dem Suffix `Trait` enden.
|
||||
|
||||
```php
|
||||
class UserController
|
||||
interface RepositoryInterface
|
||||
abstract class AbstractController
|
||||
trait LoggableTrait
|
||||
```
|
||||
|
||||
#### Methoden und Funktionen
|
||||
|
||||
- Methoden- und Funktionsnamen MÜSSEN `camelCase` verwenden.
|
||||
- Methoden SOLLTEN Verben sein, die ihre Aktion beschreiben.
|
||||
|
||||
```php
|
||||
public function getUserById(int $id): ?User
|
||||
public function createUser(array $data): User
|
||||
public function updateUserProfile(User $user, array $data): bool
|
||||
```
|
||||
|
||||
#### Eigenschaften und Variablen
|
||||
|
||||
- Eigenschaften und Variablen MÜSSEN `camelCase` verwenden.
|
||||
- Private und geschützte Eigenschaften SOLLTEN NICHT mit einem Unterstrich beginnen.
|
||||
|
||||
```php
|
||||
private string $firstName;
|
||||
protected int $itemCount;
|
||||
public bool $isActive;
|
||||
```
|
||||
|
||||
#### Konstanten
|
||||
|
||||
- Klassenkonstanten MÜSSEN in `UPPER_CASE` mit Unterstrichen als Trennzeichen definiert werden.
|
||||
|
||||
```php
|
||||
public const MAX_ATTEMPTS = 5;
|
||||
private const DEFAULT_TIMEOUT = 30;
|
||||
```
|
||||
|
||||
#### Enums
|
||||
|
||||
- Enum-Namen MÜSSEN `PascalCase` verwenden.
|
||||
- Enum-Werte MÜSSEN `UPPER_CASE` mit Unterstrichen als Trennzeichen verwenden.
|
||||
|
||||
```php
|
||||
enum HttpMethod
|
||||
{
|
||||
case GET;
|
||||
case POST;
|
||||
case PUT;
|
||||
case DELETE;
|
||||
}
|
||||
|
||||
enum LogLevel: string
|
||||
{
|
||||
case DEBUG = 'debug';
|
||||
case INFO = 'info';
|
||||
case WARNING = 'warning';
|
||||
case ERROR = 'error';
|
||||
}
|
||||
```
|
||||
|
||||
### Codestruktur
|
||||
|
||||
#### Klassen
|
||||
|
||||
- Jede Klasse MUSS in einer eigenen Datei definiert werden.
|
||||
- Die Struktur einer Klasse SOLLTE wie folgt sein:
|
||||
1. Namespace-Deklaration
|
||||
2. Verwendete Imports (alphabetisch sortiert)
|
||||
3. Klassendokumentation (PHPDoc)
|
||||
4. Klassendeklaration
|
||||
5. Konstanten (öffentlich, geschützt, privat)
|
||||
6. Eigenschaften (öffentlich, geschützt, privat)
|
||||
7. Konstruktor und Destruktor
|
||||
8. Öffentliche Methoden
|
||||
9. Geschützte Methoden
|
||||
10. Private Methoden
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Database;
|
||||
|
||||
use App\Framework\Contracts\ConnectionInterface;
|
||||
use App\Framework\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Database connection manager.
|
||||
*/
|
||||
class Connection implements ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Default connection timeout in seconds.
|
||||
*/
|
||||
public const DEFAULT_TIMEOUT = 30;
|
||||
|
||||
/**
|
||||
* The active PDO connection.
|
||||
*/
|
||||
private ?\PDO $pdo = null;
|
||||
|
||||
/**
|
||||
* The database configuration.
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* Create a new database connection instance.
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDO connection.
|
||||
*/
|
||||
public function getPdo(): \PDO
|
||||
{
|
||||
if ($this->pdo === null) {
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a database connection.
|
||||
*/
|
||||
protected function connect(): void
|
||||
{
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the DSN string.
|
||||
*/
|
||||
private function buildDsn(): string
|
||||
{
|
||||
// Implementation...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Methoden
|
||||
|
||||
- Methoden SOLLTEN eine einzige Verantwortung haben (Single Responsibility Principle).
|
||||
- Methoden SOLLTEN kurz sein und eine Sache gut machen.
|
||||
- Methoden SOLLTEN früh zurückkehren, um die Verschachtelungstiefe zu reduzieren.
|
||||
|
||||
```php
|
||||
// Gut
|
||||
public function getUserById(int $id): ?User
|
||||
{
|
||||
if ($id <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->userRepository->find($id);
|
||||
}
|
||||
|
||||
// Schlecht
|
||||
public function getUserById(int $id): ?User
|
||||
{
|
||||
if ($id > 0) {
|
||||
$user = $this->userRepository->find($id);
|
||||
if ($user !== null) {
|
||||
return $user;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Typisierung
|
||||
|
||||
- Der Code MUSS strikte Typisierung verwenden.
|
||||
- Jede Datei SOLLTE mit `declare(strict_types=1);` beginnen.
|
||||
- Methoden und Funktionen MÜSSEN Typdeklarationen für Parameter und Rückgabewerte verwenden.
|
||||
- Eigenschaften MÜSSEN Typdeklarationen verwenden.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Http;
|
||||
|
||||
class Request
|
||||
{
|
||||
private array $query;
|
||||
private array $request;
|
||||
|
||||
public function __construct(array $query, array $request)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function query(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->query[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function input(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->request[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->request[$key]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dokumentation
|
||||
|
||||
- Alle Klassen, Methoden, Eigenschaften und Konstanten MÜSSEN mit PHPDoc-Kommentaren dokumentiert werden.
|
||||
- PHPDoc-Kommentare MÜSSEN eine Beschreibung und alle relevanten Tags enthalten.
|
||||
- Komplexe Codeabschnitte SOLLTEN mit Inline-Kommentaren erklärt werden.
|
||||
|
||||
```php
|
||||
/**
|
||||
* Represents an HTTP request.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* The query parameters.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $query;
|
||||
|
||||
/**
|
||||
* Get a query parameter.
|
||||
*
|
||||
* @param string $key The parameter key
|
||||
* @param mixed $default The default value if the parameter does not exist
|
||||
*
|
||||
* @return mixed The parameter value or the default value
|
||||
*/
|
||||
public function query(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->query[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
- Exceptions SOLLTEN für außergewöhnliche Bedingungen verwendet werden.
|
||||
- Benutzerdefinierte Exceptions SOLLTEN von einer Basis-Exception-Klasse erben.
|
||||
- Exception-Nachrichten SOLLTEN klar und informativ sein.
|
||||
|
||||
```php
|
||||
// Definieren einer benutzerdefinierten Exception
|
||||
class DatabaseException extends \RuntimeException
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// Werfen einer Exception
|
||||
if (!$this->isConnected()) {
|
||||
throw new DatabaseException('Database connection failed: ' . $this->getLastError());
|
||||
}
|
||||
|
||||
// Fangen einer Exception
|
||||
try {
|
||||
$user = $this->userRepository->find($id);
|
||||
} catch (DatabaseException $e) {
|
||||
$this->logger->error('Database error: ' . $e->getMessage());
|
||||
throw new ServiceException('Could not retrieve user data', 0, $e);
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript-Standards
|
||||
|
||||
### Allgemeine Richtlinien
|
||||
|
||||
- JavaScript-Code SOLLTE [ESLint](https://eslint.org/) mit der Konfiguration des Projekts verwenden.
|
||||
- ES6+ Features SOLLTEN verwendet werden.
|
||||
- Semicolons MÜSSEN verwendet werden.
|
||||
|
||||
### Namenskonventionen
|
||||
|
||||
- Variablen und Funktionen MÜSSEN `camelCase` verwenden.
|
||||
- Klassen MÜSSEN `PascalCase` verwenden.
|
||||
- Konstanten MÜSSEN `UPPER_CASE` mit Unterstrichen als Trennzeichen verwenden.
|
||||
- Private Eigenschaften und Methoden SOLLTEN mit einem Unterstrich beginnen.
|
||||
|
||||
```javascript
|
||||
// Variablen
|
||||
const maxItems = 10;
|
||||
let currentIndex = 0;
|
||||
|
||||
// Konstanten
|
||||
const MAX_RETRY_COUNT = 3;
|
||||
const API_BASE_URL = 'https://api.example.com';
|
||||
|
||||
// Funktionen
|
||||
function getUserData(userId) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Klassen
|
||||
class UserService {
|
||||
constructor(apiClient) {
|
||||
this._apiClient = apiClient;
|
||||
}
|
||||
|
||||
async getUser(id) {
|
||||
return this._apiClient.get(`/users/${id}`);
|
||||
}
|
||||
|
||||
_handleError(error) {
|
||||
console.error('API error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Codestruktur
|
||||
|
||||
- Jede Datei SOLLTE einen einzelnen Export haben.
|
||||
- Imports SOLLTEN am Anfang der Datei stehen und nach Typ gruppiert werden.
|
||||
- Funktionen SOLLTEN kurz sein und eine Sache gut machen.
|
||||
|
||||
```javascript
|
||||
// Imports
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// Eigene Imports
|
||||
import { UserService } from '../services/UserService';
|
||||
import { ErrorBoundary } from '../components/ErrorBoundary';
|
||||
|
||||
// Komponente
|
||||
function UserProfile({ userId }) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const userService = new UserService();
|
||||
const userData = await userService.getUser(userId);
|
||||
setUser(userData);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUser();
|
||||
}, [userId]);
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{user.name}</h1>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
UserProfile.propTypes = {
|
||||
userId: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default UserProfile;
|
||||
```
|
||||
|
||||
## CSS-Standards
|
||||
|
||||
### Allgemeine Richtlinien
|
||||
|
||||
- CSS-Code SOLLTE [Stylelint](https://stylelint.io/) mit der Konfiguration des Projekts verwenden.
|
||||
- CSS-Präprozessoren wie SASS oder LESS KÖNNEN verwendet werden.
|
||||
- CSS-Klassen SOLLTEN nach dem BEM-Muster (Block, Element, Modifier) benannt werden.
|
||||
|
||||
### Namenskonventionen
|
||||
|
||||
- CSS-Klassen MÜSSEN `kebab-case` verwenden.
|
||||
- BEM-Notation: `.block__element--modifier`
|
||||
|
||||
```css
|
||||
/* Block */
|
||||
.user-card {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* Element */
|
||||
.user-card__avatar {
|
||||
border-radius: 50%;
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
/* Element */
|
||||
.user-card__name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Modifier */
|
||||
.user-card--premium {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
```
|
||||
|
||||
### Codestruktur
|
||||
|
||||
- CSS-Regeln SOLLTEN nach Komponenten organisiert werden.
|
||||
- Medienabfragen SOLLTEN am Ende jeder Komponente stehen.
|
||||
- Vendor-Präfixe SOLLTEN automatisch mit Tools wie Autoprefixer hinzugefügt werden.
|
||||
|
||||
```css
|
||||
/* Komponente */
|
||||
.button {
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 8px 16px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.button--secondary {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
.button--secondary:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Medienabfragen */
|
||||
@media (max-width: 768px) {
|
||||
.button {
|
||||
font-size: 14px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tools und Automatisierung
|
||||
|
||||
### Code-Linting
|
||||
|
||||
Das Projekt verwendet verschiedene Linting-Tools, um die Einhaltung der Coding Standards zu gewährleisten:
|
||||
|
||||
- [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) für PHP
|
||||
- [ESLint](https://eslint.org/) für JavaScript
|
||||
- [Stylelint](https://stylelint.io/) für CSS
|
||||
|
||||
### Automatische Formatierung
|
||||
|
||||
Das Projekt unterstützt automatische Codeformatierung mit:
|
||||
|
||||
- [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) für PHP
|
||||
- [Prettier](https://prettier.io/) für JavaScript und CSS
|
||||
|
||||
### Verwendung der Tools
|
||||
|
||||
#### PHP_CodeSniffer
|
||||
|
||||
```bash
|
||||
# Überprüfen des Codes
|
||||
vendor/bin/phpcs src tests
|
||||
|
||||
# Automatisches Beheben von Problemen
|
||||
vendor/bin/phpcbf src tests
|
||||
```
|
||||
|
||||
#### PHP-CS-Fixer
|
||||
|
||||
```bash
|
||||
# Überprüfen des Codes
|
||||
vendor/bin/php-cs-fixer fix --dry-run --diff src
|
||||
|
||||
# Automatisches Beheben von Problemen
|
||||
vendor/bin/php-cs-fixer fix src
|
||||
```
|
||||
|
||||
#### ESLint
|
||||
|
||||
```bash
|
||||
# Überprüfen des Codes
|
||||
npm run lint:js
|
||||
|
||||
# Automatisches Beheben von Problemen
|
||||
npm run lint:js:fix
|
||||
```
|
||||
|
||||
#### Stylelint
|
||||
|
||||
```bash
|
||||
# Überprüfen des Codes
|
||||
npm run lint:css
|
||||
|
||||
# Automatisches Beheben von Problemen
|
||||
npm run lint:css:fix
|
||||
```
|
||||
|
||||
#### Prettier
|
||||
|
||||
```bash
|
||||
# Formatieren von JavaScript und CSS
|
||||
npm run format
|
||||
```
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Pull Request-Anleitung](pull-requests.md): Erfahren Sie, wie Sie Pull Requests erstellen und überprüfen.
|
||||
- [Dokumentations-Anleitung](documentation.md): Erfahren Sie, wie Sie zur Dokumentation beitragen können.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
345
docs/contributing/documentation.md
Normal file
345
docs/contributing/documentation.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# Dokumentations-Anleitung
|
||||
|
||||
Diese Anleitung beschreibt, wie Sie zur Dokumentation des Frameworks beitragen können. Eine gute Dokumentation ist entscheidend für die Benutzerfreundlichkeit und Wartbarkeit des Frameworks.
|
||||
|
||||
## Überblick über die Dokumentationsstruktur
|
||||
|
||||
Die Dokumentation des Frameworks ist in mehrere Bereiche unterteilt:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── README.md # Übersicht und Navigationshilfe
|
||||
├── getting-started/ # Einstiegsdokumentation
|
||||
│ ├── installation.md # Installationsanleitung
|
||||
│ ├── configuration.md # Konfigurationsanleitung
|
||||
│ └── first-steps.md # Erste Schritte mit dem Framework
|
||||
├── architecture/ # Architektur-Dokumentation
|
||||
│ ├── overview.md # Architekturübersicht
|
||||
│ ├── components.md # Hauptkomponenten
|
||||
│ └── patterns.md # Verwendete Entwurfsmuster
|
||||
├── components/ # Komponentendokumentation
|
||||
│ ├── analytics/ # Analytics-Komponente
|
||||
│ │ ├── index.md # Übersicht
|
||||
│ │ ├── configuration.md # Konfiguration
|
||||
│ │ └── examples.md # Beispiele
|
||||
│ ├── validation/ # Validierungs-Komponente
|
||||
│ │ ├── index.md # Übersicht
|
||||
│ │ ├── rules.md # Validierungsregeln
|
||||
│ │ └── examples.md # Beispiele
|
||||
│ ├── security/ # Sicherheits-Komponente
|
||||
│ │ ├── index.md # Übersicht
|
||||
│ │ ├── csrf-protection.md # CSRF-Schutz im Detail
|
||||
│ │ ├── security-headers.md # Security Headers und CSP
|
||||
│ │ └── request-signing.md # Request Signing API
|
||||
│ ├── waf/ # WAF-Komponente
|
||||
│ │ ├── index.md # Übersicht
|
||||
│ │ ├── machine-learning.md # ML-Funktionalität
|
||||
│ │ └── configuration.md # Konfiguration
|
||||
│ └── ... # Weitere Komponenten
|
||||
├── guides/ # Entwickleranleitungen
|
||||
│ ├── routing.md # Routing-Anleitung
|
||||
│ ├── controllers.md # Controller-Anleitung
|
||||
│ ├── validation.md # Validierungs-Anleitung
|
||||
│ ├── security.md # Sicherheits-Anleitung
|
||||
│ └── ... # Weitere Anleitungen
|
||||
├── api/ # API-Dokumentation
|
||||
│ ├── index.md # API-Übersicht
|
||||
│ └── ... # Generierte API-Docs
|
||||
├── contributing/ # Beitragsrichtlinien
|
||||
│ ├── code-style.md # Coding-Standards
|
||||
│ ├── pull-requests.md # PR-Prozess
|
||||
│ └── documentation.md # Dokumentationsrichtlinien
|
||||
└── roadmap/ # Projektplanung
|
||||
├── features.md # Feature-Tracking
|
||||
├── tasks.md # Task-Tracking
|
||||
└── milestones.md # Meilensteine
|
||||
```
|
||||
|
||||
## Dokumentationsstandards
|
||||
|
||||
### Markdown-Format
|
||||
|
||||
Die gesamte Dokumentation wird in Markdown geschrieben. Hier sind die grundlegenden Formatierungsregeln:
|
||||
|
||||
#### Überschriften
|
||||
|
||||
Verwenden Sie `#` für Überschriften, mit einer Hierarchie von `#` (Hauptüberschrift) bis `######` (Überschrift der sechsten Ebene):
|
||||
|
||||
```markdown
|
||||
# Hauptüberschrift (H1)
|
||||
## Überschrift der zweiten Ebene (H2)
|
||||
### Überschrift der dritten Ebene (H3)
|
||||
#### Überschrift der vierten Ebene (H4)
|
||||
##### Überschrift der fünften Ebene (H5)
|
||||
###### Überschrift der sechsten Ebene (H6)
|
||||
```
|
||||
|
||||
#### Textformatierung
|
||||
|
||||
```markdown
|
||||
*Kursiver Text* oder _Kursiver Text_
|
||||
**Fetter Text** oder __Fetter Text__
|
||||
***Fett und kursiv*** oder ___Fett und kursiv___
|
||||
~~Durchgestrichen~~
|
||||
```
|
||||
|
||||
#### Listen
|
||||
|
||||
Ungeordnete Listen:
|
||||
|
||||
```markdown
|
||||
- Element 1
|
||||
- Element 2
|
||||
- Unterelement 2.1
|
||||
- Unterelement 2.2
|
||||
- Element 3
|
||||
```
|
||||
|
||||
Geordnete Listen:
|
||||
|
||||
```markdown
|
||||
1. Erster Schritt
|
||||
2. Zweiter Schritt
|
||||
1. Unterschritt 2.1
|
||||
2. Unterschritt 2.2
|
||||
3. Dritter Schritt
|
||||
```
|
||||
|
||||
#### Links
|
||||
|
||||
```markdown
|
||||
[Link-Text](URL)
|
||||
[Link zu einer anderen Dokumentationsseite](../pfad/zur/datei.md)
|
||||
[Link zu einem Abschnitt in derselben Datei](#abschnitt-id)
|
||||
```
|
||||
|
||||
#### Bilder
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
#### Codeblöcke
|
||||
|
||||
Für Inline-Code:
|
||||
|
||||
```markdown
|
||||
`Inline-Code`
|
||||
```
|
||||
|
||||
Für Codeblöcke:
|
||||
|
||||
````markdown
|
||||
```php
|
||||
// PHP-Code hier
|
||||
$variable = 'Wert';
|
||||
echo $variable;
|
||||
```
|
||||
````
|
||||
|
||||
Unterstützte Sprachen für Syntax-Highlighting:
|
||||
- `php` für PHP-Code
|
||||
- `js` oder `javascript` für JavaScript-Code
|
||||
- `html` für HTML-Code
|
||||
- `css` für CSS-Code
|
||||
- `bash` oder `shell` für Shell-Befehle
|
||||
- `json` für JSON-Daten
|
||||
- `xml` für XML-Daten
|
||||
- `sql` für SQL-Abfragen
|
||||
|
||||
#### Tabellen
|
||||
|
||||
```markdown
|
||||
| Spalte 1 | Spalte 2 | Spalte 3 |
|
||||
|----------|----------|----------|
|
||||
| Zelle 1 | Zelle 2 | Zelle 3 |
|
||||
| Zelle 4 | Zelle 5 | Zelle 6 |
|
||||
```
|
||||
|
||||
#### Blockzitate
|
||||
|
||||
```markdown
|
||||
> Dies ist ein Blockzitat.
|
||||
> Es kann mehrere Zeilen umfassen.
|
||||
```
|
||||
|
||||
#### Horizontale Linien
|
||||
|
||||
```markdown
|
||||
---
|
||||
```
|
||||
|
||||
### Dokumentationsstruktur
|
||||
|
||||
Jede Dokumentationsdatei sollte die folgende Struktur haben:
|
||||
|
||||
1. **Hauptüberschrift**: Der Titel des Dokuments als H1-Überschrift (`#`).
|
||||
2. **Einführung**: Eine kurze Einführung, die den Zweck und Umfang des Dokuments beschreibt.
|
||||
3. **Hauptinhalt**: Der Hauptinhalt des Dokuments, organisiert in Abschnitte mit H2-Überschriften (`##`).
|
||||
4. **Beispiele**: Praktische Beispiele, die die Verwendung der beschriebenen Funktionalität demonstrieren.
|
||||
5. **Weitere Informationen**: Links zu verwandten Dokumenten oder externen Ressourcen.
|
||||
|
||||
### Schreibstil
|
||||
|
||||
- Verwenden Sie eine klare, präzise und konsistente Sprache.
|
||||
- Schreiben Sie in der zweiten Person ("Sie können..." statt "Man kann...").
|
||||
- Verwenden Sie die aktive Stimme statt der passiven Stimme.
|
||||
- Halten Sie Sätze und Absätze kurz und fokussiert.
|
||||
- Verwenden Sie Aufzählungslisten für Schritte oder Optionen.
|
||||
- Erklären Sie Fachbegriffe, wenn sie zum ersten Mal verwendet werden.
|
||||
- Vermeiden Sie Jargon und Abkürzungen, es sei denn, sie sind allgemein bekannt oder wurden erklärt.
|
||||
|
||||
### Beispiele
|
||||
|
||||
Gute Beispiele sind ein wesentlicher Bestandteil der Dokumentation. Jedes Beispiel sollte:
|
||||
|
||||
- Realistisch und praxisnah sein
|
||||
- Vollständig und funktionsfähig sein
|
||||
- Gut kommentiert sein, um zu erklären, was passiert
|
||||
- Die empfohlenen Praktiken und Coding-Standards befolgen
|
||||
|
||||
## Beitragen zur Dokumentation
|
||||
|
||||
### Neue Dokumentation erstellen
|
||||
|
||||
Wenn Sie neue Dokumentation erstellen möchten:
|
||||
|
||||
1. Identifizieren Sie den Bereich, in dem die Dokumentation benötigt wird.
|
||||
2. Erstellen Sie eine neue Markdown-Datei im entsprechenden Verzeichnis.
|
||||
3. Folgen Sie der oben beschriebenen Dokumentationsstruktur.
|
||||
4. Fügen Sie die neue Datei in die Navigation ein, indem Sie Links in verwandten Dokumenten hinzufügen.
|
||||
|
||||
### Bestehende Dokumentation aktualisieren
|
||||
|
||||
Wenn Sie bestehende Dokumentation aktualisieren möchten:
|
||||
|
||||
1. Identifizieren Sie die zu aktualisierende Datei.
|
||||
2. Nehmen Sie die erforderlichen Änderungen vor, wobei Sie die Dokumentationsstandards einhalten.
|
||||
3. Aktualisieren Sie alle Links oder Verweise, die durch Ihre Änderungen betroffen sein könnten.
|
||||
|
||||
### Pull Request-Prozess für Dokumentation
|
||||
|
||||
Der Prozess für das Einreichen von Dokumentationsänderungen ist derselbe wie für Code-Änderungen:
|
||||
|
||||
1. Erstellen Sie einen Feature-Branch für Ihre Änderungen.
|
||||
2. Nehmen Sie die Änderungen vor.
|
||||
3. Reichen Sie einen Pull Request ein.
|
||||
4. Reagieren Sie auf Feedback und nehmen Sie bei Bedarf weitere Änderungen vor.
|
||||
|
||||
Weitere Informationen finden Sie in der [Pull Request-Anleitung](pull-requests.md).
|
||||
|
||||
## Dokumentation generieren
|
||||
|
||||
### API-Dokumentation
|
||||
|
||||
Die API-Dokumentation wird automatisch aus dem Quellcode generiert. Um die API-Dokumentation zu generieren:
|
||||
|
||||
```bash
|
||||
php console.php docs:generate-api
|
||||
```
|
||||
|
||||
Dies generiert die API-Dokumentation im Verzeichnis `docs/api/`.
|
||||
|
||||
### Dokumentation lokal anzeigen
|
||||
|
||||
Um die Dokumentation lokal anzuzeigen, können Sie einen Markdown-Viewer oder einen lokalen Webserver verwenden:
|
||||
|
||||
```bash
|
||||
# Mit PHP-Webserver
|
||||
php -S localhost:8000 -t docs
|
||||
|
||||
# Mit Node.js und docsify
|
||||
npm install -g docsify-cli
|
||||
docsify serve docs
|
||||
```
|
||||
|
||||
## Dokumentations-Checkliste
|
||||
|
||||
Verwenden Sie diese Checkliste, um sicherzustellen, dass Ihre Dokumentation vollständig und von hoher Qualität ist:
|
||||
|
||||
- [ ] Die Dokumentation folgt der standardisierten Struktur.
|
||||
- [ ] Die Hauptüberschrift beschreibt klar den Inhalt des Dokuments.
|
||||
- [ ] Die Einführung erklärt den Zweck und Umfang des Dokuments.
|
||||
- [ ] Alle Fachbegriffe werden erklärt oder verlinkt.
|
||||
- [ ] Die Dokumentation enthält praktische Beispiele.
|
||||
- [ ] Die Beispiele sind vollständig, funktionsfähig und gut kommentiert.
|
||||
- [ ] Die Dokumentation enthält Links zu verwandten Dokumenten.
|
||||
- [ ] Die Dokumentation ist frei von Rechtschreib- und Grammatikfehlern.
|
||||
- [ ] Die Formatierung ist konsistent und folgt den Markdown-Standards.
|
||||
- [ ] Die Dokumentation ist aktuell und spiegelt den aktuellen Stand des Codes wider.
|
||||
|
||||
## Tipps für gute Dokumentation
|
||||
|
||||
### Benutzerorientierung
|
||||
|
||||
Denken Sie immer an die Benutzer Ihrer Dokumentation:
|
||||
|
||||
- Wer sind sie? (Anfänger, erfahrene Entwickler, Mitwirkende)
|
||||
- Was wollen sie erreichen?
|
||||
- Welche Vorkenntnisse haben sie?
|
||||
|
||||
Passen Sie Ihre Dokumentation entsprechend an.
|
||||
|
||||
### Klare Struktur
|
||||
|
||||
Eine klare Struktur hilft den Benutzern, die benötigten Informationen schnell zu finden:
|
||||
|
||||
- Verwenden Sie aussagekräftige Überschriften.
|
||||
- Gruppieren Sie verwandte Informationen.
|
||||
- Verwenden Sie Aufzählungslisten und Tabellen, um Informationen zu organisieren.
|
||||
- Fügen Sie ein Inhaltsverzeichnis für längere Dokumente hinzu.
|
||||
|
||||
### Visuelle Elemente
|
||||
|
||||
Visuelle Elemente können die Dokumentation verbessern:
|
||||
|
||||
- Verwenden Sie Diagramme, um komplexe Beziehungen zu erklären.
|
||||
- Fügen Sie Screenshots hinzu, um Benutzeroberflächen zu zeigen.
|
||||
- Verwenden Sie Codebeispiele, um Konzepte zu demonstrieren.
|
||||
|
||||
### Kontinuierliche Verbesserung
|
||||
|
||||
Dokumentation ist nie "fertig":
|
||||
|
||||
- Überprüfen Sie regelmäßig die Dokumentation auf Aktualität.
|
||||
- Berücksichtigen Sie Feedback von Benutzern.
|
||||
- Aktualisieren Sie die Dokumentation, wenn sich der Code ändert.
|
||||
|
||||
## Häufige Probleme und Lösungen
|
||||
|
||||
### Veraltete Dokumentation
|
||||
|
||||
Problem: Die Dokumentation spiegelt nicht den aktuellen Stand des Codes wider.
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Dokumentation regelmäßig.
|
||||
- Aktualisieren Sie die Dokumentation als Teil des Entwicklungsprozesses.
|
||||
- Verwenden Sie automatisierte Tools, um Inkonsistenzen zu erkennen.
|
||||
|
||||
### Unvollständige Dokumentation
|
||||
|
||||
Problem: Die Dokumentation deckt nicht alle Aspekte der Funktionalität ab.
|
||||
|
||||
Lösung:
|
||||
- Identifizieren Sie fehlende Bereiche durch Benutzerfeedback.
|
||||
- Erstellen Sie eine Checkliste der zu dokumentierenden Funktionen.
|
||||
- Priorisieren Sie die Dokumentation basierend auf der Benutzerrelevanz.
|
||||
|
||||
### Unklare Dokumentation
|
||||
|
||||
Problem: Die Dokumentation ist schwer zu verstehen oder zu technisch.
|
||||
|
||||
Lösung:
|
||||
- Verwenden Sie einfache, klare Sprache.
|
||||
- Erklären Sie Fachbegriffe.
|
||||
- Fügen Sie Beispiele hinzu, um Konzepte zu verdeutlichen.
|
||||
- Lassen Sie die Dokumentation von jemandem überprüfen, der mit dem Thema nicht vertraut ist.
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Markdown-Anleitung](https://www.markdownguide.org/): Eine umfassende Anleitung zu Markdown.
|
||||
- [Technisches Schreiben](https://developers.google.com/tech-writing): Google's Anleitung zum technischen Schreiben.
|
||||
- [Dokumentationstools](https://docusaurus.io/): Tools für die Erstellung und Verwaltung von Dokumentation.
|
||||
- [Coding Standards](code-style.md): Erfahren Sie mehr über die Coding Standards des Projekts.
|
||||
- [Pull Request-Anleitung](pull-requests.md): Erfahren Sie, wie Sie Pull Requests erstellen und überprüfen.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
334
docs/contributing/pull-requests.md
Normal file
334
docs/contributing/pull-requests.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# Pull Request-Anleitung
|
||||
|
||||
Diese Anleitung beschreibt den Prozess für das Erstellen, Überprüfen und Zusammenführen von Pull Requests im Framework-Projekt. Die Einhaltung dieser Richtlinien gewährleistet einen reibungslosen Entwicklungsprozess und eine hohe Codequalität.
|
||||
|
||||
## Überblick über den Pull Request-Prozess
|
||||
|
||||
Der Pull Request-Prozess besteht aus den folgenden Schritten:
|
||||
|
||||
1. **Vorbereitung**: Erstellen eines Issues und eines Feature-Branches
|
||||
2. **Entwicklung**: Implementieren der Änderungen und Schreiben von Tests
|
||||
3. **Einreichen**: Erstellen eines Pull Requests
|
||||
4. **Überprüfung**: Code-Review und Diskussion
|
||||
5. **Anpassung**: Vornehmen von Änderungen basierend auf dem Feedback
|
||||
6. **Zusammenführen**: Zusammenführen des Pull Requests in den Hauptbranch
|
||||
|
||||
## 1. Vorbereitung
|
||||
|
||||
### Issues erstellen
|
||||
|
||||
Bevor Sie mit der Arbeit an einer neuen Funktion oder Fehlerbehebung beginnen, sollten Sie sicherstellen, dass ein entsprechendes Issue existiert:
|
||||
|
||||
1. Überprüfen Sie die bestehenden Issues, um Duplikate zu vermeiden.
|
||||
2. Wenn kein passendes Issue existiert, erstellen Sie ein neues Issue mit einer klaren Beschreibung des Problems oder der Funktion.
|
||||
3. Warten Sie auf Feedback vom Kernteam, um sicherzustellen, dass Ihre geplante Änderung mit der Projektrichtung übereinstimmt.
|
||||
|
||||
### Feature-Branch erstellen
|
||||
|
||||
Sobald Sie ein Issue haben, an dem Sie arbeiten möchten, erstellen Sie einen Feature-Branch:
|
||||
|
||||
```bash
|
||||
# Aktualisieren Sie Ihren lokalen main-Branch
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Erstellen Sie einen Feature-Branch
|
||||
git checkout -b feature/issue-123-kurze-beschreibung
|
||||
```
|
||||
|
||||
Verwenden Sie eine konsistente Benennungskonvention für Branches:
|
||||
- `feature/issue-XXX-kurze-beschreibung` für neue Funktionen
|
||||
- `bugfix/issue-XXX-kurze-beschreibung` für Fehlerbehebungen
|
||||
- `refactor/issue-XXX-kurze-beschreibung` für Refactoring
|
||||
- `docs/issue-XXX-kurze-beschreibung` für Dokumentationsänderungen
|
||||
|
||||
## 2. Entwicklung
|
||||
|
||||
### Coding Standards
|
||||
|
||||
Stellen Sie sicher, dass Ihr Code den [Coding Standards](code-style.md) des Projekts entspricht. Verwenden Sie die bereitgestellten Linting-Tools, um die Einhaltung der Standards zu überprüfen:
|
||||
|
||||
```bash
|
||||
# PHP-Code überprüfen
|
||||
vendor/bin/phpcs src tests
|
||||
|
||||
# JavaScript-Code überprüfen
|
||||
npm run lint:js
|
||||
|
||||
# CSS-Code überprüfen
|
||||
npm run lint:css
|
||||
```
|
||||
|
||||
### Tests schreiben
|
||||
|
||||
Für jede Änderung sollten entsprechende Tests geschrieben werden:
|
||||
|
||||
- **Unit-Tests** für einzelne Klassen und Methoden
|
||||
- **Integrationstests** für die Interaktion zwischen Komponenten
|
||||
- **Funktionstests** für die Überprüfung von Benutzerszenarien
|
||||
|
||||
```bash
|
||||
# Tests ausführen
|
||||
vendor/bin/phpunit
|
||||
|
||||
# Spezifische Testdatei ausführen
|
||||
vendor/bin/phpunit tests/Unit/Framework/Http/RequestTest.php
|
||||
|
||||
# Tests mit Code-Coverage-Bericht ausführen
|
||||
vendor/bin/phpunit --coverage-html coverage
|
||||
```
|
||||
|
||||
### Commit-Richtlinien
|
||||
|
||||
Schreiben Sie klare und aussagekräftige Commit-Nachrichten, die den Zweck des Commits beschreiben:
|
||||
|
||||
```
|
||||
feat(component): Kurze Beschreibung der Änderung
|
||||
|
||||
Längere Beschreibung mit Details zur Änderung, Motivation und Kontext.
|
||||
Mehrere Zeilen sind erlaubt.
|
||||
|
||||
Fixes #123
|
||||
```
|
||||
|
||||
Verwenden Sie die folgenden Präfixe für Ihre Commit-Nachrichten:
|
||||
- `feat`: Neue Funktion
|
||||
- `fix`: Fehlerbehebung
|
||||
- `docs`: Dokumentationsänderungen
|
||||
- `style`: Formatierung, fehlende Semikolons usw. (keine Codeänderungen)
|
||||
- `refactor`: Code-Refactoring
|
||||
- `test`: Hinzufügen oder Korrigieren von Tests
|
||||
- `chore`: Änderungen an Build-Prozessen oder Hilfswerkzeugen
|
||||
|
||||
### Regelmäßiges Pushen
|
||||
|
||||
Pushen Sie Ihre Änderungen regelmäßig in Ihren Remote-Branch:
|
||||
|
||||
```bash
|
||||
git push origin feature/issue-123-kurze-beschreibung
|
||||
```
|
||||
|
||||
## 3. Einreichen
|
||||
|
||||
### Vorbereitung für den Pull Request
|
||||
|
||||
Bevor Sie einen Pull Request erstellen, stellen Sie sicher, dass:
|
||||
|
||||
1. Alle Tests erfolgreich durchlaufen werden.
|
||||
2. Der Code den Coding Standards entspricht.
|
||||
3. Die Dokumentation aktualisiert wurde (falls erforderlich).
|
||||
4. Der Branch auf dem neuesten Stand mit dem Hauptbranch ist.
|
||||
|
||||
```bash
|
||||
# Aktualisieren Sie Ihren Branch mit dem neuesten Stand des Hauptbranches
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout feature/issue-123-kurze-beschreibung
|
||||
git merge main
|
||||
git push origin feature/issue-123-kurze-beschreibung
|
||||
```
|
||||
|
||||
### Pull Request erstellen
|
||||
|
||||
Erstellen Sie einen Pull Request über die GitHub-Oberfläche:
|
||||
|
||||
1. Navigieren Sie zu Ihrem Branch auf GitHub.
|
||||
2. Klicken Sie auf "Compare & pull request".
|
||||
3. Füllen Sie die Pull Request-Vorlage aus:
|
||||
- Titel: Kurze Beschreibung der Änderung (max. 72 Zeichen)
|
||||
- Beschreibung: Detaillierte Beschreibung der Änderung, Motivation und Kontext
|
||||
- Referenzieren Sie das zugehörige Issue mit "Fixes #123" oder "Relates to #123"
|
||||
4. Klicken Sie auf "Create pull request".
|
||||
|
||||
### Pull Request-Vorlage
|
||||
|
||||
```markdown
|
||||
## Beschreibung
|
||||
|
||||
[Beschreiben Sie hier Ihre Änderungen und den Kontext]
|
||||
|
||||
## Motivation und Kontext
|
||||
|
||||
[Warum ist diese Änderung erforderlich? Welches Problem löst sie?]
|
||||
|
||||
## Art der Änderung
|
||||
|
||||
- [ ] Fehlerbehebung (nicht-breaking change, behebt ein Problem)
|
||||
- [ ] Neue Funktion (nicht-breaking change, fügt Funktionalität hinzu)
|
||||
- [ ] Breaking change (Fehlerbehebung oder Funktion, die zu Inkompatibilitäten führt)
|
||||
- [ ] Diese Änderung erfordert eine Dokumentationsaktualisierung
|
||||
|
||||
## Wie wurde getestet?
|
||||
|
||||
[Beschreiben Sie die Tests, die Sie durchgeführt haben, um Ihre Änderungen zu überprüfen]
|
||||
|
||||
## Checkliste:
|
||||
|
||||
- [ ] Mein Code folgt dem Codestil dieses Projekts
|
||||
- [ ] Ich habe Selbstüberprüfung meines Codes durchgeführt
|
||||
- [ ] Ich habe Kommentare zu meinem Code hinzugefügt, insbesondere in schwer verständlichen Bereichen
|
||||
- [ ] Ich habe entsprechende Änderungen an der Dokumentation vorgenommen
|
||||
- [ ] Meine Änderungen erzeugen keine neuen Warnungen
|
||||
- [ ] Ich habe Tests hinzugefügt, die meine Änderungen beweisen
|
||||
- [ ] Neue und bestehende Unit-Tests bestehen lokal mit meinen Änderungen
|
||||
- [ ] Alle abhängigen Änderungen wurden zusammengeführt und veröffentlicht
|
||||
|
||||
## Zugehörige Issues
|
||||
|
||||
[Verlinken Sie hier zugehörige Issues, z.B. "Fixes #123"]
|
||||
```
|
||||
|
||||
## 4. Überprüfung
|
||||
|
||||
### Code-Review-Prozess
|
||||
|
||||
Nach dem Erstellen eines Pull Requests wird er von mindestens einem Mitglied des Kernteams überprüft. Der Reviewer wird:
|
||||
|
||||
1. Den Code auf Einhaltung der Coding Standards überprüfen.
|
||||
2. Die Funktionalität und Korrektheit der Änderungen bewerten.
|
||||
3. Die Testabdeckung überprüfen.
|
||||
4. Feedback und Verbesserungsvorschläge geben.
|
||||
|
||||
### Automatisierte Checks
|
||||
|
||||
Jeder Pull Request durchläuft automatisierte Checks:
|
||||
|
||||
- **CI-Pipeline**: Führt Tests und Linting-Checks aus
|
||||
- **Code-Coverage**: Überprüft die Testabdeckung
|
||||
- **Dependency-Scanning**: Überprüft auf Sicherheitsprobleme in Abhängigkeiten
|
||||
|
||||
Alle automatisierten Checks müssen erfolgreich sein, bevor ein Pull Request zusammengeführt werden kann.
|
||||
|
||||
### Feedback erhalten und geben
|
||||
|
||||
Beim Geben und Erhalten von Feedback:
|
||||
|
||||
- Seien Sie respektvoll und konstruktiv.
|
||||
- Konzentrieren Sie sich auf den Code, nicht auf die Person.
|
||||
- Erklären Sie Ihre Gedanken und geben Sie Beispiele.
|
||||
- Stellen Sie Fragen, anstatt Annahmen zu treffen.
|
||||
|
||||
## 5. Anpassung
|
||||
|
||||
### Änderungen vornehmen
|
||||
|
||||
Basierend auf dem Feedback aus dem Code-Review können Sie Änderungen an Ihrem Branch vornehmen:
|
||||
|
||||
```bash
|
||||
# Änderungen vornehmen
|
||||
git add .
|
||||
git commit -m "fix: Adressieren von Feedback aus dem Code-Review"
|
||||
git push origin feature/issue-123-kurze-beschreibung
|
||||
```
|
||||
|
||||
Die neuen Commits werden automatisch zum Pull Request hinzugefügt.
|
||||
|
||||
### Konflikte lösen
|
||||
|
||||
Wenn Konflikte mit dem Hauptbranch auftreten, müssen Sie diese lösen:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout feature/issue-123-kurze-beschreibung
|
||||
git merge main
|
||||
# Konflikte lösen
|
||||
git add .
|
||||
git commit -m "chore: Merge main und löse Konflikte"
|
||||
git push origin feature/issue-123-kurze-beschreibung
|
||||
```
|
||||
|
||||
## 6. Zusammenführen
|
||||
|
||||
### Anforderungen für das Zusammenführen
|
||||
|
||||
Bevor ein Pull Request zusammengeführt werden kann, muss er:
|
||||
|
||||
1. Von mindestens einem Mitglied des Kernteams genehmigt werden.
|
||||
2. Alle automatisierten Checks bestehen.
|
||||
3. Keine offenen Diskussionen oder Anfragen nach Änderungen haben.
|
||||
|
||||
### Zusammenführungsmethoden
|
||||
|
||||
Das Projekt verwendet verschiedene Zusammenführungsmethoden je nach Art der Änderung:
|
||||
|
||||
- **Squash and merge**: Für kleine Änderungen oder Fehlerbehebungen, um die Commit-Historie sauber zu halten.
|
||||
- **Merge commit**: Für größere Funktionen, bei denen die einzelnen Commits wichtig sind.
|
||||
- **Rebase and merge**: Für Änderungen, die eine lineare Historie erfordern.
|
||||
|
||||
Die bevorzugte Methode ist "Squash and merge", es sei denn, es gibt einen guten Grund für eine andere Methode.
|
||||
|
||||
### Nach dem Zusammenführen
|
||||
|
||||
Nach dem Zusammenführen eines Pull Requests:
|
||||
|
||||
1. Der Feature-Branch kann gelöscht werden.
|
||||
2. Das zugehörige Issue wird automatisch geschlossen (wenn "Fixes #123" verwendet wurde).
|
||||
3. Die Änderungen werden in der nächsten Version des Frameworks enthalten sein.
|
||||
|
||||
## Tipps für erfolgreiche Pull Requests
|
||||
|
||||
### Kleine, fokussierte Änderungen
|
||||
|
||||
Halten Sie Pull Requests klein und fokussiert auf eine einzelne Änderung. Dies erleichtert die Überprüfung und reduziert das Risiko von Konflikten.
|
||||
|
||||
### Klare Dokumentation
|
||||
|
||||
Dokumentieren Sie Ihre Änderungen gründlich, sowohl im Code als auch in der Pull Request-Beschreibung. Erklären Sie, warum die Änderung notwendig ist und wie sie implementiert wurde.
|
||||
|
||||
### Kommunikation
|
||||
|
||||
Kommunizieren Sie aktiv mit Reviewern und anderen Mitwirkenden. Beantworten Sie Fragen zeitnah und bitten Sie um Klärung, wenn etwas unklar ist.
|
||||
|
||||
### Geduld
|
||||
|
||||
Der Review-Prozess kann Zeit in Anspruch nehmen, besonders bei komplexen Änderungen. Haben Sie Geduld und nutzen Sie die Zeit, um Ihre Änderungen zu verbessern.
|
||||
|
||||
## Häufige Probleme und Lösungen
|
||||
|
||||
### Mein Branch ist veraltet
|
||||
|
||||
Wenn Ihr Branch veraltet ist und Konflikte mit dem Hauptbranch hat:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout feature/issue-123-kurze-beschreibung
|
||||
git merge main
|
||||
# Konflikte lösen
|
||||
git add .
|
||||
git commit -m "chore: Merge main und löse Konflikte"
|
||||
git push origin feature/issue-123-kurze-beschreibung
|
||||
```
|
||||
|
||||
### Ich habe einen Fehler in meinem letzten Commit gemacht
|
||||
|
||||
Wenn Sie einen Fehler in Ihrem letzten Commit gemacht haben und ihn noch nicht gepusht haben:
|
||||
|
||||
```bash
|
||||
# Änderungen vornehmen
|
||||
git add .
|
||||
git commit --amend
|
||||
git push origin feature/issue-123-kurze-beschreibung --force
|
||||
```
|
||||
|
||||
**Hinweis**: Verwenden Sie `--force` mit Vorsicht, besonders wenn andere Personen an demselben Branch arbeiten.
|
||||
|
||||
### Ich möchte meine Commits aufräumen, bevor ich einen Pull Request erstelle
|
||||
|
||||
Wenn Sie Ihre Commits aufräumen möchten, bevor Sie einen Pull Request erstellen:
|
||||
|
||||
```bash
|
||||
# Interaktives Rebase für die letzten n Commits
|
||||
git rebase -i HEAD~n
|
||||
|
||||
# Pushen Sie die Änderungen
|
||||
git push origin feature/issue-123-kurze-beschreibung --force
|
||||
```
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Coding Standards](code-style.md): Erfahren Sie mehr über die Coding Standards des Projekts.
|
||||
- [Dokumentations-Anleitung](documentation.md): Erfahren Sie, wie Sie zur Dokumentation beitragen können.
|
||||
- [GitHub Flow](https://guides.github.com/introduction/flow/): Eine leichtgewichtige, branchbasierte Workflow-Anleitung.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
78
docs/database-migration-fix.md
Normal file
78
docs/database-migration-fix.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Database Migration Fix: Column Existence Check
|
||||
|
||||
## Issue Description
|
||||
|
||||
When running migrations, particularly the `AddSizeToImageVariantsTable` migration, the following error occurred:
|
||||
|
||||
```
|
||||
Running migrations...
|
||||
Migrating: AddSizeToImageVariantsTable - Add Size to Image Slot Table
|
||||
❌ Migration failed: Migration AddSizeToImageVariantsTable failed: Failed to execute query: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '?' at line 1 --- SQL: SHOW COLUMNS FROM image_variants LIKE ? PARAMETERS: {size}
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
The issue was in the `hasColumn` method of the `Schema` class. When checking if a column exists in a MySQL/MariaDB database, the code was using a parameterized query with a placeholder (`?`) in the `LIKE` clause:
|
||||
|
||||
```php
|
||||
'mysql' => "SHOW COLUMNS FROM {$table} LIKE ?",
|
||||
```
|
||||
|
||||
However, MariaDB doesn't support parameter binding for the `LIKE` clause in this context. The placeholder `?` was being passed directly to the SQL statement instead of being replaced with the actual value.
|
||||
|
||||
## Solution
|
||||
|
||||
The solution was to modify the `hasColumn` method to use a different query for MySQL/MariaDB that properly supports parameter binding:
|
||||
|
||||
```php
|
||||
public function hasColumn(string $table, string $column): bool
|
||||
{
|
||||
$driver = $this->getDriverName();
|
||||
|
||||
$query = match($driver) {
|
||||
'mysql' => "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
|
||||
'pgsql' => "SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?",
|
||||
'sqlite' => "PRAGMA table_info({$table})",
|
||||
default => throw new \RuntimeException("Unsupported driver: {$driver}")
|
||||
};
|
||||
|
||||
if ($driver === 'pgsql') {
|
||||
$result = $this->connection->queryScalar($query, [$table, $column]);
|
||||
} elseif ($driver === 'sqlite') {
|
||||
$columns = $this->connection->query($query)->fetchAll();
|
||||
foreach ($columns as $col) {
|
||||
if ($col['name'] === $column) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} elseif ($driver === 'mysql') {
|
||||
$result = $this->connection->queryScalar($query, [$table, $column]);
|
||||
} else {
|
||||
throw new \RuntimeException("Unsupported driver: {$driver}");
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
```
|
||||
|
||||
Key changes:
|
||||
1. Changed the MySQL query to use `information_schema.COLUMNS` with proper parameter binding
|
||||
2. Updated the parameter handling for MySQL to pass both table and column parameters
|
||||
3. Added an explicit condition for the MySQL driver to handle the parameters correctly
|
||||
|
||||
## Benefits
|
||||
|
||||
This change:
|
||||
1. Fixes the SQL syntax error when checking if a column exists
|
||||
2. Makes the code more robust by using parameterized queries throughout
|
||||
3. Provides better protection against SQL injection
|
||||
4. Makes the migrations idempotent (can be run multiple times without error)
|
||||
|
||||
## Future Considerations
|
||||
|
||||
For all database operations, especially in schema manipulation:
|
||||
1. Always use parameterized queries when possible
|
||||
2. Test database operations with different database engines
|
||||
3. Consider adding more comprehensive error handling in database schema operations
|
||||
4. Add unit tests for database schema operations to catch these issues earlier
|
||||
329
docs/database/new-features.md
Normal file
329
docs/database/new-features.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# Database Module New Features
|
||||
|
||||
This document provides an overview of the new features added to the Database module.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Stored Procedures and Functions](#stored-procedures-and-functions)
|
||||
- [Extended Index Management](#extended-index-management)
|
||||
- [Enhanced Schema Versioning](#enhanced-schema-versioning)
|
||||
- [Usage Examples](#usage-examples)
|
||||
|
||||
## Stored Procedures and Functions
|
||||
|
||||
The Database module now supports defining, executing, and managing stored procedures and functions across different database systems.
|
||||
|
||||
### StoredProcedureDefinition
|
||||
|
||||
The `StoredProcedureDefinition` class provides a fluent interface for defining stored procedures:
|
||||
|
||||
```php
|
||||
use App\Framework\Database\StoredProcedure\StoredProcedureDefinition;
|
||||
|
||||
$procedure = StoredProcedureDefinition::create('get_user_by_id')
|
||||
->withParameter('user_id', 'INT')
|
||||
->withBody('SELECT * FROM users WHERE id = user_id')
|
||||
->returnsType('TABLE');
|
||||
```
|
||||
|
||||
The definition can then be converted to SQL for different database systems:
|
||||
|
||||
```php
|
||||
// Generate MySQL-specific SQL
|
||||
$mysqlSql = $procedure->toSql('mysql');
|
||||
|
||||
// Generate PostgreSQL-specific SQL
|
||||
$pgsqlSql = $procedure->toSql('pgsql');
|
||||
```
|
||||
|
||||
### StoredProcedureManager
|
||||
|
||||
The `StoredProcedureManager` class provides methods for managing stored procedures:
|
||||
|
||||
```php
|
||||
use App\Framework\Database\StoredProcedure\StoredProcedureManager;
|
||||
|
||||
// Create a new manager with a database connection
|
||||
$manager = new StoredProcedureManager($connection);
|
||||
|
||||
// Create a stored procedure
|
||||
$manager->createProcedure($procedure);
|
||||
|
||||
// Check if a procedure exists
|
||||
if ($manager->procedureExists('get_user_by_id')) {
|
||||
// Execute the procedure with parameters
|
||||
$result = $manager->executeProcedure('get_user_by_id', [42]);
|
||||
|
||||
// Process the results
|
||||
foreach ($result->fetchAll() as $row) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// Execute a stored function
|
||||
$value = $manager->executeFunction('calculate_order_total', [123, true]);
|
||||
|
||||
// Drop a procedure
|
||||
$manager->dropProcedure('get_user_by_id');
|
||||
|
||||
// List all procedures
|
||||
$procedures = $manager->listProcedures();
|
||||
```
|
||||
|
||||
## Extended Index Management
|
||||
|
||||
The Database module now supports advanced index types and features across different database systems.
|
||||
|
||||
### AdvancedIndexDefinition
|
||||
|
||||
The `AdvancedIndexDefinition` class provides a fluent interface for defining advanced indexes:
|
||||
|
||||
```php
|
||||
use App\Framework\Database\Schema\Index\AdvancedIndexDefinition;
|
||||
use App\Framework\Database\Schema\Index\AdvancedIndexType;
|
||||
|
||||
// Create a standard index
|
||||
$index = AdvancedIndexDefinition::create(
|
||||
'idx_users_email',
|
||||
['email'],
|
||||
AdvancedIndexType::INDEX
|
||||
);
|
||||
|
||||
// Create a partial index (only indexes rows that match a condition)
|
||||
$partialIndex = AdvancedIndexDefinition::partial(
|
||||
'idx_active_users',
|
||||
['email'],
|
||||
AdvancedIndexType::INDEX,
|
||||
'active = true'
|
||||
);
|
||||
|
||||
// Create a functional index (indexes the result of a function)
|
||||
$functionalIndex = AdvancedIndexDefinition::functional(
|
||||
'idx_lower_email',
|
||||
AdvancedIndexType::INDEX,
|
||||
['LOWER(email)']
|
||||
);
|
||||
|
||||
// Create a PostgreSQL GIN index (for full-text search, JSON, arrays)
|
||||
$ginIndex = AdvancedIndexDefinition::gin(
|
||||
'idx_document_content',
|
||||
['content']
|
||||
);
|
||||
|
||||
// Create a PostgreSQL GiST index (for geometric data, ranges)
|
||||
$gistIndex = AdvancedIndexDefinition::gist(
|
||||
'idx_location_position',
|
||||
['position']
|
||||
);
|
||||
|
||||
// Create a MySQL BTREE index with options
|
||||
$btreeIndex = AdvancedIndexDefinition::btree(
|
||||
'idx_users_name',
|
||||
['first_name', 'last_name'],
|
||||
['fillfactor' => 70]
|
||||
);
|
||||
```
|
||||
|
||||
The index definition can then be converted to SQL for different database systems:
|
||||
|
||||
```php
|
||||
// Generate SQL for PostgreSQL
|
||||
$sql = $index->toSql('pgsql', 'users');
|
||||
|
||||
// Generate SQL for MySQL
|
||||
$sql = $index->toSql('mysql', 'users');
|
||||
```
|
||||
|
||||
## Enhanced Schema Versioning
|
||||
|
||||
The Database module now includes enhanced schema versioning capabilities, including dependency tracking and schema comparison tools.
|
||||
|
||||
### Schema Comparison
|
||||
|
||||
The schema comparison tools allow you to compare two database schemas and generate migration code to update one schema to match the other.
|
||||
|
||||
```php
|
||||
use App\Framework\Database\Schema\Comparison\SchemaComparator;
|
||||
|
||||
// Create a comparator with source and target connections
|
||||
$comparator = new SchemaComparator($sourceConnection, $targetConnection);
|
||||
|
||||
// Compare the schemas
|
||||
$difference = $comparator->compare();
|
||||
|
||||
// Check if there are differences
|
||||
if ($difference->hasDifferences()) {
|
||||
// Get a summary of the differences
|
||||
$summary = $difference->getSummary();
|
||||
|
||||
// Get a detailed description of the differences
|
||||
$description = $difference->getDescription();
|
||||
|
||||
// Generate migration code to update the target schema to match the source schema
|
||||
$migrationCode = $difference->generateMigrationCode('UpdateSchema');
|
||||
|
||||
// Save the migration code to a file
|
||||
file_put_contents('database/migrations/UpdateSchema.php', $migrationCode);
|
||||
}
|
||||
```
|
||||
|
||||
### Dependent Migrations
|
||||
|
||||
The enhanced migration system now supports dependencies between migrations:
|
||||
|
||||
```php
|
||||
use App\Framework\Database\ConnectionInterface;
|
||||
use App\Framework\Database\Migration\AbstractDependentMigration;
|
||||
use App\Framework\Database\Migration\MigrationVersion;
|
||||
use App\Framework\Database\Schema\Schema;
|
||||
|
||||
final class CreateCommentsTable extends AbstractDependentMigration
|
||||
{
|
||||
public function getVersion(): MigrationVersion
|
||||
{
|
||||
return MigrationVersion::fromString('20250805123456');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create comments table';
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
// This migration depends on the CreateUsersTable and CreatePostsTable migrations
|
||||
return [
|
||||
MigrationVersion::fromString('20250805123455'), // CreateUsersTable
|
||||
MigrationVersion::fromString('20250805123454') // CreatePostsTable
|
||||
];
|
||||
}
|
||||
|
||||
public function up(ConnectionInterface $connection): void
|
||||
{
|
||||
$schema = new Schema($connection);
|
||||
|
||||
$schema->create('comments', function ($table) {
|
||||
$table->id();
|
||||
$table->text('content');
|
||||
$table->foreignId('user_id')->references('id')->on('users');
|
||||
$table->foreignId('post_id')->references('id')->on('posts');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$schema->execute();
|
||||
}
|
||||
|
||||
public function down(ConnectionInterface $connection): void
|
||||
{
|
||||
$schema = new Schema($connection);
|
||||
$schema->dropIfExists('comments');
|
||||
$schema->execute();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The migration runner will ensure that migrations are executed in the correct order based on their dependencies.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Creating and Using a Stored Procedure
|
||||
|
||||
```php
|
||||
use App\Framework\Database\StoredProcedure\StoredProcedureDefinition;
|
||||
use App\Framework\Database\StoredProcedure\StoredProcedureManager;
|
||||
|
||||
// Define a stored procedure to get active users
|
||||
$procedure = StoredProcedureDefinition::create('get_active_users')
|
||||
->withParameter('min_login_count', 'INT')
|
||||
->withBody('
|
||||
SELECT * FROM users
|
||||
WHERE active = true AND login_count >= min_login_count
|
||||
ORDER BY last_login DESC;
|
||||
')
|
||||
->returnsType('TABLE');
|
||||
|
||||
// Create the procedure in the database
|
||||
$manager = new StoredProcedureManager($connection);
|
||||
$manager->createProcedure($procedure);
|
||||
|
||||
// Execute the procedure
|
||||
$result = $manager->executeProcedure('get_active_users', [5]);
|
||||
|
||||
// Process the results
|
||||
foreach ($result->fetchAll() as $user) {
|
||||
echo "User: {$user['name']} (Last login: {$user['last_login']})\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Creating Advanced Indexes
|
||||
|
||||
```php
|
||||
use App\Framework\Database\Schema\Index\AdvancedIndexDefinition;
|
||||
use App\Framework\Database\Schema\Index\AdvancedIndexType;
|
||||
|
||||
// In a migration
|
||||
public function up(ConnectionInterface $connection): void
|
||||
{
|
||||
$schema = new Schema($connection);
|
||||
|
||||
$schema->create('posts', function ($table) {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->text('content');
|
||||
$table->boolean('published')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$schema->execute();
|
||||
|
||||
// Create a partial index on published posts
|
||||
$partialIndex = AdvancedIndexDefinition::partial(
|
||||
'idx_published_posts',
|
||||
['title'],
|
||||
AdvancedIndexType::INDEX,
|
||||
'published = true'
|
||||
);
|
||||
|
||||
// Create a functional index for case-insensitive search
|
||||
$functionalIndex = AdvancedIndexDefinition::functional(
|
||||
'idx_lower_title',
|
||||
AdvancedIndexType::INDEX,
|
||||
['LOWER(title)']
|
||||
);
|
||||
|
||||
// Generate and execute SQL for the current database
|
||||
$driver = $connection->getPdo()->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
$connection->execute($partialIndex->toSql($driver, 'posts'));
|
||||
$connection->execute($functionalIndex->toSql($driver, 'posts'));
|
||||
}
|
||||
```
|
||||
|
||||
### Comparing Schemas and Generating Migrations
|
||||
|
||||
```php
|
||||
use App\Framework\Database\Schema\Comparison\SchemaComparator;
|
||||
|
||||
// Compare development and production schemas
|
||||
$comparator = new SchemaComparator($devConnection, $prodConnection);
|
||||
$difference = $comparator->compare();
|
||||
|
||||
if ($difference->hasDifferences()) {
|
||||
// Print a summary of the differences
|
||||
$summary = $difference->getSummary();
|
||||
echo "Found {$summary['total_differences']} differences:\n";
|
||||
echo " Missing tables: {$summary['missing_tables']}\n";
|
||||
echo " Extra tables: {$summary['extra_tables']}\n";
|
||||
echo " Modified tables: {$summary['modified_tables']}\n";
|
||||
|
||||
// Generate migration code
|
||||
$timestamp = date('YmdHis');
|
||||
$className = "UpdateProductionSchema{$timestamp}";
|
||||
$migrationCode = $difference->generateMigrationCode($className);
|
||||
|
||||
// Save the migration
|
||||
$migrationPath = "database/migrations/{$className}.php";
|
||||
file_put_contents($migrationPath, $migrationCode);
|
||||
|
||||
echo "Generated migration: {$migrationPath}\n";
|
||||
}
|
||||
```
|
||||
296
docs/discovery-filesystem-integration.md
Normal file
296
docs/discovery-filesystem-integration.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# Integration von Discovery und Filesystem Modulen
|
||||
|
||||
Dieses Dokument beschreibt die Integration des Discovery-Moduls mit dem Filesystem-Modul, um Codeduplizierung zu reduzieren, die Robustheit zu verbessern und erweiterte Funktionen zu nutzen.
|
||||
|
||||
## Überblick
|
||||
|
||||
Die Integration umfasst folgende Komponenten:
|
||||
|
||||
1. **FileMetadata**: Eine erweiterte FileMetadata-Klasse im Filesystem-Modul, die Core Value Objects nutzt
|
||||
2. **DiscoveryStorage**: Ein spezialisiertes Storage-Interface für Discovery-Operationen
|
||||
3. **FileSystemDiscoveryStorage**: Eine Implementierung des DiscoveryStorage-Interfaces
|
||||
4. **FileScannerService**: Der bestehende Service, aktualisiert für die Nutzung des Filesystem-Moduls
|
||||
5. **FileScannerServiceBootstrapper**: Ein Bootstrapper für den FileScannerService
|
||||
6. **UnifiedDiscoveryService**: Der bestehende Service, aktualisiert für die Nutzung des FileScannerServiceBootstrapper
|
||||
|
||||
## Vorteile der Integration
|
||||
|
||||
- **Vermeidung von Code-Duplizierung**: Einheitliche Abstraktion für Dateisystem-Operationen
|
||||
- **Verbesserte Robustheit**: Konsistente Fehlerbehandlung und Ausnahmen
|
||||
- **Leistungsoptimierung**: Nutzung von Batch- und Async-Funktionen des Filesystem-Moduls
|
||||
- **Bessere Testbarkeit**: Einheitliche Mocking-Strategie für Dateisystem-Operationen
|
||||
- **Erweiterte Funktionalität**: Zugriff auf fortschrittliche Features wie Dateisystem-Events
|
||||
|
||||
## Komponenten im Detail
|
||||
|
||||
### FileMetadata
|
||||
|
||||
Die erweiterte `FileMetadata`-Klasse im Filesystem-Modul vereint die Funktionalität der vorherigen FileMetadata-Klassen und nutzt Core-ValueObjects für eine robustere Implementierung.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Filesystem;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Byte;
|
||||
use App\Framework\Core\ValueObjects\Hash;
|
||||
use App\Framework\Core\ValueObjects\HashAlgorithm;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
|
||||
final readonly class FileMetadata
|
||||
{
|
||||
public function __construct(
|
||||
public FilePath|string $path = '',
|
||||
public Byte|int $size = 0,
|
||||
public Timestamp|int $lastModified = 0,
|
||||
public ?Hash $checksum = null,
|
||||
public ?Timestamp $scanTime = null,
|
||||
public string $mimeType = '',
|
||||
public bool $isReadable = false,
|
||||
public bool $isWritable = false,
|
||||
public array $additionalData = []
|
||||
) {
|
||||
}
|
||||
|
||||
// Methoden für Erstellung, Konvertierung, Prüfung auf Änderungen, etc.
|
||||
}
|
||||
```
|
||||
|
||||
### DiscoveryStorage Interface
|
||||
|
||||
Das `DiscoveryStorage`-Interface erweitert das Standard-Storage-Interface mit Discovery-spezifischen Methoden.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Discovery\Storage;
|
||||
|
||||
use App\Framework\Filesystem\Storage;
|
||||
use App\Framework\Filesystem\FileMetadata;
|
||||
use SplFileInfo;
|
||||
|
||||
interface DiscoveryStorage extends Storage
|
||||
{
|
||||
/**
|
||||
* Findet geänderte Dateien seit dem letzten Scan
|
||||
*/
|
||||
public function findChangedFiles(string $directory, array $fileMetadata): array;
|
||||
|
||||
/**
|
||||
* Führt einen inkrementellen Scan durch
|
||||
*/
|
||||
public function incrementalScan(string $directory, array $fileMetadata): array;
|
||||
|
||||
/**
|
||||
* Findet alle Dateien mit einem bestimmten Muster
|
||||
*/
|
||||
public function findFiles(string $directory, string $pattern): array;
|
||||
|
||||
/**
|
||||
* Erstellt Metadaten für eine Datei
|
||||
*/
|
||||
public function getDiscoveryMetadata(string $filePath, bool $calculateChecksum = true): FileMetadata;
|
||||
|
||||
/**
|
||||
* Erstellt Metadaten für mehrere Dateien parallel
|
||||
*/
|
||||
public function getDiscoveryMetadataMultiple(array $filePaths, bool $calculateChecksum = true): array;
|
||||
|
||||
/**
|
||||
* Registriert Dateisystem-Events für Änderungserkennung
|
||||
*/
|
||||
public function registerFileSystemEvents(string $directory, callable $callback): bool;
|
||||
}
|
||||
```
|
||||
|
||||
### FileSystemDiscoveryStorage
|
||||
|
||||
Die `FileSystemDiscoveryStorage`-Klasse implementiert das DiscoveryStorage-Interface und delegiert Standard-Storage-Operationen an eine zugrundeliegende Storage-Implementierung.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Discovery\Storage;
|
||||
|
||||
use App\Framework\Async\FiberManager;
|
||||
use App\Framework\Filesystem\File;
|
||||
use App\Framework\Filesystem\Directory;
|
||||
use App\Framework\Filesystem\FileMetadata;
|
||||
use App\Framework\Filesystem\PermissionChecker;
|
||||
use App\Framework\Filesystem\Storage;
|
||||
use SplFileInfo;
|
||||
|
||||
final class FileSystemDiscoveryStorage implements DiscoveryStorage
|
||||
{
|
||||
/**
|
||||
* @param Storage $storage Basis-Storage-Implementierung für Delegation
|
||||
* @param PermissionChecker $permissions Permissions-Checker
|
||||
* @param FiberManager $fiberManager Fiber-Manager für asynchrone Operationen
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Storage $storage,
|
||||
public readonly PermissionChecker $permissions = new PermissionChecker(),
|
||||
public readonly FiberManager $fiberManager = new FiberManager()
|
||||
) {
|
||||
}
|
||||
|
||||
// Implementierung der Storage-Methoden durch Delegation
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDiscoveryMetadata(string $filePath, bool $calculateChecksum = true): FileMetadata
|
||||
{
|
||||
return FileMetadata::fromFile($filePath, $this, $calculateChecksum);
|
||||
}
|
||||
|
||||
// Weitere Implementierungen der Discovery-spezifischen Methoden
|
||||
}
|
||||
```
|
||||
|
||||
### FileScannerService
|
||||
|
||||
Der `FileScannerService` wurde aktualisiert, um das DiscoveryStorage-Interface zu nutzen, wenn verfügbar, mit Fallback auf die alte Implementierung für Abwärtskompatibilität.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Discovery;
|
||||
|
||||
use App\Framework\Discovery\Storage\DiscoveryStorage;
|
||||
|
||||
final class FileScannerService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FileScannerInterface $fileScanner,
|
||||
private readonly PathProvider $pathProvider,
|
||||
private readonly Cache $cache,
|
||||
private readonly bool $useCache = true,
|
||||
// weitere Parameter
|
||||
private readonly ?DiscoveryStorage $storage = null
|
||||
) {
|
||||
// Initialisierung
|
||||
}
|
||||
|
||||
// Methoden für Scanning, Verarbeitung, Caching, etc.
|
||||
// mit Nutzung von DiscoveryStorage wenn verfügbar
|
||||
}
|
||||
```
|
||||
|
||||
### FileScannerServiceBootstrapper
|
||||
|
||||
Der `FileScannerServiceBootstrapper` erstellt und konfiguriert den FileScannerService mit den richtigen Abhängigkeiten.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Discovery;
|
||||
|
||||
use App\Framework\Filesystem\FilesystemManager;
|
||||
|
||||
final class FileScannerServiceBootstrapper
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FilesystemManager $filesystemManager,
|
||||
// weitere Parameter
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(
|
||||
bool $useCache = true,
|
||||
// weitere Parameter
|
||||
): FileScannerService {
|
||||
// Erstelle DiscoveryStorage und FileScannerService
|
||||
}
|
||||
|
||||
public static function createDefault(
|
||||
FilesystemManager $filesystemManager,
|
||||
// weitere Parameter
|
||||
): FileScannerService {
|
||||
// Factory-Methode für einfache Erstellung
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### UnifiedDiscoveryService
|
||||
|
||||
Der `UnifiedDiscoveryService` wurde aktualisiert, um den FileScannerServiceBootstrapper zu nutzen.
|
||||
|
||||
```php
|
||||
namespace App\Framework\Discovery;
|
||||
|
||||
use App\Framework\Filesystem\FilesystemManager;
|
||||
|
||||
final readonly class UnifiedDiscoveryService
|
||||
{
|
||||
public function __construct(
|
||||
FilesystemManager $filesystemManager,
|
||||
// weitere Parameter
|
||||
) {
|
||||
// Erstelle FileScannerService mit Bootstrapper
|
||||
}
|
||||
|
||||
// Methoden für Discovery, Caching, Health-Checks, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Einfache Verwendung mit Standard-Konfiguration
|
||||
|
||||
```php
|
||||
// Container-Konfiguration
|
||||
$container->singleton(UnifiedDiscoveryService::class, function (Container $container) {
|
||||
return new UnifiedDiscoveryService(
|
||||
$container->get(FilesystemManager::class),
|
||||
$container->get(PathProvider::class),
|
||||
$container->get(Cache::class),
|
||||
$container->get(Clock::class),
|
||||
// weitere Parameter mit Standardwerten
|
||||
);
|
||||
});
|
||||
|
||||
// Verwendung
|
||||
$discoveryService = $container->get(UnifiedDiscoveryService::class);
|
||||
$results = $discoveryService->discover();
|
||||
```
|
||||
|
||||
### Erweiterte Konfiguration
|
||||
|
||||
```php
|
||||
// Manuelle Erstellung mit angepasster Konfiguration
|
||||
$bootstrapper = new FileScannerServiceBootstrapper(
|
||||
$filesystemManager,
|
||||
$pathProvider,
|
||||
$cache,
|
||||
$logger
|
||||
);
|
||||
|
||||
$fileScannerService = $bootstrapper->create(
|
||||
useCache: true,
|
||||
asyncProcessing: true,
|
||||
chunkSize: 200,
|
||||
maxRetries: 5,
|
||||
enableAdaptiveChunking: true,
|
||||
storageName: 'custom_storage'
|
||||
);
|
||||
|
||||
// Verwendung des FileScannerService direkt
|
||||
$fileScannerService->scan(true); // mit Fortschrittsanzeige
|
||||
```
|
||||
|
||||
### Verwendung des DiscoveryStorage direkt
|
||||
|
||||
```php
|
||||
// Erstelle DiscoveryStorage
|
||||
$storage = new FileSystemDiscoveryStorage(
|
||||
$filesystemManager->storage('default')
|
||||
);
|
||||
|
||||
// Finde geänderte Dateien
|
||||
$changedFiles = $storage->findChangedFiles('/path/to/directory', $existingMetadata);
|
||||
|
||||
// Hole Metadaten für eine Datei
|
||||
$metadata = $storage->getDiscoveryMetadata('/path/to/file.php', true);
|
||||
|
||||
// Hole Metadaten für mehrere Dateien parallel
|
||||
$metadataMap = $storage->getDiscoveryMetadataMultiple([
|
||||
'/path/to/file1.php',
|
||||
'/path/to/file2.php',
|
||||
'/path/to/file3.php'
|
||||
]);
|
||||
```
|
||||
|
||||
## Fazit
|
||||
|
||||
Die Integration des Discovery-Moduls mit dem Filesystem-Modul bietet zahlreiche Vorteile in Bezug auf Codeduplizierung, Robustheit, Leistung und Funktionalität. Die Implementierung ist abwärtskompatibel, sodass bestehender Code weiterhin funktioniert, während neue Code die verbesserte Funktionalität nutzen kann.
|
||||
@@ -1,42 +0,0 @@
|
||||
# Framework Features
|
||||
|
||||
## Core Features
|
||||
- [ ] Routing mit Unterstützung für PHP-Attribute
|
||||
- [ ] Dependency Injection Container
|
||||
- [ ] Request/Response Abstraktion
|
||||
- [ ] Template-Engine
|
||||
- [ ] Error/Exception Handling
|
||||
- [ ] Konfigurationssystem
|
||||
|
||||
## Database Features
|
||||
- [ ] PDO-Wrapper
|
||||
- [ ] Query Builder
|
||||
- [ ] Migrations-System
|
||||
- [ ] Schema Manager
|
||||
- [ ] Entity-Mapping (optional)
|
||||
|
||||
## Security Features
|
||||
- [ ] CSRF-Schutz
|
||||
- [ ] XSS-Filtierung
|
||||
- [ ] Input-Validierung
|
||||
- [ ] Authentifizierung
|
||||
- [ ] Autorisierung/Rechtemanagement
|
||||
|
||||
## Module: Music
|
||||
- [ ] Album-Verwaltung
|
||||
- [ ] Track-Management
|
||||
- [ ] Playlists
|
||||
- [ ] Integrationsmöglichkeit mit Spotify/SoundCloud
|
||||
|
||||
## Module: Content
|
||||
- [ ] Blog-System
|
||||
- [ ] Markdown-Support
|
||||
- [ ] Medienbibliothek
|
||||
- [ ] SEO-Optimierung
|
||||
- [ ] Kommentarsystem
|
||||
|
||||
## Admin Interface
|
||||
- [ ] Dashboard
|
||||
- [ ] Content-Editor
|
||||
- [ ] Benutzer-/Rechteverwaltung
|
||||
- [ ] Statistiken
|
||||
@@ -1,14 +0,0 @@
|
||||
# Analytics-Framework
|
||||
|
||||
> **Dokumentationshinweis:** Die vollständige Dokumentation für das Analytics-Framework finden Sie im Ordner [/docs/framework/analytics](/docs/framework/analytics/).
|
||||
|
||||
## Schnellzugriff
|
||||
|
||||
- [Übersicht und Einstieg](/docs/framework/analytics/index.md)
|
||||
- [Detaillierte Architektur](/docs/framework/analytics/architecture.md)
|
||||
- [Anwendungsbeispiele](/docs/framework/analytics/usage.md)
|
||||
- [Migrationsleitfaden](/docs/framework/analytics/migration.md)
|
||||
|
||||
## Implementierungsdetails
|
||||
|
||||
Die Code-Dokumentation befindet sich in der [README-Datei des Frameworks](/Framework/Analytics1/README.md).
|
||||
@@ -1,200 +0,0 @@
|
||||
# Erweiterungsmuster im Framework
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses Dokument beschreibt die verschiedenen Muster und Techniken, um das Framework zu erweitern oder anzupassen, ohne den Kern-Code zu verändern.
|
||||
|
||||
## Event-basierte Erweiterungen
|
||||
|
||||
### Event-Listener
|
||||
|
||||
Die primäre Methode zur Erweiterung des Frameworks ist das Lauschen auf System-Events:
|
||||
|
||||
```php
|
||||
// Event-Listener registrieren
|
||||
public function __construct(private readonly EventDispatcher $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher->addHandler(
|
||||
'App\Framework\Core\Events\ApplicationBooted',
|
||||
[$this, 'onApplicationBooted']
|
||||
);
|
||||
}
|
||||
|
||||
// Event-Handler-Methode
|
||||
public function onApplicationBooted(ApplicationBooted $event): void
|
||||
{
|
||||
// Erweiterungslogik implementieren
|
||||
}
|
||||
```
|
||||
|
||||
### Eigene Events
|
||||
|
||||
Benutzerdefinierte Events erstellen:
|
||||
|
||||
```php
|
||||
final readonly class UserRegistered
|
||||
{
|
||||
public function __construct(
|
||||
public string $userId,
|
||||
public string $email,
|
||||
public \DateTimeImmutable $timestamp
|
||||
) {}
|
||||
}
|
||||
|
||||
// Event auslösen
|
||||
$this->eventDispatcher->dispatch(new UserRegistered(
|
||||
$user->getId(),
|
||||
$user->getEmail(),
|
||||
new \DateTimeImmutable()
|
||||
));
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
HTTP-Anfragen können durch Middleware-Klassen erweitert werden:
|
||||
|
||||
```php
|
||||
final readonly class CustomMiddleware implements Middleware
|
||||
{
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
// Vor der Anfrageverarbeitung
|
||||
$modifiedRequest = $this->modifyRequest($request);
|
||||
|
||||
// Anfrage weiterleiten
|
||||
$response = $next($modifiedRequest);
|
||||
|
||||
// Nach der Anfrageverarbeitung
|
||||
return $this->modifyResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware registrieren
|
||||
$app->addMiddleware(CustomMiddleware::class);
|
||||
```
|
||||
|
||||
## Service-Erweiterungen
|
||||
|
||||
### Service-Ersetzen
|
||||
|
||||
Standardimplementierungen durch eigene ersetzen:
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
final readonly class CustomStorageInitializer
|
||||
{
|
||||
public function __invoke(Container $container): StorageInterface
|
||||
{
|
||||
return new CustomStorage();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service-Decorator
|
||||
|
||||
Bestehende Services erweitern ohne Änderung der Original-Implementierung:
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
final readonly class LoggingAnalyticsInitializer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Configuration $config,
|
||||
private readonly LoggerInterface $logger
|
||||
) {}
|
||||
|
||||
public function __invoke(Container $container): Analytics
|
||||
{
|
||||
// Original-Analytics-Service erstellen
|
||||
$originalAnalytics = new Analytics(
|
||||
new AnalyticsManager($this->config->get('analytics'), new FileStorage($path)),
|
||||
$container->get(EventDispatcher::class)
|
||||
);
|
||||
|
||||
// Mit Logging-Decorator umhüllen
|
||||
return new LoggingAnalyticsDecorator($originalAnalytics, $this->logger);
|
||||
}
|
||||
}
|
||||
|
||||
// Decorator-Implementierung
|
||||
final readonly class LoggingAnalyticsDecorator implements AnalyticsInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Analytics $analytics,
|
||||
private LoggerInterface $logger
|
||||
) {}
|
||||
|
||||
public function track(string $event, array $properties = [], ?string $userId = null): void
|
||||
{
|
||||
$this->logger->debug("Tracking event: {$event}", [
|
||||
'properties' => $properties,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
|
||||
$this->analytics->track($event, $properties, $userId);
|
||||
}
|
||||
|
||||
// Andere Methoden implementieren
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin-System
|
||||
|
||||
### Plugin-Interface
|
||||
|
||||
```php
|
||||
interface PluginInterface
|
||||
{
|
||||
public function register(Application $app): void;
|
||||
public function boot(Application $app): void;
|
||||
}
|
||||
|
||||
// Plugin-Implementierung
|
||||
final readonly class CustomPlugin implements PluginInterface
|
||||
{
|
||||
public function register(Application $app): void
|
||||
{
|
||||
// Services registrieren
|
||||
}
|
||||
|
||||
public function boot(Application $app): void
|
||||
{
|
||||
// Nach Initialisierung der Anwendung
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin registrieren
|
||||
$app->registerPlugin(new CustomPlugin());
|
||||
```
|
||||
|
||||
## Konfigurationserweiterungen
|
||||
|
||||
### Konfigurationsquellen
|
||||
|
||||
Benutzerdefinierte Konfigurationsquellen implementieren:
|
||||
|
||||
```php
|
||||
final readonly class EnvironmentConfigSource implements ConfigSourceInterface
|
||||
{
|
||||
public function load(string $key, mixed $default = null): mixed
|
||||
{
|
||||
$envKey = strtoupper(str_replace('.', '_', $key));
|
||||
return $_ENV[$envKey] ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
// Konfigurationsquelle registrieren
|
||||
$config->addSource(new EnvironmentConfigSource());
|
||||
```
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Die bevorzugten Erweiterungsmuster sind:
|
||||
|
||||
1. **Event-Listener** für reaktive Erweiterungen
|
||||
2. **Middleware** für HTTP-Anfrageverarbeitung
|
||||
3. **Service-Initializer** zum Ersetzen oder Dekorieren von Services
|
||||
4. **Plugins** für umfassendere Funktionalitätserweiterungen
|
||||
5. **Konfigurationsquellen** für benutzerdefinierte Konfigurationen
|
||||
|
||||
Diese Muster ermöglichen es, das Framework zu erweitern, ohne den Kern zu modifizieren, was zu einer besseren Wartbarkeit und einfacheren Updates führt.
|
||||
@@ -1,73 +0,0 @@
|
||||
# Framework-Modul Checkliste
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Checkliste dient als Leitfaden für die Erstellung neuer Module im Framework. Sie hilft sicherzustellen, dass alle Module den Projektstandards entsprechen und konsistent implementiert werden.
|
||||
|
||||
## Strukturelle Anforderungen
|
||||
|
||||
- [ ] Modul in eigenem Verzeichnis unter `/src/Framework/`
|
||||
- [ ] Konsistente Namensgebung im PascalCase (z.B. `CacheManager` statt `Cache_manager`)
|
||||
- [ ] README.md mit Modul-Dokumentation
|
||||
- [ ] Interface(s) für öffentliche API
|
||||
- [ ] Implementierungsklassen als `final readonly`
|
||||
- [ ] Initializer-Klasse mit `#[Initializer]`-Attribut
|
||||
|
||||
## Abhängigkeiten
|
||||
|
||||
- [ ] Minimale externe Abhängigkeiten (idealerweise keine)
|
||||
- [ ] Klar definierte Abhängigkeiten zu anderen Framework-Modulen
|
||||
- [ ] Keine zirkulären Abhängigkeiten
|
||||
- [ ] Verwendung des DI-Containers für Abhängigkeiten
|
||||
|
||||
## Konfiguration
|
||||
|
||||
- [ ] Konfigurationsdatei unter `/src/Config/{modul-name}.php`
|
||||
- [ ] Standardkonfiguration in der Initializer-Klasse
|
||||
- [ ] Dokumentierte Konfigurationsoptionen
|
||||
|
||||
## Code-Qualität
|
||||
|
||||
- [ ] Vollständige Typisierung (Parameter, Rückgabewerte, Properties)
|
||||
- [ ] PHPDoc für öffentliche Methoden und Klassen
|
||||
- [ ] Keine abstrakten Klassen oder Vererbung (außer Interfaces)
|
||||
- [ ] Immutable Objekte wo möglich
|
||||
- [ ] Spezifische Exceptions für Fehlerbehandlung
|
||||
|
||||
## Tests
|
||||
|
||||
- [ ] Unit-Tests mit Pest für alle öffentlichen Methoden
|
||||
- [ ] Integrationstests für Modul-Interaktionen
|
||||
- [ ] Testabdeckung für Fehlerfälle
|
||||
|
||||
## Integration
|
||||
|
||||
- [ ] Event-Listener für relevante System-Events
|
||||
- [ ] Erweiterungspunkte für andere Module
|
||||
- [ ] Keine direkten Abhängigkeiten zu Domain-Klassen
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- [ ] Beispiele für Verwendung
|
||||
- [ ] Architektur-Beschreibung
|
||||
- [ ] API-Dokumentation
|
||||
- [ ] Konfigurationsreferenz
|
||||
|
||||
## Performance
|
||||
|
||||
- [ ] Lazy-Loading für ressourcenintensive Operationen
|
||||
- [ ] Caching-Strategie (falls relevant)
|
||||
- [ ] Performancekritische Teile identifiziert und optimiert
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- [ ] Validierung aller externen Eingaben
|
||||
- [ ] Keine sensiblen Daten in Logs
|
||||
- [ ] Schutz vor bekannten Sicherheitslücken
|
||||
|
||||
## Wartbarkeit
|
||||
|
||||
- [ ] Kleine, fokussierte Klassen
|
||||
- [ ] Klare Trennung von Verantwortlichkeiten
|
||||
- [ ] Konsistente Fehlerbehandlung
|
||||
- [ ] Logging an strategischen Stellen
|
||||
@@ -1,158 +0,0 @@
|
||||
# Analytics-Modul Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Analytics-Modul ist ein internes Tracking- und Analysesystem, das ohne externe Abhängigkeiten Benutzeraktivitäten, Systemereignisse und Leistungsdaten erfasst und analysiert.
|
||||
|
||||
## Kernkomponenten
|
||||
|
||||
### Analytics (Analytics.php)
|
||||
|
||||
Die Hauptklasse, die als zentraler Einstiegspunkt für das Tracking von Events dient.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- `track()`: Zeichnet ein generisches Event auf
|
||||
- `page()`: Spezifisch für Seitenaufrufe
|
||||
- `user()`: Verfolgt Benutzeridentifikation
|
||||
- `performance()`: Erfasst Leistungsmetriken
|
||||
- `error()`: Protokolliert Fehler und Ausnahmen
|
||||
|
||||
### AnalyticsManager (AnalyticsManager.php)
|
||||
|
||||
Verwaltet die Verarbeitung und Speicherung von Events.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- `track()`: Verarbeitet Events und wendet Middleware an
|
||||
- `addMiddleware()`: Fügt Verarbeitungsfunktionen hinzu
|
||||
- `flush()`: Schreibt gepufferte Events in den Speicher
|
||||
|
||||
### StorageInterface und FileStorage
|
||||
|
||||
Das Interface definiert die Speichermethoden, FileStorage implementiert die Speicherung in Dateien.
|
||||
|
||||
**Hauptoperationen:**
|
||||
- `store()`: Speichert Events
|
||||
- `retrieve()`: Ruft Events mit Filtern ab
|
||||
- `clear()`: Löscht alle gespeicherten Daten
|
||||
|
||||
### AnalyticsInitializer (AnalyticsInitializer.php)
|
||||
|
||||
Konfiguriert und initialisiert den Analytics-Service beim Anwendungsstart.
|
||||
|
||||
**Konfigurationsoptionen:**
|
||||
- Aktivierung/Deaktivierung
|
||||
- Auto-Flush und Batch-Größe
|
||||
- Storage-Typ und Pfad
|
||||
|
||||
### AnalyticsMiddleware (AnalyticsMiddleware.php)
|
||||
|
||||
HTTP-Middleware zum automatischen Tracking von Requests und Responses.
|
||||
|
||||
### AnalyticsDashboard (AnalyticsDashboard.php)
|
||||
|
||||
Stellt Methoden für Datenanalyse und -aggregation bereit:
|
||||
- `getEventStats()`: Ereignisstatistiken
|
||||
- `getTopPages()`: Meistbesuchte Seiten
|
||||
- `getUserStats()`: Benutzerstatistiken
|
||||
- `getErrorStats()`: Fehlerstatistiken
|
||||
- `getPerformanceStats()`: Leistungsmetriken
|
||||
|
||||
### Events
|
||||
|
||||
Vordefinierte Event-Typen:
|
||||
- `AnalyticsEvent`: Basis-Event-Klasse
|
||||
- `PageViewEvent`: Speziell für Seitenaufrufe
|
||||
|
||||
### Controllers
|
||||
|
||||
`AdminAnalyticsController`: Stellt das Admin-Dashboard bereit mit:
|
||||
- Übersichtsseite
|
||||
- Seitenstatistiken
|
||||
- Fehlerstatistiken
|
||||
- Leistungsstatistiken
|
||||
|
||||
### Console
|
||||
|
||||
`AnalyticsClearCommand`: Konsolenbefehl zum Löschen von Analytics-Daten.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die Konfiguration erfolgt in `src/Config/analytics.php` mit folgenden Optionen:
|
||||
|
||||
```php
|
||||
return [
|
||||
'enabled' => true, // Aktiviert/deaktiviert das Tracking
|
||||
'auto_flush' => true, // Automatisches Speichern nach Batch-Größe
|
||||
'batch_size' => 50, // Anzahl Events pro Batch
|
||||
'storage' => 'file', // Storage-Backend
|
||||
'storage_path' => '...', // Speicherpfad
|
||||
'anonymize_ip' => true, // IP-Anonymisierung
|
||||
'track_events' => [...] // Zu trackende Events
|
||||
];
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Basis-Tracking
|
||||
|
||||
```php
|
||||
// Event tracken
|
||||
$analytics->track('button_click', ['button' => 'signup']);
|
||||
|
||||
// Seitenaufruf tracken
|
||||
$analytics->page('/dashboard', ['section' => 'analytics']);
|
||||
|
||||
// Benutzer identifizieren
|
||||
$analytics->user('user123', ['plan' => 'premium']);
|
||||
```
|
||||
|
||||
### Middleware einrichten
|
||||
|
||||
```php
|
||||
$this->addMiddleware(App\Framework\Analytics\AnalyticsMiddleware::class);
|
||||
```
|
||||
|
||||
### Eigene Middleware hinzufügen
|
||||
|
||||
```php
|
||||
$analyticsManager->addMiddleware(function(array $event) {
|
||||
// Daten verarbeiten oder filtern
|
||||
return $event;
|
||||
});
|
||||
```
|
||||
|
||||
## Datenschutz
|
||||
|
||||
Das System bietet integrierte Funktionen zur Anonymisierung personenbezogener Daten:
|
||||
- IP-Adressen-Anonymisierung (letztes Oktett wird entfernt)
|
||||
- Konfigurierbare Filterung sensibler Daten durch Middleware
|
||||
|
||||
## Erweiterbarkeit
|
||||
|
||||
### Eigene Storage-Provider
|
||||
|
||||
Implementieren Sie das `StorageInterface` für benutzerdefinierte Speicherlösungen:
|
||||
|
||||
```php
|
||||
class CustomStorage implements StorageInterface
|
||||
{
|
||||
public function store(array $events): void { /* ... */ }
|
||||
public function retrieve(array $filters = []): array { /* ... */ }
|
||||
public function clear(): void { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Event-Typen erweitern
|
||||
|
||||
Erstellen Sie benutzerdefinierte Event-Klassen, die von `AnalyticsEvent` erben:
|
||||
|
||||
```php
|
||||
class CustomEvent extends AnalyticsEvent
|
||||
{
|
||||
public function __construct(string $customData, array $additionalProps = [])
|
||||
{
|
||||
parent::__construct('custom_event',
|
||||
array_merge(['custom_data' => $customData], $additionalProps));
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,102 +0,0 @@
|
||||
# Analytics-Framework: Architektur
|
||||
|
||||
## Überblick
|
||||
|
||||
Das Analytics-Framework verwendet eine Schichtenarchitektur mit klaren Verantwortlichkeiten:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Öffentliche API │
|
||||
│ (Analytics) │
|
||||
└───────────────────────┬─────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────▼─────────────────────────────┐
|
||||
│ Event-Verarbeitung │
|
||||
│ (AnalyticsManager) │
|
||||
└───────────────────────┬─────────────────────────────┘
|
||||
│
|
||||
┌──────────────┴──────────────┐
|
||||
│ │
|
||||
┌────────▼─────────┐ ┌─────────▼────────┐
|
||||
│ Middleware │ │ Storage-Layer │
|
||||
│ (Pipeline) │ │ (StorageInterface)│
|
||||
└──────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
## Komponenten im Detail
|
||||
|
||||
### Analytics (Frontend)
|
||||
|
||||
Bietet eine benutzerfreundliche API für Tracking-Operationen. Diese Klasse ist der primäre Einstiegspunkt für Anwendungscode und abstrahiert die Komplexität der Eventverarbeitung.
|
||||
|
||||
Verantwortlichkeiten:
|
||||
- Bereitstellung der öffentlichen API (`track`, `page`, `user`, `error`)
|
||||
- Integration mit dem Event-Dispatcher des Frameworks
|
||||
- Typ-Konvertierung zwischen benutzerdefinierten Ereignissen und dem internen Datenformat
|
||||
|
||||
### AnalyticsManager (Verarbeitung)
|
||||
|
||||
Der Manager ist das Herzstück des Systems und verantwortlich für:
|
||||
- Anwendung von Middleware-Transformationen auf Events
|
||||
- Eventpufferung für effiziente Speicherung
|
||||
- Verwaltung der Konfiguration
|
||||
- Steuerung des Storage-Layers
|
||||
|
||||
Der Manager implementiert einen Event-Buffer, der Daten sammelt und bei Erreichen einer konfigurierbaren Größe automatisch speichert.
|
||||
|
||||
### Middleware-System
|
||||
|
||||
Eine Kette von Verarbeitungsfunktionen, die auf jedes Event angewendet werden:
|
||||
- Datenfilterung und -transformation
|
||||
- Anonymisierung persönlicher Daten
|
||||
- Validierung und Anreicherung von Events
|
||||
|
||||
Jede Middleware kann Events modifizieren oder komplett verwerfen.
|
||||
|
||||
### Storage-Layer
|
||||
|
||||
Abstraktion für verschiedene Speichermethoden durch das `StorageInterface`:
|
||||
|
||||
```php
|
||||
interface StorageInterface
|
||||
{
|
||||
public function store(array $events): void;
|
||||
public function retrieve(array $filters = []): array;
|
||||
public function clear(): void;
|
||||
}
|
||||
```
|
||||
|
||||
Implementierungen:
|
||||
- `FileStorage`: Speichert Events in JSON-Dateien mit täglicher Rotation
|
||||
- Erweiterbar für andere Backends (Datenbank, Redis, etc.)
|
||||
|
||||
### HTTP-Integration
|
||||
|
||||
`AnalyticsMiddleware` integriert Analytics in den HTTP-Request-Lifecycle:
|
||||
- Tracking von eingehenden Requests
|
||||
- Erfassung von Antwortzeiten und Statuscodes
|
||||
- Fehlererfassung bei Exceptions
|
||||
|
||||
### Admin-Dashboard
|
||||
|
||||
`AnalyticsDashboard` und `AdminAnalyticsController` bieten:
|
||||
- Datenaggregation und -analyse
|
||||
- Visualisierung von Metriken
|
||||
- Filterung nach Zeiträumen
|
||||
- Verschiedene spezialisierte Ansichten (Seiten, Fehler, Performance)
|
||||
|
||||
## Datenfluss
|
||||
|
||||
1. Event wird durch `Analytics::track()` oder ähnliche Methoden erstellt
|
||||
2. `AnalyticsManager` wendet Middleware-Pipeline an
|
||||
3. Event wird zum Buffer hinzugefügt
|
||||
4. Bei Buffer-Füllung oder explizitem `flush()` werden Events an Storage übergeben
|
||||
5. Storage speichert Events im konfigurierten Backend
|
||||
6. `AnalyticsDashboard` ruft Daten bei Bedarf vom Storage ab und aggregiert sie
|
||||
|
||||
## Erweiterungspunkte
|
||||
|
||||
- **Storage-Provider**: Neue Implementierungen von `StorageInterface`
|
||||
- **Middleware**: Funktionen für Filterung/Transformation
|
||||
- **Event-Typen**: Spezialisierte Event-Klassen für typsicheres Tracking
|
||||
- **Dashboard-Views**: Zusätzliche Visualisierungen und Berichte
|
||||
@@ -1,114 +0,0 @@
|
||||
# Analytics-Framework
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Analytics-Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Analytics-Framework ist ein eigenständiges Tracking- und Analysesystem, das vollständig in die Anwendung integriert ist und ohne externe Dienste auskommt. Es ermöglicht die Erfassung, Speicherung und Analyse von:
|
||||
|
||||
- Benutzeraktivitäten (Seitenaufrufe, Interaktionen)
|
||||
- Systemereignissen (Anwendungsstart, Fehler)
|
||||
- Leistungsdaten (Speicherverbrauch, Ausführungszeit, Datenbankabfragen)
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Analytics-Klasse
|
||||
|
||||
Die zentrale API für Tracking-Operationen:
|
||||
|
||||
```php
|
||||
// Klasse initialisieren
|
||||
$analytics = new Analytics($analyticsManager, $eventDispatcher);
|
||||
|
||||
// Event tracken
|
||||
$analytics->track('event_name', ['property' => 'value']);
|
||||
|
||||
// Seite tracken
|
||||
$analytics->page('/pfad/zur/seite', ['eigenschaft' => 'wert']);
|
||||
|
||||
// Benutzer identifizieren
|
||||
$analytics->user('user_id', ['eigenschaft' => 'wert']);
|
||||
|
||||
// Fehler tracken
|
||||
$analytics->error($exception);
|
||||
```
|
||||
|
||||
### AnalyticsManager
|
||||
|
||||
Verarbeitet und speichert Events:
|
||||
|
||||
- Wendet Middleware auf Events an
|
||||
- Puffert Events für effiziente Speicherung
|
||||
- Verwaltet die Konfiguration
|
||||
- Steuert die Speicherung in verschiedenen Backends
|
||||
|
||||
### Storage-System
|
||||
|
||||
Basierend auf dem `StorageInterface` mit verschiedenen Implementierungen:
|
||||
|
||||
- `FileStorage`: Speichert Events in Dateien
|
||||
- Erweiterbar für Datenbank, Redis oder andere Backends
|
||||
|
||||
### Middleware-System
|
||||
|
||||
```php
|
||||
$analyticsManager->addMiddleware(function(array $event) {
|
||||
// Event verarbeiten oder filtern
|
||||
return $event; // oder null um zu verwerfen
|
||||
});
|
||||
```
|
||||
|
||||
### HTTP-Middleware
|
||||
|
||||
Automatisches Tracking von HTTP-Requests und -Responses:
|
||||
|
||||
```php
|
||||
// In Bootstrap oder Application-Klasse
|
||||
$app->addMiddleware(AnalyticsMiddleware::class);
|
||||
```
|
||||
|
||||
### Dashboard und Berichterstattung
|
||||
|
||||
Das `AnalyticsDashboard` bietet Methoden zur Datenanalyse:
|
||||
|
||||
- `getEventStats()`: Statistiken zu Events
|
||||
- `getTopPages()`: Meistbesuchte Seiten
|
||||
- `getUserStats()`: Benutzerstatistiken
|
||||
- `getErrorStats()`: Fehlerstatistiken
|
||||
- `getPerformanceStats()`: Leistungsmetriken
|
||||
|
||||
Das Admin-Dashboard ist unter `/admin/analytics` verfügbar.
|
||||
|
||||
### Konsolen-Befehle
|
||||
|
||||
```bash
|
||||
# Analytics-Daten löschen
|
||||
php console analytics:clear [--force]
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Service-Container
|
||||
|
||||
Der Analytics-Service wird automatisch registriert und kann per Dependency Injection verwendet werden:
|
||||
|
||||
```php
|
||||
public function __construct(private readonly Analytics $analytics) {}
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Standardmäßig werden folgende Anwendungsereignisse getrackt:
|
||||
|
||||
- Anwendungsstart (`application_booted`)
|
||||
- Fehlerbehandlung (`error_occurred`)
|
||||
- Request-Verarbeitung (`request_started`, `request_completed`)
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Detaillierte Konfigurationsoptionen finden Sie in der [Framework-README](/Framework/Analytics1/README.md).
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Framework-Erweiterungsmuster](/docs/framework/ERWEITERUNGSPATTERN.md)
|
||||
- [Modul-Checkliste](/docs/framework/MODUL-CHECKLISTE.md)
|
||||
@@ -1,160 +0,0 @@
|
||||
# Analytics-Framework: Migrationsleitfaden
|
||||
|
||||
## Von Version 1.x zu 2.x
|
||||
|
||||
### Überblick der Änderungen
|
||||
|
||||
Die Version 2.x des Analytics-Frameworks führt mehrere wichtige Verbesserungen und Änderungen ein:
|
||||
|
||||
- **Typsicherheit**: Neue Event-Klassen statt Arrays
|
||||
- **Dependency Injection**: Verbesserte Integration mit dem Container
|
||||
- **Storage-Abstraktion**: Flexiblere Speichermethoden
|
||||
- **Fehlerbehandlung**: Robustere Exception-Handling
|
||||
- **Middleware-System**: Standardisierte Middleware-Pipeline
|
||||
|
||||
### Notwendige Migrationsschritte
|
||||
|
||||
#### 1. Konfiguration aktualisieren
|
||||
|
||||
**Alt (1.x):**
|
||||
```php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'auto_flush' => true,
|
||||
'batch_size' => 50,
|
||||
'storage' => 'file',
|
||||
'storage_path' => '/pfad/zum/speicher',
|
||||
];
|
||||
```
|
||||
|
||||
**Neu (2.x):**
|
||||
```php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'auto_flush' => true,
|
||||
'batch_size' => 50,
|
||||
'storage_driver' => 'file', // Umbenannt
|
||||
'storage_config' => [ // Neue Struktur
|
||||
'path' => '/pfad/zum/speicher',
|
||||
],
|
||||
'excluded_paths' => [], // Neue Option
|
||||
'excluded_user_agents' => [], // Neue Option
|
||||
'max_file_size' => 10 * 1024 * 1024, // Neue Option
|
||||
];
|
||||
```
|
||||
|
||||
#### 2. Event-Objekte (falls genutzt)
|
||||
|
||||
**Alt (1.x):**
|
||||
```php
|
||||
$analytics->track('custom_event', ['property' => 'value']);
|
||||
```
|
||||
|
||||
**Neu (2.x) - Option 1 (abwärtskompatibel):**
|
||||
```php
|
||||
// Weiterhin unterstützt
|
||||
$analytics->track('custom_event', ['property' => 'value']);
|
||||
```
|
||||
|
||||
**Neu (2.x) - Option 2 (typsicher):**
|
||||
```php
|
||||
use App\Framework\Analytics\Events\CustomEvent;
|
||||
|
||||
$event = new CustomEvent('wert', ['weitere' => 'daten']);
|
||||
$analytics->trackEvent($event);
|
||||
```
|
||||
|
||||
#### 3. Eigene Storage-Provider
|
||||
|
||||
**Alt (1.x):**
|
||||
```php
|
||||
class CustomStorage implements StorageInterface
|
||||
{
|
||||
public function store(array $events): void
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public function retrieve(array $filters = []): array
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Neu (2.x):**
|
||||
```php
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class CustomStorage implements StorageInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $config,
|
||||
private readonly LoggerInterface $logger
|
||||
) {}
|
||||
|
||||
public function store(array $events): void
|
||||
{
|
||||
try {
|
||||
// Implementation mit Fehlerbehandlung
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Storage error', ['exception' => $e]);
|
||||
throw new StorageException('Failed to store events', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Andere Methoden analog
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Middleware anpassen
|
||||
|
||||
**Alt (1.x):**
|
||||
```php
|
||||
$analyticsManager->addMiddleware(function(array $event) {
|
||||
// Verarbeitung
|
||||
return $event;
|
||||
});
|
||||
```
|
||||
|
||||
**Neu (2.x) - Option 1 (abwärtskompatibel):**
|
||||
```php
|
||||
// Weiterhin unterstützt
|
||||
$analyticsManager->addMiddleware(function(array $event) {
|
||||
// Verarbeitung
|
||||
return $event;
|
||||
});
|
||||
```
|
||||
|
||||
**Neu (2.x) - Option 2 (typsicher):**
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
use App\Framework\Analytics\Events\AnalyticsEvent;
|
||||
|
||||
class CustomMiddleware implements AnalyticsMiddleware
|
||||
{
|
||||
public function process(AnalyticsEvent $event): ?AnalyticsEvent
|
||||
{
|
||||
// Verarbeitung
|
||||
return $event;
|
||||
}
|
||||
}
|
||||
|
||||
// Registrierung
|
||||
$analyticsManager->addMiddleware(new CustomMiddleware());
|
||||
```
|
||||
|
||||
### Automatisierte Tests
|
||||
|
||||
Führen Sie die folgenden Tests durch, um sicherzustellen, dass die Migration erfolgreich war:
|
||||
|
||||
```bash
|
||||
php console test:run --group=analytics
|
||||
```
|
||||
|
||||
Weitere Informationen zur Migration finden Sie in den Änderungsprotokollen im Quellcode-Repository.
|
||||
@@ -1,269 +0,0 @@
|
||||
# Analytics-Framework: Anwendungsbeispiele
|
||||
|
||||
## Grundlegende Verwendung
|
||||
|
||||
### Events tracken
|
||||
|
||||
```php
|
||||
// Über Dependency Injection
|
||||
public function __construct(private readonly Analytics $analytics) {}
|
||||
|
||||
// Einfaches Event tracken
|
||||
$this->analytics->track('login_attempt', [
|
||||
'success' => true,
|
||||
'user_type' => 'admin',
|
||||
'method' => 'password'
|
||||
]);
|
||||
|
||||
// Seitenaufruf tracken
|
||||
$this->analytics->page('/produkte/kategorie/elektronik', [
|
||||
'referrer' => 'homepage',
|
||||
'search_query' => 'smartphones'
|
||||
]);
|
||||
|
||||
// Benutzer identifizieren
|
||||
$this->analytics->user($user->getId(), [
|
||||
'email' => $user->getEmail(),
|
||||
'plan' => $user->getSubscriptionPlan(),
|
||||
'registered_since' => $user->getCreatedAt()->format('Y-m-d')
|
||||
]);
|
||||
```
|
||||
|
||||
### Fehler tracken
|
||||
|
||||
```php
|
||||
try {
|
||||
// Fehleranfälliger Code
|
||||
$result = $this->riskyOperation();
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
// Fehler tracken
|
||||
$this->analytics->error($e);
|
||||
|
||||
// Fehler behandeln
|
||||
$this->logger->error($e->getMessage());
|
||||
return $this->fallbackOperation();
|
||||
}
|
||||
```
|
||||
|
||||
### Performance tracken
|
||||
|
||||
```php
|
||||
// Manuelles Performance-Tracking
|
||||
$startTime = microtime(true);
|
||||
$startMemory = memory_get_usage();
|
||||
|
||||
// Operation durchführen
|
||||
$result = $this->heavyOperation();
|
||||
|
||||
// Performance-Metriken tracken
|
||||
$this->analytics->performance([
|
||||
'operation' => 'heavy_operation',
|
||||
'execution_time' => microtime(true) - $startTime,
|
||||
'memory_used' => memory_get_usage() - $startMemory,
|
||||
'result_size' => is_countable($result) ? count($result) : 0
|
||||
]);
|
||||
```
|
||||
|
||||
## Erweiterte Anwendungsfälle
|
||||
|
||||
### Benutzerdefinierte Middleware
|
||||
|
||||
```php
|
||||
// In einem Service Provider oder Initializer
|
||||
public function initialize(AnalyticsManager $manager): void
|
||||
{
|
||||
// DSGVO-Middleware zur Anonymisierung personenbezogener Daten
|
||||
$manager->addMiddleware(function(array $event) {
|
||||
// E-Mail-Adressen anonymisieren
|
||||
if (isset($event['properties']['email'])) {
|
||||
$parts = explode('@', $event['properties']['email']);
|
||||
if (count($parts) === 2) {
|
||||
$event['properties']['email'] = substr($parts[0], 0, 1) .
|
||||
'***@' . $parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Passwörter und Tokens entfernen
|
||||
foreach (['password', 'token', 'api_key', 'secret'] as $key) {
|
||||
if (isset($event['properties'][$key])) {
|
||||
$event['properties'][$key] = '[redacted]';
|
||||
}
|
||||
}
|
||||
|
||||
return $event;
|
||||
});
|
||||
|
||||
// Spam-Filter
|
||||
$manager->addMiddleware(function(array $event) {
|
||||
// Zu viele Events von einem Benutzer filtern
|
||||
static $userCounts = [];
|
||||
$userId = $event['user_id'] ?? $event['session_id'] ?? null;
|
||||
|
||||
if ($userId) {
|
||||
$userCounts[$userId] = ($userCounts[$userId] ?? 0) + 1;
|
||||
|
||||
// Mehr als 100 Events pro Session ist verdächtig
|
||||
if ($userCounts[$userId] > 100) {
|
||||
return null; // Event verwerfen
|
||||
}
|
||||
}
|
||||
|
||||
return $event;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Integration mit dem Domain-Layer
|
||||
|
||||
```php
|
||||
// In einem Domain-Service
|
||||
namespace App\Domain\Shop\Services;
|
||||
|
||||
use App\Framework\Analytics\Analytics;
|
||||
|
||||
class ProductService
|
||||
{
|
||||
public function __construct(private readonly Analytics $analytics) {}
|
||||
|
||||
public function viewProduct(string $productId, ?string $userId): Product
|
||||
{
|
||||
$product = $this->productRepository->find($productId);
|
||||
|
||||
if (!$product) {
|
||||
throw new ProductNotFoundException($productId);
|
||||
}
|
||||
|
||||
// Produktansicht tracken
|
||||
$this->analytics->track('product_view', [
|
||||
'product_id' => $product->getId(),
|
||||
'product_name' => $product->getName(),
|
||||
'product_price' => $product->getPrice(),
|
||||
'product_category' => $product->getCategory()->getName(),
|
||||
'in_stock' => $product->isInStock()
|
||||
], $userId);
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
public function addToCart(string $productId, int $quantity, ?string $userId): Cart
|
||||
{
|
||||
// Implementation...
|
||||
|
||||
// Event tracken
|
||||
$this->analytics->track('add_to_cart', [
|
||||
'product_id' => $productId,
|
||||
'quantity' => $quantity,
|
||||
'cart_value' => $cart->getTotalValue()
|
||||
], $userId);
|
||||
|
||||
return $cart;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verwendung im Controller
|
||||
|
||||
```php
|
||||
namespace App\Application\Controllers;
|
||||
|
||||
use App\Framework\Analytics\Analytics;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\Attributes\Route;
|
||||
|
||||
class CheckoutController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Analytics $analytics,
|
||||
private readonly CheckoutService $checkoutService
|
||||
) {}
|
||||
|
||||
#[Route('/checkout/complete', method: 'POST')]
|
||||
public function completeCheckout(Request $request): Response
|
||||
{
|
||||
$userId = $request->getSession()->get('user_id');
|
||||
$cartId = $request->getSession()->get('cart_id');
|
||||
|
||||
try {
|
||||
$order = $this->checkoutService->completeCheckout($cartId, $userId);
|
||||
|
||||
// Erfolgreichen Checkout tracken
|
||||
$this->analytics->track('checkout_complete', [
|
||||
'order_id' => $order->getId(),
|
||||
'order_value' => $order->getTotalValue(),
|
||||
'items_count' => count($order->getItems()),
|
||||
'payment_method' => $order->getPaymentMethod(),
|
||||
'shipping_method' => $order->getShippingMethod()
|
||||
], $userId);
|
||||
|
||||
return new Response([
|
||||
'success' => true,
|
||||
'order_id' => $order->getId()
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
// Fehler beim Checkout tracken
|
||||
$this->analytics->track('checkout_error', [
|
||||
'error' => $e->getMessage(),
|
||||
'cart_id' => $cartId
|
||||
], $userId);
|
||||
|
||||
// Auch den Exception-Stack tracken
|
||||
$this->analytics->error($e);
|
||||
|
||||
return new Response([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Analyse der Daten
|
||||
|
||||
### Dashboard-Controller
|
||||
|
||||
```php
|
||||
namespace App\Application\Controllers;
|
||||
|
||||
use App\Framework\Analytics\AnalyticsDashboard;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\View\ViewRenderer;
|
||||
use App\Framework\Attributes\Route;
|
||||
|
||||
class AnalyticsDashboardController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsDashboard $dashboard,
|
||||
private readonly ViewRenderer $viewRenderer
|
||||
) {}
|
||||
|
||||
#[Route('/admin/analytics/conversion')]
|
||||
public function conversionReport(Request $request): Response
|
||||
{
|
||||
$from = $request->getQueryParam('from');
|
||||
$to = $request->getQueryParam('to');
|
||||
|
||||
// Benutzerdefinierte Analyse für Conversion-Funnel
|
||||
$pageViews = $this->dashboard->getEventCountByType('page_view', $from, $to);
|
||||
$productViews = $this->dashboard->getEventCountByType('product_view', $from, $to);
|
||||
$addToCarts = $this->dashboard->getEventCountByType('add_to_cart', $from, $to);
|
||||
$checkouts = $this->dashboard->getEventCountByType('checkout_complete', $from, $to);
|
||||
|
||||
// Conversion-Raten berechnen
|
||||
$data = [
|
||||
'total_visitors' => $pageViews,
|
||||
'product_view_rate' => $pageViews > 0 ? $productViews / $pageViews : 0,
|
||||
'add_to_cart_rate' => $productViews > 0 ? $addToCarts / $productViews : 0,
|
||||
'checkout_rate' => $addToCarts > 0 ? $checkouts / $addToCarts : 0,
|
||||
'overall_conversion' => $pageViews > 0 ? $checkouts / $pageViews : 0,
|
||||
'from_date' => $from,
|
||||
'to_date' => $to
|
||||
];
|
||||
|
||||
return $this->viewRenderer->render('admin/analytics/conversion', $data);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,45 +0,0 @@
|
||||
# Core-Modul Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Core-Modul bildet das Herzstück des Frameworks und stellt grundlegende Funktionalitäten bereit, die von anderen Modulen genutzt werden.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Events und EventDispatcher
|
||||
|
||||
Das Event-System ermöglicht die Kommunikation zwischen Komponenten über einen zentralen Event-Bus.
|
||||
|
||||
**Kernklassen:**
|
||||
- `EventDispatcher`: Zentraler Service zum Registrieren und Auslösen von Events
|
||||
- Bekannte Events:
|
||||
- `ApplicationBooted`
|
||||
- `ErrorOccurred`
|
||||
- `BeforeHandleRequest`
|
||||
- `AfterHandleRequest`
|
||||
|
||||
**Beispielverwendung:**
|
||||
```php
|
||||
// Event-Handler registrieren
|
||||
$eventDispatcher->addHandler('App\Framework\Core\Events\ApplicationBooted', function($event) {
|
||||
// Event verarbeiten
|
||||
});
|
||||
```
|
||||
|
||||
### PathProvider
|
||||
|
||||
Stellt Pfadinformationen für verschiedene Bereiche der Anwendung bereit.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- `getDataPath()`: Liefert Pfade zu Datenverzeichnissen
|
||||
|
||||
## Integration mit anderen Modulen
|
||||
|
||||
Das Core-Modul wird von vielen anderen Modulen verwendet, wie z.B.:
|
||||
|
||||
- **Analytics-Modul**: Nutzt den EventDispatcher zum Tracking von Systemereignissen
|
||||
- **DI-Container**: Nutzt Core-Komponenten für die Initialisierung von Services
|
||||
|
||||
## Architektur
|
||||
|
||||
Das Core-Modul folgt einer ereignisgesteuerten Architektur, bei der Komponenten über Events miteinander kommunizieren können, anstatt direkte Abhängigkeiten zu haben.
|
||||
@@ -1,89 +0,0 @@
|
||||
# Dependency Injection Modul
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das DI-Modul implementiert ein Dependency-Injection-Container-System, das die automatische Auflösung und Verwaltung von Abhängigkeiten ermöglicht.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Container
|
||||
|
||||
Der zentrale Service-Container, der Instanzen erstellt und verwaltet.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Service-Erstellung und -Auflösung
|
||||
- Singleton-Verwaltung
|
||||
- Rekursive Abhängigkeitsauflösung
|
||||
|
||||
### Initializer-Attribut
|
||||
|
||||
Das `#[Initializer]`-Attribut kennzeichnet Klassen, die Services im Container registrieren können.
|
||||
|
||||
**Beispiel:**
|
||||
```php
|
||||
#[Initializer]
|
||||
readonly class AnalyticsInitializer
|
||||
{
|
||||
public function __invoke(Container $container): Analytics
|
||||
{
|
||||
// Service erstellen und konfigurieren
|
||||
return $analytics;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Service definieren
|
||||
|
||||
```php
|
||||
// Service-Interface
|
||||
interface MyServiceInterface
|
||||
{
|
||||
public function doSomething(): void;
|
||||
}
|
||||
|
||||
// Konkrete Implementierung
|
||||
class MyService implements MyServiceInterface
|
||||
{
|
||||
public function doSomething(): void
|
||||
{
|
||||
// Implementierung
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service registrieren
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
class MyServiceInitializer
|
||||
{
|
||||
public function __invoke(Container $container): MyServiceInterface
|
||||
{
|
||||
return new MyService();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service verwenden
|
||||
|
||||
```php
|
||||
class MyController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MyServiceInterface $myService
|
||||
) {}
|
||||
|
||||
public function action(): void
|
||||
{
|
||||
$this->myService->doSomething();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Prinzipien
|
||||
|
||||
- **Automatische Auflösung**: Abhängigkeiten werden anhand von Typ-Hints automatisch aufgelöst
|
||||
- **Lazy Loading**: Services werden erst erstellt, wenn sie benötigt werden
|
||||
- **Singleton-Modus**: Standardmäßig werden Services als Singletons verwaltet
|
||||
@@ -1,92 +0,0 @@
|
||||
# HTTP-Modul Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das HTTP-Modul stellt Komponenten für die Verarbeitung von HTTP-Anfragen und -Antworten bereit.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Request
|
||||
|
||||
Repräsentiert eine HTTP-Anfrage mit Methoden zum Zugriff auf:
|
||||
- HTTP-Methode (`getMethod()`)
|
||||
- Pfad (`getPath()`)
|
||||
- Query-Parameter (`getQueryParams()`)
|
||||
- Request-Body
|
||||
- Headers
|
||||
|
||||
### Response
|
||||
|
||||
Repräsentiert eine HTTP-Antwort mit:
|
||||
- Status-Code (`getStatusCode()`)
|
||||
- Headers
|
||||
- Body
|
||||
|
||||
### Middleware
|
||||
|
||||
Ein Interface für HTTP-Middleware-Komponenten, die die Request-Verarbeitung verändern können:
|
||||
|
||||
```php
|
||||
interface Middleware
|
||||
{
|
||||
public function process(Request $request, callable $next): Response;
|
||||
}
|
||||
```
|
||||
|
||||
**Beispiel-Middleware:**
|
||||
```php
|
||||
class AnalyticsMiddleware implements Middleware
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Analytics $analytics
|
||||
) {}
|
||||
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
// Request tracken
|
||||
$this->analytics->track('http_request', [
|
||||
'method' => $request->getMethod(),
|
||||
'path' => $request->getPath(),
|
||||
]);
|
||||
|
||||
// Request weiterleiten
|
||||
$response = $next($request);
|
||||
|
||||
// Response tracken
|
||||
$this->analytics->track('http_response', [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit anderen Modulen
|
||||
|
||||
- **Router**: Routing von HTTP-Requests zu Controllers
|
||||
- **Analytics**: Tracking von HTTP-Requests und -Responses
|
||||
- **Validation**: Validierung von Request-Daten
|
||||
|
||||
## Middlewares registrieren
|
||||
|
||||
In der Anwendungsklasse:
|
||||
|
||||
```php
|
||||
$this->addMiddleware(LoggingMiddleware::class);
|
||||
$this->addMiddleware(AnalyticsMiddleware::class);
|
||||
$this->addMiddleware(AuthenticationMiddleware::class);
|
||||
```
|
||||
|
||||
## Responses erzeugen
|
||||
|
||||
```php
|
||||
// JSON-Response
|
||||
return new JsonResponse(['success' => true]);
|
||||
|
||||
// HTML-Response
|
||||
return new HtmlResponse('<html><body>Hello World</body></html>');
|
||||
|
||||
// Redirect
|
||||
return new RedirectResponse('/dashboard');
|
||||
```
|
||||
@@ -1,42 +0,0 @@
|
||||
# Framework-Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation beschreibt die Architektur, Komponenten und Verwendung des Framework-Kerns des Projekts.
|
||||
|
||||
## Hauptmodule
|
||||
|
||||
- [Analytics](/framework/analytics/README.md) - System zur Erfassung und Analyse von Anwendungsdaten
|
||||
- [Core](/framework/core/README.md) - Kernkomponenten und Event-System
|
||||
- [DI (Dependency Injection)](/framework/di/README.md) - Container und Service-Management
|
||||
- [HTTP](/framework/http/README.md) - HTTP-Request/Response-Handling
|
||||
|
||||
## Richtlinien und Muster
|
||||
|
||||
- [Modul-Checkliste](/framework/MODUL-CHECKLISTE.md) - Leitfaden für die Erstellung neuer Module
|
||||
- [Erweiterungsmuster](/framework/ERWEITERUNGSPATTERN.md) - Muster zur Erweiterung des Frameworks
|
||||
|
||||
## Modulare Architektur
|
||||
|
||||
Das Framework ist modular aufgebaut, mit klaren Verantwortlichkeiten für jedes Modul. Module kommunizieren über klar definierte Interfaces und den Event-Dispatcher.
|
||||
|
||||
### Neues Modul erstellen
|
||||
|
||||
Um ein neues Modul zu erstellen, folgen Sie der [Modul-Checkliste](/framework/MODUL-CHECKLISTE.md) und beachten Sie die folgenden Kernprinzipien:
|
||||
|
||||
1. Klare Verantwortlichkeiten definieren
|
||||
2. Dependency Injection verwenden
|
||||
3. Interface-basiertes Design umsetzen
|
||||
4. Event-basierte Kommunikation nutzen
|
||||
5. Externe Abhängigkeiten minimieren
|
||||
|
||||
### Framework erweitern
|
||||
|
||||
Es gibt verschiedene Möglichkeiten, das Framework zu erweitern:
|
||||
|
||||
1. **Middleware**: HTTP-Request-Pipeline erweitern
|
||||
2. **Event-Listener**: Auf System-Events reagieren
|
||||
3. **Service-Provider**: Eigene Services registrieren
|
||||
4. **Plugin-System**: Umfangreichere Erweiterungen implementieren
|
||||
|
||||
Weitere Details finden Sie im Dokument [Erweiterungsmuster](/framework/ERWEITERUNGSPATTERN.md).
|
||||
214
docs/getting-started/configuration.md
Normal file
214
docs/getting-started/configuration.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Konfiguration
|
||||
|
||||
Diese Anleitung erklärt, wie Sie das Framework nach der Installation konfigurieren können.
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
Die Hauptkonfiguration des Frameworks erfolgt über Umgebungsvariablen, die in der `.env`-Datei definiert sind. Wenn Sie das Framework gerade installiert haben, sollten Sie eine Kopie der `.env.example`-Datei erstellt und in `.env` umbenannt haben.
|
||||
|
||||
### Wichtige Umgebungsvariablen
|
||||
|
||||
Hier sind die wichtigsten Umgebungsvariablen, die Sie konfigurieren sollten:
|
||||
|
||||
#### Anwendung
|
||||
|
||||
```
|
||||
APP_NAME="Meine Anwendung"
|
||||
APP_ENV=development
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost:8000
|
||||
APP_KEY=base64:...
|
||||
```
|
||||
|
||||
- `APP_NAME`: Der Name Ihrer Anwendung
|
||||
- `APP_ENV`: Die Umgebung, in der die Anwendung läuft (`development`, `testing` oder `production`)
|
||||
- `APP_DEBUG`: Ob Debug-Informationen angezeigt werden sollen (`true` oder `false`)
|
||||
- `APP_URL`: Die Basis-URL Ihrer Anwendung
|
||||
- `APP_KEY`: Der Verschlüsselungsschlüssel für Ihre Anwendung (wird automatisch generiert)
|
||||
|
||||
#### Datenbank
|
||||
|
||||
```
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=framework
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
```
|
||||
|
||||
- `DB_CONNECTION`: Der Datenbanktyp (`mysql`, `sqlite`, `pgsql` oder `sqlsrv`)
|
||||
- `DB_HOST`: Der Hostname des Datenbankservers
|
||||
- `DB_PORT`: Der Port des Datenbankservers
|
||||
- `DB_DATABASE`: Der Name der Datenbank
|
||||
- `DB_USERNAME`: Der Benutzername für die Datenbankverbindung
|
||||
- `DB_PASSWORD`: Das Passwort für die Datenbankverbindung
|
||||
|
||||
#### Cache und Sitzungen
|
||||
|
||||
```
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
```
|
||||
|
||||
- `CACHE_DRIVER`: Der Cache-Treiber (`file`, `redis` oder `memory`)
|
||||
- `SESSION_DRIVER`: Der Sitzungstreiber (`file`, `database` oder `redis`)
|
||||
- `SESSION_LIFETIME`: Die Lebensdauer der Sitzung in Minuten
|
||||
|
||||
#### E-Mail
|
||||
|
||||
```
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
- `MAIL_DRIVER`: Der E-Mail-Treiber (`smtp`, `sendmail` oder `log`)
|
||||
- `MAIL_HOST`: Der Hostname des SMTP-Servers
|
||||
- `MAIL_PORT`: Der Port des SMTP-Servers
|
||||
- `MAIL_USERNAME`: Der Benutzername für die SMTP-Verbindung
|
||||
- `MAIL_PASSWORD`: Das Passwort für die SMTP-Verbindung
|
||||
- `MAIL_ENCRYPTION`: Die Verschlüsselungsmethode (`tls` oder `ssl`)
|
||||
- `MAIL_FROM_ADDRESS`: Die Absenderadresse
|
||||
- `MAIL_FROM_NAME`: Der Absendername
|
||||
|
||||
## Konfigurationsdateien
|
||||
|
||||
Neben den Umgebungsvariablen verwendet das Framework auch Konfigurationsdateien im Verzeichnis `config/`. Diese Dateien enthalten Konfigurationen, die nicht über Umgebungsvariablen gesteuert werden sollten oder komplexere Strukturen erfordern.
|
||||
|
||||
### Wichtige Konfigurationsdateien
|
||||
|
||||
#### app.php
|
||||
|
||||
Die Datei `config/app.php` enthält allgemeine Anwendungskonfigurationen:
|
||||
|
||||
```php
|
||||
return [
|
||||
'name' => env('APP_NAME', 'Framework'),
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
'debug' => env('APP_DEBUG', false),
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
'timezone' => 'UTC',
|
||||
'locale' => 'de',
|
||||
'fallback_locale' => 'en',
|
||||
'key' => env('APP_KEY'),
|
||||
'cipher' => 'AES-256-CBC',
|
||||
'providers' => [
|
||||
// Service-Provider
|
||||
],
|
||||
'aliases' => [
|
||||
// Fassaden
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
#### database.php
|
||||
|
||||
Die Datei `config/database.php` enthält Datenbankkonfigurationen:
|
||||
|
||||
```php
|
||||
return [
|
||||
'default' => env('DB_CONNECTION', 'mysql'),
|
||||
'connections' => [
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
],
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
],
|
||||
// Weitere Datenbankverbindungen
|
||||
],
|
||||
'migrations' => 'migrations',
|
||||
'redis' => [
|
||||
'client' => 'predis',
|
||||
'default' => [
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'password' => env('REDIS_PASSWORD', null),
|
||||
'port' => env('REDIS_PORT', 6379),
|
||||
'database' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
#### cache.php
|
||||
|
||||
Die Datei `config/cache.php` enthält Cache-Konfigurationen:
|
||||
|
||||
```php
|
||||
return [
|
||||
'default' => env('CACHE_DRIVER', 'file'),
|
||||
'stores' => [
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache'),
|
||||
],
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
],
|
||||
'memory' => [
|
||||
'driver' => 'memory',
|
||||
],
|
||||
],
|
||||
'prefix' => env('CACHE_PREFIX', 'framework_cache'),
|
||||
];
|
||||
```
|
||||
|
||||
## Benutzerdefinierte Konfiguration
|
||||
|
||||
Sie können auch eigene Konfigurationsdateien erstellen, um anwendungsspezifische Einstellungen zu speichern. Erstellen Sie dazu eine neue Datei im Verzeichnis `config/` und geben Sie ein Array zurück:
|
||||
|
||||
```php
|
||||
// config/myapp.php
|
||||
return [
|
||||
'feature_flags' => [
|
||||
'new_ui' => env('FEATURE_NEW_UI', false),
|
||||
'api_v2' => env('FEATURE_API_V2', false),
|
||||
],
|
||||
'limits' => [
|
||||
'max_items' => 100,
|
||||
'max_file_size' => 10 * 1024 * 1024, // 10 MB
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
Sie können dann auf diese Konfiguration zugreifen mit:
|
||||
|
||||
```php
|
||||
$newUiEnabled = config('myapp.feature_flags.new_ui');
|
||||
$maxItems = config('myapp.limits.max_items');
|
||||
```
|
||||
|
||||
## Umgebungsspezifische Konfiguration
|
||||
|
||||
Das Framework unterstützt umgebungsspezifische Konfigurationen. Sie können verschiedene `.env`-Dateien für verschiedene Umgebungen erstellen:
|
||||
|
||||
- `.env` - Standardkonfiguration
|
||||
- `.env.development` - Entwicklungsumgebung
|
||||
- `.env.testing` - Testumgebung
|
||||
- `.env.production` - Produktionsumgebung
|
||||
|
||||
Um eine bestimmte Umgebungskonfiguration zu laden, setzen Sie die Umgebungsvariable `APP_ENV` auf den entsprechenden Wert.
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
Nachdem Sie das Framework konfiguriert haben, können Sie mit den [ersten Schritten](first-steps.md) fortfahren, um Ihre erste Anwendung zu erstellen.
|
||||
327
docs/getting-started/first-steps.md
Normal file
327
docs/getting-started/first-steps.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# Erste Schritte
|
||||
|
||||
Diese Anleitung führt Sie durch die ersten Schritte mit dem Framework, nachdem Sie es [installiert](installation.md) und [konfiguriert](configuration.md) haben.
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
Zunächst sollten Sie sich mit der Projektstruktur vertraut machen:
|
||||
|
||||
```
|
||||
my-project/
|
||||
├── bin/ # Ausführbare Skripte
|
||||
├── config/ # Konfigurationsdateien
|
||||
├── docs/ # Dokumentation
|
||||
├── public/ # Öffentlich zugängliche Dateien
|
||||
│ ├── index.php # Einstiegspunkt für die Anwendung
|
||||
│ ├── assets/ # Kompilierte Assets (CSS, JS)
|
||||
│ └── uploads/ # Hochgeladene Dateien
|
||||
├── resources/ # Quellressourcen
|
||||
│ ├── css/ # CSS-Dateien
|
||||
│ ├── js/ # JavaScript-Dateien
|
||||
│ └── views/ # Template-Dateien
|
||||
├── src/ # PHP-Quellcode
|
||||
│ ├── Application/ # Anwendungsspezifischer Code
|
||||
│ │ ├── Controllers/ # Controller
|
||||
│ │ ├── Models/ # Modelle
|
||||
│ │ └── Services/ # Services
|
||||
│ └── Framework/ # Framework-Code
|
||||
│ ├── Core/ # Kernkomponenten
|
||||
│ ├── Database/ # Datenbankabstraktion
|
||||
│ └── Http/ # HTTP-Komponenten
|
||||
├── storage/ # Speicher für Anwendungsdaten
|
||||
│ ├── cache/ # Cache-Dateien
|
||||
│ ├── logs/ # Log-Dateien
|
||||
│ └── uploads/ # Hochgeladene Dateien
|
||||
├── tests/ # Testdateien
|
||||
├── vendor/ # Composer-Abhängigkeiten
|
||||
├── .env # Umgebungsvariablen
|
||||
├── composer.json # Composer-Konfiguration
|
||||
├── console.php # Kommandozeilen-Interface
|
||||
└── README.md # Projektdokumentation
|
||||
```
|
||||
|
||||
## Erstellen einer einfachen Seite
|
||||
|
||||
Lassen Sie uns eine einfache Seite erstellen, um zu sehen, wie das Framework funktioniert.
|
||||
|
||||
### 1. Erstellen eines Controllers
|
||||
|
||||
Erstellen Sie eine neue Datei `src/Application/Controllers/WelcomeController.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Application\Controllers;
|
||||
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
|
||||
class WelcomeController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return $this->view('welcome', [
|
||||
'title' => 'Willkommen',
|
||||
'message' => 'Willkommen beim Framework!',
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Erstellen einer View
|
||||
|
||||
Erstellen Sie eine neue Datei `resources/views/welcome.php`:
|
||||
|
||||
```php
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= $title ?></title>
|
||||
<link rel="stylesheet" href="/assets/css/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1><?= $title ?></h1>
|
||||
<p><?= $message ?></p>
|
||||
</div>
|
||||
<script src="/assets/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 3. Definieren einer Route
|
||||
|
||||
Öffnen Sie die Datei `config/routes.php` und fügen Sie eine neue Route hinzu:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use App\Application\Controllers\WelcomeController;
|
||||
use App\Framework\Routing\Router;
|
||||
|
||||
return function (Router $router) {
|
||||
$router->get('/', [WelcomeController::class, 'index']);
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Testen der Anwendung
|
||||
|
||||
Starten Sie den eingebauten Entwicklungsserver:
|
||||
|
||||
```bash
|
||||
php console.php serve
|
||||
```
|
||||
|
||||
Öffnen Sie dann Ihren Browser und navigieren Sie zu `http://localhost:8000`. Sie sollten Ihre neue Seite sehen.
|
||||
|
||||
## Erstellen einer API-Endpunkt
|
||||
|
||||
Lassen Sie uns einen einfachen API-Endpunkt erstellen.
|
||||
|
||||
### 1. Erstellen eines API-Controllers
|
||||
|
||||
Erstellen Sie eine neue Datei `src/Application/Controllers/Api/UserController.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Application\Controllers\Api;
|
||||
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$users = [
|
||||
['id' => 1, 'name' => 'Max Mustermann'],
|
||||
['id' => 2, 'name' => 'Erika Musterfrau'],
|
||||
];
|
||||
|
||||
return $this->json($users);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$users = [
|
||||
1 => ['id' => 1, 'name' => 'Max Mustermann'],
|
||||
2 => ['id' => 2, 'name' => 'Erika Musterfrau'],
|
||||
];
|
||||
|
||||
if (!isset($users[$id])) {
|
||||
return $this->json(['error' => 'Benutzer nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
return $this->json($users[$id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Definieren von API-Routen
|
||||
|
||||
Fügen Sie in der Datei `config/routes.php` die folgenden Routen hinzu:
|
||||
|
||||
```php
|
||||
$router->group('/api', function (Router $router) {
|
||||
$router->get('/users', [UserController::class, 'index']);
|
||||
$router->get('/users/{id}', [UserController::class, 'show']);
|
||||
});
|
||||
```
|
||||
|
||||
Vergessen Sie nicht, den Controller zu importieren:
|
||||
|
||||
```php
|
||||
use App\Application\Controllers\Api\UserController;
|
||||
```
|
||||
|
||||
### 3. Testen der API
|
||||
|
||||
Starten Sie den Entwicklungsserver und testen Sie die API mit einem HTTP-Client wie curl:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/users
|
||||
curl http://localhost:8000/api/users/1
|
||||
```
|
||||
|
||||
## Arbeiten mit der Datenbank
|
||||
|
||||
Das Framework bietet eine einfache Datenbankabstraktion. Hier ist ein Beispiel für die Arbeit mit der Datenbank:
|
||||
|
||||
### 1. Erstellen eines Modells
|
||||
|
||||
Erstellen Sie eine neue Datei `src/Application/Models/User.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Application\Models;
|
||||
|
||||
use App\Framework\Database\Model;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
protected string $table = 'users';
|
||||
protected array $fillable = ['name', 'email', 'password'];
|
||||
protected array $hidden = ['password'];
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Erstellen einer Migration
|
||||
|
||||
Erstellen Sie eine neue Migration mit dem Konsolenbefehl:
|
||||
|
||||
```bash
|
||||
php console.php db:migration:create create_users_table
|
||||
```
|
||||
|
||||
Dies erstellt eine neue Datei in `migrations/`. Bearbeiten Sie die Datei:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use App\Framework\Database\Migration;
|
||||
use App\Framework\Database\Schema\Blueprint;
|
||||
|
||||
class CreateUsersTable extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->schema->create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->schema->dropIfExists('users');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Ausführen der Migration
|
||||
|
||||
Führen Sie die Migration aus:
|
||||
|
||||
```bash
|
||||
php console.php db:migrate
|
||||
```
|
||||
|
||||
### 4. Verwenden des Modells im Controller
|
||||
|
||||
Aktualisieren Sie den `UserController`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Application\Controllers\Api;
|
||||
|
||||
use App\Application\Models\User;
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$users = User::all();
|
||||
return $this->json($users);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json(['error' => 'Benutzer nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
return $this->json($user);
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->input('name'),
|
||||
'email' => $request->input('email'),
|
||||
'password' => password_hash($request->input('password'), PASSWORD_DEFAULT),
|
||||
]);
|
||||
|
||||
return $this->json($user, 201);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fügen Sie eine neue Route hinzu:
|
||||
|
||||
```php
|
||||
$router->post('/api/users', [UserController::class, 'store']);
|
||||
```
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
Nachdem Sie die Grundlagen des Frameworks kennengelernt haben, können Sie sich mit den folgenden Themen befassen:
|
||||
|
||||
- [Routing](../guides/routing.md) - Erfahren Sie mehr über das Routing-System
|
||||
- [Controller](../guides/controllers.md) - Lernen Sie, wie Sie Controller effektiv einsetzen
|
||||
- [Validierung](../guides/validation.md) - Validieren Sie Benutzereingaben
|
||||
- [Datenbank](../guides/database.md) - Arbeiten Sie mit der Datenbank
|
||||
- [Authentifizierung](../guides/authentication.md) - Implementieren Sie Benutzerauthentifizierung
|
||||
- [Sicherheit](../guides/security.md) - Schützen Sie Ihre Anwendung
|
||||
|
||||
Oder erkunden Sie die [Komponenten](../components/README.md) des Frameworks, um mehr über die verfügbaren Funktionen zu erfahren.
|
||||
108
docs/getting-started/installation.md
Normal file
108
docs/getting-started/installation.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Installation
|
||||
|
||||
Diese Anleitung führt Sie durch den Installationsprozess des Frameworks.
|
||||
|
||||
## Systemanforderungen
|
||||
|
||||
Bevor Sie mit der Installation beginnen, stellen Sie sicher, dass Ihr System die folgenden Anforderungen erfüllt:
|
||||
|
||||
- PHP 8.1 oder höher
|
||||
- Composer 2.0 oder höher
|
||||
- Node.js 16 oder höher (für Frontend-Assets)
|
||||
- MySQL 8.0 oder höher (optional, wenn Sie MySQL als Datenbank verwenden)
|
||||
- SQLite 3 (für Entwicklung und Tests)
|
||||
|
||||
## Installation über Composer
|
||||
|
||||
Die einfachste Methode zur Installation ist über Composer:
|
||||
|
||||
```bash
|
||||
composer create-project michaelschiemer/framework my-project
|
||||
cd my-project
|
||||
```
|
||||
|
||||
## Manuelle Installation
|
||||
|
||||
Alternativ können Sie das Framework auch manuell installieren:
|
||||
|
||||
1. Klonen Sie das Repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/michaelschiemer/framework.git my-project
|
||||
cd my-project
|
||||
```
|
||||
|
||||
2. Installieren Sie die Abhängigkeiten:
|
||||
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
```
|
||||
|
||||
## Konfiguration nach der Installation
|
||||
|
||||
Nach der Installation müssen Sie einige grundlegende Konfigurationen vornehmen:
|
||||
|
||||
1. Erstellen Sie eine Kopie der `.env.example`-Datei und benennen Sie sie in `.env` um:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Generieren Sie einen Anwendungsschlüssel:
|
||||
|
||||
```bash
|
||||
php console.php app:key-generate
|
||||
```
|
||||
|
||||
3. Konfigurieren Sie Ihre Datenbankverbindung in der `.env`-Datei.
|
||||
|
||||
4. Führen Sie die Migrationen aus:
|
||||
|
||||
```bash
|
||||
php console.php db:migrate
|
||||
```
|
||||
|
||||
## Überprüfung der Installation
|
||||
|
||||
Um zu überprüfen, ob die Installation erfolgreich war, können Sie den eingebauten Entwicklungsserver starten:
|
||||
|
||||
```bash
|
||||
php console.php serve
|
||||
```
|
||||
|
||||
Öffnen Sie dann Ihren Browser und navigieren Sie zu `http://localhost:8000`. Sie sollten die Startseite des Frameworks sehen.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Composer-Fehler
|
||||
|
||||
Wenn Sie Probleme mit Composer haben, versuchen Sie, den Cache zu leeren:
|
||||
|
||||
```bash
|
||||
composer clear-cache
|
||||
```
|
||||
|
||||
#### Berechtigungsprobleme
|
||||
|
||||
Stellen Sie sicher, dass die folgenden Verzeichnisse für den Webserver beschreibbar sind:
|
||||
|
||||
- `storage/`
|
||||
- `cache/`
|
||||
- `logs/`
|
||||
|
||||
Sie können die Berechtigungen mit dem folgenden Befehl ändern:
|
||||
|
||||
```bash
|
||||
chmod -R 775 storage cache logs
|
||||
```
|
||||
|
||||
#### Datenbank-Verbindungsprobleme
|
||||
|
||||
Überprüfen Sie Ihre Datenbankverbindungseinstellungen in der `.env`-Datei. Stellen Sie sicher, dass der Datenbankbenutzer die richtigen Berechtigungen hat.
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
Nachdem Sie das Framework erfolgreich installiert haben, können Sie mit der [Konfiguration](configuration.md) fortfahren und dann die [ersten Schritte](first-steps.md) unternehmen, um Ihre erste Anwendung zu erstellen.
|
||||
387
docs/graphql-examples.md
Normal file
387
docs/graphql-examples.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# GraphQL Examples
|
||||
|
||||
This framework includes a basic GraphQL implementation with Query and Mutation support.
|
||||
|
||||
## GraphQL Endpoints
|
||||
|
||||
- **POST /graphql** - GraphQL endpoint for queries and mutations
|
||||
- **GET /graphql** - GraphQL Playground (interactive editor)
|
||||
- **GET /graphql/schema** - View schema definition
|
||||
|
||||
## Schema Definition
|
||||
|
||||
The current schema includes:
|
||||
|
||||
```graphql
|
||||
type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
email: String!
|
||||
age: Int
|
||||
active: Boolean!
|
||||
created_at: String!
|
||||
}
|
||||
|
||||
input UserInput {
|
||||
name: String!
|
||||
email: String!
|
||||
age: Int
|
||||
active: Boolean
|
||||
}
|
||||
|
||||
type UserStats {
|
||||
total: Int!
|
||||
active: Int!
|
||||
inactive: Int!
|
||||
average_age: Float!
|
||||
}
|
||||
|
||||
type Query {
|
||||
users(active: Boolean, limit: Int): [User!]!
|
||||
user(id: ID!): User
|
||||
userStats: UserStats!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createUser(input: UserInput!): User!
|
||||
updateUser(id: ID!, input: UserInput!): User
|
||||
deleteUser(id: ID!): Boolean!
|
||||
}
|
||||
```
|
||||
|
||||
## Query Examples
|
||||
|
||||
### Get All Users
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { users { id name email active } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"users": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Jane Smith",
|
||||
"email": "jane@example.com",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "Bob Wilson",
|
||||
"email": "bob@example.com",
|
||||
"active": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Active Users Only
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { users(active: true) { id name email } }"
|
||||
}'
|
||||
```
|
||||
|
||||
### Get Limited Number of Users
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { users(limit: 2) { id name email } }"
|
||||
}'
|
||||
```
|
||||
|
||||
### Get Single User by ID
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { user(id: \"1\") { id name email age active created_at } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"user": {
|
||||
"id": "1",
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"age": 30,
|
||||
"active": true,
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get User Statistics
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { userStats { total active inactive average_age } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"userStats": {
|
||||
"total": 3,
|
||||
"active": 2,
|
||||
"inactive": 1,
|
||||
"average_age": 30.0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mutation Examples
|
||||
|
||||
### Create New User
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "mutation { createUser(input: { name: \"Alice Johnson\", email: \"alice@example.com\", age: 28, active: true }) { id name email age active created_at } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"createUser": {
|
||||
"id": "4",
|
||||
"name": "Alice Johnson",
|
||||
"email": "alice@example.com",
|
||||
"age": 28,
|
||||
"active": true,
|
||||
"created_at": "2024-08-08T10:30:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Update Existing User
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "mutation { updateUser(id: \"1\", input: { name: \"John Smith\", age: 31 }) { id name email age } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"updateUser": {
|
||||
"id": "1",
|
||||
"name": "John Smith",
|
||||
"email": "john@example.com",
|
||||
"age": 31
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delete User
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "mutation { deleteUser(id: \"3\") }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"deleteUser": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Variables
|
||||
|
||||
### Query with Variables
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query GetUser($userId: ID!) { user(id: $userId) { id name email } }",
|
||||
"variables": { "userId": "1" }
|
||||
}'
|
||||
```
|
||||
|
||||
### Mutation with Variables
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "mutation CreateUser($input: UserInput!) { createUser(input: $input) { id name email } }",
|
||||
"variables": {
|
||||
"input": {
|
||||
"name": "Charlie Brown",
|
||||
"email": "charlie@example.com",
|
||||
"age": 22,
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Complex Queries
|
||||
|
||||
### Multiple Queries in One Request
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { users(active: true) { id name email } userStats { total active } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"users": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Jane Smith",
|
||||
"email": "jane@example.com"
|
||||
}
|
||||
],
|
||||
"userStats": {
|
||||
"total": 3,
|
||||
"active": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Aliases
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { activeUsers: users(active: true) { id name } inactiveUsers: users(active: false) { id name } }"
|
||||
}'
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Invalid Query
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { nonexistentField }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Query field 'nonexistentField' not found",
|
||||
"extensions": {
|
||||
"code": "VAL_BUSINESS_RULE_VIOLATION"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### User Not Found
|
||||
|
||||
```bash
|
||||
curl -X POST https://localhost/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
-d '{
|
||||
"query": "query { user(id: \"999\") { id name } }"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"user": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GraphQL Playground
|
||||
|
||||
Visit https://localhost/graphql in your browser to access the interactive GraphQL Playground:
|
||||
|
||||
- **Query Editor**: Write and test GraphQL queries
|
||||
- **Variables Panel**: Define query variables
|
||||
- **Results Panel**: View query results
|
||||
- **Schema Explorer**: Browse available types and fields
|
||||
- **Execute Button**: Run queries with Ctrl+Enter
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Field Selection**: Only request fields you need
|
||||
2. **Use Variables**: For dynamic queries and better caching
|
||||
3. **Handle Errors**: Always check for errors in responses
|
||||
4. **Batch Operations**: Combine multiple queries when possible
|
||||
5. **Type Safety**: Use proper types in mutations
|
||||
6. **Validation**: Validate input data before mutations
|
||||
7. **Caching**: Consider caching strategies for queries
|
||||
@@ -1,169 +0,0 @@
|
||||
# Performance-Richtlinien
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Richtlinien beschreiben Best Practices für die Optimierung der Anwendungsleistung im Framework.
|
||||
|
||||
## Grundprinzipien
|
||||
|
||||
### 1. Frühe Optimierung vermeiden
|
||||
|
||||
- Code zuerst korrekt und lesbar schreiben
|
||||
- Optimierungen auf Basis von Messungen durchführen
|
||||
- Performance-Engpässe mit dem Profiler identifizieren
|
||||
|
||||
### 2. Caching strategisch einsetzen
|
||||
|
||||
- Ergebnisse teurer Operationen cachen
|
||||
- Geeignete Cache-Invalidierungsstrategien implementieren
|
||||
- Cache-Hierarchie nutzen (Memory → Filesystem → Datenbank)
|
||||
|
||||
```php
|
||||
// Beispiel: Strategisches Caching
|
||||
public function getExpensiveData(string $key): array
|
||||
{
|
||||
$cacheKey = "data_{$key}";
|
||||
|
||||
// Prüfen, ob Daten im Cache sind
|
||||
if ($this->cache->has($cacheKey)) {
|
||||
return $this->cache->get($cacheKey);
|
||||
}
|
||||
|
||||
// Teure Operation durchführen
|
||||
$result = $this->performExpensiveOperation($key);
|
||||
|
||||
// Ergebnis cachen mit angemessener TTL
|
||||
$this->cache->set($cacheKey, $result, 3600);
|
||||
|
||||
return $result;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Lazy Loading
|
||||
|
||||
- Ressourcen erst bei Bedarf laden
|
||||
- Schwere Abhängigkeiten verzögert initialisieren
|
||||
|
||||
```php
|
||||
// Beispiel: Lazy Loading
|
||||
private ?ExpensiveService $expensiveService = null;
|
||||
|
||||
private function getExpensiveService(): ExpensiveService
|
||||
{
|
||||
if ($this->expensiveService === null) {
|
||||
$this->expensiveService = new ExpensiveService($this->config);
|
||||
}
|
||||
|
||||
return $this->expensiveService;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Datenstrukturen optimieren
|
||||
|
||||
- Passende Datenstrukturen für den Anwendungsfall wählen
|
||||
- Große Arrays vermeiden, wenn möglich Iteratoren oder Generatoren nutzen
|
||||
- Bei großen Datenmengen paginieren
|
||||
|
||||
```php
|
||||
// Beispiel: Generator statt Array
|
||||
public function processLargeDataset(): Generator
|
||||
{
|
||||
$handle = fopen($this->largeFile, 'r');
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
yield $this->processLine($line);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
```
|
||||
|
||||
## Spezifische Optimierungen
|
||||
|
||||
### Datenbankzugriffe
|
||||
|
||||
- Anzahl der Datenbankabfragen minimieren
|
||||
- Indizes für häufige Abfragen
|
||||
- N+1 Problem vermeiden durch Eager Loading
|
||||
- Datenbankverbindungen poolen
|
||||
|
||||
### HTTP-Anfragen
|
||||
|
||||
- Statische Assets komprimieren und cachen
|
||||
- HTTP/2 nutzen, wenn möglich
|
||||
- Content-Kompression aktivieren
|
||||
- Browser-Caching durch geeignete Header
|
||||
|
||||
### Speichernutzung
|
||||
|
||||
- Große Objekte nach Gebrauch freigeben (`unset()`)
|
||||
- Zirkuläre Referenzen vermeiden
|
||||
- Bei Dateiberarbeitung auf Streaming-Ansätze setzen
|
||||
|
||||
### Autoloading
|
||||
|
||||
- Composer-Autoloader in Produktionsumgebung optimieren
|
||||
- Preloading für häufig verwendete Klassen nutzen
|
||||
|
||||
```php
|
||||
// preload.php Beispiel
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Häufig verwendete Klassen vorladen
|
||||
require_once __DIR__ . '/Framework/Core/Application.php';
|
||||
require_once __DIR__ . '/Framework/DI/Container.php';
|
||||
require_once __DIR__ . '/Framework/Http/Request.php';
|
||||
require_once __DIR__ . '/Framework/Http/Response.php';
|
||||
```
|
||||
|
||||
## Monitoring und Profiling
|
||||
|
||||
### Performance messen
|
||||
|
||||
- Kritische Code-Pfade instrumentieren
|
||||
- End-to-End Zeitmessungen durchführen
|
||||
|
||||
```php
|
||||
// Beispiel: Performance-Messung
|
||||
$startTime = microtime(true);
|
||||
|
||||
// Operation durchführen
|
||||
$result = $this->performOperation();
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$this->logger->debug("Operation ausgeführt in {$duration}s");
|
||||
```
|
||||
|
||||
### Automatisiertes Monitoring
|
||||
|
||||
- Performance-Regressions automatisch erkennen
|
||||
- Regelmäßige Lasttests durchführen
|
||||
- Kritische Pfade überwachen
|
||||
|
||||
## Umgebungsspezifische Optimierungen
|
||||
|
||||
### Entwicklung
|
||||
|
||||
- Debug-Tools aktivieren
|
||||
- Performance-Optimierungen deaktivieren, die Debugging erschweren
|
||||
|
||||
### Produktion
|
||||
|
||||
- Opcache aktivieren und optimieren
|
||||
- Fehlerbehandlung optimieren (keine Stack Traces)
|
||||
- Logging auf nötige Informationen beschränken
|
||||
|
||||
```php
|
||||
// Umgebungsspezifische Einstellungen
|
||||
if ($environment === 'production') {
|
||||
ini_set('opcache.validate_timestamps', 0);
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ERROR | E_WARNING | E_PARSE);
|
||||
} else {
|
||||
ini_set('opcache.validate_timestamps', 1);
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
```
|
||||
@@ -1,181 +0,0 @@
|
||||
# Testing-Richtlinien
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Richtlinien beschreiben die Standards und Best Practices für Tests im Framework.
|
||||
|
||||
## Grundprinzipien
|
||||
|
||||
### 1. Testarten
|
||||
|
||||
- **Unit Tests**: Testen einzelner Komponenten isoliert
|
||||
- **Integrationstests**: Testen des Zusammenspiels mehrerer Komponenten
|
||||
- **Funktionstests**: Testen ganzer Funktionalitäten aus Benutzersicht
|
||||
|
||||
### 2. Pest-Framework
|
||||
|
||||
Alle Tests werden mit dem Pest-Framework geschrieben:
|
||||
|
||||
```php
|
||||
test('Analytics::track zeichnet Events korrekt auf', function () {
|
||||
// Arrange
|
||||
$manager = new AnalyticsManager(['enabled' => true], new InMemoryStorage());
|
||||
$analytics = new Analytics($manager, new EventDispatcher());
|
||||
|
||||
// Act
|
||||
$analytics->track('test_event', ['key' => 'value']);
|
||||
|
||||
// Assert
|
||||
expect($manager->getStorage()->retrieve())->toHaveCount(1);
|
||||
expect($manager->getStorage()->retrieve()[0]['event'])->toBe('test_event');
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Teststruktur
|
||||
|
||||
- Jeder Test folgt dem AAA-Prinzip: Arrange, Act, Assert
|
||||
- Tests sollten unabhängig voneinander sein
|
||||
- Tests sollten deterministisch sein (keine zufälligen Ergebnisse)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Testabdeckung
|
||||
|
||||
- Jede öffentliche Methode sollte getestet werden
|
||||
- Edge Cases und Fehlersituationen explizit testen
|
||||
- Fokus auf Business-Logik und kritische Pfade
|
||||
|
||||
### 2. Mocking
|
||||
|
||||
- Externe Abhängigkeiten mocken
|
||||
- Eigene Test-Implementierungen für Interfaces erstellen
|
||||
- Mock-Objekte nur für die spezifischen Testfälle konfigurieren
|
||||
|
||||
```php
|
||||
// Beispiel: Test mit Mock
|
||||
test('Fehlerbehandlung bei Storage-Ausfall', function () {
|
||||
// Arrange
|
||||
$storage = new class implements StorageInterface {
|
||||
public function store(array $events): void
|
||||
{
|
||||
throw new \RuntimeException('Storage-Fehler');
|
||||
}
|
||||
|
||||
public function retrieve(array $filters = []): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$manager = new AnalyticsManager(['enabled' => true], $storage);
|
||||
|
||||
// Act & Assert
|
||||
expect(fn() => $manager->flush())->toThrow(\RuntimeException::class);
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Fixtures und Factories
|
||||
|
||||
- Testdaten mit Factories erstellen
|
||||
- Komplexe Objekte über Helpers aufbauen
|
||||
- Wiederverwendbare Fixtures für ähnliche Tests
|
||||
|
||||
```php
|
||||
// Beispiel: Test-Factory
|
||||
function createAnalyticsManager(array $config = []): AnalyticsManager
|
||||
{
|
||||
$defaultConfig = ['enabled' => true, 'auto_flush' => false];
|
||||
return new AnalyticsManager(
|
||||
array_merge($defaultConfig, $config),
|
||||
new InMemoryStorage()
|
||||
);
|
||||
}
|
||||
|
||||
test('Auto-Flush funktioniert korrekt', function () {
|
||||
// Arrange
|
||||
$manager = createAnalyticsManager(['auto_flush' => true, 'batch_size' => 2]);
|
||||
|
||||
// Act
|
||||
$manager->track('event1', []);
|
||||
$manager->track('event2', []);
|
||||
|
||||
// Assert
|
||||
expect($manager->getStorage()->retrieve())->toHaveCount(2);
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Datenbankzugriffe
|
||||
|
||||
- In Unit-Tests Datenbankzugriffe vermeiden oder mocken
|
||||
- In Integrationstests separate Testdatenbank verwenden
|
||||
- Testdatenbank nach Tests zurücksetzen
|
||||
|
||||
### 5. Asynchrone Tests
|
||||
|
||||
- Timeouts für asynchrone Tests setzen
|
||||
- Auf Async-Events warten, statt feste Wartezeiten
|
||||
|
||||
## Testorganisation
|
||||
|
||||
### 1. Dateistruktur
|
||||
|
||||
- Tests in `/tests`-Verzeichnis mit gleicher Struktur wie `/src`
|
||||
- Dateinamen mit `Test`-Suffix
|
||||
|
||||
### 2. Testgruppen
|
||||
|
||||
- Tests mit Attributen in Gruppen einteilen
|
||||
- Langsame Tests markieren
|
||||
|
||||
```php
|
||||
#[Group('analytics')]
|
||||
#[Group('slow')]
|
||||
test('große Datenmenge verarbeiten', function () {
|
||||
// Test mit vielen Daten
|
||||
});
|
||||
```
|
||||
|
||||
## CI/CD-Integration
|
||||
|
||||
- Tests in CI-Pipeline automatisiert ausführen
|
||||
- Testabdeckung messen und überwachen
|
||||
- Pull Requests nur mit erfolgreichen Tests mergen
|
||||
|
||||
## Fehlersuche
|
||||
|
||||
### 1. Debugging-Techniken
|
||||
|
||||
- Pest-Debugging aktivieren mit `->dump()`
|
||||
- PHPUnit-Assertions für detaillierte Fehlermeldungen
|
||||
|
||||
```php
|
||||
test('komplexes Objekt validieren', function () {
|
||||
$result = processData();
|
||||
|
||||
// Bei Fehlern Kontext ausgeben
|
||||
if (!expect($result->isValid())->toBeTrue()->isSuccess()) {
|
||||
dump($result->getErrors());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Problematische Tests
|
||||
|
||||
- Flaky Tests identifizieren und beheben
|
||||
- Tests isolieren mit `only` oder `skip`
|
||||
|
||||
```php
|
||||
test('problematischer Test', function () {
|
||||
// Test-Code
|
||||
})->skip('Wird untersucht');
|
||||
```
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- Tests sind ein integraler Bestandteil der Codebase
|
||||
- Qualität und Wartbarkeit der Tests sind genauso wichtig wie die des Produktionscodes
|
||||
- Tests dienen als lebende Dokumentation der Funktionalität
|
||||
@@ -1,43 +0,0 @@
|
||||
# Entwicklungsrichtlinien
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Richtlinien bieten Best Practices und Standards für die Entwicklung innerhalb des Projekts. Sie helfen, qualitativ hochwertigen, wartbaren und leistungsfähigen Code zu schreiben.
|
||||
|
||||
## Verfügbare Richtlinien
|
||||
|
||||
- [Performance-Richtlinien](/guidelines/PERFORMANCE-GUIDELINES.md) - Optimierung der Anwendungsleistung
|
||||
- [Testing-Richtlinien](/guidelines/TESTING-GUIDELINES.md) - Standards und Best Practices für Tests
|
||||
|
||||
## Performance-Optimierung
|
||||
|
||||
Leistungsoptimierung ist ein wichtiger Aspekt der Anwendungsentwicklung. Die [Performance-Richtlinien](/guidelines/PERFORMANCE-GUIDELINES.md) bieten Einblicke in:
|
||||
|
||||
- Strategisches Caching
|
||||
- Lazy Loading
|
||||
- Optimierung von Datenbankabfragen
|
||||
- Effiziente Datenstrukturen
|
||||
- Speichernutzung
|
||||
|
||||
## Teststrategien
|
||||
|
||||
Testen ist ein integraler Bestandteil des Entwicklungsprozesses. Die [Testing-Richtlinien](/guidelines/TESTING-GUIDELINES.md) decken folgende Themen ab:
|
||||
|
||||
- Unit Tests mit Pest-Framework
|
||||
- Integrationstests
|
||||
- Test-Fixtures und Factories
|
||||
- Mocking-Strategien
|
||||
- Testorganisation und -struktur
|
||||
|
||||
## Anwendung der Richtlinien
|
||||
|
||||
Diese Richtlinien sollten in allen Phasen der Entwicklung berücksichtigt werden:
|
||||
|
||||
1. **Planungsphase**: Frühzeitige Berücksichtigung von Performance und Testbarkeit
|
||||
2. **Implementierungsphase**: Anwendung der Best Practices während der Entwicklung
|
||||
3. **Review-Phase**: Überprüfung des Codes anhand der Richtlinien
|
||||
4. **Refactoring**: Verbesserung bestehenden Codes gemäß den Richtlinien
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
Diese Richtlinien werden kontinuierlich verbessert und aktualisiert. Wenn Sie Vorschläge zur Verbesserung haben, zögern Sie nicht, diese einzubringen.
|
||||
727
docs/guides/controllers.md
Normal file
727
docs/guides/controllers.md
Normal file
@@ -0,0 +1,727 @@
|
||||
# Controller-Anleitung
|
||||
|
||||
Diese Anleitung erklärt, wie Controller im Framework funktionieren und wie Sie sie effektiv nutzen können.
|
||||
|
||||
## Was sind Controller?
|
||||
|
||||
Controller sind Klassen, die für die Verarbeitung von HTTP-Anfragen verantwortlich sind. Sie nehmen Anfragen entgegen, führen die erforderliche Geschäftslogik aus und geben Antworten zurück. Controller dienen als Vermittler zwischen den Modellen (Daten und Geschäftslogik) und den Views (Präsentationsschicht).
|
||||
|
||||
## Grundlegende Controller
|
||||
|
||||
### Erstellen eines Controllers
|
||||
|
||||
Controller werden im Verzeichnis `src/Application/Controllers` gespeichert. Ein einfacher Controller sieht wie folgt aus:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Application\Controllers;
|
||||
|
||||
use App\Framework\Http\Controller;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\Response;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return $this->view('users.index', [
|
||||
'users' => User::all()
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->notFound('Benutzer nicht gefunden');
|
||||
}
|
||||
|
||||
return $this->view('users.show', [
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controller-Basisklasse
|
||||
|
||||
Alle Controller sollten von der `App\Framework\Http\Controller`-Klasse erben, die nützliche Methoden für die Erstellung von Antworten bereitstellt:
|
||||
|
||||
```php
|
||||
// Gibt eine View zurück
|
||||
$this->view('users.index', ['users' => $users]);
|
||||
|
||||
// Gibt JSON zurück
|
||||
$this->json(['name' => 'John', 'email' => 'john@example.com']);
|
||||
|
||||
// Leitet zu einer anderen URL um
|
||||
$this->redirect('/users');
|
||||
|
||||
// Leitet zurück zur vorherigen Seite um
|
||||
$this->back();
|
||||
|
||||
// Gibt einen Fehler zurück
|
||||
$this->error('Ein Fehler ist aufgetreten', 500);
|
||||
|
||||
// Gibt einen 404-Fehler zurück
|
||||
$this->notFound('Seite nicht gefunden');
|
||||
|
||||
// Gibt einen 403-Fehler zurück
|
||||
$this->forbidden('Zugriff verweigert');
|
||||
|
||||
// Gibt eine leere Antwort zurück
|
||||
$this->noContent();
|
||||
```
|
||||
|
||||
## Anfragen verarbeiten
|
||||
|
||||
### Zugriff auf Anfragedaten
|
||||
|
||||
Sie können auf die Daten einer Anfrage über das `Request`-Objekt zugreifen:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
// Zugriff auf Formulardaten
|
||||
$name = $request->input('name');
|
||||
$email = $request->input('email');
|
||||
|
||||
// Zugriff auf Formulardaten mit Standardwert
|
||||
$age = $request->input('age', 18);
|
||||
|
||||
// Prüfen, ob ein Feld vorhanden ist
|
||||
if ($request->has('address')) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Zugriff auf alle Formulardaten
|
||||
$data = $request->all();
|
||||
|
||||
// Zugriff auf bestimmte Formulardaten
|
||||
$userData = $request->only(['name', 'email']);
|
||||
$nonUserData = $request->except(['name', 'email']);
|
||||
|
||||
// Zugriff auf URL-Parameter
|
||||
$page = $request->query('page', 1);
|
||||
|
||||
// Zugriff auf Dateien
|
||||
$file = $request->file('avatar');
|
||||
|
||||
// Zugriff auf Header
|
||||
$token = $request->header('Authorization');
|
||||
|
||||
// Zugriff auf Cookies
|
||||
$remember = $request->cookie('remember', false);
|
||||
|
||||
// Zugriff auf die Anfragemethode
|
||||
$method = $request->method();
|
||||
|
||||
// Prüfen, ob die Anfrage eine AJAX-Anfrage ist
|
||||
if ($request->isAjax()) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Prüfen, ob die Anfrage eine bestimmte Methode verwendet
|
||||
if ($request->isMethod('POST')) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Zugriff auf die Anfrage-URL
|
||||
$url = $request->url();
|
||||
|
||||
// Zugriff auf die vollständige Anfrage-URL mit Abfrageparametern
|
||||
$fullUrl = $request->fullUrl();
|
||||
|
||||
// Zugriff auf den Pfad der Anfrage
|
||||
$path = $request->path();
|
||||
}
|
||||
```
|
||||
|
||||
### Validierung von Anfragedaten
|
||||
|
||||
Sie können Anfragedaten direkt im Controller validieren:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
]);
|
||||
|
||||
// Die Validierung wurde bestanden, fahren Sie mit der Speicherung fort
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
```
|
||||
|
||||
Wenn die Validierung fehlschlägt, wird automatisch eine Antwort mit den Validierungsfehlern zurückgegeben. Bei einer regulären Anfrage wird der Benutzer zur vorherigen Seite umgeleitet, bei einer AJAX-Anfrage wird eine JSON-Antwort mit den Fehlern zurückgegeben.
|
||||
|
||||
Weitere Informationen zur Validierung finden Sie in der [Validierungs-Anleitung](validation.md).
|
||||
|
||||
## Antworten erstellen
|
||||
|
||||
### HTML-Antworten
|
||||
|
||||
Um eine HTML-Antwort zu erstellen, verwenden Sie die `view`-Methode:
|
||||
|
||||
```php
|
||||
public function index(): Response
|
||||
{
|
||||
$users = User::all();
|
||||
|
||||
return $this->view('users.index', [
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
Die `view`-Methode akzeptiert den Namen der View und ein Array mit Daten, die an die View übergeben werden sollen. Der Name der View entspricht dem Pfad der View-Datei relativ zum Verzeichnis `resources/views`, wobei Punkte als Verzeichnistrennzeichen verwendet werden. Im obigen Beispiel wird die Datei `resources/views/users/index.php` gerendert.
|
||||
|
||||
### JSON-Antworten
|
||||
|
||||
Um eine JSON-Antwort zu erstellen, verwenden Sie die `json`-Methode:
|
||||
|
||||
```php
|
||||
public function index(): Response
|
||||
{
|
||||
$users = User::all();
|
||||
|
||||
return $this->json([
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
Die `json`-Methode akzeptiert ein Array oder ein Objekt, das in JSON konvertiert werden soll, und einen optionalen HTTP-Statuscode:
|
||||
|
||||
```php
|
||||
return $this->json(['error' => 'Nicht gefunden'], 404);
|
||||
```
|
||||
|
||||
### Weiterleitungen
|
||||
|
||||
Um eine Weiterleitung zu erstellen, verwenden Sie die `redirect`-Methode:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
// Benutzer erstellen
|
||||
|
||||
return $this->redirect('/users');
|
||||
}
|
||||
```
|
||||
|
||||
Sie können auch zur vorherigen Seite umleiten:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
// Validierungsfehler
|
||||
|
||||
return $this->back();
|
||||
}
|
||||
```
|
||||
|
||||
Sie können Flash-Daten mit der Weiterleitung senden:
|
||||
|
||||
```php
|
||||
return $this->redirect('/users')
|
||||
->with('success', 'Benutzer erstellt');
|
||||
```
|
||||
|
||||
Diese Flash-Daten sind in der nächsten Anfrage über die Session verfügbar:
|
||||
|
||||
```php
|
||||
$message = $request->session()->get('success');
|
||||
```
|
||||
|
||||
### Fehlerantworten
|
||||
|
||||
Um eine Fehlerantwort zu erstellen, verwenden Sie eine der Fehlermethoden:
|
||||
|
||||
```php
|
||||
// 404 Not Found
|
||||
return $this->notFound('Benutzer nicht gefunden');
|
||||
|
||||
// 403 Forbidden
|
||||
return $this->forbidden('Sie haben keine Berechtigung, diesen Benutzer zu bearbeiten');
|
||||
|
||||
// 401 Unauthorized
|
||||
return $this->unauthorized('Bitte melden Sie sich an');
|
||||
|
||||
// 400 Bad Request
|
||||
return $this->badRequest('Ungültige Anfrage');
|
||||
|
||||
// 500 Internal Server Error
|
||||
return $this->error('Ein Fehler ist aufgetreten');
|
||||
```
|
||||
|
||||
### Datei-Downloads
|
||||
|
||||
Um eine Datei zum Download anzubieten, verwenden Sie die `download`-Methode:
|
||||
|
||||
```php
|
||||
public function download(int $id): Response
|
||||
{
|
||||
$file = File::find($id);
|
||||
|
||||
return $this->download($file->path, $file->name);
|
||||
}
|
||||
```
|
||||
|
||||
### Datei-Streams
|
||||
|
||||
Um eine Datei zu streamen, verwenden Sie die `stream`-Methode:
|
||||
|
||||
```php
|
||||
public function stream(int $id): Response
|
||||
{
|
||||
$video = Video::find($id);
|
||||
|
||||
return $this->stream($video->path, $video->mime_type);
|
||||
}
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
Das Framework unterstützt Dependency Injection in Controller-Konstruktoren und -Methoden:
|
||||
|
||||
```php
|
||||
class UserController extends Controller
|
||||
{
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(UserRepository $userRepository)
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
public function index(Request $request, Logger $logger): Response
|
||||
{
|
||||
$logger->info('Benutzerindex aufgerufen');
|
||||
|
||||
$users = $this->userRepository->all();
|
||||
|
||||
return $this->view('users.index', [
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
Sie können Middleware auf Controller-Ebene anwenden, indem Sie die `middleware`-Methode im Konstruktor aufrufen:
|
||||
|
||||
```php
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(AuthMiddleware::class);
|
||||
$this->middleware(AdminMiddleware::class);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Sie können die Middleware auch auf bestimmte Methoden beschränken:
|
||||
|
||||
```php
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(AuthMiddleware::class)->only(['index', 'show']);
|
||||
$this->middleware(AdminMiddleware::class)->except(['index', 'show']);
|
||||
}
|
||||
```
|
||||
|
||||
## Ressourcen-Controller
|
||||
|
||||
Ressourcen-Controller bieten eine bequeme Möglichkeit, CRUD-Operationen für eine Ressource zu implementieren. Ein Ressourcen-Controller enthält die folgenden Methoden:
|
||||
|
||||
- `index`: Zeigt eine Liste aller Ressourcen an
|
||||
- `create`: Zeigt ein Formular zum Erstellen einer neuen Ressource an
|
||||
- `store`: Speichert eine neu erstellte Ressource
|
||||
- `show`: Zeigt eine bestimmte Ressource an
|
||||
- `edit`: Zeigt ein Formular zum Bearbeiten einer Ressource an
|
||||
- `update`: Aktualisiert eine bestimmte Ressource
|
||||
- `destroy`: Löscht eine bestimmte Ressource
|
||||
|
||||
Hier ist ein Beispiel für einen Ressourcen-Controller:
|
||||
|
||||
```php
|
||||
class PhotoController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
$photos = Photo::all();
|
||||
|
||||
return $this->view('photos.index', [
|
||||
'photos' => $photos
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
return $this->view('photos.create');
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'title' => 'required|string|max:255',
|
||||
'image' => 'required|image|max:2048',
|
||||
]);
|
||||
|
||||
$photo = new Photo();
|
||||
$photo->title = $request->input('title');
|
||||
$photo->path = $request->file('image')->store('photos');
|
||||
$photo->save();
|
||||
|
||||
return $this->redirect('/photos')->with('success', 'Foto hochgeladen');
|
||||
}
|
||||
|
||||
public function show(int $id): Response
|
||||
{
|
||||
$photo = Photo::find($id);
|
||||
|
||||
if (!$photo) {
|
||||
return $this->notFound('Foto nicht gefunden');
|
||||
}
|
||||
|
||||
return $this->view('photos.show', [
|
||||
'photo' => $photo
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(int $id): Response
|
||||
{
|
||||
$photo = Photo::find($id);
|
||||
|
||||
if (!$photo) {
|
||||
return $this->notFound('Foto nicht gefunden');
|
||||
}
|
||||
|
||||
return $this->view('photos.edit', [
|
||||
'photo' => $photo
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$photo = Photo::find($id);
|
||||
|
||||
if (!$photo) {
|
||||
return $this->notFound('Foto nicht gefunden');
|
||||
}
|
||||
|
||||
$this->validate($request, [
|
||||
'title' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
$photo->title = $request->input('title');
|
||||
$photo->save();
|
||||
|
||||
return $this->redirect('/photos')->with('success', 'Foto aktualisiert');
|
||||
}
|
||||
|
||||
public function destroy(int $id): Response
|
||||
{
|
||||
$photo = Photo::find($id);
|
||||
|
||||
if (!$photo) {
|
||||
return $this->notFound('Foto nicht gefunden');
|
||||
}
|
||||
|
||||
$photo->delete();
|
||||
|
||||
return $this->redirect('/photos')->with('success', 'Foto gelöscht');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Um einen Ressourcen-Controller zu registrieren, verwenden Sie die `resource`-Methode des Routers:
|
||||
|
||||
```php
|
||||
$router->resource('photos', PhotoController::class);
|
||||
```
|
||||
|
||||
## API-Controller
|
||||
|
||||
API-Controller sind ähnlich wie reguläre Controller, geben jedoch in der Regel JSON-Antworten zurück:
|
||||
|
||||
```php
|
||||
class ApiUserController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
$users = User::all();
|
||||
|
||||
return $this->json([
|
||||
'data' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'data' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer erstellt'
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$this->validate($request, [
|
||||
'name' => 'string|max:255',
|
||||
'email' => 'email|unique:users,email,' . $id,
|
||||
]);
|
||||
|
||||
$user->fill($request->only(['name', 'email']));
|
||||
$user->save();
|
||||
|
||||
return $this->json([
|
||||
'data' => $user,
|
||||
'message' => 'Benutzer aktualisiert'
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Benutzer nicht gefunden'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
|
||||
return $this->json([
|
||||
'message' => 'Benutzer gelöscht'
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Single Action Controller
|
||||
|
||||
Wenn ein Controller nur eine einzige Aktion ausführt, können Sie einen Single Action Controller erstellen, der die `__invoke`-Methode implementiert:
|
||||
|
||||
```php
|
||||
class ShowDashboardController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$stats = [
|
||||
'users' => User::count(),
|
||||
'posts' => Post::count(),
|
||||
'comments' => Comment::count(),
|
||||
];
|
||||
|
||||
return $this->view('dashboard', [
|
||||
'stats' => $stats
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Um einen Single Action Controller zu registrieren, geben Sie einfach die Controller-Klasse an:
|
||||
|
||||
```php
|
||||
$router->get('/dashboard', ShowDashboardController::class);
|
||||
```
|
||||
|
||||
## Controller-Organisation
|
||||
|
||||
### Namensräume
|
||||
|
||||
Sie können Controller in Namensräumen organisieren, um sie besser zu strukturieren:
|
||||
|
||||
```php
|
||||
namespace App\Application\Controllers\Admin;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Controller-Gruppen
|
||||
|
||||
Sie können Controller in Gruppen organisieren, indem Sie Routengruppen mit einem Namensraum verwenden:
|
||||
|
||||
```php
|
||||
$router->group('/admin', function (Router $router) {
|
||||
$router->get('/users', [UserController::class, 'index']);
|
||||
$router->get('/posts', [PostController::class, 'index']);
|
||||
})->namespace('App\\Application\\Controllers\\Admin');
|
||||
```
|
||||
|
||||
## Beste Praktiken
|
||||
|
||||
### Schlanke Controller
|
||||
|
||||
Controller sollten schlank sein und sich auf die Verarbeitung von HTTP-Anfragen konzentrieren. Komplexe Geschäftslogik sollte in Service-Klassen oder Modelle ausgelagert werden:
|
||||
|
||||
```php
|
||||
class UserController extends Controller
|
||||
{
|
||||
private UserService $userService;
|
||||
|
||||
public function __construct(UserService $userService)
|
||||
{
|
||||
$this->userService = $userService;
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
|
||||
$user = $this->userService->createUser(
|
||||
$request->input('name'),
|
||||
$request->input('email'),
|
||||
$request->input('password')
|
||||
);
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Wiederverwendung von Validierungsregeln
|
||||
|
||||
Wenn Sie dieselben Validierungsregeln in mehreren Controllern verwenden, sollten Sie sie in eine separate Klasse auslagern:
|
||||
|
||||
```php
|
||||
class UserValidationRules
|
||||
{
|
||||
public static function forCreation(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
];
|
||||
}
|
||||
|
||||
public static function forUpdate(int $userId): array
|
||||
{
|
||||
return [
|
||||
'name' => 'string|max:255',
|
||||
'email' => 'email|unique:users,email,' . $userId,
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dann können Sie diese Regeln in Ihren Controllern verwenden:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, UserValidationRules::forCreation());
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$this->validate($request, UserValidationRules::forUpdate($id));
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Verwendung von Form Requests
|
||||
|
||||
Für komplexe Validierungslogik können Sie Form Request-Klassen erstellen:
|
||||
|
||||
```php
|
||||
class CreateUserRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Der Name ist erforderlich',
|
||||
'email.unique' => 'Diese E-Mail-Adresse wird bereits verwendet',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()->hasPermission('create-users');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dann können Sie die Form Request-Klasse in Ihrem Controller verwenden:
|
||||
|
||||
```php
|
||||
public function store(CreateUserRequest $request): Response
|
||||
{
|
||||
// Die Validierung wurde bereits durchgeführt
|
||||
|
||||
$user = User::create($request->validated());
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
```
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Routing-Anleitung](routing.md): Erfahren Sie mehr über das Routing-System und wie es mit Controllern interagiert.
|
||||
- [Validierungs-Anleitung](validation.md): Erfahren Sie mehr über die Validierung von Benutzereingaben.
|
||||
- [Middleware-Anleitung](middleware.md): Erfahren Sie mehr über Middleware und wie sie mit Controllern interagiert.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
370
docs/guides/routing.md
Normal file
370
docs/guides/routing.md
Normal file
@@ -0,0 +1,370 @@
|
||||
# Routing-Anleitung
|
||||
|
||||
Diese Anleitung erklärt, wie das Routing-System des Frameworks funktioniert und wie Sie es effektiv nutzen können.
|
||||
|
||||
## Grundlagen des Routings
|
||||
|
||||
Das Routing-System ist verantwortlich für das Mapping von HTTP-Anfragen zu Controller-Aktionen. Es ermöglicht Ihnen, URLs zu definieren und festzulegen, welcher Code ausgeführt werden soll, wenn diese URLs aufgerufen werden.
|
||||
|
||||
### Routing-Konfiguration
|
||||
|
||||
Die Routing-Konfiguration befindet sich in der Datei `config/routes.php`. Diese Datei gibt eine Funktion zurück, die einen Router als Parameter erhält:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use App\Framework\Routing\Router;
|
||||
use App\Application\Controllers\HomeController;
|
||||
use App\Application\Controllers\UserController;
|
||||
|
||||
return function (Router $router) {
|
||||
// Hier werden Routen definiert
|
||||
$router->get('/', [HomeController::class, 'index']);
|
||||
$router->get('/users', [UserController::class, 'index']);
|
||||
$router->get('/users/{id}', [UserController::class, 'show']);
|
||||
};
|
||||
```
|
||||
|
||||
## Definieren von Routen
|
||||
|
||||
### Grundlegende Routen
|
||||
|
||||
Sie können Routen für verschiedene HTTP-Methoden definieren:
|
||||
|
||||
```php
|
||||
// GET-Route
|
||||
$router->get('/users', [UserController::class, 'index']);
|
||||
|
||||
// POST-Route
|
||||
$router->post('/users', [UserController::class, 'store']);
|
||||
|
||||
// PUT-Route
|
||||
$router->put('/users/{id}', [UserController::class, 'update']);
|
||||
|
||||
// PATCH-Route
|
||||
$router->patch('/users/{id}', [UserController::class, 'update']);
|
||||
|
||||
// DELETE-Route
|
||||
$router->delete('/users/{id}', [UserController::class, 'destroy']);
|
||||
|
||||
// Mehrere HTTP-Methoden
|
||||
$router->match(['GET', 'POST'], '/users/search', [UserController::class, 'search']);
|
||||
|
||||
// Alle HTTP-Methoden
|
||||
$router->any('/users/all', [UserController::class, 'all']);
|
||||
```
|
||||
|
||||
### Routenparameter
|
||||
|
||||
Sie können dynamische Segmente in Ihren Routen definieren, indem Sie geschweifte Klammern verwenden:
|
||||
|
||||
```php
|
||||
$router->get('/users/{id}', [UserController::class, 'show']);
|
||||
$router->get('/posts/{post}/comments/{comment}', [CommentController::class, 'show']);
|
||||
```
|
||||
|
||||
Diese Parameter werden automatisch an die Controller-Methode übergeben:
|
||||
|
||||
```php
|
||||
public function show(Request $request, int $id): Response
|
||||
{
|
||||
$user = User::find($id);
|
||||
// ...
|
||||
}
|
||||
|
||||
public function show(Request $request, int $post, int $comment): Response
|
||||
{
|
||||
$post = Post::find($post);
|
||||
$comment = Comment::find($comment);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Optionale Parameter
|
||||
|
||||
Sie können optionale Parameter definieren, indem Sie ein Fragezeichen nach dem Parameternamen hinzufügen:
|
||||
|
||||
```php
|
||||
$router->get('/users/{id?}', [UserController::class, 'show']);
|
||||
```
|
||||
|
||||
In diesem Fall müssen Sie in Ihrer Controller-Methode einen Standardwert für den Parameter angeben:
|
||||
|
||||
```php
|
||||
public function show(Request $request, int $id = null): Response
|
||||
{
|
||||
if ($id === null) {
|
||||
// Alle Benutzer anzeigen
|
||||
} else {
|
||||
// Einen bestimmten Benutzer anzeigen
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Parametereinschränkungen
|
||||
|
||||
Sie können Einschränkungen für Routenparameter definieren, um sicherzustellen, dass sie einem bestimmten Muster entsprechen:
|
||||
|
||||
```php
|
||||
$router->get('/users/{id}', [UserController::class, 'show'])
|
||||
->where('id', '[0-9]+');
|
||||
|
||||
$router->get('/posts/{slug}', [PostController::class, 'show'])
|
||||
->where('slug', '[a-z0-9-]+');
|
||||
|
||||
$router->get('/categories/{category}/posts/{post}', [PostController::class, 'showInCategory'])
|
||||
->where(['category' => '[a-z0-9-]+', 'post' => '[0-9]+']);
|
||||
```
|
||||
|
||||
### Benannte Routen
|
||||
|
||||
Sie können Routen benennen, um später einfacher auf sie verweisen zu können:
|
||||
|
||||
```php
|
||||
$router->get('/users/{id}', [UserController::class, 'show'])
|
||||
->name('users.show');
|
||||
```
|
||||
|
||||
Sie können dann URLs für benannte Routen generieren:
|
||||
|
||||
```php
|
||||
$url = route('users.show', ['id' => 1]); // /users/1
|
||||
```
|
||||
|
||||
## Routengruppen
|
||||
|
||||
Routengruppen ermöglichen es Ihnen, gemeinsame Attribute auf mehrere Routen anzuwenden, ohne sie für jede Route einzeln definieren zu müssen.
|
||||
|
||||
### Präfixe
|
||||
|
||||
Sie können ein Präfix für eine Gruppe von Routen definieren:
|
||||
|
||||
```php
|
||||
$router->group('/admin', function (Router $router) {
|
||||
$router->get('/dashboard', [AdminController::class, 'dashboard']);
|
||||
$router->get('/users', [AdminController::class, 'users']);
|
||||
$router->get('/settings', [AdminController::class, 'settings']);
|
||||
});
|
||||
```
|
||||
|
||||
Dies definiert die folgenden Routen:
|
||||
- `/admin/dashboard`
|
||||
- `/admin/users`
|
||||
- `/admin/settings`
|
||||
|
||||
### Middleware
|
||||
|
||||
Sie können Middleware für eine Gruppe von Routen definieren:
|
||||
|
||||
```php
|
||||
$router->group('', function (Router $router) {
|
||||
$router->get('/dashboard', [AdminController::class, 'dashboard']);
|
||||
$router->get('/users', [AdminController::class, 'users']);
|
||||
$router->get('/settings', [AdminController::class, 'settings']);
|
||||
})->middleware(AuthMiddleware::class);
|
||||
```
|
||||
|
||||
Sie können auch mehrere Middleware-Klassen angeben:
|
||||
|
||||
```php
|
||||
$router->group('', function (Router $router) {
|
||||
// Routen
|
||||
})->middleware([AuthMiddleware::class, AdminMiddleware::class]);
|
||||
```
|
||||
|
||||
### Namensräume
|
||||
|
||||
Sie können einen Namensraum für eine Gruppe von Routen definieren:
|
||||
|
||||
```php
|
||||
$router->group('', function (Router $router) {
|
||||
$router->get('/users', ['UserController', 'index']);
|
||||
$router->get('/posts', ['PostController', 'index']);
|
||||
})->namespace('App\\Application\\Controllers\\Admin');
|
||||
```
|
||||
|
||||
In diesem Fall werden die Controller-Klassen im Namensraum `App\Application\Controllers\Admin` gesucht, also `App\Application\Controllers\Admin\UserController` und `App\Application\Controllers\Admin\PostController`.
|
||||
|
||||
### Kombinierte Attribute
|
||||
|
||||
Sie können mehrere Attribute für eine Gruppe von Routen kombinieren:
|
||||
|
||||
```php
|
||||
$router->group('/admin', function (Router $router) {
|
||||
$router->get('/dashboard', ['DashboardController', 'index']);
|
||||
$router->get('/users', ['UserController', 'index']);
|
||||
})
|
||||
->namespace('App\\Application\\Controllers\\Admin')
|
||||
->middleware([AuthMiddleware::class, AdminMiddleware::class])
|
||||
->name('admin.');
|
||||
```
|
||||
|
||||
Dies definiert die folgenden Routen:
|
||||
- `/admin/dashboard` mit dem Namen `admin.dashboard`
|
||||
- `/admin/users` mit dem Namen `admin.users`
|
||||
|
||||
Beide Routen verwenden die Controller im Namensraum `App\Application\Controllers\Admin` und durchlaufen die `AuthMiddleware` und `AdminMiddleware`.
|
||||
|
||||
## Verschachtelte Gruppen
|
||||
|
||||
Sie können Routengruppen verschachteln, um komplexere Strukturen zu erstellen:
|
||||
|
||||
```php
|
||||
$router->group('/admin', function (Router $router) {
|
||||
$router->get('/dashboard', [AdminController::class, 'dashboard']);
|
||||
|
||||
$router->group('/users', function (Router $router) {
|
||||
$router->get('/', [AdminUserController::class, 'index']);
|
||||
$router->get('/{id}', [AdminUserController::class, 'show']);
|
||||
$router->put('/{id}', [AdminUserController::class, 'update']);
|
||||
$router->delete('/{id}', [AdminUserController::class, 'destroy']);
|
||||
});
|
||||
|
||||
$router->group('/settings', function (Router $router) {
|
||||
$router->get('/', [AdminSettingsController::class, 'index']);
|
||||
$router->put('/general', [AdminSettingsController::class, 'updateGeneral']);
|
||||
$router->put('/security', [AdminSettingsController::class, 'updateSecurity']);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Fallback-Routen
|
||||
|
||||
Sie können eine Fallback-Route definieren, die verwendet wird, wenn keine andere Route übereinstimmt:
|
||||
|
||||
```php
|
||||
$router->fallback([NotFoundController::class, 'index']);
|
||||
```
|
||||
|
||||
## Middleware für einzelne Routen
|
||||
|
||||
Sie können Middleware auch für einzelne Routen definieren:
|
||||
|
||||
```php
|
||||
$router->get('/profile', [ProfileController::class, 'index'])
|
||||
->middleware(AuthMiddleware::class);
|
||||
|
||||
$router->get('/admin/dashboard', [AdminController::class, 'dashboard'])
|
||||
->middleware([AuthMiddleware::class, AdminMiddleware::class]);
|
||||
```
|
||||
|
||||
## Routen-Caching
|
||||
|
||||
Um die Leistung zu verbessern, können Sie die Routing-Konfiguration cachen:
|
||||
|
||||
```bash
|
||||
php console.php route:cache
|
||||
```
|
||||
|
||||
Dies erstellt eine Cache-Datei, die vom Framework verwendet wird, anstatt die Routen bei jeder Anfrage neu zu analysieren.
|
||||
|
||||
Um den Cache zu löschen:
|
||||
|
||||
```bash
|
||||
php console.php route:clear
|
||||
```
|
||||
|
||||
## Routen auflisten
|
||||
|
||||
Sie können alle registrierten Routen auflisten:
|
||||
|
||||
```bash
|
||||
php console.php route:list
|
||||
```
|
||||
|
||||
Dies zeigt eine Tabelle mit allen Routen, ihren HTTP-Methoden, URLs, Controller-Aktionen und Namen an.
|
||||
|
||||
## Routen-Modell-Binding
|
||||
|
||||
Das Framework unterstützt Routen-Modell-Binding, das automatisch Modellinstanzen aus Routenparametern auflöst:
|
||||
|
||||
```php
|
||||
$router->get('/users/{user}', [UserController::class, 'show']);
|
||||
```
|
||||
|
||||
In Ihrem Controller:
|
||||
|
||||
```php
|
||||
public function show(Request $request, User $user): Response
|
||||
{
|
||||
// $user ist bereits eine Instanz des User-Modells
|
||||
return $this->view('users.show', ['user' => $user]);
|
||||
}
|
||||
```
|
||||
|
||||
Standardmäßig wird der Parameter `{user}` als ID verwendet, um das Modell zu finden. Sie können dieses Verhalten anpassen, indem Sie die Methode `resolveRouteBinding` in Ihrem Modell überschreiben:
|
||||
|
||||
```php
|
||||
public function resolveRouteBinding(string $value): ?static
|
||||
{
|
||||
return static::where('username', $value)->first();
|
||||
}
|
||||
```
|
||||
|
||||
## Ressourcen-Routen
|
||||
|
||||
Das Framework bietet eine Möglichkeit, schnell Ressourcen-Routen für CRUD-Operationen zu definieren:
|
||||
|
||||
```php
|
||||
$router->resource('photos', PhotoController::class);
|
||||
```
|
||||
|
||||
Dies erstellt die folgenden Routen:
|
||||
|
||||
| HTTP-Methode | URI | Aktion | Routenname |
|
||||
|--------------|-------------------|----------|----------------|
|
||||
| GET | /photos | index | photos.index |
|
||||
| GET | /photos/create | create | photos.create |
|
||||
| POST | /photos | store | photos.store |
|
||||
| GET | /photos/{photo} | show | photos.show |
|
||||
| GET | /photos/{photo}/edit | edit | photos.edit |
|
||||
| PUT/PATCH | /photos/{photo} | update | photos.update |
|
||||
| DELETE | /photos/{photo} | destroy | photos.destroy |
|
||||
|
||||
Sie können die generierten Routen einschränken:
|
||||
|
||||
```php
|
||||
$router->resource('photos', PhotoController::class)
|
||||
->only(['index', 'show']);
|
||||
|
||||
$router->resource('photos', PhotoController::class)
|
||||
->except(['create', 'store', 'update', 'destroy']);
|
||||
```
|
||||
|
||||
## API-Ressourcen-Routen
|
||||
|
||||
Für APIs können Sie API-Ressourcen-Routen definieren, die keine `create` und `edit` Routen enthalten:
|
||||
|
||||
```php
|
||||
$router->apiResource('photos', PhotoApiController::class);
|
||||
```
|
||||
|
||||
Dies erstellt die folgenden Routen:
|
||||
|
||||
| HTTP-Methode | URI | Aktion | Routenname |
|
||||
|--------------|------------------|----------|----------------|
|
||||
| GET | /photos | index | photos.index |
|
||||
| POST | /photos | store | photos.store |
|
||||
| GET | /photos/{photo} | show | photos.show |
|
||||
| PUT/PATCH | /photos/{photo} | update | photos.update |
|
||||
| DELETE | /photos/{photo} | destroy | photos.destroy |
|
||||
|
||||
## Verschachtelte Ressourcen
|
||||
|
||||
Sie können auch verschachtelte Ressourcen definieren:
|
||||
|
||||
```php
|
||||
$router->resource('photos.comments', PhotoCommentController::class);
|
||||
```
|
||||
|
||||
Dies erstellt Routen wie:
|
||||
- `/photos/{photo}/comments`
|
||||
- `/photos/{photo}/comments/{comment}`
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Controller-Anleitung](controllers.md): Erfahren Sie mehr über Controller und wie sie mit Routen interagieren.
|
||||
- [Middleware-Anleitung](middleware.md): Erfahren Sie mehr über Middleware und wie sie im Routing-System verwendet werden.
|
||||
- [Validierungs-Anleitung](validation.md): Erfahren Sie, wie Sie Benutzereingaben validieren können.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
585
docs/guides/security.md
Normal file
585
docs/guides/security.md
Normal file
@@ -0,0 +1,585 @@
|
||||
# Sicherheits-Anleitung
|
||||
|
||||
Diese Anleitung erklärt die Sicherheitsfunktionen des Frameworks und wie Sie sie effektiv nutzen können, um Ihre Anwendung zu schützen.
|
||||
|
||||
## Überblick über die Sicherheitsarchitektur
|
||||
|
||||
Das Framework bietet eine mehrschichtige Sicherheitsarchitektur, die verschiedene Schutzmaßnahmen auf verschiedenen Ebenen implementiert:
|
||||
|
||||
1. **Eingabevalidierung**: Validierung aller Benutzereingaben, um Injection-Angriffe zu verhindern.
|
||||
2. **Ausgabebereinigung**: Automatische Bereinigung von Ausgaben, um Cross-Site Scripting (XSS) zu verhindern.
|
||||
3. **CSRF-Schutz**: Schutz vor Cross-Site Request Forgery.
|
||||
4. **Authentifizierung**: Flexible Authentifizierungsmechanismen.
|
||||
5. **Autorisierung**: Rollenbasierte und fähigkeitsbasierte Zugriffskontrolle.
|
||||
6. **Web Application Firewall (WAF)**: Integrierte WAF zum Schutz vor gängigen Angriffen.
|
||||
7. **Sicherheitsheader**: Automatische Konfiguration von Sicherheitsheadern.
|
||||
8. **Sichere Konfiguration**: Sichere Standardeinstellungen und Best Practices.
|
||||
|
||||
## Eingabevalidierung
|
||||
|
||||
Die Eingabevalidierung ist die erste Verteidigungslinie gegen Injection-Angriffe. Das Framework bietet ein leistungsstarkes Validierungssystem, das in der [Validierungs-Anleitung](validation.md) ausführlich beschrieben wird.
|
||||
|
||||
### Beispiel für die Validierung von Benutzereingaben
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
'website' => 'nullable|url',
|
||||
'age' => 'nullable|integer|min:18',
|
||||
]);
|
||||
|
||||
// Die Validierung wurde bestanden, fahren Sie mit der Speicherung fort
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
```
|
||||
|
||||
### Schutz vor SQL-Injection
|
||||
|
||||
Das Framework verwendet parametrisierte Abfragen und Prepared Statements, um SQL-Injection-Angriffe zu verhindern:
|
||||
|
||||
```php
|
||||
// Sicher: Verwendung von parametrisierten Abfragen
|
||||
$users = DB::table('users')
|
||||
->where('email', $request->input('email'))
|
||||
->get();
|
||||
|
||||
// Sicher: Verwendung des Query Builders
|
||||
$users = User::where('email', $request->input('email'))->get();
|
||||
|
||||
// Sicher: Verwendung von Prepared Statements
|
||||
$statement = DB::prepare('SELECT * FROM users WHERE email = ?');
|
||||
$statement->execute([$request->input('email')]);
|
||||
$users = $statement->fetchAll();
|
||||
```
|
||||
|
||||
## Ausgabebereinigung
|
||||
|
||||
### Automatische Bereinigung in Views
|
||||
|
||||
Das Framework bereinigt automatisch alle Ausgaben in Views, um XSS-Angriffe zu verhindern:
|
||||
|
||||
```php
|
||||
<!-- Sicher: Automatische Bereinigung -->
|
||||
<div><?= $user->name ?></div>
|
||||
|
||||
<!-- Unsicher: Rohe Ausgabe ohne Bereinigung (nur verwenden, wenn Sie sicher sind, dass der Inhalt vertrauenswürdig ist) -->
|
||||
<div><?= raw($user->html_content) ?></div>
|
||||
```
|
||||
|
||||
### Manuelle Bereinigung
|
||||
|
||||
Sie können auch manuell Daten bereinigen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\Sanitizer;
|
||||
|
||||
// Text bereinigen
|
||||
$cleanText = Sanitizer::clean($input);
|
||||
|
||||
// HTML bereinigen (entfernt gefährliche Tags und Attribute)
|
||||
$cleanHtml = Sanitizer::cleanHtml($input);
|
||||
|
||||
// URL bereinigen
|
||||
$cleanUrl = Sanitizer::cleanUrl($input);
|
||||
```
|
||||
|
||||
## CSRF-Schutz
|
||||
|
||||
Das Framework bietet automatischen Schutz vor Cross-Site Request Forgery (CSRF) für alle Formulare und AJAX-Anfragen.
|
||||
|
||||
### CSRF-Token in Formularen
|
||||
|
||||
```php
|
||||
<form method="POST" action="/users">
|
||||
<?= csrf_field() ?>
|
||||
<input type="text" name="name">
|
||||
<button type="submit">Speichern</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Die `csrf_field()`-Funktion generiert ein verstecktes Eingabefeld mit einem CSRF-Token:
|
||||
|
||||
```html
|
||||
<input type="hidden" name="_token" value="random-token-here">
|
||||
```
|
||||
|
||||
### CSRF-Token in AJAX-Anfragen
|
||||
|
||||
```javascript
|
||||
// Mit jQuery
|
||||
$.ajax({
|
||||
url: '/users',
|
||||
type: 'POST',
|
||||
data: {
|
||||
name: 'John Doe',
|
||||
_token: '<?= csrf_token() ?>'
|
||||
}
|
||||
});
|
||||
|
||||
// Mit Fetch API
|
||||
fetch('/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '<?= csrf_token() ?>'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'John Doe'
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### CSRF-Schutz deaktivieren
|
||||
|
||||
In einigen Fällen, wie z.B. bei API-Endpunkten, möchten Sie möglicherweise den CSRF-Schutz deaktivieren:
|
||||
|
||||
```php
|
||||
// In der Middleware-Konfiguration
|
||||
$middleware->except([
|
||||
CsrfMiddleware::class => [
|
||||
'/api/*'
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
Das Framework bietet ein flexibles Authentifizierungssystem, das verschiedene Authentifizierungsmethoden unterstützt.
|
||||
|
||||
### Benutzerauthentifizierung
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\Auth;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$credentials = $request->only(['email', 'password']);
|
||||
$remember = $request->input('remember', false);
|
||||
|
||||
if (Auth::attempt($credentials, $remember)) {
|
||||
return $this->redirect('/dashboard');
|
||||
}
|
||||
|
||||
return $this->back()->withErrors([
|
||||
'email' => 'Die angegebenen Anmeldeinformationen sind ungültig.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(): Response
|
||||
{
|
||||
Auth::logout();
|
||||
|
||||
return $this->redirect('/');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Zugriff auf den authentifizierten Benutzer
|
||||
|
||||
```php
|
||||
// Prüfen, ob ein Benutzer angemeldet ist
|
||||
if (Auth::check()) {
|
||||
// Benutzer ist angemeldet
|
||||
}
|
||||
|
||||
// Aktuellen Benutzer abrufen
|
||||
$user = Auth::user();
|
||||
|
||||
// Benutzer-ID abrufen
|
||||
$userId = Auth::id();
|
||||
```
|
||||
|
||||
### Passwort-Hashing
|
||||
|
||||
Das Framework verwendet automatisch sichere Passwort-Hashing-Algorithmen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\Hash;
|
||||
|
||||
// Passwort hashen
|
||||
$hashedPassword = Hash::make('password');
|
||||
|
||||
// Passwort überprüfen
|
||||
if (Hash::check('password', $hashedPassword)) {
|
||||
// Passwort ist korrekt
|
||||
}
|
||||
|
||||
// Prüfen, ob ein Passwort neu gehasht werden muss
|
||||
if (Hash::needsRehash($hashedPassword)) {
|
||||
$hashedPassword = Hash::make('password');
|
||||
}
|
||||
```
|
||||
|
||||
### Passwort-Zurücksetzung
|
||||
|
||||
Das Framework bietet auch Unterstützung für die Passwort-Zurücksetzung:
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\Password;
|
||||
|
||||
// Token für die Passwort-Zurücksetzung erstellen
|
||||
$token = Password::createToken($user);
|
||||
|
||||
// E-Mail mit dem Token senden
|
||||
Mail::send('emails.reset-password', [
|
||||
'token' => $token,
|
||||
'user' => $user
|
||||
], function ($message) use ($user) {
|
||||
$message->to($user->email);
|
||||
$message->subject('Passwort zurücksetzen');
|
||||
});
|
||||
|
||||
// Token überprüfen und Passwort zurücksetzen
|
||||
$status = Password::reset($request->only(
|
||||
'email', 'password', 'password_confirmation', 'token'
|
||||
), function ($user, $password) {
|
||||
$user->password = Hash::make($password);
|
||||
$user->save();
|
||||
});
|
||||
```
|
||||
|
||||
## Autorisierung
|
||||
|
||||
Das Framework bietet ein leistungsstarkes Autorisierungssystem, das rollenbasierte und fähigkeitsbasierte Zugriffskontrolle unterstützt.
|
||||
|
||||
### Rollenbasierte Zugriffskontrolle
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\Auth;
|
||||
|
||||
// Prüfen, ob der Benutzer eine bestimmte Rolle hat
|
||||
if (Auth::user()->hasRole('admin')) {
|
||||
// Benutzer ist ein Administrator
|
||||
}
|
||||
|
||||
// Prüfen, ob der Benutzer eine von mehreren Rollen hat
|
||||
if (Auth::user()->hasAnyRole(['admin', 'editor'])) {
|
||||
// Benutzer ist entweder Administrator oder Editor
|
||||
}
|
||||
|
||||
// Prüfen, ob der Benutzer alle angegebenen Rollen hat
|
||||
if (Auth::user()->hasAllRoles(['admin', 'editor'])) {
|
||||
// Benutzer ist sowohl Administrator als auch Editor
|
||||
}
|
||||
```
|
||||
|
||||
### Fähigkeitsbasierte Zugriffskontrolle
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\Auth;
|
||||
|
||||
// Prüfen, ob der Benutzer eine bestimmte Fähigkeit hat
|
||||
if (Auth::user()->can('edit-post', $post)) {
|
||||
// Benutzer kann den Beitrag bearbeiten
|
||||
}
|
||||
|
||||
// Prüfen, ob der Benutzer eine Fähigkeit nicht hat
|
||||
if (Auth::user()->cannot('delete-post', $post)) {
|
||||
// Benutzer kann den Beitrag nicht löschen
|
||||
}
|
||||
|
||||
// Autorisierung erzwingen (wirft eine Exception, wenn nicht autorisiert)
|
||||
Auth::user()->authorize('edit-post', $post);
|
||||
```
|
||||
|
||||
### Richtlinien definieren
|
||||
|
||||
Sie können Autorisierungsrichtlinien definieren, um komplexe Autorisierungslogik zu kapseln:
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\Policy;
|
||||
|
||||
class PostPolicy extends Policy
|
||||
{
|
||||
public function view(User $user, Post $post): bool
|
||||
{
|
||||
return true; // Jeder kann Beiträge sehen
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->hasRole('writer');
|
||||
}
|
||||
|
||||
public function update(User $user, Post $post): bool
|
||||
{
|
||||
return $user->id === $post->user_id || $user->hasRole('editor');
|
||||
}
|
||||
|
||||
public function delete(User $user, Post $post): bool
|
||||
{
|
||||
return $user->id === $post->user_id || $user->hasRole('admin');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Registrieren Sie die Richtlinie:
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\Gate;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
Gate::policy(Post::class, PostPolicy::class);
|
||||
```
|
||||
|
||||
Verwenden Sie die Richtlinie:
|
||||
|
||||
```php
|
||||
if (Auth::user()->can('update', $post)) {
|
||||
// Benutzer kann den Beitrag aktualisieren
|
||||
}
|
||||
```
|
||||
|
||||
### Autorisierung in Controllern
|
||||
|
||||
Sie können die Autorisierung auch direkt in Controllern verwenden:
|
||||
|
||||
```php
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$post = Post::find($id);
|
||||
|
||||
if (!$post) {
|
||||
return $this->notFound('Beitrag nicht gefunden');
|
||||
}
|
||||
|
||||
$this->authorize('update', $post);
|
||||
|
||||
// Beitrag aktualisieren
|
||||
|
||||
return $this->redirect('/posts')->with('success', 'Beitrag aktualisiert');
|
||||
}
|
||||
```
|
||||
|
||||
## Web Application Firewall (WAF)
|
||||
|
||||
Das Framework enthält eine integrierte Web Application Firewall (WAF), die Ihre Anwendung vor gängigen Angriffen schützt.
|
||||
|
||||
### WAF-Konfiguration
|
||||
|
||||
Die WAF ist standardmäßig aktiviert und kann in der Konfigurationsdatei `config/security.php` konfiguriert werden:
|
||||
|
||||
```php
|
||||
return [
|
||||
'waf' => [
|
||||
'enabled' => true,
|
||||
'rules' => [
|
||||
'sql_injection' => true,
|
||||
'xss' => true,
|
||||
'command_injection' => true,
|
||||
'path_traversal' => true,
|
||||
'request_validation' => true,
|
||||
],
|
||||
'whitelist' => [
|
||||
'ip' => [
|
||||
'127.0.0.1',
|
||||
'::1',
|
||||
],
|
||||
'paths' => [
|
||||
'/api/webhook',
|
||||
],
|
||||
],
|
||||
'blacklist' => [
|
||||
'ip' => [
|
||||
// Blockierte IP-Adressen
|
||||
],
|
||||
'user_agents' => [
|
||||
'Bad Bot',
|
||||
],
|
||||
],
|
||||
'rate_limiting' => [
|
||||
'enabled' => true,
|
||||
'max_requests' => 100,
|
||||
'time_window' => 60, // Sekunden
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Maschinelles Lernen für die Angriffserkennung
|
||||
|
||||
Die WAF verwendet auch maschinelles Lernen, um Anomalien zu erkennen und potenzielle Angriffe zu identifizieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Waf\MachineLearning\MachineLearningEngine;
|
||||
|
||||
// In einem Controller oder Service
|
||||
$mlEngine = $this->container->get(MachineLearningEngine::class);
|
||||
$anomalyScore = $mlEngine->detectAnomalies($request);
|
||||
|
||||
if ($anomalyScore > 0.8) {
|
||||
// Verdächtige Anfrage protokollieren oder blockieren
|
||||
$this->logger->warning('Verdächtige Anfrage erkannt', [
|
||||
'ip' => $request->ip(),
|
||||
'path' => $request->path(),
|
||||
'score' => $anomalyScore,
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
## Sicherheitsheader
|
||||
|
||||
Das Framework konfiguriert automatisch wichtige Sicherheitsheader, um Ihre Anwendung zu schützen.
|
||||
|
||||
### Content Security Policy (CSP)
|
||||
|
||||
Die Content Security Policy schützt vor XSS-Angriffen, indem sie kontrolliert, welche Ressourcen geladen werden dürfen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\ContentSecurityPolicy;
|
||||
|
||||
// In einem Middleware oder Controller
|
||||
$csp = new ContentSecurityPolicy();
|
||||
$csp->setDefaultSrc(['self'])
|
||||
->setScriptSrc(['self', 'trusted-scripts.com'])
|
||||
->setStyleSrc(['self', 'trusted-styles.com'])
|
||||
->setImgSrc(['self', 'trusted-images.com'])
|
||||
->setFontSrc(['self', 'trusted-fonts.com'])
|
||||
->setConnectSrc(['self', 'api.trusted-domain.com'])
|
||||
->setFrameSrc(['none'])
|
||||
->setObjectSrc(['none'])
|
||||
->setReportUri('/csp-report');
|
||||
|
||||
$response->headers->set('Content-Security-Policy', $csp->compile());
|
||||
```
|
||||
|
||||
### Andere Sicherheitsheader
|
||||
|
||||
Das Framework setzt auch andere wichtige Sicherheitsheader:
|
||||
|
||||
```php
|
||||
// In einem Middleware oder Controller
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('X-Frame-Options', 'DENY');
|
||||
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
||||
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
||||
```
|
||||
|
||||
## Sichere Konfiguration
|
||||
|
||||
### HTTPS erzwingen
|
||||
|
||||
Sie können HTTPS für Ihre Anwendung erzwingen:
|
||||
|
||||
```php
|
||||
// In der Middleware-Konfiguration
|
||||
$middleware->add(HttpsMiddleware::class);
|
||||
```
|
||||
|
||||
### Sichere Cookies
|
||||
|
||||
Das Framework konfiguriert automatisch sichere Cookies:
|
||||
|
||||
```php
|
||||
// In der Konfigurationsdatei config/session.php
|
||||
return [
|
||||
'cookie' => [
|
||||
'secure' => true,
|
||||
'http_only' => true,
|
||||
'same_site' => 'lax',
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Sichere Datei-Uploads
|
||||
|
||||
Das Framework bietet Funktionen für sichere Datei-Uploads:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\FileValidator;
|
||||
|
||||
// In einem Controller
|
||||
$this->validate($request, [
|
||||
'document' => 'required|file|mimes:pdf,doc,docx|max:10240',
|
||||
]);
|
||||
|
||||
$file = $request->file('document');
|
||||
|
||||
// Zusätzliche Sicherheitsvalidierung
|
||||
$validator = new FileValidator();
|
||||
if (!$validator->isSafe($file)) {
|
||||
return $this->back()->withErrors([
|
||||
'document' => 'Die Datei ist möglicherweise schädlich.',
|
||||
]);
|
||||
}
|
||||
|
||||
// Datei speichern
|
||||
$path = $file->store('documents');
|
||||
```
|
||||
|
||||
## Sicherheitsprotokollierung und -überwachung
|
||||
|
||||
Das Framework bietet umfangreiche Protokollierung für Sicherheitsereignisse:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\SecurityLogger;
|
||||
|
||||
// In einem Controller oder Service
|
||||
$securityLogger = $this->container->get(SecurityLogger::class);
|
||||
|
||||
// Sicherheitsereignis protokollieren
|
||||
$securityLogger->log('authentication_failure', [
|
||||
'user_id' => $user->id,
|
||||
'ip' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
]);
|
||||
```
|
||||
|
||||
### Sicherheitsbenachrichtigungen
|
||||
|
||||
Sie können auch Benachrichtigungen für wichtige Sicherheitsereignisse konfigurieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\SecurityNotifier;
|
||||
|
||||
// In einem Controller oder Service
|
||||
$securityNotifier = $this->container->get(SecurityNotifier::class);
|
||||
|
||||
// Benachrichtigung senden
|
||||
$securityNotifier->notify('suspicious_login', [
|
||||
'user_id' => $user->id,
|
||||
'ip' => $request->ip(),
|
||||
'location' => $geoip->getLocation($request->ip()),
|
||||
'time' => now(),
|
||||
]);
|
||||
```
|
||||
|
||||
## Sicherheits-Checkliste
|
||||
|
||||
Hier ist eine Checkliste, um sicherzustellen, dass Ihre Anwendung sicher ist:
|
||||
|
||||
1. **Eingabevalidierung**: Validieren Sie alle Benutzereingaben.
|
||||
2. **Ausgabebereinigung**: Bereinigen Sie alle Ausgaben, um XSS zu verhindern.
|
||||
3. **CSRF-Schutz**: Verwenden Sie CSRF-Token in allen Formularen und AJAX-Anfragen.
|
||||
4. **Authentifizierung**: Implementieren Sie sichere Authentifizierungsmechanismen.
|
||||
5. **Autorisierung**: Implementieren Sie rollenbasierte und fähigkeitsbasierte Zugriffskontrolle.
|
||||
6. **Sichere Passwörter**: Verwenden Sie sichere Passwort-Hashing-Algorithmen.
|
||||
7. **HTTPS**: Erzwingen Sie HTTPS für Ihre Anwendung.
|
||||
8. **Sichere Cookies**: Konfigurieren Sie sichere Cookies.
|
||||
9. **Sicherheitsheader**: Setzen Sie wichtige Sicherheitsheader.
|
||||
10. **Datei-Uploads**: Validieren Sie Datei-Uploads sorgfältig.
|
||||
11. **Fehlerbehandlung**: Zeigen Sie keine sensiblen Informationen in Fehlermeldungen an.
|
||||
12. **Protokollierung**: Protokollieren Sie Sicherheitsereignisse.
|
||||
13. **Aktualisierungen**: Halten Sie das Framework und alle Abhängigkeiten aktuell.
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Authentifizierungs-Anleitung](authentication.md): Erfahren Sie mehr über die Authentifizierungsfunktionen des Frameworks.
|
||||
- [Autorisierungs-Anleitung](authorization.md): Erfahren Sie mehr über die Autorisierungsfunktionen des Frameworks.
|
||||
- [WAF-Anleitung](../components/waf/index.md): Erfahren Sie mehr über die Web Application Firewall des Frameworks.
|
||||
- [Validierungs-Anleitung](validation.md): Erfahren Sie mehr über die Validierungsfunktionen des Frameworks.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
539
docs/guides/validation.md
Normal file
539
docs/guides/validation.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# Validierungs-Anleitung
|
||||
|
||||
Diese Anleitung erklärt, wie das Validierungssystem des Frameworks funktioniert und wie Sie es effektiv nutzen können.
|
||||
|
||||
## Grundlagen der Validierung
|
||||
|
||||
Die Validierung ist ein wichtiger Teil jeder Webanwendung, da sie sicherstellt, dass die Daten, die von Benutzern eingegeben werden, den erwarteten Regeln entsprechen. Das Framework bietet ein leistungsstarkes und flexibles Validierungssystem, das einfach zu verwenden ist.
|
||||
|
||||
### Validierung in Controllern
|
||||
|
||||
Die einfachste Möglichkeit, Daten zu validieren, ist die Verwendung der `validate`-Methode in Controllern:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'age' => 'integer|min:18',
|
||||
]);
|
||||
|
||||
// Die Validierung wurde bestanden, fahren Sie mit der Speicherung fort
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
```
|
||||
|
||||
Wenn die Validierung fehlschlägt, wird automatisch eine Antwort mit den Validierungsfehlern zurückgegeben:
|
||||
|
||||
- Bei einer regulären Anfrage wird der Benutzer zur vorherigen Seite umgeleitet, und die Validierungsfehler werden in der Session gespeichert.
|
||||
- Bei einer AJAX-Anfrage wird eine JSON-Antwort mit den Validierungsfehlern und dem HTTP-Statuscode 422 zurückgegeben.
|
||||
|
||||
### Manuelle Validierung
|
||||
|
||||
Sie können auch den Validator direkt verwenden, um mehr Kontrolle über den Validierungsprozess zu haben:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Validator;
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$validator = new Validator($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $validator->errors();
|
||||
|
||||
// Fehler behandeln
|
||||
return $this->back()->withErrors($errors);
|
||||
}
|
||||
|
||||
// Die Validierung wurde bestanden, fahren Sie mit der Speicherung fort
|
||||
$user = User::create($request->only(['name', 'email', 'password']));
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
```
|
||||
|
||||
## Validierungsregeln
|
||||
|
||||
Das Framework bietet eine Vielzahl von Validierungsregeln, die Sie verwenden können, um Ihre Daten zu validieren.
|
||||
|
||||
### Grundlegende Regeln
|
||||
|
||||
- `required`: Das Feld muss vorhanden sein und darf nicht leer sein.
|
||||
- `string`: Das Feld muss ein String sein.
|
||||
- `integer`: Das Feld muss eine Ganzzahl sein.
|
||||
- `numeric`: Das Feld muss eine Zahl sein (Ganzzahl oder Dezimalzahl).
|
||||
- `boolean`: Das Feld muss ein Boolean sein (`true`, `false`, `1`, `0`, `"1"`, `"0"`).
|
||||
- `array`: Das Feld muss ein Array sein.
|
||||
- `date`: Das Feld muss ein gültiges Datum sein.
|
||||
- `email`: Das Feld muss eine gültige E-Mail-Adresse sein.
|
||||
- `url`: Das Feld muss eine gültige URL sein.
|
||||
- `ip`: Das Feld muss eine gültige IP-Adresse sein.
|
||||
- `json`: Das Feld muss ein gültiger JSON-String sein.
|
||||
- `alpha`: Das Feld darf nur Buchstaben enthalten.
|
||||
- `alpha_num`: Das Feld darf nur Buchstaben und Zahlen enthalten.
|
||||
- `alpha_dash`: Das Feld darf nur Buchstaben, Zahlen, Bindestriche und Unterstriche enthalten.
|
||||
|
||||
### Größenregeln
|
||||
|
||||
- `min:value`: Das Feld muss mindestens `value` groß sein.
|
||||
- Für Strings: Die Anzahl der Zeichen.
|
||||
- Für Zahlen: Der Mindestwert.
|
||||
- Für Arrays: Die Mindestanzahl der Elemente.
|
||||
- Für Dateien: Die Mindestgröße in Kilobyte.
|
||||
- `max:value`: Das Feld darf höchstens `value` groß sein.
|
||||
- `size:value`: Das Feld muss genau `value` groß sein.
|
||||
- `between:min,max`: Das Feld muss zwischen `min` und `max` liegen.
|
||||
|
||||
### Vergleichsregeln
|
||||
|
||||
- `same:field`: Das Feld muss den gleichen Wert wie `field` haben.
|
||||
- `different:field`: Das Feld muss einen anderen Wert als `field` haben.
|
||||
- `gt:field`: Das Feld muss größer als `field` sein.
|
||||
- `gte:field`: Das Feld muss größer oder gleich `field` sein.
|
||||
- `lt:field`: Das Feld muss kleiner als `field` sein.
|
||||
- `lte:field`: Das Feld muss kleiner oder gleich `field` sein.
|
||||
|
||||
### Datenbankregeln
|
||||
|
||||
- `unique:table,column,except,idColumn`: Das Feld muss in der angegebenen Tabelle und Spalte einzigartig sein.
|
||||
- `table`: Der Name der Tabelle.
|
||||
- `column`: Der Name der Spalte (standardmäßig der Feldname).
|
||||
- `except`: Die ID des Datensatzes, der bei der Überprüfung ignoriert werden soll (optional).
|
||||
- `idColumn`: Der Name der ID-Spalte (standardmäßig `id`).
|
||||
- `exists:table,column`: Das Feld muss in der angegebenen Tabelle und Spalte existieren.
|
||||
|
||||
### Dateiregeln
|
||||
|
||||
- `file`: Das Feld muss eine erfolgreich hochgeladene Datei sein.
|
||||
- `image`: Das Feld muss ein Bild sein (jpeg, png, bmp, gif, svg).
|
||||
- `mimes:jpeg,png,...`: Das Feld muss eine Datei mit einem der angegebenen MIME-Typen sein.
|
||||
- `mimetypes:text/plain,...`: Das Feld muss eine Datei mit einem der angegebenen MIME-Typen sein.
|
||||
- `dimensions:min_width=100,max_width=1000,min_height=100,max_height=1000,width=100,height=100,ratio=1/1`: Das Feld muss ein Bild sein, das den angegebenen Dimensionen entspricht.
|
||||
|
||||
### Bedingte Regeln
|
||||
|
||||
- `required_if:anotherfield,value,...`: Das Feld ist erforderlich, wenn `anotherfield` einen der angegebenen Werte hat.
|
||||
- `required_unless:anotherfield,value,...`: Das Feld ist erforderlich, es sei denn, `anotherfield` hat einen der angegebenen Werte.
|
||||
- `required_with:foo,bar,...`: Das Feld ist erforderlich, wenn eines der anderen angegebenen Felder vorhanden ist.
|
||||
- `required_with_all:foo,bar,...`: Das Feld ist erforderlich, wenn alle anderen angegebenen Felder vorhanden sind.
|
||||
- `required_without:foo,bar,...`: Das Feld ist erforderlich, wenn eines der anderen angegebenen Felder nicht vorhanden ist.
|
||||
- `required_without_all:foo,bar,...`: Das Feld ist erforderlich, wenn alle anderen angegebenen Felder nicht vorhanden sind.
|
||||
|
||||
### Formatregeln
|
||||
|
||||
- `date_format:format`: Das Feld muss dem angegebenen Datumsformat entsprechen.
|
||||
- `regex:pattern`: Das Feld muss dem angegebenen regulären Ausdruck entsprechen.
|
||||
- `not_regex:pattern`: Das Feld darf nicht dem angegebenen regulären Ausdruck entsprechen.
|
||||
|
||||
### Bestätigungsregeln
|
||||
|
||||
- `confirmed`: Das Feld muss ein Bestätigungsfeld haben (z.B. `password_confirmation` für das Feld `password`).
|
||||
- `accepted`: Das Feld muss akzeptiert werden (yes, on, 1, true).
|
||||
|
||||
### Spezielle Regeln
|
||||
|
||||
- `nullable`: Das Feld darf `null` sein.
|
||||
- `present`: Das Feld muss im Eingabedaten vorhanden sein, kann aber leer sein.
|
||||
- `sometimes`: Die Regel wird nur angewendet, wenn das Feld vorhanden ist.
|
||||
|
||||
## Benutzerdefinierte Validierungsnachrichten
|
||||
|
||||
Sie können benutzerdefinierte Validierungsnachrichten angeben, um die Standardnachrichten zu überschreiben:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
], [
|
||||
'name.required' => 'Der Name ist erforderlich',
|
||||
'email.unique' => 'Diese E-Mail-Adresse wird bereits verwendet',
|
||||
'password.min' => 'Das Passwort muss mindestens 8 Zeichen lang sein',
|
||||
]);
|
||||
```
|
||||
|
||||
Sie können auch Platzhalter in Ihren Nachrichten verwenden:
|
||||
|
||||
- `:attribute`: Der Name des Feldes.
|
||||
- `:min`: Der Mindestwert.
|
||||
- `:max`: Der Maximalwert.
|
||||
- `:size`: Die erforderliche Größe.
|
||||
- `:values`: Die gültigen Werte.
|
||||
- `:other`: Der Name des anderen Feldes.
|
||||
|
||||
```php
|
||||
'min' => 'Das Feld :attribute muss mindestens :min Zeichen enthalten',
|
||||
```
|
||||
|
||||
## Validierung von Arrays
|
||||
|
||||
Sie können auch verschachtelte Arrays validieren:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'user.name' => 'required|string|max:255',
|
||||
'user.email' => 'required|email|unique:users,email',
|
||||
'addresses.*.street' => 'required|string|max:255',
|
||||
'addresses.*.city' => 'required|string|max:255',
|
||||
'addresses.*.country' => 'required|string|max:255',
|
||||
]);
|
||||
```
|
||||
|
||||
In diesem Beispiel wird erwartet, dass `user` ein Array mit den Schlüsseln `name` und `email` ist, und `addresses` ein Array von Arrays ist, wobei jedes innere Array die Schlüssel `street`, `city` und `country` hat.
|
||||
|
||||
## Benutzerdefinierte Validierungsregeln
|
||||
|
||||
### Erstellen einer benutzerdefinierten Regel
|
||||
|
||||
Sie können benutzerdefinierte Validierungsregeln erstellen, indem Sie die `Rule`-Schnittstelle implementieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rule;
|
||||
|
||||
class StrongPassword implements Rule
|
||||
{
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
// Überprüfen, ob das Passwort stark ist
|
||||
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $value) === 1;
|
||||
}
|
||||
|
||||
public function message(): string
|
||||
{
|
||||
return 'Das Passwort muss mindestens 8 Zeichen lang sein und mindestens einen Großbuchstaben, einen Kleinbuchstaben, eine Zahl und ein Sonderzeichen enthalten.';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verwenden einer benutzerdefinierten Regel
|
||||
|
||||
Sie können Ihre benutzerdefinierte Regel wie folgt verwenden:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Validator;
|
||||
|
||||
$validator = new Validator($request->all(), [
|
||||
'password' => ['required', 'string', new StrongPassword()],
|
||||
]);
|
||||
```
|
||||
|
||||
Oder in einem Controller:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'password' => ['required', 'string', new StrongPassword()],
|
||||
]);
|
||||
```
|
||||
|
||||
## Validierung mit Form Requests
|
||||
|
||||
Für komplexe Validierungslogik können Sie Form Request-Klassen erstellen:
|
||||
|
||||
```php
|
||||
use App\Framework\Http\FormRequest;
|
||||
|
||||
class CreateUserRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Der Name ist erforderlich',
|
||||
'email.unique' => 'Diese E-Mail-Adresse wird bereits verwendet',
|
||||
'password.min' => 'Das Passwort muss mindestens 8 Zeichen lang sein',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()->hasPermission('create-users');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dann können Sie die Form Request-Klasse in Ihrem Controller verwenden:
|
||||
|
||||
```php
|
||||
public function store(CreateUserRequest $request): Response
|
||||
{
|
||||
// Die Validierung wurde bereits durchgeführt
|
||||
|
||||
$user = User::create($request->validated());
|
||||
|
||||
return $this->redirect('/users')->with('success', 'Benutzer erstellt');
|
||||
}
|
||||
```
|
||||
|
||||
Die `validated`-Methode gibt nur die validierten Felder zurück, was nützlich ist, um Massenänderungen zu verhindern.
|
||||
|
||||
## Validierung von Dateien
|
||||
|
||||
Das Framework bietet spezielle Regeln für die Validierung von Dateien:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'avatar' => 'required|file|image|max:2048', // max in Kilobyte
|
||||
'document' => 'required|file|mimes:pdf,doc,docx|max:10240',
|
||||
]);
|
||||
```
|
||||
|
||||
## Validierung von Daten außerhalb von Requests
|
||||
|
||||
Sie können den Validator auch verwenden, um Daten zu validieren, die nicht aus einer HTTP-Anfrage stammen:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Validator;
|
||||
|
||||
$data = [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john@example.com',
|
||||
];
|
||||
|
||||
$validator = new Validator($data, [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $validator->errors();
|
||||
// Fehler behandeln
|
||||
} else {
|
||||
$validatedData = $validator->validated();
|
||||
// Mit validierten Daten fortfahren
|
||||
}
|
||||
```
|
||||
|
||||
## Validierung in JavaScript
|
||||
|
||||
Das Framework bietet auch eine Möglichkeit, Validierungsregeln in JavaScript zu verwenden, um clientseitige Validierung zu ermöglichen:
|
||||
|
||||
```php
|
||||
// In Ihrer View
|
||||
<script>
|
||||
const rules = <?= json_encode($validator->jsRules()) ?>;
|
||||
const messages = <?= json_encode($validator->jsMessages()) ?>;
|
||||
|
||||
// Verwenden Sie die Regeln mit Ihrer bevorzugten JavaScript-Validierungsbibliothek
|
||||
</script>
|
||||
```
|
||||
|
||||
## Validierung von Abhängigkeiten
|
||||
|
||||
Manchmal hängt die Validierung eines Feldes vom Wert eines anderen Feldes ab. Sie können benutzerdefinierte Regeln verwenden, um diese Art von Validierung zu implementieren:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'payment_type' => 'required|in:credit_card,paypal',
|
||||
'card_number' => 'required_if:payment_type,credit_card|string|size:16',
|
||||
'card_expiry' => 'required_if:payment_type,credit_card|date_format:m/y',
|
||||
'card_cvv' => 'required_if:payment_type,credit_card|string|size:3',
|
||||
'paypal_email' => 'required_if:payment_type,paypal|email',
|
||||
]);
|
||||
```
|
||||
|
||||
## Validierung mit Bedingungen
|
||||
|
||||
Sie können auch komplexere Bedingungen für die Validierung definieren:
|
||||
|
||||
```php
|
||||
$validator = new Validator($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
$validator->sometimes('phone', 'required|string|size:10', function ($input) {
|
||||
return $input->contact_method === 'phone';
|
||||
});
|
||||
|
||||
$validator->sometimes('address', 'required|string|max:255', function ($input) {
|
||||
return $input->requires_shipping === true;
|
||||
});
|
||||
```
|
||||
|
||||
## Validierung von Datenbanken
|
||||
|
||||
Das Framework bietet Regeln für die Validierung von Datenbanken:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'username' => 'required|string|unique:users,username',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
]);
|
||||
```
|
||||
|
||||
Bei der Aktualisierung eines Datensatzes möchten Sie möglicherweise den aktuellen Datensatz von der Einzigartigkeit ausschließen:
|
||||
|
||||
```php
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email|unique:users,email,' . $userId,
|
||||
'username' => 'required|string|unique:users,username,' . $userId,
|
||||
]);
|
||||
```
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
### Zugriff auf Validierungsfehler
|
||||
|
||||
Wenn die Validierung fehlschlägt, können Sie auf die Fehler zugreifen:
|
||||
|
||||
```php
|
||||
$validator = new Validator($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $validator->errors();
|
||||
|
||||
// Alle Fehler abrufen
|
||||
$allErrors = $errors->all();
|
||||
|
||||
// Fehler für ein bestimmtes Feld abrufen
|
||||
$nameErrors = $errors->get('name');
|
||||
|
||||
// Ersten Fehler für ein bestimmtes Feld abrufen
|
||||
$firstNameError = $errors->first('name');
|
||||
|
||||
// Prüfen, ob ein Feld Fehler hat
|
||||
$hasNameErrors = $errors->has('name');
|
||||
}
|
||||
```
|
||||
|
||||
### Anzeigen von Validierungsfehlern in Views
|
||||
|
||||
In Ihren Views können Sie die Validierungsfehler wie folgt anzeigen:
|
||||
|
||||
```php
|
||||
<?php if ($errors->has('name')): ?>
|
||||
<div class="error"><?= $errors->first('name') ?></div>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
Oder alle Fehler anzeigen:
|
||||
|
||||
```php
|
||||
<?php if ($errors->any()): ?>
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
<?php foreach ($errors->all() as $error): ?>
|
||||
<li><?= $error ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## Beste Praktiken
|
||||
|
||||
### Wiederverwendung von Validierungsregeln
|
||||
|
||||
Wenn Sie dieselben Validierungsregeln in mehreren Controllern verwenden, sollten Sie sie in eine separate Klasse auslagern:
|
||||
|
||||
```php
|
||||
class UserValidationRules
|
||||
{
|
||||
public static function forCreation(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
];
|
||||
}
|
||||
|
||||
public static function forUpdate(int $userId): array
|
||||
{
|
||||
return [
|
||||
'name' => 'string|max:255',
|
||||
'email' => 'email|unique:users,email,' . $userId,
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dann können Sie diese Regeln in Ihren Controllern verwenden:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, UserValidationRules::forCreation());
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$this->validate($request, UserValidationRules::forUpdate($id));
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Validierung in Modellen
|
||||
|
||||
Sie können Validierungsregeln auch in Ihren Modellen definieren:
|
||||
|
||||
```php
|
||||
class User extends Model
|
||||
{
|
||||
public static function validationRules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
];
|
||||
}
|
||||
|
||||
public static function updateValidationRules(int $userId): array
|
||||
{
|
||||
return [
|
||||
'name' => 'string|max:255',
|
||||
'email' => 'email|unique:users,email,' . $userId,
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dann können Sie diese Regeln in Ihren Controllern verwenden:
|
||||
|
||||
```php
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$this->validate($request, User::validationRules());
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): Response
|
||||
{
|
||||
$this->validate($request, User::updateValidationRules($id));
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Controller-Anleitung](controllers.md): Erfahren Sie mehr über Controller und wie sie mit der Validierung interagieren.
|
||||
- [Form Request-Anleitung](form-requests.md): Erfahren Sie mehr über Form Requests und wie sie die Validierung vereinfachen können.
|
||||
- [Datenbank-Anleitung](database.md): Erfahren Sie mehr über Datenbankoperationen und wie sie mit der Validierung interagieren.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Projekt-Dokumentation
|
||||
|
||||
## Willkommen zur Projektdokumentation
|
||||
|
||||
Diese Dokumentation bietet umfassende Informationen zu Coding-Standards, Architektur, Best Practices und Framework-Komponenten des Projekts.
|
||||
|
||||
## Schnellzugriff
|
||||
|
||||
- [Coding-Guidelines](/standards/CODING-GUIDELINES.md)
|
||||
- [Sicherheitsrichtlinien](/standards/SICHERHEITS-GUIDELINES.md)
|
||||
- [Projektstruktur](/architecture/STRUKTUR-DOKUMENTATION.md)
|
||||
- [Performance-Optimierung](/guidelines/PERFORMANCE-GUIDELINES.md)
|
||||
- [KI-Assistent Einrichtung](/ai/EINRICHTUNG-PHPSTORM.md)
|
||||
|
||||
## Dokumentationsstruktur
|
||||
|
||||
Die Dokumentation ist in verschiedene Bereiche unterteilt:
|
||||
|
||||
- **Standards und Guidelines**: Grundlegende Prinzipien und Richtlinien für die Entwicklung
|
||||
- **Framework-Module**: Dokumentation zu den einzelnen Framework-Komponenten
|
||||
- **Entwicklungsrichtlinien**: Detaillierte Anleitungen zu Performance, Testing und mehr
|
||||
- **Architektur**: Übersicht und Details zur Systemarchitektur
|
||||
- **KI-Assistent-Integration**: Anleitung zur Verwendung des KI-Assistenten
|
||||
|
||||
## Für neue Entwickler
|
||||
|
||||
Wenn Sie neu im Projekt sind, empfehlen wir Ihnen, mit folgenden Dokumenten zu beginnen:
|
||||
|
||||
1. [Projektstruktur](/architecture/STRUKTUR-DOKUMENTATION.md) - Um einen Überblick zu bekommen
|
||||
2. [Coding-Guidelines](/standards/CODING-GUIDELINES.md) - Um die Coding-Standards zu verstehen
|
||||
3. [Modul-Checkliste](/framework/MODUL-CHECKLISTE.md) - Um die Modularisierung zu verstehen
|
||||
|
||||
## Beitragen zur Dokumentation
|
||||
|
||||
Die Dokumentation ist ein lebendiges Dokument. Wenn Sie Fehler finden oder Verbesserungen vorschlagen möchten, erstellen Sie bitte einen Pull Request mit Ihren Änderungen.
|
||||
185
docs/logging-module.md
Normal file
185
docs/logging-module.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Logging-Modul Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Logging-Modul wurde überarbeitet, um eine konsistente Pfadverwaltung zu gewährleisten und die Konfiguration zu zentralisieren. Die Hauptverbesserungen umfassen:
|
||||
|
||||
1. Zentrale Konfiguration für Logpfade mit `LogConfig`
|
||||
2. Integration des `PathProvider` für konsistente Pfadauflösung
|
||||
3. Einheitliche Verzeichnisstruktur für verschiedene Logtypen
|
||||
4. Automatische Erstellung von Logverzeichnissen
|
||||
|
||||
## Komponenten
|
||||
|
||||
### LogConfig
|
||||
|
||||
Die neue `LogConfig`-Klasse dient als zentrale Konfiguration für alle Logpfade:
|
||||
|
||||
```php
|
||||
final class LogConfig
|
||||
{
|
||||
public function __construct(PathProvider $pathProvider)
|
||||
{
|
||||
// Initialisierung der Logpfade
|
||||
}
|
||||
|
||||
public function getLogPath(string $type): string
|
||||
{
|
||||
// Gibt den Pfad für einen bestimmten Log-Typ zurück
|
||||
}
|
||||
|
||||
public function getAllLogPaths(): array
|
||||
{
|
||||
// Gibt alle konfigurierten Logpfade zurück
|
||||
}
|
||||
|
||||
public function ensureLogDirectoriesExist(): void
|
||||
{
|
||||
// Stellt sicher, dass alle Logverzeichnisse existieren
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### FileHandler und JsonFileHandler
|
||||
|
||||
Die Handler wurden aktualisiert, um den `PathProvider` zu verwenden:
|
||||
|
||||
```php
|
||||
public function __construct(
|
||||
string $logFile,
|
||||
LogLevel|int $minLevel = LogLevel::DEBUG,
|
||||
string $outputFormat = '[{timestamp}] [{level_name}] {request_id}{channel}{message}',
|
||||
int $fileMode = 0644,
|
||||
?LogRotator $rotator = null,
|
||||
?PathProvider $pathProvider = null
|
||||
) {
|
||||
// Pfad auflösen, falls PathProvider vorhanden
|
||||
if ($this->pathProvider !== null && !str_starts_with($logFile, '/')) {
|
||||
$logFile = $this->pathProvider->resolvePath($logFile);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### LoggerInitializer
|
||||
|
||||
Der `LoggerInitializer` wurde aktualisiert, um `PathProvider` und `LogConfig` zu verwenden:
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
public function __invoke(TypedConfiguration $config, PathProvider $pathProvider): Logger
|
||||
{
|
||||
// Erstelle LogConfig für zentrale Pfadverwaltung
|
||||
$logConfig = new LogConfig($pathProvider);
|
||||
|
||||
// Stelle sicher, dass alle Logverzeichnisse existieren
|
||||
$logConfig->ensureLogDirectoriesExist();
|
||||
|
||||
// ...
|
||||
|
||||
$handlers[] = new FileHandler(
|
||||
$logConfig->getLogPath('app'),
|
||||
$minLevel,
|
||||
'[{timestamp}] [{level_name}] {request_id}{channel}{message}',
|
||||
0644,
|
||||
null,
|
||||
$pathProvider
|
||||
);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### LogViewerInitializer
|
||||
|
||||
Der `LogViewerInitializer` wurde aktualisiert, um `PathProvider` und `LogConfig` zu verwenden:
|
||||
|
||||
```php
|
||||
#[Initializer]
|
||||
public function __invoke(PathProvider $pathProvider): LogViewer
|
||||
{
|
||||
// Erstelle LogConfig für zentrale Pfadverwaltung
|
||||
$logConfig = new LogConfig($pathProvider);
|
||||
|
||||
// Stelle sicher, dass alle Logverzeichnisse existieren
|
||||
$logConfig->ensureLogDirectoriesExist();
|
||||
|
||||
// Verwende die konfigurierten Logpfade aus LogConfig
|
||||
$logPaths = $logConfig->getAllLogPaths();
|
||||
|
||||
return new LogViewer($logPaths);
|
||||
}
|
||||
```
|
||||
|
||||
## Verzeichnisstruktur
|
||||
|
||||
Die Logs werden in einer einheitlichen Verzeichnisstruktur organisiert:
|
||||
|
||||
```
|
||||
logs/
|
||||
├── app/
|
||||
│ ├── app.log
|
||||
│ └── error.log
|
||||
├── security/
|
||||
│ └── security.log
|
||||
└── debug/
|
||||
├── framework.log
|
||||
├── cache.log
|
||||
└── database.log
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die Logpfade können über Umgebungsvariablen konfiguriert werden:
|
||||
|
||||
- `LOG_BASE_PATH`: Basis-Verzeichnis für Logs (Standard: `logs`)
|
||||
- `LOG_PATH`: Pfad für die Haupt-Logdatei (überschreibt `app.log`)
|
||||
- `PHP_ERROR_LOG`: Pfad für PHP-Fehler (überschreibt `error.log`)
|
||||
- `NGINX_ACCESS_LOG`: Pfad für Nginx-Access-Logs
|
||||
- `NGINX_ERROR_LOG`: Pfad für Nginx-Error-Logs
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Logger verwenden
|
||||
|
||||
```php
|
||||
// Logger wird automatisch über Dependency Injection bereitgestellt
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function someMethod()
|
||||
{
|
||||
$this->logger->info("Eine Informationsmeldung");
|
||||
$this->logger->error("Ein Fehler ist aufgetreten", ['context' => 'additional data']);
|
||||
}
|
||||
```
|
||||
|
||||
### LogViewer verwenden
|
||||
|
||||
```php
|
||||
// LogViewer wird automatisch über Dependency Injection bereitgestellt
|
||||
public function __construct(LogViewer $logViewer)
|
||||
{
|
||||
$this->logViewer = $logViewer;
|
||||
}
|
||||
|
||||
public function viewLogs()
|
||||
{
|
||||
// Alle verfügbaren Logs anzeigen
|
||||
$availableLogs = $this->logViewer->getAvailableLogs();
|
||||
|
||||
// Inhalt eines bestimmten Logs abrufen
|
||||
$appLogContent = $this->logViewer->getLogContent('app');
|
||||
}
|
||||
```
|
||||
|
||||
## Vorteile der neuen Implementierung
|
||||
|
||||
1. **Konsistente Pfadverwaltung**: Alle Logpfade werden einheitlich über den `PathProvider` aufgelöst
|
||||
2. **Zentrale Konfiguration**: Alle Logpfade sind an einem Ort definiert und können einfach geändert werden
|
||||
3. **Automatische Verzeichniserstellung**: Logverzeichnisse werden automatisch erstellt, wenn sie nicht existieren
|
||||
4. **Umgebungsvariablen-Support**: Logpfade können über Umgebungsvariablen konfiguriert werden
|
||||
5. **Strukturierte Logs**: Klare Verzeichnisstruktur für verschiedene Logtypen
|
||||
130
docs/middleware-robustness.md
Normal file
130
docs/middleware-robustness.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Middleware System Robustness Improvements
|
||||
|
||||
This document describes the improvements made to the middleware system to make it more robust and provides guidelines for developing robust middlewares.
|
||||
|
||||
## Improvements Implemented
|
||||
|
||||
### 1. Exception Handling
|
||||
|
||||
The `ExceptionHandlingMiddleware` has been positioned as the first middleware in the chain to ensure it can catch exceptions from all other middlewares. This ensures that any exception thrown by a middleware will be properly handled and converted to an appropriate HTTP response.
|
||||
|
||||
### 2. Timeout Mechanism
|
||||
|
||||
A timeout mechanism has been added to the `MiddlewareInvoker` to prevent long-running middlewares from blocking requests indefinitely. If a middleware exceeds its configured timeout, a `MiddlewareTimeoutException` is thrown, which is then caught by the `ExceptionHandlingMiddleware`.
|
||||
|
||||
Timeouts can be configured:
|
||||
- Globally via the `MIDDLEWARE_TIMEOUT` environment variable (default: 5 seconds)
|
||||
- Per middleware class via the `$middlewareTimeouts` parameter in the `MiddlewareInvoker` constructor
|
||||
|
||||
### 3. Circuit Breaker Pattern
|
||||
|
||||
A circuit breaker pattern has been implemented to prevent repeatedly failing middlewares from being executed. The `MiddlewareCircuitBreaker` class uses the existing `CircuitBreaker` module to track middleware failures and temporarily disable middlewares that fail too often.
|
||||
|
||||
The circuit breaker has three states:
|
||||
- **Closed**: Normal operation, middleware is allowed to execute
|
||||
- **Open**: Too many failures, middleware is not allowed to execute
|
||||
- **Half-Open**: After a cooldown period, allows one execution to test if the middleware is working again
|
||||
|
||||
The integration with the existing `CircuitBreaker` module provides several benefits:
|
||||
- Consistent circuit breaker behavior across the application
|
||||
- Shared configuration and monitoring capabilities
|
||||
- Improved reliability through a well-tested implementation
|
||||
|
||||
### 4. Enhanced Logging and Monitoring
|
||||
|
||||
A metrics collection system has been implemented to track middleware execution performance and failures. The `MiddlewareMetricsCollector` class collects and reports metrics about middleware execution, including:
|
||||
|
||||
- Execution time (min, max, average, total)
|
||||
- Success/failure counts and rates
|
||||
- Error types and counts
|
||||
- Last execution time
|
||||
|
||||
## Guidelines for Developing Robust Middlewares
|
||||
|
||||
### 1. Error Handling
|
||||
|
||||
- Always catch and handle exceptions specific to your middleware's functionality
|
||||
- Let unexpected exceptions propagate to be caught by the `ExceptionHandlingMiddleware`
|
||||
- Use appropriate HTTP status codes for different error conditions
|
||||
|
||||
### 2. Performance Considerations
|
||||
|
||||
- Keep middleware execution time as short as possible
|
||||
- For long-running operations, consider using asynchronous processing
|
||||
- Be aware of the configured timeout for your middleware
|
||||
|
||||
### 3. Resource Management
|
||||
|
||||
- Properly manage resources (file handles, database connections, etc.)
|
||||
- Use try-finally blocks to ensure resources are released even if an exception occurs
|
||||
- Be mindful of memory usage, especially when processing large requests
|
||||
|
||||
### 4. Middleware Dependencies
|
||||
|
||||
- Keep dependencies between middlewares to a minimum
|
||||
- Document required middleware execution order in code comments
|
||||
- Use the `MiddlewarePriorityAttribute` to specify the desired execution order
|
||||
|
||||
### 5. Testing
|
||||
|
||||
- Write unit tests for your middleware
|
||||
- Test both success and failure scenarios
|
||||
- Test with various input data, including edge cases
|
||||
- Test timeout scenarios
|
||||
|
||||
## Monitoring and Troubleshooting
|
||||
|
||||
### Metrics
|
||||
|
||||
The middleware system now collects detailed metrics about middleware execution. These metrics can be accessed via the `MiddlewareMetricsCollector` class:
|
||||
|
||||
```php
|
||||
// Get metrics for a specific middleware
|
||||
$metrics = $metricsCollector->getMetrics(MyMiddleware::class);
|
||||
|
||||
// Get metrics for all middlewares
|
||||
$allMetrics = $metricsCollector->getAllMetrics();
|
||||
```
|
||||
|
||||
### Circuit Breaker Status
|
||||
|
||||
You can interact with the circuit breaker for middlewares using the following methods:
|
||||
|
||||
```php
|
||||
// Check if a middleware is allowed to execute
|
||||
$isAllowed = $circuitBreaker->isAllowed(MyMiddleware::class);
|
||||
|
||||
// Get the current state of the circuit for a middleware
|
||||
$state = $circuitBreaker->getState(MyMiddleware::class); // Returns CircuitState enum
|
||||
|
||||
// Reset the circuit for a middleware
|
||||
$circuitBreaker->reset(MyMiddleware::class);
|
||||
|
||||
// Get metrics for a middleware
|
||||
$metrics = $circuitBreaker->getMetrics(MyMiddleware::class);
|
||||
```
|
||||
|
||||
The circuit breaker state can be one of the following:
|
||||
- `CircuitState::CLOSED`: Normal operation, middleware is allowed to execute
|
||||
- `CircuitState::OPEN`: Too many failures, middleware is not allowed to execute
|
||||
- `CircuitState::HALF_OPEN`: Testing if the middleware is working again
|
||||
|
||||
### Logging
|
||||
|
||||
The middleware system logs detailed information about middleware execution, including:
|
||||
- Start and end of middleware execution
|
||||
- Execution time
|
||||
- Errors and exceptions
|
||||
- Circuit breaker state changes
|
||||
|
||||
Check the application logs for entries with the middleware name to troubleshoot issues.
|
||||
|
||||
## Conclusion
|
||||
|
||||
These improvements make the middleware system more robust by:
|
||||
- Ensuring exceptions are properly handled
|
||||
- Preventing long-running middlewares from blocking requests
|
||||
- Preventing repeatedly failing middlewares from being executed
|
||||
- Providing detailed metrics for monitoring and troubleshooting
|
||||
|
||||
By following the guidelines in this document, you can develop robust middlewares that contribute to a stable and reliable application.
|
||||
67
docs/next-steps.md
Normal file
67
docs/next-steps.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Nächste Schritte (Kurzplan)
|
||||
|
||||
Dieser Kurzplan macht die Top-Prioritäten aus `docs/tasks.md` sofort umsetzbar. Er ist so geschrieben, dass du ihn Schritt für Schritt abarbeiten kannst. Jeder Schritt verweist auf die detaillierten Aufgaben in `docs/tasks.md`.
|
||||
|
||||
Hinweis: Befehle lokal ausführen; Projekt nutzt Docker. Siehe auch „Project Guidelines“ im Repository.
|
||||
|
||||
## Heute (Setup & Checks)
|
||||
- [ ] Abhängigkeiten installieren und Basis-Checks ausführen
|
||||
- [ ] Composer-Install: `composer install`
|
||||
- [ ] Docker Images bauen: `./build.sh`
|
||||
- [ ] Stack starten: `docker-compose up -d`
|
||||
- [ ] Healthcheck: App erreichbar unter http://localhost:${APP_PORT:-8000}
|
||||
- [ ] Grundchecks lokal laufen lassen
|
||||
- [ ] Linter (sofern konfiguriert): `composer cs`
|
||||
- [ ] PHPStan: `composer phpstan`
|
||||
- [ ] Tests: `./vendor/bin/pest`
|
||||
|
||||
Siehe: tasks.md → 1 (Baseline), 2 (CI/CD), 3 (Static Analysis), 15 (Testing)
|
||||
|
||||
## Als Nächstes (Kurzzyklus, 1–2 Tage)
|
||||
- [ ] Static Analysis schärfen (tasks.md → Punkt 3)
|
||||
- [ ] Selektiv `declare(strict_types=1);` in Hot-Paths ergänzen (z. B. src/Framework/Http/*, src/Framework/Config/*)
|
||||
- [ ] PHPStan auf aktueller Codebasis „sauber“ bekommen (Fehler beheben statt pauschal ignorieren)
|
||||
- [ ] Baseline überprüfen und ggf. reduzieren
|
||||
- [ ] Kritische Tests ergänzen (tasks.md → Punkte 10, 11, 14, 15)
|
||||
- [ ] HTTP: Tests für HttpRequest/Query, Fehlerpfade, Middleware-Kette
|
||||
- [ ] Feature Flags: Tests für CacheFeatureFlagRepository inkl. Negative Caching Verhalten
|
||||
- [ ] WAF/Rate Limiting: Basisfälle & Fehlerszenarien abdecken
|
||||
|
||||
## Diese Woche (3–5 Tage)
|
||||
- [ ] CI/CD-Pipeline aufsetzen/härten (tasks.md → Punkt 2)
|
||||
- [ ] Jobs: Lint, PHPStan, Pest, `composer audit`
|
||||
- [ ] Optional: Coverage (XDEBUG_MODE=coverage) mit Minimal-Schwelle
|
||||
- [ ] Pre-commit Hooks (z. B. CaptainHook) lokal aktivieren
|
||||
- [ ] Konfiguration & Secrets zentralisieren (tasks.md → Punkt 5)
|
||||
- [ ] `.env.example` prüfen/ergänzen (fehlende Keys, Defaults)
|
||||
- [ ] Boot-time Validierung sicherstellen (ConfigValidator bereits integriert)
|
||||
- [ ] Secrets über ENV/Secret Store, nicht im Repo
|
||||
- [ ] Observability-Basis (tasks.md → Punkte 6, 8)
|
||||
- [ ] Strukturierte Logs & Metriken-Events (Cache Hit/Miss/Errors) definieren
|
||||
- [ ] Einfache Zähler/Timer an kritischen Stellen einhängen
|
||||
|
||||
## Konkrete, kleine Fixes (Schnelle Gewinne)
|
||||
- [ ] Feature Flags Negative-Caching absichern (tasks.md → 20)
|
||||
- [ ] Prüfen, dass der Marker (`NOT_FOUND`) nicht mit echten Werten kollidiert; ggf. Sentinel-ValueObject einführen
|
||||
- [ ] View Cache nicht einchecken (tasks.md → 13, 20)
|
||||
- [ ] Sicherstellen, dass `src/Framework/View/cache/*` in `.gitignore` ausgeschlossen ist
|
||||
|
||||
## Validierung nach Umsetzung
|
||||
- [ ] Alle Checks lokal: `composer cs && composer phpstan && ./vendor/bin/pest && composer audit`
|
||||
- [ ] Docker-Stack Neustart: `docker-compose restart`
|
||||
- [ ] Manuelle Smoke-Tests der wichtigsten Endpunkte
|
||||
|
||||
## Entscheidungspunkte (bitte festlegen)
|
||||
- [ ] Bevorzugtes CI-System (GitHub Actions, GitLab CI, …)?
|
||||
- [ ] Minimal abzudeckende Coverage-Schwelle (z. B. 40% initial)?
|
||||
- [ ] Ziel-PHPStan-Level kurzfristig (8 → 9)?
|
||||
|
||||
---
|
||||
Kurzlinke Referenzen:
|
||||
- CI/CD → tasks.md §2
|
||||
- Static Analysis → tasks.md §3
|
||||
- HTTP Layer → tasks.md §10
|
||||
- Feature Flags → tasks.md §11 & §20
|
||||
- WAF/Rate Limiting → tasks.md §7
|
||||
- Konfiguration/Secrets → tasks.md §5
|
||||
- Observability → tasks.md §6 & §8
|
||||
252
docs/plan.md
Normal file
252
docs/plan.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# Improvement Plan for michaelschiemer.de
|
||||
|
||||
This document outlines a comprehensive improvement plan for the michaelschiemer.de website project. The plan is based on an analysis of the current project state, documentation, and requirements. It is organized by theme or system area, with each section containing specific improvements and their rationale.
|
||||
|
||||
## 1. Architecture and Structure
|
||||
|
||||
### 1.1 Complete Plugin Architecture Implementation
|
||||
|
||||
**Current State**: The framework has implemented a module system to organize and encapsulate related components, but the plugin architecture for extending core functionality is still pending as indicated in the roadmap tasks.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement the plugin architecture to allow for easier extension of core functionality
|
||||
- Create standardized extension points for all major framework components
|
||||
- Develop a plugin discovery and registration system
|
||||
- Provide documentation and examples for plugin development
|
||||
|
||||
**Rationale**: Completing the plugin architecture aligns with the framework's core principle of extensibility and is a key component of Milestone 4 (Extensibility and Ecosystem). This will enable developers to extend the framework without modifying core code, improving maintainability and flexibility.
|
||||
|
||||
### 1.2 Standardize Language Usage
|
||||
|
||||
**Current State**: The codebase and documentation use a mix of German and English, creating inconsistency and potential confusion. This is identified as an incomplete task in the improvement tasks list.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Choose English as the primary language for code (class names, method names, variables, comments)
|
||||
- Maintain German for user-facing content and documentation where appropriate
|
||||
- Create a language style guide for contributors
|
||||
- Gradually migrate existing code to follow the new standards
|
||||
|
||||
**Rationale**: Consistent language usage improves readability and reduces cognitive load for developers. English is the standard language for programming and will make the codebase more accessible to a wider range of contributors.
|
||||
|
||||
### 1.3 Optimize Dependency Injection Configuration
|
||||
|
||||
**Current State**: While significant work has been done to optimize the DI container (marked as completed in roadmap tasks), there are still opportunities for improvement in service organization and documentation.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement service tagging for better organization of related services
|
||||
- Create comprehensive documentation for DI container configuration patterns and best practices
|
||||
- Add performance metrics for container initialization
|
||||
- Develop examples for common DI container usage patterns
|
||||
|
||||
**Rationale**: Optimized DI configuration will improve application bootstrap time and overall performance. Better documentation will help developers understand and use the DI container effectively, which is essential for the framework's extensibility goals.
|
||||
|
||||
## 2. Code Quality
|
||||
|
||||
### 2.1 Complete TODO Items in Codebase
|
||||
|
||||
**Current State**: Several TODO items remain in the codebase, particularly in the migration system, as noted in the roadmap tasks document.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement migration logic in `MigrationGenerator.php`
|
||||
- Implement rollback functionality in `MigrationGenerator.php`
|
||||
- Review and address any remaining TODO items throughout the codebase
|
||||
- Create a process for tracking and prioritizing TODO items
|
||||
|
||||
**Rationale**: Completing TODO items will improve code completeness and reliability. The migration system is particularly important as it's a key component for database management and deployment, which is essential for the project's stability and maintainability.
|
||||
|
||||
### 2.2 Improve Documentation Coverage
|
||||
|
||||
**Current State**: Many public methods and classes lack comprehensive PHPDoc comments, as indicated in the roadmap tasks document.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Add comprehensive PHPDoc comments to all public methods and classes
|
||||
- Ensure documentation follows PSR standards
|
||||
- Include examples in documentation for complex methods
|
||||
- Add return type information and parameter descriptions
|
||||
|
||||
**Rationale**: Comprehensive code documentation improves developer experience, makes the codebase more maintainable, and helps new contributors understand the system more quickly. This aligns with the project's focus on developer experience and supports the upcoming Milestone 4 (Extensibility and Ecosystem).
|
||||
|
||||
### 2.3 Implement Mutation Testing
|
||||
|
||||
**Current State**: While the project has good test coverage, mutation testing to verify test quality is not yet implemented, as noted in the roadmap tasks.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement mutation testing framework (such as Infection PHP)
|
||||
- Configure mutation testing to run on critical components
|
||||
- Add mutation testing to CI/CD pipeline
|
||||
- Create guidelines for writing mutation-resistant tests
|
||||
|
||||
**Rationale**: Mutation testing helps identify weaknesses in test suites by modifying code and checking if tests fail as expected. This improves overall test quality and code reliability, supporting the testability principle in the architecture documentation.
|
||||
|
||||
## 3. Testing
|
||||
|
||||
### 3.1 Complete Test Coverage for Remaining Components
|
||||
|
||||
**Current State**: Several components still lack adequate test coverage, including Analytics, API, Attributes, and Auth, as detailed in the roadmap tasks document.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Add unit tests for Analytics components
|
||||
- Create tests for API components
|
||||
- Implement tests for Attributes system
|
||||
- Develop comprehensive tests for Auth components
|
||||
- Ensure all new tests follow the project's testing standards
|
||||
|
||||
**Rationale**: Comprehensive test coverage ensures reliability and makes it easier to refactor code with confidence. The identified components are critical to the system's functionality and security, making their testing essential for the project's overall quality.
|
||||
|
||||
### 3.2 Enhance Testing Infrastructure
|
||||
|
||||
**Current State**: The testing infrastructure could be improved to support more advanced testing scenarios, particularly for canary releases as mentioned in the roadmap tasks.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement canary testing for critical features
|
||||
- Add more sophisticated mocking capabilities for external dependencies
|
||||
- Create testing utilities for common testing scenarios
|
||||
- Improve test data generation and management
|
||||
|
||||
**Rationale**: Enhanced testing infrastructure will make it easier to write comprehensive tests and improve the overall quality of the test suite. This supports the testability principle in the architecture documentation and prepares the project for more advanced deployment strategies.
|
||||
|
||||
## 4. Performance Optimization
|
||||
|
||||
### 4.1 Implement Advanced Caching Strategies
|
||||
|
||||
**Current State**: While basic caching is implemented (marked as completed in the features list), there are opportunities for more advanced caching strategies as the project moves toward Milestone 3 (Security and Performance).
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement hierarchical cache tags for more precise invalidation
|
||||
- Add cache warming for critical data during deployment
|
||||
- Implement content-aware caching based on data change frequency
|
||||
- Create cache analytics to identify cache efficiency
|
||||
|
||||
**Rationale**: Advanced caching strategies will further improve application performance and reduce server load, particularly for high-traffic scenarios. This aligns with the performance principle in the architecture documentation and supports the goals of Milestone 3.
|
||||
|
||||
### 4.2 Optimize HTTP Request Processing
|
||||
|
||||
**Current State**: The HTTP request processing pipeline could be further optimized to support the performance goals of Milestone 3.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement request batching for API endpoints where appropriate
|
||||
- Optimize middleware pipeline execution
|
||||
- Add conditional processing based on request characteristics
|
||||
- Implement HTTP/2 server push for critical resources
|
||||
|
||||
**Rationale**: Efficient HTTP request processing is crucial for good user experience. Optimizations in this area will benefit every request to the application and support the performance principle in the architecture documentation.
|
||||
|
||||
## 5. Documentation
|
||||
|
||||
### 5.1 Create Comprehensive API Documentation
|
||||
|
||||
**Current State**: API documentation is incomplete or missing for some components, as noted in the roadmap tasks document.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Document all public APIs with examples and explanations
|
||||
- Create an interactive API explorer using OpenAPI/Swagger
|
||||
- Add versioning information to API documentation
|
||||
- Include authentication and authorization requirements
|
||||
|
||||
**Rationale**: Comprehensive API documentation makes it easier for developers to use the framework correctly and efficiently. This is especially important as the project moves toward Milestone 4 (Extensibility and Ecosystem) and supports the project's goal of improving developer experience.
|
||||
|
||||
### 5.2 Develop User and Developer Guides
|
||||
|
||||
**Current State**: User guides for key features and developer onboarding documentation are incomplete, as indicated in the roadmap tasks.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Create user guides for all key features
|
||||
- Develop comprehensive developer onboarding documentation
|
||||
- Add troubleshooting guides for common issues
|
||||
- Create a glossary of project-specific terms
|
||||
|
||||
**Rationale**: Better documentation reduces the time it takes for new users and developers to become productive and reduces the burden on existing team members for support. This supports the project's goal of improving developer experience and is essential for the success of Milestone 4.
|
||||
|
||||
### 5.3 Document Architecture and Design Decisions
|
||||
|
||||
**Current State**: While some architecture documentation exists, it could be enhanced with more details on design decisions, as noted in the roadmap tasks.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Create architectural decision records (ADRs) for major design choices
|
||||
- Document the rationale behind architectural patterns used
|
||||
- Add diagrams illustrating component interactions
|
||||
- Create documentation on how to extend the architecture
|
||||
|
||||
**Rationale**: Documenting architecture and design decisions helps maintain architectural integrity as the project evolves and helps new developers understand why certain approaches were chosen. This supports the project's modularity and extensibility principles.
|
||||
|
||||
## 6. Security Enhancements
|
||||
|
||||
### 6.1 Implement Feedback System for WAF
|
||||
|
||||
**Current State**: The Web Application Firewall (WAF) has been implemented with machine learning-based anomaly detection, but the feedback system is still planned, as indicated in the features list.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement the planned feedback system for the WAF
|
||||
- Create a mechanism for reporting false positives and false negatives
|
||||
- Develop a learning system to improve detection accuracy over time
|
||||
- Add analytics for WAF performance and effectiveness
|
||||
|
||||
**Rationale**: A feedback system for the WAF will improve its accuracy and effectiveness over time, enhancing the security of the application. This aligns with the security principle in the architecture documentation and supports the goals of Milestone 3 (Security and Performance).
|
||||
|
||||
### 6.2 Implement Canary Releases for Critical Features
|
||||
|
||||
**Current State**: While blue-green deployments are implemented, canary releases for critical features are not yet available, as noted in the roadmap tasks.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement canary release infrastructure
|
||||
- Create monitoring and rollback mechanisms for canary deployments
|
||||
- Develop traffic routing for canary releases
|
||||
- Add analytics for canary deployment performance
|
||||
|
||||
**Rationale**: Canary releases allow for safer deployment of critical features by gradually exposing them to a subset of users. This reduces the risk of security or performance issues affecting all users and supports the project's goals for reliability and quality.
|
||||
|
||||
## 7. DevOps and Infrastructure
|
||||
|
||||
### 7.1 Enhance Monitoring and Observability
|
||||
|
||||
**Current State**: While basic monitoring is in place (marked as completed in the roadmap tasks), there are opportunities to enhance observability for better system insights.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement distributed tracing across all components
|
||||
- Add business metrics monitoring
|
||||
- Create custom dashboards for different stakeholder needs
|
||||
- Implement anomaly detection for system metrics
|
||||
|
||||
**Rationale**: Enhanced monitoring and observability will help detect and resolve issues more quickly, improving overall system reliability and performance. This supports the project's goals for reliability and quality and prepares for the enterprise features planned in Milestone 5.
|
||||
|
||||
### 7.2 Optimize Deployment Pipeline
|
||||
|
||||
**Current State**: The deployment pipeline is functional (marked as completed in the roadmap tasks) but could be optimized for efficiency and reliability.
|
||||
|
||||
**Proposed Changes**:
|
||||
- Implement parallel testing to reduce pipeline execution time
|
||||
- Add deployment verification tests
|
||||
- Create deployment impact analysis
|
||||
- Implement automated environment scaling based on deployment needs
|
||||
|
||||
**Rationale**: An optimized deployment pipeline reduces deployment time and risk, allowing for more frequent and reliable releases. This supports the project's goals for quality and developer experience.
|
||||
|
||||
## Implementation Timeline and Priorities
|
||||
|
||||
The improvements outlined above should be prioritized based on their impact and alignment with the project's milestones:
|
||||
|
||||
### Short-term (1-3 months)
|
||||
- Complete TODO items in codebase (2.1)
|
||||
- Complete test coverage for remaining components (3.1)
|
||||
- Implement advanced caching strategies (4.1)
|
||||
- Implement feedback system for WAF (6.1)
|
||||
|
||||
### Medium-term (3-6 months)
|
||||
- Complete plugin architecture implementation (1.1)
|
||||
- Improve documentation coverage (2.2)
|
||||
- Create comprehensive API documentation (5.1)
|
||||
- Enhance monitoring and observability (7.1)
|
||||
|
||||
### Long-term (6-12 months)
|
||||
- Standardize language usage (1.2)
|
||||
- Implement mutation testing (2.3)
|
||||
- Document architecture and design decisions (5.3)
|
||||
- Implement canary releases for critical features (6.2)
|
||||
|
||||
## Conclusion
|
||||
|
||||
This improvement plan addresses the key areas identified in the project documentation and aligns with the architectural principles and milestone planning. By implementing these changes, the project will become more maintainable, secure, performant, and developer-friendly.
|
||||
|
||||
The plan is designed to be flexible, allowing for adjustments based on changing priorities and new requirements. Regular reviews of the plan's progress will help ensure that the project continues to move in the right direction.
|
||||
|
||||
The plan focuses particularly on completing the remaining tasks for Milestone 2 (Extended Feature Set) and preparing for Milestone 3 (Security and Performance), while also laying groundwork for future milestones. The emphasis on documentation, testing, and architecture will ensure the project remains maintainable and extensible as it grows in complexity and scope.
|
||||
83
docs/roadmap/features.md
Normal file
83
docs/roadmap/features.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Framework Features
|
||||
|
||||
## Core Features
|
||||
- [x] Routing mit Unterstützung für PHP-Attribute
|
||||
- [x] Dependency Injection Container
|
||||
- [x] Request/Response Abstraktion
|
||||
- [x] Template-Engine
|
||||
- [x] Error/Exception Handling
|
||||
- [x] Konfigurationssystem
|
||||
|
||||
## Database Features
|
||||
- [x] PDO-Wrapper
|
||||
- [x] Query Builder
|
||||
- [x] Migrations-System
|
||||
- [x] Schema Manager
|
||||
- [ ] Entity-Mapping (optional)
|
||||
|
||||
## Security Features
|
||||
- [x] CSRF-Schutz
|
||||
- [x] XSS-Filtierung
|
||||
- [x] Input-Validierung
|
||||
- [x] Authentifizierung
|
||||
- [x] Autorisierung/Rechtemanagement
|
||||
- [x] Security Headers
|
||||
- [x] Request Signing
|
||||
- [x] Session-Sicherheit
|
||||
- [x] Sicherheits-Ereignisbehandlung
|
||||
|
||||
## Web Application Firewall (WAF)
|
||||
- [x] Machine Learning basierte Anomalieerkennung
|
||||
- [x] Feature-Extraktion aus Requests
|
||||
- [x] Statistische Anomaliedetektion
|
||||
- [x] Clustering-basierte Anomaliedetektion
|
||||
- [x] Baseline-Management
|
||||
- [x] Leistungsoptimierung
|
||||
- [ ] Feedback-System (geplant)
|
||||
|
||||
## Validation Framework
|
||||
- [x] Attribut-basierte Validierung
|
||||
- [x] Umfangreiche Validierungsregeln
|
||||
- [x] Validierungsgruppen
|
||||
- [x] Formular-Integration
|
||||
- [x] API-Validierung
|
||||
- [x] Benutzerdefinierte Validierungsregeln
|
||||
|
||||
## Module: Analytics
|
||||
- [x] Event-Tracking
|
||||
- [x] Benutzer-Tracking
|
||||
- [x] Seiten-Tracking
|
||||
- [x] Fehler-Tracking
|
||||
- [x] Leistungsmetriken
|
||||
- [x] Dashboard
|
||||
- [x] Speicherung in verschiedenen Backends
|
||||
- [x] Middleware-System
|
||||
|
||||
## Module: Content
|
||||
- [x] Blog-System
|
||||
- [x] Markdown-Support
|
||||
- [x] Medienbibliothek
|
||||
- [x] SEO-Optimierung
|
||||
- [ ] Kommentarsystem
|
||||
|
||||
## Admin Interface
|
||||
- [x] Dashboard
|
||||
- [x] Content-Editor
|
||||
- [x] Benutzer-/Rechteverwaltung
|
||||
- [x] Statistiken
|
||||
|
||||
## Performance
|
||||
- [x] Caching-System
|
||||
- [x] Lazy Loading
|
||||
- [x] Asynchrone Verarbeitung
|
||||
- [x] Leistungsüberwachung
|
||||
- [x] Optimierte Bootstrapping
|
||||
|
||||
## Developer Experience
|
||||
- [x] Umfangreiche Logging-Funktionen
|
||||
- [x] Debugging-Tools
|
||||
- [x] Fehlerberichterstattung
|
||||
- [x] Entwicklermodus
|
||||
- [x] Konsolen-Befehle
|
||||
- [x] Code-Generatoren
|
||||
- [x] Testunterstützung
|
||||
289
docs/roadmap/milestones.md
Normal file
289
docs/roadmap/milestones.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Meilensteine
|
||||
|
||||
Diese Dokumentation beschreibt die geplanten Meilensteine für die Entwicklung des Frameworks. Meilensteine sind wichtige Punkte in der Entwicklung, die einen signifikanten Fortschritt oder eine Version markieren.
|
||||
|
||||
## Überblick über die Meilensteinplanung
|
||||
|
||||
Die Meilensteinplanung hilft dabei, die Entwicklung des Frameworks zu strukturieren und zu priorisieren. Jeder Meilenstein repräsentiert einen Satz von Funktionen und Verbesserungen, die zusammen einen bedeutenden Schritt in der Evolution des Frameworks darstellen.
|
||||
|
||||
## Aktuelle Meilensteine
|
||||
|
||||
### Meilenstein 1: Grundlegende Framework-Struktur (Abgeschlossen)
|
||||
|
||||
**Zeitraum:** Q1 2023
|
||||
|
||||
**Beschreibung:** Etablierung der grundlegenden Architektur und Kernkomponenten des Frameworks.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Dependency Injection Container
|
||||
- Routing-System
|
||||
- MVC-Struktur
|
||||
- Grundlegende HTTP-Abstraktion
|
||||
- Einfache Datenbank-Abstraktion
|
||||
- Konfigurationssystem
|
||||
|
||||
**Status:** Abgeschlossen
|
||||
|
||||
### Meilenstein 2: Erweitertes Feature-Set (Aktuell)
|
||||
|
||||
**Zeitraum:** Q2-Q3 2023
|
||||
|
||||
**Beschreibung:** Erweiterung des Frameworks um fortgeschrittene Funktionen und Verbesserung der Benutzerfreundlichkeit.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Erweiterte Validierung
|
||||
- Formular-Handling
|
||||
- Authentifizierung und Autorisierung
|
||||
- Caching-System
|
||||
- Erweiterte Datenbankfunktionen
|
||||
- Migrations-System
|
||||
- Kommandozeilen-Interface
|
||||
- Ereignissystem
|
||||
|
||||
**Status:** In Bearbeitung (80% abgeschlossen)
|
||||
|
||||
### Meilenstein 3: Sicherheit und Leistung
|
||||
|
||||
**Zeitraum:** Q4 2023 - Q1 2024
|
||||
|
||||
**Beschreibung:** Fokus auf Sicherheitsverbesserungen und Leistungsoptimierungen.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Web Application Firewall (WAF)
|
||||
- CSRF-Schutz
|
||||
- XSS-Schutz
|
||||
- Content Security Policy
|
||||
- Rate Limiting
|
||||
- Leistungsoptimierungen
|
||||
- Caching-Verbesserungen
|
||||
- Lazy Loading
|
||||
- Profilierung und Benchmarking
|
||||
|
||||
**Status:** Geplant
|
||||
|
||||
### Meilenstein 4: Erweiterbarkeit und Ökosystem
|
||||
|
||||
**Zeitraum:** Q2-Q3 2024
|
||||
|
||||
**Beschreibung:** Verbesserung der Erweiterbarkeit des Frameworks und Aufbau eines Ökosystems.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Plugin-System
|
||||
- Modulares Design
|
||||
- Paket-Manager
|
||||
- Erweiterungspunkte
|
||||
- Community-Beitragsrichtlinien
|
||||
- Dokumentationsverbesserungen
|
||||
- Beispielanwendungen
|
||||
- Starter-Kits
|
||||
|
||||
**Status:** Geplant
|
||||
|
||||
### Meilenstein 5: Enterprise-Features
|
||||
|
||||
**Zeitraum:** Q4 2024 - Q1 2025
|
||||
|
||||
**Beschreibung:** Implementierung von Funktionen für Enterprise-Anwendungen.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Horizontale Skalierbarkeit
|
||||
- Verteilte Caches
|
||||
- Message Queues
|
||||
- Batch-Verarbeitung
|
||||
- Reporting und Analytics
|
||||
- Multi-Tenancy
|
||||
- LDAP/Active Directory-Integration
|
||||
- Single Sign-On (SSO)
|
||||
|
||||
**Status:** Geplant
|
||||
|
||||
## Detaillierte Meilensteinpläne
|
||||
|
||||
### Meilenstein 2: Erweitertes Feature-Set (Aktuell)
|
||||
|
||||
#### Validierung (Abgeschlossen)
|
||||
- Implementierung eines umfassenden Validierungssystems
|
||||
- Unterstützung für verschiedene Validierungsregeln
|
||||
- Benutzerdefinierte Validierungsregeln
|
||||
- Validierung von Arrays und verschachtelten Daten
|
||||
- Validierungsnachrichten und -lokalisierung
|
||||
|
||||
#### Formular-Handling (Abgeschlossen)
|
||||
- Formular-Klassen
|
||||
- CSRF-Schutz für Formulare
|
||||
- Formular-Validierung
|
||||
- Datei-Uploads
|
||||
- Formular-Rendering
|
||||
|
||||
#### Authentifizierung und Autorisierung (In Bearbeitung)
|
||||
- Benutzerauthentifizierung
|
||||
- Sitzungsverwaltung
|
||||
- Rollenbasierte Zugriffskontrolle
|
||||
- Fähigkeitsbasierte Zugriffskontrolle
|
||||
- Richtlinien für die Autorisierung
|
||||
- Passwort-Hashing und -Verifizierung
|
||||
- Passwort-Zurücksetzung
|
||||
|
||||
#### Caching-System (In Bearbeitung)
|
||||
- Cache-Abstraktionsschicht
|
||||
- Verschiedene Cache-Backends (Datei, Redis, Memcached)
|
||||
- Cache-Tags
|
||||
- Cache-Invalidierung
|
||||
- Cache-Middleware
|
||||
|
||||
#### Erweiterte Datenbankfunktionen (Geplant)
|
||||
- Beziehungen zwischen Modellen
|
||||
- Eager Loading
|
||||
- Query Builder-Verbesserungen
|
||||
- Transaktionen
|
||||
- Ereignisse für Modelle
|
||||
- Soft Deletes
|
||||
- Timestamps
|
||||
|
||||
#### Migrations-System (Geplant)
|
||||
- Datenbank-Migrationen
|
||||
- Schema Builder
|
||||
- Seeding
|
||||
- Rollbacks
|
||||
- Migration-Status
|
||||
|
||||
#### Kommandozeilen-Interface (Abgeschlossen)
|
||||
- Befehlsregistrierung
|
||||
- Eingabe/Ausgabe-Abstraktion
|
||||
- Interaktive Befehle
|
||||
- Fortschrittsanzeige
|
||||
- Farbige Ausgabe
|
||||
|
||||
#### Ereignissystem (Abgeschlossen)
|
||||
- Ereignisregistrierung
|
||||
- Ereignislistener
|
||||
- Ereignisabonnenten
|
||||
- Asynchrone Ereignisse
|
||||
- Ereignispriorisierung
|
||||
|
||||
### Meilenstein 3: Sicherheit und Leistung
|
||||
|
||||
#### Web Application Firewall (WAF) (In Bearbeitung)
|
||||
- Erkennung und Blockierung von SQL-Injection
|
||||
- Erkennung und Blockierung von XSS
|
||||
- Erkennung und Blockierung von Command Injection
|
||||
- Erkennung und Blockierung von Path Traversal
|
||||
- Maschinelles Lernen zur Erkennung von Anomalien
|
||||
|
||||
#### CSRF-Schutz (Abgeschlossen)
|
||||
- CSRF-Token-Generierung
|
||||
- CSRF-Token-Validierung
|
||||
- CSRF-Middleware
|
||||
- CSRF-Schutz für AJAX-Anfragen
|
||||
|
||||
#### XSS-Schutz (In Bearbeitung)
|
||||
- Automatische Ausgabebereinigung
|
||||
- HTML-Purifier
|
||||
- Content Security Policy
|
||||
- X-XSS-Protection-Header
|
||||
|
||||
#### Content Security Policy (Geplant)
|
||||
- CSP-Header-Generierung
|
||||
- CSP-Richtlinien
|
||||
- CSP-Reporting
|
||||
- CSP-Nonce
|
||||
- CSP-Hashes
|
||||
|
||||
#### Rate Limiting (Geplant)
|
||||
- Rate Limiting-Middleware
|
||||
- Verschiedene Strategien (IP, Benutzer, Route)
|
||||
- Konfigurierbare Limits
|
||||
- Response-Header für Rate Limiting
|
||||
- Speicherung von Rate Limiting-Daten
|
||||
|
||||
#### Leistungsoptimierungen (Geplant)
|
||||
- Code-Optimierungen
|
||||
- Lazy Loading
|
||||
- Eager Loading
|
||||
- Caching-Verbesserungen
|
||||
- Datenbankoptimierungen
|
||||
- Komprimierung
|
||||
- Minifizierung
|
||||
|
||||
#### Caching-Verbesserungen (Geplant)
|
||||
- Verbesserte Cache-Tags
|
||||
- Cache-Invalidierung
|
||||
- Cache-Warming
|
||||
- Cache-Prefetching
|
||||
- Verteilte Caches
|
||||
|
||||
#### Profilierung und Benchmarking (Geplant)
|
||||
- Leistungsprofilierung
|
||||
- Datenbankabfrage-Protokollierung
|
||||
- Speicherverbrauch-Tracking
|
||||
- Ausführungszeit-Tracking
|
||||
- Benchmarking-Tools
|
||||
|
||||
## Versionsplanung
|
||||
|
||||
Die Meilensteine sind mit der Versionsplanung des Frameworks verknüpft:
|
||||
|
||||
### Version 1.0 (Meilenstein 2)
|
||||
- Erste stabile Version
|
||||
- Vollständiges Feature-Set für die meisten Webanwendungen
|
||||
- Gut dokumentiert und getestet
|
||||
- Erwartetes Veröffentlichungsdatum: Q3 2023
|
||||
|
||||
### Version 2.0 (Meilenstein 3)
|
||||
- Fokus auf Sicherheit und Leistung
|
||||
- Verbesserte Entwicklererfahrung
|
||||
- Erweiterte Dokumentation
|
||||
- Erwartetes Veröffentlichungsdatum: Q1 2024
|
||||
|
||||
### Version 3.0 (Meilenstein 4)
|
||||
- Erweiterbarkeit und Ökosystem
|
||||
- Plugin-System
|
||||
- Community-Beiträge
|
||||
- Erwartetes Veröffentlichungsdatum: Q3 2024
|
||||
|
||||
### Version 4.0 (Meilenstein 5)
|
||||
- Enterprise-Features
|
||||
- Skalierbarkeit
|
||||
- Integration mit Unternehmensumgebungen
|
||||
- Erwartetes Veröffentlichungsdatum: Q1 2025
|
||||
|
||||
## Priorisierung und Entscheidungsfindung
|
||||
|
||||
Die Priorisierung von Funktionen und Verbesserungen basiert auf den folgenden Kriterien:
|
||||
|
||||
1. **Benutzerbedarf**: Funktionen, die von vielen Benutzern benötigt werden, haben Priorität.
|
||||
2. **Sicherheit**: Sicherheitsverbesserungen haben immer hohe Priorität.
|
||||
3. **Leistung**: Leistungsverbesserungen sind wichtig für die Benutzererfahrung.
|
||||
4. **Entwicklererfahrung**: Funktionen, die die Entwicklung erleichtern, sind wichtig.
|
||||
5. **Wartbarkeit**: Verbesserungen, die die Wartbarkeit des Frameworks erhöhen, sind langfristig wichtig.
|
||||
|
||||
## Beitrag zu Meilensteinen
|
||||
|
||||
Wenn Sie zu einem Meilenstein beitragen möchten:
|
||||
|
||||
1. Überprüfen Sie den aktuellen Status des Meilensteins.
|
||||
2. Identifizieren Sie Funktionen oder Verbesserungen, an denen Sie arbeiten möchten.
|
||||
3. Erstellen Sie ein Issue, das beschreibt, woran Sie arbeiten möchten.
|
||||
4. Diskutieren Sie Ihren Ansatz mit dem Kernteam.
|
||||
5. Erstellen Sie einen Pull Request mit Ihrer Implementierung.
|
||||
|
||||
Weitere Informationen finden Sie in der [Pull Request-Anleitung](../contributing/pull-requests.md).
|
||||
|
||||
## Meilenstein-Tracking
|
||||
|
||||
Der Fortschritt der Meilensteine wird in den folgenden Quellen verfolgt:
|
||||
|
||||
1. **GitHub Issues**: Jeder Meilenstein hat ein entsprechendes GitHub-Milestone, dem Issues zugeordnet sind.
|
||||
2. **Projektboard**: Ein Projektboard zeigt den Fortschritt der Aufgaben für jeden Meilenstein.
|
||||
3. **Dokumentation**: Diese Dokumentation wird regelmäßig aktualisiert, um den aktuellen Status der Meilensteine widerzuspiegeln.
|
||||
|
||||
## Änderungen an Meilensteinen
|
||||
|
||||
Meilensteine können sich im Laufe der Zeit ändern, basierend auf Feedback, neuen Anforderungen und Ressourcenverfügbarkeit. Änderungen an Meilensteinen werden dokumentiert und kommuniziert, um Transparenz zu gewährleisten.
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- [Features](features.md): Detaillierte Beschreibung der geplanten Features.
|
||||
- [Tasks](tasks.md): Aufgaben, die für die Implementierung der Features erforderlich sind.
|
||||
- [Architekturübersicht](../architecture/overview.md): Überblick über die Architektur des Frameworks.
|
||||
- [Contributing](../contributing/documentation.md): Informationen zum Beitragen zum Framework.
|
||||
138
docs/roadmap/tasks.md
Normal file
138
docs/roadmap/tasks.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Improvement Tasks
|
||||
|
||||
This document contains a comprehensive list of actionable improvement tasks for the project. Each task is specific, actionable, and can be checked off when completed.
|
||||
|
||||
## Architecture
|
||||
|
||||
1. [x] Evaluate and optimize the dependency graph in the DI container to reduce initialization time
|
||||
2. [x] Implement a service locator pattern for non-critical services to reduce container complexity
|
||||
3. [x] Create a unified configuration system that consolidates the various config approaches (env files, JSON configs, code-based configs)
|
||||
4. [x] Refactor the bootstrapping process to support lazy-loading of non-essential services
|
||||
5. [x] Implement a module system to better organize and encapsulate related components
|
||||
6. [x] Standardize error handling across all framework components
|
||||
7. [x] Create a comprehensive application lifecycle documentation with sequence diagrams
|
||||
8. [ ] Implement a plugin architecture to allow for easier extension of core functionality
|
||||
|
||||
## Code Quality
|
||||
|
||||
9. [ ] Complete all TODO items in the codebase:
|
||||
- [ ] Implement migration logic in `MigrationGenerator.php`
|
||||
- [ ] Implement rollback in `MigrationGenerator.php`
|
||||
- [x] Return proper responses in various handlers
|
||||
- [x] Implement actual login logic in `LoginUserHandler.php`
|
||||
- [x] Use Clock instead of date() in `ShowImageUpload.php`
|
||||
- [x] Implement Size for ImageVariant in `ImageVariant.php`
|
||||
- [x] Remove TTL in RedisCache
|
||||
- [x] Get RequestData from Request Object in Container
|
||||
- [x] Integrate McpInitializer with the discovery system
|
||||
10. [x] Implement consistent error handling and logging across all components
|
||||
11. [ ] Add comprehensive PHPDoc comments to all public methods and classes
|
||||
12. [x] Standardize naming conventions across the codebase
|
||||
13. [x] Refactor large classes (>200 lines) into smaller, more focused components
|
||||
14. [x] Implement strict type checking across all files
|
||||
15. [x] Remove commented-out code and unused imports
|
||||
16. [x] Apply consistent code formatting using PHP-CS-Fixer or similar tool
|
||||
|
||||
## Performance
|
||||
|
||||
17. [x] Implement caching for frequently accessed data and expensive operations
|
||||
18. [x] Optimize database queries by adding appropriate indexes and query analysis
|
||||
19. [x] Implement lazy loading for non-critical components
|
||||
20. [x] Add performance benchmarks for critical paths in the application
|
||||
21. [x] Optimize the discovery service to reduce bootstrap time
|
||||
22. [x] Implement resource pooling for database connections and other expensive resources
|
||||
23. [x] Add memory usage optimization for large data processing
|
||||
24. [x] Implement asynchronous processing for non-blocking operations
|
||||
25. [x] Optimize the dependency injection container initialization
|
||||
26. [x] Add performance monitoring for production environments
|
||||
|
||||
## Security
|
||||
|
||||
27. [x] Implement comprehensive input validation for all user inputs
|
||||
28. [x] Add Content Security Policy (CSP) headers
|
||||
29. [x] Implement rate limiting for all public endpoints
|
||||
30. [x] Add security headers (X-Content-Type-Options, X-Frame-Options, etc.)
|
||||
31. [x] Implement proper password hashing and storage
|
||||
32. [x] Add CSRF protection to all forms
|
||||
33. [x] Implement proper session management with secure cookies
|
||||
34. [x] Add security scanning in the CI/CD pipeline
|
||||
35. [x] Implement proper error handling that doesn't expose sensitive information
|
||||
36. [x] Add security audit logging for sensitive operations
|
||||
|
||||
## Testing
|
||||
|
||||
37. [ ] Add unit tests for components without test coverage:
|
||||
- [ ] Analytics
|
||||
- [ ] Api
|
||||
- [ ] Attributes
|
||||
- [ ] Auth
|
||||
- [x] CircuitBreaker
|
||||
- [x] Config
|
||||
- [x] Console
|
||||
- [x] Context
|
||||
- [x] Debug
|
||||
- [x] Encryption
|
||||
- [x] ErrorAggregation
|
||||
- [x] ErrorBoundaries
|
||||
- [x] ErrorHandling
|
||||
- [x] ErrorReporting
|
||||
- [x] EventBus
|
||||
- [x] Exception
|
||||
- [x] Firewall
|
||||
- [x] Health
|
||||
- [x] HttpClient
|
||||
- [x] Logging
|
||||
- [x] Markdown
|
||||
- [x] Meta
|
||||
- [x] OpenApi
|
||||
- [x] Performance
|
||||
- [x] Quality
|
||||
- [x] QueryBus
|
||||
- [x] Queue
|
||||
- [x] Random
|
||||
- [x] RateLimit
|
||||
- [x] Redis
|
||||
- [x] Reflection
|
||||
- [x] Sitemap
|
||||
- [x] Smartlinks
|
||||
- [x] SyntaxHighlighter
|
||||
- [x] Tracing
|
||||
- [x] Ulid
|
||||
- [x] UserAgent
|
||||
- [x] Waf
|
||||
- [x] Worker
|
||||
38. [x] Implement integration tests for critical workflows
|
||||
39. [x] Add end-to-end tests for key user journeys
|
||||
40. [x] Implement performance tests for critical paths
|
||||
41. [x] Add security tests for authentication and authorization
|
||||
42. [ ] Implement mutation testing to verify test quality
|
||||
43. [x] Add code coverage reporting to CI/CD pipeline
|
||||
44. [x] Implement contract tests for API endpoints
|
||||
45. [x] Add load testing for high-traffic endpoints
|
||||
46. [x] Implement snapshot testing for UI components
|
||||
|
||||
## Documentation
|
||||
|
||||
47. [ ] Create comprehensive API documentation
|
||||
48. [ ] Add inline code documentation for complex algorithms
|
||||
49. [ ] Create user guides for key features
|
||||
50. [ ] Document the architecture and design decisions
|
||||
51. [x] Add setup and installation instructions
|
||||
52. [ ] Create troubleshooting guides
|
||||
53. [ ] Document performance optimization strategies
|
||||
54. [ ] Add security best practices documentation
|
||||
55. [ ] Create developer onboarding documentation
|
||||
56. [ ] Document testing strategies and approaches
|
||||
|
||||
## DevOps
|
||||
|
||||
57. [x] Implement automated deployment pipelines
|
||||
58. [x] Add infrastructure as code for all environments
|
||||
59. [x] Implement comprehensive monitoring and alerting
|
||||
60. [x] Add automated database migrations
|
||||
61. [x] Implement blue-green deployments
|
||||
62. [ ] Add canary releases for critical features
|
||||
63. [x] Implement feature flags for gradual rollouts
|
||||
64. [x] Add automated rollback mechanisms
|
||||
65. [x] Implement comprehensive logging and log aggregation
|
||||
66. [x] Add performance monitoring and profiling in production
|
||||
472
docs/search-api-examples.md
Normal file
472
docs/search-api-examples.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# Search API Examples
|
||||
|
||||
This framework provides a comprehensive RESTful API for full-text search functionality.
|
||||
|
||||
## Base URL
|
||||
|
||||
All search endpoints are available under `/api/search/`
|
||||
|
||||
## Authentication
|
||||
|
||||
Search endpoints require a valid User-Agent header to avoid triggering firewall rules:
|
||||
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" \
|
||||
https://localhost/api/search/...
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### 1. Search Documents
|
||||
|
||||
**GET** `/api/search/{entityType}`
|
||||
|
||||
Search for documents of a specific entity type.
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Description | Default |
|
||||
|-----------|------|-------------|---------|
|
||||
| `q` | string | Search query | `*` |
|
||||
| `limit` | integer | Results per page (1-100) | `20` |
|
||||
| `offset` | integer | Results offset | `0` |
|
||||
| `page` | integer | Page number (alternative to offset) | `1` |
|
||||
| `per_page` | integer | Results per page when using page | `20` |
|
||||
| `fields` | string/array | Comma-separated fields to return | all |
|
||||
| `highlight` | string/array | Fields to highlight | `title,content,description` |
|
||||
| `sort_by` | string | Field to sort by | - |
|
||||
| `sort_direction` | string | `asc` or `desc` | `asc` |
|
||||
| `sort_by_relevance` | boolean | Sort by relevance score | `true` |
|
||||
| `enable_highlighting` | boolean | Enable result highlighting | `true` |
|
||||
| `enable_fuzzy` | boolean | Enable fuzzy matching | `false` |
|
||||
| `min_score` | float | Minimum relevance score | `0.0` |
|
||||
|
||||
#### Examples
|
||||
|
||||
**Basic Search:**
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
"https://localhost/api/search/products?q=iPhone"
|
||||
```
|
||||
|
||||
**Advanced Search with Filters:**
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
"https://localhost/api/search/products?q=smartphone&filter[category][type]=equals&filter[category][value]=electronics&filter[price][type]=range&filter[price][value][min]=500&filter[price][value][max]=1500"
|
||||
```
|
||||
|
||||
**Search with JSON Filters:**
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://localhost/api/search/products?q=laptop&filters=%7B%22category%22%3A%7B%22type%22%3A%22equals%22%2C%22value%22%3A%22computers%22%7D%2C%22price%22%3A%7B%22type%22%3A%22range%22%2C%22value%22%3A%7B%22min%22%3A800%2C%22max%22%3A2000%7D%7D%7D"
|
||||
```
|
||||
|
||||
**Pagination:**
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
"https://localhost/api/search/articles?q=technology&page=2&per_page=10"
|
||||
```
|
||||
|
||||
**Field Boosting:**
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
"https://localhost/api/search/articles?q=artificial intelligence&boosts=%7B%22title%22%3A2.0%2C%22tags%22%3A1.5%7D"
|
||||
```
|
||||
|
||||
#### Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"hits": [
|
||||
{
|
||||
"id": "1",
|
||||
"entity_type": "products",
|
||||
"score": 1.2345,
|
||||
"source": {
|
||||
"title": "iPhone 15 Pro Max",
|
||||
"description": "Latest iPhone with advanced features",
|
||||
"category": "smartphones",
|
||||
"price": 1199
|
||||
},
|
||||
"highlight": {
|
||||
"title": ["<em>iPhone</em> 15 Pro Max"],
|
||||
"description": ["Latest <em>iPhone</em> with advanced features"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 145,
|
||||
"max_score": 2.5432,
|
||||
"took": 23.45,
|
||||
"has_more": true,
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"total_pages": 8
|
||||
},
|
||||
"aggregations": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Index Document
|
||||
|
||||
**POST** `/api/search/{entityType}/{id}`
|
||||
|
||||
Index a single document for searching.
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "iPhone 15 Pro Max",
|
||||
"description": "Latest iPhone with advanced camera and A17 Pro chip",
|
||||
"category": "smartphones",
|
||||
"brand": "Apple",
|
||||
"price": 1199,
|
||||
"tags": ["phone", "apple", "premium"],
|
||||
"created_at": "2024-08-08T10:00:00Z"
|
||||
}' \
|
||||
https://localhost/api/search/products/iphone-15-pro-max
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"entity_type": "products",
|
||||
"id": "iphone-15-pro-max",
|
||||
"indexed": true,
|
||||
"message": "Document indexed successfully"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Bulk Index Documents
|
||||
|
||||
**POST** `/api/search/{entityType}/bulk`
|
||||
|
||||
Index multiple documents at once.
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"documents": [
|
||||
{
|
||||
"id": "product-1",
|
||||
"data": {
|
||||
"title": "MacBook Pro 14",
|
||||
"description": "Professional laptop with M3 chip",
|
||||
"category": "laptops",
|
||||
"price": 1999
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "product-2",
|
||||
"data": {
|
||||
"title": "iPad Air",
|
||||
"description": "Versatile tablet for work and play",
|
||||
"category": "tablets",
|
||||
"price": 599
|
||||
},
|
||||
"metadata": {
|
||||
"priority": "high"
|
||||
}
|
||||
}
|
||||
]
|
||||
}' \
|
||||
https://localhost/api/search/products/bulk
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"entity_type": "products",
|
||||
"bulk_result": {
|
||||
"total": 2,
|
||||
"successful": 2,
|
||||
"failed": 0,
|
||||
"successful_ids": ["product-1", "product-2"],
|
||||
"failed_ids": [],
|
||||
"errors": {},
|
||||
"started_at": "2024-08-08T10:00:00Z",
|
||||
"finished_at": "2024-08-08T10:00:01Z",
|
||||
"duration_ms": 1234.56,
|
||||
"success_rate": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update Document
|
||||
|
||||
**PUT** `/api/search/{entityType}/{id}`
|
||||
|
||||
Update an existing document in the search index.
|
||||
|
||||
```bash
|
||||
curl -X PUT \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "iPhone 15 Pro Max - Updated",
|
||||
"description": "Latest iPhone with enhanced features and longer battery life",
|
||||
"price": 1099
|
||||
}' \
|
||||
https://localhost/api/search/products/iphone-15-pro-max
|
||||
```
|
||||
|
||||
### 5. Delete Document
|
||||
|
||||
**DELETE** `/api/search/{entityType}/{id}`
|
||||
|
||||
Remove a document from the search index.
|
||||
|
||||
```bash
|
||||
curl -X DELETE \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
https://localhost/api/search/products/iphone-15-pro-max
|
||||
```
|
||||
|
||||
## Index Management
|
||||
|
||||
### 6. Get Engine Statistics
|
||||
|
||||
**GET** `/api/search/_stats`
|
||||
|
||||
Get overall search engine statistics.
|
||||
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
https://localhost/api/search/_stats
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"engine_stats": {
|
||||
"engine_name": "Database",
|
||||
"version": "8.0.33",
|
||||
"is_healthy": true,
|
||||
"total_documents": 1250,
|
||||
"index_counts": {
|
||||
"products": 450,
|
||||
"articles": 800
|
||||
},
|
||||
"disk_usage_mb": 128.5,
|
||||
"memory_usage_mb": 45.2,
|
||||
"last_updated": "2024-08-08T10:00:00Z"
|
||||
},
|
||||
"available": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Get Index Statistics
|
||||
|
||||
**GET** `/api/search/{entityType}/_stats`
|
||||
|
||||
Get statistics for a specific index.
|
||||
|
||||
```bash
|
||||
curl -H "User-Agent: Mozilla/5.0" \
|
||||
https://localhost/api/search/products/_stats
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"index_stats": {
|
||||
"entity_type": "products",
|
||||
"document_count": 450,
|
||||
"index_size_mb": 67.3,
|
||||
"query_count": 0,
|
||||
"average_query_time_ms": 0.0,
|
||||
"last_indexed": "2024-08-08T09:30:00Z",
|
||||
"created_at": "2024-08-01T12:00:00Z"
|
||||
},
|
||||
"entity_type": "products"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Create Index
|
||||
|
||||
**PUT** `/api/search/{entityType}/_index`
|
||||
|
||||
Create or update a search index with specific field configurations.
|
||||
|
||||
```bash
|
||||
curl -X PUT \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"fields": {
|
||||
"title": {
|
||||
"type": "text",
|
||||
"searchable": true,
|
||||
"highlightable": true,
|
||||
"boost": 2.0
|
||||
},
|
||||
"description": {
|
||||
"type": "text",
|
||||
"searchable": true,
|
||||
"highlightable": true,
|
||||
"boost": 1.0
|
||||
},
|
||||
"category": {
|
||||
"type": "keyword",
|
||||
"searchable": false,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
},
|
||||
"price": {
|
||||
"type": "float",
|
||||
"searchable": false,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
},
|
||||
"created_at": {
|
||||
"type": "datetime",
|
||||
"searchable": false,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"min_score_threshold": 0.1
|
||||
}
|
||||
}' \
|
||||
https://localhost/api/search/products/_index
|
||||
```
|
||||
|
||||
### 9. Delete Index
|
||||
|
||||
**DELETE** `/api/search/{entityType}/_index`
|
||||
|
||||
Delete a search index and all its documents.
|
||||
|
||||
```bash
|
||||
curl -X DELETE \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
https://localhost/api/search/products/_index
|
||||
```
|
||||
|
||||
## Filter Types
|
||||
|
||||
The search API supports various filter types:
|
||||
|
||||
### Equals
|
||||
```json
|
||||
{
|
||||
"category": {
|
||||
"type": "equals",
|
||||
"value": "smartphones"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### In (Array)
|
||||
```json
|
||||
{
|
||||
"category": {
|
||||
"type": "in",
|
||||
"value": ["smartphones", "tablets", "laptops"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Range
|
||||
```json
|
||||
{
|
||||
"price": {
|
||||
"type": "range",
|
||||
"value": {
|
||||
"min": 500,
|
||||
"max": 1500
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Greater/Less Than
|
||||
```json
|
||||
{
|
||||
"price": {
|
||||
"type": "greater_than",
|
||||
"value": 1000
|
||||
},
|
||||
"stock": {
|
||||
"type": "less_than",
|
||||
"value": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Contains (Text Search)
|
||||
```json
|
||||
{
|
||||
"description": {
|
||||
"type": "contains",
|
||||
"value": "wireless"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exists/Not Exists
|
||||
```json
|
||||
{
|
||||
"discount": {
|
||||
"type": "exists"
|
||||
},
|
||||
"legacy_field": {
|
||||
"type": "not_exists"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All endpoints return consistent error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"message": "Search query cannot be empty",
|
||||
"code": "VAL_BUSINESS_RULE_VIOLATION",
|
||||
"details": {
|
||||
"field": "query",
|
||||
"provided_value": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common HTTP Status Codes
|
||||
|
||||
- `200 OK` - Successful search/get operation
|
||||
- `201 Created` - Document indexed successfully
|
||||
- `202 Accepted` - Bulk operation partially successful
|
||||
- `400 Bad Request` - Invalid request parameters
|
||||
- `404 Not Found` - Index or document not found
|
||||
- `500 Internal Server Error` - Server error
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use Field Restrictions**: Only request fields you need with the `fields` parameter
|
||||
2. **Limit Results**: Use reasonable `limit` values (20-50 for UI, 100 max)
|
||||
3. **Filter Before Search**: Use filters to narrow down results before full-text search
|
||||
4. **Boost Important Fields**: Use field boosting for better relevance
|
||||
5. **Batch Operations**: Use bulk indexing for multiple documents
|
||||
6. **Index Only Searchable Data**: Don't index fields you'll never search
|
||||
256
docs/search-external-mapping-examples.md
Normal file
256
docs/search-external-mapping-examples.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# External Entity Mapping für Search System
|
||||
|
||||
**Suchfunktionalität ohne Entity-Änderungen** - Komplette Integration über externe Konfiguration.
|
||||
|
||||
## 1. Basic Mapping Configuration
|
||||
|
||||
```php
|
||||
// In einem Service Provider oder Initializer
|
||||
final class SearchMappingProvider
|
||||
{
|
||||
public static function registerMappings(SearchableMappingRegistry $registry): void
|
||||
{
|
||||
// User Entity Mapping
|
||||
$userMapping = SearchableMapping::for(User::class)
|
||||
->entityType('users')
|
||||
->idField('id')
|
||||
->field('name', 'name')
|
||||
->field('email', 'email')
|
||||
->field('bio', 'biography')
|
||||
->field('created', 'createdAt', fn($date) => $date->format('Y-m-d'))
|
||||
->boost('name', 2.0)
|
||||
->boost('email', 1.5)
|
||||
->autoIndex(true)
|
||||
->build();
|
||||
|
||||
$registry->register($userMapping);
|
||||
|
||||
// Product Entity Mapping
|
||||
$productMapping = SearchableMapping::for(Product::class)
|
||||
->entityType('products')
|
||||
->field('title', 'name')
|
||||
->field('description', 'description')
|
||||
->field('category', 'category.name') // Nested field access
|
||||
->field('price', 'price', fn($price) => $price->getCents() / 100) // Transform Money object
|
||||
->field('tags', 'getTags', fn($tags) => implode(' ', $tags)) // Method call + transform
|
||||
->boost('title', 3.0)
|
||||
->boost('description', 1.0)
|
||||
->build();
|
||||
|
||||
$registry->register($productMapping);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Configuration-Based Mapping
|
||||
|
||||
```php
|
||||
// config/search_mappings.php
|
||||
return [
|
||||
User::class => [
|
||||
'entity_type' => 'users',
|
||||
'id_field' => 'id',
|
||||
'auto_index' => true,
|
||||
'fields' => [
|
||||
'name' => 'name',
|
||||
'email' => 'email',
|
||||
'bio' => 'biography',
|
||||
'created' => [
|
||||
'field' => 'createdAt',
|
||||
'transformer' => fn($date) => $date->format('Y-m-d')
|
||||
]
|
||||
],
|
||||
'boosts' => [
|
||||
'name' => 2.0,
|
||||
'email' => 1.5
|
||||
]
|
||||
],
|
||||
|
||||
Product::class => [
|
||||
'entity_type' => 'products',
|
||||
'fields' => [
|
||||
'title' => 'name',
|
||||
'description' => 'description',
|
||||
'category' => 'category.name', // Nested access
|
||||
'price' => [
|
||||
'field' => 'price',
|
||||
'transformer' => fn($price) => $price->toFloat()
|
||||
]
|
||||
],
|
||||
'boosts' => [
|
||||
'title' => 3.0
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// Load configuration
|
||||
$config = require 'config/search_mappings.php';
|
||||
$registry->registerFromConfig($config);
|
||||
```
|
||||
|
||||
## 3. Repository Integration (No Entity Changes)
|
||||
|
||||
```php
|
||||
final class UserRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private SearchIndexingService $searchIndexing
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(array $userData): User
|
||||
{
|
||||
$user = new User($userData);
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Auto-index after creation (if enabled)
|
||||
$this->searchIndexing->indexEntity($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function update(User $user, array $changes): void
|
||||
{
|
||||
// Apply changes to user
|
||||
foreach ($changes as $field => $value) {
|
||||
$user->{"set" . ucfirst($field)}($value);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Update search index
|
||||
$this->searchIndexing->updateEntity($user);
|
||||
}
|
||||
|
||||
public function delete(User $user): void
|
||||
{
|
||||
// Remove from search first
|
||||
$this->searchIndexing->removeEntity($user);
|
||||
|
||||
$this->entityManager->remove($user);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function reindexAll(): BulkIndexResult
|
||||
{
|
||||
return $this->searchIndexing->reindexEntityType('users', function() {
|
||||
return $this->entityManager->findAll(User::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Service Usage Without Entity Knowledge
|
||||
|
||||
```php
|
||||
final class ProductSearchService
|
||||
{
|
||||
public function __construct(
|
||||
private SearchService $searchService,
|
||||
private SearchableMappingRegistry $mappingRegistry
|
||||
) {
|
||||
}
|
||||
|
||||
public function makeProductSearchable(Product $product): bool
|
||||
{
|
||||
// Check if product is configured as searchable
|
||||
if (!$this->mappingRegistry->isSearchable($product)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create adapter automatically
|
||||
$adapter = $this->mappingRegistry->createAdapter($product);
|
||||
|
||||
if (!$adapter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Index using the mapped fields
|
||||
return $this->searchService->index(
|
||||
$adapter->getEntityType(),
|
||||
$adapter->getId(),
|
||||
$adapter->toSearchDocument()
|
||||
);
|
||||
}
|
||||
|
||||
public function searchProducts(string $query): SearchResult
|
||||
{
|
||||
return $this->searchService
|
||||
->for('products')
|
||||
->query($query)
|
||||
->boost('title', 3.0) // Use configured boosts
|
||||
->limit(20)
|
||||
->search();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Event-Driven Auto-Indexing
|
||||
|
||||
```php
|
||||
// Events werden automatisch von Entity Operations gefeuert
|
||||
$eventDispatcher->dispatch(new EntityCreatedEvent($user)); // Auto-indexes
|
||||
$eventDispatcher->dispatch(new EntityUpdatedEvent($product)); // Auto-updates
|
||||
$eventDispatcher->dispatch(new EntityDeletedEvent($order)); // Auto-removes
|
||||
```
|
||||
|
||||
## 6. Advanced Field Transformations
|
||||
|
||||
```php
|
||||
$mapping = SearchableMapping::for(Article::class)
|
||||
->field('title', 'title')
|
||||
->field('content', 'body')
|
||||
->field('author', 'user.name') // Nested object access
|
||||
->field('tags', 'tags', fn($tags) => implode(' ', array_column($tags, 'name')))
|
||||
->field('published_date', 'publishedAt', fn($date) => $date?->format('Y-m-d'))
|
||||
->field('word_count', 'body', fn($content) => str_word_count(strip_tags($content)))
|
||||
->field('reading_time', 'body', fn($content) => ceil(str_word_count($content) / 200))
|
||||
->build();
|
||||
```
|
||||
|
||||
## 7. Conditional Indexing
|
||||
|
||||
```php
|
||||
final class ConditionalSearchListener
|
||||
{
|
||||
#[EventListener(event: 'entity.updated')]
|
||||
public function onEntityUpdated(EntityUpdatedEvent $event): void
|
||||
{
|
||||
$entity = $event->getEntity();
|
||||
|
||||
// Only index published articles
|
||||
if ($entity instanceof Article && $entity->isPublished()) {
|
||||
$this->indexingService->updateEntity($entity);
|
||||
} elseif ($entity instanceof Article && !$entity->isPublished()) {
|
||||
// Remove from index if unpublished
|
||||
$this->indexingService->removeEntity($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Vorteile dieses Ansatzes:
|
||||
|
||||
✅ **Keine Entity-Änderungen erforderlich**
|
||||
✅ **Zentrale Konfiguration** aller Mappings
|
||||
✅ **Flexible Field-Transformationen**
|
||||
✅ **Automatische Indexierung** über Events
|
||||
✅ **Repository-Integration** ohne Abhängigkeiten
|
||||
✅ **Conditional Indexing** möglich
|
||||
✅ **Backward Compatible** mit bestehenden Entities
|
||||
|
||||
## Integration:
|
||||
|
||||
```php
|
||||
// Container Registration
|
||||
$container->singleton(SearchableMappingRegistry::class);
|
||||
$container->singleton(SearchIndexingService::class);
|
||||
|
||||
// Register mappings at startup
|
||||
$registry = $container->get(SearchableMappingRegistry::class);
|
||||
SearchMappingProvider::registerMappings($registry);
|
||||
```
|
||||
|
||||
Dieser Ansatz ermöglicht **vollständige Suchfunktionalität ohne jegliche Änderungen an bestehenden Entity-Klassen**.
|
||||
126
docs/spa-router-backend-integration.md
Normal file
126
docs/spa-router-backend-integration.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# SPA Router Backend Integration
|
||||
|
||||
Das SPA Router System benötigt Backend-Unterstützung, um nur den `<main>` Content für AJAX-Requests zu liefern.
|
||||
|
||||
## HTTP Headers
|
||||
|
||||
Das SPA System sendet diese Headers bei Navigation:
|
||||
|
||||
```http
|
||||
X-Requested-With: XMLHttpRequest
|
||||
X-SPA-Request: true
|
||||
Accept: text/html
|
||||
```
|
||||
|
||||
## Backend Implementation
|
||||
|
||||
### Option 1: Separate SPA Views
|
||||
|
||||
```php
|
||||
// In your controller
|
||||
public function showContact(HttpRequest $request): HttpResponse
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Kontakt',
|
||||
// ... other data
|
||||
];
|
||||
|
||||
// Check if this is an SPA request
|
||||
if ($request->headers->get('X-SPA-Request') === 'true') {
|
||||
// Return only the main content
|
||||
return new ViewResult('contact-spa.view.php', $data);
|
||||
}
|
||||
|
||||
// Return full page
|
||||
return new ViewResult('contact.view.php', $data);
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Template Fragments
|
||||
|
||||
```php
|
||||
// Create a partial template system
|
||||
public function showContact(HttpRequest $request): HttpResponse
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Kontakt',
|
||||
'content' => $this->renderPartial('contact-main.view.php')
|
||||
];
|
||||
|
||||
if ($request->headers->get('X-SPA-Request') === 'true') {
|
||||
// Return only main content as JSON
|
||||
return new JsonResult([
|
||||
'title' => $data['title'],
|
||||
'content' => $data['content']
|
||||
]);
|
||||
}
|
||||
|
||||
return new ViewResult('contact.view.php', $data);
|
||||
}
|
||||
```
|
||||
|
||||
### Option 3: Layout Conditional (Recommended)
|
||||
|
||||
Modify your main layout to conditionally include header/footer:
|
||||
|
||||
```php
|
||||
// main.view.php layout
|
||||
<?php
|
||||
$isSPARequest = $request->headers->get('X-SPA-Request') === 'true';
|
||||
?>
|
||||
|
||||
<?php if (!$isSPARequest): ?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><?= $title ?></title>
|
||||
<!-- head content -->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<!-- header content -->
|
||||
</header>
|
||||
<?php endif; ?>
|
||||
|
||||
<main>
|
||||
<!-- This is what gets sent for SPA requests -->
|
||||
<?= $content ?>
|
||||
</main>
|
||||
|
||||
<?php if (!$isSPARequest): ?>
|
||||
<footer>
|
||||
<!-- footer content -->
|
||||
</footer>
|
||||
|
||||
<!-- scripts -->
|
||||
</body>
|
||||
</html>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Test SPA requests with curl:
|
||||
|
||||
```bash
|
||||
# Normal request (full page)
|
||||
curl https://localhost/kontakt
|
||||
|
||||
# SPA request (only main content)
|
||||
curl -H "X-SPA-Request: true" -H "X-Requested-With: XMLHttpRequest" https://localhost/kontakt
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Das Frontend fällt automatisch auf normale Navigation zurück wenn:
|
||||
- HTTP Status >= 400
|
||||
- Network Fehler
|
||||
- Parse Fehler
|
||||
- Timeout
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- SPA Requests sind ~70% kleiner (nur Content, kein Header/Footer/Scripts)
|
||||
- Reduzierte Server-Last durch weniger Asset-Requests
|
||||
- Schnellere Navigation durch weniger DOM-Updates
|
||||
- Browser-Cache wird optimal genutzt
|
||||
@@ -1,234 +0,0 @@
|
||||
# Coding Guidelines
|
||||
|
||||
## Allgemeine Prinzipien
|
||||
|
||||
Diese Guidelines definieren die Standards für die Entwicklung und Wartung des Projekts. Sie sorgen für Konsistenz, Lesbarkeit und Wartbarkeit des Codes.
|
||||
|
||||
## PHP-Version
|
||||
|
||||
- **PHP 8.4**: Die Codebase nutzt stets die neueste stabile PHP-Version (aktuell PHP 8.4)
|
||||
- Alle neuen PHP-Sprachfeatures sollten, wo sinnvoll, genutzt werden
|
||||
|
||||
## Abhängigkeiten
|
||||
|
||||
- **Externe Abhängigkeiten vermeiden**: Das Projekt soll möglichst ohne externe Bibliotheken auskommen
|
||||
- **Eigene Implementierungen bevorzugen**: Anstatt externe Pakete einzubinden, sollten eigene Lösungen entwickelt werden
|
||||
- **Erlaubte Abhängigkeiten**: Nur die bereits in composer.json definierten Pakete dürfen verwendet werden
|
||||
- **Neue Abhängigkeiten**: Müssen explizit genehmigt werden und sollten nur in Ausnahmefällen hinzugefügt werden
|
||||
|
||||
## Klassenstruktur
|
||||
|
||||
### Klassen
|
||||
|
||||
- **Alle Klassen müssen `final` sein**, es sei denn, es gibt einen zwingenden Grund für Vererbung
|
||||
- **Keine abstrakten Klassen** - bevorzuge Interfaces und Kompositionen
|
||||
- **Klassen sollten `readonly` sein**, wann immer möglich
|
||||
- Interfaces verwenden, um Verträge zwischen Komponenten zu definieren
|
||||
|
||||
```php
|
||||
// Gut
|
||||
final readonly class AnalyticsManager
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// Vermeiden
|
||||
abstract class BaseManager
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
- **Properties sollten `readonly` sein**, wann immer möglich
|
||||
- Private Visibility für alle Properties, es sei denn, es gibt einen zwingenden Grund
|
||||
- Typisierung ist obligatorisch für alle Properties
|
||||
|
||||
```php
|
||||
// Gut
|
||||
private readonly StorageInterface $storage;
|
||||
|
||||
// Vermeiden
|
||||
public array $config;
|
||||
```
|
||||
|
||||
## Methoden und Funktionen
|
||||
|
||||
- Typisierung ist obligatorisch für alle Parameter und Rückgabewerte
|
||||
- Methoden sollten klein und fokussiert sein (Single Responsibility)
|
||||
- Verwende named arguments für bessere Lesbarkeit
|
||||
|
||||
```php
|
||||
// Gut
|
||||
public function track(string $event, array $properties = [], ?string $userId = null): void
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// Vermeiden
|
||||
public function process($data)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
- Constructor Injection für alle Abhängigkeiten
|
||||
- Keine Service Locator oder globale Zustände
|
||||
- Verwende das `#[Initializer]`-Attribut für Service-Registrierung
|
||||
|
||||
```php
|
||||
// Gut
|
||||
public function __construct(
|
||||
private readonly Configuration $config,
|
||||
private readonly StorageInterface $storage
|
||||
) {}
|
||||
|
||||
// Vermeiden
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = Container::get(Configuration::class);
|
||||
}
|
||||
```
|
||||
|
||||
## Moderne PHP-Features
|
||||
|
||||
### Nullable Types und Union Types
|
||||
|
||||
```php
|
||||
public function findUser(?int $id): ?User
|
||||
public function process(int|string $identifier): void
|
||||
```
|
||||
|
||||
### Match Expression statt Switch
|
||||
|
||||
```php
|
||||
// Gut
|
||||
return match($storageType) {
|
||||
'file' => new FileStorage($path),
|
||||
'redis' => new RedisStorage($connection),
|
||||
default => throw new \InvalidArgumentException("Nicht unterstützter Storage-Typ: {$storageType}")
|
||||
};
|
||||
|
||||
// Vermeiden
|
||||
switch ($storageType) {
|
||||
case 'file':
|
||||
return new FileStorage($path);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Named Arguments
|
||||
|
||||
```php
|
||||
$this->createUser(
|
||||
email: 'user@example.com',
|
||||
role: 'admin',
|
||||
sendWelcomeEmail: true
|
||||
);
|
||||
```
|
||||
|
||||
### Constructor Property Promotion
|
||||
|
||||
```php
|
||||
public function __construct(
|
||||
private readonly Configuration $config,
|
||||
private readonly PathProvider $pathProvider
|
||||
) {}
|
||||
```
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
- Spezifische Exceptions werfen
|
||||
- Typed Exceptions verwenden
|
||||
- Early Return Pattern bevorzugen
|
||||
|
||||
```php
|
||||
// Gut
|
||||
if (!$this->isValid()) {
|
||||
throw new ValidationException('Ungültige Daten');
|
||||
}
|
||||
|
||||
// Vermeiden
|
||||
if ($this->isValid()) {
|
||||
// Lange Verarbeitung...
|
||||
} else {
|
||||
throw new Exception('Fehler');
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
- Tests mit Pest-Framework schreiben
|
||||
- Für jede öffentliche Methode sollte mindestens ein Test existieren
|
||||
- Mocking nur für externe Abhängigkeiten verwenden
|
||||
|
||||
```php
|
||||
test('track method records events correctly', function () {
|
||||
$analytics = new Analytics($manager, $dispatcher);
|
||||
$analytics->track('test_event', ['key' => 'value']);
|
||||
|
||||
expect($manager->getEvents())->toHaveCount(1);
|
||||
});
|
||||
```
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- PHPDoc für alle öffentlichen Methoden und Klassen
|
||||
- Typen in PHPDoc sollten den tatsächlichen Typen entsprechen
|
||||
- Klare, beschreibende Kommentare für komplexe Logik
|
||||
|
||||
```php
|
||||
/**
|
||||
* Zeichnet ein Event auf und wendet Middleware an
|
||||
*
|
||||
* @param string $event Event-Name
|
||||
* @param array $properties Event-Eigenschaften
|
||||
* @param string|null $userId Benutzer-ID (optional)
|
||||
*/
|
||||
public function track(string $event, array $properties = [], ?string $userId = null): void
|
||||
```
|
||||
|
||||
## Namenskonventionen
|
||||
|
||||
- **Klassen**: PascalCase (`AnalyticsManager`)
|
||||
- **Methoden/Funktionen**: camelCase (`getEventStats()`)
|
||||
- **Variablen**: camelCase (`$eventData`)
|
||||
- **Konstanten**: SNAKE_CASE (`MAX_BATCH_SIZE`)
|
||||
- **Dateien**: Klassenname.php (`AnalyticsManager.php`)
|
||||
|
||||
## Architektur
|
||||
|
||||
- Dependency Inversion Principle befolgen
|
||||
- Interfaces für alle externen Abhängigkeiten
|
||||
- Vermeide zirkuläre Abhängigkeiten zwischen Modulen
|
||||
- Services sollten eine klare, fokussierte Verantwortung haben
|
||||
|
||||
## Performance
|
||||
|
||||
- Lazy Loading für ressourcenintensive Operationen
|
||||
- Caching wo sinnvoll
|
||||
- Datenstrukturen sorgfältig auswählen
|
||||
|
||||
## Security
|
||||
|
||||
- Alle Benutzereingaben validieren und bereinigen
|
||||
- Prepared Statements für Datenbankabfragen
|
||||
- Vermeidung von `eval()` und ähnlichen unsicheren Funktionen
|
||||
- Sensible Daten niemals in Logs schreiben
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Immutability**: Bevorzuge unveränderliche Objekte
|
||||
- **Pure Functions**: Bevorzuge reine Funktionen ohne Seiteneffekte
|
||||
- **Enums**: Verwende Enums statt String-Konstanten für feste Optionssätze
|
||||
- **DTOs**: Verwende Data Transfer Objects für Datentransport
|
||||
- **Value Objects**: Verwende Value Objects für semantisch reiche Datentypen
|
||||
|
||||
## Refactoring
|
||||
|
||||
- Code, der diese Guidelines nicht erfüllt, sollte beim Bearbeiten aktualisiert werden
|
||||
- Tests müssen vor und nach dem Refactoring bestehen
|
||||
- Große Refactorings sollten in kleinen, separaten Commits erfolgen
|
||||
@@ -1,187 +0,0 @@
|
||||
# Sicherheitsrichtlinien
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Richtlinien definieren Standards und Best Practices für sichere Softwareentwicklung im Projekt.
|
||||
|
||||
## Grundprinzipien
|
||||
|
||||
### 1. Defense in Depth
|
||||
|
||||
- Mehrere Sicherheitsschichten implementieren
|
||||
- Nicht auf eine einzelne Sicherheitsmaßnahme vertrauen
|
||||
- Fail-Safe-Mechanismen einbauen
|
||||
|
||||
### 2. Least Privilege
|
||||
|
||||
- Minimale Berechtigungen für Funktionen und Benutzer
|
||||
- Ressourcenzugriff nur bei Bedarf gewähren
|
||||
- Temporäre Berechtigungen nach Gebrauch entziehen
|
||||
|
||||
### 3. Input-Validierung
|
||||
|
||||
- Alle Benutzereingaben validieren und bereinigen
|
||||
- Whitelist-Ansatz bevorzugen (erlaubte Eingaben definieren)
|
||||
- Typprüfung und Formatvalidierung durchführen
|
||||
|
||||
```php
|
||||
// Beispiel: Sichere Input-Validierung
|
||||
public function processUserInput(string $input): string
|
||||
{
|
||||
// Länge prüfen
|
||||
if (strlen($input) > 100) {
|
||||
throw new ValidationException('Input zu lang (max 100 Zeichen)');
|
||||
}
|
||||
|
||||
// Inhalt validieren (Whitelist-Ansatz)
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\-_]+$/', $input)) {
|
||||
throw new ValidationException('Input enthält ungültige Zeichen');
|
||||
}
|
||||
|
||||
// Bereinigung
|
||||
return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
```
|
||||
|
||||
## Spezifische Sicherheitsmaßnahmen
|
||||
|
||||
### 1. SQL-Injection-Prävention
|
||||
|
||||
- Prepared Statements für alle Datenbankabfragen
|
||||
- Keine dynamischen SQL-Queries
|
||||
- ORM-Framework bevorzugen
|
||||
|
||||
```php
|
||||
// Sicher
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
|
||||
// Unsicher - NIEMALS SO MACHEN
|
||||
$query = "SELECT * FROM users WHERE email = '{$email}'";
|
||||
```
|
||||
|
||||
### 2. Cross-Site Scripting (XSS) Prävention
|
||||
|
||||
- Output-Escaping für alle benutzergenerierten Inhalte
|
||||
- Content-Security-Policy (CSP) implementieren
|
||||
- HttpOnly und Secure Flags für Cookies
|
||||
|
||||
```php
|
||||
// Ausgabe in HTML
|
||||
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Ausgabe in JavaScript
|
||||
echo json_encode($userInput, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
|
||||
```
|
||||
|
||||
### 3. Cross-Site Request Forgery (CSRF) Schutz
|
||||
|
||||
- CSRF-Token für alle ändernden Anfragen
|
||||
- SameSite-Attribut für Cookies
|
||||
|
||||
```php
|
||||
// CSRF-Token generieren
|
||||
public function generateCsrfToken(): string
|
||||
{
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$_SESSION['csrf_token'] = $token;
|
||||
return $token;
|
||||
}
|
||||
|
||||
// CSRF-Token validieren
|
||||
public function validateCsrfToken(string $token): bool
|
||||
{
|
||||
return hash_equals($_SESSION['csrf_token'] ?? '', $token);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Sichere Authentifizierung
|
||||
|
||||
- Passwörter mit starken Algorithmen hashen (Argon2id)
|
||||
- Multi-Faktor-Authentifizierung anbieten
|
||||
- Ratelimiting für Login-Versuche
|
||||
|
||||
```php
|
||||
// Passwort hashen
|
||||
public function hashPassword(string $password): string
|
||||
{
|
||||
return password_hash($password, PASSWORD_ARGON2ID);
|
||||
}
|
||||
|
||||
// Passwort verifizieren
|
||||
public function verifyPassword(string $password, string $hash): bool
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Sichere Datenspeicherung
|
||||
|
||||
- Sensible Daten verschlüsseln
|
||||
- Separate Schlüssel für unterschiedliche Daten
|
||||
- Schlüsselrotation implementieren
|
||||
|
||||
```php
|
||||
// Daten verschlüsseln
|
||||
public function encrypt(string $data, string $purpose): string
|
||||
{
|
||||
$key = $this->getEncryptionKey($purpose);
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
|
||||
$cipher = sodium_crypto_secretbox(
|
||||
$data,
|
||||
$nonce,
|
||||
$key
|
||||
);
|
||||
|
||||
return base64_encode($nonce . $cipher);
|
||||
}
|
||||
|
||||
// Daten entschlüsseln
|
||||
public function decrypt(string $encrypted, string $purpose): string
|
||||
{
|
||||
$key = $this->getEncryptionKey($purpose);
|
||||
$decoded = base64_decode($encrypted);
|
||||
|
||||
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
|
||||
$cipher = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
|
||||
|
||||
return sodium_crypto_secretbox_open(
|
||||
$cipher,
|
||||
$nonce,
|
||||
$key
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Sicherheitstests
|
||||
|
||||
### 1. Automatisierte Sicherheitstests
|
||||
|
||||
- Static Application Security Testing (SAST)
|
||||
- Dynamic Application Security Testing (DAST)
|
||||
- Dependency-Scanning für bekannte Schwachstellen
|
||||
|
||||
### 2. Penetrationstests
|
||||
|
||||
- Regelmäßige Sicherheitsaudits
|
||||
- Manuelle Penetrationstests
|
||||
- Bug-Bounty-Programme
|
||||
|
||||
## Sicherheitskultur
|
||||
|
||||
### 1. Entwicklerschulungen
|
||||
|
||||
- Regelmäßige Sicherheitsschulungen
|
||||
- Code-Reviews mit Sicherheitsfokus
|
||||
- Sicherheits-Champions im Team
|
||||
|
||||
### 2. Incident Response
|
||||
|
||||
- Sicherheitsvorfälle dokumentieren
|
||||
- Prozess für Sicherheitsmeldungen
|
||||
- Notfallpläne für Sicherheitsvorfälle
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Sicherheit ist ein kontinuierlicher Prozess, keine einmalige Aufgabe. Diese Richtlinien sollten regelmäßig überprüft und aktualisiert werden, um neuen Bedrohungen zu begegnen.
|
||||
@@ -1,50 +0,0 @@
|
||||
# Coding-Standards und Richtlinien
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Standards und Richtlinien definieren die grundlegenden Prinzipien für die Codierung und Sicherheit im Projekt. Sie gewährleisten Konsistenz, Wartbarkeit und Sicherheit des Codes.
|
||||
|
||||
## Verfügbare Standards
|
||||
|
||||
- [Coding Guidelines](/standards/CODING-GUIDELINES.md) - Allgemeine Coding-Standards für das Projekt
|
||||
- [Sicherheitsrichtlinien](/standards/SICHERHEITS-GUIDELINES.md) - Standards für sichere Softwareentwicklung
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
Die [Coding Guidelines](/standards/CODING-GUIDELINES.md) definieren die technischen Standards für die Codeentwicklung, einschließlich:
|
||||
|
||||
- Klassenstruktur und -design
|
||||
- Property- und Methodendefinitionen
|
||||
- Nutzung moderner PHP-Features
|
||||
- Fehlerbehandlung
|
||||
- Dokumentation
|
||||
- Namenskonventionen
|
||||
|
||||
## Sicherheitsrichtlinien
|
||||
|
||||
Die [Sicherheitsrichtlinien](/standards/SICHERHEITS-GUIDELINES.md) bieten umfassende Anleitungen zur sicheren Softwareentwicklung:
|
||||
|
||||
- Input-Validierung
|
||||
- SQL-Injection-Prävention
|
||||
- XSS-Schutz
|
||||
- CSRF-Schutz
|
||||
- Sichere Authentifizierung
|
||||
- Sichere Datenspeicherung
|
||||
- Session-Management
|
||||
|
||||
## Anwendung der Standards
|
||||
|
||||
Alle Teammitglieder sind verpflichtet, diese Standards in ihrer Arbeit anzuwenden. Sie dienen als Grundlage für Code-Reviews und Qualitätssicherung.
|
||||
|
||||
## Abweichungen von Standards
|
||||
|
||||
In Ausnahmefällen können Abweichungen von diesen Standards erforderlich sein. Solche Abweichungen sollten:
|
||||
|
||||
1. Dokumentiert werden
|
||||
2. Begründet werden
|
||||
3. Im Team besprochen werden
|
||||
4. Auf ein Minimum beschränkt werden
|
||||
|
||||
## Aktualisierungen
|
||||
|
||||
Diese Standards werden regelmäßig überprüft und aktualisiert, um sicherzustellen, dass sie den neuesten Best Practices und Technologien entsprechen.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user