Модель

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

У класі за допомогою конструктора ми попросимо передати нам базу даних 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 та конфігурацію.