Comentarios

Hemos subido el blog al servidor web y publicado algunas entradas muy interesantes usando Adminer. La gente lee nuestro blog y está muy entusiasmada con él. Recibimos muchos correos electrónicos con elogios todos los días. Pero, ¿de qué sirven todos estos elogios si solo los tenemos en el correo electrónico y nadie más puede leerlos? Sería mejor si el lector pudiera comentar directamente en el artículo, para que todos pudieran leer lo geniales que somos.

Así que vamos a programar los comentarios.

Creación de una nueva tabla

Iniciamos Adminer y creamos una tabla comments con estas columnas:

  • id int, marcamos autoincrement (AI)
  • post_id, clave foránea que hace referencia a la tabla posts
  • name varchar, longitud 255
  • email varchar, longitud 255
  • content text
  • created_at timestamp

La tabla debería verse algo así:

No olvides usar de nuevo el almacenamiento InnoDB.

CREATE TABLE `comments` (
	`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`post_id` int(11) NOT NULL,
	`name` varchar(250) NOT NULL,
	`email` varchar(250) NOT NULL,
	`content` text NOT NULL,
	`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
	FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
) ENGINE=InnoDB CHARSET=utf8;

Formulario para comentar

Primero, debemos crear un formulario que permita a los usuarios comentar las entradas. Nette Framework tiene un soporte increíble para formularios. Podemos configurarlos en el presenter y renderizarlos en la plantilla.

Nette Framework utiliza el concepto de componentes. Un componente es una clase reutilizable o una parte de código que se puede adjuntar a otro componente. Incluso el presenter es un componente. Cada componente se crea a través de una fábrica. Por lo tanto, crearemos una fábrica para el formulario de comentarios en el presenter PostPresenter.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // significa Nette\Application\UI\Form

	$form->addText('name', 'Nombre:')
		->setRequired();

	$form->addEmail('email', 'E-mail:');

	$form->addTextArea('content', 'Comentario:')
		->setRequired();

	$form->addSubmit('send', 'Publicar comentario');

	return $form;
}

Vamos a explicarlo un poco de nuevo. La primera línea crea una nueva instancia del componente Form. Los métodos siguientes adjuntan entradas HTML a la definición de este formulario. ->addText se renderizará como <input type="text" name="name"> con <label>Nombre:</label>. Como probablemente ya adivinas correctamente, ->addTextArea se renderizará como <textarea> y ->addSubmit como <input type="submit">. Hay muchos más métodos similares, pero estos son suficientes para este formulario por ahora. Puedes leer más en la documentación.

Si el formulario ya está definido en el presenter, podemos renderizarlo (mostrarlo) en la plantilla. Esto se hace colocando la etiqueta {control} al final de la plantilla que renderiza una entrada específica, en Post/show.latte. Como el componente se llama commentForm (el nombre se deriva del nombre del método createComponentCommentForm), la etiqueta se verá así:

...
<h2>Añadir nuevo comentario</h2>

{control commentForm}

Si ahora ves la página con el detalle de la entrada, verás un nuevo formulario para comentarios al final.

Guardando en la base de datos

¿Ya has intentado rellenar el formulario y enviarlo? Probablemente te hayas dado cuenta de que el formulario en realidad no hace nada. Necesitamos adjuntar un método de callback que guarde los datos enviados.

En la línea antes de return en la fábrica para el componente commentForm, colocamos la siguiente línea:

$form->onSuccess[] = $this->commentFormSucceeded(...);

La notación anterior significa “después de enviar el formulario con éxito, llama al método commentFormSucceeded del presenter actual”. Sin embargo, este método aún no existe. Vamos a crearlo:

private function commentFormSucceeded(\stdClass $data): void
{
	$id = $this->getParameter('id');

	$this->database->table('comments')->insert([
		'post_id' => $id,
		'name' => $data->name,
		'email' => $data->email,
		'content' => $data->content,
	]);

	$this->flashMessage('Gracias por tu comentario', 'success');
	$this->redirect('this');
}

Colocamos este método justo después de la fábrica del formulario commentForm.

Este nuevo método tiene un argumento, que es una instancia del formulario que fue enviado – creado por la fábrica. Obtenemos los valores enviados en $data. Y luego guardamos los datos en la tabla de la base de datos comments.

Todavía hay otros dos métodos que merecen ser explicados. El método redirect literalmente redirige de vuelta a la página actual. Es conveniente hacer esto después de cada envío de formulario, si contenía datos válidos y el callback realizó la operación como debía. Y también, si redirigimos la página después de enviar el formulario, no veremos el conocido mensaje ¿Quieres volver a enviar los datos del formulario?, que a veces podemos ver en el navegador. (En general, después de enviar un formulario con el método POST, siempre debe seguir una redirección a una acción GET).

El método flashMessage es para informar al usuario sobre el resultado de alguna operación. Como estamos redirigiendo, el mensaje no puede simplemente pasarse a la plantilla y renderizarse. Por eso existe este método, que guarda este mensaje y lo hace accesible en la próxima carga de la página. Los mensajes flash se renderizan en la plantilla principal app/Presentation/@layout.latte y se ve así:

<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
	{$flash->message}
</div>

Como ya sabemos, los mensajes flash se pasan automáticamente a la plantilla, así que no tenemos que pensar mucho en ello, simplemente funciona. Para más información, visita la documentación.

Renderizando comentarios

Esta es una de esas cosas que simplemente te encantarán. Nette Database tiene una función genial llamada Explorer. ¿Recuerdas que creamos intencionalmente las tablas en la base de datos usando el almacenamiento InnoDB? Adminer creó así algo llamado claves foráneas, que nos ahorrarán mucho trabajo.

Nette Database Explorer utiliza claves foráneas para resolver la relación mutua entre tablas y, a partir del conocimiento de estas relaciones, puede crear automáticamente consultas a la base de datos.

Como seguramente recordarás, pasamos la variable $post a la plantilla usando el método PostPresenter::renderShow() y ahora queremos iterar sobre todos los comentarios que tienen el valor de la columna post_id igual a $post->id. Podemos lograr esto llamando a $post->related('comments'). Sí, así de simple. Veamos el código resultante:

public function renderShow(int $id): void
{
	...
	$this->template->post = $post;
	$this->template->comments = $post->related('comments')->order('created_at');
}

Y la plantilla:

...
<h2>Comentarios</h2>

<div class="comments">
	{foreach $comments as $comment}
		<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
			{$comment->name}
		</a></b> escribió:</p>

		<div>{$comment->content}</div>
	{/foreach}
</div>
...

Observa el atributo especial n:tag-if. Ya sabes cómo funcionan los n:atributos. Si adjuntas el prefijo tag- al atributo, la funcionalidad se aplica solo a la etiqueta HTML, no a su contenido. Esto nos permite convertir el nombre del comentarista en un enlace solo si proporcionó su correo electrónico. Estas dos líneas son idénticas:

<strong n:tag-if="$important"> ¡Hola! </strong>

{if $important}<strong>{/if} ¡Hola! {if $important}</strong>{/if}