Vlastní blog v Laravel: Vytvoření článku

Aktualizováno pro Laravel 10

V tomto díle se zaměříme na vytvoření nového článku. Jako první si upravíme PostController a vytvoříme si šablonu s formulářem pro vytvoření článku.

Formulář

V Http/Controllers/PostController.php si upravíme metodu create():

use App\Models\Category;
use App\Models\User;

public function create()  
{  
	$users = User::all();
    $categories = Category::all();
    return view('posts.create', compact('users', 'categories'));  
}

Jako první si zde vytáhneme z databáze všechny uživatele a kategorie, protože ty pak budeme dávat na výběr při vytváření nového článku. Poté vrátíme šablonu. Dalším krokem je vytvoření nové šablony s formulářem resources/views/posts/create.blade.php:

@extends('layouts.app')

@section('title', 'Nový článek')

@section('content')
	<div class="container mx-auto px-4">
		<h1 class="text-2xl font-bold mb-4">Vytvořit nový článek</h1>

		<form action="{{ route('posts.store') }}" method="POST">
			@csrf
			
			<div class="mb-4">
			    <label for="author" class="block text-sm font-bold mb-2">Autor:</label>
			    <select name="author" id="author" class="border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
			        @foreach ($users as $user)
			            <option value="{{ $user->id }}">{{ $user->name }}</option>
			        @endforeach
			    </select>
			    @error('author')
			        <span class="text-red-500 text-xs italic">{{ $message }}</span>
			    @enderror
			</div>

			<div class="mb-4">
			    <label for="category" class="block text-sm font-bold mb-2">Kategorie:</label>
			    <select name="category" id="category" class="border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
			        @foreach ($categories as $category)
			            <option value="{{ $category->id }}">{{ $category->name }}</option>
			        @endforeach
			    </select>
			    @error('category')
			        <span class="text-red-500 text-xs italic">{{ $message }}</span>
			    @enderror
			</div>
			
			<div class="mb-4">
				<label for="title" class="block text-sm font-bold mb-2">Název článku:</label>
				<input type="text" name="title" id="title" value="{{ old('title') }}" class="border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
				@error('title')
					<span class="text-red-500 text-xs italic">{{ $message }}</span>
				@enderror
			</div>

			<div class="mb-4">
				<label for="content" class="block text-sm font-bold mb-2">Obsah článku:</label>
				<textarea name="content" id="content" rows="10" class="border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">{{ old('content') }}</textarea>
				@error('content')
					<span class="text-red-500 text-xs italic">{{ $message }}</span>
				@enderror
			</div>

			<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
				Vytvořit článek
			</button>
		</form>
	</div>
@endsection

Stručně popíšu co se zde odehrává:

  • Příkaz @extends('layouts.app') na začátku ukazuje, že tento pohled bude používat základní layout s názvem app' Je to běžná praxe, kdy máme v Laravelu jednotný layout pro celou aplikaci, a jednotlivé pohledy poté tento layout rozšiřují svým specifickým obsahem.
  • @section('title', 'Nový článek') definuje hodnotu sekce title' která je poté použita v hlavičce HTML dokumentu pro definici titulku stránky.
  • Hlavní část kódu je obalena v @section('content') ... @endsection, což znamená, že všechny kódy uvnitř těchto značek budou vloženy do sekce content základního layoutu.

V rámci sekce content se nachází formulář, který je odesílán na URL definovanou pomocí {{ route('posts.store') }}. Funkce route je v Laravelu používána k generování URL na základě názvu dané trasy. posts.store je název trasy, která zpracovává ukládání nových příspěvků. Ta je automaticky zaregistrována v web.php pomocí metody resource():

Route::resource('posts', PostController::class);

Formulář obsahuje dvě proměnné, jedna pro název článku a druhá pro jeho obsah. Funkce {{ old('title') }} a {{ old('content') }} jsou použity k naplnění hodnot, pokud byl formulář odeslán, ale vrátil chyby validace. Tímto způsobem uživatel nemusí znovu vyplňovat všechna pole, pokud udělá chybu. Direktivy @error('title') ... @enderror a @error('content') ... @enderror jsou zase použity k zobrazení chybových zpráv, pokud dojde k chybě při validaci názvu článku nebo jeho obsahu.

Direktiva @csrf v Laravel Blade šablonách vkládá do formuláře skrytý vstupní prvek, který obsahuje CSRF (Cross-Site Request Forgery) token. Tento token je klíčovým nástrojem pro ochranu aplikace před CSRF útoky. CSRF útoky jsou typem útoku, při kterém škodlivý web nutí uživatelův prohlížeč vykonat nežádoucí akci v jiné webové aplikaci, kde je uživatel legitimně přihlášen. Bez CSRF ochrany by útočník mohl vytvořit falešný požadavek, který by se tvářil jako požadavek od přihlášeného uživatele. Použitím CSRF tokenu Laravel zajišťuje, že každý odeslaný formulář pochází skutečně z aplikace a ne z externího zdroje. Když je formulář odeslán, Laravel zkontroluje, zda hodnota CSRF tokenu v požadavku odpovídá hodnotě uložené v uživatelově session. Pokud se hodnoty neshodují, Laravel vyhodí chybu a požadavek je zastaven. Tedy direktiva @csrf je důležitá pro zajištění bezpečnosti formulářů v Laravel aplikacích a měla by být vždy přítomna ve všech formulářích, které odesílají data metodou POST.

Na konci formuláře je tlačítko pro odeslání, které, když uživatel klikne, odešle formulář k serveru ke zpracování.

Uložení do databáze

V Http/Controllers/PostController.php si upravíme metodu store():

$request->validate([
    'title' => 'required|max:255',
    'content' => 'required',
    'author' => 'required|exists:users,id',
    'category' => 'required|exists:categories,id',
]);

$post = new Post();
$post->title = $request->input('title');
$post->slug = Str::slug($request->input('title'));
$post->content = $request->input('content');
$post->user_id = $request->input('author');
$post->category_id = $request->input('category');
$post->save();
  
return redirect()->route('posts.index')->with('success', 'Článek byl úspěšně vytvořen.');

Začátek kódu se zabývá validací dat získaných z HTTP požadavku. Metoda validate na objektu $request přijímá asociativní pole, kde klíče odpovídají názvům polí ve formuláři a hodnoty definují pravidla pro validaci těchto polí. V tomto případě se vyžaduje, aby byly obě pole title a content vyplněny. Kromě toho je pro pole title stanoveno další pravidlo, že nesmí být delší než 255 znaků. Také se kontroluje, jestli byl vyplněn autor článku a také kategorie. Všimněte si metody exists:users,id, která kontroluje podle ID jestli daný uživatel existuje v tabulce users. To samé platí pro kategorie. Pokud data neprojdou validací, Laravel automaticky přesměruje uživatele zpět na formulář a předá do pohledu informace o chybách.

Pokud data validaci projdou, vytvoří se nová instance třídy Post, což je model reprezentující články v databázi. Následně jsou naplněny jeho atributy title, slug, content, user_id a category_id hodnotami z formuláře. slug je vytvořen pomocí Laravel funkce Str::slug, která převede název článku na formát vhodný pro URL.

Poté je pomocí metody save tento nově vytvořený článek uložen do databáze.

Na konci se uživatel přesměruje na přehled článků pomocí redirect()->route('posts.index'). Metoda with přidá do session flash zprávu, která může být poté zobrazena uživateli jako potvrzení úspěšné akce.

Odkaz v navigaci

Na závěr si ještě upravíme navigaci v layoutu, kde si přidáme odkaz na vytvoření nového článku. Upravíme si tedy resources/views/layouts/app.blade.php:

		<nav class="bg-white p-4 shadow-md">
			<div class="container mx-auto">
				<ul class="flex">
					<li class="mr-4">
						<a href="/" class="text-blue-500 hover:text-blue-800">Domů</a>
						<a href="{{ route('posts.create') }}" class="ms-4 hover:text-blue-800">Nový článek</a>
					</li>
				</ul>
			</div>
		</nav>

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

Seriál

  1. 1. Úvod a instalace
  2. 2. Příprava databáze
  3. 3. Layout a zobrazení článků
  4. 4. Vytvoření článku
  5. 5. Editace článku
  6. 6. Smazání článku
  7. 7. Zobrazení článku