Strona z postem

Teraz stworzymy kolejną stronę bloga, która będzie wyświetlać jeden konkretny post.

Musimy stworzyć nową metodę render, która pobierze jeden konkretny artykuł i przekaże go do szablonu. Umieszczenie tej metody w HomePresenter nie jest zbyt eleganckie, ponieważ mówimy o artykule, a nie o stronie głównej. Stwórzmy więc PostPresenter w app/Presentation/Post/. Ten presenter również potrzebuje połączenia z bazą danych, więc ponownie napiszemy konstruktor, który będzie wymagał połączenia z bazą danych.

PostPresenter mógłby więc wyglądać tak:

<?php
namespace App\Presentation\Post;

use Nette;
use Nette\Application\UI\Form;

final class PostPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Nette\Database\Explorer $database,
	) {
	}

	public function renderShow(int $id): void
	{
		$this->template->post = $this->database
			->table('posts')
			->get($id);
	}
}

Nie możemy zapomnieć o podaniu poprawnej przestrzeni nazw App\Presentation\Post, która podlega ustawieniom mapowania presenterów.

Metoda renderShow wymaga jednego argumentu – ID jednego konkretnego artykułu, który ma być wyświetlony. Następnie ten artykuł wczytuje z bazy danych i przekazuje go do szablonu.

Do szablonu Home/default.latte wstawimy link do akcji Post:show.

...
<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2>
...

Znacznik {link} generuje adres URL, który wskazuje na akcję Post:show. Przekazuje również ID posta jako argument.

To samo możemy zapisać skrótowo za pomocą n:atrybutu:

...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...

Atrybut n:href jest odpowiednikiem tagu {link}.

Dla akcji Post:show jednak jeszcze nie istnieje szablon. Możemy spróbować otworzyć link do tego posta. Tracy wyświetli błąd, ponieważ szablon Post/show.latte jeszcze nie istnieje. Jeśli widzisz inny komunikat błędu, prawdopodobnie będziesz musiał włączyć mod_rewrite na serwerze WWW.

Stworzymy więc szablon Post/show.latte z następującą zawartością:

{block content}

<p><a n:href="Home:default">← wróć do listy postów</a></p>

<div class="date">{$post->created_at|date:'j F Y'}</div>

<h1 n:block="title">{$post->title}</h1>

<div class="post">{$post->content}</div>

Teraz przejdziemy przez poszczególne części szablonu.

Pierwsza linia zaczyna definicję bloku o nazwie “content”, tak samo jak było to na stronie głównej. Ten blok zostanie ponownie wyświetlony w głównym szablonie. Jak widzisz, brakuje końcowego znacznika {/block}. Jest on bowiem opcjonalny.

W następnej linii znajduje się link powrotny do listy artykułów bloga, dzięki czemu użytkownik może łatwo poruszać się między listą artykułów a jednym konkretnym. Ponieważ używamy atrybutu n:href, Nette samo zadba o generowanie linków. Link wskazuje na akcję default presentera Home (możemy również napisać n:href="Home:", ponieważ akcja o nazwie default może być pominięta, zostanie uzupełniona automatycznie).

Trzecia linia formatuje wyświetlanie daty za pomocą filtra, który już znamy.

Czwarta linia wyświetla tytuł bloga w tagu HTML <h1>. Ten tag zawiera atrybut, którego być może nie znasz (n:block="title"). Zgadniesz, co robi? Jeśli czytałeś poprzednią część uważnie, już wiesz, że jest to n:atrybut. To jest ich kolejny przykład, który jest równoważny z:

{block title}<h1>{$post->title}</h1>{/block}

Mówiąc prościej, ten blok przedefiniowuje blok o nazwie title. Ten blok jest już zdefiniowany w głównym szablonie layout (/app/Presentation/@layout.latte:11) i tak jak w przypadku nadpisywania metod w OOP, dokładnie tak samo ten blok w głównym szablonie zostanie nadpisany. Tak więc <title> strony teraz zawiera tytuł wyświetlanego posta, a wystarczyło nam do tego użyć tylko jednego prostego atrybutu n:block="title". Świetnie, prawda?

Piąta i ostatnia linia szablonu wyświetla całą treść jednego konkretnego posta.

Sprawdzanie ID posta

Co się stanie, jeśli ktoś zmieni ID w URL i wstawi jakieś nieistniejące id? Powinniśmy zaoferować użytkownikowi ładny błąd typu “strona nie została znaleziona”. Zmodyfikujemy więc trochę metodę render w PostPresenter:

public function renderShow(int $id): void
{
	$post = $this->database
		->table('posts')
		->get($id);
	if (!$post) {
		$this->error('Strona nie została znaleziona');
	}

	$this->template->post = $post;
}

Jeśli post nie może zostać znaleziony, wywołaniem $this->error(...) wyświetlimy stronę błędu 404 z czytelnym komunikatem. Uważaj na to, że w trybie deweloperskim (localhost) tej strony błędu nie zobaczysz. Zamiast tego pokaże się Tracy ze szczegółami wyjątku, co jest dość wygodne podczas rozwoju. Jeśli chcemy wyświetlić oba tryby, wystarczy zmienić argument metody setDebugMode w pliku Bootstrap.php.

Podsumowanie

Mamy bazę danych z postami i aplikację internetową, która ma dwa widoki – pierwszy wyświetla przegląd wszystkich postów, a drugi wyświetla jeden konkretny post.