Blog Home Page

Let’s create the home page displaying your recent posts.

Before we start, you should know at least some basics about Model-View-Presenter design pattern (similar to MVC):

  • Model – data manipulation layer. It is completely separated from the rest of the application. It only communicates with presenters.
  • View – a front-end definition layer. It renders requested data to the user using templates.
  • Presenter (or Controller) – a connection layer. Presenter connects Model and View. Handles requests, asks Model for data and then passes them to the current View.

In case of a very simple application like our blog, the Model layer will actually consist only of queries to the database itself – we don't need any extra PHP code for it. We only need to create Presenter and View layers. In Nette, each Presenter has its own Views, so we will continue with both simultaneously.

Creating the Database with Adminer

To store the data, we will use the MySQL database because it is the most common choice among web developers. But if you don’t like it, feel free to use a database of your choice.

Let’s prepare the database which will store our blog posts. We can start very simply – just with a single table for posts.

To create the database we can download Adminer, or you can use another tool for database management.

Let’s open Adminer and create a new database called quickstart.

Create a new table named posts and add these columns:

  • id int, click on autoincrement (AI)
  • title varchar, length 255
  • content text
  • created_at timestamp

It should look like this:

CREATE TABLE `posts` (
	`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`title` varchar(255) NOT NULL,
	`content` text NOT NULL,
	`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARSET=utf8;

It’s very important to use the InnoDB table storage. You will see the reason later. For now, just choose that and submit. You can hit Save now.

Try adding some sample blog posts before we implement the capability of adding new posts directly from our application.

INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES
(1,	'Article One',	'Lorem ipusm dolor one',	CURRENT_TIMESTAMP),
(2,	'Article Two',	'Lorem ipsum dolor two',	CURRENT_TIMESTAMP),
(3,	'Article Three',	'Lorem ipsum dolor three',	CURRENT_TIMESTAMP);

Connecting to the Database

Now, when the database is created and we have some posts in it, it’s the right time to display them on our new shiny page.

First, we need to tell our application about which database to use. The database connection configuration is stored in config/common.neon. Set the connection DSN and your credentials. It should look like this:

database:
	dsn: 'mysql:host=127.0.0.1;dbname=quickstart'
	user: *enter user name*
	password: *enter password here*

Be aware of indenting while editing this file. NEON format accepts both spaces and tabs but not both together! The configuration file in the Web Project uses tabs as default.

Injecting the Database Connection

The presenter HomePresenter, which will list the articles, needs a database connection. To receive it, write a constructor like this:

<?php
namespace App\Presenters;

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

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

	// ...
}

Loading Posts from the Database

Now let’s fetch the posts from the database and pass them to the template, which will then render the HTML code. This is what the so-called render method is for:

public function renderDefault(): void
{
	$this->template->posts = $this->database
		->table('posts')
		->order('created_at DESC')
		->limit(5);
}

The presenter now has one render method renderDefault() that passes data to a view called default. Presenter templates can be found in app/Presenters/templates/{PresenterName}/{viewName}.latte, so in this case the template will be located in app/Presenters/templates/Home/default.latte. In the template, a variable named $posts is now available, which contains the posts from database.

Template

There is a generic template for the whole page (called layout, with header, stylesheets, footer, …) and then specific templates for each view (e.g. for displaying the list of blog posts), which can override some of layout template parts.

By default, the layout template is located in templates/@layout.latte, which contains:

...
{include content}
...

{include content} inserts a block named content into the main template. You can define it in the templates of each view. In this case, we will edit the file Home/default.latte like this:

{block content}
	Hello World
{/block}

It defines the content block, which will be inserted into the layout. If you refresh the browser, you’ll see a page with text “Hello world” (in source code also with HTML header and footer defined in @layout.latte).

Let’s display the blog posts – we will edit the template like this:

{block content}
	<h1>My blog</h1>

	{foreach $posts as $post}
	<div class="post">
		<div class="date">{$post->created_at|date:'j. n. Y'}</div>

		<h2>{$post->title}</h2>

		<div>{$post->content|truncate:256}</div>
	</div>
	{/foreach}
{/block}

If you refresh your browser, you’ll see the list of your blog posts. The list isn't very fancy or colorful, so feel free to add some shiny CSS to www/css/style.css and link it in a layout:

	...
	<link rel="stylesheet" href="{$basePath}/css/style.css">
</head>
...

The {foreach} tag iterates over all posts passed to the template in $posts variable and displays a piece of HTML code for each post. Just like a PHP code would.

The |date thing is called a filter. Filters are used to format the output. This particular filter converts a date (e.g. 2013-04-12) to its more readable form (12. 4. 2013). The |truncate filter truncates the string to the specified maximum length, and adds a ellipsis to the end if the string is truncated. Since this is a preview, there is no point in displaying the full content of the article. Other default filters can be found in the documentation or you can create your own if needed.

One more thing. We can make the code a little bit shorter and thus simpler. We can replace Latte tags with n:attributes like this:

{block content}
	<h1>My blog</h1>

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

		<h2>{$post->title}</h2>

		<div>{$post->content}</div>
	</div>
{/block}

The n:foreach, simply wraps the div with a foreach block (it does exactly the same thing as the previous block of code).

Summary

We have a very simple MySQL database with some blog posts in it. The application connects to the database and displays a simple list of the posts.