wip: email uniqueness validation

This commit is contained in:
Björn Fromme
2025-10-23 09:29:27 +02:00
parent 60176d19f4
commit 3e54a58b9b
11 changed files with 549 additions and 783 deletions
+81
View File
@@ -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;
}
}
}
}