wip: email uniqueness validation

This commit is contained in:
Björn Fromme
2026-03-16 11:59:11 +01:00
parent 54a3145710
commit 89979d6ce4
11 changed files with 549 additions and 783 deletions
@@ -1,138 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\Form\Model\BookingDto;
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
/**
* Handles email uniqueness validation for booking participants.
*
* This handler provides real-time feedback during form refresh when an adult
* participant enters an email address that is already used by another adult
* participant in the same booking. Children (under 16) are exempt from this
* validation and can share email addresses with adults or other children.
*
* The handler adds warning notifications during the HTMX refresh cycle,
* providing immediate user feedback without blocking form submission.
* Hard validation is enforced via BookingDto::validateEmailUniqueness()
* when the user attempts to proceed to the next step.
*/
class ParticipantEmailFieldHandler extends AbstractParticipantFieldHandler
{
/**
* Returns the form field name this handler processes.
*
* @return string The field name 'email'
*/
public function getFieldName(): string
{
return 'email';
}
/**
* Returns the field dependencies for proper processing order.
*
* Email validation has no dependencies on other fields.
*
* @return string[] Empty array (no dependencies)
*/
public function getDependencies(): array
{
return [];
}
/**
* Determines if this handler should process the field based on submitted data.
*
* Email validation should always run when the email field is present in
* the submitted data, regardless of booking mode or participant index.
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param string $mode The booking mode (BookingDto::MODE_CREATE or MODE_EDIT)
* @param int $participantIndex The index of the participant being processed
*
* @return bool True if email field exists in submitted data
*/
public function shouldProcess(array $submittedData, string $mode, int $participantIndex): bool
{
return array_key_exists($this->getFieldName(), $submittedData);
}
/**
* Processes the email field for a specific participant.
*
* This method checks if the participant's email address is already used by
* another adult participant in the same booking. If a duplicate is found,
* a warning notification is added to the participant for display as a toast.
*
* Validation rules:
* - Children (under 16 at current date) are exempt from uniqueness validation
* - Null or empty emails are skipped (handled by Symfony's @Assert\Email)
* - Only duplicates with other adult participants trigger warnings
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param BookingDto $bookingDto The booking DTO to update (create or edit)
* @param int $participantIndex The index of the participant being processed
*/
public function processField(array $submittedData, BookingDto $bookingDto, int $participantIndex): void
{
// Safely get the participant object, returning early if not found
$participant = $this->getParticipant($bookingDto, $participantIndex);
if (null === $participant) {
return;
}
// Skip validation for children (they can share email addresses)
if (true === $participant->isChild()) {
return;
}
// Get the email value from submitted data (field handlers run in PRE_SUBMIT, before form binding)
$email = $submittedData[$this->getFieldName()] ?? null;
// Skip if email is null or empty (handled by other validators)
if (null === $email || '' === trim($email)) {
return;
}
// Normalize email for comparison (case-insensitive)
$normalizedEmail = strtolower(trim($email));
// Check for duplicate emails among other adult participants
$hasDuplicate = false;
foreach ($bookingDto->participants as $index => $otherParticipant) {
// Skip comparing with self
if ($index === $participantIndex) {
continue;
}
// Skip if other participant is a child (children can share emails)
if (true === $otherParticipant->isChild()) {
continue;
}
// Skip if other participant has no email
if (null === $otherParticipant->email || '' === trim($otherParticipant->email)) {
continue;
}
// Compare normalized emails
$otherNormalizedEmail = strtolower(trim($otherParticipant->email));
if ($normalizedEmail === $otherNormalizedEmail) {
$hasDuplicate = true;
break;
}
}
// Add warning notification if duplicate found
if (true === $hasDuplicate) {
$participant->addNotification(
'warning',
'Diese E-Mail Adresse wird bereits von einem anderen erwachsenen Teilnehmer verwendet'
);
}
}
}