wip: refactor
This commit is contained in:
@@ -33,7 +33,7 @@ class CreateStep1Controller extends AbstractController
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
|
||||
$participantsCount = $this->bookingService->getParticipantsCount($bookingCreateDto->roomSelections, $bookingCreateDto->travelData);
|
||||
$participantsCount = $this->bookingService->getParticipantsCount($bookingCreateDto->roomSelections, $bookingCreateDto->travel);
|
||||
|
||||
// Validate step access - allow step 1 or redirect to current step
|
||||
$this->validateStepAccess($bookingCreateDto, 1);
|
||||
|
||||
@@ -112,7 +112,7 @@ class CreateStep2Controller extends AbstractController
|
||||
{
|
||||
return $this
|
||||
->bookingService
|
||||
->getParticipantsCount($bookingCreateDto->roomSelections, $bookingCreateDto->travelData);
|
||||
->getParticipantsCount($bookingCreateDto->roomSelections, $bookingCreateDto->travel);
|
||||
}
|
||||
|
||||
private function ensureCorrectNumberOfParticipants(BookingCreateDto $bookingCreateDto): void
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Form;
|
||||
use App\BusProNet\Form\CountryType;
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\ParticipantFieldOptionsProvider;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||
@@ -90,11 +91,11 @@ class BookingCreateParticipantType extends AbstractType
|
||||
$rootForm = $rootForm->getParent();
|
||||
}
|
||||
|
||||
/** @var BookingCreateDto $bookingCreateDto */
|
||||
$bookingCreateDto = $rootForm->getData();
|
||||
/** @var BookingDtoInterface $bookingDto */
|
||||
$bookingDto = $rootForm->getData();
|
||||
|
||||
$this->addDynamicFields($form, $bookingCreateDto, $participantData->index);
|
||||
$this->applyFieldStates($form, $bookingCreateDto, $participantData->index);
|
||||
$this->addDynamicFields($form, $bookingDto, $participantData->index);
|
||||
$this->applyFieldStates($form, $bookingDto, $participantData->index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,16 +110,16 @@ class BookingCreateParticipantType extends AbstractType
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the root form data to access BookingCreateDto
|
||||
// Get the root form data to access BookingDtoInterface
|
||||
$rootForm = $form;
|
||||
while ($rootForm->getParent()) {
|
||||
$rootForm = $rootForm->getParent();
|
||||
}
|
||||
|
||||
/** @var BookingCreateDto $bookingCreateDto */
|
||||
$bookingCreateDto = $rootForm->getData();
|
||||
/** @var BookingDtoInterface $bookingDto */
|
||||
$bookingDto = $rootForm->getData();
|
||||
|
||||
if (null === $bookingCreateDto) {
|
||||
if (null === $bookingDto) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,7 +130,7 @@ class BookingCreateParticipantType extends AbstractType
|
||||
}
|
||||
|
||||
// Apply updated field states based on submitted data
|
||||
$this->applyFieldStates($form, $bookingCreateDto, $participantData->index, $submittedData);
|
||||
$this->applyFieldStates($form, $bookingDto, $participantData->index, $submittedData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,14 +140,14 @@ class BookingCreateParticipantType extends AbstractType
|
||||
* the appropriate state modifications (readonly, disabled, etc.).
|
||||
*
|
||||
* @param FormInterface $form The form to modify
|
||||
* @param BookingCreateDto $bookingCreateDto The booking data for context
|
||||
* @param BookingDtoInterface $bookingDto The booking data for context (create or edit)
|
||||
* @param int $participantIndex The participant index
|
||||
* @param array<string, mixed> $formData Optional submitted form data for state calculation
|
||||
*/
|
||||
private function applyFieldStates(FormInterface $form, BookingCreateDto $bookingCreateDto, int $participantIndex, array $formData = []): void
|
||||
private function applyFieldStates(FormInterface $form, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): void
|
||||
{
|
||||
// Get all fields that have state conditions
|
||||
$allFieldStates = $this->fieldOptionsProvider->getAllFieldStates($bookingCreateDto, $participantIndex, $formData);
|
||||
$allFieldStates = $this->fieldOptionsProvider->getAllFieldStates($bookingDto, $participantIndex, $formData);
|
||||
|
||||
foreach ($allFieldStates as $fieldName => $fieldState) {
|
||||
if ($form->has($fieldName)) {
|
||||
@@ -167,18 +168,18 @@ class BookingCreateParticipantType extends AbstractType
|
||||
/**
|
||||
* Adds all configured dynamic fields to the form with state conditions applied.
|
||||
*/
|
||||
private function addDynamicFields(FormInterface $form, BookingCreateDto $bookingCreateDto, int $participantIndex): void
|
||||
private function addDynamicFields(FormInterface $form, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||
{
|
||||
$dynamicFields = ['assignedRoomId']; // List of fields that need dynamic configuration
|
||||
|
||||
foreach ($dynamicFields as $fieldName) {
|
||||
if ($this->fieldOptionsProvider->hasFieldOptions($fieldName)) {
|
||||
// Get base field options
|
||||
$fieldOptions = $this->fieldOptionsProvider->getFieldOptions($fieldName, $bookingCreateDto, $participantIndex);
|
||||
$fieldOptions = $this->fieldOptionsProvider->getFieldOptions($fieldName, $bookingDto, $participantIndex);
|
||||
|
||||
// Apply dynamic field state if conditions exist
|
||||
if ($this->fieldOptionsProvider->hasStateConditions($fieldName)) {
|
||||
$fieldState = $this->fieldOptionsProvider->getFieldState($fieldName, $bookingCreateDto, $participantIndex);
|
||||
$fieldState = $this->fieldOptionsProvider->getFieldState($fieldName, $bookingDto, $participantIndex);
|
||||
$fieldOptions = $this->mergeFieldState($fieldOptions, $fieldState);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ use App\BusProNet\Form\CountryType;
|
||||
use App\BusProNet\Model\Pickup;
|
||||
use App\BusProNet\Model\Service;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\EditFieldStateProvider;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
@@ -19,6 +21,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class BookingEditParticipantType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EditFieldStateProvider $fieldStateProvider,
|
||||
) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
|
||||
@@ -33,34 +39,33 @@ class BookingEditParticipantType extends AbstractType
|
||||
|
||||
$form = $event->getForm();
|
||||
|
||||
$personalDataMutable = $options['personal_data_mutable'] && $participantData->mutable;
|
||||
$isApplicant = 0 === $participantIndex;
|
||||
// Traverse up the form tree to get the root form's data (BookingEditDto)
|
||||
$rootForm = $form;
|
||||
while ($rootForm->getParent()) {
|
||||
$rootForm = $rootForm->getParent();
|
||||
}
|
||||
/** @var BookingDtoInterface $bookingDto */
|
||||
$bookingDto = $rootForm->getData();
|
||||
|
||||
// Helper to get state for a field
|
||||
$getState = fn(string $field) => $this->fieldStateProvider->getFieldState($field, $bookingDto, $participantIndex);
|
||||
|
||||
$form
|
||||
->add('firstName', TextType::class, [
|
||||
->add('firstName', TextType::class, $this->mergeFieldState([
|
||||
'label' => 'Vorname',
|
||||
'attr' => [
|
||||
'readonly' => false === $personalDataMutable,
|
||||
],
|
||||
'clean_xss' => true,
|
||||
])
|
||||
->add('lastName', TextType::class, [
|
||||
], $getState('firstName')))
|
||||
->add('lastName', TextType::class, $this->mergeFieldState([
|
||||
'label' => 'Nachname',
|
||||
'attr' => [
|
||||
'readonly' => false === $personalDataMutable,
|
||||
],
|
||||
'clean_xss' => true,
|
||||
])
|
||||
->add('dateOfBirth', BirthdayType::class, [
|
||||
], $getState('lastName')))
|
||||
->add('dateOfBirth', BirthdayType::class, $this->mergeFieldState([
|
||||
'label' => 'Geburtsdatum',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
'input' => 'datetime_immutable',
|
||||
'attr' => [
|
||||
'readonly' => false === $personalDataMutable,
|
||||
],
|
||||
])
|
||||
->add('gender', ChoiceType::class, [
|
||||
], $getState('dateOfBirth')))
|
||||
->add('gender', ChoiceType::class, $this->mergeFieldState([
|
||||
'label' => 'Geschlecht',
|
||||
'required' => false,
|
||||
'placeholder' => 'keine Angabe',
|
||||
@@ -69,36 +74,22 @@ class BookingEditParticipantType extends AbstractType
|
||||
'weiblich' => 'W',
|
||||
'divers' => 'D',
|
||||
],
|
||||
'attr' => [
|
||||
'readonly' => false === $personalDataMutable,
|
||||
'style' => false === $personalDataMutable ? 'pointer-events: none' : null,
|
||||
],
|
||||
])
|
||||
->add('nationality', CountryType::class, [
|
||||
], $getState('gender')))
|
||||
->add('nationality', CountryType::class, $this->mergeFieldState([
|
||||
'label' => 'Nationalität',
|
||||
'property' => 'nationality',
|
||||
'preferred_choices' => ['D', 'A', 'CH'],
|
||||
'attr' => [
|
||||
'readonly' => false === $personalDataMutable,
|
||||
'style' => false === $personalDataMutable ? 'pointer-events: none' : null,
|
||||
],
|
||||
])
|
||||
->add('email', EmailType::class, [
|
||||
], $getState('nationality')))
|
||||
->add('email', EmailType::class, $this->mergeFieldState([
|
||||
'label' => 'E-Mail',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'readonly' => true === $isApplicant,
|
||||
],
|
||||
'clean_xss' => true,
|
||||
])
|
||||
->add('mobile', TextType::class, [
|
||||
], $getState('email')))
|
||||
->add('mobile', TextType::class, $this->mergeFieldState([
|
||||
'label' => 'Telefon (mobil)',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'readonly' => true === $isApplicant,
|
||||
],
|
||||
'clean_xss' => true,
|
||||
])
|
||||
], $getState('mobile')))
|
||||
->add('bodyDimensions', BodyDimensionsType::class);
|
||||
|
||||
$commonChoiceFieldOptions = [
|
||||
@@ -294,6 +285,33 @@ class BookingEditParticipantType extends AbstractType
|
||||
return;
|
||||
}
|
||||
|
||||
// Traverse up the form tree to get the root form's data (BookingEditDto)
|
||||
$rootForm = $form;
|
||||
while ($rootForm->getParent()) {
|
||||
$rootForm = $rootForm->getParent();
|
||||
}
|
||||
/** @var BookingDtoInterface $bookingDto */
|
||||
$bookingDto = $rootForm->getData();
|
||||
$participantIndex = $form->getData()->index;
|
||||
|
||||
// Helper to get state for a field based on submitted data
|
||||
$getState = fn(string $field) => $this->fieldStateProvider->getFieldState($field, $bookingDto, $participantIndex, $data);
|
||||
|
||||
// Re-apply field states to all personal data fields
|
||||
$personalDataFields = [
|
||||
'firstName', 'lastName', 'dateOfBirth', 'gender', 'nationality', 'email', 'mobile'
|
||||
];
|
||||
foreach ($personalDataFields as $field) {
|
||||
if ($form->has($field)) {
|
||||
$fieldConfig = $form->get($field)->getConfig();
|
||||
$currentOptions = $fieldConfig->getOptions();
|
||||
$updatedOptions = $this->mergeFieldState($currentOptions, $getState($field));
|
||||
$fieldType = $fieldConfig->getType()->getInnerType();
|
||||
$form->remove($field);
|
||||
$form->add($field, $fieldType::class, $updatedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
$transportationId = $data['transportationServiceTo'] ?? null;
|
||||
$transportation = $options['travel']->pickups[$transportationId] ?? null;
|
||||
|
||||
@@ -344,6 +362,33 @@ class BookingEditParticipantType extends AbstractType
|
||||
'transportation_services_mutable' => true,
|
||||
'pickups_mutable' => true,
|
||||
'applicant_id' => null,
|
||||
// Booking DTO for field state provider (must be set by parent form)
|
||||
'booking' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges field state modifications into existing field options.
|
||||
*
|
||||
* This method combines the base field options with dynamic state modifications,
|
||||
* handling attribute merging and option overrides correctly.
|
||||
*
|
||||
* @param array<string, mixed> $fieldOptions The base field options
|
||||
* @param array<string, mixed> $fieldState The dynamic state modifications
|
||||
*
|
||||
* @return array<string, mixed> The merged field options with state applied
|
||||
*/
|
||||
private function mergeFieldState(array $fieldOptions, array $fieldState): array
|
||||
{
|
||||
foreach ($fieldState as $key => $value) {
|
||||
if ('attr' === $key && isset($fieldOptions['attr'])) {
|
||||
// Merge attributes instead of overwriting
|
||||
$fieldOptions['attr'] = array_merge($fieldOptions['attr'], $value);
|
||||
} else {
|
||||
// Direct assignment for non-attribute options
|
||||
$fieldOptions[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $fieldOptions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,11 @@ interface BookingDtoInterface
|
||||
* @return ParticipantDto|null The participant DTO or null if not found
|
||||
*/
|
||||
public function getParticipant(int $index): ?ParticipantDto;
|
||||
|
||||
/**
|
||||
* Gets all selected rooms for the booking.
|
||||
*
|
||||
* @return array<int, RoomSelectionDto> Array of selected room DTOs (may be empty for edit DTOs)
|
||||
*/
|
||||
public function getSelectedRooms(): array;
|
||||
}
|
||||
@@ -83,4 +83,14 @@ class BookingEditDto implements BookingDtoInterface
|
||||
{
|
||||
return $this->participants[$index] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all selected rooms for the booking (edit context).
|
||||
*
|
||||
* @return array<int, RoomSelectionDto> Always returns an empty array for edit DTOs unless implemented.
|
||||
*/
|
||||
public function getSelectedRooms(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Abstract base class providing common functionality for participant field handlers.
|
||||
@@ -54,14 +54,15 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
||||
* if the participant exists at the given index. Returns null if the
|
||||
* participant doesn't exist, preventing array access errors.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The booking DTO containing participants
|
||||
* @param int $participantIndex The index of the participant to retrieve
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO containing participants (create or edit)
|
||||
* @param int $participantIndex The index of the participant to retrieve
|
||||
*
|
||||
* @return object|null The participant object, or null if not found
|
||||
*/
|
||||
protected function getParticipant(BookingCreateDto $bookingDto, int $participantIndex): ?object
|
||||
protected function getParticipant(BookingDtoInterface $bookingDto, int $participantIndex): ?object
|
||||
{
|
||||
return $bookingDto->participants[$participantIndex] ?? null;
|
||||
$participants = $bookingDto->getParticipants();
|
||||
return $participants[$participantIndex] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,12 +127,12 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
||||
* processing results.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param BookingCreateDto $bookingDto The booking DTO (potentially modified by processing)
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO (potentially modified by processing)
|
||||
* @param int $participantIndex The participant index being processed
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Empty array (no state modifications by default)
|
||||
*/
|
||||
public function getFieldStateModifications(array $submittedData, BookingCreateDto $bookingDto, int $participantIndex): array
|
||||
public function getFieldStateModifications(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Condition that evaluates participant age against specified range criteria.
|
||||
@@ -53,15 +53,15 @@ class AgeRangeCondition implements FieldConditionInterface
|
||||
* checks if it falls within the configured age range. Returns false if
|
||||
* the participant has no date of birth set.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data
|
||||
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (unused for age conditions)
|
||||
*
|
||||
* @return bool True if the participant's age meets the criteria, false otherwise
|
||||
*/
|
||||
public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
$participant = $bookingDto->participants[$participantIndex] ?? null;
|
||||
$participant = $bookingDto->getParticipant($participantIndex);
|
||||
|
||||
if (null === $participant || null === $participant->dateOfBirth) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
||||
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Condition that checks if the participant is the applicant.
|
||||
*
|
||||
* This is used to apply field state logic (e.g., enable/disable fields)
|
||||
* specifically for the applicant participant in the booking.
|
||||
*
|
||||
* The default logic assumes the applicant is the first participant (index 0),
|
||||
* but this can be adjusted if your domain uses a different rule or property.
|
||||
*/
|
||||
class ApplicantCondition implements FieldConditionInterface
|
||||
{
|
||||
/**
|
||||
* Evaluates if the participant is the applicant.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (unused)
|
||||
*
|
||||
* @return bool True if the participant is the applicant, false otherwise
|
||||
*/
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
// Default: applicant is the first participant (index 0)
|
||||
return $participantIndex === 0;
|
||||
}
|
||||
|
||||
public function getDependentFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Checks if the participant is the applicant (index 0)';
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Composite condition that combines multiple conditions with logical operators.
|
||||
@@ -63,13 +63,13 @@ class CompositeCondition implements FieldConditionInterface
|
||||
* evaluation for optimal performance. The evaluation stops as soon as the
|
||||
* final result can be determined.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data
|
||||
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return bool True if the composite condition is met, false otherwise
|
||||
*/
|
||||
public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
return match ($this->operator) {
|
||||
self::OPERATOR_AND => $this->evaluateAnd($bookingDto, $participantIndex, $formData),
|
||||
@@ -167,7 +167,7 @@ class CompositeCondition implements FieldConditionInterface
|
||||
* Returns false as soon as any condition evaluates to false,
|
||||
* avoiding unnecessary evaluation of remaining conditions.
|
||||
*/
|
||||
private function evaluateAnd(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
private function evaluateAnd(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
foreach ($this->conditions as $condition) {
|
||||
if (!$condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||
@@ -184,7 +184,7 @@ class CompositeCondition implements FieldConditionInterface
|
||||
* Returns true as soon as any condition evaluates to true,
|
||||
* avoiding unnecessary evaluation of remaining conditions.
|
||||
*/
|
||||
private function evaluateOr(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
private function evaluateOr(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
foreach ($this->conditions as $condition) {
|
||||
if ($condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||
@@ -198,7 +198,7 @@ class CompositeCondition implements FieldConditionInterface
|
||||
/**
|
||||
* Evaluates NOT logic by inverting the result of the single condition.
|
||||
*/
|
||||
private function evaluateNot(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
private function evaluateNot(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
return !$this->conditions[0]->evaluate($bookingDto, $participantIndex, $formData);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Interface for evaluating field state conditions.
|
||||
@@ -29,13 +30,13 @@ interface FieldConditionInterface
|
||||
* state of the booking, participant data, and submitted form values.
|
||||
* The result is used to determine field state (enabled/disabled/readonly).
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data
|
||||
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (may include partial submissions)
|
||||
*
|
||||
* @return bool True if the condition is met, false otherwise
|
||||
*/
|
||||
public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool;
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool;
|
||||
|
||||
/**
|
||||
* Returns field names that trigger re-evaluation of this condition.
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Condition that evaluates field states based on other field values.
|
||||
@@ -59,13 +59,13 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
* against the expected value using the configured operator. Supports
|
||||
* both participant-level fields and booking-level fields.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data
|
||||
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return bool True if the field value meets the condition criteria, false otherwise
|
||||
*/
|
||||
public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
$fieldValue = $this->getFieldValue($formData, $participantIndex, $bookingDto);
|
||||
|
||||
@@ -174,7 +174,7 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
/**
|
||||
* Retrieves field value from form data or participant data.
|
||||
*/
|
||||
private function getFieldValue(array $formData, int $participantIndex, BookingCreateDto $bookingDto): mixed
|
||||
private function getFieldValue(array $formData, int $participantIndex, BookingDtoInterface $bookingDto): mixed
|
||||
{
|
||||
// First check participant-specific form data
|
||||
if (isset($formData['participants'][$participantIndex][$this->fieldName])) {
|
||||
@@ -182,7 +182,7 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
}
|
||||
|
||||
// Then check participant DTO data
|
||||
$participant = $bookingDto->participants[$participantIndex] ?? null;
|
||||
$participant = $bookingDto->getParticipant($participantIndex);
|
||||
if (null !== $participant && property_exists($participant, $this->fieldName)) {
|
||||
return $participant->{$this->fieldName};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
||||
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Condition that checks if a participant's personal data is mutable in the edit flow.
|
||||
*
|
||||
* This is used to set fields as readonly or disabled if the booking or participant
|
||||
* is not allowed to be modified (e.g., after a certain workflow step or status).
|
||||
*
|
||||
* The logic assumes the BookingDtoInterface or its participant DTOs expose a
|
||||
* 'personalDataMutable' property or method. Adjust as needed for your domain.
|
||||
*/
|
||||
class MutabilityCondition implements FieldConditionInterface
|
||||
{
|
||||
/**
|
||||
* Evaluates if the participant's personal data is mutable.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (unused)
|
||||
*
|
||||
* @return bool True if the participant's data is mutable, false otherwise
|
||||
*/
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
$participant = $bookingDto->getParticipant($participantIndex);
|
||||
if (null === $participant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $participant->mutable;
|
||||
}
|
||||
|
||||
public function getDependentFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Checks if the participant\'s personal data is mutable (edit flow)';
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Form\ParticipantFieldHandler;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Handles processing of the assignedRoomId field for booking participants.
|
||||
@@ -52,10 +53,10 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle
|
||||
* 4. Updates the participant's assignedRoomId property
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param BookingCreateDto $bookingDto The booking DTO to update
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO to update (create or edit)
|
||||
* @param int $participantIndex The index of the participant being processed
|
||||
*/
|
||||
public function processField(array $submittedData, BookingCreateDto $bookingDto, int $participantIndex): void
|
||||
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||
{
|
||||
// Safely get the participant object, returning early if not found
|
||||
$participant = $this->getParticipant($bookingDto, $participantIndex);
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ParticipantFieldHandler;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Interface for handling dynamic participant form field processing and state modification.
|
||||
@@ -32,10 +32,10 @@ interface ParticipantFieldHandlerInterface
|
||||
* Processes the participant field data from submitted form data and updates the DTO.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param BookingCreateDto $bookingDto The booking DTO to update
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO to update (create or edit)
|
||||
* @param int $participantIndex The participant index being processed
|
||||
*/
|
||||
public function processField(array $submittedData, BookingCreateDto $bookingDto, int $participantIndex): void;
|
||||
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void;
|
||||
|
||||
/**
|
||||
* Determines if this handler should process the field based on submitted participant data.
|
||||
@@ -50,12 +50,12 @@ interface ParticipantFieldHandlerInterface
|
||||
* to enable/disable/hide fields based on the handler's processing results.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param BookingCreateDto $bookingDto The booking DTO (potentially modified by processing)
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO (potentially modified by processing)
|
||||
* @param int $participantIndex The participant index being processed
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Field state modifications indexed by field name
|
||||
*/
|
||||
public function getFieldStateModifications(array $submittedData, BookingCreateDto $bookingDto, int $participantIndex): array;
|
||||
public function getFieldStateModifications(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): array;
|
||||
|
||||
/**
|
||||
* Returns field names whose state is affected by this handler's processing.
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Form\ParticipantFieldHandler;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Registry for managing and executing participant field handlers in dependency order.
|
||||
@@ -70,9 +71,9 @@ class ParticipantFieldHandlerRegistry
|
||||
* participant's data and updates the booking DTO accordingly.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted form data containing participants array
|
||||
* @param BookingCreateDto $bookingDto The booking DTO to update with processed field values
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO to update with processed field values (create or edit)
|
||||
*/
|
||||
public function processFields(array $submittedData, BookingCreateDto $bookingDto): void
|
||||
public function processFields(array $submittedData, BookingDtoInterface $bookingDto): void
|
||||
{
|
||||
// Early return if no participant data exists in submission
|
||||
if (!isset($submittedData['participants']) || !is_array($submittedData['participants'])) {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
||||
use App\Form\ParticipantFieldHandler\Condition\MutabilityCondition;
|
||||
use App\Form\ParticipantFieldHandler\Condition\ApplicantCondition;
|
||||
use App\Form\ParticipantFieldHandler\Condition\CompositeCondition;
|
||||
|
||||
/**
|
||||
* Field state provider for the booking edit workflow.
|
||||
*
|
||||
* This service calculates dynamic field states (readonly, disabled, etc.)
|
||||
* for participant fields in the edit flow, using edit-specific conditions.
|
||||
*
|
||||
* It is designed to be extensible and composable, allowing reuse of
|
||||
* existing condition classes and easy registration of new logic.
|
||||
*/
|
||||
class EditFieldStateProvider implements FieldStateProviderInterface
|
||||
{
|
||||
/** @var array<string, array<string, FieldConditionInterface>> */
|
||||
private array $fieldStateConditions = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->registerFieldStateConditions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers field state conditions for the edit workflow.
|
||||
*
|
||||
* Add or modify conditions as needed for your domain.
|
||||
*/
|
||||
private function registerFieldStateConditions(): void
|
||||
{
|
||||
// Make all personal data fields readonly if not mutable OR if applicant
|
||||
$personalDataFields = [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'dateOfBirth',
|
||||
'gender',
|
||||
'nationality',
|
||||
'email',
|
||||
'mobile',
|
||||
];
|
||||
foreach ($personalDataFields as $field) {
|
||||
$this->fieldStateConditions[$field] = [
|
||||
'readonly' => CompositeCondition::or(
|
||||
new ApplicantCondition(),
|
||||
new MutabilityCondition()
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// Example: Only applicant can edit email
|
||||
$this->fieldStateConditions['email']['readonly'] = new ApplicantCondition();
|
||||
}
|
||||
|
||||
public function getFieldState(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
||||
{
|
||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stateModifications = [];
|
||||
$attributes = [];
|
||||
|
||||
foreach ($this->fieldStateConditions[$fieldName] as $stateType => $condition) {
|
||||
if ($condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||
switch ($stateType) {
|
||||
case 'readonly':
|
||||
$attributes['readonly'] = true;
|
||||
break;
|
||||
case 'disabled':
|
||||
$stateModifications['disabled'] = true;
|
||||
break;
|
||||
case 'required':
|
||||
$stateModifications['required'] = true;
|
||||
break;
|
||||
case 'hidden':
|
||||
$attributes['style'] = ($attributes['style'] ?? '').' display: none;';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($attributes)) {
|
||||
$stateModifications['attr'] = $attributes;
|
||||
}
|
||||
|
||||
return $stateModifications;
|
||||
}
|
||||
|
||||
public function hasStateConditions(string $fieldName): bool
|
||||
{
|
||||
return isset($this->fieldStateConditions[$fieldName]) && !empty($this->fieldStateConditions[$fieldName]);
|
||||
}
|
||||
|
||||
public function getFieldStateDependencies(string $fieldName): array
|
||||
{
|
||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
||||
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
||||
}
|
||||
return array_unique($dependencies);
|
||||
}
|
||||
|
||||
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
||||
{
|
||||
$allStates = [];
|
||||
foreach (array_keys($this->fieldStateConditions) as $fieldName) {
|
||||
$fieldState = $this->getFieldState($fieldName, $bookingDto, $participantIndex, $formData);
|
||||
if (!empty($fieldState)) {
|
||||
$allStates[$fieldName] = $fieldState;
|
||||
}
|
||||
}
|
||||
return $allStates;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
|
||||
/**
|
||||
* Interface for providing dynamic field state based on conditions.
|
||||
@@ -46,13 +46,13 @@ interface FieldStateProviderInterface
|
||||
* - 'attr' => ['class' => 'conditional-field'] - Add CSS classes
|
||||
*
|
||||
* @param string $fieldName The name of the field to evaluate
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (may include partial submissions)
|
||||
*
|
||||
* @return array<string, mixed> Symfony form field options for state modifications, empty if no changes needed
|
||||
*/
|
||||
public function getFieldState(string $fieldName, BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
public function getFieldState(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
|
||||
/**
|
||||
* Checks whether a field has state conditions configured.
|
||||
@@ -91,11 +91,11 @@ interface FieldStateProviderInterface
|
||||
* when multiple field states need to be determined simultaneously. It's
|
||||
* particularly useful during form building and bulk state updates.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Field states indexed by field name
|
||||
*/
|
||||
public function getAllFieldStates(BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
||||
|
||||
/**
|
||||
@@ -64,13 +64,13 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
* - 'disabled' - Whether the field should be disabled
|
||||
* - 'required' - Whether the field is required
|
||||
*
|
||||
* @param string $fieldName The name of the field to configure
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param int $participantIndex The index of the participant being configured
|
||||
* @param string $fieldName The name of the field to configure
|
||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @param int $participantIndex The index of the participant being configured
|
||||
*
|
||||
* @return array<string, mixed> Symfony form field options, or empty array if field not supported
|
||||
*/
|
||||
public function getFieldOptions(string $fieldName, BookingCreateDto $bookingDto, int $participantIndex): array
|
||||
public function getFieldOptions(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex): array
|
||||
{
|
||||
// Check if we have a provider for this field
|
||||
if (!isset($this->fieldOptionProviders[$fieldName])) {
|
||||
@@ -121,7 +121,7 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
private function registerFieldOptionProviders(): void
|
||||
{
|
||||
// Room assignment field provider
|
||||
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingCreateDto $bookingDto, int $participantIndex) => [
|
||||
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||
'label' => 'Zimmer',
|
||||
'placeholder' => 'Bitte wählen',
|
||||
// Use factory to create context-aware choice loader that:
|
||||
@@ -155,13 +155,13 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
* disabled, etc.) and evaluated independently.
|
||||
*
|
||||
* @param string $fieldName The name of the field to evaluate
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return array<string, mixed> Symfony form field options for state modifications
|
||||
*/
|
||||
public function getFieldState(string $fieldName, BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array
|
||||
public function getFieldState(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
||||
{
|
||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||
return [];
|
||||
@@ -233,13 +233,13 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
/**
|
||||
* Calculates field states for all configured fields at once.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Field states indexed by field name
|
||||
*/
|
||||
public function getAllFieldStates(BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array
|
||||
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
||||
{
|
||||
$allStates = [];
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
</div>
|
||||
<div>
|
||||
<h3>Zusammenfassung</h3>
|
||||
<p>Reise: {{ bookingCreateDto.travelData.label }}</p>
|
||||
<p>Datum: {{ bookingCreateDto.travelData.dateFrom | date('d.m.Y') }} - {{ bookingCreateDto.travelData.dateTo | date('d.m.Y') }}</p>
|
||||
<p>Hotel: {{ bookingCreateDto.travelData.hotel.name }}</p>
|
||||
<p>Reise: {{ bookingCreateDto.travel.label }}</p>
|
||||
<p>Datum: {{ bookingCreateDto.travel.dateFrom | date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo | date('d.m.Y') }}</p>
|
||||
<p>Hotel: {{ bookingCreateDto.travel.hotel.name }}</p>
|
||||
{% if participantsCount > 0 %}
|
||||
<p>Teilnehmerzahl: {{ participantsCount }}</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -71,19 +71,19 @@
|
||||
Reise
|
||||
</h4>
|
||||
<p>
|
||||
{{ bookingCreateDto.travelData.label }}
|
||||
{{ bookingCreateDto.travel.label }}
|
||||
</p>
|
||||
<h4>
|
||||
Datum
|
||||
</h4>
|
||||
<p>
|
||||
{{ bookingCreateDto.travelData.dateFrom | date('d.m.Y') }} - {{ bookingCreateDto.travelData.dateTo | date('d.m.Y') }}
|
||||
{{ bookingCreateDto.travel.dateFrom | date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo | date('d.m.Y') }}
|
||||
</p>
|
||||
<h4>
|
||||
Unterkunft
|
||||
</h4>
|
||||
<p>
|
||||
{{ bookingCreateDto.travelData.hotel.name }}
|
||||
{{ bookingCreateDto.travel.hotel.name }}
|
||||
</p>
|
||||
{% if participantsCount > 0 %}
|
||||
<h4>
|
||||
|
||||
Reference in New Issue
Block a user