Lekce 8 - Jednoduchý redakční systém v Laravel - Tvorba článků

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Jednoduchý redakční systém v Laravel - Výpis článku, jsme si zobrazili náš první článek, který jsme si předtím připravili. Dnes se přesuneme k tvorbě administrace a začneme rovnou upravováním vygenerovaných metod kontroleru a vytvářením pohledů, jelikož modelovou vrstvu a routy již máme přichystané.

Seznam článků

Jako první si vytvoříme zobrazení seznamu článků.

Akce kontroleru

Jak již víme, použijeme k tomu metodu index(). Ta nebude obsahovat nic jiného než zobrazení pohledu, kterému předá všechny články seřazené podle abecedy:

/**
 * Zobraz seznam článků seřazený podle abecedy.
 *
 * @return Response
 */
public function index()
{
    return view('article.index', ['articles' => Article::orderBy('title')->get()]);
}

Pohled

Nyní si vytvoříme nový pohled ve složce resources/views/article/ a nazveme ho index.blade.php. Bude se jednat o jednoduchý výpis článků do tabulky:

@extends('base')

@section('title', 'Seznam článků')
@section('description', 'Výpis všech článků v administraci.')

@section('content')
    <table class="table table-striped table-bordered table-responsive-md">
        <thead>
            <tr>
                <th>Titulek</th>
                <th>Popisek</th>
                <th>Datum vytvoření</th>
                <th>Datum poslední změny</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @forelse ($articles as $article)
                <tr>
                    <td>
                        <a href="{{ route('article.show', ['article' => $article]) }}">
                            {{ $article->title }}
                        </a>
                    </td>
                    <td>{{ $article->description }}</td>
                    <td>{{ $article->created_at }}</td>
                    <td>{{ $article->updated_at }}</td>
                    <td>
                        <a href="{{ route('article.edit', ['article' => $article]) }}">Editovat</a>
                        <a href="#" onclick="event.preventDefault(); $('#article-delete-{{ $article->id }}').submit();">Odstranit</a>

                        <form action="{{ route('article.destroy', ['article' => $article]) }}" method="POST" id="article-delete-{{ $article->id }}" class="d-none">
                            @csrf
                            @method('DELETE')
                        </form>
                    </td>
                </tr>
            @empty
                <tr>
                    <td colspan="5" class="text-center">
                        Nikdo zatím nevytvořil žádný článek.
                    </td>
                </tr>
            @endforelse
        </tbody>
    </table>

    <a href="{{ route('article.create') }}" class="btn btn-primary">
        Vytvořit nový článek
    </a>
@endsection

První zajímavostí na tomto pohledu je použití Blade direktivy @forelse ... @empty ... @endforelse, jež vypíše záznamy pomocí PHP cyklu foreach() pouze v případě, že nějaké existují. V opačném případě se uživateli zobrazí text o chybějících článcích.

Spuštění DELETE metody

Za povšimnutí také stojí odkázání na editaci článku, kdy jako druhý parametr helper funkci route() předáváme pole s parametry pro danou routu. Klíč každé hodnoty, která je buď identifikátor záznamu (většinou id, v našem případě url), nebo instance daného modelu, je název parametru.

Pro odstranění článku však nemůžeme použít jednoduché odkázání, jelikož se provádí HTTP metodou DELETE. Odstraňování dat by z bezpečnsotních důvodů nemělo být odkázáno na metody GET ani POST. DELETE je vlastně nadstavba POST. Místo toho si tedy vytvoříme skrytý formulář, jenž se odešle po kliknutí na odkaz (přes event onclick). Deklarace HTTP metody DELETE ve formuláři probíhá skrz Blade direktivu @method.

Blade direktiva @method vloží skryté políčko do formuláře stejně jako Blade direktiva @csrf. Pokud se podíváme na skrytý formulář jednoho z článků přes "Inspect element" (klávesa F12 v prohlížeči), uvidíme pouze dvě skrytá políčka, jejichž názvy začínají prefixem _, aby se případně nepletly s námi definovanými políčky, viz níže.

<form action="http://localhost:8000/article/uvod" method="POST" id="article-delete-1" class="d-none">
    <input type="hidden" name="_token" value="g7K5Lt8LRE1pzVlrWfVhCwNy78UgP6f8fPIwHXnb">
    <input type="hidden" name="_method" value="DELETE">
</form>

Odkaz na seznam článků

Nakonec nesmíme zapomenout odkázat na nově fungující stránku v našem menu, které se nachází v hlavní šabloně resources/views/base.blade.php:

<nav class="my-2 my-md-0 mr-md-3">
    <a class="p-2 text-dark" href="#">Hlavní stránka</a>
    <a class="p-2 text-dark" href="{{ route('article.index') }}">Seznam článků</a>
    <a class="p-2 text-dark" href="#">Kontakt</a>
</nav>

Vytváření nových článků

Dále se podíváme na vytváření nového článku.

Akce create() a store()

Formulář pro vytváření nového článku si zobrazíme v akci create():

/**
 * Zobraz formulář pro vytváření nového článku.
 *
 * @return Response
 */
public function create()
{
    return view('article.create');
}

Validace formuláře a ukládání nového článku bude probíhat v akci store():

/**
 * Zvaliduj odeslaná data přes formulář a vytvoř nový článek.
 *
 * @param  Request $request
 * @return Response
 * @throws ValidationException
 */
public function store(Request $request)
{
    $this->validate($request, [
        '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'],
    ]);

    $article = new Article();
    $article->title = $request->input('title');
    $article->url = $request->input('url');
    $article->description = $request->input('description');
    $article->content = $request->input('content');
    $article->save();

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

Jak si můžete všimnout, metoda store() obsahuje parametr $request, i když není definovaný v tabulce rout. Získáme ho opět pomocí dependency injection, jelikož definujeme, o jaký typ objektu se jedná. Můžeme sice pracovat s helper funkcí request() jako v minulých lekcích (v takovém případě by metoda neměla žádný parametr), v následující lekci si však ukážeme, proč se v některých případech vyplatí více používat právě tento objektový přístup.

Pohled

Vytvoříme pohled create.blade.php ve složce resources/views/article/. Novinkou tohoto pohledu je použití helper funkce old(), která obsahuje stará data formuláře, například v případě, kdy data pro nový článek neprojdou přes validační pravidla:

@extends('base')

@section('title', 'Tvorba článku')
@section('description', 'Editor pro vytvoření nového článku.')

@section('content')
    <h1>Tvorba článku</h1>

    <form action="{{ route('article.store') }}" method="POST">
        @csrf

        <div class="form-group">
            <label for="title">Nadpis</label>
            <input type="text" name="title" id="title" class="form-control" value="{{ old('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') }}" 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') }}</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') }}</textarea>
        </div>

        <button type="submit" class="btn btn-primary">Vytvoř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

Pro editor obsahu článku jsem se rozhodl použít externí nástroj TinyMCE, užitečný WYSIWYG HTML editor připomínající např. MS Word.

Vytváření nových článků je plně funkční. Můžeme si to vyzkoušet na stránce /article/create (jednoduše se na ni proklikáte ze seznamu článků):

Editor pro vytváření nových článků v redakčním systému v Laravel

Avšak kód v metodě store() pro vytvoření nového článku se zdá repetitivní a může tak akorát spět ke zbytečným chybám kvůli překlepům, jelikož musíme definovat hodnotu pro každý atribut:

$article = new Article();
$article->title = $request->input('title');
$article->url = $request->input('url');
$article->description = $request->input('description');
$article->content = $request->input('content');
$article->save();

Pojďme si tuto akci tedy trochu vylepšit.

Mass assignment

Místo nastavování hodnot jedné po druhé můžeme využít Eloquent metody create(), kde předáme pouze pole dat z formuláře:

Article::create($request->all());

Z šesti řádků jsme udělali pouze jeden a přitom jsme zachovali stejnou logiku aplikace. Bohužel jak už možná tušíte, touto metodou by se do článku mohly dostat i nechtěná data. V našem případě by to ničemu nevadilo, přeci jen není čeho zneužít na článcích. U důležitějších databázových tabulek, jako jsou například uživatelé, by však mohlo bez našeho vědomí dojít k předání jiné hodnoty, než bychom očekávali, a to třeba pro administrátorská práva. Tento útok se nazývá mass assignment a více se o něm dočtete v odkazovaném článku.

Laravel nás automaticky chrání před tímto útokem. Pokud si nyní zkusíme vytvořit nový článek, dostaneme následující chybu:

Mass assignment chyba v redakčním systému v Laravel

Jak nám chybová hláška napovídá, v našem modelu Article bychom měli definovat pole vlastností, které mohou být předávané stylem uvedeným výše. Na to slouží proměnná $fillable, kam dosadíme všechny vlastnosti našich článků:

/**
 * Pole vlastností, které nejsou chráněné před mass assignment útokem.
 *
 * @var array
 */
protected $fillable = [
    'title', 'url', 'description', 'content',
];

Pokud si nyní zkusíme vytvořit článek, vše už bude fungovat tak, jak má. Tuto možnost předávání hodnot v poli využijeme i u editace článku. To je však předmětem další lekce, Jednoduchý redakční systém v Laravel - Správa článků, kde i mimo jiné dokončíme administraci.


 

 

Článek pro vás napsal Jan Lupčík
Avatar
Jak se ti líbí článek?
1 hlasů
Autor se primárně věnuje vývoji webových stránek a aplikacích v PHP (speciálně ve frameworku Laravel) a je jedním z vývojářů komunitního módu TruckersMP.
Předchozí článek
Jednoduchý redakční systém v Laravel - Výpis článku
Všechny články v sekci
Laravel framework pro PHP
Miniatura
Následující článek
Jednoduchý redakční systém v Laravel - Správa článků
Aktivity (2)

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!