feat: split booking participant controllers

This commit is contained in:
Björn Fromme committed 2026-04-11 18:28:32 +02:00
1 parent 346256f895
commit f22ceae41c
10 files changed
+767 -673

No files matched your search

@@ -0,0 +1,223 @@
<?php
declare(strict_types=1);
namespace App\Controller\Booking\Create;
use App\Form\BookingParticipantType;
use App\Form\Model\BookingDto;
use App\Form\Service\DummyDataFillService;
use App\Htmx\HxTrait;
use App\Service\BookingService;
use App\Service\BookingSummaryDataService;
use App\Service\ParticipantFormSupportService;
use App\Service\TravelDataService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
/**
* Handles participant editing and refresh actions within booking step 2.
*/
class Step2ParticipantController extends AbstractController
{
use HxTrait;
public function __construct(
private readonly BookingService $bookingService,
private readonly BookingSummaryDataService $summaryDataService,
private readonly TravelDataService $travelDataService,
private readonly DummyDataFillService $dummyDataFillService,
private readonly ParticipantFormSupportService $participantFormSupportService,
) {
}
/**
* Show or submit individual participant form.
*/
#[Route(
path: '/bookings/create/participants/{index}',
name: 'app_booking_create_step_2_participant',
requirements: ['index' => '\d+']
)]
public function editParticipant(int $index, Request $request): Response
{
$result = $this->loadBookingDtoOrRedirect($request);
if ($result instanceof Response) {
return $result;
}
$bookingDto = $result;
$this->participantFormSupportService->ensureParticipantExists($bookingDto, $index);
$this->travelDataService->enrichWithFreshAvailabilities($bookingDto->travel);
$form = $this->createParticipantForm($bookingDto, $index);
$form->handleRequest($request);
$isSubmitted = $form->isSubmitted();
$isDummyDataFill = $this
->dummyDataFillService
->isTokenMatch($bookingDto->participants[$index], $bookingDto->getMode())
;
if (true === $isSubmitted && true === $isDummyDataFill) {
$this->dummyDataFillService->fill($bookingDto->participants[$index], $index);
$this->bookingService->preselectDefaultServices($bookingDto);
$this->bookingService->applyCreateBookingStatusRules($bookingDto);
$this->bookingService->saveBookingDto($request, $bookingDto, BookingDto::MODE_CREATE);
$form = $this->createParticipantForm($bookingDto, $index);
return $this->renderParticipantForm($form, $index, $bookingDto);
}
if (true === $isSubmitted) {
$this->bookingService->preselectDefaultServices($bookingDto);
$this->bookingService->applyCreateBookingStatusRules($bookingDto);
}
$notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingDto);
if (true === $isSubmitted && true === $form->isValid()) {
$this->bookingService->saveBookingDto($request, $bookingDto, BookingDto::MODE_CREATE);
$this->addNotificationsAsFlashMessages($notifications);
return $this->redirectToRoute('app_booking_create_step_2');
}
return $this->renderParticipantForm($form, $index, $bookingDto);
}
/**
* HTMX refresh endpoint for individual participant form.
*/
#[Route(
path: '/bookings/create/participants/{index}/refresh',
name: 'app_booking_create_step_2_participant_refresh',
requirements: ['index' => '\d+'],
methods: ['POST']
)]
public function refreshParticipantForm(int $index, Request $request): Response
{
$result = $this->loadBookingDtoOrRedirect($request);
if ($result instanceof Response) {
return $result;
}
$bookingDto = $result;
$this->participantFormSupportService->ensureParticipantExists($bookingDto, $index);
$this->travelDataService->enrichWithFreshAvailabilities($bookingDto->travel);
return $this->handleParticipantRefresh(
$request,
$bookingDto,
$index,
'app_booking_create_step_2_participant_refresh'
);
}
private function renderParticipantForm(
FormInterface $form,
int $index,
BookingDto $bookingDto,
): Response {
return $this->render('booking/create/step_2_participant.html.twig', [
'form' => $form->createView(),
'participantIndex' => $index,
'bookingDto' => $bookingDto,
'summaryData' => $this->summaryDataService->getSummaryData($bookingDto),
'refreshRouteName' => 'app_booking_create_step_2_participant_refresh',
]);
}
private function loadBookingDtoOrRedirect(Request $request): BookingDto|Response
{
$bookingDto = $this->bookingService->getBookingDto($request, BookingDto::MODE_CREATE);
if (null === $bookingDto) {
$this->addFlash('info', 'Deine Sitzung ist abgelaufen. Bitte starte eine neue Buchung.');
return $this->redirectToRoute('app_login');
}
return $bookingDto;
}
private function createParticipantForm(BookingDto $bookingDto, int $index): FormInterface
{
$wrapper = $this->participantFormSupportService->createParticipantEditDto($bookingDto, $index);
return $this->createForm(
BookingParticipantType::class,
$wrapper,
$this->participantFormSupportService->getParticipantFormOptions($bookingDto)
);
}
private function handleParticipantRefresh(
Request $request,
BookingDto $bookingDto,
int $index,
string $refreshRouteName,
): Response {
$form = $this->createForm(
BookingParticipantType::class,
$this->participantFormSupportService->createParticipantEditDto($bookingDto, $index),
$this->participantFormSupportService->getParticipantFormOptions($bookingDto, true)
);
$form->handleRequest($request);
$this->bookingService->preselectDefaultServices($bookingDto);
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
$this->bookingService->applyCreateBookingStatusRules($bookingDto);
}
$form = $this->createForm(
BookingParticipantType::class,
$this->participantFormSupportService->createParticipantEditDto($bookingDto, $index),
$this->participantFormSupportService->getParticipantFormOptions($bookingDto, true)
);
$notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingDto);
$this->bookingService->saveBookingDto($request, $bookingDto, $bookingDto->getMode());
$summaryData = $this->summaryDataService->getSummaryData($bookingDto);
$response = $this->htmxOobResponse(
'booking/_participant_form.html.twig',
['participant_form', 'booking_summary'],
[
'form' => $form->createView(),
'participantIndex' => $index,
'bookingDto' => $bookingDto,
'summaryData' => $summaryData,
'refreshRouteName' => $refreshRouteName,
]
);
if (false === empty($notifications)) {
$response->headers->set('HX-Trigger', json_encode([
'showNotifications' => $notifications,
]));
}
return $response;
}
/**
* @param array<array{type: string, message: string}> $notifications
*/
private function addNotificationsAsFlashMessages(array $notifications): void
{
foreach ($notifications as $notification) {
$this->addFlash($notification['type'], $notification['message']);
}
}
}