ageConstraintType; } /** * Evaluates if a service is available for a specific participant. * * Checks the participant's age or birth year against the service's * constraint requirements. Returns false if the participant doesn't * meet the age requirements or if no birth date is provided. * * Age constraints are evaluated at the travel start date to ensure * that services are appropriately filtered based on the participant's * age at the time of travel, not their current age. * * @param Service $service The service to evaluate * @param BookingDtoInterface $bookingDto The booking containing participant data * @param int $participantIndex The index of the participant to evaluate * * @return bool True if the service is available for the participant */ public function isServiceAvailableForParticipant(Service $service, BookingDtoInterface $bookingDto, int $participantIndex): bool { $participant = $bookingDto->getParticipant($participantIndex); if (null === $participant || null === $participant->dateOfBirth) { return false; // Cannot evaluate without birth date } // Use travel start date as reference for absolute age calculations $travelStartDate = $bookingDto->travel->dateFrom; return match ($service->ageConstraintType) { 'absolute_age' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth, $travelStartDate), 'birth_year' => $this->evaluateBirthYear($service, $participant->dateOfBirth), 'mixed' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth, $travelStartDate) && $this->evaluateBirthYear($service, $participant->dateOfBirth), default => true, // No constraints or unknown type }; } /** * Evaluates absolute age constraints against participant's age at travel date. * * @param Service $service The service with age constraints * @param \DateTimeImmutable $dateOfBirth The participant's date of birth * @param \DateTimeImmutable|null $referenceDate The reference date for age calculation (travel start date) * * @return bool True if the participant meets the absolute age requirements */ private function evaluateAbsoluteAge(Service $service, \DateTimeImmutable $dateOfBirth, ?\DateTimeImmutable $referenceDate = null): bool { $age = $this->calculateAge($dateOfBirth, $referenceDate); if (null !== $service->ageFrom && $age < $service->ageFrom) { return false; } if (null !== $service->ageTo && $age > $service->ageTo) { return false; } return true; } /** * Evaluates birth year constraints against participant's birth year. * * @param Service $service The service with birth year constraints * @param \DateTimeImmutable $dateOfBirth The participant's date of birth * * @return bool True if the participant meets the birth year requirements */ private function evaluateBirthYear(Service $service, \DateTimeImmutable $dateOfBirth): bool { $birthYear = (int) $dateOfBirth->format('Y'); if (null !== $service->birthYearFrom && $birthYear < $service->birthYearFrom) { return false; } if (null !== $service->birthYearTo && $birthYear > $service->birthYearTo) { return false; } return true; } /** * Calculates age in years from a date of birth at a reference date. * * Uses DateTimeImmutable to ensure accurate age calculations accounting * for leap years and exact birth date anniversaries. * * @param \DateTimeImmutable $dateOfBirth The participant's date of birth * @param \DateTimeImmutable|null $referenceDate The reference date for age calculation (defaults to today) * * @return int The calculated age in complete years */ private function calculateAge(\DateTimeImmutable $dateOfBirth, ?\DateTimeImmutable $referenceDate = null): int { $referenceDate = $referenceDate ?? new \DateTimeImmutable(); return $dateOfBirth->diff($referenceDate)->y; } /** * Gets a human-readable description of the service's age constraints. * * Generates descriptive text explaining the age requirements, * useful for user interfaces and debugging. * * @param Service $service The service to describe * * @return string Description of the age constraints */ public function getConstraintDescription(Service $service): string { return match ($service->ageConstraintType) { 'absolute_age' => $this->getAbsoluteAgeDescription($service), 'birth_year' => $this->getBirthYearDescription($service), 'mixed' => sprintf('%s and %s', $this->getAbsoluteAgeDescription($service), $this->getBirthYearDescription($service)), default => 'No age restrictions', }; } /** * Gets description for absolute age constraints. * * @param Service $service The service with age constraints * * @return string Description of absolute age requirements */ private function getAbsoluteAgeDescription(Service $service): string { if (null !== $service->ageFrom && null !== $service->ageTo) { return sprintf('Ages %d-%d', $service->ageFrom, $service->ageTo); } if (null !== $service->ageFrom) { return sprintf('Age %d+', $service->ageFrom); } if (null !== $service->ageTo) { return sprintf('Age up to %d', $service->ageTo); } return ''; } /** * Gets description for birth year constraints. * * @param Service $service The service with birth year constraints * * @return string Description of birth year requirements */ private function getBirthYearDescription(Service $service): string { if (null !== $service->birthYearFrom && null !== $service->birthYearTo) { if ($service->birthYearFrom === $service->birthYearTo) { return sprintf('Born in %d', $service->birthYearFrom); } return sprintf('Born %d-%d', $service->birthYearFrom, $service->birthYearTo); } if (null !== $service->birthYearFrom) { return sprintf('Born %d or later', $service->birthYearFrom); } if (null !== $service->birthYearTo) { return sprintf('Born up to %d', $service->birthYearTo); } return ''; } }