getBookingDto($request, BookingDto::MODE_CREATE); if (null !== $bookingDto) { return $bookingDto; } throw new BookingSessionNotFoundException(); } /** * Gets or creates the baseline room selection snapshot for change detection. * * @return array */ public function getOrCreateBaselineSnapshot(Request $request, BookingDto $bookingCreateDto): array { $baselineKey = self::BOOKING_CREATE_BASELINE_KEY; if (!$request->getSession()->has($baselineKey) || $request->query->has('reset_baseline')) { return $this->saveBaselineSnapshot($request, $bookingCreateDto); } return $request->getSession()->get($baselineKey); } /** * Stores the current room selection snapshot in the session. * * @return array */ public function saveBaselineSnapshot(Request $request, BookingDto $bookingCreateDto): array { $baseline = $bookingCreateDto->createRoomSelectionSnapshot(); $request->getSession()->set(self::BOOKING_CREATE_BASELINE_KEY, $baseline); return $baseline; } /** * Clears the baseline snapshot from the session. */ public function clearBaselineSnapshot(Request $request): void { $request->getSession()->remove(self::BOOKING_CREATE_BASELINE_KEY); } /** * Saves the booking DTO to the session. */ public function saveBookingDto(Request $request, BookingDto $bookingDto, string $mode): void { $sessionKey = $this->getSessionKey($mode); $request->getSession()->set($sessionKey, $bookingDto); } /** * Retrieves the booking DTO from the session and restores the Travel object. */ public function getBookingDto(Request $request, string $mode): ?BookingDto { $sessionKey = $this->getSessionKey($mode); $session = $request->getSession(); if (false === $session->has($sessionKey)) { return null; } $bookingDto = $session->get($sessionKey); $shouldCheck = BookingDto::MODE_CREATE === $mode && $this->shouldRefreshCreateAvailability($request); $hydrated = $this->hydrate($bookingDto, $shouldCheck); if (BookingDto::MODE_CREATE === $mode && true === $shouldCheck && true === $hydrated) { $this->markCreateAvailabilityRefreshed($request); } return $bookingDto; } /** * Clears the booking DTO from the session. */ public function clearBookingDto(Request $request, string $mode): void { $sessionKey = $this->getSessionKey($mode); $request->getSession()->remove($sessionKey); } public function refreshCreateAvailability( Request $request, BookingDto $bookingDto, bool $forceRefresh = false, ): BookingAvailabilityRefreshResult { $beforeSnapshot = $bookingDto->createRoomSelectionSnapshot(); $availabilityRefreshed = $this->travelDataService->enrichWithFreshAvailabilities( $bookingDto->travel, true, $forceRefresh, refreshRooms: true, ); $roomSelectionQuantityReset = $bookingDto->syncRoomSelectionsWithAvailableRooms(); $afterSnapshot = $bookingDto->createRoomSelectionSnapshot(); if (true === $roomSelectionQuantityReset) { $bookingDto->resetParticipantAssignments(); } $this->saveBookingDto($request, $bookingDto, BookingDto::MODE_CREATE); if (true === $forceRefresh && true === $availabilityRefreshed) { $this->markCreateAvailabilityRefreshed($request); } return new BookingAvailabilityRefreshResult( availabilityRefreshed: $availabilityRefreshed, roomSelectionQuantityReset: $roomSelectionQuantityReset, roomSelectionSnapshotChanged: $beforeSnapshot !== $afterSnapshot, hasAvailableRooms: [] !== $bookingDto->travel->getAvailableRooms(), ); } /** * Clears all booking-related session data except the return URL. */ public function clearBookingSession(Request $request): void { $session = $request->getSession(); $session->remove(self::BOOKING_CREATE_KEY); $session->remove(self::BOOKING_CREATE_BASELINE_KEY); $session->remove(self::BOOKING_CREATE_AVAILABILITY_REFRESHED_AT_KEY); } /** * Stores the return URL in the session. */ public function storeReturnUrl(Request $request, ?string $returnUrl): void { $url = self::DEFAULT_RETURN_URL; if (null !== $returnUrl && '' !== trim($returnUrl)) { if (false !== filter_var($returnUrl, \FILTER_VALIDATE_URL) && 1 === preg_match('#^https?://#i', $returnUrl)) { $url = $returnUrl; } } $request->getSession()->set(self::RETURN_URL_KEY, $url); } /** * Retrieves the return URL from the session. */ public function getReturnUrl(Request $request): string { return $request->getSession()->get(self::RETURN_URL_KEY, self::DEFAULT_RETURN_URL); } private function getSessionKey(string $mode): string { return BookingDto::MODE_EDIT === $mode ? self::BOOKING_EDIT_KEY : self::BOOKING_CREATE_KEY; } /** * Restores the full Travel object after session deserialization. */ public function markCreateAvailabilityRefreshed(Request $request): void { $request->getSession()->set(self::BOOKING_CREATE_AVAILABILITY_REFRESHED_AT_KEY, time()); } private function shouldRefreshCreateAvailability(Request $request): bool { $lastRefreshedAt = (int) $request->getSession()->get(self::BOOKING_CREATE_AVAILABILITY_REFRESHED_AT_KEY, 0); return 0 === $lastRefreshedAt || (time() - $lastRefreshedAt) >= self::CREATE_AVAILABILITY_REFRESH_INTERVAL; } private function hydrate(BookingDto $bookingDto, bool $forceRefresh = false): bool { $travel = $this->travelDataService->getTravelData( $bookingDto->travel->id, $bookingDto->hotelId ); if (null === $travel) { return false; } $availabilityRefreshed = $this->travelDataService->enrichWithFreshAvailabilities($travel, true, $forceRefresh, refreshRooms: true); $bookingDto->travel = $travel; if (null !== $bookingDto->booking) { $bookingDto->booking->travelData = $travel; } return $availabilityRefreshed; } }