Ahogy az előző részben ígértem, most már tényleg nekiállunk egy weboldal felépítésének! Igen, ez még mindig a OOPweb cikksorozat, ahol objektum orientáltan próbálunk meg weboldalt készíteni - ha még nem olvastad volna az előző részeket, akkor az oldalsávban találod meg azokat!
Induláshoz először is, tervezzük meg mire lesz szükségünk:
Egy blogot akarunk készíteni, belépési és hozzászólási lehetőséggel - ezek alapján 3 osztály máris adott: szükségünk lesz egyre a blogbejegyzések kezelésére, egy másikra a belépéshez, és egy harmadik felelhet a felhasználói műveletekre, mint amilyen a kommentelés is lesz.
Ezeken kívül elég hamar felmerül az igény még legalább másik három osztályra is, a weboldal interakcióinak kezelésére: nem árthat egy osztály az adatbázis műveletekhez, egy a bejövő kérések feldolgozásához, és egy harmadik is kellhet a válasz, az az a teljes, megjelenítendő HTML kód visszaadásához.
Első körben nézzük az utóbbiakat: Az adatbázis kezelést gyorsan le lehet tudni, egy ilyen osztályt unalmas lenne megírni, de emiatt szerencsére ingyenesen felhasználható osztályok százai sietnek a segítségünkre. Mi most a beszédes nevű PHP MySQLi Database Class-ot fogjuk használni.
A beérkező get és post kérések kezelésére is számos megoldás, és megvalósítási logika közül választhatnánk:
Megoldhatnánk úgy, hogy egy kérés esetén egy másik osztály megfelelő függvényét hívjuk, amelyeket egy beállítás részben adunk meg, vagy JavaScriptból ismerős "onload" eseményeket adhatnánk hozzá az osztályunkhoz, valahogy így:
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
Mi egyiket sem fogjuk használni, utóbbi túl Laravel-es (úgy hogy ha ilyet akarnánk, csak használnánk a Laravel-t, és nem írogatnánk meg magunknak az osztályt), előbbi pedig sok, alapból nem definiált osztály létrehozását igényelné. Úgy hogy az OOP elveit kicsit megsértve, az osztályunk előre beállított szabályok alapján simán be fogják include-olni a megfelelő fájlokat.
A HTML válasz legenerálására pedig egy saját sablonkezelőt fogok használni, de ilyenből is egy csomót találhatunk az interneten is, ha valaki nem szeretne sajátot írni.
Ezek után jöhet a mappaszerkezet megtervezése:
A példában egy assets mappában lesznek a PHP fájljaink, azon belül is két részre osztva: Az engine mappán belül lesznek a beállítási fájlok, azon belül pedig egy class mappában az osztályok. Az assets-en belül a másik mappánk a template nevet viseli, ide kerülnek a sablonkezelő által használt fájlok.
Kezdjük a programozást a engine mappában, és hozzunk létre egy autoload.php-t!
Az osztályaink a class mappán belül lesznek, minden osztály egy külön fájlban, és az autoload.php arra szolgál, hogy ezeket az osztályokat betöltögessük. Ez klasszikus esetben így nézne ki:
include_once 'class/content.class.php';
include_noce 'class/login.class.php';
include_once 'class/route.class.php';
[...]
Nos, látható hogy ez nem lenne a legszebb megoldás. Szerencsére a PHP a segítségünkre siet: Az spl_autoload_register függvényt pont a mi helyzetünk megoldására találták ki: ebben egy olyan függvényt írhatunk meg, ami akkor fog lefutni, amikor egy olyan osztályt akarunk használni, ami nem létezik (nincs még beinclude-olva).
Ezzel egyszerűsítve a helyzetünket a kódunk így néz ki:
spl_autoload_register(function ($class) {
include 'class/' . $class . '.class.php';
});
A csomó egymás alatti include egyetlen include sorrá redukálódott, és így az sem fordulhat elő, hogy valamelyik osztályt elfelejtjük behívni.
Érdemes lehet még egy config.php-t is elhelyezni ebben a mappában, ahol az oldalunk (és az osztályaink) alapvető beállításait tárolhatjuk.
Ezek után írjuk is meg az első osztályunkat, az útvonalkezelőt, tehát a route.class.php-t:
class route{
private $routes;
private $defaultRoute;
private $pagesFolder;
private $params = null;
/**
* @brief Some brief description.
* @param array $settings beállítások
*/
public function __construct($settings){
$this->routes = $settings['routes'];
$this->defaultRoute = $settings['default'];
$this->pagesFolder = $settings['pagesFolder'];
}
/**
* @brief megfelelő oldal betöltése
* @return string betöltendő oldal
*/
public function route($path){
if(empty($path) or $path==null){
$path = '/';
}
if(isset($this->routes[$path])){
$page = $this->routes[$path];
if(is_array($this->routes[$path])){
$params = $this->routes[$path][1];
$page = $this->routes[$path][0];
}
if(isset($params)){
$this->params = $params;
}
return $this->pagesFolder.'/'.$page;
}
return $this->pagesFolder.'/'.$this->defaultRoute;
}
}
Ez egy nagyon egyszerű kód lett, ami visszaadja hogy melyik PHP fájlt kell be incloude-olni. Nyilvánvalóan ezt a gyökér könyvtárban elhelyezett index.php-ban kell megtegyük, így írjuk meg az oda tartozó PHP kódot is:
include_once 'assets/engine/autoload.php';
$route = new route($routeSettings);
include_once $route->route((isset($_GET['path']) ? $_GET['path'] : ''));
Ez a config.php-ban megadott $routeSettings változó beállításai alapján tölt be egy PHP fájlt. Esetünkben legyen ez például egy, a template, és azon belül egy pages mappában található PHP fájl. Mivel a cikksorozatnak nem célja a teljes programlogikának a bemutatása, így példaként nézzük meg például, hogy egy főoldal hogyan nézhet ki, ahol listázzuk a legfrissebb blog bejegyzéseket:
$template->set_param('title','Főoldal');
$content = new content($database);
$contentList = $content->list(10); //10 legfrissebb bejegyzés lekérése
$template->add_globalBlock('items', 'home/item.html',$contentList); //sablonkezelőnek átadjuk a 10 bejegyzést, és hogy melyik HTML sablonba helyezze be az elemeket
$template->get_content('home.html'); //sablonkezelőben a megfelelő sablon megjelenítése
Természetesen a kódunk működéséhez szükségünk van a blogbejegyzéseket kezelő content osztályra, melynek példánkban a list függvénye a paraméterben megadott első x bejegyzését adja vissza. Ezt adjuk majd át a sablonkezelő osztálynak, majd megjelenítjük a megfelelő HTML oldalt, amelybe be lesz ágyazva a megfelelő helyre a listázás HTML sablonja is, és az egész fel lesz töltve az átadott paraméterekkel. Ilyen sablon kezelőből rengeteget találunk, a régi, klasszikus megoldás a Smarty vagy a Mustache, de rengeteg újabb is létezik, például ilyen a Dwoo is - de akár sajátot is írhatunk, mely az általunk elképzelt logika alapján működik.
Nyilván valóan ezek után az oldallogikát jelentő osztályok megírása alap PHP tudással már nem lehet gond (de ha kell egy kis segítség, a belépés osztály szerkezetére az előző részekben van minta, a blogbejegyzések kezelésére egy bonyolultabb esetet leíró szöveg pedig az előző részben található (az öröklődésnél, ahol a példában nem csak blog, hanem egyéb bejegyzéseink is lehettek) - aztán ezeket az osztályok megfelelő funkcióit csak megfelelő sorrendben meg kell hívni a fájljainkban, és átadni az adatokat a sablonkezelőnknek a HTML-el együtt - és tulajdonképpen fel is épült a weboldalunk).
Mire a fenti bloggal elkészülünk, nagy eséllyel már érteni fogjuk az OOP lényegét, de még egyetlen fontos dologról nem esett szó: az objektumok másolásáról. Aki tanult C vagy C++-t, az tudhatja, hogy a másolás az nem mindig egyértelmű művelet ((khmm: pointerek, pointerek mindenütt)), de szerencsére ezzel a PHP békén hagy minket, és hozzászokhattunk, hogy bármit csinálunk, a változóink tényleg másolódni fognak, és nem fogjuk az eredeti változó tartalmát is módosítani, ha a másolattal csinálunk valamit. Viszont mindez csak addig igaz, amíg nem dolgozunk objektumokkal! (vagy esetleg referenciákkal.) Ha megpróbálunk csak simán egy objektumból másolatot csinálni, azt a furcsaságot vehetjük észre, hogy ha valamit a másolatban átírunk, akkor az az eredetiben is módosul, valahogy így:
$a = new test();
$a->val = 10;
$b = $a;
echo $a->val.' - '.$b->val;
$b->val = 5;
echo $a->val.' - '.$b->val;
Tehát az $a és a $b tökéletesen ugyanannak tűnik, ha az egyik módosul, akkor a másik is. (ez sok esetben hasznos lehet, ha visszatérési értéknek vissza akarunk adni egy objektumot, de az objektum tartalmán még egy másik függvényhívás módosítana (pl. több CURL hívásakor visszaadhatunk mindegyikre egy-egy objektumot, ami majd a kimenetet fogja tartalmazni, de a több CURL kérést ténylegesen egyszerre fogjuk lefuttatni, nem egymás után, pl. egy run függvény hívásakor. Ekkor a run függvény előtt az objektumjaink üresek lesznek, utána viszont már ugyanúgy benne lesz a lekérdezett tartalom, ugyan úgy, mint ha a CURL kérések egymás után futottak volna le - csak éppen sokkal gyorsabb lesz így a kódunk.)
Viszont mi a helyzet abban az esetben, ha tényleg másolni akarjuk az objektumunkat?
Ez esetben siet a segítségünkre a clone kulcsszó, illetve az esemény bekövetkeztekor lefuttatható __clone magic methods. Így a __clone függvényben az esetlegesen az objektumon belül lévő objektumokat is tudjuk másoltatni, de ha ilyenünk nincs, akkor nincs is erre a függvényre szükségünk, simán meghívhatjuk az osztályukra a clone-t:
$a = new test();
$a->val = 10;
$b = clone $a;
echo $a->val.' - '.$b->val;
$b->val = 5;
echo $a->val.' - '.$b->val;
Ezzel a PHP részét be is fejeztük a weboldalnak, a következő részben már a (kliens oldali) JavaScriptel fogunk foglalkozni!
Hozzászólások
-