Создадим форму обратной связи

Разберём, как создать в 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;
	}

	private function contactFormSucceeded(Form $form, $data): void
	{
		// отправка письма
	}
}

Как видите, мы создали два метода. Первый, createComponentContactForm(), создаёт новый экземпляр формы. В нём есть поля для имени, e-mail и сообщения, добавленные соответственно методами addText(), addEmail() и addTextArea(). Мы также добавили кнопку отправки. А что, если пользователь оставит поле пустым? В этом случае нам следует сообщить ему, что поле обязательно. Этого мы добились методом setRequired(). Наконец, мы привязали обработчик события к onSuccess, который срабатывает при успешной отправке формы. В нашем случае он вызывает метод contactFormSucceeded, который займётся обработкой отправленных данных. Реализуем его чуть позже.

Отрисуем компонент contactForm в шаблоне Home/default.latte:

{block content}
<h1>Форма обратной связи</h1>
{control contactForm}

Для самой отправки письма создадим новый класс с именем ContactFacade и поместим его в файл app/Model/ContactFacade.php:

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') // ваш e-mail
			->setFrom($email, $name)
			->setSubject('Сообщение из формы обратной связи')
			->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('Сообщение отправлено');
		$this->redirect('this');
	}
}

После отправки письма мы показываем пользователю flash-сообщение, подтверждающее отправку. Затем перенаправляем, чтобы форму нельзя было отправить повторно обновлением страницы в браузере.

Итак, если всё настроено правильно, теперь вы должны иметь возможность отправить письмо из своей формы обратной связи. Поздравляем!

HTML-шаблон письма

Сейчас отправляется обычное текстовое письмо, содержащее только сообщение, отправленное через форму. Однако мы можем использовать в письме 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') // ваш e-mail
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Получившееся HTML-содержимое письма мы затем передаём в метод setHtmlBody() вместо исходного setBody(). Тему письма через setSubject() указывать тоже не нужно, потому что библиотека автоматически берёт её из элемента <title> в шаблоне.

Настройка

В коде класса ContactFacade наш e-mail администратора admin@example.com по-прежнему прописан жёстко. Лучше было бы перенести его в конфигурационный файл. Как это сделать?

Сначала изменим класс ContactFacade, заменив жёстко прописанную строку с e-mail переменной, передаваемой через конструктор:

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

И готово!