Создаем контактную форму

Посмотрим, как в Nette создать контактную форму, включая отправку на email. Итак, приступим!

Сначала нам нужно создать новый проект. Как это сделать, объясняется на странице Начало работы. А затем уже можно приступать к созданию формы.

Проще всего создать форму прямо в презентере. Мы можем использовать готовый 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, который получает как зависимость через конструктор. Узнайте больше об отправке email.

Теперь вернемся к презентеру и завершим метод 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, содержащий только сообщение, отправленное формой. Но в 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

И готово!