feat: extract booking session handling
This commit is contained in:
@@ -32,7 +32,7 @@ class BookingEditDataLoaderService
|
||||
public function __construct(
|
||||
private readonly ApiClient $apiClient,
|
||||
private readonly BookingDataProcessor $bookingDataProcessor,
|
||||
private readonly BookingService $bookingService,
|
||||
private readonly BookingSessionService $bookingSessionService,
|
||||
private readonly BookingFingerprintService $fingerprintService,
|
||||
private readonly TravelDataService $travelDataService,
|
||||
private readonly BookingEditDraftService $draftService,
|
||||
@@ -73,11 +73,11 @@ class BookingEditDataLoaderService
|
||||
{
|
||||
$this->draftWasRestored = false;
|
||||
|
||||
$formData = $this->bookingService->getBookingDto($request, BookingDto::MODE_EDIT);
|
||||
$formData = $this->bookingSessionService->getBookingDto($request, BookingDto::MODE_EDIT);
|
||||
|
||||
// Validate session data matches requested booking - clear stale data if mismatched
|
||||
if (null !== $formData && $formData->booking?->id !== $bookingId) {
|
||||
$this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT);
|
||||
$this->bookingSessionService->clearBookingDto($request, BookingDto::MODE_EDIT);
|
||||
$formData = null;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class BookingEditDataLoaderService
|
||||
}
|
||||
}
|
||||
|
||||
$this->bookingService->saveBookingDto($request, $formData, BookingDto::MODE_EDIT);
|
||||
$this->bookingSessionService->saveBookingDto($request, $formData, BookingDto::MODE_EDIT);
|
||||
|
||||
return $formData;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class BookingEditParticipantFormService
|
||||
public function __construct(
|
||||
private readonly BookingEditDataLoaderService $dataLoader,
|
||||
private readonly BookingEditDraftService $draftService,
|
||||
private readonly BookingService $bookingService,
|
||||
private readonly BookingSessionService $bookingSessionService,
|
||||
private readonly BookingSummaryDataService $summaryDataService,
|
||||
private readonly TravelDataService $travelDataService,
|
||||
) {
|
||||
@@ -32,7 +32,7 @@ class BookingEditParticipantFormService
|
||||
|
||||
public function loadBookingDto(Request $request): ?BookingDto
|
||||
{
|
||||
return $this->bookingService->getBookingDto($request, BookingDto::MODE_EDIT);
|
||||
return $this->bookingSessionService->getBookingDto($request, BookingDto::MODE_EDIT);
|
||||
}
|
||||
|
||||
public function fetchBookingData(int $bookingId, User $user): Booking|Notification|null
|
||||
@@ -52,7 +52,7 @@ class BookingEditParticipantFormService
|
||||
|
||||
public function saveBookingDto(Request $request, BookingDto $bookingDto): void
|
||||
{
|
||||
$this->bookingService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT);
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT);
|
||||
}
|
||||
|
||||
public function saveDraft(User $user, int $bookingId, BookingDto $bookingDto): void
|
||||
|
||||
@@ -10,7 +10,6 @@ use App\BusProNet\Model\Service;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\BusProNet\Service\BookingStatusRuleRegistry;
|
||||
use App\BusProNet\XmlLoader\AgencyLoader;
|
||||
use App\Exception\BookingSessionNotFoundException;
|
||||
use App\Exception\NoRoomsAvailableException;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
@@ -22,13 +21,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class BookingService
|
||||
{
|
||||
public const BOOKING_CREATE_KEY = 'booking_create';
|
||||
public const BOOKING_CREATE_BASELINE_KEY = 'booking_create_baseline_snapshot';
|
||||
public const BOOKING_EDIT_KEY = 'booking_edit';
|
||||
public const RETURN_URL_KEY = 'booking_return_url';
|
||||
public const DEFAULT_RETURN_URL = 'https://www.ep-reisen.de';
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingSessionService $bookingSessionService,
|
||||
private readonly TravelDataService $travelDataService,
|
||||
private readonly BookingPriceCalculatorService $priceCalculator,
|
||||
private readonly ParticipantEligibilityService $participantEligibilityService,
|
||||
@@ -39,195 +33,6 @@ class BookingService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the baseline room selection snapshot for change detection.
|
||||
*
|
||||
* The baseline snapshot captures the initial room selection state when step 1
|
||||
* is first loaded, before any HTMX modifications. This ensures accurate change
|
||||
* detection for room assignment resets.
|
||||
*/
|
||||
public function getOrCreateBaselineSnapshot(Request $request, BookingDto $bookingCreateDto): array
|
||||
{
|
||||
$baselineKey = self::BOOKING_CREATE_BASELINE_KEY;
|
||||
|
||||
if (!$request->getSession()->has($baselineKey) || $request->query->has('reset_baseline')) {
|
||||
$baseline = $bookingCreateDto->createRoomSelectionSnapshot();
|
||||
$request->getSession()->set($baselineKey, $baseline);
|
||||
|
||||
return $baseline;
|
||||
}
|
||||
|
||||
return $request->getSession()->get($baselineKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the baseline snapshot from the session.
|
||||
*
|
||||
* Should be called when moving to the next step or when the baseline
|
||||
* needs to be refreshed.
|
||||
*/
|
||||
public function clearBaselineSnapshot(Request $request): void
|
||||
{
|
||||
$request->getSession()->remove('booking_create_baseline_snapshot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the booking creation DTO from the session.
|
||||
*
|
||||
* This method enforces the secure booking flow by only returning existing
|
||||
* session data. Users must go through the proper initialization flow via
|
||||
* CreateInitController to create new booking sessions.
|
||||
*
|
||||
* @param Request $request The HTTP request containing session data
|
||||
*
|
||||
* @return BookingDto The booking DTO from session
|
||||
*
|
||||
* @throws BookingSessionNotFoundException When no valid booking session exists
|
||||
*/
|
||||
public function getOrCreateBookingCreateDto(Request $request): BookingDto
|
||||
{
|
||||
$bookingDto = $this->getBookingDto($request, BookingDto::MODE_CREATE);
|
||||
|
||||
if (null !== $bookingDto) {
|
||||
return $bookingDto;
|
||||
}
|
||||
|
||||
throw new BookingSessionNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the booking DTO to the session.
|
||||
*
|
||||
* @param Request $request The HTTP request with session
|
||||
* @param BookingDto $bookingDto The booking DTO to persist
|
||||
* @param string $mode The booking mode (create/edit)
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* After deserialization the DTO contains only a Travel skeleton with the ID.
|
||||
* This method replaces it with the full Travel via hydrate().
|
||||
*
|
||||
* @param Request $request The HTTP request containing session data
|
||||
* @param string $mode The booking mode (create/edit)
|
||||
*
|
||||
* @return BookingDto|null The booking DTO or null if not found
|
||||
*/
|
||||
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);
|
||||
$this->hydrate($bookingDto);
|
||||
|
||||
return $bookingDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the booking DTO from the session.
|
||||
*
|
||||
* @param Request $request The HTTP request with session
|
||||
* @param string $mode The booking mode (create/edit)
|
||||
*/
|
||||
public function clearBookingDto(Request $request, string $mode): void
|
||||
{
|
||||
$sessionKey = $this->getSessionKey($mode);
|
||||
$request->getSession()->remove($sessionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates session key based on mode.
|
||||
*
|
||||
* @param string $mode 'create' or 'edit'
|
||||
*
|
||||
* @return string The session key
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* BookingDto::__serialize() replaces Travel with just its ID to keep session
|
||||
* payloads small. This method fetches the complete Travel (from DB snapshot or
|
||||
* XML fallback) and sets it on both the DTO and the Booking reference.
|
||||
*/
|
||||
private function hydrate(BookingDto $bookingDto): void
|
||||
{
|
||||
$travel = $this->travelDataService->getTravelData(
|
||||
$bookingDto->travel->id,
|
||||
$bookingDto->hotelId
|
||||
);
|
||||
|
||||
if (null === $travel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bookingDto->travel = $travel;
|
||||
|
||||
if (null !== $bookingDto->booking) {
|
||||
$bookingDto->booking->travelData = $travel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all booking-related session data.
|
||||
*
|
||||
* This method removes all booking session data including the main DTO
|
||||
* and any cached snapshots to ensure a completely fresh start.
|
||||
* Note: RETURN_URL_KEY is intentionally preserved so it remains available
|
||||
* for redirects after cancel or error flows.
|
||||
*/
|
||||
public function clearBookingSession(Request $request): void
|
||||
{
|
||||
$session = $request->getSession();
|
||||
$session->remove(self::BOOKING_CREATE_KEY);
|
||||
$session->remove(self::BOOKING_CREATE_BASELINE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the return URL in the session.
|
||||
*
|
||||
* Validates that the URL is a valid absolute URL with http/https scheme.
|
||||
* Falls back to the default return URL if null or invalid.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Returns the default URL if not set in session.
|
||||
*/
|
||||
public function getReturnUrl(Request $request): string
|
||||
{
|
||||
return $request->getSession()->get(self::RETURN_URL_KEY, self::DEFAULT_RETURN_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fresh booking session with the provided travel parameters.
|
||||
*
|
||||
@@ -280,7 +85,7 @@ class BookingService
|
||||
: null;
|
||||
$bookingCreateDto->bookingStatus = $bookingStatus;
|
||||
|
||||
$this->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
|
||||
return $bookingCreateDto;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Exception\BookingSessionNotFoundException;
|
||||
use App\Form\Model\BookingDto;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Owns booking-flow session state.
|
||||
*
|
||||
* This service keeps the HTTP session concerns separate from booking
|
||||
* orchestration and pricing logic. It handles DTO persistence, baseline
|
||||
* room snapshots, and return URL storage for the booking create/edit flows.
|
||||
*/
|
||||
class BookingSessionService
|
||||
{
|
||||
public const BOOKING_CREATE_KEY = 'booking_create';
|
||||
public const BOOKING_CREATE_BASELINE_KEY = 'booking_create_baseline_snapshot';
|
||||
public const BOOKING_EDIT_KEY = 'booking_edit';
|
||||
public const RETURN_URL_KEY = 'booking_return_url';
|
||||
public const DEFAULT_RETURN_URL = 'https://www.ep-reisen.de';
|
||||
|
||||
public function __construct(
|
||||
private readonly TravelDataService $travelDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the booking creation DTO from the session.
|
||||
*
|
||||
* @throws BookingSessionNotFoundException
|
||||
*/
|
||||
public function getOrCreateBookingCreateDto(Request $request): BookingDto
|
||||
{
|
||||
$bookingDto = $this->getBookingDto($request, BookingDto::MODE_CREATE);
|
||||
|
||||
if (null !== $bookingDto) {
|
||||
return $bookingDto;
|
||||
}
|
||||
|
||||
throw new BookingSessionNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the baseline room selection snapshot for change detection.
|
||||
*/
|
||||
public function getOrCreateBaselineSnapshot(Request $request, BookingDto $bookingCreateDto): array
|
||||
{
|
||||
$baselineKey = self::BOOKING_CREATE_BASELINE_KEY;
|
||||
|
||||
if (!$request->getSession()->has($baselineKey) || $request->query->has('reset_baseline')) {
|
||||
$baseline = $bookingCreateDto->createRoomSelectionSnapshot();
|
||||
$request->getSession()->set($baselineKey, $baseline);
|
||||
|
||||
return $baseline;
|
||||
}
|
||||
|
||||
return $request->getSession()->get($baselineKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$this->hydrate($bookingDto);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private function hydrate(BookingDto $bookingDto): void
|
||||
{
|
||||
$travel = $this->travelDataService->getTravelData(
|
||||
$bookingDto->travel->id,
|
||||
$bookingDto->hotelId
|
||||
);
|
||||
|
||||
if (null === $travel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bookingDto->travel = $travel;
|
||||
|
||||
if (null !== $bookingDto->booking) {
|
||||
$bookingDto->booking->travelData = $travel;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user