From a619add4e3c12c32438e0291be77f75f1bd4527e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Fri, 31 Oct 2025 15:07:22 +0100 Subject: [PATCH] fix: ensure validation of participant data --- src/Form/Model/ParticipantDto.php | 1 + .../Constraints/BookingValidator.php | 121 +++++++++++++++--- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/src/Form/Model/ParticipantDto.php b/src/Form/Model/ParticipantDto.php index 0f034fc..22300d9 100644 --- a/src/Form/Model/ParticipantDto.php +++ b/src/Form/Model/ParticipantDto.php @@ -11,6 +11,7 @@ use App\Validator\Constraints as AppAssert; use Symfony\Component\Validator\Constraints as Assert; #[AppAssert\Participant(groups: ['booking_edit', 'booking_create'])] +#[AppAssert\Booking(groups: ['booking_edit', 'booking_create'])] class ParticipantDto { /** diff --git a/src/Validator/Constraints/BookingValidator.php b/src/Validator/Constraints/BookingValidator.php index 49cba07..45bf17f 100644 --- a/src/Validator/Constraints/BookingValidator.php +++ b/src/Validator/Constraints/BookingValidator.php @@ -7,6 +7,7 @@ namespace App\Validator\Constraints; use App\Form\Model\BookingDto; use App\Form\Model\ParticipantDto; use App\Form\Model\ParticipantEditDto; +use Psr\Log\LoggerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -34,18 +35,77 @@ use Symfony\Component\Validator\ConstraintValidator; */ class BookingValidator extends ConstraintValidator { + public function __construct(private readonly LoggerInterface $logger) + { + } + public function validate(mixed $value, Constraint $constraint): void { - if (!$value instanceof ParticipantEditDto) { + $this->logger->info('BookingValidator called', [ + 'value_type' => get_class($value), + 'is_ParticipantEditDto' => $value instanceof ParticipantEditDto, + 'is_ParticipantDto' => $value instanceof ParticipantDto, + ]); + + $propertyPathPrefix = ''; + + if ($value instanceof ParticipantEditDto) { + $this->logger->info('Validating ParticipantEditDto (individual form)'); + $participant = $value->participant; + $bookingContext = $value->bookingContext; + $propertyPathPrefix = 'participant.'; + } elseif ($value instanceof ParticipantDto) { + $this->logger->info('Validating ParticipantDto (cards view)', [ + 'participant_index' => $value->index ?? 'unknown', + ]); + + $participant = $value; + + // Try to get booking context from validation context + $root = $this->context->getRoot(); + $this->logger->info('Context root type', [ + 'root_type' => is_object($root) ? get_class($root) : gettype($root), + ]); + + if ($root instanceof BookingDto) { + $bookingContext = $root; + $this->logger->info('Successfully got BookingDto from root'); + } else { + // Root is likely a Form object - try to get data from it + if (method_exists($root, 'getData')) { + $bookingContext = $root->getData(); + $this->logger->info('Got data from Form object', [ + 'data_type' => is_object($bookingContext) ? get_class($bookingContext) : gettype($bookingContext), + ]); + } else { + $this->logger->warning('Cannot validate ParticipantDto - root has no getData method', [ + 'root_type' => is_object($root) ? get_class($root) : gettype($root), + ]); + return; + } + + if (!$bookingContext instanceof BookingDto) { + $this->logger->warning('Cannot validate ParticipantDto - data is not BookingDto', [ + 'data_type' => is_object($bookingContext) ? get_class($bookingContext) : gettype($bookingContext), + ]); + return; + } + } + } else { + $this->logger->info('BookingValidator skipping - unsupported type'); return; } - $participant = $value->participant; - $bookingContext = $value->bookingContext; - // Determine if strict validation applies - if (true === $this->shouldApplyStrictValidation($bookingContext)) { - $this->enforceStrictValidation($participant); + $shouldValidate = $this->shouldApplyStrictValidation($bookingContext); + $this->logger->info('Validation decision', [ + 'should_validate' => $shouldValidate, + 'mode' => $bookingContext->getMode(), + 'participant_index' => $participant->index ?? 'unknown', + ]); + + if (true === $shouldValidate) { + $this->enforceStrictValidation($participant, $propertyPathPrefix); } // Relaxed validation: no required checks, format validation handled by existing constraints @@ -89,36 +149,50 @@ class BookingValidator extends ConstraintValidator * - Address: street, postCode, city, country * - Services: skiPass, transportationOutbound, transportationInbound, insurance * - * @param ParticipantDto $participant The participant to validate + * @param ParticipantDto $participant The participant to validate + * @param string $propertyPathPrefix Prefix for property paths ('participant.' for wrapped DTO, '' for direct) */ - private function enforceStrictValidation(ParticipantDto $participant): void + private function enforceStrictValidation(ParticipantDto $participant, string $propertyPathPrefix = 'participant.'): void { // Skip validation for canceled participants if (true === $participant->isCanceled()) { + $this->logger->info('Skipping validation for canceled participant', [ + 'participant_index' => $participant->index, + ]); return; } + $this->logger->info('Enforcing strict validation', [ + 'participant_index' => $participant->index, + 'firstName' => $participant->firstName ?? 'null', + 'lastName' => $participant->lastName ?? 'null', + 'email' => $participant->email ?? 'null', + 'dateOfBirth' => $participant->dateOfBirth?->format('Y-m-d') ?? 'null', + 'skiPass' => $participant->skiPass?->id ?? 'null', + 'insurance' => $participant->insurance?->id ?? 'null', + ]); + // Personal data validation - $this->validateRequired($participant->firstName, 'participant.firstName', 'Bitte angeben'); - $this->validateRequired($participant->lastName, 'participant.lastName', 'Bitte angeben'); - $this->validateRequired($participant->email, 'participant.email', 'Bitte angeben'); + $this->validateRequired($participant->firstName, $propertyPathPrefix.'firstName', 'Bitte angeben'); + $this->validateRequired($participant->lastName, $propertyPathPrefix.'lastName', 'Bitte angeben'); + $this->validateRequired($participant->email, $propertyPathPrefix.'email', 'Bitte angeben'); if (null === $participant->dateOfBirth) { $this->context->buildViolation('Bitte angeben') - ->atPath('participant.dateOfBirth') + ->atPath($propertyPathPrefix.'dateOfBirth') ->addViolation(); } // Address validation only for applicant (index 0) if (0 === $participant->index) { if (null !== $participant->address) { - $this->validateRequired($participant->address->street, 'participant.address.street', 'Bitte angeben'); - $this->validateRequired($participant->address->postCode, 'participant.address.postCode', 'Bitte angeben'); - $this->validateRequired($participant->address->city, 'participant.address.city', 'Bitte angeben'); - $this->validateRequired($participant->address->country, 'participant.address.country', 'Bitte angeben'); + $this->validateRequired($participant->address->street, $propertyPathPrefix.'address.street', 'Bitte angeben'); + $this->validateRequired($participant->address->postCode, $propertyPathPrefix.'address.postCode', 'Bitte angeben'); + $this->validateRequired($participant->address->city, $propertyPathPrefix.'address.city', 'Bitte angeben'); + $this->validateRequired($participant->address->country, $propertyPathPrefix.'address.country', 'Bitte angeben'); } else { $this->context->buildViolation('Bitte angeben') - ->atPath('participant.address') + ->atPath($propertyPathPrefix.'address') ->addViolation(); } } @@ -126,25 +200,25 @@ class BookingValidator extends ConstraintValidator // Service validation if (null === $participant->skiPass) { $this->context->buildViolation('Bitte auswählen') - ->atPath('participant.skiPass') + ->atPath($propertyPathPrefix.'skiPass') ->addViolation(); } if (null === $participant->transportationOutbound) { $this->context->buildViolation('Bitte auswählen') - ->atPath('participant.transportationOutbound') + ->atPath($propertyPathPrefix.'transportationOutbound') ->addViolation(); } if (null === $participant->transportationInbound) { $this->context->buildViolation('Bitte auswählen') - ->atPath('participant.transportationInbound') + ->atPath($propertyPathPrefix.'transportationInbound') ->addViolation(); } if (null === $participant->insurance) { $this->context->buildViolation('Bitte auswählen') - ->atPath('participant.insurance') + ->atPath($propertyPathPrefix.'insurance') ->addViolation(); } } @@ -159,6 +233,11 @@ class BookingValidator extends ConstraintValidator private function validateRequired(mixed $value, string $path, string $message): void { if (null === $value || '' === trim((string) $value)) { + $this->logger->info('Adding violation for required field', [ + 'path' => $path, + 'message' => $message, + 'value' => $value ?? 'null', + ]); $this->context->buildViolation($message) ->atPath($path) ->addViolation();