Persistentní parametr zmizí z URL, když je to defaultní hodnota

wonka007
Člen | 11
+
0
-

Mám takto definovaný router:

$router->addRoute('[<lang=cs [a-z]{2}>/][<section=sales sales|account>/]edit', 'Homepage:edit');

Router by měl matchovat adresy:

edit
cs/edit
cs/sales/edit
cs/account/edit

Když však nechám vygenerovat link

$this->link('Homepage:edit', ['lang' => 'cs', section' => 'sales']);

Tak dostanu stanu adrestu www.teststranka.cz/edit.

Existuje nějaká možnost jak zabránit, aby se vygenerovalo www.teststranka.cz/cs/sales/edit, ale pouze tehdy, když již předem byli definované v URL?

Představoval bych si něco takovéhoto:
Původní URL, na které jsem ⇒ vygenerované URL
www.teststranka.czwww.teststranka.cz/edit
www.teststranka.cz/cswww.teststranka.cz/cs/edit
www.teststranka.cz/cs/sales ⇒ www.teststranka.cz/cs/sales/edit

Napadlo mě řešení, kdy nebudu definovat defaultní hodnotu na „sales“. Tím pádem budu dostávat null hodnotu, kterou budu pouze považovat za hodnotu „sales“.
Ale to není moc elegantní řešení. Nezná někdo něco lepšího?

Děkuji

m.brecher
Generous Backer | 758
+
0
-

Ahoj,

takhle při zběžném pohledu vidím potencionální problém pro Router Nette v duplicitě url, kterou už Tvůj návrh obsahuje:

edit   /// tyto url jsou duplicitní
cs/edit
cs/sales/edit

Odhaduji z hlavy, aniž bych to testoval, že všechny tyto url vrátí Homepage:edit, lang = cs, section = sales.

Routování v Nette není úplně jednoduché a doporučuji navrhovat routy co nejjednodušší. Router sice vyhodnocuje routy odshora a použije první která matchne, jenže potom se vytvořený Request odešle do Presenteru, který ověřuje duplicitu – vygeneruje z Requestu LinkGenerátorem url a pokud se liší tak na to vygenerované url přesměruje.

wonka007
Člen | 11
+
0
-

Takže to chápu dobře, že to nedoporučujete?

Ono by to asi ani moc nepomáhalo SEO.

m.brecher
Generous Backer | 758
+
+2
-

@wonka007

Zásadní pro návrh url je zda se jedná o veřejně přístupné stránky/eshop, nebo administraci pro přihlášené.

Co se týče vlivu url na seo je dnes prakticky nulová.

a) veřejně přístupné stránky

zde obvykle chceme pěkné, jednoduché čitelné url. U jazyku obvykle cs jako defaultní a ostatní jazyky v url. Presentery ani akce v url přímo nepotřebujeme, spíš potřebujeme odlišit typy obsahu např. /clanky, /kategorie, /blog, atd., ale presentery a akce mohou být s typy obsahu v souladu, ale nutně nemusí:

/cesky-clanek
/de/deutsch-artikel
/en/english-article

b) administrace

V administraci pro přihlášené uživatele je naopak žádoucí mít presenter i akci vyjádřenou v url a defaultní akce spíš ani nepoužívat, výjimkou jsou jednoakční presentery, kde naopak je akce vždy default. Všechny presentery administrace je vhodné uvést v url stringem /admin nebo něco podobného, aby se url v administraci netloukly s veřejnou částí.

/admin/cs/sales/edit/34
/admin/de/sales/edit/34

kde sales je presenter, edit je akce a 34 je id záznamu, který se edituje, pokud se v té akci edit nebude editovat nějaký záznam, ale bude tam výpis sales s nějakými komponentami pro editaci pomocí signálu/ajaxu, tak bych já spíš volil název akce list, názvy jako edit, update, create, new bych ponechal pro práci s jedním záznamem, aby se to později nepletlo.

Z těch url jak jsi poslal ale není jasné co na těch url vlastně chceš dělat. Presenter máš pro všechno stejný Homepage:edit, což vypadá, jako kdyby Jsi na Homepage chtěl něco editovat pomocí presenteru HomepagePresenter v akci edit. Uvnitř akce ale máš parametr section = sales, takže asi chceš editovat sales a zřejmě i jiné tabulky než sales.

Udělej si pořádek v názvosloví, HomepagePresenter:default ponech pro vstupní stránku / s nějakým rozcestníkem. Pokud vstupní stránku nechceš, tak tam budeš mít rovnou nějaká data a to bude presenter odpovídající té tabulce v databázi.

Takže:

  • SalesPresenter bude obhosdpodařovat tabulku sales třeba v akcích:
    • list – výpis sales
    • update – editace sales
    • create – vytvořit nové sales

pokud se sales nebudou vůbec editovat, tak postačí akce list

data pro SalesPresenter dodá /Model/SalesModel.php, nebo se používá i /Model/SalesRepository.php

Příklad

Zde je příklad, jak jsem si udělal routy pro malý web 2 jazyky, 7 článků v jednom jazyce, administrace a přihlášení.

final class RouterFactory
{
	use Nette\StaticClass;

	public static function createRouter(): RouteList
	{
		$router = new RouteList;

        $router->addRoute('sign-<action in|out>', [
            'presenter' => [ Route::VALUE => 'Sign'],
            'lang' => [ Route::VALUE => 'cs'],
            'slug' => [ Route::VALUE => '@sign'],
        ]);

        $router->addRoute('[<lang de>/][<slug (?!admin).*>]', [
            'presenter' => [ Route::VALUE => 'Article'],
            'action' => [ Route::VALUE => 'default'],
            'lang' => [ Route::VALUE => 'cs'],
            'slug' => [ Route::VALUE => '@homepage'],
        ]);

        $router->addRoute('admin[/<presenter>/<action>[/<id \d+>]]', 'AdminEntry:default');

		return $router;
	}
}

První routa odspodu – obecná routa pro administraci:

/admin vede na vstupní stránku řízenou AdminEntry:default

Routa zvládá libovolné množství presenterů s libovolnými akcemi a volitelným parametrem id, třeba:

/admin/article/list
/admin/article/create
/admin/article/update/66

v administraci nemám jazykové verze.

Druhá routa odspodu – veřejná část jednoduchého webu:

Veřejný web pohání jednoakční presenter – tedy má akci default. Nejmenuje se HomepagePresenter, ale ArticlePresenter, protože sice dodá homepage, ale i jiné stránky ve formě článků. Který článek se vypíše řídí parametr slug – veřejný web by neměl používat číselné id, ale slug (string) ve tvaru třeba /babis-prohral-dalsi-volby. Článek na url / je vhodné, aby měl jiný slug než '' třeba začínající znakem @, který se ve slugách nepoužívá, třeba @homepage. To se nastavuje:

'slug' => [ Route::VALUE => '@homepage']   což je nastavení defaultní hodnoty

v definici parametru slug ale musíme vyloučit hodnotu admin, protože by nám nefungovala poslední routa, to zajišťuje:

[<slug (?!admin).*>]

veřejný web má dva jazyky češtinu defaultní a němčinu:

'lang' => [ Route::VALUE => 'cs'],

parametr lang je volitelný a může mít pouze hodnotu de, to se nastavuje takto:

[<lang de>/]

Poslední routa odspodu – přihlášení/odhlášení

První routa už je jednoduchá, pro přihlášení a odhlášení, dvouakční presenter SignPresenter s akcemi in/out, aby to fungovalo, je potřeba nastavit parametr slug, který se sice nevyužije, ale jinak tam vznikaly kolize:

'slug' => [ Route::VALUE => '@sign'],

Přihlašování je pouze v češtině, takže:

'lang' => [ Route::VALUE => 'cs'],

Pak je potřeba si v aplikaci pohlídat, aby neexistoval český článek se slugem admin, de, @homepage@sign

Ty si z toho budeš muset něco vybrat a modifikovat, jestli to je spíš administrace pro sales, tak jdi cestou poslední routy a přidej k tomu parametr lang, jestli je to veřejný web, tak cestou prostřední routy ;)

Editoval m.brecher (23. 3. 2023 2:29)

Kamil Valenta
Člen | 762
+
0
-

m.brecher napsal(a):
Co se týče vlivu url na seo je dnes prakticky nulová.

Má tohle nějaký zdroj? Minimálně Google má URL stále mezi seo faktory, vydává ke tvorbě URL doporučení, duplicitní URL se mu stále nelíbí, příliš zanořené slugy mají nižší rank…

m.brecher
Generous Backer | 758
+
0
-

@KamilValenta

Má tohle nějaký zdroj? Minimálně Google má URL stále mezi seo faktory, vydává ke tvorbě URL doporučení, duplicitní URL se mu stále nelíbí, příliš zanořené slugy mají nižší rank…

Takhle s tím souhlasím, jde ale o to, co má jakou váhu. Url má malou – tak 2%. Z různých experimentů co jsem dělal má naprosto klíčový vliv traffic na konkrétních stránkách, na druhém místě je přenášení page ranku mezi stránkami webu, longtail klíčových slov v textu a tag <title> především první dvě/tři slova.

Zanoření důležitých stránek dovnitř webu, kam se musí několikrát proklikávat je nejlepší způsob jak SEO zabít. Pokud je slug hodně zanořený, tak to může být spojeno se zanořením stránky jako takové a to snižuje seo, ne ten dlouhý slug jako takový.

Marek Znojil
Člen | 77
+
+2
-

Duplicity jsou prostě špatně. Už jen z principu ti tříští sílu jednotlivých stránek, popřípadě zpětných odkazů a i klíčových slov. Snižuješ tím kvalitu, důvěryhodnost.

Pokud máš správně poskládaný router, tak to Nette řeší za tebe. Je i dobré mít vyřešený systém v pořadí parametrů například v parametrickém filtrování apod.

wonka007
Člen | 11
+
0
-

Moc děkuji za rozsáhlou odpověď.

Homepage presenter jsem použil pouze jako příklad. Já jsem zvyklý tvořit administrativní stránky, kde jsem URL moc neřešil.

Měl bych tedy poslední otázku. Pro přehlednost dám konkrétnější příklad.

Web, na kterém pracuji, má být rozdělen na tři části. Část pro veřejnost, pro učitele a pro vědce. Zároveň má mít lokalizaci pro alespoň 6 jazyků s tím, že další budou přibývat.
Představoval jsem si tedy, že by URL vypala:

www.testweb.cz/vedci/cs/
www.testweb.cz/ucitele/fr/
www.testweb.cz/verejnost/en/

Nepřijde mi však uživatelsky příjemné pořád psát /verejnost/. Líbilo by se mi, abych se /verejnost/ automaticky doplnila, když zadám pouze www.testweb.cz/en.
Vlastně bych potřeboval opačný proces ke změně adresy s využitím volitelné sekcence.
S routou

'[section=verejnost verejnost|vedci|ucitele/]'

se URL www.testweb.cz/verejnost/ změní na www.testweb.cz.
Já potřebuji přesně opačný proces.

Jelikož by se URl změnila, tak by nemělo docházet k rozmělňování SEO.
Vím, že by se pro tento účel by hodily subdomény, ale klient je nechce.

m.brecher
Generous Backer | 758
+
+1
-

@wonka007

Web, na kterém pracuji, má být rozdělen na tři části. Část pro veřejnost, pro učitele a pro vědce. Zároveň má mít lokalizaci pro alespoň 6 jazyků s tím, že další budou přibývat.

Sekce asi budou mít všechny nějaké články, nějakou navigaci a homepage atd… Zásadní je jak moc se ty sekce dají vyřešit společnými presentery a šablonami s tím, že se pouze rozčlení obsah do tří url. Z toho co Jsi napsal to není jasné. Ale ideální je to udělat stejné, budeš mí 3× méně práce a 3× snazší údržbu.

url pro sekce

Klíčová je organizace obsahu pro uživatele v databázi (články, …)

Tabulku články si představuji nějak takhle:

// tabulka article:

id
section  [enum = 'PublicSection', ScienceSection', 'PedagogSection']
slug_cs
slug_fr
title_cs
title_fr
text_cs
text_fr
......

Článek se vybere z databáze podle slugu a sice podle kterého rozhodne parametr $lang. Tím je článek daný a sekce se z něj odvodí, ne naopak !! Do url dávat sekci je vlastně nadbytečné, protože článek, který se má zobrazit si nadefinujeme slugem a jaká je to sekce je PRIMÁRNĚ dáno v databázi – article.section !!

Přidat pomocný string do url, který by vizuálně ukázal do jaké sekce daný článek patří se ale bez problémů dá a dokonce se to i běžně dělá, když se např. na blogu články adresují pomocí id a slug s textem se přidá navíc. Podobný přístup by jsi mohl zvolit v tomto případě.

V presenteru si definuj parametr pro sekci třeba $section a její hodnotu nastav podle aktuálního článku. Potom tento parametr propíšeš do url – jednoduše ho definuješ v routě Routeru. A to není všechno. Parametr musí být ve všech odkazech, takže aby Jsi to nemusel pořád psát, tak si $section udělej jako persistentní parametr – třeba v BasePresenteru, pak bude ve všech url automaticky:

Jako persistentní definuj jak $section, tak i $lang:

    use Nette\Application\Attributes\Persistent;  // bacha!! tohle tam musí být

    class BasePresenter extends Presenter
    {
	    #Persistent
        public string $section;  // bacha !! musí být public, jinak to nefunguje !!

	    #Persistent
        public string $lang;

    }

Bude třeba vyřešit dva případy kanonizace url:

a) když někdo přijde na existující článek ale se špatně uvedenou sekcí v url tak přesměrovat na url se správnou sekcí – tohle Router Nette nebude umět, to si musíš zajistit v kódu.

/cs/ucitele/clanek-jenom-pro-vedce

// musíš přesměrovat na:

/cs/vedci/clanek-jenom-pro-vedce

b) Francouzům nemá smysl podsouvat url s českým názvem sekce:

/fr/ucitele/article-en-langue-francaise

// musíš přesměrovat na:

/fr/professeur/article-en-langue-francaise

Poznámka: nejlepší je se na slug sekce který přišel z webu v url vykašlat a jenom ověřit, jestli souhlasí s tím co odečteš z databáze a když nesouhlasí tak na správný slug přesměrovat ;) – to je ta kanonizace.

jazykové verze sekcí

Jak vyřešit jazykové verze sekce v url? Řešení pomocí Routeru bude řešení krkolomné, já bych to řešil v modelové třídě ArticleModel (ArticleRepository), kde si budeš číst článek.

class ArticleModel
{
   private string $lang;
   .....

  private const SectionSlugs = [
		'cs' => [
					'PublicSection' => 'verejnost',
					'ScienceSection' => 'vedci',
					'PedagogSection' => 'ucitele',
				],
		'fr' => [....],
	];

   public function getOne(string $slug): \stdClass
   {
      $article = $this->database->table('article')->where('slug_{$lang}', $slug)->fetch()->toArray();
	  $article['section'] = $this::SectionSlugs[$lang][$article['section']];
      return (object)$article;
   }

}

Model podle aktuální jazykové verze rovnou vrátí správný tvar sekce pro url.

Jak předat $lang do modelu ? – buďto přímo z presenteru, nebo přes nějakou službu v konfiguraci, to chce už nějaké zkušenosti.

jazykové verze

Parametr $lang bych v BasePresenteru definoval jako persistentní. Článek se bude vyhledávat podle slugu a podle jazyku, podle sekce NE!!

/fr/professeur/u-article-sur-l-actualite

Dotaz do databáze použije slug /u-article-sur-l-actualite a bude vyhledávat v sloupečku article.slug_{$lang} Takže když bude jazyk v url špatně:

/cs/professeur/u-article-sur-l-actualite

Tak článek nenajde, což je OK.

Poznámka: jazyk bych v url dal na první místo, protože on určuje jazykovou verzi section v url, je to tak logičtější a je běžnou praxí, že se jazyk dává v url na první místo.

Psáno z hlavy, tak si to musíš otestovat a upravit.

Editoval m.brecher (24. 3. 2023 17:24)

wonka007
Člen | 11
+
0
-

Moc děkuji za rady.