Webes alkalmazások helyes szerkezete PHP-ban
Konstantinusz Kft. © 2010
1. Tartalomjegyzék 1. 2. 3. 4. 5.
Tartalomjegyzék ...................................................................................................................... 2 Mi az a leíró? ......................................................................... Hiba! A könyvjelző nem létezik. Közvetett paraméter átadások ............................................... Hiba! A könyvjelző nem létezik. Absztrakt metódusok, absztrakt konstrukció .......................... Hiba! A könyvjelző nem létezik. Validálás leíróval .................................................................... Hiba! A könyvjelző nem létezik.
2. Bevezetés Még ma is (2010-ben) a PHP továbbra is a legelterjedtebb webes programozási nyelv és fejlesztői környezet. Hamar meg lehet tanulni, és könnyen lehet benne eredményeket elérni. Viszont ismerni egy programozási nyelvet nem ugyanaz, mint tudni, hogyan fejlesszünk összetett alkalmazásokat. Sajnos ez a probléma eléggé elterjedt, sőt, a PHP-s világban ezzel kapcsolatban kifejezetten rossz szokások terjedtek el.
Nyelv ide vagy oda, az általános programozási elvek weben, PHP-ban is ugyanúgy érvényesek, amiket ajánlott betartani, hiszen nem csak az a lényeg hogy a program elkészüljön és működjön, hanem könnyen módosíthatónak, bővíthetőnek, és karbantarthatónak kell lennie, mindezt persze megbízható módon. A következőkben megnézünk néhány PHP-s eszközt, és a hozzájuk kapcsolódó elveket, amivel tiszta, elegáns szerkezetű PHP-s alkalmazásokat építhetünk fel.
2/11
3. Hogyan ne csináld PHP-ban alapvetően nagyon kényelmes dolog PHP fájlok include-olása. Az egyik PHP fájlomon belül egy tetszőleges ponton, futási időben betölthetek egy másik PHP fájlt, ami ennek hatására ott helyben értelmeződik, és le is fut. Ezt általában 3 féle céllal szokták használni. Ebből azt a kettőt nézzük meg először ami helytelen alkalmazás szerkezethez vezet. Az első, amikor egy weboldalt akarunk a különböző részekből összeollózni. Ilyenkor például az index.php valahogy így szokott kinézni:
include “menu.php”; ?> |
include “tartalom.php”; ?> |
Tehát az oldal tetejébe betöltjük a menüt, alá pedig a tartalmat. Ebben az esetben a betöltött PHP fájl ott helyben fut le, ahol betöltjük. Ennek viszont két elvi hibája is van. Egyrészt, úgy gondolkodik, mintha a alapvetően HTML-en belül futtatnánk PHP-t. Ez rossz megközelítés, mivel mi itt egy programot írunk, márpedig egy program alapvetően utasítások sorozata, amik aztán produkálnak valamilyen kimenetet.
3/11
A másik probléma, hogy az include-ot arra használja, hogy egy az egyben lefuttasson php fájlokat az adott helyen. Ez teljesen rossz. Gondoljunk bele mi lenne ha ez egy hagyományos gépi kódú program lenne, vagyis index.exe, menu.exe, tartalom.exe. Hát hogy nézne ez ki, kérem? Az index.exe lefuttatja a menu.exe-t, az meg majd kirajzolja a menüt? Ilyen nincs. Egyébként is ekkor már nem egy programot írtam, hanem 3 kicsit programocskát. Arról nem is beszélve, hogy ezt a három kicsi programocskát külön-külön is le lehet futtatni, mivel mindegyik önállóan futtatható. Ez pedig ki tudja milyen biztonsági problémákat jelenthet. Például ha csak bejelentkezés után férhetnék hozzájuk, akkor meg kell írnom az ellenőrzést mindegyikbe külön-külön, nehogy valaki lefuttassa őket magukban. Ez pedig kódtöbbszörözést jelent, ami megint egy alapvető elv megsértése.
Egy másik szintén gyakori hiba, hogy arra akarják használni az include-ot, hogy kód hordozhatóságot, vagy procedurális absztrakciót imitáljanak vele. Például itt egy kód részlet, ami összeállít egy táblázatot egy tömbbe, majd betölti a tablazat_rajzol.php-t ami a $tablazat nevű változó tartalmát megjeleníti egy táblázatban.
$tablazat=array( array(“Alma”, ”Piros”) ,array(“Kötre”, ”Sárga”) ,array(“Szilva”, ”Lila”) );
include “tablazat_rajzol.php”;
Ezzel azt akarja a programozó megvalósítani, hogy a táblázat rajzoló kód hordozható és “paraméterezhető” legyen. Viszont amellett hogy a fent említett problémák miatt ez eleve rossz megoldás, még az is súlyosbítja a helyzetet, hogy a tablazat_rajzol.php-n belül egy olyan változóhoz akar hozzáférni, ami azon a fájlon kívül van, és a kód csak feltételezi hogy majd valahogyan a globális térben ott lesz amikor kell. Emiatt a kód rettenetesen megbízhatatlanná válik, mivel képtelenség átlátni hogy a tablazat_rajzol.php-nek milyen “paraméterekre” van szüksége, ráadásul így a tablazat_rajzol.php-n belüli kód ugyanabban a névtérben fog létezni mint az őt betöltő kód, tehát bármikor felléphet névütközés, amit képtelenség követni.
4/11
4. Hogyan csináld jól Most lássuk tehát, hogy a fent említett esetek helyett mik a jó megoldások. Először is programozzunk objektum orientáltan. PHP-ban is remek OO eszközrendszer áll rendelkezésre 5ös verziótól felfele. Külön az OO alapvető elveire most nem térünk ki, csak néhány PHP-s jellegzetességre.
Osztályok elhelyezésére az általános elv a legtöbb nyelv esetén az szokott lenni, hogy minden osztályt egy külön fájlba helyezzünk el. Ez egyrészt az átláthatóságot segíti, de PHP-ban ennek működési szempontból is fontos szerepe lesz.
Az első fontos dolog, hogy az alkalmazásunknak lehetőség szerint 1 belépési pontja legyen. Belépési pont alatt azt értjük, hogy melyik az a fájl amit közvetlenül is le lehet futtatni, és közvetlenül végrehajtható kódot tartalmaz. Ez a fenti példákban csak az index.php lehet. Minden más fájl csak osztályokat és függvénydefiníciókat tartalmazhat. Ezzel nem futtathatóak közvetlenül. Tehát innentől kezdve azért töltünk be más fájlokat, hogy használhassuk a bennük lévő osztályokat vagy függvényeket.
Továbbá ne egyből HTML kimenettel kezdjük a programunkat. Ha már osztályokból és függvényekből áll minden, akkor az index.php-nak igazából csak pár függvényt kellene meghívnia, illetve objektumokat példányosítani, azok metódusait meghívni.
Szóval így nézhetne ki a fent látott példa helyesen:
fooldal.php:
require_once “menu.php”; require_once “tartalom.php”;
class Fooldal{ function megjelenit(){ $menu=new Menu();
5/11
$tartalom=newTartalom(); ?>
$menu->megjelenit(); ?> |
$tartalom->megjelenit(); ?> |
} } ?>
index.php:
require_once “fooldal.php”;
6/11
$fooldal=new Fooldal();
$fooldal->megjelenit(); ?>
Ez így már program. A menü, a tartalom, de még a főoldal is egy osztály, aminek egy metódusa jeleníti majd meg a HTML kódot, az index.php pedig csak annyit teszt, hogy ezeket betölti és futtatja. Ezzel az is megvalósul, hogy nincs sok belépési pont, mivel az egyes fájlokban csak osztálydefiníciók vannak, így azokat hiába futtatom le, nem csinálnak semmit.
Ha csak ezt a formátumot betartja valaki, akkor máris mérföldeket lépett előre a minőségi PHP-s alkalmazás fejlesztése irányába.
7/11
5. Autoload
Persze itt még van egy kis probléma, amit tisztázni kell. Láthatjuk ugyanis, hogy különböző osztályok hivatkozhatnak egymásra, és persze mindannyian külön fájlban vannak. Ez az jelenti, hogy ha az egyik osztály szeretné a másikat használni, akkor annak is be kell töltve lennie. A fenti példában azt láttuk, hogy a Fooldal osztályt tartalmazó php fájl automatikusan betölti a menu.php-t és a tartalom.php-t is, mivel az azokban lévő osztályokat fogja használni. Ez viszont még mindig nem a legjobb megoldás.
Az egyik oka, hogy például a Menu és Tartalom osztályokat több másik osztály is használhatja, nem csak a Fooldal. Ezért, hogy biztosra mehessünk, azoknak az osztályoknak is be kellene töltenie ezt a kettőt. Viszont ekkor belefuthatunk abba a problémába, hogy többször akarjuk betölteni ugyanaz a fájl, márpedig egy osztály csak egyszer lehet definiálva. Persze ez adott esetben könnyen megoldható ha require_once-ot használunk, mivel akkor csak egyszer kerül betöltésre a fájl.
A másik probléma viszont az, hogy ha úgy írjuk meg a függőséget betöltését, ahogy fentebb láthattuk, akkor például az Menu és a Tartalom osztály akkor is betöltődik ha a Fooldal osztály igazából nem is fogja őket használni. Ez gondot jelenthet ha sok osztályunk van, hiszen a PHP interpreteres nyelv, ezért az is némi időt igényel, hogy értelmezze a betöltött fájl, függetlenül attól hogy annak a kódja lefut-e. Persze ezt megoldhatjuk úgy is, hogy a függőségek betöltését oda tesszük ahol tényleg használni fogjuk őket, viszont ekkor több helyre is el kell helyeznünk a kódban, ráadásul ez a munka teljesen manuális, nekünk kell figyelni rá, hogy minden ott legyen ahol szükséges. Ez nagyon megbonyolítja a munkát, és túlságosan sok benne a hibalehetőség, nehezen tesztelhető, átláthatatlan és megbízhatatlan lesz a kód.
Szerencsére a PHP-nak van egy beépített eszköze ami kényelmes, megbízható, és teljesítmény szempontból is hatékony megoldást nyújt, az autoload. Az autoload lényege az, hogy egy osztály akkor kerül betöltésre, amikor arra futási időben legelőször ténylegesen szükség van. Ez által gyors lesz a kódunk, hiszen nem végez felesleges munkát, és nem is nekünk kell figyelni arra, hogy minden osztály betöltését a megfelelő helyen elvégezzük.
Az autoload használatához írnunk kell egy egyszerű függvényt, ami egyetlen paramétert vár, majd regisztrálni kell e függvényünket, hogy ez egy autoload függvény. Ezután a rendszer ha találkozik egy olyan osztállyal amit még nem ismer, akkor meghívja a regisztrált autoload függvényeket (több is lehet), és átadja nekik a keresett osztály nevét paraméterként. Innentől kezdve pedig ránk van bízva, hogy a keresett osztályt betöltsük a megfelelő fájlból (hiszen mi írjuk a függvényt).
8/11
Módosítsuk tehát a fenti példánkat autoload-osra:
betolto.php:
function betolto($osztalynev){ /*Itt most egy switch-case-szel döntjük el, melyik fájlt kell betölteni, egyébként ez tetszőleges. */ switch ($osztalynev) { case "Menu": require_once “menu.php”; break; case "Tartalom": require_once “tartalom.php”; break; case "Fooldal": require_once “fooldal.php”; break; default: echo “Ismeretlen osztály:”.$osztalynev; break; } }
//regisztráljuk autoload függvényként a “betolt” függvényünket spl_autoload_register(“betolto”);
9/11
?>
index.php:
require_once “betolto.php”;
$fooldal=new Fooldal();
$fooldal->megjelenit(); ?>
Tehát itt van egy külön fájlunk amiben csak a betöltő van, és innentől kezdve nem is kell a fooldal.php-ban betölteni a menu.php-t és a tartalom.php-t. Mindent az autoload függvényünk tölt be, így az index.php-ban is csak azt kell betölteni, és onnantól azonnal hivatkozhatunk is akármelyik osztályunkra.
10/11
6. Összefoglalás
Tehát, legfontosabb tanulság, hogy nem sok kis szkriptecskét írunk, hanem alkalmazást, ami csak osztályokat és függvényeket tölt be más fájlokból. Nem összeollózzuk a weboldalunkat php fájlok include-olgatásával, hanem függvényeket és metódusokat futtatunk. Nem HTML-t írunk amibe PHP szkriptet rakunk, hanem programot, ami HTML-t ír ki.
Mindenki a saját érdekében fogadja meg ezeket az egyszerű elveket, még annak ellenére is, hogy sajnos a PHP-s világ többsége még ma is az ellenpéldákban leírtak szerint működik.
11/11