Модель

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

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

Мы поместим функцию в класс PostFacade и назовем её getPublicArticles().

Создадим наш класс модели PostFacade в директории app/Model/, чтобы позаботиться о наших статьях. Давайте поместим его в файл PostFacade.php.

<?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.php, который мы отредактируем так, чтобы избавиться от зависимости от Nette\Database\Explorer, заменив её новой зависимостью от нашего созданного класса.

<?php
namespace App\UI\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 (не бойтесь, он работает даже в комментариях, и ваша умная IDE должна быть в состоянии справиться с этим).

Остался последний шаг – научить 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-контейнер.

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