V minulém dílu jsme si vytvořili kontaktní formulář a v tomto dílu se vrátíme k hlavní části našeho blogu a tím je zobrazení samostatného článku. Vytvoříme si proto vlastní routy a také si připravíme kategorie a zobrazíme článek, který je k ní přiřazen.

Controller

Jako první si vytvoříme controller PostController, který se bude starat o veškerou business logiku a také bude hrát prostředníka mezi modelem a pohledem. Controller vytvoříme pomocí příkazu:

php artisan make:controller PostController --resource

Tím se nám vytvoří nová třída v app/Http/Controllers. Do příkazu jsem přidal možnost --resource , díky čemuž se nám vygeneruje i obsah třídy, konkrétně metody, které se volají přes REST, které Laravel nabízí. Na první "pohled" to může znít trochu zmateně, ale za chvilku to více vysvětlím.

REST (Representational state transfer) je architektura, která zjednodušeně definuje, jak se má aplikace chovat na základě HTTP požadavků. Existují čtyři základní HTTP požadavky - GET, POST, PUT a DELETE. Každý slouží pro něco jiného a také každý by měl požadavek zpracovat jinak, například pro náš blog budou požadavky reprezentovat tyto činnosti:

  • GET: zobrazení článku, formuláře apod.
  • POST: uložení nového článku, odeslání formuláře, odeslání přihlášení...
  • PUT: editace existujícího článku
  • DELETE: smazání článku

Laravel staví na REST architektuře a proto pro každý typ HTTP požadavku definuje vlastní metodu, která se v controlleru defaultně zavolá:

  • GET: index(), create(), show(), edit()
  • POST: store()
  • PUT: update()
  • DELETE: destroy()

Požadavek typu GET obsahuje více metod, které Laravel volá na základě routy, která se používá. Tedy pro výpis všech článků se volá routa "/", podle kterého framework rozpozná, že se má zavolat metoda index() . Pokud bychom zavolali pomocí GETu např. /posts/create  (cesta ještě neexistuje), pak Laravel zvolí metodu create()  atd.

A přesně tyto metody se vygenerují v PostControlleru díky přidání flagu --resource . V této třídě si upravíme pouze dvě metody, zbytek využijeme až v administraci (vytváření, editace a smazání):

public function index()
{
    return view('post.index', ['posts' => Post::all()]);
}

Metoda index()  se volá při zobrazení všech článků a proto zkopírujeme kód z routes/web.php . Nezapomeňte importovat model use App\Post;

public function show(Post $post)
{
    return view('post.show', ['post' => $post]);
}

Hlavním tématem tohoto článku je zobrazení samostatného příspěvku. Toto dosáhneme právě pomocí této metody, kde si do pohledu předáme z modelu všechny údaje o příspěvku.

Routy

V této části aplikace nás ohledně příspěvků zajímají pouze dvě věci - zobrazení všech článků a zobrazení samostatného článku. Proto si v routes/web.php  upravíme cesty:

Route::get('/', 'PostController@index')->name('post.index');
Route::get('/post/{post}', 'PostController@show')->name('post.show');

První cesta je jednoduchá, pokud zadáme cestu "/", tedy např. nasblog.com, tak se zavolá třída PostController a metoda index() . Pro tuto cestu si také vytvoříme alias post.index , který můžeme využít v případě helperu route("post.index")  třeba v pohledu.

Druhou cestu využijeme při zobrazení samostatného příspěvku. Funguje na stejném principu jako předchozí cesta, liší se ale především tím, že obsahuhe {post} . Tato syntaxe definuje proměnnou, která může obsahovat prakticky cokoliv (samozřejmě můžeme vytvořit i validaci, aby tam nebylo možné napsat úplně cokoliv, ale o tom třeba někdy příště). Je poměrně nepraktické mít v adrese, která odkazuje na daný příspěvek, pouze ID příspěvku. Naopak mnohem přijatelnější je mít v cestě název/slug příspěvku, tedy například nasblog.com/posts/lorem-ipsum-dolore.  A právě díky tomu, že v routě můžeme definovat proměnnou, se tohoto dá dosáhnout velmi jednoduše.

Model

Model app/Post  již máme vytvořený a jedinou úpravu, kterou provdeme, je vložení této metody:

public function getRouteKeyName()
{
    return 'slug';
}

Laravel se snaží defaultně vkládat do proměnné primární klíč, konkrétně hodnotu ze sloupce id . Abychom toto obešli, přepíšeme metodu getRouteKeyName  a vrátíme název sloupce, podle kterého má v databázi článek vyhledávat a vracet jeho model.

View

Poslední částí je vytvoření nového pohledu resources/views/post/show.blade.php :

@extends('layouts.app')

@section('title', $post->title)

@section('content')
   <div class="card mb-4">
      <img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
      <div class="card-body">
         <h2 class="card-title">{{ $post->title }}</h2>
         <small>
            Autor |
            {{ $post->created_at->format('d.m.Y') }} |
            <a href="#">
               Kategorie
            </a>
         </small>
         <p class="card-text">{{ $post->content }}</p>
      </div>
   </div>
   <a href="{{ route('post.index') }}" class="btn btn-primary">Zpět na příspěvky</a>
@endsection

Autora a kategorii článku si vytvoříme za chvíli a poté tento pohled upravíme. Pro zobrazení data v českém formátu jsem využil metodu format()  třídy DateTime. Laravel automaticky sloupce created_at  a updated_at  trasformuje do instance DateTime . Pokud bychom měli více sloupců typu date  nebo datetime, pak můžeme upravit model tabulky tak, že přidáme novou proměnnou $dates  a vložíme do ní pole s našimi sloupci, např.:

protected $dates = ['deleted_at']; 

Další výhodou je to, že Laravel automaticky tuto hodnotu převede do instance třídy Carbon, která dědí ze třídy DateTime. Tato knihovna obsahuje mnoho užitečných metod pro pracování s datem a časem a velmi doporučuji si ji projít, najdete zde mnoho užitečných metod, které můžete využít. Dále v pohledu využíváme již dříve zmíněný helper route() , který odkazuje na seznam všech příspěvků.

Další úpravu provedeme v šabloně resources/views/post/index.blade.php , kde upravíme cestu odkazující na článek:

<a class="btn btn-primary" href="{{ route('post.show', $post->slug) }}">Číst dále &amp;rarr;</a>

V helperu route()  si definujeme druhý parametr, který slouží pro přidání dalších parametrů do cesty. Celý proces pak bude vypadat takto:

  • V šabloně využijeme helper {{ route('post.show', $post->slug) }} . Jako první se Laravel podívá, jestli existuje alias pro název post.show . Pokud ano, tak z helperu přebere druhý parametr a vloží ho definice routy, tedy v našem případě do proměnné {post} . Poté vygeneruje celou cestu, např. nasblog.com/post/lorem-ipsum-dolore
  • Po kliknutí na odkaz se skrze routes/web.php  zavolá PostController  a metoda show() . Tato metoda má parametr $post  modelu app/Post. V něm jsme si definovali, podle jakého klíče se má příspěvek hledat. Převezme si tedy hodnotu z proměnné {post}, která obsahuje string "lorem-ipsum-dolore", a pokusí se v tabulce posts ve sloupci slug najít tuto hodnotu. Pokud ji najde, model vrátí celou instanci modelu, kterou poté v PostControlleru  předáme do šablony. Pokud by model string nenašel, vyhodí chybu 404.

Tímto jsme dokončili zobrazení samostatného článku. V další sekci si vytvoříme model a controller pro kategorie a definujeme si vazby s příspěvky.

Kategorie

Controller

Kategorie budou v mnoha ohledech stejné jako příspěvky. Prakticky postup v Laravelu je většinou stejný - vytvoříme si controller, model, šablonu a routy. Tím si vytvoříme základní kostru a poté nabalujeme další logiku. Jako první tedy začneme s vytvořením nového controlleru:

php artisan make:controller CategoryController --resource

Jako jedinou metodu budeme využívat show(), protože chceme zobrazit všechny články, které pod danou kategorii spadají. Upravíme si ji tedy aby nám vrátila celou instanci dané kategorie:

public function show(Category $category)
{
    return view('category.show', ['category' => $category]);
}

Nezapomeňte přidat na začátek třídy use App\Category;

Routy

V routes/web.php  přidáme novou cestu pro zobrazení kategorie a jejich příspěvků:

Route::get('/category/{category}', 'CategoryController@show')->name('category.show');

Model

Model app/Category  již máme vytvořený a upravíme si ho tak, aby routa vyhledávala kategorii podle slugu:

public function getRouteKeyName()
{
    return 'slug';
}

Dále je potřeba vytvořit si novou vazbu na články. V našem případě je vazba 1:n, jelikož kategorie může obsahovat n příspěvků, ale každý příspěvek může mít přiřazenou pouze jednu kategorii. Tuto vazbu tak můžeme v modelu definovat takto:

public function posts()
{
    return $this->hasMany(Post::class);
}

Díky metodě hasMany  můžeme definovat právě vztah 1:n. Jako jediný parametr obsahuje odkaz na model app/Post . Možná si říkáte, jak může Laravel poznat, podle čeho se má kategorie napárovat na kategorie, když tam pouze odkazuje na model? Laravel má předdefinované syntaxe, které nám mohou usnadnit psaní a vývoj. Defaultně předpokládá, že primární klíč tabulky bude mít název id . Cizí klíče (nebo odkazy na jinou tabulku) pak budou obsahovat syntaxi nazevtabulky_id  (název tabulky musí být v singularu). Když se podíváte do databáze, kterou jsme si vytvořili, pak zjistíte, že tabulka categories  má primární klíč s názvem id  a tabulka posts  obsahuje cizí klíč s názvem category_id . Díky tomu, že splňujeme tuto syntaxi, není potřeba nic dalšího definovat. Pokud bychom tuto syntaxi nedodrželi nebo měli svou vlastní, pak je potřeba definovat v metodě hasMany  další parametry - hasMany(referenční tabulka, cizí klíč, primární klíč) .

To samé, ale v obráceném vztahu pomocí metody belongsTo je potřeba definovat v modelu app/Post :

public function category()
{
   return $this->belongsTo(Category::class);
}

Pohled

V resources/views/post/show.blade.php  si upravíme odkaz na kategorii:

<a href="{{ route('category.show', $post->category->slug) }}">
   {{ $post->category->name }}
</a>

Díky tomu, že jsme si v modelu Post definovali vztah ke kategorii, můžeme automaticky pomocí metody category()  přistupovat ke kategorii příspěvku a zobrazit/využít její hodnoty.

Vytvoříme si nový adresář a v něm šablonu resources/views/category/show.blade.php:

@extends('layouts.app')

@section('title', 'Příspěvky kategorie ' . $category->name)

@section('content')

   @foreach($category->posts as $post)
      <div class="card mb-4">
         <img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
         <div class="card-body">
            <h2 class="card-title">{{ $post->title }}</h2>
            <p class="card-text">{{ $post->content }}</p>
            <a href="{{ route('post.show', $post->slug) }}" class="btn btn-primary">Číst dále &amp;amp;rarr;</a>
         </div>
      </div>
   @endforeach
@endsection

Obsah pohledu je víceméně totožný jako při zobrazování všechny článku na úvodní stránce, nicméně pro cyklus využíváme metodu posts() , která nám díky definici vazby v modelu Category  vrací všechny příspěvky, které jsou přiřazené k dané kategorii. Protože jsme si na začátku této série vytvořili velmi jednoduchý seeder, tak máme aktuálně ke každé kategorii přiřazen právě jeden příspěvek.

Uživatelé

V případě zájmu lze vytvořit i seznam příspěvků od jednoho autora. Princip zobrazení je téměř totožný jako kategorie (za předpokladu vztahu 1:n). Pro jednoduchost si pouze v modelu Post  definuji vztah:

public function user()
{
   return $this->belongsTo(User::class);
}

A v resources/views/post/show.blade.php  zobrazím jméno autora: php {{ $post->user->name }}

Není vám něco jasné, nefunguje nebo mám někde chybu? Napište do komentářů!

Zdrojové kódy pro tento díl naleznete na GitHubu

Užitečné odkazy: