Request-scoped cache for participant eligibility */ private array $eligibilityCache = []; /** * Checks if a participant is eligible for booking. * * Returns true when the participant can book services (at least one skipass available). * Returns false when the participant cannot book (no skipasses available for their age). * * Baby age exemption: Participants aged 0-2 years (at travel date) are always eligible * regardless of skipass availability, as babies don't require ski passes. * * Results are cached per request to avoid redundant calculations. * * @param BookingDto $bookingDto The current booking data * @param int $participantIndex The index of the participant being evaluated * * @return bool True if participant is eligible (has available skipasses or is baby age) */ public function isParticipantEligible(BookingDto $bookingDto, int $participantIndex): bool { $participant = $bookingDto->getParticipant($participantIndex); if (null === $participant || null === $participant->dateOfBirth) { return false; } // Create cache key based on participant's birth date, travel date, and index $cacheKey = sprintf( 'participant_eligibility_%s_%s_%d', $participant->dateOfBirth->format('Y-m-d'), $bookingDto->travel->dateFrom->format('Y-m-d'), $participantIndex ); return $this->eligibilityCache[$cacheKey] ??= (function () use ($bookingDto, $participantIndex, $participant) { // Baby age exemption: participants at or under BABY_MAX_AGE are always eligible $age = $participant->getAge($bookingDto->travel->dateFrom); if (null !== $age && $age <= Constants::BABY_MAX_AGE) { return true; } $allSkiPasses = $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_SKI_PASS, true); $availableSkiPasses = array_filter( $allSkiPasses, fn (Service $service) => $this->isSkiPassAvailableForParticipant($service, $bookingDto, $participantIndex) ); return !empty($availableSkiPasses); })(); } /** * Checks if a skipass service is available for the given participant based on age constraints. */ private function isSkiPassAvailableForParticipant(Service $service, BookingDto $bookingDto, int $participantIndex): bool { $participant = $bookingDto->getParticipant($participantIndex); if (null === $participant || null === $participant->dateOfBirth) { return false; } // No age constraints = available to all if (null === $service->ageConstraintType) { return true; } $travelStartDate = CarbonImmutable::instance($bookingDto->travel->dateFrom); $birthDate = CarbonImmutable::instance($participant->dateOfBirth); $ageAtTravelStart = $birthDate->diffInYears($travelStartDate); $birthYear = (int) $birthDate->format('Y'); $constraintType = $service->ageConstraintType ?? 'absolute_age'; if ('absolute_age' === $constraintType || 'mixed' === $constraintType) { $minAge = $service->ageFrom; $maxAge = $service->ageTo; if (null !== $minAge && $ageAtTravelStart < $minAge) { return false; } if (null !== $maxAge && $ageAtTravelStart > $maxAge) { return false; } } if ('birth_year' === $constraintType || 'mixed' === $constraintType) { $minBirthYear = $service->birthYearFrom; $maxBirthYear = $service->birthYearTo; if (null !== $minBirthYear && $birthYear < $minBirthYear) { return false; } if (null !== $maxBirthYear && $birthYear > $maxBirthYear) { return false; } } return true; } }