Creemos un formulario de contacto

Veamos cómo crear en Nette un formulario de contacto, incluido el envío por correo de los datos enviados. ¡Vamos allá!

Primero tenemos que crear un proyecto nuevo. La página Primeros pasos explica cómo hacerlo. Después ya podemos empezar a crear el formulario.

El enfoque más sencillo es crear el formulario directamente en el presenter. Podemos aprovechar el HomePresenter ya existente. Le añadiremos un componente llamado contactForm, que representará nuestro formulario. Lo conseguimos añadiendo al código del presenter el método factory createComponentContactForm(), que creará 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('Please enter your name');
		$form->addEmail('email', 'E-mail:')
			->setRequired('Please enter your email');
		$form->addTextArea('message', 'Message:')
			->setRequired('Please enter a message');
		$form->addSubmit('send', 'Send');
		$form->onSuccess[] = $this->contactFormSucceeded(...);
		return $form;
	}

	private function contactFormSucceeded(Form $form, $data): void
	{
		// envío del correo
	}
}

Como ve, hemos creado dos métodos. El primero, createComponentContactForm(), crea una nueva instancia del formulario. Incluye campos para el nombre, el correo y el mensaje, añadidos con los métodos addText(), addEmail() y addTextArea(), respectivamente. También hemos añadido un botón de envío. ¿Pero qué pasa si el usuario deja un campo vacío? En ese caso deberíamos avisarle de que el campo es obligatorio. Lo hemos conseguido con el método setRequired(). Por último hemos enganchado a onSuccess un manejador de eventos que se dispara tras el envío correcto del formulario. En nuestro caso llama al método contactFormSucceeded, que se ocupará de procesar los datos enviados. Lo implementaremos enseguida.

Rendericemos el componente contactForm en la plantilla Home/default.latte:

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

Para el envío del correo propiamente dicho crearemos una nueva clase llamada ContactFacade y la colocaremos en el archivo 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') // su correo
			->setFrom($email, $name)
			->setSubject('Message from the contact form')
			->setBody($message);

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

El método sendMessage() crea y envía el correo. Para ello usa un servicio mailer, que recibe como dependencia mediante el constructor. Lea más sobre el envío de correos.

Ahora volvamos al presenter y completemos el método contactFormSucceeded(). Llamará al método sendMessage() de la clase ContactFacade y le pasará los datos enviados con el formulario. ¿Y cómo obtenemos el objeto ContactFacade? Se lo pediremos mediante el constructor usando la inyección de dependencias:

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('Message has been sent');
		$this->redirect('this');
	}
}

Tras enviar el correo mostramos al usuario un mensaje flash que confirma el envío. Después redirigimos para que el formulario no se vuelva a enviar al recargar la página en el navegador.

Así que, si todo está bien configurado, ya debería poder enviar un correo desde su formulario de contacto. ¡Enhorabuena!

Plantilla HTML del correo

De momento se envía un correo en texto plano que contiene solo el mensaje enviado con el formulario. Pero podemos usar HTML en el correo para que su aspecto resulte más atractivo. Le crearemos una plantilla en Latte y la guardaremos como 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 que use esta plantilla. En el constructor pediremos la clase LatteFactory, que sabe crear un objeto Latte\Engine, el renderizador de plantillas Latte. Con el método renderToString() renderizamos la plantilla en una cadena. El primer parámetro es la ruta al archivo de la plantilla y el segundo, un array de variables que se le pasan.

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

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

El contenido HTML generado del correo se lo pasamos después al método setHtmlBody() en lugar del original setBody(). Tampoco tenemos que indicar el asunto del correo con setSubject(), porque la biblioteca lo extrae automáticamente del elemento <title> de la plantilla.

Configuración

En el código de la clase ContactFacade sigue estando escrito a fuego nuestro correo de administrador admin@example.com. Sería mejor trasladarlo al archivo de configuración. ¿Cómo lo hacemos?

Primero modifique la clase ContactFacade y sustituya la cadena del correo escrita a fuego por una variable que se pasa 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);
		// ...
	}
}

El segundo paso es indicar el valor de esta variable en la configuración. En el archivo app/config/services.neon añada:

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

Y ya está. Si la sección services contiene muchos elementos y le parece que la dirección de correo se pierde entre ellos, podemos convertirla en un parámetro. Modifique la entrada así:

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

Y defina este parámetro en el archivo app/config/common.neon:

parameters:
	adminEmail: admin@example.com

¡Y listo!