Blok 2 · rozdział 4

Kontrolery

Funkcja wpisana od razu w routes/web.php wystarczy na jedną stronę. Przy karcie szczytu i formularzu kod przenosisz do klasy kontrolera. W pliku tras zostaje tylko wskazanie metody.

Po tym rozdziale powinieneś umieć

  • utworzyć kontroler poleceniem Artisan;
  • przenieść listę do metody index, a kartę do show;
  • zwrócić 404, gdy slugu nie ma w tablicy;
  • zostawić w pliku tras same wskazania na kontroler i powiedzieć, czemu adres z {slug} nie może stać przed stałym adresem.

1. Wygeneruj kontroler

W rozdziale 3 lista i strona główna siedzą w funkcjach w routes/web.php. To wystarcza, dopóki akcja ma kilka linii. Karta szczytu dokłada pętlę i decyzję „jest czy 404”. Formularz z rozdziału 6 dokłada walidację. Taki plik tras przestaje być spisem adresów i staje się drugim programem.

W katalogu projektu, w drugim terminalu (pierwsze okno nadal trzyma php artisan serve):

php artisan make:controller SzczytController

Powstaje app/Http/Controllers/SzczytController.php. Otwórz go. Zostaw nagłówek, przestrzeń nazw i deklarację klasy. Metody dopiszesz w środku nawiasów klamrowych klasy. Gdy Artisan wstawił use Illuminate\Http\Request;, nie kasuj tej linii — w rozdziale 6 przyda się do formularza.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SzczytController extends Controller
{
    // tu będą index() i show()
}

Na górze routes/web.php, obok istniejącego use dla Route, dopisz:

use App\Http\Controllers\SzczytController;
Kontroler Klasa, której metody obsługują konkretne akcje: pokaż listę, pokaż jeden rekord, przyjmij formularz. Metoda dostaje dane z adresu albo z formularza, coś z nimi robi i oddaje widok albo przekierowanie. HTML zostaje w pliku Blade.
Jedna klasa na rzecz w adresie SzczytController trzyma akcje szczytu: lista, karta, a w rozdziale 6 jeszcze zapis planu. Strona główna na razie zostaje przy funkcji w pliku tras. Nie ma w niej decyzji, tylko return view('start').

2. Lista

Tablicę czterech szczytów z rozdziału 3 przenieś z pliku tras do kontrolera. Do każdego wiersza dopisz klucz slug. To kawałek adresu karty: małe litery, bez polskich znaków, spacja zamieniona na myślnik. Babia Góra ma slug babia-gora, Śnieżka — sniezka.

W klasie, zamiast komentarza, wklej dwie metody. index jest publiczna, bo woła ją trasa. listaSzczytow jest prywatna, bo woła ją tylko ta klasa.

public function index()
{
    $szczyty = $this->listaSzczytow();

    return view('szczyty.index', [
        'szczyty' => $szczyty,
    ]);
}

private function listaSzczytow(): array
{
    return [
        ['slug' => 'rysy', 'nazwa' => 'Rysy', 'pasmo' => 'Tatry', 'wysokosc' => 2499],
        ['slug' => 'babia-gora', 'nazwa' => 'Babia Góra', 'pasmo' => 'Beskid Żywiecki', 'wysokosc' => 1725],
        ['slug' => 'sniezka', 'nazwa' => 'Śnieżka', 'pasmo' => 'Karkonosze', 'wysokosc' => 1603],
        ['slug' => 'lysica', 'nazwa' => 'Łysica', 'pasmo' => 'Góry Świętokrzyskie', 'wysokosc' => 614],
    ];
}

Metoda index robi trzy rzeczy:

  1. $this->listaSzczytow() bierze tablicę z metody prywatnej. $this to bieżący obiekt kontrolera.
  2. return view(...) wybiera plik resources/views/szczyty/index.blade.php. Kropka w nazwie widoku to katalog.
  3. Tablica drugiego argumentu nadaje widokowi zmienną. Klucz 'szczyty' staje się w Blade nazwą $szczyty.

Ten sam efekt daje skrót compact('szczyty'). PHP buduje wtedy tablicę ['szczyty' => $szczyty] ze zmiennej o tej nazwie. W projekcie zostaw jawną tablicę, bo od razu widać, co trafia do widoku.

W routes/web.php podmień funkcję przy /szczyty na wskazanie metody. Trasę / zostaw bez zmian.

Route::get('/szczyty', [SzczytController::class, 'index'])->name('szczyty.index');

Zapis [SzczytController::class, 'index'] znaczy: klasa i nazwa metody. Laravel tworzy obiekt kontrolera i wywołuje index. Starej funkcji z tablicą szczytów w pliku tras już nie ma.

W widoku listy, przy nazwie szczytu, link ma prowadzić do karty. Podmień sam nagłówek wewnątrz pętli:

<a href="{{ route('szczyty.show', $szczyt['slug']) }}">
    {{ $szczyt['nazwa'] }}
</a>

Drugi argument route() wchodzi w miejsce parametru {slug}. Dla Rysów powstaje adres /szczyty/rysy. Trasa szczyty.show powstaje w następnym podrozdziale — do tego momentu klik w link pokaże błąd o braku trasy.

Metoda listaSzczytow() jest osobno, bo w rozdziale 9 ją usuniesz, a w index zostanie zapytanie do modelu. Widok listy tego nie zauważy, jeśli nadal dostanie zmienną $szczyty.

Sprawdzenie listy Odśwież http://127.0.0.1:8000/szczyty. Nadal widać cztery szczyty. Gdy Laravel pisze, że nie zna klasy, sprawdź use na górze pliku tras i czy nazwa pliku to SzczytController.php.

3. Karta i 404

public function show(string $slug)
{
    $znaleziony = null;

    foreach ($this->listaSzczytow() as $szczyt) {
        if ($szczyt['slug'] === $slug) {
            $znaleziony = $szczyt;
            break;
        }
    }

    if ($znaleziony === null) {
        abort(404);
    }

    return view('szczyty.show', [
        'szczyt' => $znaleziony,
    ]);
}

Ta metoda dostaje z adresu jeden napis. Dla /szczyty/rysy w $slug jest rysy.

  1. Szuka wiersza, którego slug jest równy temu napisowi. break kończy pętlę przy pierwszym trafieniu — dalej nie ma czego szukać.
  2. Gdy po pętli $znaleziony nadal jest null, woła abort(404). Laravel przerywa metodę i oddaje swoją stronę „nie znaleziono”. Pusta karta z pustymi polami nie powstaje.
  3. Gdy wiersz jest, oddaje widok szczyty.show i jedną zmienną $szczyt. W tym widoku nie ma tablicy, tylko jeden wiersz.

Utwórz resources/views/szczyty/show.blade.php:

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="utf-8">
    <title>{{ $szczyt['nazwa'] }}</title>
</head>
<body>
    <p><a href="{{ route('szczyty.index') }}">Wróć do listy</a></p>
    <h1>{{ $szczyt['nazwa'] }}</h1>
    <p>{{ $szczyt['pasmo'] }} · {{ $szczyt['wysokosc'] }} m n.p.m.</p>
</body>
</html>

Własnego <html> pozbędziesz się w rozdziale 5, gdy karta wejdzie we wspólny układ. Dziś strona ma działać sama.

Trasę z parametrem dopisz pod trasą listy:

Route::get('/szczyty/{slug}', [SzczytController::class, 'show'])->name('szczyty.show');

Wejdź na /szczyty/rysy, potem na /szczyty/babia-gora i na /szczyty/nie-ma-takiego. Dwa pierwsze pokazują kartę. Trzeci pokazuje stronę 404 Laravela.

Parametr w adresie Klamra {slug} w trasie i argument string $slug w metodzie to ten sam fragment URL. Nazwy mają się zgadzać. Gdy w trasie jest {slug}, a w metodzie $id, Laravel nie złoży tego w jeden byt. Od rozdziału 9 zamiast ręcznej pętli zrobi to wiązanie modelu.
Kolejność tras Laravel bierze pierwszy wpis, który pasuje do adresu. Stały adres trzymaj przed wzorcem z klamrą, gdy oba mogłyby złapać ten sam URL. Wpis Route::get('/{slug}', ...) postawiony nad /szczyty połknąłby słowo szczyty jako slug i lista przestałaby się otwierać. Para /szczyty i /szczyty/{slug} jest bezpieczna, bo mają wspólny początek, a dokładniejszy adres listy stoi wyżej.

4. Cienkie trasy

Po przeniesieniu w web.php zostają krótkie wpisy: metoda HTTP, adres, klasa, akcja, nazwa. Pętla, abort i tablica są w kontrolerze. Plik tras czyta się wtedy jak spis adresów.

Docelowy routes/web.php wygląda tak:

<?php

use App\Http\Controllers\SzczytController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('start');
})->name('start');

Route::get('/szczyty', [SzczytController::class, 'index'])->name('szczyty.index');

Route::get('/szczyty/{slug}', [SzczytController::class, 'show'])->name('szczyty.show');
Sprawdzenie W drugim terminalu: php artisan route:list --name=szczyty. Mają być dwie pozycje, index i show, obie wskazujące SzczytController. W kolumnie akcji nie ma już funkcji anonimowej przy tych adresach.

5. Pliki na koniec rozdziału

Jeśli po drodze coś przestało działać, porównaj kontroler z tym plikiem. Lista pod /szczyty i karta pod /szczyty/rysy mają działać, a /szczyty/nie-ma-takiego ma kończyć się stroną 404.

app/Http/Controllers/SzczytController.php:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SzczytController extends Controller
{
    public function index()
    {
        $szczyty = $this->listaSzczytow();

        return view('szczyty.index', [
            'szczyty' => $szczyty,
        ]);
    }

    public function show(string $slug)
    {
        $znaleziony = null;

        foreach ($this->listaSzczytow() as $szczyt) {
            if ($szczyt['slug'] === $slug) {
                $znaleziony = $szczyt;
                break;
            }
        }

        if ($znaleziony === null) {
            abort(404);
        }

        return view('szczyty.show', [
            'szczyt' => $znaleziony,
        ]);
    }

    private function listaSzczytow(): array
    {
        return [
            ['slug' => 'rysy', 'nazwa' => 'Rysy', 'pasmo' => 'Tatry', 'wysokosc' => 2499],
            ['slug' => 'babia-gora', 'nazwa' => 'Babia Góra', 'pasmo' => 'Beskid Żywiecki', 'wysokosc' => 1725],
            ['slug' => 'sniezka', 'nazwa' => 'Śnieżka', 'pasmo' => 'Karkonosze', 'wysokosc' => 1603],
            ['slug' => 'lysica', 'nazwa' => 'Łysica', 'pasmo' => 'Góry Świętokrzyskie', 'wysokosc' => 614],
        ];
    }
}
Commit

Gdy lista i karta idą przez kontroler, w katalogu korona:

git add .
git status
git commit -m "Kontroler listy i karty"

Przed commitem git status nie pokazuje .env.