Správný postup odstranění řádku z databáze
- David Kregl
- Člen | 52
Zdravím všechny,
v Nette už jsem se dostal poměrně daleko, avšak včera jsem se rozhodl své aplikace přepsat za použití modelu, který jsem tak dlouho opomíjel. Zajímá mě, jaký požíváte způsob pro odstranění jednoho řádku z tabulky. Existuje nějaký best practice?
Od řešení očekávám:
- ověření, zda-li záznam vůbec existuje;
- informovat uživatele v případě, že byl záznam odstraněn (flashMessage);
Používám Nette Database s využitím modelu.
Současná situace:
BaseModel.php
/**
* @param $id
* @return int
*/
public function deleteById($id)
{
return $this->db->table($this->table)->where('id', $id)->delete();
}
OrderPresenter.php
/**
* @param $id
*/
public function handleDeleteOrder($id)
{
if ($this->orderModel->findById($id)) {
$this->orderModel->deleteById($id);
$this->flashMessage('Objednávka byla úspěšně odstraněna.', 'success');
}
}
Editoval David Kregl (8. 11. 2015 0:14)
- hitzoR
- Člen | 51
delete() vrací počet ovlivněných řádků, takže stačí v modelu zkontrolovat, jestli je 1 a pokud ne, tak vyhodit výjímku.
Model:
/**
* @param $id
*/
public function delete($id)
{
$query = $this->db->table($this->table)->where('id', $id)->delete();
if ($query !== 1) throw new Exception();
}
Presenter:
/**
* @param $id
*/
public function handleDeleteOrder($id)
{
try {
$this->orderModel->delete($id);
$this->flashMessage('...', 'success');
} catch (Exception $e) {
$this->flashMessage('...', 'danger'); // informování uživatele o chybě
}
}
- David Grudl
- Nette Core | 8239
- slovo
Model
v názvu tříd, které jsou součástí modelu, je zbytečná vycpávka. Spíš bych to použil ve jmenném prostoru - pokud
BaseModel
reprezentuje (jestli dobře chápu) databázovou tabulku, měl by to mít v názvu, takže třebaModel\Table
. Ano, i toBase
mi připadá zbytečné. Třída pak může mít potomka např.Model\OrderTable
. - pracovat v prezenterech přímo s tabulkami (resp. se třídami
reprezentujícími tabulky) nemusí být ideální. Třeba pokud by po smazání
objednávky mělo následovat odeslání potvrzujícího emailu, měl by to
zajistit model (protože to je chování vnitřní logiky, modelu, nikoliv
prezentační vrstvy), ale nepatří to do pravomoci třídy reprezentující
tabulku. Vytvořil bych si další třídu, například
Model\OrderFacade
, jejíž metoda by uměla smazat záznam i odeslat email, a tuhle bych si injektoval a používal v presenteru.
- David Kregl
- Člen | 52
David Grudl napsal(a):
- slovo
Model
v názvu tříd, které jsou součástí modelu, je zbytečná vycpávka. Spíš bych to použil ve jmenném prostoru- pokud
BaseModel
reprezentuje (jestli dobře chápu) databázovou tabulku, měl by to mít v názvu, takže třebaModel\Table
. Ano, i toBase
mi připadá zbytečné. Třída pak může mít potomka např.Model\OrderTable
.- pracovat v prezenterech přímo s tabulkami (resp. se třídami reprezentujícími tabulky) nemusí být ideální. Třeba pokud by po smazání objednávky mělo následovat odeslání potvrzujícího emailu, měl by to zajistit model (protože to je chování vnitřní logiky, modelu, nikoliv prezentační vrstvy), ale nepatří to do pravomoci třídy reprezentující tabulku. Vytvořil bych si další třídu, například
Model\OrderFacade
, jejíž metoda by uměla smazat záznam i odeslat email, a tuhle bych si injektoval a používal v presenteru.
Děkuji za konstruktivní názor, OrderFacade je skvělý nápad, už jsem ji implementoval. Velice si Vážím vyjádření právě od Vás.
S přáním pohodového zbytku pracovního „večera“,
David
Editoval David Kregl (8. 11. 2015 1:18)
- David Kregl
- Člen | 52
hitzoR napsal(a):
delete() vrací počet ovlivněných řádků, takže stačí v modelu zkontrolovat, jestli je 1 a pokud ne, tak vyhodit výjímku.
Model:
/** * @param $id */ public function delete($id) { $query = $this->db->table($this->table)->where('id', $id)->delete(); if ($query !== 1) throw new Exception(); }
Presenter:
/** * @param $id */ public function handleDeleteOrder($id) { try { $this->orderModel->delete($id); $this->flashMessage('...', 'success'); } catch (Exception $e) { $this->flashMessage('...', 'danger'); // informování uživatele o chybě } }
Věděl jsem, že to jde udělat elegantně. Děkuji.
Editoval David Kregl (8. 11. 2015 11:33)
- yrow
- Člen | 12
David Grudl napsal(a):
- slovo
Model
v názvu tříd, které jsou součástí modelu, je zbytečná vycpávka. Spíš bych to použil ve jmenném prostoru- pokud
BaseModel
reprezentuje (jestli dobře chápu) databázovou tabulku, měl by to mít v názvu, takže třebaModel\Table
. Ano, i toBase
mi připadá zbytečné. Třída pak může mít potomka např.Model\OrderTable
.- pracovat v prezenterech přímo s tabulkami (resp. se třídami reprezentujícími tabulky) nemusí být ideální. Třeba pokud by po smazání objednávky mělo následovat odeslání potvrzujícího emailu, měl by to zajistit model (protože to je chování vnitřní logiky, modelu, nikoliv prezentační vrstvy), ale nepatří to do pravomoci třídy reprezentující tabulku. Vytvořil bych si další třídu, například
Model\OrderFacade
, jejíž metoda by uměla smazat záznam i odeslat email, a tuhle bych si injektoval a používal v presenteru.
Zdravím,
takový dotaz, pokud implemetuji OrderFacade, který může obsahovat různé
jiné metody (odesílání emailu), tak samotný model (OderModel) implementuji
do této Facade třídy, nebo ta Facade třídá je zároveň i modelem, a nebo
si předám v presentru ten model přes seter do Facade?
- Šaman
- Člen | 2666
Modelem je všechno, model je celá vrstva aplikace na kterou se odkazuješ z presenterů a většinou je to to hlavní, co programátor programuje (v presenterech to v ideálním případě jen prováže a nasype do šablon). Takže i repozitář, tabulka, fasáda, vše patří do modelu.
Znáš přednášku 5 vrstev modelu?
Sice už je starší, ale tohle by ti mohla docela objasnit. To, čemu David
říká Facade
je v přednášce Service
a tomu,
čemu ty asi říkáš Model
je tam Repository
.
Obecně jde o to, že repository je většinou jeden na tabulku a obsluhuje
jí poměrně nízkoúrovňově (já tam mám třeba specifické metody jako
(articles) findNewest($count)
která vrátí požadovaný počet
nejnovějších článků). Repository obsluhuje jednu entitu a vzhledem k principu
jediné odpovědnosti by neměl dělat nic dalšího.
Zatímco fasád může být víc a už nemají tak těsný vztah
k jednotlivým entitám. Jedna může třeba po vydání článku načíst
všechny emaily (pomocí jednoho repozitáře), na které se má článek poslat
(ten načte z jiného repozitáře), a provede rozeslání (pomocí služby
typu IMailer
). A SRP je zachovaný, protože tahle třída dělá
konkrétní a jednu věc – rozesílá novinky. Na další práci se články
budou další fasády/service.
P.S.
Když už jsem teda odkázal na starší článek, tak ještě upřesním jak to
vypadá v Nette\Database
- úložiště je SQL databáze
- mapperu zhruba odpovídá Nette\Database(\Table). Nebo Dibi pokud používáš starší knihovnu.
- entita jako samostatný objekt neexistuje, ale s výsledkem dotazu se jako s entitou dá do určitě míry pracovat (je to opravdu jen přepravka na data)
- repository je nejspíš to, čemu jsi říkal Model – reprezentuje jednu
tabulku (resp. stará se o načítání jedné entity – v určitých
případech může být entita rozložená třeba do dvou tabulek). Pokud
nechceš žádné metody specifické pro danou tabulku (např. zmíněné
findNewest($count)
, nebo u uživatelegetByEmail($email)
), tak ti samotnéNette\Database\Table
stačí jako primitivní generický repozitář. - service/facade – vyšší vrstvy modelu, které už jsou od databáze odstíněné – získají entity z jednotlivých repozitářů a něco s nimi provádějí. Ale pro všechny CRUD operace používají repository.
A poslední dodatek:
yrow napsal(a):
… a nebo si předám v presentru ten model přes seter do Facade?
Presenter z toho úplně vynech, předávej to pomocí konstruktoru a
fasádu vytvářej v DIC kontejneru (jako službu, zapiš ji do configu).
Správný repozitář se jí pak dodá automaticky. V mém příkladu výše si
presenter načte jen nějakou SendNewsFacade
a hned ji může
používat. Nepatří do odpovědnosti presenteru, aby ji taky správně
vytvořil, to je odpovědnost DIC kontejneru. (Když mi to kdysi opravdu dělal
presenter, tak byl plný závislostí které sám nepotřeboval a jen je
předával. V tomto případě by si musel načíst
ArticleRepository
, EmailRepository
a
Mailer
a presenter byl pak ošklivě nabobtnalý. A hlavně
přidat pak jednu závislost – SendNewsFacade
je strašně moc
psaní.)
Editoval Šaman (11. 11. 2015 12:43)
- yrow
- Člen | 12
Šaman napsal(a):
Modelem je všechno, model je celá vrstva aplikace na kterou se odkazuješ z presenterů a většinou je to to hlavní, co programátor programuje (v presenterech to v ideálním případě jen prováže a nasype do šablon). Takže i repozitář, tabulka, fasáda, vše patří do modelu.
Znáš přednášku 5 vrstev modelu?
Sice už je starší, ale tohle by ti mohla docela objasnit. To, čemu David říkáFacade
je v přednášceService
a tomu, čemu ty asi říkášModel
je tamRepository
.Obecně jde o to, že repository je většinou jeden na tabulku a obsluhuje jí poměrně nízkoúrovňově (já tam mám třeba specifické metody jako (articles)
findNewest($count)
která vrátí požadovaný počet nejnovějších článků). Repository obsluhuje jednu entitu a vzhledem k principu jediné odpovědnosti by neměl dělat nic dalšího.Zatímco fasád může být víc a už nemají tak těsný vztah k jednotlivým entitám. Jedna může třeba po vydání článku načíst všechny emaily (pomocí jednoho repozitáře), na které se má článek poslat (ten načte z jiného repozitáře), a provede rozeslání (pomocí služby typu
IMailer
). A SRP je zachovaný, protože tahle třída dělá konkrétní a jednu věc – rozesílá novinky. Na další práci se články budou další fasády/service.P.S.
Když už jsem teda odkázal na starší článek, tak ještě upřesním jak to vypadá v Nette\Database
- úložiště je SQL databáze
- mapperu zhruba odpovídá Nette\Database(\Table). Nebo Dibi pokud používáš starší knihovnu.
- entita jako samostatný objekt neexistuje, ale s výsledkem dotazu se jako s entitou dá do určitě míry pracovat (je to opravdu jen přepravka na data)
- repository je nejspíš to, čemu jsi říkal Model – reprezentuje jednu tabulku (resp. stará se o načítání jedné entity – v určitých případech může být entita rozložená třeba do dvou tabulek). Pokud nechceš žádné metody specifické pro danou tabulku (např. zmíněné
findNewest($count)
, nebo u uživatelegetByEmail($email)
), tak ti samotnéNette\Database\Table
stačí jako primitivní generický repozitář.- service/facade – vyšší vrstvy modelu, které už jsou od databáze odstíněné – získají entity z jednotlivých repozitářů a něco s nimi provádějí. Ale pro všechny CRUD operace používají repository.
A poslední dodatek:
yrow napsal(a):
… a nebo si předám v presentru ten model přes seter do Facade?
Presenter z toho úplně vynech, předávej to pomocí konstruktoru a fasádu vytvářej v DIC kontejneru (jako službu, zapiš ji do configu). Správný repozitář se jí pak dodá automaticky. V mém příkladu výše si presenter načte jen nějakou
SendNewsFacade
a hned ji může používat. Nepatří do odpovědnosti presenteru, aby ji taky správně vytvořil, to je odpovědnost DIC kontejneru. (Když mi to kdysi opravdu dělal presenter, tak byl plný závislostí které sám nepotřeboval a jen je předával. V tomto případě by si musel načístArticleRepository
,EmailRepository
aMailer
a presenter byl pak ošklivě nabobtnalý. A hlavně přidat pak jednu závislost –SendNewsFacade
je strašně moc psaní.)
Super díky moc, pěkná odpověď,
takže si do presenteru normálně injektnu pouze fasádu, (ta fasáda třeba
vyžaduje dva repository) potřebné repository si fasáda obstará sama ve
svém konstruktoru.
To jsem chtěl vědět. Že ta fasáda by tedy neměla být zároveň tím
základním repository který obstarává data ani se to nepředává
nějak jiank.
- Šaman
- Člen | 2666
Přesně tak.
Ještě jedna myšlenka:
Jsou různě názory, jak přistupovat i k takhle jasně definované
struktuře modelu. Někdo si dělá fasády i pro běžný přístup
k entitám (idea je taková, že presenter dostane vždy jen fasády/service,
nikdy repozitář), ale takové fasády většinou nedělají nic než delegují
požadavek na repozitář. Pak jsou běžné fasády typu
UserFacade
, nebo ArticleFacade
.
Já dávám přednost tomu, že pokud presenter potřebuje jen jednoduché
CRUD operace, dostane přímo repozitář.
A fasády (dávám přednost označení service) řeší všechny požadavky,
které potřebují víc závislostí, než ten jeden repozitář. A těch
může být spousta a většinou se už nejmenují tak genericky. Takže třeba
nikoliv UserService
, ale třeba RegisterUserService
(zaregistruje uživatele do db, vygeneruje mu heslo a odešle email), nebo ta
SendNewsService
.
Lépe se vyznává i ve větším počtu služeb, pokud bude každá dělat jen jednu věc, než hledat, jestli odesílání novinek je nacpáno do EmailFacade, nebo do ArticleFacade, nebo je to naprogramované přímo v presenteru.
Editoval Šaman (11. 11. 2015 17:17)
- Šaman
- Člen | 2666
Oli napsal(a):
zaregistruje uživatele do db, vygeneruje mu heslo a odešle na email
Chápu, že to je jen příklad, ale takhle ne. Nebo se ti tam to
na
dostalo omylem? :-)
Odešle na email aktivační odkaz, nebo tak nějak :) Tohle byl jen příklad z hlavy, kam jsem chtěl nacpat víc, než triviální CRUD operaci. Osobně hesla zásadně negeneruji, ty si má nastavit uživatel sám. Když mu vygeneruji supersilné heslo, tak ho buď zapomene, nebo si ho nalepí na monitor a bezpečnost je v kelu :D
Ale radši jsem to upravil, aby to někdo tak opravdu neudělal :)
Editoval Šaman (11. 11. 2015 17:17)