fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
458
docs/livecomponents/livecomponent-formbuilder-usage.md
Normal file
458
docs/livecomponents/livecomponent-formbuilder-usage.md
Normal file
@@ -0,0 +1,458 @@
|
||||
# LiveComponent FormBuilder Integration
|
||||
|
||||
Elegante Integration zwischen LiveComponent System und bestehendem FormBuilder.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ MultiStepFormDefinition │ ← Value Object mit Steps
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
├─► FormStepDefinition ← Step-Info + Fields
|
||||
│
|
||||
└─► FormFieldDefinition ← Field-Config (text, email, etc.)
|
||||
│
|
||||
├─► FieldType (enum)
|
||||
├─► FieldCondition (conditional rendering)
|
||||
└─► StepValidator (validation logic)
|
||||
|
||||
┌─────────────────────────┐
|
||||
│ MultiStepFormComponent │ ← Generic LiveComponent
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
└─► LiveFormBuilder ← Erweitert bestehenden FormBuilder
|
||||
│
|
||||
└─► FormBuilder (bestehend, wiederverwendet!)
|
||||
```
|
||||
|
||||
## Verwendungsbeispiel
|
||||
|
||||
### 1. Form Definition erstellen (Deklarativ, Type-Safe)
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\FormBuilder\MultiStepFormDefinition;
|
||||
use App\Framework\LiveComponents\FormBuilder\FormStepDefinition;
|
||||
use App\Framework\LiveComponents\FormBuilder\FormFieldDefinition;
|
||||
use App\Framework\LiveComponents\FormBuilder\FieldCondition;
|
||||
|
||||
// Deklarative Form-Definition - kein Code-Duplikat!
|
||||
$userRegistrationForm = new MultiStepFormDefinition(
|
||||
steps: [
|
||||
// Step 1: Personal Information
|
||||
new FormStepDefinition(
|
||||
title: 'Persönliche Informationen',
|
||||
description: 'Bitte geben Sie Ihre persönlichen Daten ein',
|
||||
fields: [
|
||||
FormFieldDefinition::text(
|
||||
name: 'first_name',
|
||||
label: 'Vorname',
|
||||
required: true
|
||||
),
|
||||
FormFieldDefinition::text(
|
||||
name: 'last_name',
|
||||
label: 'Nachname',
|
||||
required: true
|
||||
),
|
||||
FormFieldDefinition::email(
|
||||
name: 'email',
|
||||
label: 'E-Mail Adresse',
|
||||
required: true
|
||||
)
|
||||
],
|
||||
validator: new PersonalInfoValidator()
|
||||
),
|
||||
|
||||
// Step 2: Account Type
|
||||
new FormStepDefinition(
|
||||
title: 'Konto-Typ',
|
||||
description: 'Wählen Sie Ihren Konto-Typ',
|
||||
fields: [
|
||||
FormFieldDefinition::radio(
|
||||
name: 'account_type',
|
||||
label: 'Account Type',
|
||||
options: [
|
||||
'personal' => 'Privatkonto',
|
||||
'business' => 'Geschäftskonto'
|
||||
],
|
||||
required: true
|
||||
),
|
||||
// Conditional Field - nur bei Business
|
||||
FormFieldDefinition::text(
|
||||
name: 'company_name',
|
||||
label: 'Firmenname',
|
||||
required: true
|
||||
)->showWhen(
|
||||
FieldCondition::equals('account_type', 'business')
|
||||
),
|
||||
FormFieldDefinition::text(
|
||||
name: 'vat_number',
|
||||
label: 'USt-IdNr.',
|
||||
placeholder: 'DE123456789'
|
||||
)->showWhen(
|
||||
FieldCondition::equals('account_type', 'business')
|
||||
)
|
||||
],
|
||||
validator: new AccountTypeValidator()
|
||||
),
|
||||
|
||||
// Step 3: Preferences
|
||||
new FormStepDefinition(
|
||||
title: 'Präferenzen',
|
||||
description: 'Passen Sie Ihre Einstellungen an',
|
||||
fields: [
|
||||
FormFieldDefinition::checkbox(
|
||||
name: 'newsletter',
|
||||
label: 'Newsletter abonnieren'
|
||||
),
|
||||
FormFieldDefinition::select(
|
||||
name: 'language',
|
||||
label: 'Bevorzugte Sprache',
|
||||
options: [
|
||||
'en' => 'English',
|
||||
'de' => 'Deutsch',
|
||||
'fr' => 'Français'
|
||||
],
|
||||
defaultValue: 'de'
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
submitHandler: new UserRegistrationSubmitHandler()
|
||||
);
|
||||
```
|
||||
|
||||
### 2. Validator implementieren
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\FormBuilder\StepValidator;
|
||||
|
||||
final readonly class PersonalInfoValidator implements StepValidator
|
||||
{
|
||||
public function validate(array $formData): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (empty($formData['first_name'] ?? '')) {
|
||||
$errors['first_name'] = 'Vorname ist erforderlich';
|
||||
}
|
||||
|
||||
if (empty($formData['last_name'] ?? '')) {
|
||||
$errors['last_name'] = 'Nachname ist erforderlich';
|
||||
}
|
||||
|
||||
if (empty($formData['email'] ?? '')) {
|
||||
$errors['email'] = 'E-Mail ist erforderlich';
|
||||
} elseif (!filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$errors['email'] = 'Ungültige E-Mail Adresse';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class AccountTypeValidator implements StepValidator
|
||||
{
|
||||
public function validate(array $formData): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (empty($formData['account_type'] ?? '')) {
|
||||
$errors['account_type'] = 'Bitte wählen Sie einen Konto-Typ';
|
||||
}
|
||||
|
||||
// Conditional validation für Business
|
||||
if (($formData['account_type'] ?? '') === 'business') {
|
||||
if (empty($formData['company_name'] ?? '')) {
|
||||
$errors['company_name'] = 'Firmenname ist erforderlich';
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Submit Handler implementieren
|
||||
|
||||
```php
|
||||
use App\Framework\LiveComponents\FormBuilder\FormSubmitHandler;
|
||||
use App\Framework\LiveComponents\FormBuilder\SubmitResult;
|
||||
|
||||
final readonly class UserRegistrationSubmitHandler implements FormSubmitHandler
|
||||
{
|
||||
public function __construct(
|
||||
private UserService $userService
|
||||
) {}
|
||||
|
||||
public function handle(array $formData): SubmitResult
|
||||
{
|
||||
try {
|
||||
$user = $this->userService->registerUser(
|
||||
firstName: $formData['first_name'],
|
||||
lastName: $formData['last_name'],
|
||||
email: $formData['email'],
|
||||
accountType: $formData['account_type'],
|
||||
companyName: $formData['company_name'] ?? null,
|
||||
newsletter: ($formData['newsletter'] ?? 'no') === 'yes',
|
||||
language: $formData['language'] ?? 'de'
|
||||
);
|
||||
|
||||
return SubmitResult::success(
|
||||
message: 'Registrierung erfolgreich!',
|
||||
redirectUrl: '/dashboard',
|
||||
data: ['user_id' => $user->id]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return SubmitResult::failure(
|
||||
message: 'Registrierung fehlgeschlagen: ' . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Controller Setup
|
||||
|
||||
```php
|
||||
use App\Framework\Http\Attributes\Route;
|
||||
use App\Framework\Http\Method;
|
||||
use App\Framework\Http\ViewResult;
|
||||
use App\Framework\LiveComponents\FormBuilder\MultiStepFormComponent;
|
||||
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
||||
|
||||
final readonly class UserRegistrationController
|
||||
{
|
||||
#[Route('/register', method: Method::GET)]
|
||||
public function showRegistrationForm(): ViewResult
|
||||
{
|
||||
// Form Definition (könnte auch aus Container kommen)
|
||||
$formDefinition = $this->createUserRegistrationForm();
|
||||
|
||||
// Component erstellen
|
||||
$component = new MultiStepFormComponent(
|
||||
id: ComponentId::generate('user-registration'),
|
||||
formDefinition: $formDefinition
|
||||
);
|
||||
|
||||
return new ViewResult(
|
||||
template: 'pages/register',
|
||||
data: [
|
||||
'registration_form' => $component
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function createUserRegistrationForm(): MultiStepFormDefinition
|
||||
{
|
||||
return new MultiStepFormDefinition(
|
||||
steps: [
|
||||
// ... (wie oben)
|
||||
],
|
||||
submitHandler: new UserRegistrationSubmitHandler($this->userService)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Template Usage
|
||||
|
||||
```html
|
||||
<!-- pages/register.view.php -->
|
||||
<div class="registration-page">
|
||||
<h1>Benutzerregistrierung</h1>
|
||||
|
||||
<!-- LiveComponent einbinden -->
|
||||
<livecomponent name="multi-step-form" data="registration_form" />
|
||||
</div>
|
||||
```
|
||||
|
||||
## Vorteile dieser Lösung
|
||||
|
||||
### ✅ Kein Code-Duplikat
|
||||
- Nutzt bestehenden `FormBuilder` aus View-Modul
|
||||
- `LiveFormBuilder` erweitert nur mit LiveComponent-Features
|
||||
- Keine doppelte Field-Rendering-Logik
|
||||
|
||||
### ✅ Type-Safe & Framework-Compliant
|
||||
- Alle Value Objects: `FormFieldDefinition`, `FormStepDefinition`, `MultiStepFormDefinition`
|
||||
- Readonly Classes überall
|
||||
- Enums für `FieldType`
|
||||
|
||||
### ✅ Deklarativ statt Imperativ
|
||||
- Form-Definition rein deklarativ (kein Code für Rendering)
|
||||
- Klare Trennung: Definition vs. Rendering vs. Validation vs. Submission
|
||||
|
||||
### ✅ Conditional Fields eingebaut
|
||||
```php
|
||||
FormFieldDefinition::text('company_name', 'Firma', required: true)
|
||||
->showWhen(FieldCondition::equals('account_type', 'business'))
|
||||
```
|
||||
|
||||
### ✅ Wiederverwendbare Validators
|
||||
```php
|
||||
final readonly class EmailValidator implements StepValidator
|
||||
{
|
||||
public function validate(array $formData): array
|
||||
{
|
||||
// Wiederverwendbare Validation-Logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Testbar
|
||||
```php
|
||||
// Unit Test für Validator
|
||||
it('validates email format', function () {
|
||||
$validator = new PersonalInfoValidator();
|
||||
|
||||
$errors = $validator->validate(['email' => 'invalid']);
|
||||
|
||||
expect($errors)->toHaveKey('email');
|
||||
});
|
||||
|
||||
// Integration Test für Component
|
||||
it('moves to next step after validation', function () {
|
||||
$component = new MultiStepFormComponent(
|
||||
id: ComponentId::generate('test'),
|
||||
formDefinition: $this->testFormDef
|
||||
);
|
||||
|
||||
$result = $component->nextStep([
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'email' => 'john@example.com'
|
||||
]);
|
||||
|
||||
expect($result->get('current_step'))->toBe(2);
|
||||
});
|
||||
```
|
||||
|
||||
## Erweiterungsmöglichkeiten
|
||||
|
||||
### Custom Field Types
|
||||
|
||||
```php
|
||||
// Neuen FieldType hinzufügen
|
||||
enum FieldType: string
|
||||
{
|
||||
case TEXT = 'text';
|
||||
case EMAIL = 'email';
|
||||
// ... existing types
|
||||
case DATE = 'date';
|
||||
case PHONE = 'phone';
|
||||
case CURRENCY = 'currency';
|
||||
}
|
||||
|
||||
// LiveFormBuilder erweitern
|
||||
final readonly class LiveFormBuilder
|
||||
{
|
||||
public function addLiveDateInput(
|
||||
string $name,
|
||||
string $label,
|
||||
?string $value = null
|
||||
): self {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Field Conditions
|
||||
|
||||
```php
|
||||
final readonly class AndCondition implements FieldConditionContract
|
||||
{
|
||||
public function __construct(
|
||||
private FieldCondition $left,
|
||||
private FieldCondition $right
|
||||
) {}
|
||||
|
||||
public function matches(array $formData): bool
|
||||
{
|
||||
return $this->left->matches($formData)
|
||||
&& $this->right->matches($formData);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
FormFieldDefinition::text('special_field', 'Special')
|
||||
->showWhen(
|
||||
new AndCondition(
|
||||
FieldCondition::equals('account_type', 'business'),
|
||||
FieldCondition::equals('country', 'DE')
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Renderers
|
||||
|
||||
```php
|
||||
interface FieldRenderer
|
||||
{
|
||||
public function render(FormFieldDefinition $field, mixed $value): string;
|
||||
}
|
||||
|
||||
final readonly class CustomTextRenderer implements FieldRenderer
|
||||
{
|
||||
public function render(FormFieldDefinition $field, mixed $value): string
|
||||
{
|
||||
// Custom rendering logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Vergleich: Vorher vs. Nachher
|
||||
|
||||
### ❌ Vorher (Code-Duplikat)
|
||||
|
||||
```php
|
||||
// Hardcoded Form in Component
|
||||
public function getRenderData(): ComponentRenderData
|
||||
{
|
||||
return new ComponentRenderData(
|
||||
templatePath: 'livecomponent-dynamic-form',
|
||||
data: [
|
||||
'form_first_name' => $formData['first_name'] ?? '',
|
||||
'form_last_name' => $formData['last_name'] ?? '',
|
||||
// ... 50 weitere Zeilen hardcoded mappings
|
||||
]
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Nachher (Wiederverwendbar)
|
||||
|
||||
```php
|
||||
// Deklarative Definition
|
||||
$formDef = new MultiStepFormDefinition(
|
||||
steps: [
|
||||
new FormStepDefinition(
|
||||
title: 'Personal Info',
|
||||
fields: [
|
||||
FormFieldDefinition::text('first_name', 'First Name'),
|
||||
FormFieldDefinition::text('last_name', 'Last Name')
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// Generic Component - keine Anpassungen nötig!
|
||||
$component = new MultiStepFormComponent(
|
||||
id: ComponentId::generate('my-form'),
|
||||
formDefinition: $formDef
|
||||
);
|
||||
```
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Diese Integration:
|
||||
- ✅ Nutzt bestehenden `FormBuilder` (keine Code-Duplizierung)
|
||||
- ✅ Erweitert ihn minimal für LiveComponent-Features
|
||||
- ✅ Vollständig type-safe mit Value Objects
|
||||
- ✅ Framework-compliant (readonly, final, composition)
|
||||
- ✅ Deklarative Form-Definitionen
|
||||
- ✅ Conditional Fields eingebaut
|
||||
- ✅ Wiederverwendbare Validators
|
||||
- ✅ Generische Component (kein Custom-Code pro Form)
|
||||
- ✅ Einfach testbar
|
||||
- ✅ Leicht erweiterbar
|
||||
Reference in New Issue
Block a user