Модель
По мере роста нашего приложения мы вскоре обнаружим, что похожие
операции с базой данных нужно выполнять в разных местах и в разных
презентерах, например получать последние опубликованные записи. Если
мы улучшим приложение, например добавив к записям признак того,
черновики ли они, нам придётся пройтись по всем местам приложения, где
записи достаются из базы данных, и добавить условие where, чтобы
выбирались только не-черновики.
В этот момент прямой работы с базой данных становится недостаточно, и умнее будет использовать новый метод, возвращающий опубликованные записи. А когда мы позже добавим ещё одно условие (например, не показывать записи с будущей датой), править код придётся только в одном месте.
Метод мы поместим в класс PostFacade и назовём его
getPublicArticles().
Наш класс модели PostFacade, который позаботится о наших записях,
мы создадим в каталоге app/Model/:
<?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');
}
}
В классе мы через конструктор запрашиваем Explorer базы данных. Тем самым мы пользуемся силой DI-контейнера.
Перейдём к HomePresenter, который изменим так, чтобы избавиться от
зависимости от Nette\Database\Explorer и заменить её новой зависимостью от
нашего нового класса.
<?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);
}
}
В секции use у нас есть App\Model\PostFacade, так что в PHP-коде
запись можно сократить до PostFacade. Этот объект мы запрашиваем в
конструкторе, записываем в свойство $facade и используем в методе
renderDefault.
Последний шаг – научить DI-контейнер этот объект порождать. Обычно
это делается добавлением пункта в файл config/services.neon в секцию
services с указанием полного имени класса и параметров
конструктора. Тем самым он регистрируется, и объект тогда называют
сервисом. Благодаря волшебству autowiring нам обычно не нужно
указывать параметры конструктора, потому что DI распознает и передаст
их автоматически. Так что хватило бы указать только имя класса:
...
services:
- App\Model\PostFacade
Впрочем, добавлять эту строку тоже не нужно. В секции search файла
services.neon определено, что все классы, оканчивающиеся на
-Facade или -Factory, DI найдёт сам, а это относится и к
PostFacade.
Итоги
Класс PostFacade просит в своём конструкторе Nette\Database\Explorer, и
поскольку этот класс зарегистрирован в DI-контейнере, контейнер
создаёт этот экземпляр и передаёт его. DI тем самым создаёт за нас
экземпляр PostFacade и передаёт его в конструктор класса
HomePresenter, который его запросил. Это как матрёшка. :) Каждый лишь
говорит, что ему нужно, и его не волнует, где и как это создаётся.
Созданием занимается DI-контейнер.
Здесь можно подробнее почитать о внедрении зависимостей и конфигурации.