Creando un formulario de contacto

Veremos cómo crear un formulario de contacto en Nette, incluyendo el envío por correo electrónico. ¡Así que manos a la obra!

Primero, debemos crear un nuevo proyecto. Cómo hacerlo se explica en la página Empezando. Y luego podemos empezar a crear el formulario.

La forma más sencilla es crear el formulario directamente en el Presenter. Podemos usar el HomePresenter predefinido. Añadiremos el componente contactForm que representa el formulario. Haremos 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', 'Nombre:')
			->setRequired('Introduce tu nombre');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Introduce tu e-mail');
		$form->addTextarea('message', 'Mensaje:')
			->setRequired('Introduce tu mensaje');
		$form->addSubmit('send', 'Enviar');
		$form->onSuccess[] = [$this, 'contactFormSucceeded'];
		return $form;
	}

	public function contactFormSucceeded(Form $form, $data): void
	{
		// envío de correo electrónico
	}
}

Como puedes ver, hemos creado dos métodos. El primer método createComponentContactForm() crea un nuevo formulario. Tiene campos para el nombre, correo electrónico y mensaje, que añadimos con los métodos addText(), addEmail() y addTextArea(). También hemos añadido un botón para enviar el formulario. Pero, ¿y si el usuario no rellena algún campo? En ese caso, deberíamos informarle de que es un campo obligatorio. Logramos esto con el método setRequired(). Finalmente, también añadimos el evento onSuccess, que se dispara si el formulario se envía con éxito. En nuestro caso, llama al método contactFormSucceeded, que se encargará de procesar el formulario enviado. Añadiremos esto al código en un momento.

Haremos que el componente contactForm se renderice en la plantilla Home/default.latte:

{block content}
<h1>Formulario de contacto</h1>
{control contactForm}

Para el envío real del correo electrónico, crearemos una nueva clase, que llamaremos ContactFacade y la ubicaremos 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') // tu correo electrónico
			->setFrom($email, $name)
			->setSubject('Mensaje del formulario de contacto')
			->setBody($message);

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

El método sendMessage() crea y envía el correo electrónico. Utiliza para ello el llamado mailer, que recibe como dependencia a través del constructor. Lee más sobre envío de correos electrónicos.

Ahora volveremos al Presenter y completaremos el método contactFormSucceeded(). Este llamará al método sendMessage() de la clase ContactFacade y le pasará los datos del formulario. ¿Y cómo obtenemos el objeto ContactFacade? Lo recibiremos a través del 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('El mensaje ha sido enviado');
		$this->redirect('this');
	}
}

Después de enviar el correo electrónico, mostraremos al usuario un llamado flash message, confirmando que el mensaje se ha enviado, y luego redirigiremos a la siguiente página para que no sea posible reenviar el formulario usando refresh en el navegador.

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

Plantilla HTML del correo electrónico

Hasta ahora, se envía un correo electrónico de texto sin formato que contiene solo el mensaje enviado por el formulario. Pero podemos usar HTML en el correo electrónico y hacer que su apariencia sea más atractiva. Crearemos una plantilla para ello en Latte, que escribiremos en app/Model/contactEmail.latte:

<html>
	<title>Mensaje del formulario de contacto</title>

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

Queda por modificar ContactFacade para que use esta plantilla. En el constructor, solicitaremos la clase LatteFactory, que puede crear un objeto Latte\Engine, es decir, el renderizador de plantillas Latte. Usando el método renderToString(), renderizaremos la plantilla en un archivo, el primer parámetro es la ruta a la plantilla y el segundo son 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') // tu correo electrónico
			->setFrom($email, $name)
			->setHtmlBody($body);

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

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

Configuración

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

Primero, modificaremos la clase ContactFacade y reemplazaremos la cadena con el correo electrónico por una variable pasada a través del 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 especificar el valor de esta variable en la configuración. En el archivo app/config/services.neon, escribimos:

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

Y eso es todo. Si hubiera muchos elementos en la sección services y sintieras que el correo electrónico se pierde entre ellos, podemos convertirlo en una variable. Modificamos la entrada a:

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

Y en el archivo app/config/common.neon, definimos esta variable:

parameters:
	adminEmail: admin@example.com

¡Y listo!