Vytváříme kontaktní formulář

Podíváme se na to, jak v Nette vytvořit kontaktní formulář včetně odesílání na email. Tak tedy do toho!

Nejprve musíme vytvořit nový projekt. Jak na to vysvětluje stránka Začínáme. A pak už můžeme začít s tvorbou formuláře.

Nejjednodušší je vytvoření formuláře přímo v presenteru. Můžeme využít předpřipravený HomePresenter. Do něj přidáme komponentu contactForm představující formulář. Uděláme to tak, že do kódu zapíšeme tovární metodu createComponentContactForm(), která komponentu vyrobí:

use Nette\Application\UI\Form;
use Nette\Application\UI\Presenter;

class HomePresenter extends Presenter
{
	protected function createComponentContactForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Jméno:')
			->setRequired('Zadejte jméno');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Zadejte e-mail');
		$form->addTextarea('message', 'Zpráva:')
			->setRequired('Zadejte zprávu');
		$form->addSubmit('send', 'Odeslat');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// odeslání emailu
	}
}

Jak vidíte, vytvořili jsme dvě metody. První metoda createComponentContactForm() vytváří nový formulář. Ten má políčka pro jméno, email a zprávu, která přidáváme metodami addText(), addEmail() a addTextArea(). Také jsme přidali tlačítko pro odeslání formuláře. Ale co když uživatel nevyplní nějaké pole? V takovém případě bychom mu měli dát vědět, že je to povinné pole. Toho jsme docílili metodou setRequired(). Nakonec jsme přidali také událost onSuccess, která se spustí, pokud je formulář úspěšně odeslán. V našem případě zavolá metodu contactFormSucceeded, která se postará o zpracování odeslaného formuláře. To do kódu doplníme za okamžik.

Komponentu contantForm necháme vykreslit v šabloně templates/Home/default.latte:

{block content}
<h1>Kontantní formulář</h1>
{control contactForm}

Pro samotné odeslání emailu vytvoříme novou třídu, kterou nazveme ContactFacade a umístíme ji do souboru app/Model/ContactFacade.php:

<?php
declare(strict_types=1);

namespace App\Model;

use Nette\Mail\Mailer;
use Nette\Mail\Message;

class ContactFacade
{
	public function __construct(
		private Mailer $mailer,
	) {
	}

	public function sendMessage(string $email, string $name, string $message): void
	{
		$mail = new Message;
		$mail->addTo('admin@example.com') // váš email
			->setFrom($email, $name)
			->setSubject('Zpráva z kontaktního formuláře')
			->setBody($message);

		$this->mailer->send($mail);
	}
}

Metoda sendMessage() vytvoří a odešle email. Využívá k tomu tzv. mailer, který si nechá předat jako závislost přes konstruktor. Přečtete si více o odesílání emailů.

Nyní se vrátíme zpátky k presenteru a dokončíme metodu contactFormSucceeded(). Ta zavolá metodu sendMessage() třídy ContactFacade a předá jí údaje z formuláře. A jak získáme objekt ContactFacade? Necháme si jej předat konstruktorem:

use App\Model\ContactFacade;
use Nette\Application\UI\Form;
use Nette\Application\UI\Presenter;

class HomePresenter extends Presenter
{
	public function __construct(
		private ContactFacade $facade,
	) {
	}

	protected function createComponentContactForm(): Form
	{
		// ...
	}

	public function contactFormSucceeded(stdClass $data): void
	{
		$this->facade->sendMessage($data->email, $data->name, $data->message);
		$this->flashMessage('Zpráva byla odeslána');
		$this->redirect('this');
	}
}

Poté, co se email odešle, ještě zobrazíme uživateli tzv. flash message, potvrzující, že zpráva se odeslala, a poté přesměrujeme na další stránku, aby nebylo možné formulář opakovaně odeslat pomocí refresh v prohlížeči.

Tak, a pokud všechno funguje, měli byste být schopni odeslat email z vašeho kontaktního formuláře. Gratuluji!

HTML šablona emailu

Zatím se odesílá prostý textový email obsahující pouze zprávu odeslanou formulářem. V emailu ale můžeme využít HTML a udělat jeho podobu atraktivnější. Vytvoříme pro něj šablonu v Latte, kterou zapíšeme do app/Model/contactEmail.latte:

<html>
	<title>Zpráva z kontaktního formuláře</title>

	<body>
		<p><strong>Jméno:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Zpráva:</strong> {$message}</p>
	</body>
</html>

Zbývá upravit ContactFacade, aby tuto šablonu používal. V konstruktoru si vyžádáme třídu LatteFactory, která umí vyrobit objekt Latte\Engine, tedy vykreslovač Latte šablon. Pomocí metody renderToString() šablonu vykreslíme do souboru, prvním parametrem je cesta k šabloně a druhým jsou proměnné.

namespace App\Model;

use Nette\Bridges\ApplicationLatte\LatteFactory;
use Nette\Mail\Mailer;
use Nette\Mail\Message;

class ContactFacade
{
	public function __construct(
		private Mailer $mailer,
		private LatteFactory $latteFactory,
	) {
	}

	public function sendMessage(string $email, string $name, string $message): void
	{
		$latte = $this->latteFactory->create();
		$body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [
			'email' => $email,
			'name' => $name,
			'message' => $message,
		]);

		$mail = new Message;
		$mail->addTo('admin@example.com') // váš email
			->setFrom($email, $name)
			->setHtmlBody($body);

		$this->mailer->send($mail);
	}
}

Vygenerovaný HTML email pak předáme metodě setHtmlBody() místo původní setBody(). Taktéž nemusíme uvádět předmět emailu v setSubject(), protože si jej knihovna vezme z elementu <title> šablony.

Konfigurace

V kódu třídy ContactFacade je pořád natvrdo zapsaný náš administrátorský email admin@example.com. Bylo by lepší jej přesunout do konfiguračního souboru. Jak na to?

Nejprve upravíme třídu ContactFacade a řetězec s emailem nahradíme proměnnou předanou konstruktorem:

class ContactFacade
{
	public function __construct(
		private Mailer $mailer,
		private LatteFactory $latteFactory,
		private string $adminEmail,
	) {
	}

	public function sendMessage(string $email, string $name, string $message): void
	{
		// ...
		$mail = new Message;
		$mail->addTo($this->adminEmail)
			->setFrom($email, $name)
			->setHtmlBody($body);
		// ...
	}
}

A druhým krokem je uvedení hodnoty této proměnné v konfiguraci. Do souboru app/config/services.neon zapíšeme:

services:
	- App\Model\ContactFacade(adminEmail: admin@example.com)

A je to. Pokud by položek v sekci services bylo hodně a měli byste pocit, že email se mezi nimi ztrácí, můžeme z něj udělat proměnnou. Upravíme zápis na:

services:
	- App\Model\ContactFacade(adminEmail: %adminEmail%)

A v souboru app/config/common.neon nadefinujeme tuto proměnnou:

parameters:
	adminEmail: admin@example.com

A je hotovo!