Nette Documentation Preview

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

.[perex]
Разберём, как создать в Nette форму обратной связи, включая отправку введённых данных по электронной почте. Приступим!

Сначала нужно создать новый проект. Как это сделать, объясняет страница [Первые шаги |nette:installation]. Затем можно приступить к созданию формы.

Проще всего создать [форму прямо в презентере |forms:in-presenter]. Мы можем воспользоваться уже существующим `HomePresenter`. Мы добавим компонент с именем `contactForm`, представляющий нашу форму. Для этого добавим в код презентера фабричный метод `createComponentContactForm()`, который создаст компонент:

```php
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()`. Наконец, мы привязали обработчик [события |nette:glossary#События] к `onSuccess`, который срабатывает при успешной отправке формы. В нашем случае он вызывает метод `contactFormSucceeded`, который займётся обработкой отправленных данных. Реализуем его чуть позже.

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

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

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

```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()` создаёт и отправляет письмо. Для этого он использует сервис отправки почты, который получает как зависимость через конструктор. Подробнее об [отправке писем |mail:].

Теперь вернёмся в презентер и допишем метод `contactFormSucceeded()`. Он вызовет метод `sendMessage()` класса `ContactFacade` и передаст данные, отправленные через форму. А как получить объект `ContactFacade`? Мы запросим его через конструктор с помощью внедрения зависимостей:

```php
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-сообщение |application:components#Flash-сообщения], подтверждающее отправку. Затем перенаправляем, чтобы форму нельзя было отправить повторно обновлением страницы в браузере.


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


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

Сейчас отправляется обычное текстовое письмо, содержащее только сообщение, отправленное через форму. Однако мы можем использовать в письме HTML, чтобы его вид стал привлекательнее. Создадим для него шаблон на Latte и сохраним как `app/Model/contactEmail.latte`:

```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 |latte:develop#Как отрисовать шаблон]. Методом `renderToString()` мы отрисуем шаблон в строку. Первый параметр - путь к файлу шаблона, второй - массив переменных, которые в него передаются.

```php
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 переменной, передаваемой через конструктор:

```php
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` добавьте:

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

Вот и всё. Если в секции `services` много записей и вам кажется, что адрес электронной почты в них теряется, его можно превратить в параметр. Измените запись так:

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

А сам параметр определите в файле `app/config/common.neon`:

```neon
parameters:
	adminEmail: admin@example.com
```

И готово!

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

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

И готово!