Single Post Page
Let’s add another page to our blog, which will display the content of one particular post.
We need to create a new render method that will retrieve one specific post and pass it to the template. Putting this method in
HomePresenter
is not very neat because it’s about a single post, not the homepage. So, let’s create a new class
PostPresenter
and place it in app/Presentation/Post/
. This presenter will also need a database
connection, so we'll add the constructor which will require the database connection.
The PostPresenter
could look like this:
<?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);
}
}
We must not forget to specify the correct namespace App\Presentation\Post
, which depends on the presenter mapping configuration.
The renderShow
method requires one argument – the ID of the post to be displayed. It then loads the post from
the database and passes it to the template.
In the Home/default.latte
template, we add a link to the Post:show
action:
...
<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2>
...
The {link}
tag generates a URL address that points to the Post:show
action. It also passes the post
ID as an argument.
The same can be written concisely using an n:attribute:
...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...
The n:href
attribute is similar to the {link}
tag.
However, the template for the Post:show
action does not exist yet. We can try opening the link to this post. Tracy will show an error because the template Post/show.latte
doesn't exist
yet. If you see a different error message, you probably need to enable mod_rewrite
on your web server.
So, we'll create Post/show.latte
with this content:
{block content}
<p><a n:href="Home:default">← back to posts list</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>
Now let’s review the individual parts of the template.
The first line starts the definition of a block named “content”, just like on the homepage. This block will again be
displayed within the main layout template. As you can see, the end tag {/block}
is missing. It is optional.
The second line provides a backlink to the list of blog posts, allowing the user to navigate easily between the post list and a
specific post. We use the n:href
attribute again, so Nette will take care of generating the URL. The link points to
the default
action of the Home
presenter (you could also write n:href="Home:"
, as the
default
action name can be omitted; it will be added automatically).
The third line formats the date using a filter, which we are already familiar with.
The fourth line displays the title of the blog post within an <h1>
HTML tag. This tag contains an
attribute you might not recognize (n:block="title"
). Can you guess what it does? If you’ve read the previous
section carefully, you already know it's an n:attribute
. This is another example, equivalent to:
{block title}<h1>{$post->title}</h1>{/block}
Simply put, this block redefines the block named title
. This block is already defined in the main layout
template (/app/Presentation/@layout.latte:11
), and just like method overriding in OOP, this block overrides the one
in the main template. Therefore, the page's <title>
will now contain the title of the displayed post, and all
we needed was this simple n:block="title"
attribute. Awesome, right?
The fifth and final line of the template displays the full content of the specific post.
Checking Post ID
What happens if someone alters the ID in the URL and inserts a non-existent id
? We should show the user a nice
“page not found” error. Let’s modify the render method in PostPresenter
slightly:
public function renderShow(int $id): void
{
$post = $this->database
->table('posts')
->get($id);
if (!$post) {
$this->error('Post not found');
}
$this->template->post = $post;
}
If the post cannot be found, calling $this->error(...)
will display a 404 page with an understandable message.
Note that in the development environment (on localhost), you won’t see this error page. Instead, Tracy will show the exception
with full details, which is quite convenient for development. If you want to test both modes, simply change the argument passed to
the setDebugMode
method in Bootstrap.php
.
Summary
We have a database with posts and a web application with two views – the first displays an overview of all posts, and the second displays one specific post.