Strona pojedynczego wpisu

Dodajmy do naszego bloga kolejną stronę, która będzie wyświetlać treść jednego konkretnego wpisu.

Musimy utworzyć nową metodę render, która pobierze jeden konkretny wpis i przekaże go do szablonu. Umieszczenie tej metody w HomePresenter nie jest zbyt eleganckie, bo chodzi o pojedynczy wpis, a nie o stronę główną. Utwórzmy więc nową klasę PostPresenter i umieśćmy ją w app/Presentation/Post/. Ten presenter też będzie potrzebował połączenia z bazą danych, więc dodamy konstruktor, który będzie tego połączenia wymagał.

PostPresenter mógłby 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 zależy od konfiguracji mapowania presenterów.

Metoda renderShow wymaga jednego argumentu: ID wpisu, który ma zostać wyświetlony. Następnie wczytuje wpis z bazy danych i przekazuje go do szablonu.

W szablonie Home/default.latte dodajemy odnośnik do akcji Post:show:

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

Tag {link} generuje adres URL wskazujący na akcję Post:show. Przekazuje też jako argument ID wpisu.

To samo można zapisać zwięźle za pomocą n:atrybutu:

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

Atrybut n:href jest podobny do tagu {link}.

Szablon dla akcji Post:show jednak jeszcze nie istnieje. Możemy spróbować otworzyć odnośnik do tego wpisu. Tracy pokaże błąd, bo szablon Post/show.latte jeszcze nie istnieje. Jeśli widzisz inny komunikat o błędzie, prawdopodobnie musisz włączyć na swoim serwerze webowym mod_rewrite.

Utworzymy więc Post/show.latte z taką treścią:

{block content}

<p><a n:href="Home:default">← powrót do listy wpisów</a></p>

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

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

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

Przejrzyjmy teraz poszczególne części szablonu.

Pierwsza linia zaczyna definicję bloku o nazwie “content”, tak samo jak na stronie głównej. Blok ten znów zostanie wyświetlony w ramach głównego szablonu layoutu. Jak widzisz, brakuje tagu końcowego {/block}. Jest on opcjonalny.

Druga linia daje odnośnik powrotny do listy wpisów blogowych, pozwalając użytkownikowi łatwo poruszać się między listą wpisów a konkretnym wpisem. Znów używamy atrybutu n:href, więc o wygenerowanie URL zadba Nette. Odnośnik wskazuje na akcję default presentera Home (mógłbyś napisać też n:href="Home:", bo nazwę akcji default można pominąć, zostanie dodana automatycznie).

Trzecia linia formatuje datę filtrem, który już znamy.

Czwarta linia wyświetla tytuł wpisu blogowego wewnątrz tagu HTML <h1>. Tag ten zawiera atrybut, którego możesz nie rozpoznawać (n:block="title"). Zgadniesz, co robi? Jeśli uważnie przeczytałeś poprzednią sekcję, już wiesz, że to n:atrybut. To kolejny przykład, równoważny z:

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

Upraszczając, blok ten redefiniuje blok o nazwie title. Blok ten jest już zdefiniowany w głównym szablonie layoutu (/app/Presentation/@layout.latte:11) i podobnie jak nadpisywanie metod w OOP nadpisuje ten z szablonu głównego. <title> strony będzie więc teraz zawierać tytuł wyświetlanego wpisu, a wystarczył nam do tego prosty atrybut n:block="title". Świetne, prawda?

Piąta i ostatnia linia szablonu wyświetla pełną treść konkretnego wpisu.

Sprawdzanie ID wpisu

Co się stanie, jeśli ktoś zmieni ID w URL i wstawi nieistniejące id? Powinniśmy pokazać użytkownikowi ładny błąd “strona nie znaleziona”. Zmodyfikujmy nieco metodę render w PostPresenter:

public function renderShow(int $id): void
{
	$post = $this->database
		->table('posts')
		->get($id);
	if (!$post) {
		$this->error('Post not found');
	}

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

Jeśli wpisu nie da się znaleźć, wywołanie $this->error(...) wyświetli stronę 404 ze zrozumiałym komunikatem. Zwróć uwagę, że w środowisku deweloperskim (na localhoście) tej strony błędu nie zobaczysz. Zamiast niej Tracy pokaże wyjątek z pełnymi szczegółami, co jest przy tworzeniu całkiem wygodne. Jeśli chcesz przetestować oba tryby, po prostu zmień argument przekazywany metodzie setDebugMode w Bootstrap.php.

Podsumowanie

Mamy bazę danych z wpisami i aplikację webową z dwoma widokami: pierwszy wyświetla przegląd wszystkich wpisów, a drugi jeden konkretny wpis.