wip: email uniqueness validation
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
/**
|
||||
* Wrapper DTO for editing a participant within a booking context.
|
||||
*
|
||||
* This wrapper enables clean validation of participant email uniqueness
|
||||
* without requiring back-references or form coupling. It contains both
|
||||
* the participant being edited and the full booking context needed for
|
||||
* cross-participant validation.
|
||||
*/
|
||||
class ParticipantEditDto
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\Valid]
|
||||
public readonly ParticipantDto $participant,
|
||||
public readonly BookingDto $bookingContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that adult participants have unique email addresses.
|
||||
*
|
||||
* Children (under 16) are exempt from this validation and can share
|
||||
* email addresses with adults. Email comparison is case-insensitive
|
||||
* and whitespace is normalized.
|
||||
*/
|
||||
#[Assert\Callback(groups: ['booking_create', 'booking_edit'])]
|
||||
public function validateEmailUniqueness(ExecutionContextInterface $context): void
|
||||
{
|
||||
// Skip validation for children (under 16)
|
||||
if (true === $this->participant->isChild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if email is null or empty (handled by @Email and @NotBlank constraints)
|
||||
if (null === $this->participant->email || '' === trim($this->participant->email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize current participant's email for comparison
|
||||
$normalizedEmail = strtolower(trim($this->participant->email));
|
||||
|
||||
// Check against all adult participants in booking context
|
||||
foreach ($this->bookingContext->participants as $index => $otherParticipant) {
|
||||
// Skip self-comparison (same participant index)
|
||||
if ($index === $this->participant->index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip children (they can share email addresses)
|
||||
if (true === $otherParticipant->isChild()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip null or empty emails
|
||||
if (null === $otherParticipant->email || '' === trim($otherParticipant->email)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare normalized emails
|
||||
$otherNormalizedEmail = strtolower(trim($otherParticipant->email));
|
||||
|
||||
if ($normalizedEmail === $otherNormalizedEmail) {
|
||||
// Add violation to participant.email path
|
||||
$context->buildViolation('Diese E-Mail Adresse wird bereits von einem anderen Teilnehmer verwendet')
|
||||
->atPath('participant.email')
|
||||
->addViolation();
|
||||
|
||||
// Stop after first duplicate found (no need to report multiple times)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user