Створюємо контактну форму

Розглянемо, як у Nette створити контактну форму, включно з надсиланням на електронну пошту. Отже, до справи!

Спочатку потрібно створити новий проект. Як це зробити, пояснюється на сторінці Починаємо. А потім вже можемо почати створювати форму.

Найпростіше створити форму безпосередньо в презентері. Можемо використати заготовлений HomePresenter. До нього додамо компонент contactForm, що представляє форму. Зробимо це так: запишемо в код фабричний метод createComponentContactForm(), який створить компонент:

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

class HomePresenter extends Presenter
{
	protected function createComponentContactForm(): Form
	{
		$form = new Form;
		$form->addText('name', "Ім'я:")
			->setRequired("Введіть ім'я");
		$form->addEmail('email', 'E-mail:')
			->setRequired('Введіть e-mail');
		$form->addTextarea('message', 'Повідомлення:')
			->setRequired('Введіть повідомлення');
		$form->addSubmit('send', 'Надіслати');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// надсилання email
	}
}

Як бачите, ми створили два методи. Перший метод createComponentContactForm() створює нову форму. Вона має поля для імені, email та повідомлення, які ми додаємо методами addText(), addEmail() та addTextArea(). Також ми додали кнопку для надсилання форми. Але що, якщо користувач не заповнить якесь поле? У такому випадку ми повинні повідомити йому, що це обов'язкове поле. Цього ми досягли за допомогою методу setRequired(). Нарешті, ми також додали подію onSuccess, яка спрацює, якщо форма успішно надіслана. У нашому випадку вона викличе метод contactFormSucceeded, який подбає про обробку надісланої форми. Це ми доповнимо в код за мить.

Компонент contactForm виведемо в шаблоні Home/default.latte:

{block content}
<h1>Контактна форма</h1>
{control contactForm}

Для самого надсилання email створимо новий клас, який назвемо ContactFacade і розмістимо його у файлі 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') // ваш email
			->setFrom($email, $name)
			->setSubject('Повідомлення з контактної форми')
			->setBody($message);

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

Метод sendMessage() створює та надсилає email. Для цього він використовує так званий mailer, який отримує як залежність через конструктор. Дізнайтеся більше про надсилання електронних листів.

Тепер повернемося до презентера і завершимо метод contactFormSucceeded(). Він викличе метод sendMessage() класу ContactFacade і передасть йому дані з форми. А як отримати об'єкт ContactFacade? Отримаємо його через конструктор:

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('Повідомлення було надіслано');
		$this->redirect('this');
	}
}

Після надсилання email ми ще покажемо користувачеві так зване flash-повідомлення, що підтверджує надсилання повідомлення, а потім перенаправимо на наступну сторінку, щоб не можна було повторно надіслати форму за допомогою refresh у браузері.

Отже, якщо все працює, ви повинні мати можливість надіслати email з вашої контактної форми. Вітаю!

HTML-шаблон електронного листа

Поки що надсилається простий текстовий email, що містить лише повідомлення, надіслане формою. Але в email ми можемо використовувати HTML і зробити його вигляд привабливішим. Створимо для нього шаблон у Latte, який запишемо до app/Model/contactEmail.latte:

<html>
	<title>Повідомлення з контактної форми</title>

	<body>
		<p><strong>Ім'я:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Повідомлення:</strong> {$message}</p>
	</body>
</html>

Залишилося змінити ContactFacade, щоб він використовував цей шаблон. У конструкторі ми запросимо клас LatteFactory, який вміє створювати об'єкт Latte\Engine, тобто рендер шаблонів Latte. За допомогою методу renderToString() ми відрендеримо шаблон у файл, першим параметром є шлях до шаблону, а другим – змінні.

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

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

Згенерований HTML email потім передамо методу setHtmlBody() замість початкового setBody(). Також нам не потрібно вказувати тему email у setSubject(), оскільки бібліотека візьме її з елемента <title> шаблону.

Конфігурація

У коді класу ContactFacade все ще жорстко прописаний наш адміністраторський email admin@example.com. Було б краще перенести його до конфігураційного файлу. Як це зробити?

Спочатку змінимо клас ContactFacade і рядок з email замінимо змінною, переданою конструктором:

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);
		// ...
	}
}

А другим кроком є вказівка значення цієї змінної в конфігурації. До файлу app/config/services.neon запишемо:

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

І все. Якщо елементів у секції services буде багато і ви відчуватимете, що email серед них губиться, ми можемо зробити з нього змінну. Змінимо запис на:

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

А у файлі app/config/common.neon визначимо цю змінну:

parameters:
	adminEmail: admin@example.com

І готово!