wip: refactor forms

This commit is contained in:
Björn Fromme
2026-03-16 11:59:09 +01:00
parent 1f5877460b
commit cd7a724e62
9 changed files with 644 additions and 76 deletions
+9
View File
@@ -66,3 +66,12 @@ services:
App\Form\Service\ParticipantRoomChoiceLoaderFactory: App\Form\Service\ParticipantRoomChoiceLoaderFactory:
arguments: arguments:
$choiceListFactory: '@form.choice_list_factory.default' $choiceListFactory: '@form.choice_list_factory.default'
# Participant Field Handler Registry with hybrid handler configuration
App\Form\ParticipantFieldHandler\ParticipantFieldHandlerRegistry:
arguments:
$handlers:
# Simple handlers (no dependencies) - use class names
- 'App\Form\ParticipantFieldHandler\ParticipantAssignedRoomFieldHandler'
# Complex handlers (with dependencies) would use service references like:
# - '@participant.complex.handler'
+40 -19
View File
@@ -5,7 +5,7 @@ namespace App\Form;
use App\BusProNet\Form\CountryType; use App\BusProNet\Form\CountryType;
use App\Form\Model\BookingCreateDto; use App\Form\Model\BookingCreateDto;
use App\Form\Model\ParticipantDto; use App\Form\Model\ParticipantDto;
use App\Form\Service\ParticipantFormConfigurator; use App\Form\Service\ParticipantFieldOptionsProvider;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType; use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@@ -14,12 +14,13 @@ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
class BookingCreateParticipantType extends AbstractType class BookingCreateParticipantType extends AbstractType
{ {
public function __construct( public function __construct(
private readonly ParticipantFormConfigurator $formConfigurator, private readonly ParticipantFieldOptionsProvider $fieldOptionsProvider,
) { ) {
} }
@@ -66,27 +67,47 @@ class BookingCreateParticipantType extends AbstractType
'clean_xss' => true, 'clean_xss' => true,
]) ])
->add('bodyDimensions', BodyDimensionsType::class) ->add('bodyDimensions', BodyDimensionsType::class)
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { ->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'onPreSetData']);
/** @var ParticipantDto|null $participantData */ }
$participantData = $event->getData();
$form = $event->getForm();
if (null === $participantData) { /**
return; * Adds dynamic fields to the form based on participant data.
} */
public function onPreSetData(FormEvent $event): void
{
/** @var ParticipantDto|null $participantData */
$participantData = $event->getData();
$form = $event->getForm();
// Traverse up the form tree to get the root form's data. if (null === $participantData) {
$rootForm = $form; return;
while ($rootForm->getParent()) { }
$rootForm = $rootForm->getParent();
}
/** @var BookingCreateDto $bookingCreateDto */ // Traverse up the form tree to get the root form's data.
$bookingCreateDto = $rootForm->getData(); $rootForm = $form;
while ($rootForm->getParent()) {
$rootForm = $rootForm->getParent();
}
$roomOptions = $this->formConfigurator->getRoomFieldOptions($bookingCreateDto, $participantData->index); /** @var BookingCreateDto $bookingCreateDto */
$form->add('assignedRoomId', ChoiceType::class, $roomOptions); $bookingCreateDto = $rootForm->getData();
});
$this->addDynamicFields($form, $bookingCreateDto, $participantData->index);
}
/**
* Adds all configured dynamic fields to the form.
*/
private function addDynamicFields(FormInterface $form, BookingCreateDto $bookingCreateDto, int $participantIndex): void
{
$dynamicFields = ['assignedRoomId']; // List of fields that need dynamic configuration
foreach ($dynamicFields as $fieldName) {
if ($this->fieldOptionsProvider->hasFieldOptions($fieldName)) {
$fieldOptions = $this->fieldOptionsProvider->getFieldOptions($fieldName, $bookingCreateDto, $participantIndex);
$form->add($fieldName, ChoiceType::class, $fieldOptions);
}
}
} }
public function configureOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver): void
+11 -16
View File
@@ -3,6 +3,7 @@
namespace App\Form; namespace App\Form;
use App\Form\Model\BookingCreateDto; use App\Form\Model\BookingCreateDto;
use App\Form\ParticipantFieldHandler\ParticipantFieldHandlerRegistry;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -13,6 +14,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class BookingCreateStep2Type extends AbstractType class BookingCreateStep2Type extends AbstractType
{ {
public function __construct(
private readonly ParticipantFieldHandlerRegistry $participantFieldHandlerRegistry,
) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder $builder
@@ -35,9 +41,9 @@ class BookingCreateStep2Type extends AbstractType
} }
/** /**
* Handles dynamic updates on POST requests (e.g., from HTMX). * Handles dynamic participant form field updates on POST requests (e.g., from HTMX).
* *
* This listener synchronizes the DTO with the submitted data *before* * This listener synchronizes the BookingCreateDto with the submitted participant data *before*
* the form's children are processed. It then rebuilds the participants * the form's children are processed. It then rebuilds the participants
* field to ensure choice loaders are created with the fresh state. * field to ensure choice loaders are created with the fresh state.
*/ */
@@ -49,21 +55,10 @@ class BookingCreateStep2Type extends AbstractType
/** @var BookingCreateDto $bookingDto */ /** @var BookingCreateDto $bookingDto */
$bookingDto = $form->getData(); $bookingDto = $form->getData();
// If participant data isn't in the submission, we can't do anything. // Process all registered participant field handlers
if (!isset($submittedData['participants']) || !is_array($submittedData['participants'])) { $this->participantFieldHandlerRegistry->processFields($submittedData, $bookingDto);
return;
}
// Manually update the DTO with the submitted room assignments. // Rebuild the 'participants' field with the updated DTO.
foreach ($submittedData['participants'] as $index => $participantData) {
if (isset($participantData['assignedRoomId']) && isset($bookingDto->participants[$index])) {
$roomId = $participantData['assignedRoomId'];
// An unselected choice submits an empty string.
$bookingDto->participants[$index]->assignedRoomId = empty($roomId) ? null : (int)$roomId;
}
}
// Now, rebuild the 'participants' field with the updated DTO.
$this->addParticipantsField($form); $this->addParticipantsField($form);
} }
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace App\Form\ParticipantFieldHandler;
use App\Form\Model\BookingCreateDto;
/**
* Abstract base class providing common functionality for participant field handlers.
*
* This class implements common patterns used across participant field handlers,
* reducing boilerplate code in concrete implementations.
*/
abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandlerInterface
{
/**
* Returns the field names this handler depends on.
*
* Default implementation returns an empty array, meaning no dependencies.
* Override this method in concrete handlers that depend on other fields
* being processed first (e.g., a meal preference handler might depend on
* the room assignment being processed first).
*
* @return string[] Array of field names that must be processed before this handler
*/
public function getDependencies(): array
{
return [];
}
/**
* Determines if this handler should process the field based on submitted data.
*
* Default implementation checks if the field exists in the submitted data.
* Override this method for more complex processing conditions (e.g., only
* process if certain other conditions are met).
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param int $participantIndex The index of the participant being processed
*
* @return bool True if the handler should process this field, false otherwise
*/
public function shouldProcess(array $submittedData, int $participantIndex): bool
{
return isset($submittedData[$this->getFieldName()]);
}
/**
* Safely retrieves a participant object from the booking DTO.
*
* This helper method provides safe access to participant data by checking
* if the participant exists at the given index. Returns null if the
* participant doesn't exist, preventing array access errors.
*
* @param BookingCreateDto $bookingDto The booking DTO containing participants
* @param int $participantIndex The index of the participant to retrieve
*
* @return object|null The participant object, or null if not found
*/
protected function getParticipant(BookingCreateDto $bookingDto, int $participantIndex): ?object
{
return $bookingDto->participants[$participantIndex] ?? null;
}
/**
* Safely extracts a field value from submitted participant data.
*
* This helper method provides safe access to form field values using
* the null coalescing operator. Useful for extracting field values
* without worrying about undefined array keys.
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param string $fieldName The name of the field to retrieve
* @param mixed $default The default value to return if field is not set
*
* @return mixed The field value, or the default value if not found
*/
protected function getFieldValue(array $submittedData, string $fieldName, mixed $default = null): mixed
{
return $submittedData[$fieldName] ?? $default;
}
/**
* Normalizes empty string values to null.
*
* Form inputs often submit empty strings for unselected/empty fields.
* This helper converts those empty strings to null values, which is
* typically more appropriate for database storage and business logic.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value (null if empty, original value otherwise)
*/
protected function normalizeEmptyValue(mixed $value): mixed
{
return empty($value) ? null : $value;
}
/**
* Converts and normalizes string values to integers.
*
* Form inputs submit all values as strings. This helper safely converts
* string values to integers while handling empty strings and non-numeric
* values gracefully by returning null.
*
* @param mixed $value The value to convert to integer
*
* @return int|null The integer value, or null if empty/non-numeric
*/
protected function normalizeIntValue(mixed $value): ?int
{
if (empty($value)) {
return null;
}
return is_numeric($value) ? (int) $value : null;
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Form\ParticipantFieldHandler;
use App\Form\Model\BookingCreateDto;
/**
* Handles processing of the assignedRoomId field for booking participants.
*
* This handler manages room assignments for participants in the booking creation
* process. It processes the assignedRoomId field from form submissions and updates
* the participant DTO with the selected room. The handler properly handles empty
* selections (converting them to null) and validates numeric room IDs.
*
* Field Processing:
* - Extracts room ID from submitted form data
* - Converts empty strings to null (unselected choice)
* - Normalizes string values to integers
* - Updates the participant's assignedRoomId property
*
* Dependencies: None (this is a base field that other handlers may depend on)
*/
class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandler
{
/**
* Returns the form field name this handler processes.
*
* This handler is responsible for the 'assignedRoomId' field, which contains
* the selected room ID for each participant in the booking form.
*
* @return string The field name 'assignedRoomId'
*/
public function getFieldName(): string
{
return 'assignedRoomId';
}
/**
* Processes the assignedRoomId field for a specific participant.
*
* This method extracts the room assignment from the submitted form data and
* updates the corresponding participant in the booking DTO. It handles the
* common form processing pattern where empty selections are submitted as
* empty strings but should be stored as null values.
*
* Processing steps:
* 1. Safely retrieves the participant object from the DTO
* 2. Extracts the assignedRoomId value from submitted data
* 3. Normalizes the value (empty string → null, numeric string → integer)
* 4. Updates the participant's assignedRoomId property
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param BookingCreateDto $bookingDto The booking DTO to update
* @param int $participantIndex The index of the participant being processed
*/
public function processField(array $submittedData, BookingCreateDto $bookingDto, int $participantIndex): void
{
// Safely get the participant object, returning early if not found
$participant = $this->getParticipant($bookingDto, $participantIndex);
if (null === $participant) {
return;
}
// Extract the room ID from form data (defaults to null if not present)
$roomId = $this->getFieldValue($submittedData, $this->getFieldName());
// Convert form string to integer, handling empty selections as null
$participant->assignedRoomId = $this->normalizeIntValue($roomId);
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Form\ParticipantFieldHandler;
use App\Form\Model\BookingCreateDto;
/**
* Interface for handling dynamic participant form field processing.
*
* Participant field handlers are responsible for processing submitted form data
* for booking participants and updating the BookingCreateDto accordingly.
* They support dependency management to ensure fields are processed in the correct order.
*/
interface ParticipantFieldHandlerInterface
{
/**
* Returns the field name this handler processes.
*/
public function getFieldName(): string;
/**
* Returns field names this handler depends on.
*
* @return string[]
*/
public function getDependencies(): array;
/**
* Processes the participant field data from submitted form data and updates the DTO.
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param BookingCreateDto $bookingDto The booking DTO to update
* @param int $participantIndex The participant index being processed
*/
public function processField(array $submittedData, BookingCreateDto $bookingDto, int $participantIndex): void;
/**
* Determines if this handler should process the field based on submitted participant data.
*/
public function shouldProcess(array $submittedData, int $participantIndex): bool;
}
@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
namespace App\Form\ParticipantFieldHandler;
use App\Form\Model\BookingCreateDto;
/**
* Registry for managing and executing participant field handlers in dependency order.
*
* This class ensures that participant field handlers are executed in the correct order
* based on their dependencies, preventing issues where a field depends on
* another field that hasn't been processed yet.
*/
class ParticipantFieldHandlerRegistry
{
/** @var ParticipantFieldHandlerInterface[] Registered handlers indexed by field name */
private array $handlers = [];
/** @var string[]|null Cached array of handler names sorted by dependency order */
private ?array $sortedHandlers = null;
/**
* Initializes the registry with a hybrid array of handlers.
*
* This constructor supports both simple handlers (passed as class names) and
* complex handlers (passed as instantiated service objects). Simple handlers
* with no dependencies can be passed as strings and will be instantiated
* automatically. Complex handlers with dependencies should be passed as
* already-instantiated objects via dependency injection.
*
* @param array<ParticipantFieldHandlerInterface|string> $handlers Array of handler instances or class names
*
* @throws \Exception If a class name cannot be instantiated
*/
public function __construct(array $handlers)
{
foreach ($handlers as $handler) {
if (is_string($handler)) {
// It's a class name - instantiate it (for simple handlers without dependencies)
$handler = new $handler();
}
// If it's already an object (service), use as-is (for complex handlers with dependencies)
$this->addHandler($handler);
}
}
/**
* Registers a participant field handler with the registry.
*
* Handlers are indexed by their field name to ensure uniqueness and enable
* fast lookups. Adding a handler invalidates the dependency sort cache,
* forcing a re-sort on the next processing request.
*
* @param ParticipantFieldHandlerInterface $handler The handler to register
*/
public function addHandler(ParticipantFieldHandlerInterface $handler): void
{
$this->handlers[$handler->getFieldName()] = $handler;
$this->sortedHandlers = null; // Reset cache to force re-sorting with new handler
}
/**
* Processes all participant fields from submitted form data using registered handlers.
*
* This is the main entry point for field processing. It iterates through all
* participants in the submitted data and applies the appropriate handlers in
* dependency order. Each handler determines whether it should process the
* participant's data and updates the booking DTO accordingly.
*
* @param array<string, mixed> $submittedData The submitted form data containing participants array
* @param BookingCreateDto $bookingDto The booking DTO to update with processed field values
*/
public function processFields(array $submittedData, BookingCreateDto $bookingDto): void
{
// Early return if no participant data exists in submission
if (!isset($submittedData['participants']) || !is_array($submittedData['participants'])) {
return;
}
// Get handlers sorted by dependency order (uses cache if available)
$sortedHandlerNames = $this->getSortedHandlers();
// Process each participant's data
foreach ($submittedData['participants'] as $participantIndex => $participantData) {
// Skip invalid participant data
if (!is_array($participantData)) {
continue;
}
// Apply each handler in dependency order
foreach ($sortedHandlerNames as $handlerName) {
$handler = $this->handlers[$handlerName];
// Let each handler decide if it should process this participant's data
if ($handler->shouldProcess($participantData, (int) $participantIndex)) {
$handler->processField($participantData, $bookingDto, (int) $participantIndex);
}
}
}
}
/**
* Returns handler names sorted by dependency order using cached results when possible.
*
* This method implements lazy loading with caching for performance. The dependency
* sort is only performed once and the result is cached until handlers are added
* or modified.
*
* @return string[] Array of handler field names in dependency execution order
*/
private function getSortedHandlers(): array
{
// Return cached result if available
if (null !== $this->sortedHandlers) {
return $this->sortedHandlers;
}
// Perform topological sort and cache the result
$this->sortedHandlers = $this->topologicalSort();
return $this->sortedHandlers;
}
/**
* Performs topological sort to determine safe handler execution order.
*
* This method uses Kahn's algorithm to sort handlers based on their declared
* dependencies. It ensures that no handler is executed before its dependencies
* have been processed, preventing data consistency issues.
*
* The algorithm works by:
* 1. Building a dependency graph of handlers
* 2. Finding handlers with no dependencies (in-degree = 0)
* 3. Iteratively removing handlers and updating dependencies
* 4. Detecting circular dependencies (deadlock prevention)
*
* @return string[] Array of handler field names in safe execution order
*
* @throws \InvalidArgumentException When a handler depends on a non-existent handler
* @throws \InvalidArgumentException When circular dependencies are detected
*/
private function topologicalSort(): array
{
$inDegree = []; // Count of dependencies for each handler
$graph = []; // Adjacency list of handler dependencies
$handlerNames = array_keys($this->handlers);
// Initialize all handlers with zero dependencies
foreach ($handlerNames as $handlerName) {
$inDegree[$handlerName] = 0;
$graph[$handlerName] = [];
}
// Build dependency graph by examining each handler's dependencies
foreach ($this->handlers as $handlerName => $handler) {
foreach ($handler->getDependencies() as $dependency) {
// Validate that the dependency exists
if (!isset($this->handlers[$dependency])) {
throw new \InvalidArgumentException(sprintf('Handler "%s" depends on unknown handler "%s"', $handlerName, $dependency));
}
// Add edge from dependency to dependent handler
$graph[$dependency][] = $handlerName;
++$inDegree[$handlerName];
}
}
// Kahn's topological sort algorithm
$queue = []; // Handlers ready to be processed (no remaining dependencies)
$result = []; // Final sorted order
// Start with handlers that have no dependencies
foreach ($inDegree as $handlerName => $degree) {
if (0 === $degree) {
$queue[] = $handlerName;
}
}
// Process handlers in dependency order
while (!empty($queue)) {
$current = array_shift($queue);
$result[] = $current;
// Remove this handler's dependencies from dependent handlers
foreach ($graph[$current] as $dependent) {
--$inDegree[$dependent];
// If dependent now has no remaining dependencies, add to queue
if (0 === $inDegree[$dependent]) {
$queue[] = $dependent;
}
}
}
// Detect circular dependencies (if not all handlers were processed)
if (count($result) !== count($handlerNames)) {
throw new \InvalidArgumentException('Circular dependency detected in participant field handlers');
}
return $result;
}
}
@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\Form\Model\BookingCreateDto;
/**
* Provides dynamic field options for participant form fields.
*
* This service acts as a field option provider registry for the participant form
* system. It generates context-aware Symfony form field options based on the
* overall booking context and individual participant data.
*
* Key Responsibilities:
* - Manages field option providers for dynamic fields
* - Provides context-aware field configurations
* - Handles interdependent field relationships
* - Supports extensible field option generation
*
* The provider uses a callable pattern where each field has a function
* that generates the appropriate Symfony form options based on the current
* booking and participant state.
*/
class ParticipantFieldOptionsProvider
{
/** @var array<string, callable> Field option providers indexed by field name */
private array $fieldOptionProviders = [];
/**
* Initializes the provider with required dependencies.
*
* The provider automatically registers all field option providers
* during construction to ensure they're available for form building.
* This approach keeps all field configuration logic centralized and
* makes it easy to add new dynamic fields.
*
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
*/
public function __construct(
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
) {
$this->registerFieldOptionProviders();
}
/**
* Retrieves form field options for a specified dynamic field.
*
* This is the main entry point for getting field configurations. It looks up
* the appropriate option provider for the field and executes it with the
* current booking and participant context to generate dynamic field options.
*
* The returned array contains Symfony form field options such as:
* - 'label' - The field label
* - 'placeholder' - Placeholder text
* - 'choices' - Available choices for choice fields
* - 'choice_loader' - Dynamic choice loader for complex choices
* - 'disabled' - Whether the field should be disabled
* - 'required' - Whether the field is required
*
* @param string $fieldName The name of the field to configure
* @param BookingCreateDto $bookingDto The current booking data for context
* @param int $participantIndex The index of the participant being configured
*
* @return array<string, mixed> Symfony form field options, or empty array if field not supported
*/
public function getFieldOptions(string $fieldName, BookingCreateDto $bookingDto, int $participantIndex): array
{
// Check if we have a provider for this field
if (!isset($this->fieldOptionProviders[$fieldName])) {
return [];
}
// Execute the provider with current context to generate dynamic options
return $this->fieldOptionProviders[$fieldName]($bookingDto, $participantIndex);
}
/**
* Checks whether a field has option provider support.
*
* This method allows form builders to determine if a field can be
* dynamically configured by this service. It's useful for deciding
* whether to use static field options or dynamic configuration.
*
* @param string $fieldName The name of the field to check
*
* @return bool True if the field has registered option providers, false otherwise
*/
public function hasFieldOptions(string $fieldName): bool
{
return isset($this->fieldOptionProviders[$fieldName]);
}
/**
* Registers all field option providers during service initialization.
*
* This method defines the option generation logic for each supported dynamic field.
* Each provider is a callable that receives the booking DTO and participant
* index and returns appropriate Symfony form field options.
*
* Adding new fields:
* To add support for a new dynamic field, simply add a new provider here:
*
* $this->fieldOptionProviders['newField'] = fn($bookingDto, $participantIndex) => [
* 'label' => 'New Field Label',
* 'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
* ];
*
* Provider Pattern Benefits:
* - Lazy evaluation (options only generated when needed)
* - Context-aware configuration
* - Easy to test individual field logic
* - Supports complex interdependencies
*/
private function registerFieldOptionProviders(): void
{
// Room assignment field provider
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingCreateDto $bookingDto, int $participantIndex) => [
'label' => 'Zimmer',
'placeholder' => 'Bitte wählen',
// Use factory to create context-aware choice loader that:
// - Shows only available rooms for this participant
// - Excludes rooms already assigned to other participants
// - Respects room capacity and booking constraints
'choice_loader' => $this->roomChoiceLoaderFactory->create(
$bookingDto->participants,
$bookingDto->getSelectedRooms(),
$participantIndex
),
];
// Future field providers would be added here, for example:
//
// $this->fieldOptionProviders['mealPreference'] = fn($bookingDto, $participantIndex) => [
// 'label' => 'Meal Preference',
// 'choices' => [
// 'Standard' => 'standard',
// 'Vegetarian' => 'vegetarian',
// 'Vegan' => 'vegan',
// ],
// // Could depend on room assignment or other factors
// 'disabled' => !$this->hasMealOptions($bookingDto, $participantIndex),
// ];
}
}
@@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\Form\Model\BookingCreateDto;
/**
* Central service for configuring dynamic fields in the participant form.
*
* This class encapsulates all business logic for determining field options,
* choices, and states (e.g., visibility, disabled status) based on the
* overall booking state and individual participant data.
*/
class ParticipantFormConfigurator
{
public function __construct(
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
)
{
}
/**
* Gets the complete form options for the 'assignedRoomId' field.
*/
public function getRoomFieldOptions(BookingCreateDto $bookingDto, int $participantIndex): array
{
$choiceLoader = $this->roomChoiceLoaderFactory->create(
$bookingDto->participants,
$bookingDto->getSelectedRooms(),
$participantIndex
);
return [
'label' => 'Zimmer',
'placeholder' => 'Bitte wählen',
'choice_loader' => $choiceLoader,
];
}
}