Nette Documentation Preview

syntax
Stwórzmy formularz kontaktowy
*****************************

.[perex]
Zobaczmy, jak w Nette utworzyć formularz kontaktowy wraz z wysyłaniem wpisanych danych e-mailem. No to do dzieła!

Najpierw musimy utworzyć nowy projekt. Jak to zrobić, wyjaśnia strona [Pierwsze kroki |nette:installation]. Potem możemy zabrać się za tworzenie formularza.

Najprostszym podejściem jest utworzenie [formularza bezpośrednio w presenterze |forms:in-presenter]. Możemy wykorzystać przygotowany `HomePresenter`. Dodamy do niego komponent `contactForm` reprezentujący nasz formularz. Zrobimy to, dopisując do kodu presentera metodę fabrykującą `createComponentContactForm()`, która ten komponent utworzy:

```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', 'Imię:')
			->setRequired('Podaj swoje imię');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Podaj swój e-mail');
		$form->addTextArea('message', 'Wiadomość:')
			->setRequired('Wpisz wiadomość');
		$form->addSubmit('send', 'Wyślij');
		$form->onSuccess[] = $this->contactFormSucceeded(...);
		return $form;
	}

	private function contactFormSucceeded(Form $form, $data): void
	{
		// wysłanie e-maila
	}
}
```

Jak widzisz, utworzyliśmy dwie metody. Pierwsza z nich, `createComponentContactForm()`, tworzy nową instancję formularza. Zawiera pola na imię, e-mail i wiadomość, dodane odpowiednio metodami `addText()`, `addEmail()` i `addTextArea()`. Dodaliśmy też przycisk wysyłający. A co, jeśli użytkownik zostawi któreś pole puste? W takim razie powinniśmy go poinformować, że pole jest wymagane. Osiągnęliśmy to metodą `setRequired()`. Na koniec podpięliśmy jeszcze handler [zdarzenia |nette:glossary#Zdarzenia] `onSuccess`, które wywołuje się po udanym wysłaniu formularza. W naszym przypadku wywołuje on metodę `contactFormSucceeded`, która zajmie się przetworzeniem wysłanych danych. Za chwilę ją uzupełnimy.

Komponent `contactForm` wyrenderujmy w szablonie `Home/default.latte`:

```latte
{block content}
<h1>Formularz kontaktowy</h1>
{control contactForm}
```

Do samego wysyłania e-maila utworzymy nową klasę o nazwie `ContactFacade` i umieścimy ją w pliku `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') // Twój e-mail
			->setFrom($email, $name)
			->setSubject('Wiadomość z formularza kontaktowego')
			->setBody($message);

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

Metoda `sendMessage()` tworzy i wysyła e-mail. Wykorzystuje do tego usługę mailera, którą otrzymuje jako zależność przez konstruktor. Przeczytaj więcej o [wysyłaniu e-maili |mail:].

Wróćmy teraz do presentera i uzupełnijmy metodę `contactFormSucceeded()`. Wywoła ona metodę `sendMessage()` klasy `ContactFacade` i przekaże jej dane wysłane formularzem. A skąd weźmiemy obiekt `ContactFacade`? Poprosimy o niego w konstruktorze za pomocą wstrzykiwania zależności:

```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('Wiadomość została wysłana');
		$this->redirect('this');
	}
}
```

Po wysłaniu e-maila wyświetlimy użytkownikowi [wiadomość flash |application:components#Wiadomości flash] potwierdzającą wysłanie. A następnie przekierujemy, żeby formularz nie dało się wysłać ponownie odświeżeniem strony w przeglądarce.


No i jeśli wszystko jest ustawione poprawnie, powinieneś już móc wysłać e-mail ze swojego formularza kontaktowego. Gratulacje!


Szablon e-maila w HTML
----------------------

Na razie wysyła się zwykły e-mail tekstowy zawierający tylko wiadomość wysłaną formularzem. W e-mailu możemy jednak użyć HTML-a i uatrakcyjnić jego wygląd. Utworzymy dla niego szablon w Latte i zapiszemy go jako `app/Model/contactEmail.latte`:

```latte
<html>
	<title>Wiadomość z formularza kontaktowego</title>

	<body>
		<p><strong>Imię:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Wiadomość:</strong> {$message}</p>
	</body>
</html>
```

Pozostaje zmodyfikować `ContactFacade` tak, żeby ten szablon wykorzystywała. W konstruktorze poprosimy o klasę `LatteFactory`, która potrafi utworzyć obiekt `Latte\Engine`, czyli [renderer szablonów Latte |latte:develop#Jak wyrenderować szablon]. Metodą `renderToString()` wyrenderujemy szablon do ciągu znaków. Pierwszym parametrem jest ścieżka do pliku szablonu, drugim tablica zmiennych, które mu przekazujemy.

```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') // Twój e-mail
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Wygenerowaną treść e-maila w HTML przekazujemy potem metodzie `setHtmlBody()` zamiast pierwotnej `setBody()`. Nie musimy też podawać tematu e-maila metodą `setSubject()`, bo biblioteka pobiera go automatycznie z elementu `<title>` w szablonie.


Konfigurowanie
--------------

W kodzie klasy `ContactFacade` nadal mamy zapisany na sztywno e-mail administratora `admin@example.com`. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić?

Najpierw zmodyfikujemy klasę `ContactFacade` i zamiast zapisanego na sztywno ciągu z e-mailem użyjemy zmiennej przekazanej przez konstruktor:

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

Drugim krokiem jest podanie wartości tej zmiennej w konfiguracji. Do pliku `app/config/services.neon` dopiszemy:

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

I to wszystko. Jeśli pozycji w sekcji `services` jest dużo i masz wrażenie, że adres e-mail się wśród nich gubi, możemy zrobić z niego parametr. Zmodyfikujemy wpis tak:

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

A ten parametr zdefiniujemy w pliku `app/config/common.neon`:

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

I gotowe!

Stwórzmy formularz kontaktowy

Zobaczmy, jak w Nette utworzyć formularz kontaktowy wraz z wysyłaniem wpisanych danych e-mailem. No to do dzieła!

Najpierw musimy utworzyć nowy projekt. Jak to zrobić, wyjaśnia strona Pierwsze kroki. Potem możemy zabrać się za tworzenie formularza.

Najprostszym podejściem jest utworzenie formularza bezpośrednio w presenterze. Możemy wykorzystać przygotowany HomePresenter. Dodamy do niego komponent contactForm reprezentujący nasz formularz. Zrobimy to, dopisując do kodu presentera metodę fabrykującą createComponentContactForm(), która ten komponent utworzy:

use Nette\Application\UI\Form;
use Nette\Application\UI\Presenter;

class HomePresenter extends Presenter
{
	protected function createComponentContactForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Imię:')
			->setRequired('Podaj swoje imię');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Podaj swój e-mail');
		$form->addTextArea('message', 'Wiadomość:')
			->setRequired('Wpisz wiadomość');
		$form->addSubmit('send', 'Wyślij');
		$form->onSuccess[] = $this->contactFormSucceeded(...);
		return $form;
	}

	private function contactFormSucceeded(Form $form, $data): void
	{
		// wysłanie e-maila
	}
}

Jak widzisz, utworzyliśmy dwie metody. Pierwsza z nich, createComponentContactForm(), tworzy nową instancję formularza. Zawiera pola na imię, e-mail i wiadomość, dodane odpowiednio metodami addText(), addEmail() i addTextArea(). Dodaliśmy też przycisk wysyłający. A co, jeśli użytkownik zostawi któreś pole puste? W takim razie powinniśmy go poinformować, że pole jest wymagane. Osiągnęliśmy to metodą setRequired(). Na koniec podpięliśmy jeszcze handler zdarzenia onSuccess, które wywołuje się po udanym wysłaniu formularza. W naszym przypadku wywołuje on metodę contactFormSucceeded, która zajmie się przetworzeniem wysłanych danych. Za chwilę ją uzupełnimy.

Komponent contactForm wyrenderujmy w szablonie Home/default.latte:

{block content}
<h1>Formularz kontaktowy</h1>
{control contactForm}

Do samego wysyłania e-maila utworzymy nową klasę o nazwie ContactFacade i umieścimy ją w pliku 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') // Twój e-mail
			->setFrom($email, $name)
			->setSubject('Wiadomość z formularza kontaktowego')
			->setBody($message);

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

Metoda sendMessage() tworzy i wysyła e-mail. Wykorzystuje do tego usługę mailera, którą otrzymuje jako zależność przez konstruktor. Przeczytaj więcej o wysyłaniu e-maili.

Wróćmy teraz do presentera i uzupełnijmy metodę contactFormSucceeded(). Wywoła ona metodę sendMessage() klasy ContactFacade i przekaże jej dane wysłane formularzem. A skąd weźmiemy obiekt ContactFacade? Poprosimy o niego w konstruktorze za pomocą wstrzykiwania zależności:

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('Wiadomość została wysłana');
		$this->redirect('this');
	}
}

Po wysłaniu e-maila wyświetlimy użytkownikowi wiadomość flash potwierdzającą wysłanie. A następnie przekierujemy, żeby formularz nie dało się wysłać ponownie odświeżeniem strony w przeglądarce.

No i jeśli wszystko jest ustawione poprawnie, powinieneś już móc wysłać e-mail ze swojego formularza kontaktowego. Gratulacje!

Szablon e-maila w HTML

Na razie wysyła się zwykły e-mail tekstowy zawierający tylko wiadomość wysłaną formularzem. W e-mailu możemy jednak użyć HTML-a i uatrakcyjnić jego wygląd. Utworzymy dla niego szablon w Latte i zapiszemy go jako app/Model/contactEmail.latte:

<html>
	<title>Wiadomość z formularza kontaktowego</title>

	<body>
		<p><strong>Imię:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Wiadomość:</strong> {$message}</p>
	</body>
</html>

Pozostaje zmodyfikować ContactFacade tak, żeby ten szablon wykorzystywała. W konstruktorze poprosimy o klasę LatteFactory, która potrafi utworzyć obiekt Latte\Engine, czyli renderer szablonów Latte. Metodą renderToString() wyrenderujemy szablon do ciągu znaków. Pierwszym parametrem jest ścieżka do pliku szablonu, drugim tablica zmiennych, które mu przekazujemy.

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') // Twój e-mail
			->setFrom($email, $name)
			->setHtmlBody($body);

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

Wygenerowaną treść e-maila w HTML przekazujemy potem metodzie setHtmlBody() zamiast pierwotnej setBody(). Nie musimy też podawać tematu e-maila metodą setSubject(), bo biblioteka pobiera go automatycznie z elementu <title> w szablonie.

Konfigurowanie

W kodzie klasy ContactFacade nadal mamy zapisany na sztywno e-mail administratora admin@example.com. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić?

Najpierw zmodyfikujemy klasę ContactFacade i zamiast zapisanego na sztywno ciągu z e-mailem użyjemy zmiennej przekazanej przez konstruktor:

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);
		// ...
	}
}

Drugim krokiem jest podanie wartości tej zmiennej w konfiguracji. Do pliku app/config/services.neon dopiszemy:

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

I to wszystko. Jeśli pozycji w sekcji services jest dużo i masz wrażenie, że adres e-mail się wśród nich gubi, możemy zrobić z niego parametr. Zmodyfikujemy wpis tak:

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

A ten parametr zdefiniujemy w pliku app/config/common.neon:

parameters:
	adminEmail: admin@example.com

I gotowe!