Model
As our application grows, we soon find out that we need to perform similar database operations in various places and in various
presenters, for example, retrieving the latest published posts. If we improve our application, for instance, by adding a flag to
posts indicating whether they are drafts, we must also review all places in our application where posts are retrieved from the
database and add a where
condition to ensure only non-draft posts are selected.
At this point, working directly with the database becomes insufficient, and it will be smarter to use a new method that returns the published posts. And when we later add another condition (for example, not to display posts with a future date), we only edit our code in one place.
We'll place the method into the PostFacade
class and call it getPublicArticles()
.
We'll create our model class PostFacade
in the app/Model/
directory to take care of our posts:
<?php
namespace App\Model;
use Nette;
final class PostFacade
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function getPublicArticles()
{
return $this->database
->table('posts')
->where('created_at < ', new \DateTime)
->order('created_at DESC');
}
}
In the class, we request the database Explorer via the constructor. This utilizes the power of the DI container.
We will switch to HomePresenter
, which we will modify by getting rid of the dependency on
Nette\Database\Explorer
and replacing it with a new dependency on our new class.
<?php
namespace App\Presentation\Home;
use App\Model\PostFacade;
use Nette;
final class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PostFacade $facade,
) {
}
public function renderDefault(): void
{
$this->template->posts = $this->facade
->getPublicArticles()
->limit(5);
}
}
In the use
section, we have App\Model\PostFacade
, so we can shorten the notation in the PHP code to
PostFacade
. We request this object in the constructor, write it to the $facade
property, and use it in
the renderDefault
method.
The last step is to teach the DI container to produce this object. This is usually done by adding an item to the
config/services.neon
file in the services
section, specifying the full class name and constructor
parameters. This registers it, and the object is then called a service. Thanks to the magic of autowiring, we usually don't need to specify the constructor
parameters because DI will recognize and pass them automatically. Thus, it would be sufficient to just provide the
class name:
...
services:
- App\Model\PostFacade
However, you don't need to add this line either. In the search
section at the beginning of
services.neon
, it is defined that all classes ending with -Facade
or -Factory
will be found
by DI automatically, which is also the case for PostFacade
.
Summary
The PostFacade
class asks for Nette\Database\Explorer
in its constructor, and since this class is
registered in the DI container, the container creates this instance and passes it. DI thus creates an instance of
PostFacade
for us and passes it in the constructor to the HomePresenter
class, which requested it.
It's like a Matryoshka doll. :) Everyone just states what they want, and they don't care where or how it gets created. The
creation is handled by the DI container.
Here you can read more about dependency injection and configuration.