Tworzymy formularz kontaktowy

Zobaczymy, jak w Nette stworzyć formularz kontaktowy, w tym wysyłanie na e-mail. Zatem do dzieła!

Najpierw musimy stworzyć nowy projekt. Jak to zrobić, wyjaśnia strona Pierwsze kroki. A potem już możemy zacząć tworzyć formularz.

Najprościej jest stworzyć formularz bezpośrednio w prezenterze. Możemy wykorzystać przygotowany HomePresenter. Dodamy do niego komponent contactForm reprezentujący formularz. Zrobimy to tak, że do kodu wpiszemy metodę fabryczną createComponentContactForm(), która wyprodukuje 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', 'Imię:')
			->setRequired('Proszę podać imię');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Proszę podać e-mail');
		$form->addTextarea('message', 'Wiadomość:')
			->setRequired('Proszę wpisać wiadomość');
		$form->addSubmit('send', 'Wyślij');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// wysłanie e-maila
	}
}

Jak widzisz, stworzyliśmy dwie metody. Pierwsza metoda createComponentContactForm() tworzy nowy formularz. Ma on pola na imię, e-mail i wiadomość, które dodajemy metodami addText(), addEmail() i addTextArea(). Dodaliśmy również przycisk do wysłania formularza. Ale co jeśli użytkownik nie wypełni jakiegoś pola? W takim przypadku powinniśmy go poinformować, że jest to pole obowiązkowe. Osiągnęliśmy to metodą setRequired(). Na koniec dodaliśmy również zdarzenie onSuccess, które uruchamia się, jeśli formularz zostanie pomyślnie wysłany i jest poprawny. W naszym przypadku wywołuje metodę contactFormSucceeded, która zajmie się przetwarzaniem wysłanego formularza. Uzupełnimy to w kodzie za chwilę.

Komponent contactForm wyrenderujemy w szablonie Home/default.latte:

{block content}
<h1>Formularz kontaktowy</h1>
{control contactForm}

Do samego wysłania e-maila stworzymy nową klasę, którą nazwiemy ContactFacade i umieścimy 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') // twój e-mail
			->setFrom($email, $name)
			->setSubject('Wiadomość z formularza kontaktowego')
			->setBody($message);

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

Metoda sendMessage() tworzy i wysyła e-mail. Wykorzystuje do tego tzw. mailer, który przyjmuje jako zależność przez konstruktor. Przeczytaj więcej o wysyłaniu e-maili.

Teraz wrócimy do presentera i dokończymy metodę contactFormSucceeded(). Wywoła ona metodę sendMessage() klasy ContactFacade i przekaże jej dane z formularza. A jak uzyskać obiekt ContactFacade? Przyjmiemy go 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('Wiadomość została wysłana');
		$this->redirect('this');
	}
}

Po wysłaniu e-maila jeszcze wyświetlimy użytkownikowi tzw. flash message, potwierdzającą, że wiadomość została wysłana, a następnie przekierujemy z powrotem na tę samą stronę, aby nie było możliwe ponowne wysłanie formularza za pomocą refresh w przeglądarce.

Tak, i jeśli wszystko działa, powinieneś być w stanie wysłać e-mail z twojego formularza kontaktowego. Gratulacje!

Szablon HTML e-maila

Na razie wysyłany jest prosty tekstowy e-mail zawierający tylko wiadomość wysłaną formularzem. W e-mailu możemy jednak wykorzystać HTML i uczynić jego wygląd bardziej atrakcyjnym. Stworzymy dla niego szablon w Latte, który zapiszemy w app/Model/contactEmail.latte:

<html>
	<title>Wiadomość z formularza kontaktowego</title>

	<body>
		<p><strong>Imię:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Wiadomość:</strong> {$message}</p>
	</body>
</html>

Pozostaje zmodyfikować ContactFacade, aby używał tego szablonu. W konstruktorze zażądamy klasy LatteFactory, która potrafi stworzyć obiekt Latte\Engine, czyli renderera szablonów Latte. Za pomocą metody renderToString() wyrenderujemy szablon do stringa, pierwszym parametrem jest ścieżka do szablonu, a drugim są parametry.

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') // twój e-mail
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Wygenerowany e-mail HTML przekażemy następnie metodzie setHtmlBody() zamiast pierwotnej setBody(). Również nie musimy podawać tematu e-maila w setSubject(), ponieważ biblioteka pobierze go z elementu <title> szablonu.

Konfiguracja

W kodzie klasy ContactFacade nadal jest na sztywno zapisany nasz e-mail administratora admin@example.com. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić?

Najpierw zmodyfikujemy klasę ContactFacade i ciąg znaków z e-mailem zastąpimy zmienną przekazaną 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 drugim krokiem jest podanie wartości tej zmiennej w konfiguracji. Do pliku app/config/services.neon zapiszemy:

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

I to wszystko. Jeśli pozycji w sekcji services byłoby dużo i mielibyście wrażenie, że e-mail ginie wśród nich, możemy uczynić go parametrem. Zmodyfikujemy zapis na:

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

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

parameters:
	adminEmail: admin@example.com

I gotowe!