Správné předávání závislostí v modelech
- Hitny14
- Člen | 90
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
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
@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
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
@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)
- Šaman
- Člen | 2662
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
@Š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
@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
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ě.