Controllers

Namísto definování veškeré logiky v route souborech můžete tuto logiku přesměrovat do samostatných Controller tříd. Controllery mohou seskupovat požadované requesty do jediné třídy. Controllery jsou uloženy v adresáři app/Http/Controllers.

Definování controllerů

Controller je třída jako každá jiná. Ve většině případů využijete rozšíření od rodičovského controlleru, který obsahuje samotný Laravel framework. Tento rodič poskytuje některé velmi užitečné metody jako jsou middleware, validate nebo dispatch. Toto rozšíření samozřejmě nemusíte používat, pokud k tomu máte nějaký důvod. Controller pak vypadá takto:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return View
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

Routu s tímto controllerem pak můžeme definovat takto:

Route::get('user/{id}', 'UserController@show');

Jakmile se tedy HTTP požadavek shoduje se specifikovanou URI adresou, zavolá se metoda show ve třídě UserController. Parametry se samozřejmě také přesunou.

Namespace

Je důležité si všimnout, že jsme v routě nedefinovali žádný namespace. To proto, že RouteServiceProvider automaticky načítá všechny route soubory a přidává k nim specifikovaný namespace, kdy výchozí namespace je App\Http\Controllers. Název controlleru pak tedy zadáváme až po tomto namespace.

Pokud budete potřebovat vytvořit hlubší strukturu v adresáři App\Http\Controllers, pak tento namespace berte jako kořenový a pokračujte v jeho pojmenování. Například pokud bychom měli třídu AdminController v adresáři app/Http/Controllers/Photos, pak namespace ve třídě bude App\Http\Controllers\Photos\AdminController a controller v routě definujeme takhle:

Route::get('foo', 'Photos\AdminController@method');

Controller Middleware

Middleware můžeme ke controlleru přiřadit v routě:

Route::get('profile', 'UserController@show')->middleware('auth');

Nicméně je mnohem pohodlnější middleware specifikovat přímo ve třídě, nejlépe hned v konstruktoru. Díky tomu můžeme hned na začátku určit, jaký middleware se má použít a také můžeme definovat přímo metodu, pro kterou se má či nemá použít:

class UserController extends Controller
{
    /**
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

Resource Controllers

V controllerech nejčastěji řešíme CRUD operace (create, read, update, delete). Klasicky bychom museli pro každou operaci zaregistrovat novou routu a poté ve controlleru vytvořit pro každou opeaci samostatnou metodu, která by požadavek zpracovávala. Sice banalita, avšak psát to pokaždé je celkem neefektivní. Navíc seznam rout se nám rapidně zvýší.

Laravel nám umožňuje velmi jednoduše definovat controller, který tyto operace bude zpracovávat a routa bude mít pouze jeden řádek. Mějme příklad, kdy potřebujeme vytvořit controller, který bude zpracovávat všechny HTTP požadavky pro fotky. S použitím Artisan příkazu make:controller můžeme velmi rychle takovýto controller vytvořit:

php artisan make:controller PhotoController --resource

Tento příkaz nám vygeneruje nový controller app/Http/Controllers/PhotoController.php. A právě možnost --resource vygeneruje ve třídě všechny metody, které potřebujeme pro zpracování CRUD operací.

A teď k routě. Abychom nemuseli psát pro každou operaci samostatnou routu, využijeme metodu resource, která automaticky registruje HTTP požadavky typu GET, POST, PUT/PATCH a DELETE:

Route::resource('photos', 'PhotoController');

Naráz můžeme definovat i více controllerů, pro které chceme využít metody resource:

Route::resources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);

Když bychom se podívali do třídy PhotoController, kterou jsme si vytvořili, našli bychom sedm různých metod. Každá metoda odpovídá samostatně na různou routu. Zde je seznam na základě jaké routy se volá jaká metoda v controlleru:

Typ URI Akce Název routy
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

Specifikování resource modelu

Pokud používáte route model binding a chcete, aby se vygenerovali v controlleru metody s type-hintem instance modelu, můžeme v Artisan příkazu při generování nového controlleru použít možnost --model:

php artisan make:controller PhotoController --resource --model=Photo

Odesílání formuláře

HTML formuláře nedokážou odesílat požadavky typu PUT, PATCH nebo DELETE a proto je potřeba přidat skryté pole, podle kterého Laravel rozpozná, o jaký typ se jedná. Blade direktiva @method tento input za vás rovnou vygeneruje:

<form action="/foo/bar" method="POST">
    @method('PUT')
</form>

Částečný resource

Když definujeme resource routu, můžeme specifikovat pouze akce, na které má controller reagovat:

Route::resource('photos', 'PhotoController')->only([
    'index', 'show'
]);

Route::resource('photos', 'PhotoController')->except([
    'create', 'store', 'update', 'destroy'
]);

API resource

Pokud deklarujeme routy, které mají zpracováva API požadavky, většinou chceme vyloučit routy, které obsahují HTML šablony, například create a edit. Laravel umožňuje takto rovnou specifikovat routy bez těchto dvou pomocí metody apiResource:

Route::apiResource('photos', 'PhotoController');

Stejně jako u resource i zde můžeme naráz definovat více controllerů:

Route::apiResources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);

Pro rychle vygenerování controlleru bez metod create a edit můžeme v Artisan příkazu použít možnost --api:

php artisan make:controller API/PhotoController --api

Vlastní pojmenování resource rout

Pokud použijeme v routě resource, pak všechny akce mají automaticky i vlastní název, jak můžeme vidět v předešlé tabulce. Pokud bychom tyto názvy chtěli přepsat, pak můžeme použít metodu names:

Route::resource('photos', 'PhotoController')->names([
    'create' => 'photos.build'
]);

Vlastní pojmenování resource parametrů

Ve výchozím nastavení Route::resource vytvoří parametry rout z názvu resource v jednotném čísle:

// web.php
Route::resource('users', 'AdminUserController');

// AdminUserController
public function show(User $user) {}

Pokud bychom chtěli názvy těchto parametrů změnit, pak můžeme využít metody parameters a názvy přepsat:

Route::resource('users', 'AdminUserController')->parameters([
    'users' => 'admin_user'
]);

To nám vygeneruje následující URI pro metodu show:

/users/{admin_user}

Doplnění resource

Pokud potřebujete přidat routy do resource, které nejsou v ní definované, pak je potřeba definovat tyto routy ještě dříve, než zavoláte Route::resource. Jinak se může stát, že routy definované v resource získají nechtěně přednost před vlastními routami:

Route::get('photos/popular', 'PhotoController@method');

Route::resource('photos', 'PhotoController');

Dependency Injection

Constructor Injection

Pro zavolání všech Laravel controllerů framework využívá service containeru. Výsledkem je to, že díky type-hintu můžete definovat jakékoliv závislosti, které váš controller může potřebovat v jeho konstruktoru. Deklarované závislosti se automaticky načtou a injektnou do instance controlleru:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

Method Injection

Kromě constructor injection můžeme také využít type-hintu závislostí v metodách controlleru. Nejběžnější případem method injection je vkládání instance Illuminate\Http\Request do metody controlleru:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Store a new user.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->name;

        //
    }
}

Pokud vaše metoda controlleru také očekává parametr z routy, vkládejte tyto parametry až po ostatních specifikovaných závislostech. Například pokud je vaše routa definována takto:

Route::put('user/{id}', 'UserController@update');

Pak stále můžete využít type-hintu Illuminate\Http\Request a mít zároveň přístup k parametru id následným definováním metody:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Update the given user.
     *
     * @param  Request  $request
     * @param  string  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        //
    }
}

Kešování rout

Hned na úvod je potřeba upozornit, že routy obsahující closure nemohou být zakešované. Abyste mohli routy zakešovat, musíte všechny closure převést na třídy controllerů.

Použití kešování rout můžete drasticky snížit zatížení aplikace, kdy jinak musíte prvně zaregistrovat všechny routy aplikace a až poté vykonat požadavek. V některých případech může tento proces být až 100x rychlejší. Pro zakešování rout stačí zavolat tento příkaz:

php artisan route:cache

Poté co se vykoná tento příkaz, zakešované routy se budou načítat při každém requestu. Nezapomeňte ale, že pokud přidáte novou routu nebo nějakou upravíte, musíte znovu přegenerovat zakešované routy, jinak se změna nezaregistruje.

Pro vymazání kešky rout můžete využít příkaz route:clear:

php artisan route:clear