By Taylor Ren

Nette Framework: First Impressions

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.


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:

	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

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

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">

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):

	dsn: 'mysql:host=;dbname=quickstart'
	user: root
	password: xxxxxx
		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>

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!

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('registered', 'guest');

In the above code, role register inherits from guest.

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


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">

<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>


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

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

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.

  • Thanks for the quick overview! It misses two important concepts: components and signals.

    Components are really powerful aproach which allows decomposition of the functionality. Forms are actually components too! (As you can see, you defined them in createComponent factory method.). Sadly, there is too litle doc about this ( Basically, components allow creating a tree structure which encapsulates functionality into separated classes without polution a presenter.

    Action vs. render
    Nette’s presenter implements special methods for the lifecycle (as documented here: The action consist of “action” and “render” phase. The “action” phase should take care of fetching the main information, like post. If the post doesn’t exist, the sition should be handled here ($this->error()). The render phase should fetch the remaining data (like post’s comments) and prepare other thins connected with rendering. Signals are special events which may be triggered between the action and render phases.

    Signals are special events that are processed before the render phase. Signal is not connected to specific action, rather to the presenter itself. Signal’s purpose is to handle classic post-save-redirect request, like form submittion. The added value is the behaviour during the AJAX processing. If you call the signal via AJAX request, you don’t need to redirect, it’s ok only to redraw some snippets in the template.

    This may better explain processing of the article’s form: form’s processing is internally handle by signals, even it’s not obvious for the frist impression. Article misses the form success handler, but basically the handler should contain $this->redirect() call, specifically $this->redirect(‘this’); which will call the same URL without the POST data. The generated form html has hidden input with name “do” and it’s value is the signal name. The defined form’s success callback is triggered from the form’s success signal, which is defined in the NetteFormsForm class. (The signal name consist of the path to the component and signal name separated by dash – “commentForm-submit”.)

  • Hi Taylor, thank you for the article! We’re working hard to make the quickstart really good so every feedback is welcome. But let me explain better the parts you might have missed.

    There is prepared CSS file for the blog application that you can use, mentioned here It should look a bit nicer with it

    The quickstart doesn’t go into much details about model, because Nette tries not to dictate you what to use.
    The default and most straightforward for most people is to use something that does MySQL queries and that that’s what NetteDatabase (PDO wrapper) is for. There is also the NDBT tool (the Table class) that extends NetteDatabase and allows for the fancy fluent syntax which allows extremely effective querying of database – it’s quite fascinating how smart the tool is :)
    But if you wanna, you can simply ignore the nettedatabase package (even uninstall it) and use for example an extension for Doctrine is a standalone tool (which in fact is a lot better and faster than PhpMyAdmin), that is included in the sandbox for simplicity, so the begginer doesn’t have to struggle with installing other tools to manage database. Power users usually start with which is cleaned sandbox that doesn’t include the Adminer.

    About Latte, one of it’s most powerful features is that it’s context-aware and knows that in javascript or CSS you have to escape differenty. It can even properly escape variables used in javascript that is in onclick attribute in HTML. How cool is that :)

    About the auth, it’s just defaults, you don’t really have to use it. Nette has really powerful DI Container, and basically every single part of Nette can be replaced or extended.

    About the forms, have you noticed the hidden input with name “do”? That’s where Nette stores what form was submitted, the “do” parameter is for signals. Signal is a subrequest for the current action more on that here

    You don’t actually have to disable tracy in production, it has autodetection that knows when you’re not on localhost and automatically goes into production mode which doesn’t render bluescreens but stores them in logs/ directory.

    Nette itself is quite mature, first release came in 2006 . But you’re right, that the documentation still needs a lot of work.

  • Good article, great to see Nette noticed :)) A quick note to Adminer, there’s a code directly in sandbox preventing Adminer usage from outside of localhost, see source:

  • Jan Tvrdík

    Great article! Few notes:

    ad “Model layer is almost missing”:
    QuickStart uses NetteDatabase which is very good at efficient database traversing if you don’t need lots of abstractions. For complex applications use Doctrine as you would with SF2. Kdyby/Doctrine ( provides easy to use integration with Nette (similar to DoctrineBundle for SF2).

    ad “something missing or not clearly explained between when a user clicks the “Submit” button”:
    Nette uses concept called “subrequests” ( The hidden “do” input is processed before the render method is called ( and results in calling the form factory and firing the “submit” signal (or subrequest) on form object which results in calling user-defined form events (usually onSuccess).

  • > 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.

    That’s, I believe, the difference between the MVC and MVP architecture. In MVC, form submission is a separate action and requires a new request to a new view to be sent and processed by the controller, whereas MVP allows for tighter, event-based interaction between the presenter and the view. In Nette, you can do things without changing the view via so-called “signals” (see ) and that’s also how form submissions are handled (notice the hidden input named ‘do’ – that triggers the ‘submit’ handler which in turn calls the hooked callback).

  • Miloslav Hůla

    Hi Taylor,

    many thanks for this article! I’m a big fan of Nette and it is very nice to read a purely technical opinion without thoughtless emotions.

    My few comments:

    “the missing model layer” – That’s true. The Nette Database with a few service classes are usually used in simple applications. For a complex model layer, Doctrine is quite popular here. In addion, there are some 3rd party addons ( ).

    “Controllers and Actions” – There ( ) is a complete Presenter’s life cycle scheme. A “render” or “action” methods doesn’t need to exists at all. It is sufficient that an template for action exists.

    “Auth and Forms” – the createComponentCommentForm method name is a convention. Nette offers a component tree model ( ). Component can have sub-components and so on. Actually, Presenters are components too. In your example, Latte macro {control commentForm} is looking for a component named ‘commentForm’ and if it does not exist in a tree, it tries to call ‘createComponentCommentForm()’. So, this method is just a factory.

    Router and component model process URL parameters for a whole tree (for all sub-components). You don’t need to care about it and you only need to setup correct event handler.

    Best regards, Milo

  • Robin Martinez


  • I am impressed by the loyalty of Nette’s fanbase – how you all rallied to explain things further in no more than a day after the publication is awe inspiring. I wish every open source project had such an enthusiastic following, rather than a fanatical one (like in Laravel).

  • With the “foreach” example, you might have used n:foreach macro as well for the sake of simplicity. These features make the Latte engine really stand out for me.

  • Lukáš Huňka

    Hello Taylor,

    only one note about Adminer … I am not sure If u tried running it on production server but the version included with NETTE Sandbox is strictly forbidden only to local use by default :)

  • Taylor Ren

    Hi all,

    First of all, much appreciated to all’s valuable input and quick comments.

    I am not a Nette user in a serious sense but when I noticed such a framework pops out, I feel impulsed to write something about it. Now I am more sure that I had made a right decision and thanks again to Bruno for approving this topic and his great effort to make this article a better one.

    One thing I am sure: this should not be the last article in Sitepoint on Nette. The stuffs I covered is only the tip of the iceberg. I would love to see more in-depth articles on Nette coming along.



  • nette_bench

    What about Nette’s performance? AFAIK it’s pretty slow, at least version 1.X used to be. What about it’s workflow control – is it still using exceptions to handle routing (this is what made it so slow) – which was weird and wrong design approach? Nette is more like “Czech baby”, that explains loyalty of their user base – which does not necessary mean it’s properly designed or has great performance. At our work we were evaluating couple of frameworks cca 2-3 years ago and Nette came out like a slow framework with bad design, but still better regarding performance than ZF1. But it was some time ago so things might have changed for better…

    • Jan Tvrdík

      Nette 2.3 (latest stable) did improve on performance ( However doing independent performance comparison is very difficult. If framework is tested by people who never used it before, they often do some stupid mistake such as measuring speed in development mode instead of production mode.

    • Matěj Račinský

      Hi, I do not know, which version is on your mind, because there are versions 0.X and 2.X, 1.X was skipped. You can see it in github versions here
      Nette 0.X and 2.X are very different and it went through deep refactoring. Many things in Nette 0.X were bad, and devs learnt from it, many practises from Nette 0.X, such as service locator, as now antipattern in Nette 2.X and proper Dependency injection is used nowadays.

  • amazingnettejustarouterhelper

    I just discovered Nette too and looks very good.

    About database there is this ActiveRecord clone on php:

    Maybe Nette team can write a router helper with sinatra syntax like that:

    can be done like:

    NetteSimpleRouter::route(‘GET /’, function(){
    echo ‘It”s a very simple router’;

    • Jan Tvrdík

      Similar feature is already supported (although AFAIK not widely used).

      $router[] = new Route('about', function ($presenter) {
      return $presenter->createTemplate('about.latte');

  • Sam Wong

    Hi Taylor,

    Thanks for introduce this. Does nette has schema builder like laravel migration which easier to control the upgrade / rollback of database schema? I can’t find in their documentation.

    • Jan Tvrdík

      No, Nette does not have any built-in tool for database migrations. But there are many 3rd party libraries which you can choose from– I personally use and contribute to NextrasMigrations, others use for example Doctrine Migrations. You can probably use Laravel Migrations with Nette as well.

  • I’m deeply interested in Nette, where can I find a community/support like Google Groups or /r/PHP ?

  • Miroslav Koula

    From other perspective I think it would be nice also to mention that Nette is Framework with history, ’cause I still remember Fabien Potencier presenting SF2 Alpha on WebExpo Conference in 2010 when there were already dozens of huge projects running on Nette with features that comes to SF2 years later.

    The people outside of Czech Republic have usually no idea about the Czech market and projects. Besides of Nette, Google is not number 1 on search market. Still laughing? It’s not a joke. So in my eyes, eyes of somebody who is living in Berlin couple of years and seeing the Czech market a bit out of the box, Nette nowadays could be used on Pareto’s principle at about 80% projects made in Czech Republic. If you search for the job position, many companies are searching not for PHP Developer, but for Nette Developer :-) It’s more like industry standard now. Maybe that’s why the survey poll was influenced by Nette users.

    There’s website – which is nice, but does not tell you many real information. So briefly Nette is used on Czech Nr. 1 Discount Portal (aka Groupon) – Slevomat ( and some competitors too, several publishing houses (Vltava Labe Press, Internet Info, Mladá Fronta – they all have several titles of different content from daily news, IT special topics, healthy living etc.). It’s also used in Czech IMDB clone ČSFD (, Food Delivery Nr. 1 player Dáme Jídlo ( and many many others (Socialbakers, Testomato etc.)

    I just want to present a bit from business perspective that Nette Framework is used on many successful projects which of course are years in production and on really high traffic.

    I am working in Germany last 3 years in ZF1, Yii, Symfony2 and lately in Phalcon and in many cases I can say that something which is really easy to do in Nette I am missing in the above mentioned ones. The only negative is missing full documentation, mainly between versions, but it’s better and better and any book, at least a book for beginners.

  • obiesco

    Its Official!!! its now nette. I have been looking for a framework to switch to after CI’s transition

Get the latest in PHP, once a week, for free.