Stwórzmy formularz kontaktowy

Przyjrzyjmy się jak stworzyć formularz kontaktowy w Nette, łącznie z wysłaniem go na maila. A więc do dzieła!

Najpierw musimy stworzyć nowy projekt. Jak wyjaśnia strona Getting Started. A następnie możemy przystąpić do tworzenia formularza.

Najprostszym sposobem jest stworzenie formularza bezpośrednio w Presenterze. Możemy skorzystać z gotowego HomePresenter. Dodamy komponent contactForm reprezentujący formularz. Zrobimy to wpisując metodę createComponentContactForm() factory do kodu, który będzie wytwarzał komponent:

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

class HomePresenter extends Presenter
{
	protected function createComponentContactForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:')
			->setRequired('Enter your name');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Enter your e-mail');
		$form->addTextarea('message', 'Message:')
			->setRequired('Enter message');
		$form->addSubmit('send', 'Send');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// sending an email
	}
}

Jak widać, stworzyliśmy dwie metody. Pierwsza metoda createComponentContactForm() tworzy nowy formularz. Ma on pola na imię, e-mail i wiadomość, które dodajemy za pomocą metod addText(), addEmail() i addTextArea(). Dodaliśmy również przycisk do wysłania formularza. Ale co jeśli użytkownik nie wypełni niektórych pól? W takim przypadku powinniśmy dać mu znać, że jest to pole wymagane. Zrobiliśmy to za pomocą metody setRequired(). Na koniec dodaliśmy również zdarzenie onSuccess, które jest wywoływane, jeśli formularz zostanie przesłany pomyślnie. W naszym przypadku wywołuje ono metodę contactFormSucceeded, która zajmuje się przetwarzaniem przesłanego formularza. Za chwilę dodamy to do kodu.

Niech komponent contantForm będzie renderowany w szablonie templates/Home/default.latte:

{block content}
<h1>Contant Form</h1>
{control contactForm}

Aby wysłać sam e-mail, tworzymy nową klasę o nazwie ContactFacade i umieszczamy ją w pliku 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') // your email
			->setFrom($email, $name)
			->setSubject('Message from the contact form')
			->setBody($message);

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

Metoda sendMessage() będzie tworzyć i wysyłać e-mail. Używa do tego tzw. mailera, którego przekazuje jako zależność poprzez konstruktor. Przeczytaj więcej o wysyłaniu e-maili.

Teraz wrócimy do prezentera i zakończymy pracę nad metodą contactFormSucceeded(). Wywołuje ona metodę sendMessage() klasy ContactFacade i przekazuje jej dane formularza. A jak otrzymamy obiekt ContactFacade? Będziemy mieli go przekazanego nam przez konstruktor:

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('The message has been sent');
		$this->redirect('this');
	}
}

Po wysłaniu maila pokazujemy użytkownikowi tzw. flash message, potwierdzający wysłanie wiadomości, a następnie przekierowujemy na następną stronę, aby nie można było ponownie wysłać formularza za pomocą refresh w przeglądarce.

No cóż, jeśli wszystko działa, powinieneś móc wysłać maila ze swojego formularza kontaktowego. Gratulacje!!!

Szablon HTML Email

Na razie wysyłany jest zwykły email tekstowy zawierający tylko wiadomość wysłaną przez formularz. Możemy jednak wykorzystać w mailu HTML i uatrakcyjnić go. Stworzymy do tego szablon w Latte, który zapiszemy w app/Model/contactEmail.latte:

<html>
	<title>Message from the contact form</title>

	<body>
		<p><strong>Name:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Message:</strong> {$message}</p>
	</body>
</html>

Pozostaje jeszcze zmodyfikować ContactFacade, aby wykorzystać ten szablon. W konstruktorze żądamy klasy LatteFactory, która może wyprodukować obiekt Latte\Engine, czyli renderer szablonu Latte. Używamy metody renderToString() do renderowania szablonu do pliku, pierwszy parametr to ścieżka do szablonu, a drugi to zmienne.

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') // your email
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Następnie przekazujemy wygenerowany e-mail HTML do metody setHtmlBody() zamiast oryginalnego setBody(). Nie musimy również określać tematu wiadomości w setSubject(), ponieważ biblioteka pobiera go z elementu <title> w szablonie.

Konfiguracja

W kodzie klasy ContactFacade nasz adminowy email admin@example.com jest wciąż hardcoded. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić?

Najpierw modyfikujemy klasę ContactFacade i zamieniamy ciąg email na zmienną przekazywaną przez konstruktor:

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 drugi krok to umieszczenie wartości tej zmiennej w konfiguracji. W pliku app/config/services.neon dodajemy:

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

I to wszystko. Jeśli w sekcji services jest dużo pozycji i mamy wrażenie, że mail się wśród nich gubi, możemy uczynić go zmienną. Zmodyfikujemy wpis na:

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

I zdefiniujemy tę zmienną w pliku app/config/common.neon:

parameters:
	adminEmail: admin@example.com

I gotowe!