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

Давайте розглянемо, як створити контактну форму в Nette, в тому числі відправити її на електронну пошту. Тож давайте зробимо це!

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

Найпростіший спосіб – створити форму безпосередньо у Presenter. Ми можемо використати готову форму 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', '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
	}
}

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

Нехай компонент contantForm рендериться в шаблоні templates/Home/default.latte:

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

Для відправки самого листа ми створюємо новий клас з ім'ям 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') // your email
			->setFrom($email, $name)
			->setSubject('Message from the contact form')
			->setBody($message);

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

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

Тепер повернемося до доповідача і завершимо метод 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('The message has been sent');
		$this->redirect('this');
	}
}

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

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

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

Наразі надсилається звичайний текстовий лист, що містить лише повідомлення, надіслане за допомогою форми. Але ми можемо використовувати HTML в листі і зробити його більш привабливим. Ми створимо для цього шаблон в Latte, який збережемо в 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>

Залишилося модифікувати 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') // your email
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Потім ми передаємо згенерований HTML-лист методу setHtmlBody() замість оригінального setBody(). Нам також не потрібно вказувати тему листа в 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 багато елементів, і ви відчуваєте, що лист губиться серед них, ми можемо зробити його змінною. Змінимо запис на:

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

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

parameters:
	adminEmail: admin@example.com

І все готово!