První boj za hezké URL, aneb Router a jeho FILTER_IN, FILTER_OUT

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
theacastus
Člen | 81
+
+1
-

Zdravím,

pustil jsem se do boje „za hezké URL“, tj aby adresa vypadala třeba /user/admin a ne /user/show/1.
Jde mi vlastně o první krok, pak už budu schopný si vše implicitně překopat, ale…

Na záznamu z přednášky Honzi Smitka (Posobota) https://www.youtube.com/watch?… jsem to celé prošel a začal jsem zkoušet na vlastní pěst. V tuto chvíli takto vypadá část mé RouterFactory.php:

$frontRouter[] = new Route('user/<id>', array(
                'presenter' => 'User',
                'action' => 'show',
                'id' => array(
                    Route::FILTER_IN => function ($id) use ($container) {
                        if (is_numeric($id)) {
                            return $id;
                        } else {
                            /** @var user \Nette\Database\Context */
                            $user = $container->NECO();

                            return $user->where('username', $id)->fetch()->id;
                        }
                    },
                    Route::FILTER_OUT => function ($id) use ($container) {
                        if (!is_numeric($id)) {
                            return $id;
                        } else {
                            /** @var user \Nette\Database\Context */
                            $user = $container->NECO();

                            return $user->get($id)->username;
                        }
                    }
                )
            ));

Po prohlédnutí zjistíte že se tam nachází metoda NECO() a to kdyz chci získat přístup k databázi přes $container.
PS: Ano, vím že psaní celé routy do RouterFactory není asi moc čisté, ale jak bude zmíněno níže, s modely jsem si zatím moc nepotykal…

EDIT: Landěnka dokonce hlásí že používám nedefinovanou proměnou $container, je čas naučit se s Modely

Ve videu je použita funkce createPages(), ale nevím kde je definovaná, co dělá apod. To jsem ve videu neviděl a zdroják jsem nenašel :/. Ano, v ukázce jde o hezčí odkazy na stránky, ne o profily uživatelů. Ale logika je stejná.

Samotný princip jsem pochopil, doufám, Route::FILTER_IN překládá/transformuje hezkou URL do originální nette URL tj. dělá z /user/admin/user/show/1 a Route::FILTER_OUT dělá to samé druhým směrem. OUT zajištuje to že v odkazech generovaných nette je použita nová hezká URL a IN že ji umí zpracovat.
Takto jsem to pochopil ±, doufám že nejsem zas tak daleko od pravdy.

Následně jsem začal zápasit i s URL u dalších věcí jako jsou příspěvky na blogu a stránky typu /contact apod… (z originálního /intro/contact) a jako zdroj pro URI je sloupec v databázi, jenže přemýšlím o tom zda by nebylo vhodnější mít speciální tabulku jen pro URI.

Pokud by někdo věděl, jak Router přemluvit a naučit mě trochu používat Modely, bylo by to skvělé. Našel jsem například https://pla.nette.org/…ru-ve-filtru ale tam se řeší něco jiného a není vysvětleno jak funguje jejich router

$router[] = new Route('[!<lang>]/[!<article [a-z-]+>]', [
    'presenter' => 'Article',
    'action' => 'default',
    'article' => [
        Route::FILTER_IN => ['ArticleModel::slugToId'],
        Route::FILTER_OUT => ['ArticleModel::idToSlug']
    ],
    'lang' => 'cs',
]);

Konkrétně si nevím rady s Route::FILTER_IN => ['ArticleModel::slugToId'] a Route::FILTER_OUT => ['ArticleModel::idToSlug']. Pochopil jsem že odkazují na \Model\ArticleModel a jeho metudy slugToId a idTuSlug. Ale nevím jak na to. Uživatelé, přihlašování registrace ještě nemám hotové, takže UserManager a jiné modely zatím nejsou.

A krom toho bojuji na další frontě s componenty, jako jsou přihlašovací a registrovací formuláře, sandbox mi dává zabrat :D

Editoval theacastus (11. 9. 2016 9:19)

Oli
Člen | 1215
+
0
-

Budeš potřebovat tohle: https://doc.nette.org/…dependencies#…. Pokud jsi to nečetl, tak si to přečti celé. Další věc je, že je dobré mít v routě název presenteru. <presenter>. Můžeš si ho i přejmenovat. Díky constructor injection si můžeš předat do konstruktoru \Nette\Database\Context, takže pak použiješ

// místo toho
/** @var user \Nette\Database\Context */
$user = $container->NECO();

// tohle
/** @var $user \Nette\Database\Selectize */
$user = $this->context->table('articles');
theacastus
Člen | 81
+
0
-

Díky, vrhám se do čtení. Co se týče presenteru, tak toto je jen pro jeden konkrétní případ routy, protože upravuji /user/show/<id> na /user/<id>, kde <id> je username z databáze.

U dalších odkazů jako je například post na blogu to asi využiju, díky

cca o hodinu později

Tak jsem asi něco pochytil

$frontRouter[] = new Route('user/<id>', array(
            'presenter' => 'User',
            'action' => 'show',
            'id' => array(
                Route::FILTER_IN => function ($id) {
                    if (is_numeric($id)) {
                        return $id;
                    } else {
                        /** @var $user \Nette\Database\Selectize */
                        $user = $this->context->table('account');

                        return $user->where('username', $id)->fetch()->id;
                    }
                },
                Route::FILTER_OUT => function ($id) {
                    if (!is_numeric($id)) {
                        return $id;
                    } else {
                        /** @var $user \Nette\Database\Selectize */
                        $user = $this->context->table('account');

                        return $user->get($id)->username;
                    }
                }
            )
        ));

Je to upravené a laděnka hází chybu že se chovám k $this jako k objektu i když to objekt není. Což je trochu zvláštní když jsem v RouterFactory.php kde je třída RouterFactory. Ale stejně si myslím že to nevykouzlí přístup k DB.

Editoval theacastus (11. 9. 2016 11:17)

GEpic
Člen | 566
+
-2
-

Databázi v ROUTERU? Proč?!

theacastus
Člen | 81
+
0
-

@GEpic, když chci „vyhezkanou url“. Nebo si na to spíš mám sestavit model? – Návrhy jak na něj? Navíc by nebyl ve výsledku jediný a mít Model na každý hezký odkaz je hloupost, takže možná jeden velký Model nebo rovnou vlastní router ?

Jinak router v DB jsem viděl zde https://www.youtube.com/watch?…

Petr Parolek
Člen | 455
+
0
-

Ahoj, já bych dal jednodušše:

$router[] = new Route('user/<username>', array(
            'presenter' => 'User',
            'action'    => 'show'
));

Spojení s databází přes model řeší presenter

Editoval ppar (11. 9. 2016 12:52)

Oli
Člen | 1215
+
+1
-

@GEpic, @ppar viděli jste tu přednášku? On chce transformovat ID na slug a nemá model. Az se douci model, tak to predela, ale zatim na to jde dobre.

V configu je ještě potřeba upravit registraci toho routeru. Z hlavy si to nepamatuju, poslu jak budu u PC.

Landsman
Člen | 152
+
0
-

Přesně, jak říká @ppar. To tvé monstrum je ohromný overkill a web by se ti zpomalil.
Jednoduše si přidej do svého modelu metodu na najítí uživatele podle username a v presenteru s tím pak pracuj.

Petr Parolek
Člen | 455
+
0
-

@Oli, nemíchej tu špatné návyky.

Editoval ppar (11. 9. 2016 13:08)

Petr Parolek
Člen | 455
+
0
-

pracovat s modelem je dobré se naučit hned na začátkz

theacastus
Člen | 81
+
0
-

@ppar to je hezké a to jsem měl i funkční, já jsem to asi nezobeznil jak jsem měl.

Ano, chtěl jsem si to na useru vyzkoušet – jako to bylo v přednášce.

Ale co třeba transformace postů na blogu, tj nechci /blog/post/63 ale třeba /p/63-dneska-jsem-se-ucil-s-modely nebo alespoň /blog/post/63-dneska-jsem-se-ucil-s-modely. (druhý příklad celkem brutálně porušuje „pravidlo“, že čím kratší url tím lépe) Jinak číslo přidávám abych zaručil že url je unikátní a nebude to později kolidovat s něčím

  1. Neumím s modely, což je celkem problém a snažím se to napravit, ale čtení teorie je jedna věc a praktická část mého učení je věc jiná.
  2. Chci zprovoznit uživatelské rozhraní a s tím spojené komponenty pro formuláře na přihlášení, registraci a vytvořit UserManager model…

PS: Nepohádejte se tady…

Editoval theacastus (11. 9. 2016 13:23)

Landsman
Člen | 152
+
+1
-

@theacastus

S tím pravidlem krátkých adres bych si tolik hlavu nelámal. Unikátnost můžeš ověřovat před uložením do databáze (nevím jak generuješ slugy) a ID tam být nemusí, když nepovolíš uložení duplicity. Zpravidla se ti to ID ale bude hodit pro další věci :)

$router[] = new Route("p/<id>-<slug>", [
    "presenter" => "Article",
    "action" => "detail",
    "id" => null,
    "slug" => null
]);

Ty modely jsou první věc.

Oli
Člen | 1215
+
+1
-

ppar napsal(a):

@Oli, nemíchej tu špatné návyky.

Jde o to, že se k tomu zatím nedostal. Nemyslím si, že je možné, pokud někdo začíná aby měl vše od začátku „tip ťop“. Jestliže se zatím necítí na model, na to že ho potřebuje, tak ho nech. Ať si zatím ošahá ostatní části. Pokud navíc sám přijde na to, proč model potřebuje (až ho to bude tlačit) tím líp (viz. Nešahej na ty kamna spálíš se|Au, sakra to bolí, už na ty kamna šahat nebudu ;-)).

Přesně, jak říká @ppar. To tvé monstrum je ohromný overkill a web by se ti zpomalil.

Používám to ve všech projektech. Není s tím problém. Pokud je problém, stačí to cachovat…

Jednoduše si přidej do svého modelu metodu na najítí uživatele podle username a v presenteru s tím pak pracuj.

To on ale nechce. On chce jinou URL. V aplikaci pak můžeš nechat User:show 12, ale v url budeš mít místo user/show/12 třeba uzivatel/pepik-novaku.


Takovej zlatej grál tohohle problému je http://zlml.cz/…ni-url-adres od @mrtnzlml

GEpic
Člen | 566
+
0
-

Používám to ve všech projektech. Není s tím problém. Pokud je problém, stačí to cachovat…

Já ti rozumím, ale pokud se nedostal k modelům, asi nemá smysl cachování vůbec zmiňovat.

Chápu že hezké URL jsou cool, ale na druhou stranu dají se řešit VŽDY v jakékoliv části vývoje. Navíc špatně nastavené routy BĚHEM vývoje dokáží kolikrát dost zatopit (navíc pokud začneme rozlišovat např. CLI).

Radši mám při vývoji defaultní routy, abych doopravdy viděl, co se tam vlastně děje.

Editoval GEpic (11. 9. 2016 14:54)

Oli
Člen | 1215
+
+1
-

@GEpic s tím souhlasím. Podle mě je důležité si uvědomit, že se začíná učit pracovat s Nette. Možná i úplně programovat. Už je to dávno, co jsem se já učil programovat, ale vím, že největší pokrok jsem ze začátku viděl v tom co je vidět. Když použije model, tak to není navenek nikde vidět. URL je. To si myslím, že je ta motivace začít s touhle částí.

Cachování jsem zmínil „pro budoucnost“. Na localhostu ho to s 10 záznamy v db nebude omezovat. Ono ani na produkci s 500 záznamy by ho to neomezovalo.

Možná to není nejlepší místo, kde začít. Na druhou stranu je to vidět a někde se začít musí. :)

Myslím, že rejpat se v kodu a rikat, ted pouzij tohle a ted tohle a pak tohle a na venek neni nic vidět by začátečníka spíš odradilo (tak ja sedim 2. den u pc, rejpu se v tom a neni nikde nic videt)…

GEpic
Člen | 566
+
+1
-

Já získal nejvíc moudrosti skrze funkci bdump(). A i objevení funkce bdump() bylo úžasné, protože do té doby jsem používal Debugger::barDump().

Vím že trošku offtopic, za to se omlouvám. Ale nejvíc se toho člověk naučí zkoušením a sebezlepšováním, můžeme tu vést nekonečné debaty kde začít a co se učit, ale vše to stojí a padá čistě na tom jednom člověku.

theacastus
Člen | 81
+
0
-

GEpic napsal(a):

Vím že trošku offtopic, za to se omlouvám. Ale nejvíc se toho člověk naučí zkoušením a sebezlepšováním, můžeme tu vést nekonečné debaty kde začít a co se učit, ale vše to stojí a padá čistě na tom jednom člověku.

To se sebezlepšováním vás tu zmínilo hodně a jak řekl @Oli, v URL je ta změna vidět. V tuto chvíli se učím Nette, né že bych byl PHP profík. Ale když jsem poprvé nakoukl do Nette, templatů a práci s databází (tak půl roku zpět) a programovat jsem tak nějak začal 3 roky zpátky, ale Nette mi v mnoha věcech otevírá cestu. Teď jsem třeba měl a stále mám problém s Modely – jak postavit Model, k čemu a KDY je dobrý a do toho se mi tu míchají komponenty, továrničky a další „kouzla“. Samozřejmě že chci aby vše jelo hned ale něčím se začít musí, cachování to ale opravdu nebude

Oli
Člen | 1215
+
0
-

Trochu jsme se odklonili od tematu. :) Nevím, jestli jsi to už vyřešil, ale databázi do routeru dostaneš takhle: https://forum.nette.org/…zi-v-routeru#…

theacastus
Člen | 81
+
0
-

Dobrá, co jsem tak koumal a hrabal se ve svém kódu a po nějakém to přemýšlení je tahání databáze do routeru opravdu zlo, je tu někde na fóru, planette popř. jinde nějaký návod/dokumentace k Modelům a jeho prací s URL, URI a slugy ?

Popř. jestli někde máte nějaké basic model. Já se v tom stejně budu hrabat a budu to rozbíjet a následně spravovat, aneb já se učím v nette :D

CZechBoY
Člen | 3608
+
0
-

@theacastus mrkni třeba k čemu je DI a jak se to používá v Nette https://doc.nette.org/…introduction

Petr Parolek
Člen | 455
+
0
-

@theacastus já čerpal odtud:

https://pla.nette.org/…tr-s-lomitky

Oli
Člen | 1215
+
0
-

Už jsem ti psal

Oli napsal(a):

Takovej zlatej grál tohohle problému je http://zlml.cz/…ni-url-adres od @mrtnzlml

Máš obecně 2 možnosti

  1. použít databázi (ideálně v nějaké repository|service třídě), kde si namapuješ ID-slu; ID-folder; ID-whatever
  2. všude v aplikaci předělat id na slug. Pokud se pak rozhodneš, že chceš místo slugu třeba id-slug, tak to zase všude předělat.

Právě aby jsi zanesl závislost na databázi na jedno místo slouží ty filtry. Pokud je nepoužiješ, tak máš závislost na třeba slugu napříč aplikací. Pokud je použiješ, tak máš závislost na id. A jen v routeru pracuješ s tím slugem. Pokud to pak (z jakéhokoli důvodu) přejmenuješ ze slug na třeba url, tak to upravíš na asi 4 řádcích v routeru…

Proto je podle mě lepší řešení číslo 1 než 2.