Model | First Application
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 the newest published articles. If we improve our application by adding a flag to articles to indicate a 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 a 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.
namespace App\Model;
use Nette;
class ArticleManager
{
use Nette\SmartObject;
private Nette\Database\Explorer $database;
public function __construct(Nette\Database\Explorer $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 Explorer.
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\Explorer
instance that would later be used to create an
ArticleManager
instance.
...
// everything needed for Explorer
$dbConnection = new Nette\Database\Connection(...);
$dbStructure = new Nette\Database\Structure(...);
$dbConventions = new Nette\Database\Conventions\DiscoveredConventions(...);
$dbCache = new Nette\Caching\Storages\FileStorage(...);
// creating Explorer
$dbExplorer = new Nette\Database\Explorer($dbConnection, $dbStructure, $dbConventions, $dbCache);
// creating ArticleManager
$articleManager = new App\Model\ArticleManager($dbExplorer)
...
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 the full class name to the config/common.neon
file under services.
services:
- App\Router\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\Explorer
replacing that with a new dependency on our new class.
namespace App\Presenters;
use Nette;
use App\Model\ArticleManager;
class HomepagePresenter extends Nette\Application\UI\Presenter
{
private ArticleManager $articleManager;
public function __construct(ArticleManager $articleManager)
{
$this->articleManager = $articleManager;
}
public function renderDefault(): void
{
$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\Explorer
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.