Konečně Filters pro Nette Database
- Tomáš Votruba
- Moderator | 1114
Na frontendu chceš komentáře mazat,
ale v adminu je chceš stále vidět všechny. V praxi se to jmenuje soft
deletable.
V Doctrine…
…je to snadné díky Sql filtrům.
Jak na to v Nette Database?
Balíček Zenify/NetteDatabaseFilters umožňuje psaní filtrů, které se o to postají. Třeba takto:
namespace App\Database\Filter;
use Nette\Database\Table\Selection;
use Nette\Security\User;
use Zenify\NetteDatabaseFilters\Contract\FilterInterface;
final class SoftdeletableFilter implements FilterInterface
{
/**
* @var User
*/
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public function applyFilter(Selection $selection)
{
// 1. skip filter for admin user
if ($this->user->isLoggedIn() && $this->user->hasRole('admin')) {
return;
}
// 2. apply only to "comment" table
$tableName = $selection->getName();
if ($tableName !== 'comment') {
return;
}
// 3. show only visible (not deleted) comments
$selection->where('is_deleted = ?', FALSE);
}
}
Chci to vyzkoušet!
Stačí věnovat 2 minuty času a tento krátký článek ti řekne vše podstatné.
Enjoy coding!
Editoval Tomáš Votruba (19. 6. 2016 10:56)
- Tomáš Votruba
- Moderator | 1114
@CZechBoY Z README to není pochopitelné?
Safra, to to musím napsat líp. Kde ses tam zasekl?
Doplnil jsem ještě link na článek, kde popisuji kroky k rozjetí. Budu rád za feedback. Chci, aby to bylo maximálně srozumitelné.
Editoval Tomáš Votruba (19. 6. 2016 10:57)
- Tomáš Votruba
- Moderator | 1114
Filtry vlastně nepřinášejí žádnou operativu navíc, takže píšeš dotazy stejně jako dřív.
$comments = $this->database->table('comment')
->fetchAll();
Na pozadí se přidá filter, který podle tvých podmínek přidá podmínku nebo ne.
Aktualizoval jsem článek o konkrétní kód z praxe.
Mkrni prosím a dej vědět, co z toho chápeš.
Editoval Tomáš Votruba (19. 6. 2016 15:04)
- Tomáš Votruba
- Moderator | 1114
Přesně tak! Viz:
This will influence every query for „comment“ table. So you can be sure you'll never forget to add the condition.
Jak bys to vysvětlil vlastními slovy? Možná to bude srozumitelnější pro ostatní.
Editoval Tomáš Votruba (19. 6. 2016 16:58)
- Tomáš Votruba
- Moderator | 1114
Supr, v praxi to funguje?
Já se učím psát anglicky pro Čechy a zároveň se snažím dělat osvětu ve světě, takže díky za feedback :). Příště na to budu myslet.
Editoval Tomáš Votruba (19. 6. 2016 21:01)
- Tomáš Votruba
- Moderator | 1114
Tyhle případy by to taky mělo pokrýt, pokud je to technicky možné.
Sám NDB příliš nepoužívám, takže jsem zatím pokryl Selection.
Kdybys měl failující a replikovatelný kód, budu rád za issue.
Díky za čas a feedback!
- CZechBoY
- Člen | 3608
Ono to je složitý, občas vyjdu z týhle tabulky, občas z týhle, … Myslim, že ani SqlBuilder nedisponuje nějakou metodou getSelect() nebo tak něco – že bych zjistil co vlastně je výsledkem (mám ve výsledcích dost složený entity a většinou nejsou vůbec úplný) a potom až filtrovat.
- Tomáš Votruba
- Moderator | 1114
Teoreticky tomu moc nerozumím. Můžeš mi hodit co nejjednodušší zdroják, na kterým bys mi to ukázal?
- Pavel Kravčík
- Člen | 1195
Nám se tenhle přístup moc neosvědčil, teď ho opouštíme.
Jak například řešíš edge case typu
$this->query("SELECT * FROM table WHERE name LIKE%%");
? Jelikož
se ten filtr doplňuje „nějak“ tajně, tak ve skládaném dotazu po dvou
měsících na to zapomenu a zobrazuji smazaná data.
- newPOPE
- Člen | 648
Suhlasim s @PavelKravčík. Tato veta v clanku je dosti nebezpecna
This will influence every query for "comment" table. So you can be sure you'll never forget to add the condition.
a v podstate taka dvojsecna zbran. Na jednu stranu super, ze sa to tam samo
doplna (aj ked na zaklade ifovania) na strane druhej ked si otvorim kod kde sa
dane „query“ spusta nie je uplne jasne co sa tam deje prave tym, ze sa to
doplni automaticky.
Nerozmyslal si skor nad cestou nejakych query object-ov (vytvorim object,
nastavim dajme tomu cez konstruktor a Nette\Db
ho len spusti).
Takto vytvaram triedy ktore su presne postavene na dane query s jasnymi
nastaveniami a ked potrebujem nieco vymazat. Lahko viem dohladat kde sa dane
query pouziva.
- Tomáš Votruba
- Moderator | 1114
@PavelKravčík Jak jste ho používali?
Konkrétní custom query nevidím jako vhodný use case pro filtry. Zatím to
funguje nad Selection
a možná to tak zůstane.
V praxi je používám v těchto případech
- již zmíněné softdeletable
- u článků – sloupec
is_published
- u lokalizace – sloupec
lang
- v případě různých verzí webů nad jednou datazí – sloupec
website_id
určený dle domény
Editoval Tomáš Votruba (20. 6. 2016 11:14)
- Tomáš Votruba
- Moderator | 1114
@newPOPE „every query“ je možná silné slovo. Jak jsem psal, používal jsem NDB spíše tradičním způsobem. Custom query jsem nevyužíval a tam filtry nezasahují. Hledám teď zpětnou vazbu od vás, kteří používáte NDB hlouběji, abych lépe poznal žádoucí hranici.
Filtry jsou spíše pro obecná a duplicitní řešení, viz příklady
v příspěvku výše.
Spuštění filtru lze ovlivnit podmínkou, takže proces je zcela pod kontrolou
programátora.
Objevujete v kódu jiné duplicitní případy, než jsem zmínil, které by vám filtry pomohly řešit?
- Pavel Kravčík
- Člen | 1195
Prakticky stejné případy jak říkáš (delete, active, processed, send) Naopak si myslím, že je to velmi vhodný use-case. U větších a složitějších aplikací si se selection nevystačíš – klasicky napíšeš CRUD s ORM/Selection velmi rychle a pak se staráš o bussiness logiku nebo přibývající věci. A v případě, že například píšeš složitější dotaz – snadno zapomeneš, že je tam nějaká „magie“ na pozadí se soft-delete. Například joinování z jiné tabulky. Ten join ti ten řádek normálně připojí i když je smazaný a už vzniká nějaká nekonzistence. To se mi nelíbí a vymstí se to, dříve či později.
V současné chvíli máme „storeManager“, který se dá navázat na tabulku a „historizuje“ jí na disk. S tím, že smazání je opravdu smazání, jen se zálohu otisk někam do úložiště (disk, db). To je za mě lepší přístup.
Ale určitě je to fajn vychytávka pro jednodušší věci, prakticky stejnou věc jsme používali skoro 2 roky pro všechny tabulky.
- Tomáš Votruba
- Moderator | 1114
@PavelKravčík Díky za podrobný popis.
Kde by se teda podle tebe měly filtry aplikovat a kde už ne?
To se mi nelíbí a vymstí se to, dříve či později.
Kde se vám to vymstilo?
Ale určitě je to fajn vychytávka pro jednodušší věci, prakticky stejnou věc jsme používali skoro 2 roky pro všechny tabulky.
Co používáte teď? Všechno ručně?
- Pavel Kravčík
- Člen | 1195
@TomášVotruba: Přestali jsme to používat „na pozadí“ a
kompletně se v nových projektech zbavujeme „sof-delete“. Tj.
$repository->delete()
už nedělá něco podobného
SET is_deleted = 1
, ale opravdu maže.
Kde se vám to vymstilo?
Například ve statistikách, které se dělaly až cca rok po dokončení
projektu. To už se pak těžko dohledává, že tahle položka by měla být
53 231 343-,Kč místo 57 554 908,–Kč, pokud tam někdo zapomene dát
ON y.id = x.y_id is_delete = 1
:)
Co používáte teď? Všechno ručně?
V neon si definuji tabulky, které chci „verzovat“. Např.
client
. Pokud se provede
$clientRepository->delete()
, tak se vytvoří JSON data, která
se vloží do texťáku do cca takovéhle adresy
/5/11/20160101083021_12345.dat
. Kde 5000 je omezení na počet ID
v jedné složce (5 pro id 1–5000,10 pro 5001–10000 atd.) následuje ID
z DB a nakonec timestamp s random hashem. Tj. pokud chci povolit nějaký
soft-delete pro novou tabulku, jen přidám další řádek do neonu a nemusím
nic řešit.
Editoval Pavel Kravčík (20. 6. 2016 11:51)
- Felix
- Nette Core | 1196
@PavelKravčík Muzu de zeptat co presne znamena to verzovani a na co to pouzivate?
Chapu to spravne, ze z DB se to smaze a na disku mas nejaky soubor s puvodnimi daty? K cemu to pouzivate? :)
Moc jsem jeste nepochopil ty 1–5000, muzes to prosim trochu priblizit.
Diky moc za objasneni, mozna budu resit neco podobneho, tak budu rad za konkurencni reseni.
- Pavel Kravčík
- Člen | 1195
@Felix: Například pokud se změní nějaký údaj. Při založení
záznamu se udělá celý otisk datové struktury. Při jednotlivých editacích
pak něco podobného:
{contract_refund: 5999, contract_status: end, __user: 18}
. Je to
celkem veliký systém na správu smluv a klientů, takže je požadavek na
hlídání takových změn. Přistupuje se k nim relativně málo (akorát na
detailu smlouvy), takže je zbytečné to mít v DB. Pak to vypadá jako na
obrázku. Ano, v DB může být smazaný, ale chceme mít třeba historicky
informaci o tom, že minulý měsíc bylo odebráno nějaké připojištění.
Prostě ukládání nedůležitých a málo exponovaných dat od špatného
programátora. :)
Nezjišťoval jsem proč, ale systém měl problém pokud bylo na disku 20.000 složek. Tak to dělíme po 5.000. Tj. id 3702 najdeš ve složce /5/, id 12342 ve složce /15/. id 176012 ve složce /180/.
Editoval Pavel Kravčík (20. 6. 2016 21:23)
- Pavel Kravčík
- Člen | 1195
@TomášVotruba: Jasně, na tom se shodneme. Pointa spíše je, kdy se rozhodnout pro jejich použití a kdy ne, což je velice složité a nakonec vyjde lépe se tomu vyhnout kompletně.
- Tomáš Votruba
- Moderator | 1114
@PavelKravčík Pro tvoje případy bych je nepoužil. Pro jiné aplikace, které dlouhodobě neškálují a drží nízkou komplexitu, ano.