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 */ protected function registerFieldOptionProviders(): void { // Room assignment field provider (only available for create workflow) $this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $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' => $bookingDto instanceof BookingCreateDto ? $this->roomChoiceLoaderFactory->create( $bookingDto->participants, $bookingDto->getSelectedRooms(), $participantIndex ) : null, ]; // Courses field provider - provides age-appropriate courses from travel data $this->fieldOptionProviders['courses'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [ 'label' => 'Kurse', 'multiple' => true, 'expanded' => true, 'required' => false, 'choices' => $this->filterServicesByAgeConstraints( $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_COURSES), $bookingDto, $participantIndex ), 'choice_label' => 'label', ]; // Additional services field provider - provides age-appropriate additional services with mandatory pre-selection $this->fieldOptionProviders['additionalServices'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [ 'label' => 'Zusatzleistungen', 'multiple' => true, 'expanded' => true, 'required' => false, 'choices' => $this->filterServicesByAgeConstraints( $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_ADDITIONAL), $bookingDto, $participantIndex ), 'choice_value' => 'id', 'choice_label' => 'label', 'choice_attr' => function (?Service $service) { if (null === $service) { return []; } $attributes = []; // Pre-select and make readonly for mandatory services if (true === $service->mandatory) { $attributes['checked'] = true; $attributes['readonly'] = true; $attributes['class'] = 'text-pink'; $attributes['title'] = 'Diese Leistung ist nicht abwählbar'; } return $attributes; }, ]; // Board field provider - provides age-appropriate board options from travel data $this->fieldOptionProviders['board'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [ 'label' => 'Verpflegung', 'multiple' => true, 'expanded' => true, 'required' => false, 'choices' => $this->filterServicesByAgeConstraints( $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_BOARD), $bookingDto, $participantIndex ), 'choice_label' => 'label', ]; // Rentals field provider - provides age-appropriate rental options from travel data filtered by date range $this->fieldOptionProviders['rentals'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [ 'label' => 'Leihmaterial', 'multiple' => true, 'expanded' => true, 'required' => false, 'choices' => $this->filterServicesByAgeConstraints( $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTALS, true, true), $bookingDto, $participantIndex ), 'choice_label' => 'label', ]; // 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', // ], // ]; } /** * Filters services based on participant's age constraints. * * Removes services that have age restrictions the participant doesn't meet. * If no age evaluator is configured or participant has no birth date, * returns empty array to be handled by field visibility conditions. * * @param array $services Services to filter * @param BookingDtoInterface $bookingDto Booking data containing participant info * @param int $participantIndex Index of participant to evaluate * * @return array Filtered services array */ private function filterServicesByAgeConstraints(array $services, BookingDtoInterface $bookingDto, int $participantIndex): array { $ageEvaluator = new ServiceAgeEvaluator(); $participant = $bookingDto->getParticipant($participantIndex); // If no birth date provided, return empty array (handled by field visibility conditions) if (null === $participant || null === $participant->dateOfBirth) { return []; } return array_filter($services, function (Service $service) use ($bookingDto, $participantIndex, $ageEvaluator) { // No age constraints = available to all if (false === $ageEvaluator->canEvaluate($service)) { return true; } return $ageEvaluator->isServiceAvailableForParticipant($service, $bookingDto, $participantIndex); }); } }