From c85c72863cc7170f91e5473374f90a774760677c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 23 Jul 2025 10:27:04 +0200 Subject: [PATCH] wip: refactoring --- composer.lock | 25 ++++---- config/services.yaml | 4 ++ src/Form/BookingCreateParticipantType.php | 31 ++++++++-- src/Form/BookingCreateStep2Type.php | 58 ++++++------------- src/Form/BookingEditParticipantType.php | 5 +- src/Form/BookingEditType.php | 3 +- .../ParticipantRoomChoiceLoader.php | 37 +++++------- .../DataAdapters/ParticipantDataAdapter.php | 34 ----------- .../DataTransformer/XssCleanTransformer.php | 29 ++++++++++ src/Form/Extension/AntiXssExtension.php | 45 -------------- src/Form/Extension/XssCleanExtension.php | 38 ++++++++++++ src/Form/PersonalDataType.php | 14 ++--- src/Form/RegistrationType.php | 4 +- .../ParticipantRoomChoiceLoaderFactory.php | 17 +++--- 14 files changed, 165 insertions(+), 179 deletions(-) delete mode 100644 src/Form/DataAdapters/ParticipantDataAdapter.php create mode 100644 src/Form/DataTransformer/XssCleanTransformer.php delete mode 100644 src/Form/Extension/AntiXssExtension.php create mode 100644 src/Form/Extension/XssCleanExtension.php diff --git a/composer.lock b/composer.lock index 1a6b2cc..1dfe667 100644 --- a/composer.lock +++ b/composer.lock @@ -1758,16 +1758,16 @@ }, { "name": "league/flysystem-bundle", - "version": "3.4.0", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-bundle.git", - "reference": "6493f7f2ab49bc5817e4b064b9b971d93faabc12" + "reference": "bf5ab3c072c0def47872e28f69876548d8669657" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/6493f7f2ab49bc5817e4b064b9b971d93faabc12", - "reference": "6493f7f2ab49bc5817e4b064b9b971d93faabc12", + "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/bf5ab3c072c0def47872e28f69876548d8669657", + "reference": "bf5ab3c072c0def47872e28f69876548d8669657", "shasum": "" }, "require": { @@ -1791,6 +1791,7 @@ "league/flysystem-read-only": "^3.15", "league/flysystem-sftp-v3": "^3.1", "league/flysystem-webdav": "^3.29", + "platformcommunity/flysystem-bunnycdn": "^3.3", "symfony/dotenv": "^5.4 || ^6.0 || ^7.0", "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0", @@ -1822,9 +1823,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-bundle/issues", - "source": "https://github.com/thephpleague/flysystem-bundle/tree/3.4.0" + "source": "https://github.com/thephpleague/flysystem-bundle/tree/3.5.0" }, - "time": "2025-01-23T18:07:31+00:00" + "time": "2025-06-22T12:43:01+00:00" }, { "name": "league/flysystem-local", @@ -10425,16 +10426,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.82.2", + "version": "v3.84.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "684ed3ab41008a2a4848de8bde17eb168c596247" + "reference": "38dad0767bf2a9b516b976852200ae722fe984ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/684ed3ab41008a2a4848de8bde17eb168c596247", - "reference": "684ed3ab41008a2a4848de8bde17eb168c596247", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca", + "reference": "38dad0767bf2a9b516b976852200ae722fe984ca", "shasum": "" }, "require": { @@ -10518,7 +10519,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.82.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0" }, "funding": [ { @@ -10526,7 +10527,7 @@ "type": "github" } ], - "time": "2025-07-08T21:13:15+00:00" + "time": "2025-07-15T18:21:57+00:00" }, { "name": "myclabs/deep-copy", diff --git a/config/services.yaml b/config/services.yaml index f8f496a..fd5b81c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -62,3 +62,7 @@ services: $logger: '@monolog.logger.core' $preferRemote: '%env(bool:APP_TRAVEL_PREFER_REMOTE)%' $enableFallback: '%env(bool:APP_TRAVEL_ENABLE_FALLBACK)%' + + App\Form\Service\ParticipantRoomChoiceLoaderFactory: + arguments: + $choiceListFactory: '@form.choice_list_factory.default' diff --git a/src/Form/BookingCreateParticipantType.php b/src/Form/BookingCreateParticipantType.php index 827a3a2..7404aff 100644 --- a/src/Form/BookingCreateParticipantType.php +++ b/src/Form/BookingCreateParticipantType.php @@ -3,6 +3,7 @@ namespace App\Form; use App\BusProNet\Form\CountryType; +use App\Form\Model\BookingCreateDto; use App\Form\Model\ParticipantDto; use App\Form\Service\ParticipantRoomChoiceLoaderFactory; use Symfony\Component\Form\AbstractType; @@ -27,9 +28,11 @@ class BookingCreateParticipantType extends AbstractType $builder ->add('firstName', TextType::class, [ 'label' => 'Vorname', + 'clean_xss' => true, ]) ->add('lastName', TextType::class, [ 'label' => 'Nachname', + 'clean_xss' => true, ]) ->add('dateOfBirth', BirthdayType::class, [ 'label' => 'Geburtsdatum', @@ -55,19 +58,36 @@ class BookingCreateParticipantType extends AbstractType ->add('email', EmailType::class, [ 'label' => 'E-Mail', 'required' => false, + 'clean_xss' => true, ]) ->add('mobile', TextType::class, [ 'label' => 'Telefon (mobil)', 'required' => false, + 'clean_xss' => true, ]) ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) { - $data = $event->getData(); + /** @var ParticipantDto|null $participantData */ + $participantData = $event->getData(); $form = $event->getForm(); - $choiceLoader = $this->choiceLoaderFactory->createChoiceLoader( - $options['participant_adapter'], + if (null === $participantData) { + return; + } + + // Traverse up the form tree to get the root form's data. + $rootForm = $form; + while ($rootForm->getParent()) { + $rootForm = $rootForm->getParent(); + } + + /** @var BookingCreateDto $bookingCreateDto */ + $bookingCreateDto = $rootForm->getData(); + $allParticipants = $bookingCreateDto->participants; + + $choiceLoader = $this->choiceLoaderFactory->create( + $allParticipants, $options['selected_rooms'], - $data->index + $participantData->index ); $form->add('assignedRoomId', ChoiceType::class, [ @@ -83,8 +103,9 @@ class BookingCreateParticipantType extends AbstractType { $resolver->setDefaults([ 'data_class' => ParticipantDto::class, - 'participant_adapter' => null, 'selected_rooms' => [], ]); + + $resolver->setAllowedTypes('selected_rooms', 'array'); } } diff --git a/src/Form/BookingCreateStep2Type.php b/src/Form/BookingCreateStep2Type.php index b4c503c..d8614c4 100644 --- a/src/Form/BookingCreateStep2Type.php +++ b/src/Form/BookingCreateStep2Type.php @@ -2,61 +2,37 @@ namespace App\Form; -use App\Form\DataAdapters\ParticipantDataAdapter; use App\Form\Model\BookingCreateDto; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class BookingCreateStep2Type extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { - $builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'onPreSetData']); - $builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'onPreSubmit']); - } + $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { + /** @var BookingCreateDto|null $data */ + $data = $event->getData(); + $form = $event->getForm(); - public function onPreSetData(FormEvent $event): void - { - /** @var BookingCreateDto $data */ - $data = $event->getData(); - $form = $event->getForm(); + if (null === $data) { + return; + } - $adapter = new ParticipantDataAdapter($data->participants); - $this->addParticipantsField($form, $adapter, $data->getSelectedRooms()); - } - - public function onPreSubmit(FormEvent $event): void - { - $data = $event->getData(); - $form = $event->getForm(); - /** @var BookingCreateDto $bookingCreateDto */ - $bookingCreateDto = $form->getData(); - - $adapter = new ParticipantDataAdapter($data['participants']); - $form->remove('participants'); - $this->addParticipantsField($form, $adapter, $bookingCreateDto->getSelectedRooms()); - } - - /** - * Adds the participants collection field to the form. - */ - private function addParticipantsField(FormInterface $form, ParticipantDataAdapter $adapter, array $selectedRooms): void - { - $form->add('participants', CollectionType::class, [ - 'entry_type' => BookingCreateParticipantType::class, - 'allow_add' => false, - 'allow_delete' => false, - 'by_reference' => false, - 'entry_options' => [ - 'participant_adapter' => $adapter, - 'selected_rooms' => $selectedRooms, - ], - ]); + $form->add('participants', CollectionType::class, [ + 'entry_type' => BookingCreateParticipantType::class, + 'allow_add' => false, + 'allow_delete' => false, + 'by_reference' => false, + 'entry_options' => [ + 'selected_rooms' => $data->getSelectedRooms(), + ], + ]); + }); } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Form/BookingEditParticipantType.php b/src/Form/BookingEditParticipantType.php index a71f4c8..2e2d890 100644 --- a/src/Form/BookingEditParticipantType.php +++ b/src/Form/BookingEditParticipantType.php @@ -42,12 +42,14 @@ class BookingEditParticipantType extends AbstractType 'attr' => [ 'readonly' => false === $personalDataMutable, ], + 'anti_xss' => true, ]) ->add('lastName', TextType::class, [ 'label' => 'Nachname', 'attr' => [ 'readonly' => false === $personalDataMutable, ], + 'anti_xss' => true, ]) ->add('dateOfBirth', BirthdayType::class, [ 'label' => 'Geburtsdatum', @@ -87,6 +89,7 @@ class BookingEditParticipantType extends AbstractType 'attr' => [ 'readonly' => true === $isApplicant, ], + 'anti_xss' => true, ]) ->add('mobile', TextType::class, [ 'label' => 'Telefon (mobil)', @@ -94,6 +97,7 @@ class BookingEditParticipantType extends AbstractType 'attr' => [ 'readonly' => true === $isApplicant, ], + 'anti_xss' => true, ]) ->add('height', ChoiceType::class, [ 'label' => 'Körpergröße', @@ -379,7 +383,6 @@ class BookingEditParticipantType extends AbstractType 'transportation_services_mutable' => true, 'pickups_mutable' => true, 'applicant_id' => null, - 'anti_xss' => true, ]); } } diff --git a/src/Form/BookingEditType.php b/src/Form/BookingEditType.php index 34df0be..e431457 100644 --- a/src/Form/BookingEditType.php +++ b/src/Form/BookingEditType.php @@ -12,7 +12,7 @@ use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; -final class BookingEditType extends AbstractType +class BookingEditType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { @@ -69,7 +69,6 @@ final class BookingEditType extends AbstractType { $resolver->setDefaults([ 'data_class' => BookingEditDto::class, - 'anti_xss' => true, ]); } } diff --git a/src/Form/ChoiceLoader/ParticipantRoomChoiceLoader.php b/src/Form/ChoiceLoader/ParticipantRoomChoiceLoader.php index f4f0688..d3b3aa2 100644 --- a/src/Form/ChoiceLoader/ParticipantRoomChoiceLoader.php +++ b/src/Form/ChoiceLoader/ParticipantRoomChoiceLoader.php @@ -4,7 +4,8 @@ declare(strict_types=1); namespace App\Form\ChoiceLoader; -use App\Form\DataAdapters\ParticipantDataAdapter; +use App\Form\Model\ParticipantDto; +use App\Form\Model\RoomSelectionDto; use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; @@ -21,9 +22,14 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface { private ?ChoiceListInterface $choiceList = null; + /** + * @param ParticipantDto[] $allParticipants all participant DTOs from the root form + * @param RoomSelectionDto[] $selectedRooms the rooms selected in the previous step + * @param int $participantIndex the index of the current participant + */ public function __construct( private readonly ChoiceListFactoryInterface $factory, - private readonly ParticipantDataAdapter $adapter, + private readonly array $allParticipants, private readonly array $selectedRooms, private readonly int $participantIndex, ) { @@ -66,7 +72,7 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface { $roomOccupancy = $this->calculateRoomOccupancy(); $participantRoomChoices = []; - $assignedRoomId = $this->getParticipantAssignedRoomId(); + $assignedRoomId = $this->allParticipants[$this->participantIndex]->assignedRoomId ?? null; foreach ($this->selectedRooms as $roomSelection) { $currentOccupancy = $roomOccupancy[$roomSelection->roomId] ?? 0; @@ -77,7 +83,8 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface --$adjustedOccupancy; } - $remainingCapacity = $roomSelection->minPax - $adjustedOccupancy; + $totalAvailableRooms = $roomSelection->quantity; + $remainingCapacity = $totalAvailableRooms - $adjustedOccupancy; // Include room if it has capacity OR if it's the participant's current assignment if ($remainingCapacity > 0 || $assignedRoomId === $roomSelection->roomId) { @@ -89,7 +96,7 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface } /** - * Calculates room occupancy based on participant assignments. + * Calculates room occupancy based on all participant assignments. * * @return array */ @@ -97,24 +104,12 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface { $roomOccupancy = []; - foreach ($this->adapter->getParticipants() as $participant) { - $assignedRoomId = $this->adapter->getAssignedRoomId($participant); - if (null !== $assignedRoomId) { - $roomOccupancy[$assignedRoomId] = ($roomOccupancy[$assignedRoomId] ?? 0) + 1; + foreach ($this->allParticipants as $participant) { + if (null !== $participant->assignedRoomId) { + $roomOccupancy[$participant->assignedRoomId] = ($roomOccupancy[$participant->assignedRoomId] ?? 0) + 1; } } return $roomOccupancy; } - - /** - * Gets the assigned room ID for the current participant. - */ - private function getParticipantAssignedRoomId(): ?int - { - $participants = $this->adapter->getParticipants(); - $participant = $participants[$this->participantIndex] ?? null; - - return null !== $participant ? $this->adapter->getAssignedRoomId($participant) : null; - } -} \ No newline at end of file +} diff --git a/src/Form/DataAdapters/ParticipantDataAdapter.php b/src/Form/DataAdapters/ParticipantDataAdapter.php deleted file mode 100644 index b88af11..0000000 --- a/src/Form/DataAdapters/ParticipantDataAdapter.php +++ /dev/null @@ -1,34 +0,0 @@ -participants = $participants; - } - - public function getParticipants(): array - { - return $this->participants; - } - - public function getAssignedRoomId(mixed $participant): ?int - { - if (is_array($participant)) { - $assignedRoomId = $participant['assignedRoomId'] ?? null; - return null !== $assignedRoomId ? (int) $assignedRoomId : null; - } - - return $participant->assignedRoomId; - } -} diff --git a/src/Form/DataTransformer/XssCleanTransformer.php b/src/Form/DataTransformer/XssCleanTransformer.php new file mode 100644 index 0000000..7c74576 --- /dev/null +++ b/src/Form/DataTransformer/XssCleanTransformer.php @@ -0,0 +1,29 @@ +antiXss) { + $this->antiXss = new AntiXSS(); + } + + return $this->antiXss->xss_clean($value); + } +} diff --git a/src/Form/Extension/AntiXssExtension.php b/src/Form/Extension/AntiXssExtension.php deleted file mode 100644 index 6712b2f..0000000 --- a/src/Form/Extension/AntiXssExtension.php +++ /dev/null @@ -1,45 +0,0 @@ -addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { - $data = $event->getData(); - if (null === $data) { - return; - } - $antiXss = new AntiXSS(); - foreach ($data as $key => $value) { - $data[$key] = $antiXss->xss_clean($value); - } - $event->setData($data); - }); - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'anti_xss' => false, - ]); - } - - public static function getExtendedTypes(): iterable - { - return [FormType::class]; - } -} diff --git a/src/Form/Extension/XssCleanExtension.php b/src/Form/Extension/XssCleanExtension.php new file mode 100644 index 0000000..daae308 --- /dev/null +++ b/src/Form/Extension/XssCleanExtension.php @@ -0,0 +1,38 @@ +addModelTransformer(new XssCleanTransformer()); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'clean_xss' => false, + ]); + $resolver->setAllowedTypes('clean_xss', 'bool'); + } + + public static function getExtendedTypes(): iterable + { + return [ + TextType::class, + TextareaType::class, + EmailType::class, + ]; + } +} diff --git a/src/Form/PersonalDataType.php b/src/Form/PersonalDataType.php index 3ffe9ca..92f0797 100644 --- a/src/Form/PersonalDataType.php +++ b/src/Form/PersonalDataType.php @@ -7,7 +7,6 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; class PersonalDataType extends AbstractType { @@ -17,14 +16,17 @@ class PersonalDataType extends AbstractType ->add('street', TextType::class, [ 'label' => 'Straße', 'property_path' => 'address.street', + 'anti_xss' => true, ]) ->add('postCode', TextType::class, [ 'label' => 'PLZ', 'property_path' => 'address.postCode', + 'anti_xss' => true, ]) ->add('city', TextType::class, [ 'label' => 'Stadt', 'property_path' => 'address.city', + 'anti_xss' => true, ]) ->add('country', CountryType::class, [ 'label' => 'Land', @@ -33,23 +35,19 @@ class PersonalDataType extends AbstractType ->add('email', EmailType::class, [ 'label' => 'E-Mail', 'property_path' => 'communication.email', + 'anti_xss' => true, ]) ->add('phone', TextType::class, [ 'label' => 'Telefon', 'required' => false, 'property_path' => 'communication.phone', + 'anti_xss' => true, ]) ->add('mobile', TextType::class, [ 'label' => 'Mobil', 'property_path' => 'communication.mobile', + 'anti_xss' => true, ]) ; } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'anti_xss' => true, - ]); - } } diff --git a/src/Form/RegistrationType.php b/src/Form/RegistrationType.php index cfbf1ce..85256f8 100644 --- a/src/Form/RegistrationType.php +++ b/src/Form/RegistrationType.php @@ -25,13 +25,14 @@ class RegistrationType extends AbstractType ]) ->add('firstName', TextType::class, [ 'label' => 'Name', + 'anti_xss' => true, ]) ->add('name', TextType::class, [ 'label' => 'Nachname', ]) ->add('email', EmailType::class, [ 'label' => 'E-Mail', - 'property_path' => 'email', + 'anti_xss' => true, ]) ; } @@ -40,7 +41,6 @@ class RegistrationType extends AbstractType { $resolver->setDefaults([ 'data_class' => RegistrationDto::class, - 'anti_xss' => true, ]); } } diff --git a/src/Form/Service/ParticipantRoomChoiceLoaderFactory.php b/src/Form/Service/ParticipantRoomChoiceLoaderFactory.php index 9b7d77b..5a07322 100644 --- a/src/Form/Service/ParticipantRoomChoiceLoaderFactory.php +++ b/src/Form/Service/ParticipantRoomChoiceLoaderFactory.php @@ -5,14 +5,12 @@ declare(strict_types=1); namespace App\Form\Service; use App\Form\ChoiceLoader\ParticipantRoomChoiceLoader; -use App\Form\DataAdapters\ParticipantDataAdapter; +use App\Form\Model\ParticipantDto; +use App\Form\Model\RoomSelectionDto; use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; /** * Factory service for creating ParticipantRoomChoiceLoader instances. - * - * This service provides a clean way to create choice loaders with proper - * dependency injection instead of manually instantiating factories in form types. */ class ParticipantRoomChoiceLoaderFactory { @@ -23,17 +21,20 @@ class ParticipantRoomChoiceLoaderFactory /** * Creates a ParticipantRoomChoiceLoader for the given participant and room data. + * + * @param ParticipantDto[] $allParticipants + * @param RoomSelectionDto[] $selectedRooms */ - public function createChoiceLoader( - ParticipantDataAdapter $adapter, + public function create( + array $allParticipants, array $selectedRooms, int $participantIndex, ): ParticipantRoomChoiceLoader { return new ParticipantRoomChoiceLoader( $this->choiceListFactory, - $adapter, + $allParticipants, $selectedRooms, $participantIndex ); } -} \ No newline at end of file +}