- 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
12 KiB
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.
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.
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.
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.
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.
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.
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.
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).
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:
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:
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 readonlyclasses - Keine
extendsoderabstractclasses - 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:
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:
// 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
- Verwende Factory Methods: Nutze
::create()statt direkter Konstruktor-Aufrufe - Type-spezifische Validierung: Jeder Field Type validiert seine Inputs
- Wiederverwendbare Komponenten:
FieldWrapperfür konsistentes HTML - Value Objects:
FieldMetadata,FieldAttributes,FieldOptionsfür gemeinsame Daten - Interface-basiert: Alle Fields implementieren
FormFieldInterface - Composition: Komponiere Funktionalität statt Vererbung zu nutzen
Migration von alter zu neuer API
Alt (direkter FormBuilder):
$form->addTextInput('username', $value, 'Username');
$form->addEmailInput('email', $value, 'Email');
Neu (composition-basierte Fields):
$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:
$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
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"');
});
});