Model

Wraz z rozwojem aplikacji szybko okazuje się, że musimy wykonywać podobne operacje na bazie danych w różnych miejscach, w różnych prezenterach. Na przykład, aby pobrać najnowsze opublikowane artykuły. Jeśli wzbogacimy aplikację, dodając na przykład flagę dla artykułów, aby wskazać, czy są one niepublikowane, musimy wtedy przejść przez wszystkie miejsca w aplikacji, gdzie artykuły są pobierane z bazy danych i dodać warunek where, aby wybrać tylko niepublikowane artykuły.

W tym momencie praca bezpośrednio z bazą danych staje się niewystarczająca i przydatne będzie użycie nowej funkcji do zwrócenia opublikowanych artykułów. A jeśli później dodamy kolejny warunek, np. że artykuły z przyszłą datą nie powinny być wyświetlane, zmodyfikujemy kod tylko w jednym miejscu.

Na przykład umieść funkcję w klasie PostFacade i nazwij ją getPublicArticles().

W katalogu app/Model/ stworzymy naszą klasę modelową PostFacade, która zajmie się artykułami:

<?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 będziemy mieli konstruktor przekazujący eksplorator bazy danych. Wykorzystamy moc kontenera DI.

Przechodzimy na stronę HomePresenter, którą modyfikujemy pozbywając się zależności od Nette\Database\Explorer i zastępując ją zależnością od naszej nowej klasy.

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

W sekcji use mamy App\Model\PostFacade, więc możemy skrócić zapis kodu PHP do PostFacade. Będziemy żądać tego obiektu w konstruktorze, zapisywać go do właściwości $facade i używać w metodzie renderDefault.

Pozostał ostatni krok – nauczenie kontenera DI produkowania tego obiektu. Zwykle robi się to poprzez dodanie punktu wypunktowania do pliku config/services.neon w sekcji services, podając pełną nazwę klasy i parametry konstruktora. To rejestruje go, że tak powiem, a obiekt jest następnie nazywany serwisem. Dzięki magicznej sztuczce zwanej autowiring, zazwyczaj nie musimy podawać parametrów konstruktora, ponieważ DI sam je rozpozna i przekaże. Tak więc wystarczyłoby po prostu podać nazwę klasy:

...

services:
	- App\Model\PostFacade

Nie musisz jednak dodawać również tej linii. Sekcja search na początku services.neon definiuje, że wszystkie klasy kończące się na -Facade lub -Factory będą wyszukiwane przez DI, co dotyczy również PostFacade.

Streszczenie.

Klasa PostFacade prosi konstruktora o przekazanie Nette\Database\Explorer, a ponieważ ta klasa jest zarejestrowana w kontenerze DI, kontener tworzy i przekazuje tę instancję. DI tworzy więc dla nas instancję PostFacade i przekazuje ją w konstruktorze do klasy HomePresenter, która jej zażądała. To jest matryca. :) Każdy mówi tylko to, co chce i nie przejmuje się tym, gdzie co powstaje i jak. Kontener DI zajmuje się tworzeniem.

Możesz przeczytać więcej o wtrysku zależnościkonfiguracji tutaj.