Megjegyzés: ha a bevezetőben használt rövidítések nem lennének ismerősek, akkor érdemes újra átfutni az előző blogbejegyzésemet.
A
mostani blogbejegyzéshez használt ábra szerencsére nagyon beszédes: ha
jobb oldalról haladunk, akkor láthatjuk, hogy használhatunk mindenféle
adatbáziskezelő rendszert (mi is alkalmaztunk már a MySQL-en kívül
SQLite-ot, MS SQL-t, felhőbeli adatszolgáltatást is). A "DB Connection"
rész felelős volt azért, hogy ezekhez a típusú rendszerekhez fel tudjuk
építeni az alkalmazásunkból a kapcsolatot. Most pedig rátérünk arra,
hogy hogyan lehet majd manipulálni az adattábláinkat (INSERT, UPDATE,
DELETE stb.) illetve lekérdezni belőlük (SELECT). Ez a témakör olyan
nagy, hogy biztosan több blogbejegyzésen át fogom ismertetni ezt a
területet.
A Laravel keretrendszer kétféle lehetőséget nyújt a számunkra:
- Eloquent: ez egy ORM (Object–Relational Mapping)
eszköz, ami lehetővé teszi, hogy objektumokat használjunk az adatbázis
műveletek elvégzésére, a művelet eredményét pedig könnyedén feldolgozzuk
a segítéségével. Ezzel tehát egy olyan eszközhöz jutunk, amellyel
könnyedén tudunk ún. CRUD (Create-Read-Update-Delete, vagyis a
leggyakrabban használt) műveleteket végrehajtani az adatbázisunkban.
Érdemes most tehát jól figyelni majd, mert ilyen ORM eszközt nem csak a
Laravel keretrendszer nyújt nekünk, hanem minden más fejlett
programozási keretrendszer is, csak éppen azok nem Eloquent-nek hívják,
hanem például a .NET-ben Entity Framework-nek... de a mögöttes logika
ugyanaz ezeknél az ORM eszközöknél, a használatuk célja azonos,
adatokhoz szeretnénk vele hozzájutni vagy manipulálni könnyedén.
- Query Builder:
ez pedig egy olyan eszköz, amelynek a segítségével az SQL utasításokra
kísértetiesen hasonlító lekérdezéseket rakhatunk össze, építhetünk fel
utasításrészletek egymáshoz fűzésével. Ezzel a témakörrel akkor fogunk
foglalkozni, ha az Eloquent-et már kellőképpen megismertük. De ha
beszélhetek szimpátiáról, akkor én a gyakorlatban jobban szeretem ezt
alkalmazni, ízlések és pofonok... megismerjük mindkettőt és akkor
mindenki maga választhatja majd ki, hogy neki mi áll jobban kézre.
Miért is használjuk ezeket? Miért nem írunk simán SQL lekérdezéseket?
A válasz többrétű, mivel ezen fenti két eszköz használata olyan
előnyöket nyújt a számunkra, amelyeket vétek lenne nem kihasználni,
például:
- helyes és optimalizált SQL lekérdezéseket generálnak a háttérben, kisebb a hibázási lehetőség,
- a lekérdezések könnyebben frissíthetők, karban tarthatók, újrafelhasználhatók,
- megvédik a használóját (minket) attól, hogy SQL beszúrásos (injection) támadást tudjanak végrehajtani a kódjaik ellen,
- segítségével
lehetőségünk van arra, amit korábban már csináltunk is: könnyedén
leválthatják az alkalmazásunk alatt futó adatbáziskezelő-rendszert, és
nem kell emiatt megváltoztatnunk (vagy csak nagyon ritkán) sem az
Eloquent, sem a Query Builder-rel összerakott lekérdezéseinket.
- ... és talán a legfontosabb:
a programozók úgy manipulálhatják a kódjukat, mintha objektumokkal
dolgoznának. Ezt mindjárt kifejtem és gyakorlatban is megnézzük a
működését.
Az Eloquent használatához szükség van arra,
hogy az eddig "érintetlen" MVC elemhez nyúljunk, ez pedig nem más, mint a
Model. Aki jól figyelt a blogbejegyzéseimre (főleg erre), akkor az már tudhatja, hogy a Model-ek két főbb dologért felelnek:
- Ők
biztosítják az üzleti logikát, tehát olyan adattagokat és metódusokat,
amelyek az alkalmazásunk funkcióinak működését teszik lehetővé.
- Ők azok, akik az Eloquent ORM segítségével elérik a hozzájuk tartozó (!) adatbázistáblát.
Az
első pontra ismét felhívnám a figyelmet, mert nagyon sokan összekeverik
az "üzleti logika megvalósítása" szempontjából a Model-t a
Controller-rel. Fontos, hogy megjegyezzük, hogy a Model biztosítja az
üzleti logikát.
De térjünk rá most a második pontra, hiszen
az Eloquent szempontjából vizsgáljuk majd a Model-eket. Hozzunk is létre
egy Model-t a projektünkbe:
php artisan make:model Post
Ennek
hatására létrejött egy PHP fájl, a helye pedig, ha a Laravel 5.7-es
verziójával dolgozunk, akkor az app mappába került be, ha pedig magasabb
verziószámú a Laravel alkalmazásunk, akkor az app / Models mappába
került be a Post.php fájl (a Laravel keretrendszerünk verzióját így
tudjuk lekérdezni: php artisan --version). Igazából a mostani gyakorlatunk szerint nincs jelentősége annak, hogy melyik verziójú keretrendszerrel dolgozunk.
Ami viszont fontos: figyeljünk a névkonvenciókra!
A Model neve nagy kezdőbetűvel indul és angolul egyes számban van,
viszont alapértelmezetten ez a posts nevű (kis kezdőbetűs, bár az SQL
erre nem érzékeny, de fontosabb, hogy angolul többes számban van)
táblához fogja biztosítani a hozzáférést.
A létrejött Post
Model-ünket megnézhetjük, azt láthatjuk, hogy ez pusztán egy egyszerű
osztály, egy class, ami kiterjeszti a Model ősosztályt. Egyelőre ne is
írjunk még bele semmi, pusztán használjuk!
Ha megvan még a
PostsController-ünk (figyelem, névkonvenció itt is, angolul a Controller
nevében többesszámban használjuk a Post-ot), akkor a show() metódusára
tekintsünk. Bár akkor még nem tudtuk, amikor beleírtuk, de igazából egy
Query Builder-t raktunk össze akkor:
\DB::table('posts')->where('slug', $slug)->first(). Ezt fogjuk
most lecserélni, úgyhogy kikommentezhetjük azt a sort és helyette írjuk
be ezt:
$post = Post::where('slug', $slug)->first();
És a dd() -s sor elől vegyük ki a kommentezést, hogy kapásból megjelenjenek itt a $post objektum értékei majd.
A
VSCode egyből alá is fogja húzni a Post szöveget, ez azért van, mert
importálnunk kellene az előbb létrehozott Post osztályunkat. Szerencsés
szituációban vagyunk, ha használunk Laravel-specifikus kiterjesztéseket a
VSCode-ban, mert ekkor a Post szövegre kattintva jobb egérgombbal
előjön a helyi menü, benne pedig az "Import class" menüpont (itt javasoltam a kiterjesztés csomagot).
Ahogy korábban említettem, a Laravel keretrendszerünk verziószámától
függően fogja az iménti Import-álás hatására vagy az App\Post vagy az
App\Models\Post osztályt importálni a fájl tetejére. Ennek hatására
megszűnik a Post felirat pirossal történő aláhúzása, most már hibamentes
a kódunk újra.
(Remélhetőleg) elég sokat tesztelgettük már a
rendszerünket, az adatbázisunkat, úgyhogy a további helyes működés
érdekében szúrjunk be egy sort a posts nevű adattáblánkba:
INSERT INTO `posts` (`slug`, `title`, `body`) VALUES ('my-first-original-post', 'My first original post', 'This is my first original post with a title.');
(Ugye az oszlopok nevének sorrendje mindegy, a lényeg, hogy a VALUES után is ugyanabban a sorrendben következzenek az értékek.)
Teszteléshez hozzuk be utána a következő URL-t a böngészőnkben: http://127.0.0.1:8000/posts/my-first-original-post
Eredményül ezt kapjuk, ha az attributes melletti kis háromszögre is rákattintunk:
Látható, hogy egy elég komplex objektumot kaptunk vissza, ami
önmagában a $post volt a kódunkban. Ennek az attributes mezőjébe került
bele a tömb, ami tartalmazza az adatbázisból érkező egyetlen sor mezőinek értékeit, ahogy
az az ábrán látható is.
Az Eloquent ORM (és a Query Builder-es) kapcsolatok nélküli lekérdezéshez kapcsolódó működési elv tehát a következőként fogható fel (példával is illusztrálva):
- Az imént láthattuk, hogy ami a programkódunkban 1 objektum volt, az az adatbázistáblában 1 sornak felelt meg, vagyis gyakorlatilag egy SELECT lekérdezést csináltunk, ha adatbázisos "szemüveggel" nézzük;
- Ha több sort kértünk volna le, mondjuk az elmúlt 1 hónap blogbejegyzéseit, akkor programozói szempontból egy gyűjteményt (például egy tömböt) kaphatunk vissza az adattáblából, ami ott több sort jelképezne;
- Ilyen terminológia szerint tehát a Post osztály jelképezi a posts táblát, egyetlen osztálypéldány (vagyis objektum) jelképez egyetlen adatsort a táblában, több objektum pedig egy gyűjteményt képez, amik az adattáblában több sort jelképeznek.
De mi a helyzet a manipulációs műveletekkel (létrehozás vagyis beszúrás, frissítés, törlés)? Ha így próbáljuk értelmezni az Eloquent ORM működését, akkor ...
- Például egy Post osztály szerinti egy új objektum létrehozása (példányosítás), adattagjaink értékadása, majd mentése egy új sor beszúrásának felel meg az adattáblába;
- Egy meglévő (lekért) objektum/adatsor adattagjainak lekérése, majd módosítása és mentése megfelel egy sor frissítésének az adatbázisban;
- Ha pedig egy adatsort lekérünk egy objektumba, majd töröljük azt és "mentjük", akkor az adatbázisban egy sor törlését hajtottuk végre;
- Természetesen a frissítésre és a törlésre vonatkozó megfogalmazás igaz akkor is, ha nem csak egy objektumot/sort kérünk le, hanem többet (kvázi egy gyűjteményt), akkor ezeket is tudjuk együttesen frissíteni és törölni is.
Visszatérve a példakódunkra, hogy egy picit szebb eredményt kapjunk, ismét
kommentezzük ki a dd()-s sort és adjuk át a $post objektumot a post
nézetnek. A nézetben korábban a post body adattagját használtuk fel,
most egy kicsit még finomítsuk a post.blade.php kódját: a post title mezőjét írjuk ki egy h1-es fejlécbe a "My blog" helyett.
Ha azt szeretnénk, hogy egy kis hibakezelés is legyen a működésben,
akkor a PostsController show metódusában a first() metódust cseréljük le
erre: firstOrFail(), ami eléggé beszédes, hogy ha a posts adattáblában
nem létező "slug"-ot próbálunk lekérni a böngésző URL-jében, akkor
valamilyen barátságosabb hibát kapunk, mint ha simán a first() metódus
esetében tennénk ugyanezt.
Megjegyzés:
a Laravel 5.7-ben volt ilyen szép 404-es hibakódos nézet. Illetve a
fordítás azért létezik, mert korábban már elkészítettük hozzá ennek a bejegyzésnek a végén.
A következő bejegyzésekben mélyítjük az Eloquent-es tudásunkat, használni fogunk egy Tinker nevű alkalmazást a Terminal-ban, aminek a segítségével valós időben fogjuk tudni tesztelni az alkalmazásunk Model-jeit, így az adatbázis menedzselését (beszúrás - INSERT, frissítés - UPDATE, törlés - DELETE), valamint a lekérdezését (SELECT). Utána pedig következhet majd az adattábla kapcsolatok kezelése, de nem rohanok még ennyire előre...
A legutóbbi és ezen blogbejegyzésnek a kódjai elérhetők ennél a git commit-nél.