Модель

По мере роста нашего приложения мы вскоре обнаружим, что похожие операции с базой данных нужно выполнять в разных местах и в разных презентерах, например получать последние опубликованные записи. Если мы улучшим приложение, например добавив к записям признак того, черновики ли они, нам придётся пройтись по всем местам приложения, где записи достаются из базы данных, и добавить условие 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-контейнер.

Здесь можно подробнее почитать о внедрении зависимостей и конфигурации.