Nette Documentation Preview

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

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

Сначала нам нужно создать новый проект. Как это сделать, объясняется на странице [Начало работы |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;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// отправка email
	}
}
```

Как видите, мы создали два метода. Первый метод `createComponentContactForm()` создает новую форму. У нее есть поля для имени, email и сообщения, которые мы добавляем методами `addText()`, `addEmail()` и `addTextArea()`. Также мы добавили кнопку для отправки формы. Но что, если пользователь не заполнит какое-то поле? В таком случае мы должны сообщить ему, что это обязательное поле. Этого мы добились с помощью метода `setRequired()`. Наконец, мы добавили также [событие |nette:glossary#События Events] `onSuccess`, которое сработает, если форма успешно отправлена. В нашем случае оно вызовет метод `contactFormSucceeded`, который позаботится об обработке отправленной формы. Это мы добавим в код через мгновение.

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

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

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

```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 |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');
	}
}
```

После того как email будет отправлен, мы еще покажем пользователю так называемое [flash-сообщение |application:components#Flash-сообщения], подтверждающее, что сообщение отправлено, а затем перенаправим на другую страницу, чтобы нельзя было повторно отправить форму с помощью *refresh* в браузере.


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


HTML шаблон email
-----------------

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

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

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


Конфигурация
------------

В коде класса `ContactFacade` все еще жестко прописан наш администраторский email `admin@example.com`. Было бы лучше перенести его в конфигурационный файл. Как это сделать?

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

```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` было много и у вас было бы ощущение, что email среди них теряется, мы можем сделать из него переменную. Изменим запись на:

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

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

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

И готово!


{{sitename: Best Practices}}

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

Посмотрим, как в 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

И готово!