Модел

С нарастването на приложението скоро ще открием, че на различни места, в различни презентери, трябва да извършваме подобни операции с базата данни. Например, да получаваме най-новите публикувани статии. Когато подобрим приложението, например като добавим флаг към статиите, дали са в процес на писане, трябва след това да преминем през всички места в приложението, където се извличат статии от базата данни и да добавим условие 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 и конфигурацията.