Komplex példa 5. rész - A 7 RESTful útvonal és vezérlő metódus

Attila | 2022. 03. 31. 17:04 | Olvasási idő: 4 perc

Címkék: #Adatbázis (Database) #Adatbetöltő (Seeder) #Adatgyár (Factory) #Controller #CRUD #Eloquent #Laravel #Laravel 9 #Routing #Tinker

Áttekintjük a CRUD (Create-Read-Update-Delete) műveletek végrehajtásához szükséges 7 útvonalat és a hozzájuk kapcsolódó vezérlő metódusokat.
7_restful_routes_methods

Először tisztázzuk a REST jelentését, de kétlem, hogy a Wikipedia-nál bővebb és jobb magyarázatot tudnék adni, úgyhogy inkább ide belinkelem a releváns szócikket: https://hu.wikipedia.org/wiki/REST Annyit azért összefoglalóan mindenképpen el szeretnék mondani, hogy ez nem egy protokoll, viszont szorosan kapcsolódik a HTTP protokollhoz, amely ugye a webes szereplőket és kommunikáció részleteit határozza meg. Ha egy ilyen alkalmazás és architektúrája (mint például a Laravel is) megfelel a következő megszorításoknak, akkor szokták RESTful-nak nevezni: (1) kliens-szerver architektúra, (2) állapotmentesség, (3) gyorsítótárazhatóság, (4) réteges felépítés, (5. opcionális) igényelt kód, (6) egységes felület (interface). Aki ennél többet szeretne tudni róla, tényleg olvassa el a wikis szócikket.

A Laravel a RESTful működést az útvonalain, vezérlő metódusain keresztül valósítja meg. De van, amikor egy kép többet mond ezer szónál. Ezért készítettem ezt az összefoglaló táblázatot (és róla a képet), ami a bejegyzésben található.

A leggyakoribb műveletek (CRUD)  megvalósításához ezekre a HTTP metódusokra és útvonalakra van szükség. Ezek közül az első kettőt már ismerjük. GET HTTP metódussal képesek voltunk lekérni a '/flights' és a '/flights/{id}' útvonalakat. Mindkét útvonal elérésekor a böngészőben valamilyen erőforrást kértünk le, nevesen a flights adatbázistábla tartalmát az index metódusban teljes egészében, míg a show metódusban csak egy darab sort belőle. A 7-ből tehát 2-t már ismerünk, használtunk is őket, tudjuk, hogy hogyan működnek, ezeket tehát kipipálhatjuk.

A táblázat sorainak színezése nem véletlen! Sárgával (citrom és narancs) jelöltem az adatbázis erőforrások lekérését.

A következő két sorban az adattáblába új sort tudunk létrehozni, vagy ha az MVC tervezési minta szerint szeretném megfogalmazni, akkor a példaként használt Flight Model osztálynak egy új példányát hozom létre. Kezdjük a create-tel, mivel ez egy GET-es útvonal lekérés lesz, aminek hatására a felhasználó böngészőjének átadunk egy olyan üres (nem kitöltött) űrlapot, amelynek segítségével felparaméterezheti az elküldés gomb megnyomásával létrejövő Flight Model osztály új példányát. Ez tehát egy üres űrlapot ad vissza a felhasználó böngészőjének. A store pedig ennek a párja, hiszen amikor kitöltjük az űrlapot és elküldjük ("postázzuk" - POST) a szervernek a bemeneti információkat, akkor ő a store metódus magjában mentjük ki az adatbázisba.

A táblázat sorainál, mivel ezek (create és store) összefüggőek, bekereteztem őket, továbbá világos és sötét zöld háttérszínnel láttam el őket.

A következő két sorban az adattáblában már meglévő sort tudunk frissíteni, vagy ha az MVC tervezési minta szerint szeretném megfogalmazni, akkor a példaként használt Flight Model osztály már meglévő példányának adattagjait tudom szerkeszteni és frissíteni. Kezdjük az edit-tel, hiszen ez vonatkozik a szerkesztésre. A GET-es útvonal meglátogatásával egy űrlapot kap vissza a felhasználó, de már nem üreset, mivel ez az adatsor a flights adattáblában már létezik és most az űrlap mezői ennek a sornak az értékeit kapják meg. Utána ezt a felhasználó átírhatja a mezők értékét, majd amikor végzett a szerkesztéssel akkor a küldés (vagy frissítés) gomb megnyomásával hívódik meg az update vezérlő metódus a PUT-os útvonalon keresztül (lehetőségünk van a PUT helyett PATCH-et is használni, a kettő között nincsen különbség, ugyanúgy működnek). Ebben a metódusban már nem egy új sort szúrok be a flights adattáblába, hanem ugye a meglévő sor mezőit frissítem az érkező esetlegesen új értékekkel.

A tábla sorainál, mivel ezek (edit és update) is összefüggőek, bekereteztem őket, továbbá világos és sötét kék háttérszínnel láttam el őket.

A táblázat legutolsó sorában látszódik a destroy metódus, amelyet a DELETE-es útvonalon keresztül tudunk elérni. Ez annyit csinálni, hogy a paraméterként megkapott adatbázis sort törölni fogja a flights adattáblában.

Fontos még megjegyezni, hogy a HTML űrlap elküldését csak GET vagy POST method attribútum értékkel tudjuk elküldeni, a szabvány ezt engedi meg nekünk, de mivel nekünk szükségünk van PUT/PATCH és esetleg DELETE metódusok szerinti küldésre is, ezért utóbbiak esetén egy kis trükköt alkalmazunk: POST-ként küldjük el az űrlapot, majd az űrlapon belül állítjuk be a PUT, PATCH, DELETE értékeket.


Bővítsük a komplex példánkat csomagokkal!

De nem programcsomagokkal (packages), hanem az utasokhoz rendeljünk csomagokat, poggyászokat (luggage). A példa megvalósítása során létre fogjuk hozni a 7 útvonalat és a Controller osztályban pedig a 7 útvonalhoz tartozó 7 metódust. De kezdjük a Luggage Model osztály létrehozásával (adjunk hozzá mindent IS, a migrációs fájlt, a gyárat és a Controller-t is):

php artisan make:model Luggage -mfcr

Ezek közül a kapcsolók közül (mfcr) már ismerjük az m-et (migrációs fájl), f-et (factory), c-t (Controller), az r pedig azt adja hozzá, hogy a Controller-ben létre fog jönni majd ez a 7 RESTful metódus is, illetve a magjuk nem (természetesen), de a metódusok váza igen.

A migrációs fájlban a csomagoknál (luggage - angolban rendhagyó módon a luggage többesszáma is luggage, erre figyeljünk) plusz mezőben mindössze egy csomagszámot és egy utasazonosítót (külső kulcs, 1-több-es kapcsolat) tároljunk el:

$table->string('number');
$table->foreignId('passenger_id')->constrained()->onUpdate('cascade')->onDelete('cascade');

Mentés után migráljunk: php artisan migrate

Ha pedig kapcsolatokról beszélünk, akkor hozzuk is létre az ennek megfelelő Eloquent kapcsolatot biztosító metódusokat. Kezdjük a Luggage osztállyal:

public function passenger()
{
  return $this->belongsTo(Passenger::class);
}

Majd folytassuk a Passenger osztály bővítésével:

public function luggage()
{
  return $this->hasMany(Luggage::class);
}

Kérdés: ha tudni szeretnénk, hogy az adott repülőjáraton melyik csomagokat szállítják, akkor szükség volna-e arra, hogy a repülőjárathoz (Flight) is hozzárendeljük a csomagot? Válasz: nincs. Ha valaki nem tudja, hogy miért nincs erre szükség, akkor a bejegyzés végén elmagyarázom.

Ezekután a LuggageFactor defintion metódusának visszatérési értéke így nézhet ki, használva a faker-t:

return [
  'number' => $this->faker->lexify('???') . "-" . $this->faker->numerify('###'), // példa: 'ABC-123'
  'passenger_id' => Passenger::inRandomOrder()->first()->id,
];

(Utána ne felejtsük el importálni a Passenger osztályt a fájl tetején.) Tinker-ben le is tesztelhetjük a működését: php artisan tinker

Luggage::factory()->make()

Működik, úgyhogy vegyük is fel a gyárat egy annak megfelelő Seeder osztályba:

php artisan make:seeder LuggageSeeder

Az új osztályunk run() metódusába pedig állítsuk be, hogy a gyár mondjuk 50 darab poggyászt hozzon létre "termeléskor".

Luggage::factory()->count(50)->create();

A database/seeders/DatabaseSeeder osztályban pedig a már meglévő Flight és Passenger "gyártás" után illesszük be a poggyászok gyártását is:

$this->call([
  FlightSeeder::class,
  PassengerSeeder::class,
  LuggageSeeder::class
]);

Aki nem emlékezne a Seeder osztályok működtetésére, az itt tájékozódhat erről. Indítsunk is be egy gyártást, hogy legyen jó sok tesztadatunk: php artisan db:seed

Ezután következhet a web.php-ban a 7 új útvonal felvétele (azért, hogy működjön, importáljuk be a fájl tetején a LuggageController osztályt):

Route::get('/luggage', [LuggageController::class, 'index']);
Route::get('/luggage/{luggage}', [LuggageController::class, 'show']);

Route::get('/luggage/create', [LuggageController::class, 'create']);
Route::post('/luggage', [LuggageController::class, 'store']);

Route::get('/luggage/{luggage}/edit', [LuggageController::class, 'edit']);
Route::put('/luggage/{luggage}', [LuggageController::class, 'update']);

Route::delete( '/luggage/{luggage}', [LuggageController::class, 'destroy']);

Kiegészítés: az "id" helyett a "luggage" (Model osztály neve kis kezdőbetűvel, ugye a webcímnél nem lenne értelme nagy kezdőbetűvel írni) van. Ennek meg fogjuk nézni, hogy milyen gyakorlati haszna lesz.

Megjegyzés még az iménti kódsorokhoz: nem kell, de én próbáltam ugyanúgy elszeparálni az összetartozó párokat egy plusz sortöréssel, ahogy a fenti táblázatban csináltam a keretezés és színek szerinti elválasztást.

A terminal segítségével le is kérdezhetjük a már meglévő útvonalainkat: php artisan route:list

Az eredmény itt látható (keressük meg és koncentráljunk most a luggage-hez kapcsolódó új útvonalainkra):

Következhet a Controller szerkesztése. Ha megnézzük a LuggageController-t, akkor láthatjuk, hogy a rendszer már automatikusan beleteszi a 7 RESTful útvonalhoz tartozó 7 Controller action-t, vagyis vezérlő metódust is, ez nekünk így nagyon kényelmes. És itt utalnék vissza az útvonalakra, mármint azokra, ahol {luggage} szerepelt wildcard paraméterként az {id} helyett... ez még könnyebbé teszi a dolgunkat, mert nem kell majd kikeresnünk az adatbázistábla megfelelő sorát (find($id) vagy findOrFail($id) metódussal), hanem egyből a Luggage Model osztály példánya fog a rendelkezésünkre állni a metódus magjában. Nézzük is meg ezt rögtön a gyakorlatban: a LuggageController show metódusának magja legyen ez:

dd($luggage);

Az oldal kiszolgálásának elindítása után pedig nézzük meg ezt a webcímet: http://127.0.0.1:8000/luggage/1

Meg fogjuk kapni az egyes számú poggyász példányát.

Lenyitva az #attributes melletti kis háromszöget, már láthatók is az adatsor mezői és azok értékei, tehát működik az az elgondolás, hogy most már nem kell plusz egy sort írnunk, amivel az $id segítségével rákeresünk az adatsorra, hanem kapásból működik ez a megoldás. Maradjunk ennél a show metódusnál a LuggageController-ben és töröljük ki a dd() metódushívásos sort, helyette inkább térjünk vissza egy nézettel, ami megjeleníti a szép weboldalunkon a poggyász számát és a hozzá tartozó utas nevét.

public function show(Luggage $luggage)
{
  // dd($luggage);
  return view('luggage.show', [
    'number' => $luggage->number,
    'passenger' => $luggage->passenger->name,
  ]);
}

Az index metódust is készítsük el:

return view('luggage.index', [
  'luggage' => Luggage::get(),
]);

Ezek működnek. Aki nem hiszi, tesztelje le őket a tinker segítségével. Viszont még nincsenek meg a hozzájuk tartozó nézet fájljaink a resources/views/luggage mappában. Hozzuk előbb létre a show.blade.php -t oda.

Majd következhet ugyanoda az index.blade.php

Már csak egy dolog van hátra: a layout.blade.php -ban bővítsük ki a menüt és tegyük bele a poggyászokat is a menüpontok közé. Ennek megoldását (1 sor) most már mindenkire rábízom. A weboldal eredmény itt látható:

Illetve egy poggyász részletezése:

A következő blogbejegyzésben innen fogjuk folytatni és megnézzük a létrehozó, szerkesztő, törlő útvonalakat és metódusokat. Továbbá ott fogunk majd kódszervezést is csinálni az útvonalainknál, hogy még kényelmesebben létrehozhatók legyenek majd ez a nevezetes 7 útvonal minden erőforráshoz, elöljáróban annyit, hogy mindössze egyetlen sorral helyettesíthető lesz majd a web.php-ban lévő 7 útvonal.


A bejegyzésben feltett kérdésre azért az volt a válasz, hogy nincs szükség a csomagoknak a repülőjárathoz rendelésére, mivel az utasok amúgy is már hozzá vannak rendelve a repülőjáratokhoz egy kapcsolaton keresztül. Így a csomag-repülőjárat kapcsolat létrehozása nélkül is könnyedén lekérdezhető (az utasokon keresztül), hogy milyen csomagok is tartoznak a repülőjárathoz, és fordítva.


A bejegyzéshez tartozó módosítások Github commit-je itt található.