Aktuálně: Postihly zákazy tvou profesi? Poptávka po ajťácích prudce roste, využij slevové akce 50% výuky zdarma!
Pouze tento týden sleva až 80 % na e-learning týkající se Javy

Lekce 9 - Jednoduchý redakční systém v Laravel - Správa článků

V minulé lekci, Jednoduchý redakční systém v Laravel - Tvorba článků, jsme začali s tvorbou administrace pro jednoduchý redakční systém v Laravel frameworku. Vytvořili jsme si seznam článků a editor pro jejich tvorbu.

V dnešním tutoriálu se podíváme na správu článků a povíme si něco o třídách HTTP požadavků.

HTTP požadavky

Předtím, než se vrhneme na vytváření nových částí aplikace, se zpětně podíváme na naší metodu store() v kontroleru ArticleController.php, kterou jsme definovali v minulé lekci. Jak už název napovídá, tato metoda by měla sloužit pro vkládání nového záznamu (v našem případě článku) do databáze. Mimo jiné ale obsahuje také validaci přijatých dat, což v případě větších formulářů nemusí být zrovna praktické. Proto si představíme tzv. Request třídy.

Request třídy

Request třídy kontrolují, zdali je uživatel oprávněný pro odeslání daného požadavku, a validují odeslaná data. Můžeme tak přesunout sadu pravidel daného HTTP požadavku právě do nich a metoda kontroleru bude poté obsahovat pouze logiku akce. Pojďme si nyní takovou třídu vytvořit.

Pro akci store() si vygenerujeme třídu StoreRequest pomocí Artisan příkazu make:request, který nepotřebuje žádné speciální možnosti:

php artisan make:request Article/StoreRequest

Jelikož tříd požadavků můžeme mít pro jeden kontroler více, je dobrým zvykem vytvářet pro každý kontroler vlastní složku.

Vytvořila se nám automaticky nová složka app/Http/Requests/ následně s námi definovanou podsložkou Article/. V ní najdeme vygenerovaný soubor StoreRequest.php, jehož obsah je následující:

<?php

namespace App\Http\Requests\Article;

use Illuminate\Foundation\Http\FormRequest;

class StoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Nově vygenerovaná třída StoreRequest dědící třídu frameworku FormRequest obsahuje pouze tyto dvě metody:

  • authorize() - Určuje, zdali je uživatel oprávněný pro odeslání požadavku. Jedná se o jedno z míst, kam můžeme toto ověření umístit. Dalším z nich může být například middleware, to si však ukážeme až v dalších lekcích.
  • rules() - Vrací pole s nastavenými validačními pravidly.

Jelikož se oprávněními (a uživateli) zatím nezabýváme, můžeme metodu authorize() kompletně odstranit. Pokud totiž není definovaná, automaticky se toto ověření považuje za úspěšné. Co už nás ale zajímá více, je právě metoda rules(), kterou naplníme pravidly z metody store() našeho kontroleru. Bude tedy vypadat následovně:

/**
 * Vrať validační pravidla pro formulář, který má na starosti tvorbu článků.
 *
 * @return array
 */
public function rules(): array
{
    return [
        'title' => ['required', 'min:3', 'max:80'],
        'url' => ['required', 'min:3', 'max:80', 'unique:articles,url'],
        'description' => ['required', 'min:25', 'max:255'],
        'content' => ['required', 'min:50'],
    ];
}

Následně můžeme validaci z metody store() kompletně odstranit. Upravíme však typ objektu proměnné $request na naší Request třídu místo té obecné, aby se následně aplikovala i definovaná pravidla:

/**
 * Zvaliduj odeslaná data přes formulář a vytvoř nový článek.
 *
 * @param  StoreRequest $request
 * @return RedirectResponse
 */
public function store(StoreRequest $request): RedirectResponse
{
    Article::create($request->all());

    return redirect()->route('article.index');
}
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Nezapomeneme samozřejmě importovat naší novou třídu:

use App\Http\Requests\Article\StoreRequest;

Z metody, která na začátku měla 26 řádků včetně dokumentace, jsme následně vytvořili metodu s pouhými 12 řádky a to jsme zachovali naprosto stejnou funkčnost! Jen si teď zkuste vytvořit nějaký článek a ignorovat některé z validačních pravidel. Třída HTTP požadavku vás nepustí dále :)

Editace článků

Pojďme se nyní přesunout k editaci článku.

Akce edit() a update()

Pohled s formulářem pro jeho úpravu budeme vracet v metodě edit():

/**
 * Zobraz formulář pro editaci článku a předej danému pohledu načtený článek.
 *
 * @param  Article $article
 * @return View
 */
public function edit(Article $article): View
{
    return view('article.edit', ['article' => $article]);
}

Samotná úprava záznamu bude probíhat v metodě update():

/**
 * Zvaliduj odeslaná data přes formulář a uprav načtený článek.
 *
 * @param  UpdateRequest $request
 * @param  Article $article
 * @return RedirectResponse
 */
public function update(UpdateRequest $request, Article $article)
{
    $article->update($request->all());

    return redirect()->route('article.index');
}

Stejně jako u akce store(), i zde předáme Eloquent metodě pouze pole dat z formuláře, v tomto případě metodě update(), a následně přesměrujeme uživatele do administrace článků.

UpdateRequest

Pro validaci dat opět používáme vlastní Request třídu s názvem UpdateRequest, kterou si nyní vygenerujeme ve složce app/Http/Requests/Article/ pomocí již zmíněného Artisan příkazu:

php artisan make:request Article/UpdateRequest

Tuto vygenerovanou třídu si ihned upravíme. Odstraníme metodu authorize() a definujeme si validační pravidla v metodě rules():

<?php

namespace App\Http\Requests\Article;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpdateRequest extends FormRequest
{
    /**
     * Vrať validační pravidla pro formulář, který má na starosti editaci článků.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'min:3', 'max:80'],
            'url' => [
                'required',
                'min:3',
                'max:80',
                Rule::unique('articles', 'url')->ignore($this->route('article')->id),
            ],
            'description' => ['required', 'min:25', 'max:255'],
            'content' => ['required', 'min:50'],
        ];
    }
}

Pro editaci článku se validační pravidla stala trochu složitějšími. Pro validační pravidlo unique musíme definovat výjimku aktuálního záznamu. Kdybychom ji totiž nedefinovali a chtěli pozměnit článek bez modifikace jeho URL, validace by nám následně vyhodila chybu, že v databázi již existuje článek s danou URL adresou, i když se jedná o právě editovaný článek.

Tuto výjimku nastavujeme skrz metodu ignore() builderu Unique, která přijímá ID záznamu. Také si povšimněte další výhody parametrů fungujících přes dependency injection (tzv. route model binding). Instanci modelu článku můžeme lehce získat pomocí metody route() třídy frameworku FormRequest, kdy předáme název parametru definovaného v routovacím souboru a nemusíme tento záznam složitě vybírat z databáze pouze přes předaný identifikátor (v našem případě URL článku).

Nakonec přidáme do našeho kontroleru ArticleController import této nové třídy:

use App\Http\Requests\Article\UpdateRequest;

Pohled

Nyní si vytvoříme pohled s názvem edit.blade.php ve složce resources/views/article/, který je téměř totožný s pohledem create.blade.php:

@extends('base')

@section('title', 'Editace článku ' . $article->title)
@section('description', 'Editor pro editaci článků.')

@section('content')
    <h1>Editace článku {{ $article->title }}</h1>

    <form action="{{ route('article.update', ['article' => $article]) }}" method="POST">
        @csrf
        @method('PUT')

        <div class="form-group">
            <label for="title">Nadpis</label>
            <input type="text" name="title" id="title" class="form-control" value="{{ old('title') ?: $article->title }}" required minlength="3" maxlength="80" />
        </div>

        <div class="form-group">
            <label for="url">URL</label>
            <input type="text" name="url" id="url" class="form-control" value="{{ old('url') ?: $article->url }}" required minlength="3" maxlength="80" />
        </div>

        <div class="form-group">
            <label for="description">Popisek článku</label>
            <textarea name="description" id="description" rows="4" class="form-control" required minlength="25" maxlength="255">{{ old('description') ?: $article->description }}</textarea>
        </div>

        <div class="form-group">
            <label for="content">Obsah článku</label>
            <textarea name="content" id="content" class="form-control" rows="8">{{ old('content') ?: $article->content }}</textarea>
        </div>

        <button type="submit" class="btn btn-primary">Uložit článek</button>
    </form>
@endsection

@push('scripts')
    <script type="text/javascript" src="{{ asset('//cdn.tinymce.com/4/tinymce.min.js') }}"></script>
    <script type="text/javascript">
        tinymce.init({
            selector: '#content',
            plugins: [
                'advlist autolink lists link image charmap print preview anchor',
                'searchreplace visualblocks code fullscreen',
                'insertdatetime media table contextmenu paste'
            ],
            toolbar: 'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
            entities: '160,nbsp',
            entity_encoding: 'raw',
        });
    </script>
@endpush

Jelikož HTTP akce store() je typu PUT, musíme ve formuláři opět použít Blade direktivu @method.

Pokud nyní vyzkoušíme upravit některý z existujících článků, vše proběhne naprosto v pořádku. Za pozornost ještě stojí datum poslední změny v seznamu článků, jenž se automaticky aktualizuje při jakékoliv změně daného záznamu. Jen si to sami vyzkoušejte :)

Odstraňování článků

Poslední část administrace, která nám chybí, je odstraňování článků. Jelikož formulář pro tuto akci už máme vytvořený v seznamu článků a věnovali jsme se mu minulou lekci, stačí nám teď pouze upravit akci destroy() v našem kontroleru:

/**
 * Odstraň článek z databáze.
 *
 * @param  Article $article
 * @return RedirectResponse
 */
public function destroy(Article $article): RedirectResponse
{
    try {
        $article->delete();
    } catch (\Exception $exception) {
        return redirect()->back()->withErrors(['Při procesu odstranění článku došlo k chybě.']);
    }

    return redirect()->route('article.index');
}

K odstranění záznamu používáme Eloquent metodu delete(), ve které však může dojít k výjimce. Tu musíme ošetřit, aby se uživateli nezobrazila stránka s chybou 500. Přesměrujeme ho tedy zpátky (na to používáme metodu back() builder funkce redirect()) a dáme mu vědět, že proces odstranění se nepodařil.

Tímto jsme dokončili vzhlednou a plně fungující administraci. Pokud se vám něco nepodařilo, můžete si stáhnout projekt z přiloženého archivu níže. V opačném případě výsledek našeho snažení můžeme shrnout tímto obrázkem:

Seznam článků v administraci v Laravel redakčním systému

V příští lekci, Jednoduchý redakční systém v Laravel - Laravel Mix, se zaměříme na front-end část naší aplikace. Podíváme se totiž na možnosti kompilace souborů, jako jsou JavaScript, SCSS, LESS a další.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 87x (46.79 MB)
Aplikace je včetně zdrojových kódů v jazyce php

 

Předchozí článek
Jednoduchý redakční systém v Laravel - Tvorba článků
Všechny články v sekci
Laravel framework pro PHP
Článek pro vás napsal Jan Lupčík
Avatar
Jak se ti líbí článek?
4 hlasů
Autor se primárně věnuje vývoji webových stránek a aplikací v PHP (framework Laravel) a je jedním z herních vývojářů komunitní modifikace TruckersMP.
Aktivity (16)

 

 

Komentáře
Zobrazit starší komentáře (5)

Avatar
Jan Lupčík
Super redaktor
Avatar
Jan Lupčík:19.9.2019 17:31

Ahoj, děkuji za zpětnou vazbu a taky za připomínku. Máš pravdu, během víkendu to opravím spolu s dalšími úpravami :)

Odpovědět
19.9.2019 17:31
TruckersMP vývojář
Avatar
Martin Š.
Člen
Avatar
Martin Š.:21.9.2019 14:41

Ahoj, seriál je super. Myslím si ale, že v této kapitole chybí při úpravě metody store() poznámka, že je třeba doplnit klauzuli use:

use App\Http\Requ­ests\Article\Sto­reRequest;

V Laravelu jsem začátečník - pro zběhlého uživatele je to asi samozřejmé, ale já jsem si to musel dohledat ve výsledných zdrojácích. Možná by se ta zmínka hodila i dalším začátečníkům. ;)

Díky za pěkný seriál!

Editováno 21.9.2019 14:42
 
Odpovědět
21.9.2019 14:41
Avatar
Jan Lupčík
Super redaktor
Avatar
Jan Lupčík:14.10.2019 16:33

Martin Š. a pavlicekr: seriál byl aktualizovaný a obsahuje obě žádané změny (vysvětlení neexistující metody v předchozím díle a přidání importů). Děkuji vám ještě jednou za feedback k seriálu :)

Odpovědět
14.10.2019 16:33
TruckersMP vývojář
Avatar
Attila Jančik:7. ledna 9:50

protože se změnili licenční podmínky TinyMCE a oznámení o tom zavazí, doporučuji použít v kódu:

<script type="text/javascript" src="{{ asset('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.9.2/tinymce.min.js') }}"></script>
Odpovědět
7. ledna 9:50
čo zješ, to ti už nevezmú...
Avatar
Karel Čada
Člen
Avatar
Karel Čada:7. března 14:21

Ahoj. Nějak mi nezabírá kontrola na duplicitu url.
Mohl bys mě prosím navést, kde by mohla být chyba.

public function rules(): array {
    return [
        'title' => ['required', 'min:3', 'max:80'],
        'url' => ['required', 'min:3', 'max:80',** 'unique:articles,url'],**
        'description' => ['required', 'min:25', 'max:255'],
        'content' => ['required', 'min:50'],
    ];
 
Odpovědět
7. března 14:21
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Karel Čada
Člen
Avatar
Odpovídá na Karel Čada
Karel Čada:7. března 19:20

Ahoj. Tak ten svůj výtvor jsem přeplácnul tím zipem z této lekce a funguje to. Tak chybu už asi nenajdu :(
Koukal jsem na rozdíly, ale nenašel jsem nic. Tak snad v budoucnu nenarazím...

Děkuji za tento seriál. Ať se daří.

 
Odpovědět
7. března 19:20
Avatar
Jan Lupčík
Super redaktor
Avatar
Odpovídá na Karel Čada
Jan Lupčík:8. března 10:45

Ahoj, záleží, jestli jsi i použil danou Request třídu v daném kontroleru. Tudíž pravidla jsi mohl mít někde napsaná, ale nebyla nikde aplikovaná :)

Odpovědět
8. března 10:45
TruckersMP vývojář
Avatar
Karel Čada
Člen
Avatar
Karel Čada:8. března 12:00

Děkuji za reakci.
v controlleru jsem měl

use App\Http\Requests\Article\StoreRequest;

Přesto to tam nešlo. Když jsem kontroly vrátil z request do controlleru, tak to fungovalo.

Pak jsem pokračoval v UpdateRequest a tam to zabralo napoprvé. Asi nějaké skvrny na slunci, možná nějaký refresh, který byl mezi jednotlivými kroky - restart IDE, restart xampp, restart prohlížeče.... co já vím :)

Ještě jednou dík. Za reakci i za seriál.

 
Odpovědět
8. března 12:00
Avatar
Jan Lupčík
Super redaktor
Avatar
Odpovídá na Karel Čada
Jan Lupčík:9. března 19:01

Krom importování request třídy do kontroleru nesmíš také zapomenout na jeho použití jako parametr k metodě store, aby zafungovala dependency injection a pravidla tudíž fungovala:

public function store(StoreRequest $request): RedirectResponse
{
    // ...
}

Jsem rád, že seriál se jinak líbil! :)

Odpovědět
9. března 19:01
TruckersMP vývojář
Avatar
Karel Čada
Člen
Avatar
Odpovídá na Jan Lupčík
Karel Čada:9. března 19:37

Je to tak. Jsem slepý - v té lekci je to jasně napsaný.

Upravíme však typ objektu proměnné $request na naší Request třídu místo té obecné, aby se následně aplikovala i definovaná pravidla:

 
Odpovědět
9. března 19:37
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 15. Zobrazit vše