$submittedData The submitted participant form data * @param int $participantIndex The index of the participant being processed * * @return bool Always returns true for service selection fields */ public function shouldProcess(array $submittedData, int $participantIndex): bool { return true; // Always process to handle deselection cases } /** * Processes the courses field for a specific participant. * * This method extracts course selections from submitted form data, * validates each selection against the participant's age constraints, and * updates the participant DTO with only valid selections. Courses that are * no longer appropriate for the participant's age are automatically removed. * * @param array $submittedData The submitted participant form data * @param BookingDtoInterface $bookingDto The booking DTO to update (create or edit) * @param int $participantIndex The index of the participant being processed */ public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void { // Safely get the participant object, returning early if not found $participant = $this->getParticipant($bookingDto, $participantIndex); if (null === $participant) { return; } // Extract current course selections from submitted data $selectedCourses = $this->getFieldValue($submittedData, $this->getFieldName()) ?? []; // Get available courses from travel data $availableCourses = $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_COURSES); // Filter selections to keep only age-appropriate courses $validSelections = $this->filterValidServiceSelections( $selectedCourses, $availableCourses, $bookingDto, $participantIndex ); // Update participant with validated selections $participant->courses = $validSelections; } /** * Filters course selections to keep only those valid for the participant's age. * * @param array $selectedServices List of currently selected courses * @param array $availableServices List of all available courses * @param BookingDtoInterface $bookingDto The booking DTO for context * @param int $participantIndex The participant index for age evaluation * * @return array Filtered array of valid course selections */ private function filterValidServiceSelections( array $selectedServices, array $availableServices, BookingDtoInterface $bookingDto, int $participantIndex, ): array { $validSelections = []; foreach ($selectedServices as $selectedService) { if ($this->isServiceValidForParticipant($selectedService, $availableServices, $bookingDto, $participantIndex)) { $validSelections[] = $selectedService; } } return $validSelections; } /** * Validates if a selected course is still valid for the participant. * * @param mixed $selectedService The selected course to validate * @param array $availableServices Array of available courses * @param BookingDtoInterface $bookingDto The booking DTO for context * @param int $participantIndex The participant index for age evaluation * * @return bool True if the course is valid for the participant, false otherwise */ private function isServiceValidForParticipant( mixed $selectedService, array $availableServices, BookingDtoInterface $bookingDto, int $participantIndex, ): bool { // Find the service in available services $service = $this->findServiceInAvailableServices($selectedService, $availableServices); if (null === $service) { return false; // Service not found in available services } // Check if service has age constraints $ageEvaluator = new ServiceAgeEvaluator(); if (false === $ageEvaluator->canEvaluate($service)) { return true; // No age restrictions, service is valid } // Validate service against participant's age return $ageEvaluator->isServiceAvailableForParticipant($service, $bookingDto, $participantIndex); } /** * Finds a selected course in the list of available courses. * * @param mixed $selectedService The selected course to find * @param array $availableServices Array of available Service objects * * @return Service|null The found service or null if not found */ private function findServiceInAvailableServices(mixed $selectedService, array $availableServices): ?Service { foreach ($availableServices as $availableService) { if ($this->servicesMatch($selectedService, $availableService)) { return $availableService; } } return null; } /** * Determines if a selected course matches an available course. * * @param mixed $selectedService The selected course from form data * @param Service $availableService The available course to compare against * * @return bool True if the courses match, false otherwise */ private function servicesMatch(mixed $selectedService, Service $availableService): bool { // Direct object comparison if ($selectedService === $availableService) { return true; } // ID comparison for Service objects if ($selectedService instanceof Service) { return $selectedService->id === $availableService->id; } // ID comparison for numeric values if (is_numeric($selectedService)) { return (int) $selectedService === $availableService->id; } // String ID comparison if (is_string($selectedService)) { return $selectedService === (string) $availableService->id; } return false; } }