fix: ensure validation of participant data

This commit is contained in:
Björn Fromme
2026-03-16 11:59:11 +01:00
parent f3ea6734a1
commit 5275e6c732
2 changed files with 101 additions and 21 deletions
+1
View File
@@ -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
{
/**
+100 -21
View File
@@ -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();