PHP - - By Taylor Ren

SitePoint’s PHP channel conducted its annual survey on the most popular framework of 2015 and the results were discussed here.

We saw some familiar names: Laravel, Symfony2, Phalcon, Silex, Slim, etc. But wait, what is this: Nette?

According to the survey result, it ranked number 3 in both “at Work” and “in Personal Projects”, just tailing the two giants: Laravel and Symfony2.

I had never heard of this framework before the survey results got published. A framework so popular is worth looking into. Thus, in this article, we will take a look at Nette, see what it can do and discuss some of the features.

Nette Logo

NOTE: We will base our review on the official Getting Started tutorial.

Installation and bootstrapping

Nette uses a self-bootstrap approach (similar to Laravel) with the support of composer:

composer create-project nette/sandbox demo

This will create a demo directory in the current one, and a sandbox project will be loaded into said folder.

Nette’s Getting Started tutorial guides us through building a simple blog app which features basic blog functions like: list all posts, view an individual post, create/edit a post, comments, security etc.

Let me show you what the app will look like when you finish the tutorial (I have not added any CSS to it so the overall appearance is quite rudimentary):

Demo screenshot

NOTE: This is served in a Vagrant box.

In the next few sections, we will look at some of the fundamental concepts in Nette. As I am a long-time user of Symfony2 (SF2), I will use that for comparison most of the time. Please note that the comparison notes are purely my personal view.

Project structure

Nette is considered to be an MVC framework, though its “Model” layer is almost missing. Its project structure also reflects this but is organized in a very different way:

Above project structure is taken from Nette’s tutorial

Like in SF2, a dedicated www (web in SF2) directory is there to hold the entry PHP file: index.php and also .htaccess rules to provide rewrite instructions for Apache. It will also contain static resources (CSS, JS, fonts, images, etc).

vendor will hold all the vendor libraries, as usual.

Many other folders will go under app:

  • config: As its name suggests, all the configuration resides here. Nette uses config.neon and config.local.neon to provide configuration information related to database, security, services, app-wide parameters, etc. Nette will load config.neon first and then config.local.neon. The latter will override the same parameters defined in the former, as is common in other frameworks as well. You can find out about the Neon file format here.

  • presenters and presenters/templates: these two folders cover the controller and the template (view) portion. Nette uses Latte as its template engine. More on Latte later, and no Cappuccino – sorry.

  • router: it holds the route factory class to customize pretty URIs and thus creates a bridge between a URI and a controller/action. More on this later.

Start from database

Nette comes with a handy tool called “Adminer” to mimic a PHPMyAdmin kind of functionality. The interface is clean and easy to use:

Adminer Screenshot

As an “embedded” tool, Adminer’s capability is limited so you may want to switch to your preferred database administration tool if this one doesn’t cut it. Also, it should be noted that we access Adminer from the adminer sub-directory in www. This may not be a good approach, especially in a production environment. This folder should be ignored in deployment phases – either via .gitignore, .gitattributes or otherwise and Nette should point this out in their docs.

Router

We are developing a simple blog app. We would want a URI showing a specific post (identified by its postId) to look like this: post/show/4 but not like this: post/show?postId=4.

Nette recommends using a router factory to manage the link between a URI (or a URI pattern) and its corresponding controllers/actions. The router factory is defined in app/router/RouterFactory.php:

class RouterFactory
{

	/**
	 * @return \Nette\Application\IRouter
	 */
	public static function createRouter()
	{
		$router = new RouteList();
        $router[] = new Route('post/show/<postId>', 'Post:Show');
		$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
		return $router;
	}
}

The definition of a route is straightforward:

  • A URI “pattern” with parameter(s): post/show/<postId>.
  • and a string in the form of “Controller:Action”: Post:Show.

The detailed Nette routing documentation can be found here.

To use this router factory, we must register a service in app/config/config.neon:

services:
	router: App\RouterFactory::createRouter

To generate a link in our template based on a route, we can use the syntax below:

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

I must admit this Latte syntax is a bit shorter than the corresponding Twig syntax. It uses {} for both echo statement and control statement. For example:

//To display a variable
{$var1}

//To run a foreach loop
{foreach $items as $item}
    ...
{/foreach}

Latte also has a powerful macro system to facilitate some common tasks. For example, the below code snippet will only display a list when $items is not null:

<ul n:if="$items">
...
</ul>

This can be handy when the user wants to display a certain section based on a returned result set.

Controllers and Actions

A presenter in Nette is the controller. All presenters are in the app/presenters folder and the file name should end with Presenter.php. As an example, we have PostPresenter for post related actions, SignPresenter for sign in / sign out related actions.

In a presenter file, we define a class to hold all the actions (methods) that can be invoked. For example, to show a particular post identified by its postId (and its related comments), the method will look like this:

namespace App\Presenters;

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

class PostPresenter extends BasePresenter
{

    private $database;

    public function __construct(Nette\Database\Context $database)
    {
        $this->database = $database;
    }

    public function renderShow($postId)
    {
        $post = $this->database->table('posts')->get($postId);

        if (!$post)
        {
            $this->error('Post not found');
        }

        $this->template->post     = $post;
        $this->template->comments = $post->related('comments')->order('created_at');
    }
    ... ...
}

In renderShow($postId), a $post is grabbed from the database by matching $postId. Then, a template will be rendered with variables (the post and related comments in this case).

We notice that this process is simple but hides a lot of details. For example, where is this database coming from? 

In app/config/config.local.neon, we can see this section (following the tutorial):

database:
	dsn: 'mysql:host=127.0.0.1;dbname=quickstart'
	user: root
	password: xxxxxx
	options:
		lazy: yes

This is a familiar database connection setup. When a controller/action is to be invoked, Nette transforms this DSN into a database object (or database context) and injects it into the constructor of that controller class. Thus, the database is accessible to all methods by means of Dependency Injection.

What about the template rendering? We just see the variable assignments but no explicit call to a “render” method. Well, this is also part of the Nette convention. When an action is given a render prefix, this action will render a template at the return of the method call.

In this case, the method is renderShow. This method is linked to URI like “post/show/3” as we defined earlier (in route definitions, the render prefix is ignored): $router[] = new Route('post/show/<postId>', 'Post:Show');.

renderShow will start to look for a template under app/presenters/templates looking for:

  • A directory named “Post” because this is the controller name of this renderShow action.
  • Then a template named Show.latte to populate all the variables and display it.

So let’s summarize the naming and mapping conventions used in Nette in the below chart:

Flowchart of Nette Routing

The Latte template engine

If you are familiar with Twig, you will find that Latte is quite easy to learn.

It uses {...} pair to escape from the regular HTML parsing and does not differentiate from a pure print (Twig equivalent: {{...}}) or a control ({%...%}). Also, a variable must be prefixed with the $ sign, or a string literal inside the { } pair will be treated as a macro and most likely cause a syntax error, saying “Unknown macro {xxxx}”.

There’s a handy feature when we’re dealing with an iteration on a result set, which is very common:

<ul n:if="$items">
{foreach $items as $item}
    <li id="item-{$iterator->counter}">{$item|capitalize}</li>
{/foreach}
</ul>

Besides the regular usage of a foreach loop, a condition has been put in place to decide if the below <ul> section should be displayed or not. The <ul> section will only be there when there is at least one item in $items. With the help of this macro, we can save some lines and avoid using an if...endif pair.

Latte supports template inheritance, template including, filters, and many other cool features. Please visit its official documentation for details.

Auth and Forms

The official documentation on access control is a good starting point for us.

Nette supports in-memory and database credentials. By using in-memory authentication, we use the below snippet:

$authenticator = new Nette\Security\SimpleAuthenticator(array(
    'john' => 'IJ^%4dfh54*',
    'kathy' => '12345', // Kathy, this is a very weak password!
));
$user->setAuthenticator($authenticator);

Then, the system can explicitly make a user log in using:

$user->login($username, $password);

where the username and password can be obtained from a form submission.

Nette supports roles and ACL (Access Control List) and uses an “Authorizator” to enforce the authorization.

Firstly, we can create some roles with hierachy:

$acl = new Nette\Security\Permission;

//Define a guest role and a registered user role

$acl->addRole('guest');
$acl->addRole('registered', 'guest');

In the above code, role register inherits from guest.

Then, we define a few resources that a user may access:

$acl->addResource('article');
$acl->addResource('comments');
$acl->addResource('poll');

Finally, we set authorization rules:

$acl->allow('guest', array('article', 'comments', 'poll'), 'view');
$acl->allow('registered', 'comments', 'add');

So a guest can view an article, comments and a poll and a registered user, besides the privileges inherited from guest, can also add a comment.

I really don’t like this kind of access control. Even an annotation outside of a controlled method itself or the use of a decorator would be better than this, in my opinion. And I would say a centralized file (SF2’s security.yml) is the best practice: neat, clean, and flexible.

The forms are generated in their respective presenters. In particular, the form creation includes a callback event handler to process a successful form submission.

protected function createComponentCommentForm()
    {
        $form              = new Form;
        $form->addText('name', 'Your name:')->setRequired();
        $form->addText('email', 'Email:');
        $form->addTextArea('content', 'Comment:')->setRequired();
        $form->addSubmit('send', 'Publish');
        $form->onSuccess[] = [$this, 'commentFormSucceeded'];

        return $form;
    }

But, this is not the action of that form to be rendered.

For example, let’s look at the above code for renderShow to display a post detail page and a form for readers to enter comments. In the presenter, we only assigned a post variable and a comments variable to hold related comments. The comment input form is rendered in the template app/presenters/templates/Post/Show.latte:

<h2>Post new comments</h2>
{control commentForm}

The source of that page is extracted below:

<h2>Post new comments</h2>
<form action="/sandbox/www/post/show/4" method="post" id="frm-commentForm">

<table>
<tr class="required">
	<th><label for="frm-commentForm-name" class="required">Your name:</label></th>

	<td><input type="text" name="name" id="frm-commentForm-name" required data-nette-rules='[{"op":":filled","msg":"This field is required."}]' class="text"></td>
</tr>

...
<tr>
	<th></th>

	<td><input type="submit" name="send" value="Publish" class="button"></td>
</tr>
</table>

<div><input type="hidden" name="do" value="commentForm-submit"></div>
</form>

We see clearly that the form action assigned is /sandbox/www/post/show/4, which is essentially the URI that displays the post itself. There is no place in the source code to indicate that a hook to the commentFormSucceeded method exists.

This kind of “inside linking” may confuse Nette beginners a lot. I mean, to have a separate method to process the form is a common practice, and thus to have a URI assigned for such a process is also reasonable.

Nette using a callback/event handler to do this is also fine but there is certainly something missing or not clearly explained between when a user clicks the “Submit” button and the input is persisted in the database.

We know the persistence is performed in a method called commentFormSucceeded and we implemented that feature by ourselves. But how they are hooked up is not clear.

Other cool features

Nette comes with a debugger called “Tracy“. In debug mode, we will see a small toolbar at the bottom right corner of our page, telling us important page information:

It can be disabled in production mode by changing app/bootstrap.php:

$configurator->setDebugMode(false); // "true" for debug mode

NOTE: Please purge the temp/cache folder contents if you encounter any issues after changing from development mode to production mode.

Nette also includes a test suite called “Tester”. The usage is also straightforward. See here for details.

Final Thoughts

Nette is a relatively new framework. Its 2.0 release was about 3 years ago. It came to be noticed by many of us thanks to the SitePoint survey.

Its Github issue tracker is very active. My two questions posted there got answered in less than 10-30 minutes and both lead to the correct solution, but its documentation needs a lot of work. During my attempts to set up the tutorial app following its docs, I found a lot of typos and missing explanations.

If I could give SF2 a score of 10 – not saying SF2 is perfect but just for comparison’s sake – my initial score for Nette is between 7 to 8. It is mature, well written, easy to learn, equipped with advanced features but also has a few areas that need improving.

Are you familiar with Nette? Feel free to share your views and comments, too.

Sponsors