Toto riešenie obsahuje všetky potrebné služby v
docker-compose.yml
. Po ich spustení sa vytvorí:
- webový server, ktorý do document root namapuje adresár tejto úlohy a nastaví php mail funkciu tak, aby odosielala maily do pripraveného MailHog servera. Port 80 bude dostupný na adrese http://localhost/. Server má pridaný modul pre ladenie Xdebug 3 nastavený na port 9000.
- MailHog server, ktorý sa automaticky prepojí s PHP na porte 8025 a bude dostupný na adrese http://localhost:8025/
- V prípade potreby priamého prístupu na SMTP server je tento dostupný na adrese mailhog:1025
Samotné riešenie je rozdelené do niekoľkých častí.
Kontaktný formulár si pripravíme v HTML súbore index.php
. Bude obsahovať položky pre meno, email a pole pre napísanie správy.
<div class="contact-form">
<form method="POST">
<label for="name">Meno</label>
<input type="text" id="name" name="name" placeholder="Vaše meno">
<label for="email">Emailová adresa</label>
<input type="email" id="email" name="email" placeholder="some@mail.sk">
<label for="content">Správa</label>
<textarea id="content" name="content" placeholder="Text správy..."></textarea>
<input type="submit" value="Odošli">
</form>
</div>
Meno je textové pole. Email je tiež textové pole, ktorému sme nastavili atribút type="email"
. Na text správy sme použili element textarea
, ktorý umožňuje napísať viac riadkov textu. Ďalšou časťou je definícia základného CSS, ktoré umožní zobraziť formulár tak, ako bol definovaný v zadaní. Samotný formulár má nastavený atribút method="POST"
, čo znamená, že dáta budú odosielané HTTP metódou POST. Okrem toho sme nikde nešpecifikovali atribút action
, takže tento formulár sa odošle na rovnakú URL adresu, na ktorej sa nachádza.
.contact-form input, .contact-form label, .contact-form textarea {
display: block;
}
.contact-form label {
margin-top: 10px;
}
.contact-form textarea {
height: 200px;
}
Každému prvku tohto nášho formulára sme nastavili display: block
preto, aby sme mali jednotlivé elementy zobrazené pekne pod sebou. Okrem toho sme pridali ďalšie štýlovanie, nastavili sme pomocou margin-top
rozostup medzi prvkami a pomocou height
sme nastavili predvolenú výšku poľa na text správy.
Pri nesprávnych hodnotách je potrebné preskočiť posielanie emailu a vrátiť používateľovi formulár späť, aj s informáciou o chybách. Keďže formulár odosielame na rovnakú adresu, kde sa aktuálne nachádza, môžeme pridať validáciu na začiatok tohto súboru.
Pre jednoduchosť príkladu budeme uvažovať, že nasledovný kód je v súbore index.php
, v ktorom sa aktuálne nachádza aj HTML kód formuláru. Pri zložitejšej aplikácii je vhodné určité spoločné funkcionality oddeliť do samostatných súborov, a tieto vkladať do stránky pomocou príkazov include
alebo require
.
Validácia v našom prípade môže vyzerať nasledovne:
$errors = [];
$isPost = $_SERVER['REQUEST_METHOD'] == "POST";
if ($isPost) {
$name = trim($_POST['name']);
if (empty($name)) {
$errors['name'] = "Meno musí byť zadané.";
}
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if (!$email) {
$errors['email'] = "Emailová adresa nie je platná.";
}
$content = trim($_POST['content']);
if (empty($content)) {
$errors['content'] = "Musíte vyplniť správu.";
}
}
Samotná validácia pozostáva z týchto krokov. Na začiatku si deklarujeme premennú, do ktorej budeme ukladať validačné chyby. Túto premennú sme si deklarovali ako pole. V ďalšom kroku overíme, či ide o POST
žiadosť. Na overenie môžeme použiť viacero postupov. V závislosti od použitého servera a konfigurácie nemusia všetky fungovať. Superglobálna premenná $_SERVER
obsahuje pod kľúčom REQUEST_METHOD
typ HTTP požiadavky. Tento spôsob bude fungovať vždy. V našom prípade overíme, či $_SERVER['REQUEST_METHOD']
obsahuje hodnotu POST
.
Ďalej nasleduje validácia hodnôt jednotlivých častí formulára. Pri všetkých validáciách používame rovnaký vzor. Najskôr si zo superglobalného poľa $_POST
načítame hodnotu parametra a vyfiltrujeme ju. Pri mene a obsahu používame funkciu trim()
, ktorá odstráni začiatočné a koncové prázdne znaky. Následne overíme či meno, alebo správa nie sú prázdne pomocou funkcie empty()
. Ak je niektoré pole prázdne, tak do poľa chýb $errors
uložíme pod kľúčom danej premennej textový popis chyby.
$name = trim($_POST['name']);
if (empty($name)) {
$errors['name'] = "Meno musí byť zadané.";
}
Takýmto spôsobom môžeme postupne validovať aj ďalšie požiadavky. Ak by sme napríklad chceli, aby meno začínalo veľkým písmenom, môžeme dopísať ďalšiu podmienku, ktorá pomocou funkcie ctype-upper()
skontroluje prvý znak mena, a ak to nie je veľké písmeno, tak priradí chybovú hlášku.
Validácia emailovej adresy je trochu komplikovanejšia. V tomto prípade sme potrebovali overiť, či ide o skutočný email. Možností je niekoľko. Môžeme využiť napríklad regulárne výrazy. V PHP existuje funkcia preg_match()
, pomocou ktorej vieme skontrolovať, či reťazec spĺňa regulárny výraz. Samotný regulárny výraz na kontrolu emailovej adresy by mohol vyzerať nasledovne:
/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/`
Tento regulárny výraz ani z ďaleka nepokrýva všetky možné prípady platnej emailovej adresy podľa RFC2822. Preto sa validácia emailovej adresy pomocou tohto prístupu vo všeobecnosti neodporúča, ak by sme chceli, aby adresa bola presne podľa štandardu.
V PHP existuje funkcia filter_var()
, ktorá umožní pohodlnú kontrolu aj emailovej adresy. V našom prípade sme použili zápis filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
, ktorý zoberie hodnotu emailovej adresy z $_POST
a zvaliduje ju ako emailovú adresu. Funkcia filter_var()
vráti hodnotu, ktorú jej zadáme ako parameter, v prípade ak táto spĺňa všetky filtračné pravidlá. Ak nespĺňa, tak funkcia vráti false
.
Samotnú kontrolu by sme mali hotovú. Ak je formulár nesprávne vyplnený, potrebujeme ho zobraziť znovu a pod každý element input
, ktorý obsahuje chybu, chceme dopísať chybovú správu. Opätovné zobrazenie formulára nepredstavuje žiadny problém, nakoľko sa formulár odosiela na tú istú URL adresu, takže sa zobrazí aj v prípade POST
žiadosti. Pre zjednodušenie výpisu chybovej hlášky si deklarujeme pomocnú funkciu printErrorMessage()
, ktorá dostane ako parameter pole s chybami a názov input
elementu. V prípade, že obsahuje toto pole s chybami chybovú správu pre daný input
element, vráti HTML kód chybovej správy.
function printErrorMessage($errors, $key) : string
{
if (isset($errors[$key])) {
return "<span class='form-error'>{$errors[$key]}</span>";
}
return "";
}
Následné použitie tejto funkcie na výpis chybových hlásení bude nasledovné:
<label for="name">Meno</label>
<input type="text" id="name" name="name" placeholder="Vaše meno">
<?=printErrorMessage($errors, "name")?>
Pre úplnosť uvedieme ešte CSS pravidlo pre naštýlovanie tejto chybovej hlášky:
.form-error {
color: red;
font-size: small;
}
Toto riešenie zobrazí chyby v nasledovnom formáte:
Veľkým problémom tohto riešenia je to, že po odoslaní formuláru sa stratia vyplnené údaje, to znamená, že ak náhodou zle vyplníme emailovú adresu, tak prídeme nie len o tú, ale aj o celú správu. Takéto správanie aplikácie je neprípustné. Tento problém sa ale dá celkom jednoducho vyriešiť. HTML input
element má atribút value
, pomocou ktorého mu môžeme nastaviť hodnotu. Základný kód by mohol vyzerať nasledovne:
<input type="text" id="name" name="name" placeholder="Vaše meno" value="<?=$_POST['name']?>">
Pokiaľ teraz znovu načítame formulár, tak sa nám zobrazí chybne:
Celý element input
pre meno je aktuálne rozbitý. Ak chceme vedieť prečo, pozrieme sa do vygenerovaného HTML:
<input type="text" id="name" name="name" placeholder="Vaše meno" value="<br />
<b>Warning</b>: Undefined array key "name" in <b>/var/www/html/index.php</b> on line <b>75</b><br />
">
Ako môžeme vidieť, do atribútu value
sa vygenerovala php chybová hláška:
Warning: Undefined array key "name" in /var/www/html/index.php on line 74
Táto chyba ukazuje dva problémy tohto prístupu. Prvým je nesprávne použitie superglobálnej premennej $_POST
, ktorá obsahuje hodnoty len v prípade prijatia POST žiadosti. Druhý problém je problém escaping (zmena významu znakov) - ak naša hodnota obsahuje HTML kód, tak nám tento kód môže rozbiť celý formulár.
Na ošetrenie tohto problému si pripravíme pomocnú funkciu getParam()
:
function getParam($name) : string|null
{
if (isset($_POST[$name])) {
return htmlspecialchars(trim($_POST[$name]), ENT_QUOTES);
}
else {
return null;
}
}
Táto funkcia rieši oba spomenuté problémy. Pomocou funkcie isset()
v nej kontrolujeme, či danú hodnotu máme k dispozícii. Ak máme, tak pomocou funkcie htmlspecialchars()
zmeníme všetky HTML značky tak, aby sa právne zobrazili. Môžeme si všimnúť, že sme funkciu použili s parametrom ENT_QUOTES
, ktorý podľa dokumentácie okrem HTML ošetruje aj úvodzovky, čo je v prípade HTML atribútov potrebné.
Následné použitie už bude veľmi jednoduché:
<input type="text" id="name" name="name" placeholder="Vaše meno" value="<?=getParam('name')?>">
V prípade poľa na písanie správy, ktoré využíva element textarea
bude syntax mierne odlišná:
<textarea id="content" name="content" placeholder="Text správy..."><?=getParam('content')?></textarea>
Po úspešnej validácii môžeme odoslať email. V PHP nám na to poslúži funkcia mail()
. Na to, aby bolo možné odosielať emaily, musí byť správne nakonfigurovaný aj mailový server. Na lokálnom serveri to nemusí vždy fungovať. V prípade využitia priloženého docker-compose.yml
sa nám na lokálnom serveri sprístupní aplikácia MailHog, ktorá bude všetky odoslané emaily z PHP odchytávať a zobrazovať v prehľadnom používateľskom rozhraní, čo umožní rýchlejší vývoj aplikácie.
Samotné odoslanie emailu vykonáme vtedy, keď odošleme formulár a prebehne validácia. Ak nenarazíme na žiadne chyby, odošleme email:
if ($isPost) {
...
if (empty($errors)) {
mail("kontaktna.adresa@mojmail.sk", "Sprava z kontaktneho formulara", $content, "From: {$name}<{$email}>");
}
}
Prvým parametrom tejto funkcie je adresa príjemcu. V našom prípade chceme, aby správa z kontaktného formulára prišla na náš mail, tak tam vyplníme svoju adresu. Druhým parametrom je predmet správy, tretím telo a posledným sú hlavičky emailu. V našom príklade tam nastavíme hlavičku From
, pomocou ktorej sa nastavuje odosielateľ emailu.
Výsledný email vyzerá po zachytení aplikáciou MailHog nasledovne:
V aktuálnom stave aplikácia po odoslaní emailu opäť zobrazí formulár. Chceli by sme to upraviť tak, aby sme po úspešnom odoslaní emailu dostali informáciu, že správa bola odoslaná.
Túto úpravu spravíme jednoducho tak, že formulár zaobalíme do príkazu if
.
<?php if ($isPost && empty($errors)) { ?>
Ďakujeme za vašu správu.
<?php } else { ?>
<div class="contact-form">
...
</div>
<?php } ?>
Ak sme odoslali formulár a nemáme žiadnu validačnú chybu, tak zobrazíme správu "Ďakujeme za vašu správu." V opačnom prípade zobrazíme opäť kontaktný formulár.
Prezentovaný HTML formulár obsahuje len jednoduchú validáciu. Pre reálne nasadenie by bolo vhodné pridať ďalšie validačné pravidlá napr. na obsah správy. Aktuálny formulár neobsahuje žiadnu ochranu proti robotom (botom), takže takýto formulár môže spôsobiť záplavu spamu v emailovej schránke. Bolo by pre to vhodné implementovať nejakú ochranu proti robotom - napríklad zobrazenie formuláru až po prihlásení alebo použitie systému reCAPTCHA od Google.
V našom príklade sme nastavili mail tak, že u príjemcu to vyzerá tak, že mail bol odoslaný z adresy, ktorú zadal používateľ v kontaktnom formulári. Takéto emaily ale v dnešnej dobe väčšina hostingových providerov nepodporuje a vyžadujú, aby maily boli odosielané z domény, na ktorej daný web beží. V tomto prípade vieme upraviť odosielanie tak, aby mail odišiel z adresy, ktorá patrí danému webu, ale zároveň, aby sme nestratili kontakt na odosielajúceho používateľa.
mail(
"kontaktna.adresa@mojmail.sk",
"Sprava z kontaktneho formulara",
"Odosielateľ: $name<$email>\n$content",
"From: my@myserver.sk\r\nReply-To: $name<$email>");
Takto odoslaná správa bude vyzerať nasledovne:
Do tela správy sme doplnili meno odosielateľa a pomocou hlavičky Reply-To
sme špecifikovali adresu pre odpoveď. Vďaka tomu, keď nám príde správa z kontaktného formulára a v mailovom klientovi naň odpovieme, tak táto odpoveď pôjde automaticky na adresu nášho používateľa a nie na adresu nášho servera (my@myserver.sk
), ktorý je špecifikovaný ako odosielateľ.