Δημιουργούμε μια φόρμα επικοινωνίας

Θα δούμε πώς να δημιουργήσουμε μια φόρμα επικοινωνίας στο Nette, συμπεριλαμβανομένης της αποστολής μέσω email. Ας ξεκινήσουμε λοιπόν!

Πρώτα πρέπει να δημιουργήσουμε ένα νέο έργο. Πώς να το κάνετε αυτό εξηγείται στη σελίδα Ξεκινώντας. Και μετά μπορούμε ήδη να αρχίσουμε να δημιουργούμε τη φόρμα.

Ο ευκολότερος τρόπος είναι να δημιουργήσετε τη φόρμα απευθείας στον presenter. Μπορούμε να χρησιμοποιήσουμε τον προετοιμασμένο HomePresenter. Σε αυτόν θα προσθέσουμε το component contactForm που αντιπροσωπεύει τη φόρμα. Θα το κάνουμε γράφοντας στον κώδικα τη μέθοδο factory createComponentContactForm(), η οποία θα κατασκευάσει το component:

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(). Τέλος, προσθέσαμε επίσης το event onSuccess, το οποίο ενεργοποιείται εάν η φόρμα υποβληθεί επιτυχώς. Στην περίπτωσή μας, καλεί τη μέθοδο contactFormSucceeded, η οποία θα αναλάβει την επεξεργασία της υποβληθείσας φόρμας. Αυτό θα το συμπληρώσουμε στον κώδικα σε μια στιγμή.

Το component contactForm θα το αφήσουμε να αποδοθεί στο template 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, τον οποίο λαμβάνει ως εξάρτηση μέσω του constructor. Διαβάστε περισσότερα για την αποστολή emails.

Τώρα θα επιστρέψουμε στον presenter και θα ολοκληρώσουμε τη μέθοδο contactFormSucceeded(). Αυτή θα καλέσει τη μέθοδο sendMessage() της κλάσης ContactFacade και θα της παραδώσει τα δεδομένα από τη φόρμα. Και πώς θα αποκτήσουμε το αντικείμενο ContactFacade; Θα το ζητήσουμε να μας παραδοθεί μέσω του constructor:

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 message, επιβεβαιώνοντας ότι το μήνυμα στάλθηκε, και στη συνέχεια θα ανακατευθύνουμε σε άλλη σελίδα, ώστε να μην είναι δυνατή η επανειλημμένη αποστολή της φόρμας μέσω refresh στον browser.

Λοιπόν, και αν όλα λειτουργούν, θα πρέπει να μπορείτε να στείλετε email από τη φόρμα επικοινωνίας σας. Συγχαρητήρια!

HTML template email

Μέχρι στιγμής, αποστέλλεται ένα απλό email κειμένου που περιέχει μόνο το μήνυμα που στάλθηκε από τη φόρμα. Στο email όμως μπορούμε να χρησιμοποιήσουμε HTML και να κάνουμε την εμφάνισή του πιο ελκυστική. Θα δημιουργήσουμε γι' αυτό ένα template στο 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, ώστε να χρησιμοποιεί αυτό το template. Στον constructor θα ζητήσουμε την κλάση LatteFactory, η οποία μπορεί να δημιουργήσει ένα αντικείμενο Latte\Engine, δηλαδή τον Latte template renderer. Με τη μέθοδο renderToString() θα αποδώσουμε το template σε αρχείο, η πρώτη παράμετρος είναι η διαδρομή προς το template και η δεύτερη είναι οι μεταβλητές.

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> του template.

Διαμόρφωση

Στον κώδικα της κλάσης ContactFacade είναι ακόμα σκληρά κωδικοποιημένο το διαχειριστικό μας email admin@example.com. Θα ήταν καλύτερο να το μεταφέρουμε στο αρχείο διαμόρφωσης. Πώς να το κάνουμε αυτό;

Πρώτα θα τροποποιήσουμε την κλάση ContactFacade και θα αντικαταστήσουμε το string με το email με μια μεταβλητή που παραδίδεται μέσω του constructor:

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

Και τελειώσαμε!