Creemos un formulario de contacto

Veamos cómo crear un formulario de contacto en Nette, incluido el envío a un correo electrónico. ¡Hagámoslo!

Primero tenemos que crear un nuevo proyecto. Como explica la página de introducción. Y luego podemos empezar a crear el formulario.

La forma más sencilla es crear el formulario directamente en Presenter. Podemos utilizar el pre-hecho HomePresenter. Añadiremos el componente contactForm que representa el formulario. Hacemos esto escribiendo el método de fábrica createComponentContactForm() en el código que producirá el 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', 'Name:')
			->setRequired('Enter your name');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Enter your e-mail');
		$form->addTextarea('message', 'Message:')
			->setRequired('Enter message');
		$form->addSubmit('send', 'Send');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// sending an email
	}
}

Como puedes ver, hemos creado dos métodos. El primer método createComponentContactForm() crea un nuevo formulario. Este tiene campos para nombre, email y mensaje, que añadimos usando los métodos addText(), addEmail() y addTextArea(). También añadimos un botón para enviar el formulario. Pero, ¿qué pasa si el usuario no rellena algunos campos? En ese caso, debemos hacerle saber que se trata de un campo obligatorio. Hicimos esto con el método setRequired(). Por último, también añadimos un evento onSuccess, que se activa si el formulario se envía correctamente. En nuestro caso, llama al método contactFormSucceeded, que se encarga de procesar el formulario enviado. Lo añadiremos al código en un momento.

Dejemos que el componente contantForm sea renderizado en la plantilla Home/default.latte:

{block content}
<h1>Contant Form</h1>
{control contactForm}

Para enviar el correo electrónico propiamente dicho, creamos una nueva clase llamada ContactFacade y la colocamos en el archivo 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') // your email
			->setFrom($email, $name)
			->setSubject('Message from the contact form')
			->setBody($message);

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

El método sendMessage() creará y enviará el correo electrónico. Para ello utiliza el llamado mailer, que pasa como dependencia a través del constructor. Más información sobre el envío de correos electrónicos.

Ahora, volveremos al presentador y completaremos el método contactFormSucceeded(). Llama al método sendMessage() de la clase ContactFacade y le pasa los datos del formulario. ¿Y cómo obtenemos el objeto ContactFacade? Nos lo pasará el 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('The message has been sent');
		$this->redirect('this');
	}
}

Una vez enviado el email, mostramos al usuario el llamado mensaje flash, confirmando que el mensaje ha sido enviado, y luego redirigimos a la siguiente página para que el formulario no pueda ser reenviado usando refresh en el navegador.

Bien, si todo funciona, deberías poder enviar un correo electrónico desde tu formulario de contacto. ¡Enhorabuena!

Plantilla HTML de correo electrónico

Por ahora, se envía un email de texto plano que contiene sólo el mensaje enviado por el formulario. Pero podemos utilizar HTML en el email y hacerlo más atractivo. Crearemos una plantilla para ello en Latte, que guardaremos en app/Model/contactEmail.latte:

<html>
	<title>Message from the contact form</title>

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

Queda modificar ContactFacade para utilizar esta plantilla. En el constructor, solicitamos la clase LatteFactory, que puede producir el objeto Latte\Engine, un renderizador de plantillas Latte. Usamos el método renderToString() para renderizar la plantilla a un fichero, el primer parámetro es la ruta a la plantilla y el segundo las variables.

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

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

Luego pasamos el correo HTML generado al método setHtmlBody() en lugar del original setBody(). Tampoco necesitamos especificar el asunto del correo electrónico en setSubject(), porque la biblioteca lo toma del elemento <title> de la plantilla.

Configuración de

En el código de la clase ContactFacade, nuestro email de administrador admin@example.com está todavía codificado. Sería mejor moverlo al archivo de configuración. ¿Cómo hacerlo?

Primero, modificamos la clase ContactFacade y reemplazamos la cadena de email con una variable pasada por el 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);
		// ...
	}
}

Y el segundo paso es poner el valor de esta variable en la configuración. En el fichero app/config/services.neon añadimos:

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

Y ya está. Si hay muchos elementos en la sección services y te parece que el correo electrónico se pierde entre ellos, podemos convertirlo en una variable. Modificaremos la entrada a:

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

Y definiremos esta variable en el fichero app/config/common.neon:

parameters:
	adminEmail: admin@example.com

¡Y ya está!