Files
myep/src/Form/Service/ParticipantFieldOptionsProvider.php
T

208 lines
8.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
/**
* Provides dynamic field options for participant form fields.
*
* This service generates context-aware Symfony form field options for
* dynamic fields in the participant form system. It handles fields that
* require options to be calculated based on the current booking context,
* participant data, and business logic.
*
* 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 for field options, enabling
* lazy evaluation and complex conditional field behavior.
*/
class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
{
/**
* 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)
{
parent::__construct();
}
/**
* 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
*/
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);
});
}
}