Comentarios

El blog se ha puesto en marcha, hemos escrito algunas entradas muy buenas y las hemos publicado a través de Adminer. La gente está leyendo el blog y les apasionan nuestras ideas. Estamos recibiendo muchos correos electrónicos con elogios cada día. Pero, ¿para qué sirven todos esos elogios si sólo los recibimos en el correo electrónico, de modo que nadie más puede leerlos? ¿No sería mejor que la gente pudiera comentar directamente en el blog para que todo el mundo pudiera leer lo increíbles que somos?

Hagamos que todos los artículos se puedan comentar.

Crear una nueva tabla

Inicie Adminer de nuevo y cree una nueva tabla llamada comments con estas columnas:

  • id int, comprobar autoincremento (AI)
  • post_id, una clave externa que hace referencia a la tabla posts
  • name varchar, longitud 255
  • email varchar, longitud 255
  • content texto
  • created_at timestamp

Debería tener este aspecto:

No olvides usar el almacenamiento de tablas InnoDB y pulsa Guardar.

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 de comentarios

Primero, necesitamos crear un formulario, que permitirá a los usuarios comentar en nuestra página. Nette Framework tiene un soporte impresionante para formularios. Pueden ser configurados en un presentador y renderizados en una plantilla.

Nette Framework tiene un concepto de componentes. Un componente es una clase reutilizable o una pieza de código, que puede ser adjuntada a otro componente. Incluso un presentador es un componente. Cada componente se crea utilizando la fábrica de componentes. Así que vamos a definir la fábrica de formularios de comentarios en PostPresenter.

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

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

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

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

	$form->addSubmit('send', 'Publish comment');

	return $form;
}

Vamos a explicarlo un poco. La primera línea crea una nueva instancia del componente Form. Los siguientes métodos están adjuntando entradas HTML en la definición del formulario. ->addText se renderizará como <input type=text name=name>con <label>Your name:</label>. Como ya habrás adivinado en este momento, el ->addTextArea adjunta un <textarea> y ->addSubmit añade un <input type=submit>. Hay más métodos como este, pero esto es todo lo que tienes que saber por ahora. Puedes aprender más en la documentación.

Una vez definido el componente formulario en un presentador, podemos renderizarlo (mostrarlo) en una plantilla. Para ello, coloque la etiqueta {control} al final de la plantilla de detalles de la entrada, en Post/show.latte. Dado que el nombre del componente es commentForm (se deriva del nombre del método createComponentCommentForm), la etiqueta tendrá el siguiente aspecto

...
<h2>Post new comment</h2>

{control commentForm}

Ahora, si revisas el detalle de algún post, habrá un nuevo formulario para publicar comentarios.

Guardar en base de datos

¿Ha intentado enviar algunos datos? Te habrás dado cuenta de que el formulario no realiza ninguna acción. Simplemente está ahí, con un aspecto chulo y sin hacer nada. Tenemos que adjuntarle un método callback, que guardará los datos enviados.

Coloca la siguiente línea antes de la línea return en la fábrica del componente commentForm:

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

Significa “después de que el formulario se envíe correctamente, llama al método commentFormSucceeded del presentador actual”. Este método no existe todavía, así que 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('Thank you for your comment', 'success');
	$this->redirect('this');
}

Debes colocarlo justo después de la fábrica del componente commentForm.

El nuevo método tiene un argumento que es la instancia del formulario que se está enviando, creada por la fábrica de componentes. Recibimos los valores enviados en $data. Y luego insertamos los datos en la tabla de la base de datos comments.

Hay otras dos llamadas a métodos que explicar. La redirección redirige literalmente a la página actual. Deberías hacer eso cada vez que el formulario es enviado, válido, y la operación callback hizo lo que debería haber hecho. Además, cuando redirija la página después de enviar el formulario, no verá el conocido mensaje Would you like to submit the post data again? que a veces puede ver en el navegador. (En general, después de enviar un formulario por el método POST, siempre debes redirigir al usuario a una acción GET ).

El flashMessage sirve para informar al usuario del resultado de alguna operación. Debido a que estamos redirigiendo, el mensaje no puede ser pasado directamente a la plantilla y renderizado. Así que existe este método, que lo almacenará y lo hará disponible en la siguiente carga de página. Los mensajes flash son renderizados en el archivo por defecto app/UI/@layout.latte, y se ve así:

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

Como ya sabemos, se pasan automáticamente a la plantilla, así que no tienes que pensar mucho en ello, simplemente funciona. Para más detalles consulta la documentación.

Renderizando los Comentarios

Esta es una de las cosas que te encantará. La Base de Datos Nette tiene esta característica genial llamada Explorador. ¿Recuerdas que hemos creado las tablas como InnoDB? Adminer ha creado las llamadas claves foráneas que nos ahorrarán un montón de trabajo.

Nette Database Explorer utiliza las claves externas para resolver las relaciones entre las tablas, y conociendo las relaciones, puede crear automáticamente consultas para usted.

Como recordará, hemos pasado la variable $post a la plantilla en PostPresenter::renderShow() y ahora queremos iterar por todos los comentarios que tengan la columna post_id igual a nuestra $post->id. Puedes hacerlo llamando a $post->related('comments'). Así de sencillo. Mira 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>Comments</h2>

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

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

Fíjate en el atributo especial n:tag-if. Ya sabes cómo funciona n: attributes. Pues bien, si antepones al atributo tag-, sólo envolverá las etiquetas, no su contenido. Esto le permite convertir el nombre del comentarista en un enlace si ha proporcionado su dirección de correo electrónico. Los resultados de estas dos líneas son idénticos:

<strong n:tag-if="$important"> Hello there! </strong>

{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if}