Comentarii

Blogul a fost implementat, am scris câteva articole de blog foarte bune și le-am publicat prin Adminer. Oamenii citesc blogul și sunt foarte pasionați de ideile noastre. Primim multe e-mailuri cu laude în fiecare zi. Dar la ce ne folosesc toate laudele când le primim doar în e-mail, deci nimeni altcineva nu le poate citi? Nu ar fi mai bine dacă oamenii ar putea să comenteze direct pe blog, astfel încât toată lumea să poată citi cât de grozavi suntem?

Haideți să facem ca toate articolele să poată fi comentate.

Crearea unui tabel nou

Porniți din nou Adminer și creați un tabel nou numit comments cu aceste coloane:

  • id int, verificați autoincrementare (AI)
  • post_id, o cheie externă care face trimitere la tabelul posts
  • name varchar, lungime 255
  • email varchar, lungime 255
  • content text
  • created_at timestamp

Ar trebui să arate astfel:

Nu uitați să folosiți stocarea tabelelor InnoDB și apăsați 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;

Formular pentru comentarii

În primul rând, trebuie să creăm un formular, care va permite utilizatorilor să comenteze pe pagina noastră. Nette Framework are un suport minunat pentru formulare. Acestea pot fi configurate într-un prezentator și redate într-un șablon.

Nette Framework are un concept de componente. Un component este o clasă sau o bucată de cod reutilizabilă, care poate fi atașată unei alte componente. Chiar și un prezentator este o componentă. Fiecare componentă este creată cu ajutorul fabricii de componente. Deci, să definim fabrica de formulare de comentarii în PostPresenter.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // înseamnă 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;
}

Să o explicăm puțin. Prima linie creează o nouă instanță a componentei Form. Următoarele metode atașează intrările HTML în definiția formularului. ->addText va fi redat ca <input type=text name=name>, cu <label>Your name:</label>. După cum probabil ați ghicit deja acum, ->addTextArea atașează o componentă <textarea> iar ->addSubmit adaugă un <input type=submit>. Există mai multe metode de acest gen, dar asta este tot ce trebuie să știți acum. Puteți afla mai multe în documentație.

Odată ce componenta de formular este definită într-un prezentator, o putem reda (afișa) într-un șablon. Pentru a face acest lucru, plasați eticheta {control} la sfârșitul șablonului de detaliu al postării, în Post/show.latte. Deoarece numele componentei este commentForm (este derivat din numele metodei createComponentCommentForm), eticheta va arăta astfel

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

{control commentForm}

Acum, dacă verificați detaliul unei anumite postări, va exista un nou formular pentru a posta comentarii.

Salvarea în baza de date

Ați încercat să trimiteți niște date? Poate ați observat că formularul nu efectuează nicio acțiune. Este doar acolo, arată bine și nu face nimic. Trebuie să-i atașăm o metodă callback, care va salva datele trimise.

Așezați următoarea linie înainte de linia return în fabrica de componente pentru commentForm:

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

Aceasta înseamnă “după ce formularul este trimis cu succes, apelați metoda commentFormSucceeded a prezentatorului curent”. Această metodă nu există încă, așa că haideți să o creăm.

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

Ar trebui să o plasați imediat după fabrica de componente commentForm.

Metoda new are un singur argument, care este instanța formularului care se trimite, creată de fabrica de componente. Primim valorile trimise în $data. Apoi inserăm datele în tabelul din baza de date comments.

Mai sunt două apeluri de metode care trebuie explicate. Redirecționarea redirecționează literalmente către pagina curentă. Ar trebui să faceți acest lucru de fiecare dată când formularul este trimis, este valid și operațiunea de rechemare a făcut ceea ce trebuia să facă. De asemenea, atunci când redirecționați pagina după trimiterea formularului, nu veți vedea binecunoscutul mesaj Would you like to submit the post data again? pe care îl puteți vedea uneori în browser. (În general, după trimiterea unui formular prin metoda POST, ar trebui să redirecționați întotdeauna utilizatorul către o acțiune GET ).

flashMessage are rolul de a informa utilizatorul despre rezultatul unei anumite operațiuni. Pentru că redirecționăm, mesajul nu poate fi trecut direct în șablon și redat. Așa că există această metodă, care îl va stoca și îl va face disponibil la următoarea încărcare a paginii. Mesajele flash sunt redate în fișierul implicit app/UI/@layout.latte și arată astfel:

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

După cum știm deja, acestea sunt transmise automat șablonului, așa că nu trebuie să vă gândiți prea mult la asta, pur și simplu funcționează. Pentru mai multe detalii, consultați documentația.

Redarea comentariilor

Acesta este unul dintre lucrurile pe care le veți adora. Baza de date Nette are o funcție interesantă numită Explorer. Vă amintiți că am creat tabelele ca InnoDB? Adminer a creat așa-numitele chei străine care ne vor salva o tonă de muncă.

Nette Database Explorer folosește cheile străine pentru a rezolva relațiile dintre tabele și, cunoscând relațiile, poate crea automat interogări pentru dumneavoastră.

După cum probabil vă amintiți, am trecut variabila $post în șablonul din PostPresenter::renderShow() și acum dorim să iterăm prin toate comentariile care au coloana post_id egală cu a noastră $post->id. Puteți face acest lucru apelând $post->related('comments'). Este atât de simplu. Priviți codul rezultat.

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

Și șablonul:

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

Observați atributul special n:tag-if. Știți deja cum funcționează n: attributes. Ei bine, dacă adăugați atributul cu tag-, acesta se va înfășura doar în jurul etichetelor, nu și în jurul conținutului acestora. Acest lucru vă permite să transformați numele comentatorului într-un link dacă acesta a furnizat adresa de e-mail. Aceste două linii sunt identice ca rezultate:

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

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