Címkék: #Controller #CRUD #Laravel #Laravel 9 #Routing #Tinker #Űrlap (Form) #Üzleti logika (Business logic)
Elnevezett útvonalak
Az útvonalakat a felhasználó képes elérni a böngészőjében vagy úgy, hogy ő maga gépeli be őket, vagy valamilyen linken keresztül jut el hozzájuk, esetleg - ahogy nemrég tanultuk - egy űrlap elküldésével éri el az adott útvonalat (van még persze több más mód is, de ennyi példa egyelőre talán elég nekünk).
Az alkalmazásunk jelenlegi állapotában ezek az útvonalak még eléggé statikusak, beégetettek: tehát ha mondjuk meggondoljuk magunkat a korábbi elképzelésünkhöz képest, és más útvonalon szeretnénk elérni valamilyen erőforrást, akkor azt minden olyan helyen meg kell változtatni, ahol hivatkoztunk rá: például a weboldalunk menüstruktúrájában, az űrlapunk (form) action attribútumában, az oldalon elhelyezett egyéb linkeken stb. Ez, azontúl, hogy rettentő kényelmetlen (mármint mindenhol egyesével megváltoztatni), meglehetősen nagy hibalehetőséget is magával vonz, amit pedig ugye mi nem szeretnénk. Hiszen bárhol megfeledkezhetünk róla, hogy ott, azon a helyen is meg kellett volna változtatni a beégetett linket és emiatt a weboldalunk hibás működést eredményezhetne...
Emiatt találták ki az "elnevezett útvonalakat", amikor gyakorlatilag egy "alias" nevet adunk
az útvonalunknak és erre az alias névre hivatkozunk minden olyan helyen
ahol használni szeretnénk... ha pedig meg akarjuk változtatni az
útvonalat, akkor elég ezt megtenni egyetlen egy helyen, ott ahol azt regisztráltuk, például a routes/web.php fájlban. Az így elnevezett útvonalak neveinek mindenképpen egyedinek kell lennie, tehát például nem regisztrálhatunk két "homepage" nevű útvonalat.
Nézzünk meg ehhez egy nagyon egyszerű példát: adjunk nevet a főoldalunknak, utána pedig vizsgáljuk meg azt, hogy hogyan kell hivatkozni rá.
Route::get('/', function () {
return view('welcome');
})->name('homepage');
Itt azt a nevet adtuk neki, hogy homepage, így tudunk majd hivatkozni rá bárhol, ahol szeretnénk. A hivatkozáshoz a route() segédmetódust tudjuk használni. Jelenleg én a főoldalamra egy helyen hivatkozok, mégpedig a menümben (layout nézet). Úgyhogy átírom ott ezt az útvonalat az a tag href attribútumában a / jel helyett használjuk ezt az idézőjelek között:
Ez egy nagyon egyszerűen "használható" útvonal volt, igazából még bonyolítottunk is picit az életünkön, hogy a / jel helyett ilyen hosszan hivatkoztunk a főoldalra... de nézzük meg inkább az elmúlt bejegyzések során megismert 7 RESTful útvonalat és velük együtt a Controller metódusaikat.
RESTful útvonalak és metódusok csoportosítása
Első pillantásra is már észrevehetjük, hogy melyik két csoportba sorolhatjuk az útvonalainkat:
Ezeket használtuk már korábban is, most nézzük meg, hogy hogyan alakítható át a regisztrálásuk és a használatuk a gyakorlatban. Kezdjük az egyszerűbbekkel, a paraméter nélküliekkel. A gyakorlás során az AirlinesController-hez kapcsolódó útvonalakat és metódusokat fogom használni és átalakítani.
Itt is van egy névkonvenció, amit be kell tartanunk: hasonlóan, mint a nézetek megadásánál, ezekre az útvonal nevekre is hivatkozzunk úgy, hogy melyik erőforrás melyik metódusához tartozik, így bármikor rápillantunk, tudjuk, hogy hol kell keresni, mi a célja az útvonal elérésének.
1. Paraméter nélküli útvonalak
A paraméter nélküli útvonalak esetén tehát a következő névkonvenciókat tartsuk be:
Generáljunk az útvonalakból URL-eket. Hol használjuk ezeket?
return redirect(route('airlines.index'));
2. Paraméterrel rendelkező útvonalak
A paraméterrel rendelkező útvonalaknál valamilyen felhasználói bemenetre (input-ra) van szükségünk az útvonal eléréséhez és a kérés feldolgozásához. (Az is egy input, ha nem ad meg semmit a felhasználó, lásd még az "opcionális paramétereket" később.) A paraméterek az útvonal regisztrációjánál a wildcard helyeken vannak, amik aztán utána a Controller metódus paramétereként értelmezhetők és használhatók. Alább rögtön felsorolom, hogy az útvonalaknál milyen paramétereket lehet és érdemes használnunk.
Hogyan lehet ezeknél URL-eket generálni?
A route() segédmetódus itt is használható, viszont nem elég neki egy paraméter, hanem szükség van egy másodikra is, amelyben felsoroljuk a paramétereket... itt a RESTful útvonalaknál csak 1-1 paraméter adódik és főleg az erőforrás id-ja az, de saját, teljesen egyedi útvonalak definiálásánál és regisztrálásánál előfordulhat, hogy több paramétert is definiálunk az útvonalban: ekkor tömbként kell meghatározni a route() segédmetódus második paraméterét, amelyben kulcs-érték párokkal felsorolva tudunk az egyes paramétereknek értéket adni. Itt most elég a RESTful útvonalakra és vezérlő metódusokra koncentrálnunk még:
Új útvonal legyen a korábbi show helyett (a régit tegyük kommentbe és másoljuk le újként):
Route::get('/airlines/{airline:name}',[AirlinesController::class, 'show'])->name('airlines.show');
Ekkor viszont még, ha így néz ki a show metódusunk, akkor nem igazán fog működni, rossz eredményt kapunk vissza, mint amit mi elvárunk:
public function show(Airline $airline)
{
dd($airline);
A metódus további részét változatlanul hagyhatjuk, hiszen az már megfelelően működik, ezzel csak teszteljük, hogy ténylegesen név alapján is le tudjuk-e kérni a légitársaságot.
De mi van akkor, ha ugyanolyan nevű légitársaságaink vannak? Itt szemléltetem:
Mivel az adatbázistábla engedi, lehet ugyanolyan nevű légitársaságunk. Ha viszont most fent a címsorba ezt ütjük be: http://127.0.0.1:8000/airlines/Austrian akkor bár a keresés jól működik, de természetesen találatként csak egyet fogunk kapni... ez pedig, ahogy említettem is, inkonzisztenciához vezetne. Így néz ki nálam az eredmény a böngészőben (nyissuk le a kis háromszöget az attributes kulcs mellett):
(A másik id-jú légitársaság nem jelenik meg, természetesen.) Ahogy említettem, a probléma megoldása csak az lehet, hogy ha csak kulcs mezőre használjuk ezt a paraméteres útvonal megoldást. Ehhez a jelen példánkban vissza kellene alakítani egyedire minden légitársaság nevét, utána pedig egy új migrációs fájlban definiálni, hogy az airlines tábla name attribútumát egyedivé tesszük (kulcs táblakényszert alkalmazunk rá). Ezután már biztonsággal működhet a "nem id-alapú" útvonal paraméterezésnél a keresés és találat.
Én viszont maradnék az id (elsődleges kulcs) szerinti keresési és találati megoldásnál, mivel az sokkal gördülékenyebben tud működni ebben a szituációban, illetve, semmi sem kényszerít jelen szituációban arra, hogy név szerint kellene ezeket lekérni. Ha persze erre volna szükségem, akkor a fenti tanácsokat kellene megfogadnom és utána a műveleteket kellene végrehajtanom.
Példák, javaslatok: több, esetleg opcionális paraméter megadása az útvonalakban és vezérlő metódusokban
Természetesen abszolút előfordulhat, hogy olyan saját útvonalat definiálunk, amelyben több paraméter is szerepel, mutatok erre is példát:
Route::get('/flights/{active}/{from}/{to}', [FlightsController::class, 'getActiveFlightsFromTo']);
Mivel a repülőjáratoknál némivel több opció kérhető le, mint a légitársaságoknál, ezért inkább erre mutatok példát. Ezzel az útvonallal lehetne lekérni az aktív/passzív repülőjáratokat egy adott dátum intervallumon belül. Ekkor a felhasználó így hívhatná meg ezt az útvonalat manuálisan (esetleg egy form input mezőiből felparaméterezve):
127.0.0.1:8000/flights/1/2022-01-01/2022-04-16
Ezzel a paraméterérték megadással az idei év aktív repülőjáratait szeretné lekérni. Az útvonal kérés feldolgozásának oldalán ugye a FlightsController felé irányítjuk a felhasználót, ahogy azt tettük a RESTful metódusok esetében is. Viszont ne konkrétan ott oldjuk meg a kérés feldolgozását a getActiveFlightsFromTo nevű metódusban, hanem hívjuk segítségül az üzleti logikát biztosító Model osztályt, ami nem csak az adatkapcsolatok menedzseléséért felel, hanem ide érdemes definiálni azokat a függvényeket, metódusokat is, amelyek a Flight osztállyal, objektumaival kapcsolatosak. A getActiveFlightsFromTo metódus például így nézhet ki a FlightsController-ben:
public function getActiveFlightsFromTo($active, $from, $to)
{
return Flight::getFilteredFlights($active, $from, $to);
}
A getFilteredFlights kapcsolódó metódus pedig a Flight Model osztályban:
public static function getFilteredFlights($active, $from, $to)
{
return Flight::where('active', $active)->whereBetween('created_at', [$from, $to])->get();
}
Kérdezhetnénk, hogy van-e értelme "kiszervezni" a megvalósítást a Model osztályra? A válasz az, hogy egyértelműen igen. Mivel bármikor előfordulhat, hogy máshol is le szeretnénk majd kérni az aktív/passzív repülőjáratokat adott dátumintervallumban, ekkor pedig az MVC kialakítása miatt nem nagyon (vagy csak nagyon "nyakatekerten") tudnánk a FlightsController osztály adott metódusához hozzáférni. Az üzleti logikai metódusokat mindig a Model osztályokban tároljuk.
Mi lenne akkor, ha például csak az adott "dátumtól" (from) paramétert ismerjük és mindig a mai napig szeretnénk lekérni a repülőjáratokat?
A megoldás az, hogy nem kell ehhez új útvonalat és metódusokat regisztrálnunk, hanem csak kicsit módosítanunk kell a meglévőeket, mert lehetőségünk van opcionális paramétereket definiálni az útvonalaknál. Nézzük meg az átalakításokat:
Route::get('/flights/{active}/{from}/{to?}', [FlightsController::class, 'getActiveFlightsFromTo']);
Gyakorlatilag egy kérdőjelet kell odatenni a "to" paraméter végére. Mindez a kapcsolódó Controller metódusban ez kell a helyes működéshez a 3. paraméternél:
public function getActiveFlightsFromTo($active, $from, $to = null)
Míg a Flight Model osztály getFilteredFlights metódusa így néz ki:
public static function getFilteredFlights($active, $from, $to = null)
{
if($to == null) $to = Carbon::now()->format('Y-m-d');
return Flight::where('active', $active)->whereBetween('created_at', [$from, $to])->get();
}
Tehát látszódik, hogy a return utasítás nem változott.
Mindezt a Tinker-ben letesztelhetjük: php artisan tinker
Flight::getFilteredFlights(1, '2022-01-01', '2022-02-16');
Flight::getFilteredFlights(0, '2022-03-31');
Az első utasítás az első aktív repülőjárattal tér vissza, ami 2022. január 1. után és 2022. február 16. között lett létrehozva. A második utasítás az első "passzív" (tehát valamilyen szempontból inaktív) repülőjáratot mutatja 2022. március 1. után.
Ugyanígy persze manuálisan is letesztelhetjük, ha a böngészőnk címsorába lekérjük a következő útvonalakat:
Ezek az útvonalak persze csak példák... úgy teszteljük őket, hogy a mi adatbázisunk szerint látható eredményt hozzanak.
Egy általános megállapítás a végére
Mindezek persze mind alapértelmezetten is igazak akkor, ha a 7 RESTful útvonalat 1 resource útvonalként hoztuk létre. Mindezt ellenőrizhetjük a Terminal-ban, amikor kiadjuk a következő utasítást:
php artisan route:list
Itt láthatók, a "manuálisan" létrehozott AirlinesController-hez kapcsolódó útvonalaink:
Az utolsó előtti "oszlopban" látszódik az útvonal neve. Ahogy pedig említettem, mindezek megvannak a luggage és a passengers esetében is, hiszen ezeket így regisztráltuk:
Route::resource('luggage', LuggageController::class);
Route::resource('passengers', PassengersController::class);
A bejegyzés kódolási módosításai az itteni Github commit-ben találhatók meg.
Gyakorlásként javaslom, hogy a többi útvonalat és a hozzájuk kapcsolódó kódokat is módosítsátok. Így tud rögzülni a használata.