Files
myep/src/Validator/Constraints/BookingValidator.php
T

168 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Validator\Constraints;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Model\ParticipantEditDto;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates required fields with mode-aware and mutability-aware rules.
*
* Validation Strategy:
* - Create mode: STRICT (all fields required + format validation)
* - Edit mode + applicant immutable: STRICT (all fields required + format validation)
* - Edit mode + applicant mutable: RELAXED (only format validation, fields optional)
*
* The applicant's (index 0) mutability flag determines validation strictness for ALL participants.
* This ensures consistent validation across the entire booking based on whether the booking
* can be modified freely or has restrictions imposed by the booking system.
*
* Strict Validation (Create + Edit Immutable):
* - Personal data: firstName, lastName, dateOfBirth, email, address (all subfields) - REQUIRED
* - Services: skiPass, transportationOutbound, transportationInbound, insurance - REQUIRED
* - Format constraints (Email, date formats) also apply
*
* Relaxed Validation (Edit Mutable):
* - All fields OPTIONAL (can be null/empty)
* - Format constraints still apply IF values are provided
* - Allows partial updates without forcing complete data re-entry
*/
class BookingValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
if (!$value instanceof ParticipantEditDto) {
return;
}
$participant = $value->participant;
$bookingContext = $value->bookingContext;
// Determine if strict validation applies
if (true === $this->shouldApplyStrictValidation($bookingContext)) {
$this->enforceStrictValidation($participant);
}
// Relaxed validation: no required checks, format validation handled by existing constraints
}
/**
* Determines whether strict validation should be applied.
*
* Strict validation applies when:
* 1. In create mode (new booking), OR
* 2. In edit mode AND applicant is not mutable (booking has restrictions)
*
* @param BookingDto $bookingContext The booking context
*
* @return bool True if strict validation should apply
*/
private function shouldApplyStrictValidation(BookingDto $bookingContext): bool
{
$mode = $bookingContext->getMode();
// Create mode: always strict
if (BookingDto::MODE_CREATE === $mode) {
return true;
}
// Edit mode: check applicant mutability
$applicant = $bookingContext->participants[0] ?? null;
if (null === $applicant) {
return true; // Fail-safe: if no applicant, apply strict validation
}
// Strict validation if applicant is not mutable
return false === $applicant->mutable;
}
/**
* Enforces strict validation: all required fields must be filled.
*
* Validates:
* - Personal data: firstName, lastName, dateOfBirth, email
* - Address: street, postCode, city, country
* - Services: skiPass, transportationOutbound, transportationInbound, insurance
*
* @param ParticipantDto $participant The participant to validate
*/
private function enforceStrictValidation(ParticipantDto $participant): void
{
// Skip validation for canceled participants
if (true === $participant->isCanceled()) {
return;
}
// 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');
if (null === $participant->dateOfBirth) {
$this->context->buildViolation('Bitte angeben')
->atPath('participant.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');
} else {
$this->context->buildViolation('Bitte angeben')
->atPath('participant.address')
->addViolation();
}
}
// Service validation
if (null === $participant->skiPass) {
$this->context->buildViolation('Bitte auswählen')
->atPath('participant.skiPass')
->addViolation();
}
if (null === $participant->transportationOutbound) {
$this->context->buildViolation('Bitte auswählen')
->atPath('participant.transportationOutbound')
->addViolation();
}
if (null === $participant->transportationInbound) {
$this->context->buildViolation('Bitte auswählen')
->atPath('participant.transportationInbound')
->addViolation();
}
if (null === $participant->insurance) {
$this->context->buildViolation('Bitte auswählen')
->atPath('participant.insurance')
->addViolation();
}
}
/**
* Validates that a field is not empty (null or blank string).
*
* @param mixed $value The field value to check
* @param string $path The property path for the violation
* @param string $message The violation message
*/
private function validateRequired(mixed $value, string $path, string $message): void
{
if (null === $value || '' === trim((string) $value)) {
$this->context->buildViolation($message)
->atPath($path)
->addViolation();
}
}
}