- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
492 lines
12 KiB
Markdown
492 lines
12 KiB
Markdown
# 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"');
|
|
});
|
|
});
|
|
```
|