# Admin Form Fields Usage Guide Dokumentation für das neue composition-basierte Admin Form Field System. ## Übersicht Das Admin Form Field System wurde vollständig refactored zu einem composition-basierten Design ohne Vererbung. Alle Fields implementieren das `FormField` Interface und nutzen Value Objects für gemeinsame Funktionalität. ## Architektur ``` FormField (Interface) ├── FieldMetadata (Value Object) - name, label, help ├── FieldAttributes (Value Object) - HTML attributes ├── FieldWrapper (Component) - wrapping logic └── Field Implementations (ohne extends!) ├── TextField ├── EmailField ├── NumberField ├── TextareaField ├── SelectField ├── DateTimeField ├── CheckboxField └── HiddenField ``` ## Verfügbare Field Types ### TextField Einfaches Text Input Field. ```php use App\Framework\Admin\FormFields\Fields\TextField; $field = TextField::create( name: 'username', label: 'Username', value: 'john_doe', required: true, placeholder: 'Enter your username', help: 'Choose a unique username' ); $form = $field->render($formBuilder); ``` ### EmailField Email Input mit Browser-Validierung. ```php use App\Framework\Admin\FormFields\Fields\EmailField; $field = EmailField::create( name: 'email', label: 'Email Address', value: 'user@example.com', required: true, placeholder: 'you@example.com', help: 'We will never share your email' ); $form = $field->render($formBuilder); ``` ### NumberField Number Input mit min/max/step Constraints. ```php use App\Framework\Admin\FormFields\Fields\NumberField; $field = NumberField::create( name: 'age', label: 'Age', value: 25, required: true, min: 18, max: 120, step: 1, help: 'Must be 18 or older' ); $form = $field->render($formBuilder); ``` ### TextareaField Multi-line Text Input. ```php use App\Framework\Admin\FormFields\Fields\TextareaField; $field = TextareaField::create( name: 'description', label: 'Description', value: 'Long text content...', required: false, placeholder: 'Enter description', help: 'Provide a detailed description', rows: 10 ); $form = $field->render($formBuilder); ``` ### SelectField Dropdown Select mit Options. ```php use App\Framework\Admin\FormFields\Fields\SelectField; $field = SelectField::create( name: 'status', label: 'Status', options: [ 'draft' => 'Draft', 'published' => 'Published', 'archived' => 'Archived' ], value: 'draft', required: true, help: 'Select campaign status', placeholder: 'Choose status...' ); $form = $field->render($formBuilder); ``` ### DateTimeField DateTime Input mit verschiedenen Typen. ```php use App\Framework\Admin\FormFields\Fields\DateTimeField; // DateTime Local $field = DateTimeField::create( name: 'release_date', label: 'Release Date', value: new \DateTime('2024-06-15 14:30:00'), required: true, help: 'When to release the track', type: 'datetime-local' ); // Date Only $field = DateTimeField::create( name: 'birth_date', label: 'Birth Date', value: '1990-05-20', type: 'date' ); // Time Only $field = DateTimeField::create( name: 'appointment_time', label: 'Time', value: '14:30', type: 'time' ); $form = $field->render($formBuilder); ``` ### CheckboxField Checkbox mit custom checked value. ```php use App\Framework\Admin\FormFields\Fields\CheckboxField; $field = CheckboxField::create( name: 'accept_terms', label: 'I accept the terms and conditions', value: true, required: true, help: 'You must accept to continue', checkedValue: '1' ); $form = $field->render($formBuilder); ``` ### HiddenField Hidden Input (kein Label/Wrapper). ```php use App\Framework\Admin\FormFields\Fields\HiddenField; $field = HiddenField::create( name: 'campaign_id', value: 'abc123' ); $form = $field->render($formBuilder); ``` ## FormFieldFactory Usage Die `FormFieldFactory` erstellt Fields aus Array-Konfiguration: ```php use App\Framework\Admin\FormFields\FormFieldFactory; $factory = new FormFieldFactory(); // Single field from config $field = $factory->createFromConfig([ 'type' => 'text', 'name' => 'artist_name', 'label' => 'Artist Name', 'value' => 'John Doe', 'required' => true, 'placeholder' => 'Enter artist name', 'help' => 'The name of the performing artist' ]); // Multiple fields from config array $fields = $factory->createMultiple([ [ 'type' => 'text', 'name' => 'track_title', 'label' => 'Track Title', 'required' => true ], [ 'type' => 'select', 'name' => 'genre', 'label' => 'Genre', 'options' => [ 'pop' => 'Pop', 'rock' => 'Rock', 'jazz' => 'Jazz' ], 'required' => true ], [ 'type' => 'datetime', 'name' => 'release_date', 'label' => 'Release Date', 'datetime_type' => 'datetime-local', 'required' => true ] ]); // Render all fields to form foreach ($fields as $field) { $formBuilder = $field->render($formBuilder); } ``` ## AdminFormFactory Integration Die `AdminFormFactory` nutzt automatisch die `FormFieldFactory`: ```php use App\Framework\Admin\Factories\AdminFormFactory; use App\Framework\Admin\ValueObjects\AdminFormConfig; use App\Framework\Http\Method; $formFactory = $container->get(AdminFormFactory::class); $formConfig = new AdminFormConfig( resource: 'campaigns', action: '/admin/campaigns/store', method: Method::POST, fields: [ 'track_title' => [ 'type' => 'text', 'label' => 'Track Title', 'required' => true, 'placeholder' => 'Enter track title' ], 'artist_name' => [ 'type' => 'text', 'label' => 'Artist Name', 'required' => true ], 'release_date' => [ 'type' => 'datetime', 'label' => 'Release Date', 'datetime_type' => 'datetime-local', 'required' => true, 'help' => 'When to release the track' ], 'genre' => [ 'type' => 'select', 'label' => 'Genre', 'options' => [ 'pop' => 'Pop', 'rock' => 'Rock', 'electronic' => 'Electronic', 'jazz' => 'Jazz' ], 'required' => true ], 'description' => [ 'type' => 'textarea', 'label' => 'Description', 'rows' => 5, 'placeholder' => 'Track description...' ], 'is_featured' => [ 'type' => 'checkbox', 'label' => 'Feature this track', 'checked_value' => '1' ] ], data: [ 'track_title' => 'Summer Vibes', 'artist_name' => 'DJ Cool', 'is_featured' => true ] ); $form = $formFactory->create($formConfig); ``` ## Framework Compliance Das neue System folgt allen Framework-Prinzipien: ### ✅ No Inheritance - Alle Fields sind `final readonly` classes - Keine `extends` oder `abstract` classes - Pure composition über Value Objects und Components ### ✅ Immutability - Alle classes `readonly` - Value Objects für gemeinsame Daten - `withAdditional()` für Attribute-Erweiterung ### ✅ Explicit Dependencies - Constructor Injection für alle Dependencies - Keine globalen States oder Service Locators - Klare Dependency Chain ### ✅ Type Safety - Strikte Typisierung überall - Value Objects statt Arrays - Interface-basiertes Design ## Extension Pattern Neue Field Types hinzufügen: ```php namespace App\Framework\Admin\FormFields\Fields; use App\Framework\Admin\FormFields\FormField; use App\Framework\Admin\FormFields\ValueObjects\FieldAttributes; use App\Framework\Admin\FormFields\ValueObjects\FieldMetadata; use App\Framework\Admin\FormFields\Components\FieldWrapper; use App\Framework\View\FormBuilder; use App\Framework\View\ValueObjects\FormElement; final readonly class ColorPickerField implements FormField { public function __construct( private FieldMetadata $metadata, private FieldAttributes $attributes, private FieldWrapper $wrapper, private mixed $value = null ) {} public static function create( string $name, string $label, mixed $value = null, bool $required = false, ?string $help = null ): self { return new self( metadata: new FieldMetadata($name, $label, $help), attributes: new FieldAttributes( name: $name, id: $name, required: $required ), wrapper: new FieldWrapper(), value: $value ); } public function render(FormBuilder $form): FormBuilder { $attrs = $this->attributes->withAdditional(['type' => 'color']); $attrArray = $attrs->toArray(); if ($this->value !== null) { $attrArray['value'] = (string) $this->value; } $input = FormElement::create('input', $attrArray); $wrapped = $this->wrapper->wrap($input, $this->metadata); return $form->addElement($wrapped); } public function getName(): string { return $this->metadata->name; } public function getValue(): mixed { return $this->value; } public function getLabel(): string { return $this->metadata->label; } } ``` Dann in `FormFieldFactory` registrieren: ```php // In createFromConfig() method return match ($type) { // ... existing types 'color' => ColorPickerField::create( name: $name, label: $config['label'] ?? ucfirst($name), value: $config['value'] ?? null, required: $config['required'] ?? false, help: $config['help'] ?? null ), // ... }; ``` ## Best Practices 1. **Verwende Factory Methods**: Nutze `::create()` statt direkter Konstruktor-Aufrufe 2. **Type-spezifische Validierung**: Jeder Field Type validiert seine Inputs 3. **Wiederverwendbare Komponenten**: `FieldWrapper` für konsistentes HTML 4. **Value Objects**: `FieldMetadata`, `FieldAttributes`, `FieldOptions` für gemeinsame Daten 5. **Interface-basiert**: Alle Fields implementieren `FormField` Interface 6. **Composition**: Komponiere Funktionalität statt Vererbung zu nutzen ## Migration von alter zu neuer API ### Alt (direkter FormBuilder): ```php $form->addTextInput('username', $value, 'Username'); $form->addEmailInput('email', $value, 'Email'); ``` ### Neu (composition-basierte Fields): ```php $usernameField = TextField::create('username', 'Username', value: $value); $emailField = EmailField::create('email', 'Email', value: $value); $form = $usernameField->render($form); $form = $emailField->render($form); ``` ### Oder via Factory: ```php $fields = $fieldFactory->createMultiple([ ['type' => 'text', 'name' => 'username', 'label' => 'Username', 'value' => $value], ['type' => 'email', 'name' => 'email', 'label' => 'Email', 'value' => $value] ]); foreach ($fields as $field) { $form = $field->render($form); } ``` ## Testing ```php describe('TextField', function () { it('renders text input with value', function () { $field = TextField::create( name: 'username', label: 'Username', value: 'john_doe', required: true ); $formBuilder = FormBuilder::create('/test', 'POST', $this->formIdGenerator); $result = $field->render($formBuilder); $html = $result->build(); expect($html)->toContain('type="text"'); expect($html)->toContain('name="username"'); expect($html)->toContain('value="john_doe"'); expect($html)->toContain('required="required"'); }); }); ```