Създаваме контактна форма

Ще разгледаме как да създадем контактна форма в 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;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// изпращане на имейл
	}
}

Както виждате, създадохме два метода. Първият метод createComponentContactForm() създава нова форма. Тя има полета за име, имейл и съобщение, които добавяме с методите addText(), addEmail() и addTextArea(). Също така добавихме бутон за изпращане на формата. Но какво ще стане, ако потребителят не попълни някое поле? В такъв случай трябва да му съобщим, че това е задължително поле. Постигнахме това с метода setRequired(). Накрая добавихме и събитие onSuccess, което се задейства, ако формата е успешно изпратена. В нашия случай извиква метода contactFormSucceeded, който ще се погрижи за обработката на изпратената форма. Ще добавим това в кода след малко.

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

{block content}
<h1>Контактна форма</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') // вашият имейл
			->setFrom($email, $name)
			->setSubject('Съобщение от контактната форма')
			->setBody($message);

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

Методът sendMessage() създава и изпраща имейл. За целта използва т.нар. mailer, който получава като зависимост чрез конструктора. Прочетете повече за изпращане на имейли.

Сега ще се върнем към презентера и ще завършим метода 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 съобщение, потвърждаващо, че съобщението е изпратено, и след това ще пренасочим към следващата страница, за да не може формата да бъде повторно изпратена чрез refresh в браузъра.

Така, и ако всичко работи, трябва да можете да изпратите имейл от вашата контактна форма. Поздравления!

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') // вашият имейл
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Генерирания HTML имейл след това ще предадем на метода setHtmlBody() вместо оригиналния setBody(). Също така не е необходимо да посочваме темата на имейла в setSubject(), тъй като библиотеката ще я вземе от елемента <title> на шаблона.

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

В кода на класа ContactFacade все още е твърдо кодиран нашият администраторски имейл admin@example.com. Би било по-добре да го преместим в конфигурационния файл. Как да го направим?

Първо ще променим класа ContactFacade и ще заменим низа с имейла с променлива, предадена чрез конструктора:

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

И е готово!