Files
vollversammlung/Doku/symfony_entwicklung.md

292 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Symfony Entwicklungs-Dokumentation
## 1. Provider/Processor-Pattern
### Pattern-Struktur
Alle API-Anfragen durchlaufen zwei Phasen:
- **Provider**: Daten aus verschiedenen Quellen zusammenführen, Validierung vorbereiten, Domain-Logik entkoppeln
- **Processor**: Daten persistieren, Events auslösen, Response aufbauen
### Beispiel: Anwesenheit registrieren
```php
// src/Provider/AnwesenheitCreateProvider.php
final class AnwesenheitCreateProvider
{
public function __construct(
private VeranstaltungRepository $vr,
private PersonenRepository $pr,
private MandantIdScope $scope
) {}
public function provides(CreateAnwesenheitRequest $request): AnwesenheitCommand
{
$veranstaltung = $this->vr->findScop($request->veranstaltungId, $this->scope);
if (!$veranstaltung) {
throw new VeranstaltungNotFoundException($request->veranstaltungId);
}
return new AnwesenheitCommand(
veranstaltung: $veranstaltung,
personenId: $request->personenId,
status: $request->status
);
}
}
// src/Processor/AnwesenheitCreateProcessor.php
final class AnwesenheitCreateProcessor
{
public function __construct(
private EntityManagerInterface $em,
private EventDispatcherInterface $dispatcher
) {}
public function process(AnwesenheitCommand $command): AnwesenheitCreatedEvent
{
$anwesenheit = new Anwesenheit();
$anwesenheit->setVeranstaltung($command->veranstaltung);
$anwesenheit->setPersonenId($command->personenId);
// ...
$this->em->persist($anwesenheit);
$this->em->flush();
$event = new AnwesenheitCreatedEvent($anwesenheit);
$this->dispatcher->dispatch($event);
return $event;
}
}
```
### Controller-Einbindung
```php
#[Route('/veranstaltungen/{id}/anwesenheit', name: 'erstellen_anwesenheit', methods: ['POST'])]
public function create(CreateAnwesenheitRequestDto $dto, RequestCreator $creator): JsonResponse
{
try {
$command = $this->providerProvider->provides($dto);
$result = $this->processorProvider->process($command);
return new JsonResponse(['data' => $result], 201);
} catch (EntityNotFoundException $e) {
return JsonResponse::createNotFound($e->getMessage());
}
}
```
### Muster-Regeln
- Provider enthalten **keine** Persistenz-Logik
- Processor enthalten **keine** Validierung-Logik
- Beide sind **stateless** und leicht testbar
- Commands und Events als Data Transfer Objekte (POPOs)
---
## 2. Coding Standards
### PHP Version & Basis
- PHP >= 8.4 (LTS empfohlen)
- PSR-12 als Code-Stil-Richtlinie
- PHPStan Level 6-7
### Naming Conventions
| Typ | Konvention | Beispiel |
|-----|-----------|----------|
| Klassen | PascalCase + Substantiv | `AnwesenheitController` |
| Methoden | camelCase + Verb oder Aktonsnamen | `registriereAnwesenheit()` |
| Variablen | camelCase | `$veranstaltungsId` |
| Constants | UPPER_SNAKE_CASE | `MAX_TEILNEHMER` |
### Controller-Konventionen
- Keine Domain-Logik immer delegieren an Provider/Processor
- DTOs für Request/Response (keine Entities direkt)
```json
{
"data": {
"id": 1,
"status": "present"
},
"meta": {
"message": "Anwesenheit erfolgreich registriert"
}
}
```
### Exception Handling (→ siehe Abschnitt 3)
- Domain Exceptions werfen (keine raw Symfony Exceptions nach außen)
- Nur `JsonResponse` mit definiertem Error-Schema zurückgeben
---
## 3. Fehlerbehandlung & API Error Schema
### Exceptions-Hierarchie
Domain Exceptions → ControllerExceptionHandler → JSON Response
```php
// Domain Exception
class VeranstaltungNotFoundException extends RuntimeException implements DomainExceptionInterface {}
// Controller Exception Handler (Kernel-Register)
#[AsController]
class ControllerExceptionHandler
{
public function __invoke(DomainExceptionInterface $e): JsonResponse
{
return new JsonResponse([
'errors' => [[
'status' => 422, // oder je nach Situation
'title' => ucfirst($e->getType()),
'detail' => $e->getMessage(),
]]
], 422);
}
}
```
### HTTP Status Codes
| Code | Verwendigunbg |
|------|-----------|
| `200` | OK (GET, PUT) |
| `201` | Created (POST, Entity erstellt) |
| `400` | Invalid Request Body / Validation Error |
| `403` | Forbidden (Mandant fehlt) |
| `404` | Not Found |
| `500` | Internal Server Error |
---
## 4. Logging & Mercure
### Events-Logging-Logik
Jeder Processor löst nach Persistierung Events aus:
```php
$this->dispatcher->dispatch(new AnwesenheitAktualisiertEvent($anwesenheit));
```
Events enthalten:
- Entity-Daten (keine sensiblen)
- Timestamp
- MandantID scope
- ActionTyp ('created', 'updated')
### Mercure Integration - Vorschläge & Nutzungsszenarien
- **Echtzeit-Anwesenheitsliste**: Websocket oder Server-Sent Events über Mercure Hub
- **Live-Benachrichtigungen** bei Statusänderungen
#### Konfigurationsoptionen für Mercure:
1. **Pulled** (Clientseitig abonnieren): Client ruft `/mercure/hub` Periodisch ab
2. **Pushed** (Serverseitig publish): Processor publiziert Events direkt an Hub
3. **Hybrid**: Pushed bei User-Aktion (z.b. Anwesenheit registrieren), Pulled bei Statusabfragen
**Empfehlung**: Mercure als optional, nur wenn Echtzeit-Funktionalität wirklich gebraucht wird. Nicht default aktivieren.
---
## 5. Datenbank/Migrationen - Entwicklungskonventionen
### Doctrine Entity-Spezifikation
- **Entities** POPOs (Plain Old PHP Objects) mit Annotationen/Attributes für Mapping
- **Repository** nur Domain-spezifische Queries, kein raw SQL
- **Keine** Business-Logik in Entities stattdessen Services / Provider / Processor
- **Idempotenz**: Updates per primary key
### Migrationen-Konventionen
- Jede Tabelle hat `mandant_id`, `created_at`, `updated_at` (Timestamp)
- Fremdschlüssel immer als Index + NOT NULL
- Migrations in Version kontrolliert: `php bin/console make:migration`
- **Keine** manuellen Schema-Changes ohne Migration
### Multi-Tenant-Scope Enforcement
```
Entity::mandant_id → Unique across Mandanten. Alle Queries scoped per MandantIdContext (z.B. durch Attribute/Listener)
```
---
## 6. TDD Workflow (Red-Green-Refactor)
### Test-Schichten Priorität (TDD Red-Green)
1. **Controller/Route Tests** Request→Response, Status-Codes, JSON Schema
2. **Provider/Processor Unittests** isolierte logik-test der einzelnen Domäne-Layer
3. **Integrationstests** wenn nötig (DB-Kontext, Services)
### Test-Struktur in PHPUnit
```
api/tests/Unit/Provider/
├── AnwesenheitCreateProviderTest.php ← Provider ohne DB (mock repository)
└── VeranstaltungFindProviderTest.php
api/tests/Unit/Processor/
└── AnwesenheitCreateProcessorTest.php ← Processor mit mock EM
api/tests/Functional/Controller/
└── AnwesenheitControllerTest.php ← Full HTTP request via HttpBrowser
```
### Test-Beispiel PHPUnit
```php
// tests/Unit/Provider/AnwesenheitCreateProviderTest.php
class AnwesenheitCreateProviderTest extends TestCase
{
public function testProvidesCommandWhenVeranstaltungFound(): void
{
$veranstaltung = new Veranstaltung();
$request = new CreateAnwesenheitRequestDto(123);
$provider = new AnwesenheitCreateProvider( // Mocks injected
VeranstaltungRepositoryMock($veranstaltung),
MandantIdScope('mandant-xyz')
);
$result = $provider->provides($request);
self::assertInstanceOf(AnwesenheitCommand::class, $result);
self::assertSame($veranstaltung, $result->getVeranstaltung());
}
}
```
### Test-Prioritäten für Implementierung
1. Controller/Routen-Tests schreiben → **failend** (RED)
2. Minimal-Implementierung schreiben → **test bestanden** (GREEN)
3. Refactor (Code-Qualität, Tests verbessern)
4. Nächster Test (höhere Abdeckung im Provider/Processor)
---
## 7. Framework-Bundles
### Philosophie
- Kern-Symfony nur verwenden (Mime, HttpFoundation, Serializer, Validator, Security)
- Neue Bundles **nur bei Bedarf** dokumentieren und begründen
| Bundle | Verwendung | Begründung erforderlich |
|--------|------------|------------------------|
| `symfony/maker` | Boilerplate | Nur für Initialisierung, nicht produktiv |
| `api-platform/core` | REST API | **Explizit vermieden** manuelle Controller |
| `sensio/framework-extra-bundle` | Annotations/Cache | Symfony 7.4 native statt Bundles bevorzugen |
| `symfonycasts/validator` | Custom Validators | Nur bei komplexer Validierung |
| `mercure/bundle` | Mercure-Integration | Nur wenn Echtzeit benötigt wird |
---
## Zusammenfassung Entwicklungsflow
1. Test schreiben (RED) → failend
2. Minimal-Implementierung als Provider/Processor/Gateway
3. Test bestehen lassen (GREEN)
4. Refaktorieren und Qualität prüfen (PHPStan, Coding Standards)
5. Entity/Migration erstellen wenn nötig **nach** Code-Phase
Die Doku wird mit dem Projekt aktualisiert.