Správné předávání závislostí v modelech

Hitny14
Člen | 90
+
0
-

Ahoj,

měl bych takovou otázku jak správně postupovat při předávání závislostí v modelech.

První je předat si v BaseModel celý $container a dále si předávat pomocí něho závisloti.

Např:

/** @var Explorer|null */
protected Explorer|null $database;

abstract class BaseModel
{
	public function __construct(Container $container)
    {
		$this->database = $container->getByType(Explorer::class);
	}
}

OrderModel který dědí od BaseModelu

class OrderModel extends BaseModel
{
	/** @var ParamsModel|null */
    protected ParamsModel|null $params

	public function __construct(Container $container)
    {
        parent::__construct($container);
		$this->params = $container->getByType(ParamsModel::class);
	}
}

Druhý případ:

abstract class BaseModel
{
	public function __construct(
		protected Explorer $database;
	) {}
}

OrderModel který dědí od BaseModelu

class OrderModel extends BaseModel
{
	/** @var ParamsModel|null */
    protected ParamsModel|null $params

	public function __construct(
		Explorer $database,
		private ParamsModel $params
	) {
        parent::__construct($database);
	}
}

Otázka zni který postup je správný? Osobně se spíše přikládám k druhému protože si myslím, že není správně přidávat si napříč modelem celý container a hlavně je to i kratší zápis. Děkuji za radu

David Matějka
Moderator | 6445
+
+6
-

není správně přidávat si napříč modelem celý container

přesně tak


ale doporučuji se zcela vykašlat na BaseModel a preferovat kompozici, případně nějaké (statické) helper funkce nebo traity.

Hitny14
Člen | 90
+
0
-

@DavidMatějka Mohl bych tě prosím požádat do nějaký příklad jako to řešit místo použití BaseModel? Nenapadá mě jak vyřešit třeba připojení do DTB jinak mám v BaseModel ještě nějaké metody a ty byh asi mohl přesunout do jednotlivých trait. Děkuji

Edit:

Jediné co mě vlastně napadlo je předávat si závislost na dtb specialně v každým modelu ale v BaseModel mám tři závislosti které potřebuji předávat napříč přes všechny modely.

Editoval Hitny14 (23. 6. 2021 10:40)

stepos2
Člen | 53
+
+2
-

Pokud je BaseModel potřeba, dá se to řešit přes decorator v config.neon:

Buď:

decorator:
	App\Model\BaseModel:
		inject: true
namespace App\Model;

use Nette\DI\Attributes\Inject;

abstract class BaseModel
{
	#[Inject]
	public Explorer $database; // musí být public
}

Nebo:

decorator:
	App\Model\BaseModel:
		setup:
			- setDatabase
namespace App\Model;

abstract class BaseModel
{
	protected Explorer $database;

	public function setDatabase(Explorer $database)
	{
		$this->database = $database;
	}
}

A model:

class OrderModel extends BaseModel
{
	public function __construct(
		private ParamsModel $params
	) {}
}

Editoval stepos2 (23. 6. 2021 13:20)

dakur
Člen | 493
+
+9
-

@Hitny14 Prostě ty tři závislosti předej ve všech modelech. 🤷‍♂️

Naše konstruktory běžně vypadají takto a není s tím sebemenší problém – je to jasné a přímočaré:

public function __construct(
	private AccountReadFacade $accountReadFacade,
	private Clock $clock,
	private ReadRequestTable $readRequestTable,
	private RepresentativesTable $representativesTable,
	private ReadAcknowledgementTable $readAcknowledgementTable,
	private Translator $translator,
) {}

Ono to teď vypadá jako super zlepšovák takový BaseModel, který obsahuje opakující se závislosti, ale věř mi, že není člověk (výzkum jsem nedělal 🙂), který by s tím časem nezačal mít problémy. Takové base classy totiž vždycky začnou bobtnat. Na controller vrstvě typicky obsahují věci, které by měly být v komponentách a které navíc jsou často potřeba jen pro některé (ne všechny) potomky. Pak se nedá vyznat v tom, co se kde vzalo a k čemu co je. V model vrstvě to bude podobné.

Editoval dakur (23. 6. 2021 13:39)

Hitny14
Člen | 90
+
0
-

@dakur Ok, děkuji pokusím se to upravit abych vynechal všechny base třídy

Editoval Hitny14 (24. 6. 2021 8:05)

Šaman
Člen | 2662
+
+5
-

Já bych se ještě zeptal, čemu říkáš model?

Já používám pro samotnou práci s databází třídy Repository (i když to není přímo ORM a ten název je nepřesný). Takže mám strukturu rodič – potomci.

abstract class Repository
	- UserRepository
	- ArticleRepository
	- PostRepository

Závislost na databázi mám jen ve třídě Repository v konstruktoru a ostatní repozitáře většinou nic dalšího nepotřebují. Ve výjimečných případech bych si prostě v kontruktoru potomka volal konstruktor rodiče.

Toto je samozřejmě také v modelové vrstvě, ale nevím, jestli je to ono, čemu říkás model.

Protože aplikační logiku mám i v jiných třídách (z těch základních třeba UserFormFactory, UserGridFactory, pro jakoukoliv vyšší logiku nad více entitama pak nějaké FooBarFacade, nebo FooModel třídy).

Takže všechny Repository potřebují připojení k DB, ale všechny ostatní třídy modelu potřebují jen to své FooRepository (nebo několik repozitářů pro složitější operace).

Hitny14
Člen | 90
+
0
-

@Šaman v modelu mám veškerou logiku aplikace když dám teda příklad.

CartModel obsluhuje práci s DTB a veškerou logiku co se týče práce v košíku.

Chápu to tedy dobře že bych měl mít CartRepository kde by byla veškerá práce s DTB související s košíkem a pak mít CartModel kde by byla ostatní logika např: práce se session a tak dále. pokud bych v CartModel potřeboval DTB tak si předám CartRepository?

Šaman
Člen | 2662
+
+3
-

@Hitny14 Já bych to tak udělal. Nemohu ale jednoznačně prohlásit, že „bys to tak měl mít“. Zaleží na mnoha dalších faktorech. Mě se ale osvědčilo mít třídu jen pro get (vrací jediný záznam, nebo NULL), find (vrací kolekce nebo pole záznamů, může být i prázdné) a případně nějaké persist metody (ukládání).

Občas totiž potřebuješ přistupovat k tabulce (resp. k nějaké entitě) z více vyšších modelových tříd. A tam znovu budeš psát stejné getByEmail() nebo findAll() metody. A pokud se někdy rozhodneš přidat třeba do tabulky příznak deleted, tak stačí upravit metodu findAll() tak, že odfiltruje všechny deleted záznamy.

Obecně záleží jak moc se ti aplikace rozroste – u malých tohle nemusíš ocenit, u větších se to hodí, u obrovskych to chce mít model opravdu dobře navržený a třeba použít i složitější nástroje (třeba Doctrine jako databázovou vrstvu).

m.brecher
Generous Backer | 871
+
+3
-

Co se týče otázky na co využít BasePresenter a na co ne, tak ideálně se hodí na:

  • centrální kontrolu autorizace,
  • předání závislostí pro layoutu webu,
  • registrace globálně využívaných komponent,

ostatní funkcionality , pokud se dají umístit do komponent či jinam tak do BasePresenteru nedávat.

Čím jednodušší je vrstva presenterů, tím lépe, ideálně jedna abstract vrstva a jedna final – i za cenu psaní malého množství kódu v konstruktorech duplicitně.