Comentários

Nós carregamos o blog para o servidor web e publicamos algumas postagens muito interessantes usando o Adminer. As pessoas estão lendo nosso blog e estão muito entusiasmadas com ele. Recebemos muitos e-mails com elogios todos os dias. Mas de que adianta todo esse elogio se o temos apenas em nosso e-mail e ninguém pode lê-lo? Seria melhor se o leitor pudesse comentar diretamente no artigo, para que todos pudessem ler como somos incríveis.

Então, vamos programar os comentários.

Criação de uma nova tabela

Vamos iniciar o Adminer e criar uma tabela comments com as seguintes colunas:

  • id int, marque autoincremento (AI)
  • post_id, chave estrangeira que referencia a tabela posts
  • name varchar, comprimento 255
  • email varchar, comprimento 255
  • content text
  • created_at timestamp

A tabela deve, portanto, ter a seguinte aparência:

Não se esqueça de usar o armazenamento InnoDB novamente.

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;

Formulário para comentar

Primeiro, precisamos criar um formulário que permita aos usuários comentar nas postagens. O Nette Framework tem um suporte incrível para formulários. Podemos configurá-los no presenter e renderizá-los no template.

O Nette Framework utiliza o conceito de componentes. Um componente é uma classe reutilizável ou parte de código que pode ser anexada a outro componente. Até mesmo o presenter é um componente. Cada componente é criado através de uma fábrica. Portanto, criaremos uma fábrica para o formulário de comentários no presenter PostPresenter.

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

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

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

	$form->addTextArea('content', 'Comentário:')
		->setRequired();

	$form->addSubmit('send', 'Publicar comentário');

	return $form;
}

Vamos explicar isso um pouco mais. A primeira linha cria uma nova instância do componente Form. Os métodos seguintes anexam inputs HTML à definição deste formulário. ->addText() será renderizado como <input type="text" name="name"> com <label>Nome:</label>. Como você provavelmente já adivinhou corretamente, ->addTextArea() será renderizado como <textarea> e ->addSubmit() como <input type="submit">. Existem muitos outros métodos semelhantes, mas estes são suficientes para este formulário por enquanto. Você pode ler mais na documentação.

Se o formulário já estiver definido no presenter, podemos renderizá-lo (exibi-lo) no template. Faremos isso colocando a tag {control} no final do template que renderiza uma postagem específica, em Post/show.latte. Como o componente se chama commentForm (o nome é derivado do nome do método createComponentCommentForm), a tag ficará assim:

...
<h2>Insira uma nova postagem</h2>

{control commentForm}

Agora, se você visualizar a página com os detalhes da postagem, verá um novo formulário de comentários no final.

Salvando no banco de dados

Você já tentou preencher e enviar o formulário? Você provavelmente notou que o formulário na verdade não faz nada. Precisamos anexar um método de callback que salvará os dados enviados.

O callback commentFormSucceeded já foi adicionado ao código do presenter PostPresenter acima.

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

A escrita anterior significa “após o envio bem-sucedido do formulário, chame o método commentFormSucceeded do presenter atual”.

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('Obrigado pelo comentário', 'success');
	$this->redirect('this');
}

Colocaremos este método logo após a fábrica do formulário commentForm.

Este novo método tem um argumento, que é uma instância do formulário que foi enviado – criado pela fábrica. Obtemos os valores enviados em $data. E, em seguida, salvamos os dados na tabela do banco de dados comments.

Existem ainda outros dois métodos que merecem explicação. O método redirect literalmente redireciona de volta para a página atual. Isso é apropriado fazer após cada envio de formulário, se ele continha dados válidos e o callback realizou a operação como deveria. E também, se redirecionarmos a página após o envio do formulário, não veremos a conhecida mensagem Deseja reenviar os dados do formulário?, que às vezes podemos ver no navegador. (Geralmente, após o envio de um formulário pelo método POST, deve sempre seguir um redirecionamento para uma ação GET.)

O método flashMessage() serve para informar o usuário sobre o resultado de alguma operação. Como estamos redirecionando, a mensagem não pode ser simplesmente passada para o template e renderizada diretamente. Por isso, existe este método, que armazena esta mensagem na sessão e a disponibiliza na próxima carga da página. As mensagens flash são renderizadas no template principal app/Presentation/@layout.latte e têm a seguinte aparência:

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

Como já sabemos, as mensagens flash são automaticamente passadas para o template, então não precisamos pensar muito sobre isso, simplesmente funciona. Para mais informações visite a documentação.

Renderizando comentários

Esta é uma daquelas coisas que você simplesmente vai adorar. O Nette Database tem uma função incrível chamada Explorer. Lembra-se que criamos intencionalmente as tabelas no banco de dados usando o armazenamento InnoDB? O Adminer criou algo chamado chaves estrangeiras, que nos poupará muito trabalho.

O Nette Database Explorer usa chaves estrangeiras para resolver o relacionamento mútuo entre as tabelas e, com base no conhecimento dessas relações, pode criar automaticamente consultas ao banco de dados.

Como você certamente se lembra, passamos a variável $post para o template usando o método PostPresenter::renderShow() e agora queremos iterar sobre todos os comentários que têm o valor da coluna post_id igual a $post->id. Podemos conseguir isso chamando $post->related('comments'). Sim, é simples assim. Vejamos o código resultante:

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

E o template:

...
<h2>Comentários</h2>

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

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

Observe o atributo especial n:tag-if. Você já sabe como os n:atributos funcionam. Se você anexar o prefixo tag- ao atributo, a funcionalidade será aplicada apenas à tag HTML, não ao seu conteúdo. Isso nos permite transformar o nome do comentarista em um link apenas se ele forneceu seu e-mail. Estas duas linhas são idênticas:

<strong n:tag-if="$important"> Olá! </strong>

{if $important}<strong>{/if} Olá! {if $important}</strong>{/if}