Version 2.4

Model

As our application grows, we soon find out that we need to perform similar database operations in various locations and in various presenters, for example acquiring newest published articles. If we improve our application by adding a flag to articles to indicate work-in-progress state, we must also go through all locations in our application and add a where clause to make sure only finished articles are selected.

At this point, direct work with the database becomes insufficient and it will be smarter to help ourselves with a new function that returns published articles. And when we add another clause later (for example not to display articles with future date), we only edit our code in one place.

We'll place the function into the ArticleManager class and call it getPublicArticles().

We'll create our model class ArticleManager in the directory app/model/ to take care of our articles. Let's place it into the ArticleManager.php file.

<?php

namespace App\Model;

use Nette;

class ArticleManager
{
    use Nette\SmartObject;

    /**
     * @var Nette\Database\Context
     */
    private $database;

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

    public function getPublicArticles()
    {
        return $this->database->table('posts')
            ->where('created_at < ', new \DateTime)
            ->order('created_at DESC');
    }
}

We've created the class using a constructor that passes database Context.

If we didn't use the framework and the strength of DI container, we would have to first create the following instances: Nette\Database\Connection, Nette\Database\Structure, Nette\Database\Conventions and Nette\Caching\Storages\FileStorage (that also needs some dependencies such as database username or password). All of that only to create a Nette\Database\Context instance that would later be used to create an ArticleManager instance.

...
// everything needed for Context
$dbConnection = new Nette\Database\Connection(...);
$dbStructure = new Nette\Database\Structure(...);
$dbConventions = new Nette\Database\Conventions\DiscoveredConventions(...);
$dbCache = new Nette\Caching\Storages\FileStorage(...);

// creating Context
$dbContext = new Nette\Database\Context($dbConnection, $dbStructure, $dbConventions, $dbCache);

// creating ArticleManager
$articleManager = new App\Model\ArticleManager($dbContext)
...

All that typing only to get a working ArticleManager? We wouldn't want that. That's why we are using a DI container which we will treat as a box that generates this necessary code and returns only the required object.

The only thing we need is to tell the DI container to return the required object. We can sort this out in application configuration. We'll add a line with full class name to the app/config/config.neon file under services.

services:
    router: App\RouterFactory::createRouter
    - App\Model\ArticleManager

That way we tell Nette and its DI container the following: “If somebody wants an instance of this class, you know where to find it. Give that instance all it needs and prepare it for being used in other classes.” A class registered in this way is then called a service.

We will switch to HomepagePresenter.php which we will edit so that we get rid of the dependency on Nette\Database\Context replacing that with a new dependency on our new class.

<?php

namespace App\Presenters;

use Nette;
use App\Model\ArticleManager;


class HomepagePresenter extends Nette\Application\UI\Presenter
{
    /** @var ArticleManager */
    private $articleManager;

    public function __construct(ArticleManager $articleManager)
    {
        $this->articleManager = $articleManager;
    }

    public function renderDefault()
    {
        $this->template->posts = $this->articleManager->getPublicArticles()->limit(5);
    }
}

In the use section, we are using App\Model\ArticleManager. That way we can shorten our PHP code only to ArticleManager (don't be afraid of it, it works even in comments and your clever IDE should be able to handle it)

We request ArticleManager in a constructor which we will assign to the $articleManager attribute and in the renderDefault method we are calling the getPublicArticles() method. We are also calling the limit(5) method on the result of that function.

The ArticleManager class asks for Nette\Database\Context in a constructor and because this class is registered in the DI container, the container creates this instance and passes it. DI this way creates an ArticleManager instance for us and passes it in a constructor to the HomepagePresenter class which asked for it. Sort of a Matryoshka doll of code. :) All the components only request what they need and they don't care where and how it gets created. The creation is handled by Nette DI container.