Routování, česky bychom to mohli velmi volně pojmenovat jako překládání URL cest, je jedním ze základních konceptů Laravel frameworku a jedná se o velmi flexibilní systém. V tomto článku si na praktickém příkladě vysvětlíme různé možnosti, jak routy můžeme využít.

Co jsou routy v Laravelu?

Routy jsou způsob vytvoření URL požadavku na aplikaci. Největší výhodou těchto URL adres je to, že si můžete definovat cesty jakkoliv chcete aby vypadaly. Nejčastěji pak snadno čitelné a tzv. "SEO friendly" (lépe čitelné pro vyhledávače).

Od verze 5.5. jsou routy defaultně v adresáři /routes , kde cesty pro webovou aplikaci jsou v souboru web.php  a cesty pro API jsou v api.php .

Základní routování

Věci se nejlépe pochopí na konkrétním a praktickém příkladě. Pro náš účel si tedy budeme vytvářet cesty pro blog.

Když si otevřete routes/web.php , najdete v něm tento kód:


Route::get('/', function () {
    return view('welcome');
});

Prakticky je již definovaná routa pro domácí stránku. Tedy kdykoliv tato routa získá požadavek vykreslit cestu "/", vrátí se pohled welcome.blade.php , nacházející se v resources/views.

Struktura definice rout je poměrně jednoduchá. V požadovaném souboru (web.php  nebo api.php ) začněte zavoláním statické třídy Route:: (což je alias pro fasádu Illuminate\Support\Facades\Route) a poté následuje požadavek, který chcete přiřadit ke konkrétní routě. Dále následuje funkce, která vykoná výsledek po zpracování routy. Router, který je zodpovědný za zpracování, umožňuje registrovat routy reagující na jakýkoliv HTTP požadavek:


Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

Když bychom si to měli převést na náš blog, pak jakmile uživatelé zadají naši stránku www.blog.cz, automaticky se zavolá routa "/", která vykreslí domovskou stránku (pohled welcome). Stejný způsob lze využít, pokud bychom je chtěli odkázat například na stránku "O mě". V tom případě bychom routu definovali v souboru web.php takto:


Route::get('o-me', function () {
    return view('aboutme');
});

Adresa samozřejmě nebude fungovat a vyhodí se výjimka, protože neexistuje pohled aboutme.blade.php.

Požadavek typu POST se nejčastěji využívá ve formuláři, kdy potřebujeme vytvořit nový záznam, například nový článek. PUT/PATCH pak pro editaci a DELETE pro smazání záznamu. Typ OPTIONS se používá jen ve velmi vzácných případech a pro tento článek není potřeba ho nijak vysvětlovat.

Pro kontrolu a informacích ohledně rout je nejlepší artisan příkaz route:list, který vám vygeneruje všechny routy a informace o nich:


$ php artisan route:list
+--------+----------+----------+------+---------+--------------+
| Domain | Method   | URI      | Name | Action  | Middleware   |
+--------+----------+----------+------+---------+--------------+
|        | GET|HEAD | /        |      | Closure | web          |
|        | GET|HEAD | api/user |      | Closure | api,auth:api |
|        | GET|HEAD | o-me     |      | Closure | web          |
+--------+----------+----------+------+---------+--------------+

S controllery

V předchozí části jsme s routami použili i callback, konkrétně jsme vrátili pohled. Při zpracování složitější logiky nebo prostě pro oddělení logiky, lze namísto callbacku použít jako druhý parametr controller s názvem akce:


Route::get('about-me-controller', 'AboutMeController@index');

Prvně je název controlleru (namespace se předpokládá App\Http\Controllers) a za zavináčem následuje název metody, která se má v controlleru zavolat. V tomto případě se tedy zavolá třída AboutMeController  a metoda index() .

Parametry

Pokud se jedná o větší aplikaci tak se neobejdete bez využití parametrů v cestách. Příkladem parametru může být číslo stránky, název článku či kategorie, štítky apod. Jedná se především o dynamicky získané parametry na jejichž základě pak zpracujete potřebný požadavek. Zůstaneme stále u blogu a jako příklad parametru uvedu název článku:


Route::get('article/{name}', function ($name) {
    echo 'Název článku je ' . $name;
});

Parametr se vkládá mezi složené závorky a do callbacku předáváme parametry, které jsme v routě použili. Název parametrů v callbacku může být jakýkoliv a nemusí být shodný s názvem parametru v routě, Laravel automaticky pozná o jaký parametr se jedná na základě pořadí. Úplně stejný způsob lze využít, pokud bychom potřebovali více parametrů. Potřebujeme například vygenerovat cestu category/tutorials/article/my-first-article, kde tutorials je název kategorie a my-first-article je název článku. To lze snadno udělat takto:


Route::get('category/{categoryName}/article/{articleName}', function ($category, $article) {
    echo 'Název kategorie je ' . $category .' a název článku je ' . $article;
});

Podobný způsob lze použít s controllery, kde do metody jako parametry použijeme parametry z routy:


// web.php
Route::get('category/{categoryName}/article/{articleName}', 'TutorialController@show');

// App\Http\Controllers
class TutorialController extends Controller
{
    public function show($category, $article)
    {
        echo 'Název kategorie je ' . $category .' a název článku je ' . $article;
    }
}

Nepovinné parametry

Často narazíte na to, že parametr v routě je užitečný, ale bylo by dobré, aby se požadavek zpracoval i bez toho, aby tam musel vkládat nějaký parametr. Například pokud bychom chtěli zobrazit pouze nějakou kategorii, ale pokud by uživatel nezadal název, pak by se zobrazily všechny kategorie:


Route::get('category/{categoryName?}', function ($category = null) {
    echo $category != null ? 'Kategorie je ' . $category : 'Všechny kategorie';
});

Nepovinný parametr funguje stejně jako povinný parametr a také vkládá mezi složené závorky, důležitým rozdílem je ale otazník na konci parametru. Druhý parametr je callback, kde za parametr vkládáme výchozí hodnotu, pokud jsme nevyužili parametr. V tomto případě pokud jsme nevložili název kategorie tak zobrazíme text s všemi kategoriemi. Můžeme ale zde použít jakoukoliv výchozí hodnotu, například výchozí kategorii.

Omezení pomocí regulárních výrazů

Mějme příklad, kdy namísto názvu článku chceme zobrazovat v URL pouze jejich ID (i když to není moc SEO friendly). Článek podle ID v parametru vytáhneme z databáze a zobrazíme. Protože je id v databázi typu integer, tak jsme si jisti, že id musí být vždy číslo a nic jiného. Co když tady uživatel zadá adresu /article/1-zkouska ? V horším případě selže aplikace, protože se bude snažit z databáze vytáhnout záznam, který už z definice parametru nemůže existovat. V lepší případě budete s tímto případem počítat a začnete do zpracování přidávat logiku kontroly parametru a pokud neprojde, tak ho odmítnete. Laravel avšak tuto kontrolu umožňuje již při definici routy:


Route::get('/article/{id?}', function ($id = 1) {
      echo 'Zobrazím článek s ID ' . $id;
})->where('id', '[0-9]+');

Při zpracování této URL cesty se Laravel prvně podívá, jestli existuje parametr ID. Pokud ne, pak automaticky zobrazí text s výchozím ID 1. Pokud existuje, pak se zavolá metoda where() , která zkontroluje parametr id podle regulárního výrazu a pokud je výsledek false, pak uživatelovi vyhodí výjimku NotFoundHttpException , která se vyhodnotí jako chyba 404. V opačném případě vše v pořádku projde.

Vlastní pojmenování rout

V Laravelu je běžnou konvencí pojmenovávat si routy, ke kterým je pak mnohem jednodušší přístup pomocí názvu routy a také se vyhnete psaní celé URL adresy v šablonách. Ulehčí to také práci v případě, že potřebujeme upravit URL. Změnu provedete jen na jednom místě a vlastní pojmenování můžete nechat stejné. Jednoduchým příkladem může být URL adresa k profilu uživatele /user/profile . Místo toho, abychom museli tuto url cestu všude vypisovat, tak si k ní přiřadíme vlastní název:


Route::get('user/profile', function () {
    //
})->name('profile');

Když pak potřebujeme vygenerovat URL pro tuto cestu, stačí využít helper route():


$url = route('profile');

Stejně tak toho můžeme využít například při přesměrování:


return redirect()->route('profile');

Pokud bychom měli v routě definované parametry, pak do pomocné metody route()  přidáme druhý parametr, kde se hodnoty automaticky vloží na správné místo:


Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = route('profile', ['id' => 1]);

Seskupení

Seskupení routy (anglicky group) nám umožňuje sdílet stejné nastavení, jako jsou prefixy nebo middleware, pro více rout bez potřeby definovat stejné parametry pro každou routu zvlášť. Seskupení provedeme pomocí metody Route::group , kde je potřeba specifikovat sdílené parametry.

Middleware

Abychom přiřadili middleware do skupiny, musíme použít metodu middleware()  před definováním skupiny. Midleware jsou pak zpracovány v pořadí, jak jsou definovány v poli:


Route::middleware(['web', 'api'])->group(function () {
    Route::get('category/{categoryName?}', function ($category = null) {
        // prvne se zavola middleware web a pote api
        echo $category != null ? 'Kategorie je ' . $category : 'Všechny kategorie';
    });

    Route::get('/article/{id?}', function ($id = 1) {
        // prvne se zavola middleware web a pote api
        echo 'Zobrazím článek s ID ' . $id;
    })->where('id', '[0-9]+');
});

Prefixy

Pokud máme routy, jejichž prefixy se shodují a stále opakují, můžeme využít metody prefix() . Například pokud bychom dělali administrační rozhraní a všechny URL adresy by začínaly prefixem "admin":


Route::prefix('admin')->group(function () {
    // URL admin/category
    Route::get('category/{categoryName?}', function ($category = null) {
        echo $category != null ? 'Kategorie je ' . $category : 'Všechny kategorie';
    });

    // URL admin/article
    Route::get('/article/{id?}', function ($id = 1) {
        echo 'Zobrazím článek s ID ' . $id;
    })->where('id', '[0-9]+');
});

Vlastní pojmenování

Stejně jak jsme si dělali vlastní pojmenování rout pro jednodušší vygenerování URL cest, lze si vytvořit vlastní pojmenování, které bude stejné pro všechny routy ve skupině. Opět mějme například administrační rozhraní, kde potřebujeme, aby všechny vlastní pojmenování rout začínalo s názvem "admin":


Route::prefix('admin')->name('admin.')->group(function () {
    Route::get('category/{categoryName?}', function ($category = null) {
        echo $category != null ? 'Kategorie je ' . $category : 'Všechny kategorie';
    })->name('category');

    Route::get('/article/{id?}', function ($id = 1) {
        echo 'Zobrazím článek s ID ' . $id;
    })->where('id', '[0-9]+')->name('article');
});

Všimněte si, že v metodě name()  ve skupině je "admin." i s tečkou. Pro vygenerování URL pak můžeme použít routy s názvem admin.category  nebo admin.article .

Namespace

Pokud bychom nevyužili callbacku ale controlleru, pak výchozí namespace je App\Http\Controllers . Když bychom si chtěli třídy více zanořit, pak bychom museli tento nový namespace psát ve skupině stále dokola. Laravel naštěstí umožňuje nadefinovat si vlastní namespace, který nám ulehčí psaní a také zlepší čitelnost. Mějme třídu například namespace App\Http\Controllers\Admin :


Route::namespace('Admin')->group(function () {
    // zavolaji se controllery v namespace App\Http\Controllers\Admin
    Route::get('category/{categoryName?}', 'CategoryController');
    Route::get('/article/{id?}', 'ArticleController');
});

Navázání na model

Je běžnou praxí, že jakmile uživatel zadá do adresy název článku (třeba upraví název z "part-1" na "part-2) nebo změní ID, pak na základě tohoto parametru se vytáhne z databáze záznam. Pokud by záznam neexistuje, vyhodí se chyba 404. Laravel umožňuje parametr rovnou navázat na potřebný model, aniž bychom museli nějak řešit logiku. Začněme jednoduchým příkladem, kde jako parametr článku využíváme jeho ID, například article/55 :


Route::get('article/{article}', function (App\Article $article) {
    echo $article->name;
});

Protože proměnná $article  má type-hind Eloquent modelu App\Article  a název proměnné je stejný jako jako v URL části {article} , Laravel automaticky vloží instanci modelu, kde se ID shoduje s parametrem z URL cesty. Pokud by se žádná instance modelu nenašla, pak se vrátí odpověď typu 404. Laravel automaticky předpokládá, že má vyhledávat záznam podle sloupce, který má název id. Problém vzniká, když bychom chtěli model navázat na routu, ale parametr není ID, ale třeba název článku (respektive její slug, tedy název zformátován do správné URL podoby), například article/my-first-article . V tom případě musíme v požadovaném modelu, v tomto případě App\Article , přepsat metodu getRouteKeyName() :


/**
 * Get the route key for the model.
 *
 * @return string
 */
public function getRouteKeyName()
{
    return 'slug';
}

Pokud bychom použili stejnou definici routy, ale namísto ID vložit slug článku, pak proces bude probíhat stejně. Tedy Laravel se pokusí najít instanci modelu Article, kde parametr odpovídá záznamu ve sloupci slug.

Na pořadí záleží!

Častým problémem je nenačtení routy, nebo naopak načtení jiné, než které jsme chtěli. Znovu zkontrolujete syntaxi a vše je v pořádku, ale stále stejný výsledek. Problém pak často v tom, že jsme předtím nadefinovali jinou, velmi podobnou, která se zpracovala dříve. Laravel prochází routy v pořadí, v jakém jsou definované a pokud narazí na první, který splňuje definici routy, pak ji vrátí a nestará se, jestli dále existuje nějaká lépe definovaná routa. Například:


Route::get('article/{name?}', function ($name = 'test') {
    echo 'Název článku je ' . $name;
});

Route::get('article', function () {
    echo 'Clanky';
});

Pokud použijeme parametr /article/55 , pak se načte první routa. Nicméně i když žádný parametr nevložíme /article/ , stále se použije první routa. Je to proto, že první routa splňuje podmínky pro obě routy a tedy se žádná další routa nevykoná. Toto je potřeba mít na paměti a vytvářet routy tak, aby se náhodou nevykonala jiná, která je pro nás nežádoucí.

Přípomínky či dotazy? Pište do komentářů!