Máme připravenou databázi, design a teď přejdeme k poslední části architektury MVC a to jsou controllery. V tomto díle si vytvoříme několik jednoduchých rout, controller pro zobrazení pohledu a také využijeme komponenty jako jsou notifikace a Form Request Object pro zpracování a odeslání emailu.

Než přejdeme k samotnému formuláři, upravíme si pár souborů z předchozího dílu. Jako první si upravíme public/css/custom.css :

body {
    padding-top: 70px;
    margin: 0 0 100px;
}

footer {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 100px;
    width: 100%;
    overflow: hidden;
}

Díky této úpravě jsme patičku udělali fixní, tedy vždy se bude zobrazovat ve spodní části stránky nezávisle na jejím obsahu. Dálší úpravu provedeme v resources/views/includes/navigation.blade.php :

<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
    <div class="container">
        <a class="navbar-brand" href="{{ url('/') }}">Blog</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item {{ Request::is('/') ? 'active' : '' }}">
                    <a class="nav-link" href="{{ url('/') }}">Domů <span class="sr-only">(current)</span> </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">O nás</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link {{ Request::is('contact') ? 'active' : '' }}" href="{{ url('contact') }}">Kontakt</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" data-toggle="dropdown" id="login" href="#" role="button" aria-haspopup="true" aria-expanded="false"> Login </a>
                    <div class="dropdown-menu" aria-labelledby="login">
                        <a class="dropdown-item" href="#">Odhlášení</a>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</nav>

Důležitou změnou je zde úprava odkazů na jednotlivé stránky. Využíváme helper url() , který nám vygeneruje celou url cestu. Další úprava se týká css třídy active, která se v odkazu doplní pouze tehdy, pokud se uživatel nachází na dané routě. Prakticky řečeno pokud bude uživatel na stránce s příspěvky, pak bude odkaz "Domů" bílým písmem, pokud bude na routě contact, tedy s kontaktní formulářem, tak odkaz "Kontakt" bude bílým atd.

Routy

Jako první si nadefinujeme dvě jednoduché routy pro zobrazení a zpracování kontaktního formuláře. Do routes/web.php  přidejte tyto dvě routy:

Route::get('/contact', 'ContactController@show');
Route::post('/contact', 'ContactController@mailToAdmin');

Metoda GET slouží pro zobrazení formuláře, zatímco metoda POST pro zpracování a odeslání formuláře.

Controller

Vytvoříme si nový controller pomocí artisan příkazu, který nám vygeneruje třídu ContactController v app/Http/Controllers:

php artisan make:controller ContactController

V této třídě si vytvoříme na základě rout dvě metody show()  a mailToAdmin() . Celá třída tak bude vypadat takto:

namespace App\Http\Controllers;

use App\Admin;
use App\Http\Requests\ContactFormRequest;
use App\Notifications\InboxMessage;

class ContactController extends Controller
{
    public function show()
    {
        return view('contact.contact');
    }

    public function mailToAdmin(ContactFormRequest $message, Admin $admin)
    {        
        // posleme adminovi notifikaci
        $admin->notify(new InboxMessage($message));

        // redirect uzivatele zpet
        return redirect()->back()->with('message', 'Díky za zprávu. Odpovím hned jak budu moct!');
    }
}

Metoda show()  se volá při GET požadavku na routu /contact  a vracíme pouze pohled s kontaktní formulářem, který si vytvoříme o něco později. Druhá metoda mailToAdmin()  se volá při POST požadavku opět na stejnou routu a zpracovává odeslání formuláře. Jako první parametr je FormRequest , který si vytvoříme hned v další části a bude obsahovat validační pravidla. Jako druhý parametr předáváme model Admin , který nebude spojen s žádnou databázovou tabulkou a bude nám sloužit především jako typová kontrola a zanoření dat do vlastní třídy. Samotný obsah metody mailToAdmin()  pak obsahuje oznámení adminovi, že mu přišel email a v případě, že vše proběhlo v pořádku, přesměrujeme uživatele zpět na formulář a zobrazíme mu zprávu a úspěšném odeslání emailu.

Form Request

Validaci požadavku můžeme provést rovnou v ContactControlleru, nicméně chtěl bych vám představit tzv. form request, který slouží především pro komplexnější validační případy, nicméně můžeme ho klidně využít i v našem projektu. Form request je třída, která obsahuje validační logiku. Tato třída se zavolá v případě, kdy si ji definujeme jako parametr v metodě controlleru. Přicházející form request je zvalidován ještě dříve, než se zavolá samotná metoda controlleru a tudíž nemusíme controller jakkoliv zatěžovat validační logikou.

Vytvoříme si přes artisan vlastní form request, který se nám bude starat o validační logiku kontaktního formuláře:

php artisan make:request ContactFormRequest

Příkaz vygeneruje třídu, kterou najdete v adresáři app/Http/Request . Pokud tento adresář ještě neexistuje, pak ho artisan automaticky vytvoří. Přidáme do této třídy naši validační logiku:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name'    => 'required|max: 255',
            'email'   => 'required|email|max: 255',
            'message' => 'required',
        ];
    }
}

Důležité je, aby metoda authorize()  vracela true, jinak vám to bude vypisovat chybu, že chybí autorizace a tu v našem případě nepotřebujeme řešit. Metoda rules()  je klíčová, protože právě tato metoda se zavolá při form requestu pro validaci. Celá validace se uvádí v poli a jako klíč uvádíme atribut, který nám přichází v HTTP požadavku. Hodnotou jsou pak samotné validační pravidla. Jméno uživatele je povinná položka, proto uvádíme required  a také maximální délka je 255 znaků. Email musí splňovat stejné pravidla, nicméně přidáváme zde i pravidlo email , který hodnotu zvaliduje, jestli obsahuje správný formát pro email. U samotné zprávy jen kontrolujeme, jestli byla vyplněna.

Pokud validace selže, je okamžitě vygenerována odpověď pro přesměrování uživatele zpět na předchozí stránku, v našem případě na kontaktní formulář. Chyba se také uloží do session, takže si ji můžeme později zobrazit i v šabloně.

Admin model

Jako druhý parametr metody mailToAdmin()  v třídě ContactController je model Admin. Opět přes artisan si ho vytvoříme:

php artisan make:model Admin

Jak jsem již dříve psal, tento model nebude propojen s databázovou tabulkou a bude nám sloužit hlavně jako zapouzdření dat administrátora blogu. Model bude vypadat takto:

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;

class Admin extends Model
{
    use Notifiable;

    protected $admin;
    protected $email;

    public function __construct()
    {
        $this->admin = config('admin.name');
        $this->email = config('admin.email');
    }
}

V konstruktoru si všimněte, že předávám parametry z metody config() . Tato metoda slouží jako helper abychom si mohli vytáhnout konfigurační data, která se nacházejí v adresáři config . V tomto adresáři si tedy vytvoříme nový soubor config/admin.php s vašimi emailovými údaji:

return [
    'email' => 'admin@blog.com',
    'name'  => 'John Doe',
];

Aby objekt Admin měl přístup k metodě notify() pro odesílání emailu administrátorovi, potřebujeme importovat trait Notifiable . Ten si rozšíříme a více vysvětlíme v další části.

Notification

Jako první si vytvoříme novou třídu, která bude dědit ze třídy Notification artisan příkazem:

php artisan make:notification InboxMessage

Opět pokud neexistoval adresář Notifications , pak artisan automaticky vytvořil nový adresář a vytvořil novou třídu app/Notifications/InboxMessage.php . Po vygenerování je připraveno několik metod, které můžeme využít, nicméně si třídu upravíme podle našich potřeb, výsledek bude vypadat takto:

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use App\Http\Requests\ContactFormRequest;

class InboxMessage extends Notification
{
    use Queueable;

    protected $message;

    /**
     * Create a new notification instance.
     *
     * @param \App\Http\Requests\ContactFormRequest $message
     */
    public function __construct(ContactFormRequest $message)
    {
        $this->message = $message;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject(config('admin.name') . ', máte novou zprávu!')
            ->greeting(" ")
            ->salutation(" ")
            ->from($this->message->email, $this->message->name)
            ->line($this->message->message);
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

V konstruktoru si předáme náš form request s validovanými hodnotami, které jsme si poslali z formuláře. Poté nás zajímá především metoda toMail() , ve které si nadefinujeme hodnoty emailu. Ve výchozím nastavení Laravel automaticky vkládá do zprávy i hlavičku (Hello!) a patičku (Regards, Laravel) a to my nepotřebujeme, proto přepíšeme metody greeting()  a salutation()  a vložíme tam prázdný string.

Šablona

V poslední části si vytvoříme náš formulář pro odesílání emailu. Vytvoříme si tedy šablonu resources/views/contact/contact.blade.php :

@extends('layouts.app')

@section('title', 'Kontakt')

@section('content')
    <div class="container">
        <h1 class="mb-2 text-center">Kontaktujte nás</h1>

        @if(!empty($errors->first()))
            <div class="row col-lg-12">
                <div class="alert alert-danger">
                    <span>{{ $errors->first() }}</span>
                </div>
            </div>
        @endif

        @if(session('message'))
            <div class='alert alert-success'>
                {{ session('message') }}
            </div>
        @endif

        <div class="col-12 col-md-6">
            <form class="form-horizontal" method="POST" action="/contact">
                {{ csrf_field() }}
                <div class="form-group">
                    <label for="Name">Jméno: </label>
                    <input type="text" class="form-control" id="name" 
                            placeholder="jméno" name="name" 
                            value="{{ old('name') }}"
                            required>
                </div>

                <div class="form-group">
                    <label for="email">Email: </label>
                    <input type="text" class="form-control" id="email" 
                            placeholder="email" name="email" 
                            value="{{ old('email') }}"
                            required>
                </div>

                <div class="form-group">
                    <label for="message">Zpráva: </label>
                    <textarea type="text" class="form-control luna-message" 
                            id="message" placeholder="vaše zpáva" 
                            name="message" 
                            required>{{ old('message') }}</textarea>
                </div>

                <div class="form-group">
                    <button type="submit" class="btn btn-primary" value="Send">Odeslat</button>
                </div>
            </form>
        </div>
    </div> <!-- /container -->
@endsection

Všimněte si pár věcí. Jako první se ptám, jestli neexistuje nějaký error v session přes proměnou $errors , kterou Laravel automaticky vkládá do šablony v případě špatné validace. Pokud ano, tak vykreslím hned první. Další část se naopak zobrazí, pokud vše proběhlo v pořádku a v session existuje klíč message, do které jsme si předali zprávu z controlleru.

Helper {{ csrf_field() }}  slouží k vygenerování skrytého inputu _token, který obsahuje CSRF token. Ten slouží k ochraně proti CSRF útoku a také ho defaultně vyžaduje middleware VerifyCsrfToken, takže pokud routa zahrnuje tento middleware (v našem případě ano), tak to bude vyžadovat a v opačném případě se vyhodí výjimka při odeslání formuláře.

V případě, že uživatel vyplní formulář špatně, tak ho vrátíme zpět na formulář a zobrazíme mu chybu. Problém je v tom, že uživatel ztratí vložené hodnoty a musel by znovu vyplňovat např. email a psát celou zprávu znovu. Abychom se tomu vyhnuli tak využijeme helper {{old('atribut_name')}} , který nám v případě vracení z validace automaticky opět doplní původní hodnotu.

Počeštění validace

Pokud by jste aktuálně vyzkoušeli validaci, dostanete v šabloně chybu v angličtině. Pokud bychom chtěli mít blog čistě česky, bylo by dobré také uživateli zobrazovat chybové hlášky validace v češtině. Laravel nám v tomto vychází vstříc a je to poměrně jednoduché nastavit. Jako první najděte klíč locale v souboru config/app.php a změňte jeho hodnotu na cz:

'locale' => 'cz',

Laravel využívá tzv. translatory pro zobrazení textu. Tyto translatory najdete v resources/lang/  kde jsou rozděleny (nebo by měly být) podle jazyků. Aktuálně tam je pouze adresář en, který slouží jako výchozí translator. Vytvoříme si tedy v adresáři lang nový adresář cz a v něm soubor validation.php, který se používá právě při validaci. Vložte do něj tento obsah:

return [
    'required' => 'Pole :attribute je povinné',

    'custom' => [
        'email' => [
            'required' => 'Emailová adresa musí být vyplněna',
            'email'    => 'Musí být uveden správný formát emailu',
        ],
    ],

    'attributes' => [
        'name'    => 'jméno',
        'message' => 'zpráva',
    ],
];

Soubor obsahuje tři druhy počeštění validace. Jako první typ je takový, že uvedeme název validačního pravidla (např. required, email atd.) a kdykoliv nebude toto pravidlo vyhovovat, tak vrátíme počeštěnou hlášku, kde :attribute je název položky v requestu.

Dalším typem je vlastní hláška, kdy pod klíčem custom si uvedeme název atributu z requestu a poté pro každé validační pravidlo si definujeme vlastní hlášku.

V drtivé většině pojmenováváme atributy v angličtině, tedy např. name, email, message. Pokud bychom použili první variantu počeštění, tak bychom získali hlášku "Pole name je povinné". Abychom tedy nemuseli přejmenovávat atribut na české názvy tak můžeme využít právě posledního typu a to že si nadefinujeme jak se mají překládat atributy. Pro atribut name bychom tak dostali hlášku "Pole jméno je povinné", což je pro uživatele mnohem příjemnější varianta.

Odesílání emailu

Pro správné odeslání emailu je potřeba definovat poštovní server a to v konfiguračním souboru .env , který najdeme v kořenu projektu. Stačí upravit sekci začínající prefixem MAIL_, např. pro gmail:

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=vas.email@gmail.com
MAIL_PASSWORD="vase.heslo"
MAIL_ENCRYPTION=tls

V případě, že vám nebude odesílání emailu fungovat, doporučuji hledat na google a na stackoverflow. Problémů při odesílání emailu je celá řada (především pak z localu) a proto nejsem schopen poradit nějaké univerzální řešení. V případě problémů SSL na localu (v mém případě), kdy se s tím moc nechcete štvát, doporučuji provést úpravu souboru config/mail.php  jak popisuje Junaid Masood na SO.

Závěr

V tomto díle jsem se pokusil popsat alespoň letmo controller a form request pro validaci dat. Dále jsme použili trait Notifiable a využili ho k posílání emailu. Tímto dílem bychom měli popsanou celou architekturu MVC a měli připravený základ blogu. Další díl bude o něco kratší, jelikož si zobrazíme detail příspěvku. Připravíme si tak routy a pomocí Eloquentu si zobrazíme kategorii pod kterou spadá, kdo ho napsal a také si naformátujeme datum kdy bylo vytvořené.

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: