Konečně Filters pro Nette Database

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Tomáš Votruba
Moderator | 1114
+
+12
-

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)

CZechBoY
Člen | 3608
+
0
-

Neznám Doctrine a ani ty filtry takže… Jak to použiju v aplikaci? :-)
Mám to navázat na nějakej repozitář a vždy při filtrování dat zavolat na konec tu metodu applyFilter?

Tomáš Votruba
Moderator | 1114
+
+1
-

@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)

CZechBoY
Člen | 3608
+
0
-

Nešel by třeba přidat příklad s voláním + vytvářením nějaký Selection? :-)
Tohle rozšíření teda umí aplikovat jen jeden filtr nebo víc na tu jednu Selection?

Tomáš Votruba
Moderator | 1114
+
0
-

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)

CZechBoY
Člen | 3608
+
0
-

Z tý ukázky obyčejnýho repozitáře nejsem o moc moudřejší.
Funguje to teda nějak úplně samo a filtr se aplikuje (zavolá se ta metoda applyFilter) na úplně všechny dotazy do db?

Tomáš Votruba
Moderator | 1114
+
0
-

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)

CZechBoY
Člen | 3608
+
0
-

Aha, to bude mojí slabší znalostí angličtiny :D Affect je celkem známý slovo :-)

Tomáš Votruba
Moderator | 1114
+
+3
-

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)

CZechBoY
Člen | 3608
+
0
-

V praxi jsem to nezkoušel, pro moje účely je to až moc jednoduchý asi :-) Používám hodně subselectů, kde musím tu podmínku zpropagovat a s Nette\Database\Table to moc dobře nejde (musí se hrabat na interní SqlBuilder atd.).

Tomáš Votruba
Moderator | 1114
+
0
-

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
+
0
-

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
+
0
-

Teoreticky tomu moc nerozumím. Můžeš mi hodit co nejjednodušší zdroják, na kterým bys mi to ukázal?

CZechBoY
Člen | 3608
+
+1
-
$this->context->table('abc')
    ->select(':def.*');

Takže pokud bych vybíral z def nějaký data tak by se aplikoval nějakej filtr pomocí tvýho rozšíření.

Pavel Kravčík
Člen | 1195
+
0
-

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
+
0
-

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
+
+3
-

@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
+
0
-

@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
+
0
-

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
+
0
-

@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
+
+2
-

@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
+
0
-

@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
+
+1
-

@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/.

http://imgur.com/1CZLSnq

Editoval Pavel Kravčík (20. 6. 2016 21:23)

Felix
Nette Core | 1196
+
+2
-

@PavelKravčík Diky za osvetu. :-) Urcite se bude hodit.

PS: Ohledne tech slozek/souboru. Tam je trochu problem s OS, pri hodne souborech/slozkach v 1 urovni se hrozne zvetsuje slozitost nacitani/prochazeni a manipulace. :-)

Editoval Felix (21. 6. 2016 10:02)

Tomáš Votruba
Moderator | 1114
+
0
-

@CZechBoY Díky za podnět. Přidáno.

@PavelKravčík @newPOPE Na tyhle složité případy už se filtry nehodí, souhlasím.

Použil bych je jen tam, kde ušetří práci, mají smysl a nezpůsobují WTFs, viz příklady.

Pavel Kravčík
Člen | 1195
+
0
-

@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
+
+1
-

@PavelKravčík Pro tvoje případy bych je nepoužil. Pro jiné aplikace, které dlouhodobě neškálují a drží nízkou komplexitu, ano.