Dodajemy formularz kontaktowy 5#
Sporo zmian, większa uniwersalność
W tym poście opisuję refaktoryzację naszego wcześniejszego formularza.
Jeśli trafiłeś tu bezpośrednio, zajrzyj do pierwszego postu, gdzie opisuję założenia i tworzę podstawową strukturę formularza.
- HTML + otwieranie / zamykanie JQuery
- Walidacja HTML5/JS + AJAX
- Walidacja PHP + Swiftmailer
- Dostępność formularza
- Zrefaktoryzowana wersja + Github
Zmiany
Refaktoryzacja objęła zarówno stronę frontendową, jak i backendową. Kod jest teraz bardziej uniwersalny, łatwiejszy do modyfikacji. Stworzyłem dla niego również osobne repozytorium na GitHubie, gdzie znajdziesz najaktualniejszą wersję.
Javascript
Pobieranie oraz walidacja danych przebiega teraz automatycznie. Aby dodać nowe pole do formularza wystarczy dorzucić label wraz z inputem, który musi mieć klasę form-data. Wymagany jest też atrybut name. Opcjonalnie możemy dodać reguły walidacji HTML, które zostaną sprawdzone, czyli, np. min/maxlenght, required itd. Kompletne pole wygląda tak:
Działa to w następujący sposób:
Elementy są pobierane automatycznie pętlą i tworzą obiekt JSON:
Dzięki temu możemy w wygodny sposób operować na naszych elementach z wykorzystaniem pętli.
Używam tu anonimowych funkcji strzałkowych ( () => {} ), jest to składnia ES6. Są odpowiednikiem funkcji anonimowej w tradycyjnym zapisie: function () {}
Nasze komunikaty o błędach teraz przechowywane są w jednym obiekcie JSON, aby można było je łatwiej modyfikować, a co najważniejsze, tylko w jednym miejscu. Dodatkowo posługuję się funkcjami strzałkowymi, które w czytelny sposób budują komunikat ze zmiennych podanych w ich parametrach.
Mniejsza zmiana dotknęła zamykania formularza, gdzie teraz wszystkie pola są czyszczone przez pętlę, a nie ręcznie:
oraz również wartości inputów do wysłania pobierane są pętlą (z wyjątkiem recaptcha):
Większa zmiana dotknęła też walidacji. Teraz sprawdzane jest każde pole osobno i komunikaty o błędach pojawią się pod każdym z nich. Nie tylko jeden pod przyciskiem “Wyślij”, tak jak było to wcześniej. Tam teraz pojawia się tylko ogólna informacja o (nie)powodzeniu wysłania wiadomości.
Wszystkie błędy, które przyjdą od strony PHP w postaci JSON, oznaczane są automatycznie z pomocą pętli:
Podobnie wygląda walidacja po stronie JS. Tu każde pole jest sprawdzane za pomocą HTML5 validation API dla określonych reguł oraz generowane są wcześniej utworzone przez nas komunikaty.
Zmianie uległa także nasza funkcja oznaczająca niepoprawne pola. Teraz tworzy i dodaje komunikat pod niepoprawnym polem.
I jak widać, odseparowałem funkcję do czyszczenia błędów, która jest uruchamiana, gdy pole uzyska focus:
Osobno na callbacku od recaptchy musiałem podpiąć dla niej focus (tabindex wymagany), co aktywuje funkcję czyszczącą komunikat (clearErrors):
PHP
Tu też sporo zmian. Podpiąłem Composera do zarządzania zależnościami. Do walidacji stworzona jest funkcja korzystająca z zewnętrznego narzędzia do walidacji danych: Respect Validation. Błędy przechowywane są teraz w tablicy, a email zostanie wysłany tylko wtedy, gdy będzie pusta:
function validateContactForm(array $form): array
{
$errors = [];
$rules = [
'userEmail' => (new Validator())->addRules([new NotEmpty(), new Email()]),
'subject' => (new Validator())->addRules([new NotEmpty(), new StringType(), new Length(4, 78)]),
'message' => (new Validator())->addRules([new NotEmpty(), new StringType(), new Length(8, 6000)]),
];
$validationMessages = (require_once __DIR__ . '/settings.php')['validationMessages'];
foreach ($rules as $key => $validator) {
/** @var $validator Validator */
try {
$validator->setName($key)->assert($form[$key] ?? null);
} catch (NestedValidationException $exception) {
$exception->findMessages($validationMessages);
$errors[$key] = $exception->getMessages();
}
}
if (validateReCaptcha($form['g-recaptcha-response'] ?? '') === false) {
$errors['recaptcha'][] = "Potwierdź, że nie jesteś robotem!";
}
return $errors;
}
Plik konfiguracyjny settings.php został wzbogacony o treści komunikatów, które są podmieniane z tymi z RespectValidation:
return [
'reCaptcha' => [
'secret' => '',
],
'mailer' => [
'host' => '',
'port' => '',
'username' => '',
'password' => '',
'email' => '',
],
'validationMessages' => [
'stringType'=> 'Musi być typu string',
'length' => 'Musi zawierać od do znaków',
'email' => 'Niepoprawny email',
'notEmpty' => 'Pole nie może być puste',
'NotSent' => 'Coś poszło nie tak :(',
'Sent' => 'Wysłano! Dzięki za wiadomość'
],
];
Cały kod został również podzielony na osobne funkcje, z drobnymi modyfikacjami:
function validateReCaptcha(string $code): bool
{
$url = 'https://www.google.com/recaptcha/api/siteverify?' . http_build_query([
'secret' => (require __DIR__ . '/settings.php')['reCaptcha']['secret'],
'response' => $code,
]);
$content = file_get_contents($url);
$response = json_decode($content, true);
return $response['success'];
}
function getMailer(): Swift_Mailer
{
$config = (require __DIR__ . '/settings.php')['mailer'];
$transport = new Swift_SmtpTransport($config['host'], $config['port']);
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
return new Swift_Mailer($transport);
}
function prepareMail(array $params): Swift_Message
{
$config = (require __DIR__ . '/settings.php')['mailer'];
$mail = new Swift_Message(
$params['subject'],
$params['message'],
'text/plain',
'UTF-8'
);
$mail->setFrom($params['userEmail']);
$mail->setReplyTo($params['userEmail']);
$mail->setTo($config['email']);
return $mail;
}
function sendMail(array $params): bool
{
$mailer = getMailer();
return $mailer->send(prepareMail($params));
}
A obsługa wysłania formularza znajduję się w osobnym pliku ajaxsend.php:
require_once __DIR__ . '/functions.php';
if ($errors = validateContactForm($_POST)) {
http_response_code(400);
header('Content-Type: application/json');
echo json_encode(['errors' => $errors]);
} else {
if (sendMail($_POST)) {
http_response_code(200);
echo json_encode(['status' => (require __DIR__ . '/settings.php')['validationMessages']['Sent']]);
} else {
http_response_code(500);
echo json_encode(['status' => (require __DIR__ . '/settings.php')['validationMessages']['NotSent']]);
}
}
Całość prezentuję się w następujący sposób: