Модель

У міру зростання нашого додатка ми незабаром виявляємо, що нам необхідно виконувати аналогічні операції з базою даних у різних місцях і в різних презентерах. Наприклад, отримувати найновіші опубліковані статті. Якщо ми покращимо наш застосунок, додавши до статей прапор, що вказує на стан готовності, ми також повинні пройтися всіма місцями в нашому застосунку та додати умову 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-контейнер.

Детальніше про впровадження залежностей можна прочитати тут, а про конфігурацію – тут