Model

W miarę rozrastania się naszej aplikacji szybko odkrywamy, że w różnych miejscach i w różnych presenterach musimy wykonywać podobne operacje na bazie danych, na przykład pobierać najnowsze opublikowane wpisy. Jeśli ulepszymy naszą aplikację, na przykład dodając wpisom flagę wskazującą, czy są szkicami, musimy też przejrzeć wszystkie miejsca w aplikacji, w których pobierane są wpisy z bazy, i dodać warunek where, żeby wybierane były tylko wpisy niebędące szkicami.

W tym momencie praca bezpośrednio z bazą danych przestaje wystarczać i mądrzej będzie użyć nowej metody zwracającej opublikowane wpisy. A gdy później dodamy kolejny warunek (na przykład żeby nie wyświetlać wpisów z przyszłą datą), zmodyfikujemy nasz kod tylko w jednym miejscu.

Metodę umieścimy w klasie PostFacade i nazwiemy ją getPublicArticles().

Naszą klasę modelu PostFacade utworzymy w katalogu app/Model/, żeby zajęła się naszymi wpisami:

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

W klasie prosimy przez konstruktor o bazodanowy Explorer. Wykorzystuje to moc kontenera DI.

Przejdziemy do HomePresenter, który zmodyfikujemy, pozbywając się zależności od Nette\Database\Explorer i zastępując ją nową zależnością od naszej nowej klasy.

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

W sekcji use mamy App\Model\PostFacade, więc możemy skrócić zapis w kodzie PHP do PostFacade. O ten obiekt prosimy w konstruktorze, zapisujemy go do właściwości $facade i używamy w metodzie renderDefault.

Ostatnim krokiem jest nauczenie kontenera DI, żeby ten obiekt produkował. Robi się to zwykle przez dodanie pozycji do pliku config/services.neon w sekcji services, podając pełną nazwę klasy i parametry konstruktora. Rejestruje ją to, a obiekt nazywamy potem usługą. Dzięki magii autowiringu zwykle nie musimy podawać parametrów konstruktora, bo DI rozpozna je i przekaże automatycznie. Wystarczyłoby więc podać tylko nazwę klasy:

...

services:
	- App\Model\PostFacade

Tej linii jednak też nie musisz dodawać. W sekcji search pliku services.neon zdefiniowane jest, że wszystkie klasy kończące się na -Facade albo -Factory zostaną znalezione przez DI automatycznie, co dotyczy także PostFacade.

Podsumowanie

Klasa PostFacade prosi w swoim konstruktorze o Nette\Database\Explorer, a ponieważ klasa ta jest zarejestrowana w kontenerze DI, kontener tworzy tę instancję i ją przekazuje. DI tworzy więc za nas instancję PostFacade i przekazuje ją w konstruktorze klasie HomePresenter, która o nią poprosiła. To jak matrioszka. :) Każdy tylko mówi, czego chce, i nie obchodzi go, gdzie i jak to powstaje. Tworzeniem zajmuje się kontener DI.

Tutaj możesz przeczytać więcej o dependency injectionkonfiguracji.