Creazione di un modulo di contatto

Vediamo come creare un modulo di contatto in Nette, compreso l'invio di email. Allora, iniziamo!

Prima di tutto, dobbiamo creare un nuovo progetto. Come farlo è spiegato nella pagina Iniziare. E poi possiamo iniziare a creare il modulo.

Il modo più semplice è creare il modulo direttamente nel presenter. Possiamo utilizzare il HomePresenter pre-preparato. Aggiungeremo ad esso il componente contactForm che rappresenta il modulo. Lo faremo scrivendo nel codice il metodo factory createComponentContactForm(), che produrrà il componente:

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

class HomePresenter extends Presenter
{
	protected function createComponentContactForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Nome:')
			->setRequired('Inserisci il nome');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Inserisci l\'e-mail');
		$form->addTextarea('message', 'Messaggio:')
			->setRequired('Inserisci il messaggio');
		$form->addSubmit('send', 'Invia');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// invio dell'email
	}
}

Come potete vedere, abbiamo creato due metodi. Il primo metodo createComponentContactForm() crea un nuovo modulo. Questo ha campi per nome, email e messaggio, che aggiungiamo con i metodi addText(), addEmail() e addTextArea(). Abbiamo anche aggiunto un pulsante per inviare il modulo. Ma cosa succede se l'utente non compila qualche campo? In tal caso, dovremmo fargli sapere che è un campo obbligatorio. Abbiamo ottenuto questo risultato con il metodo setRequired(). Infine, abbiamo aggiunto anche l'evento onSuccess, che si attiva se il modulo viene inviato con successo. Nel nostro caso, chiama il metodo contactFormSucceeded, che si occuperà dell'elaborazione del modulo inviato. Lo aggiungeremo al codice tra un momento.

Faremo renderizzare il componente contactForm nel template Home/default.latte:

{block content}
<h1>Modulo di contatto</h1>
{control contactForm}

Per l'invio effettivo dell'email, creeremo una nuova classe che chiameremo ContactFacade e la posizioneremo nel file 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') // la tua email
			->setFrom($email, $name)
			->setSubject('Messaggio dal modulo di contatto')
			->setBody($message);

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

Il metodo sendMessage() crea e invia l'email. Utilizza a tal fine il cosiddetto mailer, che si fa passare come dipendenza tramite il costruttore. Leggete di più sull'invio di email.

Ora torniamo al presenter e completiamo il metodo contactFormSucceeded(). Questo chiamerà il metodo sendMessage() della classe ContactFacade e gli passerà i dati del modulo. E come otteniamo l'oggetto ContactFacade? Ce lo facciamo passare tramite il costruttore:

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('Il messaggio è stato inviato');
		$this->redirect('this');
	}
}

Dopo l'invio dell'email, mostreremo ancora all'utente il cosiddetto flash message, confermando che il messaggio è stato inviato, e poi reindirizzeremo a un'altra pagina, in modo che non sia possibile inviare nuovamente il modulo tramite refresh nel browser.

Bene, e se tutto funziona, dovreste essere in grado di inviare un'email dal vostro modulo di contatto. Congratulazioni!

Template HTML dell'email

Per ora viene inviata un'email di testo semplice contenente solo il messaggio inviato dal modulo. Ma nell'email possiamo utilizzare HTML e renderne l'aspetto più attraente. Creeremo per essa un template in Latte, che scriveremo in app/Model/contactEmail.latte:

<html>
	<title>Messaggio dal modulo di contatto</title>

	<body>
		<p><strong>Nome:</strong> {$name}</p>
		<p><strong>E-mail:</strong> {$email}</p>
		<p><strong>Messaggio:</strong> {$message}</p>
	</body>
</html>

Resta da modificare ContactFacade affinché utilizzi questo template. Nel costruttore richiederemo la classe LatteFactory, che sa produrre l'oggetto Latte\Engine, ovvero il renderizzatore di template Latte. Tramite il metodo renderToString() renderizzeremo il template in un file, il primo parametro è il percorso del template e il secondo sono le variabili.

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') // la tua email
			->setFrom($email, $name)
			->setHtmlBody($body);

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

L'email HTML generata la passeremo quindi al metodo setHtmlBody() invece dell'originale setBody(). Inoltre, non dobbiamo specificare l'oggetto dell'email in setSubject(), perché la libreria lo prenderà dall'elemento <title> del template.

Configurazione

Nel codice della classe ContactFacade è ancora hardcoded la nostra email di amministratore admin@example.com. Sarebbe meglio spostarla nel file di configurazione. Come fare?

Prima modifichiamo la classe ContactFacade e sostituiamo la stringa con l'email con una variabile passata tramite il costruttore:

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

E il secondo passo è specificare il valore di questa variabile nella configurazione. Nel file app/config/services.neon scriviamo:

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

Ed è fatto. Se ci fossero molte voci nella sezione services e aveste la sensazione che l'email si perda tra di esse, possiamo trasformarla in una variabile. Modifichiamo la scrittura in:

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

E nel file app/config/common.neon definiamo questa variabile:

parameters:
	adminEmail: admin@example.com

Ed è fatto!