Модель

По мере роста приложения мы скоро обнаружим, что в разных местах, в разных презентерах, нам нужно выполнять похожие операции с базой данных. Например, получать последние опубликованные статьи. Если мы улучшим приложение, например, добавив к статьям флаг, указывающий, является ли она черновиком, нам придется пройти по всем местам в приложении, где статьи извлекаются из базы данных, и добавить условие where, чтобы выбирались только не черновики.

В этот момент прямая работа с базой данных становится недостаточной, и будет удобнее воспользоваться новой функцией, которая будет возвращать нам опубликованные статьи. И когда позже мы добавим еще одно условие, например, что не должны отображаться статьи с будущей датой, мы изменим код только в одном месте.

Функцию разместим, например, в классе PostFacade и назовем ее getPublicArticles().

В каталоге app/Model/ создадим наш модельный класс PostFacade, который будет отвечать за статьи:

<?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');
	}
}

В классе с помощью конструктора запросим передачу Database 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-контейнер.

Здесь вы можете прочитать больше о dependency injection и конфигурации.