Comentários

O blog foi implantado, escrevemos alguns posts muito bons no blog e os publicamos via Adminer. As pessoas estão lendo o blog e são muito apaixonadas por nossas idéias. Estamos recebendo muitos e-mails com elogios a cada dia. Mas para que servem todos os elogios quando os recebemos apenas no e-mail, para que ninguém mais possa lê-lo? Não seria melhor se as pessoas pudessem comentar diretamente no blog para que todos pudessem ler o quanto somos fantásticos?

Vamos tornar todos os artigos louváveis.

Criando uma nova tabela

Ligue novamente o Adminer e crie uma nova tabela chamada comments com estas colunas:

  • id int, verificar autoincremento (AI)
  • post_id, uma chave estrangeira que faz referência à tabela posts
  • name varchar, comprimento 255
  • email varchar, comprimento 255
  • content texto
  • created_at timestamp

Deveria ser assim:

Não se esqueça de usar o armazenamento de mesa InnoDB e pressione Save.

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 permitirá aos usuários comentar em nossa página. Nette Framework tem um ótimo suporte para formulários. Eles podem ser configurados em um apresentador e renderizados em um modelo.

Nette Framework tem um conceito de componentes. Um **componente*** é uma classe ou código reutilizável, que pode ser anexado a outro componente. Até mesmo um apresentador é um componente. Cada componente é criado usando a fábrica de componentes. Portanto, vamos definir os comentários da fábrica em 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 explicar um pouco. A primeira linha cria uma nova instância do componente Form. Os seguintes métodos estão anexando entradas HTML na definição do formulário. ->addText renderizará como <input type=text name=name>, com <label>Your name:</label>. Como você já deve ter adivinhado neste momento, o ->addTextArea anexa um <textarea> e ->addSubmit acrescenta um <input type=submit>. Há mais métodos como esse, mas isso é tudo que você tem que saber agora mesmo. Você pode saber mais na documentação.

Uma vez que o componente do formulário é definido em um apresentador, podemos renderizá-lo (exibir) em um modelo. Para isso, coloque a tag {control} no final do modelo de detalhe do post, em Post/show.latte. Como o nome do componente é commentForm (é derivado do nome do método createComponentCommentForm), a tag terá o seguinte aspecto

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

{control commentForm}

Agora, se você verificar os detalhes de algum post, haverá um novo formulário para postar comentários.

Salvando para o banco de dados

Você já tentou enviar alguns dados? Você deve ter notado, que o formulário não está realizando nenhuma ação. Só que está lá, com um visual legal e sem fazer nada. Temos que anexar-lhe um método de retorno, que salvará os dados enviados.

Coloque a seguinte linha antes da linha return na fábrica de componentes para o commentForm:

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

Significa “depois que o formulário for enviado com sucesso, ligue para o método commentFormSucceeded do atual apresentador”. Este método ainda não existe, portanto, vamos criá-lo.

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');
}

Você deve colocá-lo logo após a fábrica de componentes commentForm.

O novo método tem um argumento que é a instância do formulário que está sendo apresentado, criado pela fábrica de componentes. Recebemos os valores enviados em $data. E depois inserimos os dados na tabela do banco de dados comments.

Há mais duas chamadas de método para explicar. O redirecionamento é literalmente redirecionado para a página atual. Você deve fazer isso toda vez que o formulário for apresentado, válido, e a operação de retorno da chamada fez o que deveria ter feito. Além disso, quando você redireciona a página após enviar o formulário, você não verá a conhecida mensagem Would you like to submit the post data again? que às vezes você pode ver no navegador. (Em geral, após submeter um formulário pelo método POST, você deve sempre redirecionar o usuário para uma ação GET ).

O flashMessage é para informar o usuário sobre o resultado de alguma operação. Como estamos redirecionando, a mensagem não pode ser passada diretamente para o modelo e entregue. Portanto, existe este método, que irá armazená-lo e torná-lo disponível no carregamento da próxima página. As mensagens flash são renderizadas no arquivo padrão app/UI/@layout.latte, e é assim que parece:

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

Como já sabemos, eles são passados automaticamente para o modelo, de modo que não é preciso pensar muito sobre isso, ele apenas funciona. Para obter mais detalhes, consulte a documentação.

Comentários

Esta é uma das coisas que você vai adorar. O Nette Database tem este recurso legal chamado Explorer. Você se lembra que nós criamos as tabelas como InnoDB? Adminer criou as chamadas chaves estrangeiras que nos pouparão uma tonelada de trabalho.

O Nette Database Explorer utiliza as chaves estrangeiras para resolver as relações entre tabelas, e conhecendo as relações, ele pode criar automaticamente consultas para você.

Como você deve se lembrar, passamos a variável $post para o modelo em PostPresenter::renderShow() e agora queremos iterar através de todos os comentários que têm a coluna post_id igual ao nosso $post->id. Você pode fazer isso ligando para $post->related('comments'). É muito simples. Veja 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 modelo:

...
<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>
...

Observe o atributo especial n:tag-if. Você já sabe como funciona n: attributes. Bem, se você preender o atributo com tag-, ele só envolverá as tags, não o seu conteúdo. Isto permite que você transforme o nome do comentarista em um link, caso ele tenha fornecido seu e-mail. Estas duas linhas são idênticas em resultados:

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

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