10. Laboratóriumi gyakorlat Model View Controller alapú alkalmazásfejlesztés A gyakorlat célja: Az MVC elv megértése és ismerkedés egy egyszerű MVC keretrendszerrel. Felkészüléshez szükséges anyagok: 1. A 17-es segédlet anyaga. 2. A honlapon található MVC diagramm és kód tanulmányozása. Az "MVC osztályok" csomag felhasználható a projekt megírásához. A laboron átnézzük a csomag szerkezetét és megírjuk egy használati eset kódját. A keretrendszer itt található: http://www.ms.sapientia.ro/~lszabo/webtechnologia/peldak/mvc/. Az MVC osztályok bemutatása...1 A kontroller: index.php...2 A modell: MVC_Module...3 A View megvalósítása: MVC_View...5 Egy használati eset implementálása...6 Az MVC osztályok bemutatása Az osztályok kapcsolatát az alábbi diagramm ábrázolja: A csomagban a fenti diagrammot megvalósító alap osztályok, illetve néhány használati esetet megvalósító kód található. 1
A bemutató során utalunk majd az osztályok forráskódjaira, amelyek itt: http://www.ms.sapientia.ro/~lszabo/webtechnologia/peldak/mvc/mvcdoc/ böngészhetőek. A kontroller: index.php A kontroller részt az index.php valósítja meg. A teljes kód itt tekinthető meg: http://www.ms.sapientia.ro/~lszabo/webtechnologia/peldak/mvc/mvcdoc/. Az alkalmazás oldalai az alábbi sémát megvalósító GET paraméterekkel hívhatóak: /index.php?page=login&go=send A fenti paramétereket az index.php értelmezi, és végrehajtja egy adott objektum, jelen esetben a login.php adott metódusát, ez a fenti példában a send metódus. Az indítást az alábbi kód valósítja meg: //a modul neve if (isset ($_GET['page'] )) $page = $_GET['page']; else $page = 'start'; //letezik egy start modul ha nincs modul nev //az esemeny nevet if (isset ($_GET['go'] )) $event = $_GET['go']; else $event = 'start'; //letezik egy start fuggveny minden modulban //ha nincs go parameter //betoltom a $page altal meghatarozott objektumot //PHP-ben az oszt'ly nevek nem erzekenyek a kis-nagybetűre //es a belso sztruktúrákban kisbetűsen tárolódnak //ezért nem kell azzal foglalkozni, hogy kis vagy nagybetűsen jött if (class_exists($page)) $module = new $page (); if (!$module instanceof MVC_Module ) trigger_error("rossz objektum, nem modul.", E_USER_ERROR); else trigger_error("hiányzó osztály", E_USER_ERROR); //meghivom a go parameterben levo esemenyt //megnezem, hogy van-e ilyen metodusa //illetve van-e CALL metodusa, ha proxy-kent van megirva if ( method_exists ( $module, $event ) method_exists ( $module, " call" )) //a meghivott lap itt lefut //amennyiben lenne visszateritett erteke, az a $RES be kerul $res = $module->$event(); else trigger_error("hiányzó metódus", E_USER_ERROR); 2
Látható, hogy a létrehozott $module objektum MVC_Module típusú. Ez lesz tehát a futtatható objektumok típusa: így valamennyi objektumnak, amely az alkalmazás által indítható meg kell valósítania ezt az osztályt. Ezeknek a $event nevű metódusa lesz elindítva amennyiben ilyen létezik. Így az alkalmazás futtatható objektumait moduloknak is fogjuk nevezni. A következő lépésben lefut a modul (pl. a login.php). Ez végrehajtja azt az alkalmazás logikát amit megvalósít. Azokat a tartalmakat amelyeket ki kell írni a kliensnek előkészíti egy adatstruktúrában (később majd megnézzük hogyan). Az index.php tovább fut, és az alábbi kódra lép: //ez lesz a megjelenitest megvalosito objektum $view = new MVC_View ( 'smarty', $module ); // a model valtozoinak beallitasa $view->assign(); //... itt kód kihagyva... //ez a vegso kiiras: a display felepiti az index.tpl -bol az //index.html-t es kiirja $view->display(); A kontroller létrehoz egy $view nevű objektumot, amelynek 2 paramétert ad: -a 'smarty' paraméterrel azt kéri a megjelenítőtől, hogy a Smarty-t használja sablonkezelőként -a $module megadásával megadja a MVC_View osztálynak, hogy melyik objektumtól kell a kiírandó adatokat átvenni A modell: MVC_Module Az MVC_Module osztály az alapja minden futtatható osztálynak. Ez valósítja meg a tervezési minta "modell" részét. Minden példánya tartalmaz egy referenciát az alábbi objektumokra: MVC_Session: ez a PHP $_SESSION változójának objektumorientált elérését biztosítja. Ilyenként ez egy singleton (egyke) típusú osztály. Alább láthatjuk az osztály get_instance függvényét, valamint a statikus singleton objektumot tartalmazó változót: class MVC_Session / Ez a valtozó tartalmazza a singleton osztályt @access private @var Session szesszió / private static $session; 3
/ Indításkor megnyitja a szessziót / private function construct () session_start(); / A singleton osztályt lekérő függvény / public static function get_instance () if (!isset (self::$session)) self::$session = new MVC_Session (); //ha nincs bejelentkezve a felhasznalo a user //erteke 0 if (!isset(self::$session->user_id)) self::$session->user_id=0; //a szesszio tatalmaz egy data tombot HTML kiirasok //megjegyzesehez if (!isset(self::$session->data)) self::$session->data = array(); return self::$session;... a kód folytatódik Webtechnológia gyakorlatok Figyeljük meg azt, hogy az osztály a PHP _set() és _get függvényét használja az adatok szesszióba való írására. / Változó kiolvasása @param string $n változó neve / public function get ($n) return $_SESSION[$n]; Így a szesszióba való írást az alábbi szintaxissal használhatjuk egy adott objektumból: $this->sess = MVC_Session::get_instance(); //szesszio nyitas $this->sess->user_id = 1; Az MVC_Module második referenciája egy MyDbiS nevű, adatbázis elérést biztosító osztályra mutat. A Dbi osztályt már ismerjük. A MyDbiS ezt terjeszti ki singleton típusúvá. Ez az osztály biztosítja a modul adatbázis elérését: 4
<?php / Adatbaziskapcsolat kezelő singleton osztaly @package Aux / class MyDbiS extends Dbi private static $singleton; protected function construct () parent:: construct (DBHOST, DBUSER, DBPASS,DBNAME); function destruct () public static function get_instance () if (! isset ( self::$singleton ) ) self::$singleton = new MyDbiS (); return self::$singleton; public function clone() die ('Ezt az osztályt nem lehet klónozni.');?> Az MVC_View is kierjeszt egy osztályt: az MVC_Object-et amelyik egy $data nevű tömböt és egy get_view_data() nevű függvényt tartalmaz. Ebbe az osztályba fogja az MVC_Module a kiírandó adatokat tárolni, egyszerűen név-érték párként. A get_view_data() függvényt fogja meghívni az MVC_View és így veszi át a kiírandó adatokat (láthatjuk, hogy ezt az osztályt - MVC_Object - más osztály is kiterjeszti). A View megvalósítása: MVC_View Az MVC_View osztály valósítja ezt meg. Ha a konstruktorát megnézzük: function construct ( $tpl_engine, $model ) //a megjelenites kulonbozo template osztalyokat //hasznalhat //ezeknek implementalniuk kell az assign es display //fuggvenyeket switch ( $tpl_engine ) 5
case 'regex' : $this->engine = new Template(); break; case 'smarty' : default : $this->engine = new Smarty(); $this->model = $model; $this->passive = array(); látható, hogy létrehoz egy engine nevű változót, amelyik egy sablon kezelőre tartalmaz hivatkozást. Ezen kívül elmenti a model változójába a modellre vonatkozó referenciát. A kiírást a View display() függvénye végzi: / A weblapot (index.tpl) kiíró függvény Az index.tpl 'content' nevű zónájába beírja a modell által generált HTML-t (a modell nevével azonos nevű sablont) utána kiírja a weblapot az Internetre / function display ( ) //a model sablonja, azonos nevu a model nevevel $template_file = strtolower ( $this->model->getname() ). ".tpl"; $s = $this->engine->fetch ( $template_file ); //ez az index.tpl content reszebe kerul $this->engine->assign('content', $s ); //! itt kód kimarad: lásd a teljes forrást //vegso kiiras $this->engine->display ( 'index.tpl' ); Az alkalmazás sablonja egy index.tpl nevű sablon, ebben egy $content nevű Smarty változó helyére ír be a View egy HTML-kódot: ez úgy áll össze, hogy a modell nevével azonos kis sablont a View feltölti a kiírandó adatokkal, és behelyettesíti a content helyébe. Ezt látjuk az előző függvényben. Az MVC osztályok még tartalmaznak un. passzív objektumokat, pl. reklámok megvalósítására. Ezeket az MVC_Passive osztály kiterjesztésével kell megvalósítani. Egy használati eset implementálása Vegyünk egy használati esetet, pl. a bejelentkezést. Ez három műveletet kell megvalósítson: 1. bejelentkező űrlap kiírása 2. űrlap fogadása és bejelentkezés, ha a név/jelszó jó 3. kijelentkezés 6
Ilyenként úgy fogjuk implementálni, hogy egy osztályon belül (legyen az osztály neve login) 3 metódust valósítunk meg (legyenek ezek start(), send() és leave() ). Ezeket, amint már láttuk az alábbi relatív URL-ekkel hívhatjuk: /index.php?page=login /index.php?page=login&go=send /index.php?page=login&go=leave A start metódust mindig meg kell valósítani egy MVC_Module-t kiterjesztő osztálynál. Ez akkor hívódik meg, ha nem adunk az URL-ben meg go paramétert. Így a start() az első, a send() a második míg a leave() a login harmadik műveletét valósítja majd meg. Megtervezzük a login eset kimenetét (a View által használt sablont), ennek tartalmaznia kell az első esetben kiírt űrlapot, és a két másik eset kimenetét: if $form == 1 <div align=center> <h3>bejelentkezés</h3> <p style="color:red;">$form_error</p> <form action="$siteuri/index.php?page=login&go=send" method="post"> <table> <tr><td> Email:</td><td><input type="text" name="email" value="$form_email" maxlength="100" size="30"> </td></tr><tr><td> Jelszó:</td><td> <input type="password" name="password" value="" size="30"> </td></tr> </table> <input type="submit" name="go" value="ok"> </form> <br> <p><i>teszt email: abc@de.ro, jelszó: cicamica</i></p> </div> else <p align="center">$form_ok</p> /if Látható, hogy két alternatívából áll (egyik az űrlap, a másik egy üzenet kiküldését biztosítja). A megírandó függvények feladata helyesen beállítani a sablon változóit, és megvalósítani a használati eset műveleteit. Íme a login.php osztály, a részletes magyarázatok elégségesek a működés megértéséhez: 7
<?php / Bejelentkezés modul Megvalósítja a be- és kijelentkezés eseteket @package Example / class Login extends MVC_Module / Konstruktor függvény A konstruktor false paramétere miatt ez az objektum nem védett oldalon fut le. Ha a felhasználó előzőleg már bejelentkezett akkor mielőtt indul kijelentkezteti. / function construct () parent:: construct( $protected = false); //nem vedett lap / Visszaadja az osztály nevét A View e szerint keresi meg a sablont ha más sablont kell használni, mint az osztály neve, akkor ezt a függvényt át kell írni / function getname () return CLASS ; / Indító függvény: ha a login modult 'go' paraméter nélkül hívjuk Elvégzi a bejelentkező űrlap kiírását a sablonból, a 'form' Smarty változó 1 értékére az űrlap íródik ki. / function start () if ( $this->auth() ) $this->data['form_error']='be volt jelenkezve. Kijelentkeztettük.'; $this->leave(); //kijlentkezteti $this->data['form']=1; //beallitom az ures div magassagat 8
$this->data['holder_height'] = HOLDER_HEIGHT; / A POST metódus kezelését valósítja meg Akkor hívódik meg ha a 'go' paraméter értéke 'send' / function send () $u= new User(); if (($_SERVER['REQUEST_METHOD'] == 'POST') && //ha POST $u->get_data_by_email ($_POST['email']) && //van ilyen felhasznalo $u->check_passwd($_post['password'])) //jo a jelszo //bejelentkezes sikerult, a user_id a szesszioba kerul $this->sess->user_id=$u->user_id; //nem irjuk ki az urlapot $this->data['form']=0; // $this->data['form_ok']='bejelentkezés sikerült.'; //ez az uzenet a jobb oldalon jelenik meg, es jelzi, hogy //a felhasznalo be van jelenkezve //mivel a szesszioba irjuk, ott marad addig amig a felhsznalo //kijelentkezik $this->sess->set_data ('right_msg', $u->name. ' ' ); else //beallitja az urlapon a hibauzenetet $this->data['form_error']='hibás email vagy rossz jelszó.'; //visszakuldi a beirt email cimet $this->data['form_email']=$_post['email']; //ez a valtozo allitja be az urlap kiirasat $this->data['form']=1; //beallitom az ures div magassagat $this->data['holder_height'] = HOLDER_HEIGHT; / A kijelentkezést valósítja meg / function leave () //ha be volt jelentkezve if ( $this->sess->user_id > 0 ) $this->sess->user_id=0; //mar nem irom ki a felhasznalo nevet $this->sess->set_data ('right_msg', ''); //uzenet az ablakban $this->data['form_ok']='kijelentkezés megtörtént.'; else 9
$this->data['form_ok']='ne csaljon, nem is volt bejelentkezve.'; //sablon form=0 ága $this->data['form']=0; //beallitom az ures div magassagat $this->data['holder_height'] = HOLDER_HEIGHT;?> Webtechnológia gyakorlatok A figyelemre méltó az MVC típusú alkalmazásokban az, hogy ha a keret működik, akkor az alkalmazás fejlesztés során az alábbiakat kell elvégezni: -meghatározzuk a használati eseteket -lebontjuk elemi web "műveletekre": HTTP kérések sorozatára -megtervezzük az eset kimenetét (view) minden kérésre -létrehozunk egy modellt megvalósító osztályt -annak függvényeiben megvalósítjuk a modell feladatait -a feladat elvégzése után/közben beállítjuk a kimenetet A legfontosabb: miközben egy használati esetet fejlesztünk, (viszonylag) függetlenek vagyunk az alkalmazás többi részétől. Rendelkezésünkre áll a szesszió objektum és az adatbázis elérés. Az MVC keret pedig stabil működést biztosít. A honlapon levő példa alkalmazás több kis minta használati eset implementálását tartalmazza itt: http://www.ms.sapientia.ro/~lszabo/webtechnologia/peldak/mvc/. 10