A leckében végignézzük, hogy példaalkalmazásunk némelyik funkciója hogyan oldható meg CodeIgniter keretrendszerben.
Minimális szinten a példaalkalmazás megértése várható el, ugyanakkor ennek mintáján meg kell tudnunk valósítani egyszerűbb feladatokat CodeIgniter segítségével.
A következőkben megmutatjuk, hogy a tananyag elején tervezett webes alkalmazásunk, egy képbemutatókat készítő és megjelenítő alkalmazás egyes részei a CodeIgniter keretrendszer segítségével hogyan valósíthatók meg. A tananyag eddig részletezett tudásával lehetővé van az oldal publikus funkcióinak (publikus bemutatók listázása, bemutató megtekintése, címke és felhasználó szerinti bemutatók listázása) és egy bejelentkezett felhasználóhoz tartozó bemutatókezelési funkciók (felhasználó bemutatóinak listázása, bemutató megtekintése, létrehozása, szerkesztése, törlése, bemutató képeinek kezelése) megvalósítására. Mivel a bejelentkezésről egyelőre nem volt még szó, ezért az ehhez tartozó funkciók hiányoznak, és a bejelentkezett felhasználónál gyakorlatilag egy beégetett azonosítót használunk (mindig az 1-est). Az így elkészült alkalmazás innen tölthető le:
Mivel az alkalmazással az MNV-mintát, illetve az arra épülő keretrendszerek kódstruktúrálási lehetőségeit szerettük volna bemutatni, így a CodeIgniter sokrétű többletlehetőségei közül nem használtunk ki mindent, a controllers, models és views mappa használata mellett csupán csak a config könyvtárban található konfigurációs állományokat módosítottuk.
Az alkalmazás indítása során egyetlen beállítást végeztünk el, a config/database.php állományban megadtuk az adatbázis kapcsolódási paramétereit. Az első öt paraméter beállítását végeztük el, ügyelve arra, hogy a mysqli bővítményt használjuk.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); $db['default']['hostname'] = 'localhost'; $db['default']['username'] = 'dyss'; $db['default']['password'] = 'jelszo'; $db['default']['database'] = 'dyss'; $db['default']['dbdriver'] = 'mysqli'; $db['default']['dbprefix'] = ''; $db['default']['pconnect'] = TRUE; $db['default']['db_debug'] = TRUE; $db['default']['cache_on'] = FALSE; $db['default']['cachedir'] = ''; $db['default']['char_set'] = 'utf8'; $db['default']['dbcollat'] = 'utf8_general_ci'; $db['default']['swap_pre'] = ''; $db['default']['autoinit'] = TRUE; $db['default']['stricton'] = FALSE; ?>
Ezzel az alapvető beállításokat el is végeztük. Következő lépésként már valamelyik funkció megvalósítására koncentrálhatunk.
Minden funkciónál először a nézettel kezdtük. A tervezés során megszülettek a képernyőképek, és ezek alapján az oldalunk általános elrendezését leíró HTML sablon és CSS stílusállományok, valamint a hozzá tartozó képek. Ezeket egy styles nevű könyvtárba rendezve az index.php (és az application mappa) mellé helyeztük, hogy publikusan közvetlenül elérhessük őket a böngészőből. A HTML sablon alapján bármelyik nézetnek neki lehet állni. Egy nézet elkészítésénél a legmódszeresebb megoldás az, ha először elkészítjük a megvalósítani kívánt oldalt statikus HTML-ben. Ekkor ez könnyen megtekinthető, szerkeszthető, nincs szükség vezérlő megírására hozzá. Az így elkészült állományt php kiterjesztéssel a views mappába kell másolni, majd a dinamikus részeket a megfelelő PHP kóddal kell helyettesíteni. A változók tartalmának kiírásához az echo parancsot, elágazásokhoz az if, ciklusokhoz a foreach utasítás ajánlott. Az átírás végére kialakulnak azok a változók, amikre a nézetnek szüksége van a megjelenítéshez. Érdemes már itt elgondolkozni azon, hogy minden esetben van-e értelme az adott változónak, minden esetben töltve lesz-e az értéke, és e szerint felkészíteni a kódot. Ez gyakorlatilag egy elágazást jelent, amelyben pl. az isset(), isnull() vagy további típuslekérdező függvénnyel vizsgálhatjuk meg egy változó jelenlétét.
A nézet elkészültével a modellel érdemes a fejlesztést folytatni. Tudjuk, hogy az adott folyamatnak milyen következményei lesznek az adatokra, a modellben ezt dolgozzuk ki, és adunk egy interfészt hozzá, amelyen keresztül majd a vezérlő kommunikál vele. Azért célszerűbb a modellt a vezérlő előtt kidolgoznunk, mert a modellben a konkrét kérés paramétereinél absztraktabban kell gondolkoznunk. A vezérlő feladat lesz majd az, hogy a modell számára a HTTP kérés adataiból előállítsa a megfelelő aktuális paramétereket.
A modellek száma és felépítése az alkalmazás jellegétől, méretétől, funkcióinak számától függ. A mi példaalkalmazásunk csupán egyetlen modell van, amely a bemutatók adatait szolgálja ki. Az állomány a models/bemutato_model.php állományban helyezkedik el. Olyan függvények találhatók ebben, amelyek adatokat szolgáltatnak a bemutatókról, képekről, címkékről, vagy az ezekhez kapcsolódó szerkesztési műveletek látják el. Vannak benne publikus függvények, amelyeket a vezérlő meghívhat, és vannak benne olyan privát függvények, amelyek egy-egy bonyolultabb funkció megoldását segítik elő.
A modellben demonstrációs célzattal (és néha egyszerűen praktikus okokból) kétféle módon állítjuk össze a megfelelő SQL utasításokat. Az első esetben mi magunk írjuk meg ezeket, és a $this->db->query() függvénnyel futtatjuk azokat. Az SQL utasításokat nem magában a függvényben tároljuk, ugyanis így azok könnyen áttekinthetetlenné válhatnak, hanem a modell elején egy privát tömbben definiáljuk őket, meghívásukkor pedig e tömb megfelelő elemét vesszük. Megvan ennek is a hátránya: hibakeresésnél két helyen kell nézni, hogy az adott funkció mit csinál. A másik esetben az SQL utasítások a CodeIgniter Active Record osztályának segítségével állnak össze. Ebben az esetben beszédes nevű, absztrakt függvények segítségével határozzuk meg, melyik oszlopokat, melyik táblából, milyen feltételek mellett szeretnénk lekérdezni, és ezek alapján a keretrendszer állítja elő a megfelelő SQL utasítást. Egyszerű lekérdezések esetén a kód így sokszor sokkal rövidebb és olvashatóbb marad, hosszabbaknál nem mindig éri meg használni. Olyankor is jól jöhetnek ezek, amikor eleve logika jelenik meg az SQL utasítás összerakásában (pl. csak akkor jelenjen a WHERE feltétel bizonyos része, ha egy másik változó valamilyen értékű). Ekkor mindenféleképpen az Active Record minta használata javasolt, hiszen éppen SQL generálására találták ki.
Az eredményeket a modell mindig asszociatív tömb formájában próbálja visszaadni (a result_array() függvény segítségével). Ennek az a prózai oka, hogy a lényegre koncentrálva próbáltuk minimalizálni az objektumok és osztályok megjelenését, így ahol a keretrendszer megengedte asszociatív tömböt használtunk ezek helyett. Ezzel nem szeretnénk azt mondani, hogy kerüljük az osztályok használatát, sőt éppen hogy az objektumorientált paradigma a korszerűbb és javallott, de módszertanilag úgy ítéljük meg, hogy a webadatbázis-programozással most ismerkedőknek egyszerűbb, ha ismerősebb adatszerkezetekkel dolgozunk.
A modellt befejezve térhetünk rá a vezérlő írására. A vezérlő feladata az, hogy a HTTP kérést feldolgozva kinyerje abból az adatokat, és ezeket átadja a modellnek, valamint az, hogy a nézet számára szükséges adatokat előkészítse és a nézetbeli változónevekhez rendelje őket. Ezért hagytuk a vezérlő megírását a végére, hiszen ekkorra már minden adatszükséglet kiderül. Egy alkalmazásban a vezérlők száma és felépítése megint csak sok mindentől függ. Mi az alkalmazás eddigi részét két vezérlőre bontottuk funkcióik szerint: a main vezérlő a publikus főoldal funkcióiért felelős, míg a bemutato vezérlő a bemutatók karbantartásáért felelős funkciókat tartalmazza. Általában egy funkcióért egy függvény felelős a vezérlőben.
A vezérlő konstruktorában helyeztük el a modell betöltését, hiszen erre mindegyik funkciónál szükség van. Ugyanilyen okokból kaptak itt helyet további helperek is. Ha egy modellre, helperre vagy egy könyvtárra mindig szükségünk van, akkor azt automatikusan is betöltethetjük a keretrendszerrel. Ehhez a config/autoload.php állomány szerkesztésére van szükség. Most az egyszerűség és az átláthatóság kedvéért az első megoldásnál maradtunk.
Ha a fenti munkafolyamatot követjük, akkor lépésről lépésre áll össze az alkalmazásunk. A következőkben néhány jellemző vagy fontosabb pontot nézünk meg az implementálásból.
A bejelentkezett felhasználó bemutatólistájának megjelenítése az egyszerűbb feladatok közé tartozik, és ezzel könnyen demonstrálhatjuk a fenti munkafolyamatot. A nézet a views/lista.php állományban található. Ennek egy részét az oldal elrendezése adja, a lényegi sablon az inner_content azonosítójú div-ben helyezkedik el.
<div id="inner_content"> <h2><?php echo $azonosito; ?> bemutatói</h2> <p><a href="<?php echo site_url('bemutato/uj'); ?>" class="button"><span>Új bemutató létrehozása</span></a></p> <ul class="lista"> <?php foreach ($bemutatok as $bemutato) : ?> <li> <div class="fo"> <a href="<?php echo site_url("main/bemutato/{$bemutato['id']}"); ?>"> <img src="<?php echo $bemutato['indexfajl']; ?>" /><br /> </a> <div class="info"> <h4><?php echo $bemutato['cim']; ?></h4> <p class="kisbetu"><?php echo $bemutato['leiras']; ?></p> <p class="kisbetu"> Megtekintések: <?php echo $bemutato['megtekintes_db']; ?><br /> Kedvencek: <?php echo $bemutato['kedvencek_szama']; ?><br /> <?php echo $bemutato['publikus'] ? 'Publikus' : 'Nem publikus'; ?><br /> </p> <p class="cimkek kisbetu">Címkék: </p> <ul class="cimkek kisbetu"> <?php foreach ($bemutato['cimkek'] as $cimke) : ?> <li> <?php echo anchor("main/cimke/{$cimke['cimke']}", $cimke['cimke']); ?> </li> <?php endforeach; ?> </ul> </div> </div> <div class="funkciok"> <ul> <li><?php echo anchor("main/bemutato/{$bemutato['id']}", 'Megtekint'); ?></li> <li><?php echo anchor("bemutato/szerkeszt/{$bemutato['id']}", 'Szerkeszt'); ?></li> <li><?php echo anchor("bemutato/torol/{$bemutato['id']}", 'Töröl'); ?></li> </ul> </div> </li> <?php endforeach; ?> </ul> <div class="separator"></div> </div>
Látható, hogy alapvetően két dinamikusan generálódó rész van, és ennek megfelelően két PHP változó jelenik meg benne. Az első a bejelentkezett felhasználó azonosítóját írja ki ($azonosito), a másik a bemutatokat írja ki egy ciklusban ($bemutatok). A cikluson belül pedig a tömb cikluson belüli aktuális értékével, egy bemutató adataival dolgozunk a $bemutato változó segítségével. A ciklus tartalmát alapvetően az oldalsablon határozta meg, csupán a megfelelő helyekre kellett az echo paranccsal dinamikus szöveget generálni. A nézetben arra is láthatunk példát, hogy különböző helper függvények hogyan segítenek URL-eket vagy hivatkozásokat generálni (site_url() és anchor() függvény).
A modellnek azt kell biztosítania a bemutatók megjelenítéséhez, hogy leválogatja és visszaadja az adott felhasználóhoz tartozó bemutatókat az adatbázis bemutato táblájából. A modell bemutato_lista() függvénye a bejelentkezett felhasználó azonosítóját várja paraméterül, és azzal hív meg egy paraméteres SQL utasítást. Az SQL utasítás az SQL tömb egyik eleme, és a bemutato táblához a kedvenc táblát is csatolja annak érdekében, hogy ki tudjuk írni azt, hányan jelölték be a bemutatót kedvencként.
<?php private $SQL = array( 'BEMUTATOK_FELHASZNALO_ID_SZERINT' => 'select b.*, count(k.bemutato_id) as kedvencek_szama from bemutato b left join kedvenc k on b.id = k.bemutato_id where b.felhasznalo_id = ? group by b.id order by kedvencek_szama desc', ); public function bemutato_lista($felhasznalo_id) { $result = $this->db->query( $this->SQL['BEMUTATOK_FELHASZNALO_ID_SZERINT'], array( $felhasznalo_id ) ) ->result_array(); $result = $this->cimkezes($result); return $result; } ?>
A lekérdezés lényegi része itt véget is ér általában. Ebben az esetben azonban még egy dolog hátravan: minden egyes bemutatóhoz lekérjük a hozzájuk tartozó címkéket tömb formájában, így ezeket a nézetben megfelelő módon egyesével tudjuk feldolgozni, megformálni. Ehhez a cimkezes() nevű privát függvényt hívtuk meg, amely végigmegy mindegyik lekérdezett bemutatón ($result tömb), és mindegyikhez plusz mezőként hozzárendeli a címketömböt a bemutato_cimkek() függvény segítségével. Ez utóbbiban a cimke és bemutato_cimke táblából kerülnek leválogatásra a címkék az átadott bemutatóazonosító ($bemutato_id) alapján.
<?php private $SQL = array( 'BEMUTATO_CIMKEK' => 'select c.id, c.cimke from cimke c, bemutato_cimke bc where c.id = bc.cimke_id and bc.bemutato_id = ?', ); private function cimkezes($result) { foreach ($result as &$bemutato) { $bemutato['cimkek'] = $this->bemutato_cimkek($bemutato['id']); } return $result; } private function bemutato_cimkek($bemutato_id) { return $this->db->query( $this->SQL['BEMUTATO_CIMKEK'], array( $bemutato_id ) ) ->result_array(); } ?>
A vezérlő a bejelentkezett felhasználó azonosítója ($felhasznalo_id) alapján a modellből lekéri a bemutatókat (a felhasználói azonosító egyelőre beégetve szerepel a kódban). Ezt követően beállítja a felhasználó belépési nevét (ez is beégetett), majd a nézet által előkészített két sablonhelyhez hozzárendeli őket, és megjeleníti a nézetet.
<?php public function lista() { //TODO A felhasznalo_id-t a sessionbol kell venni! $felhasznalo_id = 1; $bemutatok = $this->bemutato_model->bemutato_lista($felhasznalo_id); $azonosito = 'Bejelentkezés után tölteni kell!'; $this->load->view('lista', array( 'azonosito' => $azonosito, 'bemutatok' => $bemutatok, )); } ?>
A fent leírtakhoz hasonló elven működik az alkalmazásban rengeteg funkció, ahol adatlekérésre és megjelenítésre van szükség. Néhol kevés adatot kell a vezérlőnek összegyűjtenie, máshol viszont sokat. Ekkor a modellben több függvény is rendelkezésre áll az adatok kiszolgálása érdekében. Az összegyűjtött adatokat pedig egy komplexebb nézet jeleníti meg. Így működik többek között a főoldal, a bemutató megtekintése, a bemutatók címke és felhasználó szerint, egy bemutató adatai oldalak.
Egy bemutatói adatainak szerkesztése összetettebb probléma, mint adatok egyszerű megjelenítése. Ebben az esetben ugyanis meg kell jeleníteni a szerkesztendő bemutató adatait egy űrlapon, majd az űrlapról érkezett adatokkal frissíteni kell az adatbázis megfelelő rekordját. Van ebben tehát lekérdezés, űrlapfeldolgozás, hibakezelés egyaránt.
Ebben az esetben is a nézettel érdemes kezdeni a munkát. Az alábbi kódrészlet a sablon lényegi részét emeli ki:
<h3>Bemutató adatainak szerkesztése</h3> <?php if (validation_errors()) : ?> <div class="error"> <ul> <?php echo validation_errors('<li>', '</li>'); ?> </ul> </div> <?php endif; ?> <?php echo form_open("bemutato/bemutato_szerkeszt/{$bemutato_id}", array('class' => 'big')); ?> <dl> <dt><?php echo form_label('Cím', 'cim'); ?></dt> <dd><?php echo form_input(array('name' => 'cim', 'id' => 'cim', 'class' => 'text'), set_value('cim', $bemutato['cim'])); ?></dd> <dt><?php echo form_label('Leírás', 'leiras'); ?></dt> <dd><?php echo form_textarea(array('name' => 'leiras', 'id' => 'leiras', 'class' => 'text'), set_value('leiras', $bemutato['leiras'])); ?></dd> <dt><?php echo form_label('Publikus', 'Publikus'); ?></dt> <dd><?php echo form_checkbox(array('name' => 'publikus', 'id' => 'publikus'), 'publikus', set_checkbox('publikus', 'publikus', $bemutato['publikus'])); ?></dd> <dt><?php echo form_label('Címkék', 'cimkek'); ?></dt> <dd><?php echo form_input(array('name' => 'cimkek', 'id' => 'cimkek', 'class' => 'text'), set_value('cimkek', $bemutato['cimkeszoveg'])); ?></dd> <dd><?php echo form_submit(array('name' => 'szerkeszt', 'class' => 'button'), 'Bemutató adatainak módosítása'); ?></dd> </dl> <?php echo form_close(); ?>
Ebben egy tipikus űrlapmegjelenítés eszközei láthatók. Az űrlap generálásához a Form Helpert vettük igénybe, mert így nem kellett a HTML szintaxis felesleges elemeivel törődnünk, a lényegre koncentrálhattunk. Az űrlap definiálása a form_open() parancsnál kezdődik, amely az űrlap nyitóelemét generálja. Az űrlap az adatokat önmagának, azaz ugyanannak a vezérlőnek küldi, mint aki meghívta. Az űrlapon belül label és megfelelő beviteli mezők generálását láthatjuk (input, textarea, checkbox, submit). Minden inputmezőnél megadjuk a nevét, az azonosítóját és egy stílusosztályt a megfelelő formázáshoz. A form_input(), form_textarea() és form_checkbox() függvény utolsó paramétereként szükséges megadnunk az adott komponens értékét. Ide első körben közvetlenül beírhatnánk a $bemutato['valami'] értéket, ami az adott bemutató adatbázisból lekérdezett adatait tartalmazza. Ha ekkor megjelenítenénk az űrlapot, akkor azt tapasztalnánk, hogy az űrlap tökéletesen megjelenik, benne a kiválasztott bemutató adataival.
A többi kódrészlet arra való, hogy azt esetet kezelje, amikor a felhasználó hibásan tölti ki az űrlapot, és azt újra meg kell jeleníteni. A nézet elején a validation_errors() függvény az űrlap ellenőrzésekor keletkezett hibákat sorolja fel, minden hiba elé és mögé azt a szöveget rakva, amit paraméterekként megadtunk neki. A mi esetünkben most egy felsorolást generáltatunk vele.
Az űrlapmezőket generáló form_*() utolsó paramétereként megjelenő set_value() függvényekre azért van szükség, mert az űrlap újbóli megjelenítésekor nem az adatbázisbeli értéket szeretnénk újra az űrlapmezőbe generálni, hanem azt az értéket, amelyet a felhasználó töltött ki. A set_value() annak a felküldött mezőnek az értékét adja vissza, amit első paraméterben megadunk, vagy ha nincs ilyen, akkor a második paraméterként megadott értéket használja. Első megjelenítéskor nem lesznek ilyen POST-olt értékek, ezért a második paraméterként megadott adatbázisbeli értékek kerülnek megjelenítésre, viszont minden további megjelenítéskor már a felküldött értékeket használja a rendszer.
A validation_errors() és a set_value() függvénycsalád a Form Helper része.
A modell két függvénnyel biztosítja ezt a funkciót. Egyrészt megadja a kiválasztott bemutató adatait, másrészt elvégzi a bemutató adatainak módosítását az adatbázisban. Az első műveletet a bemutato_adatai() függvény végzi el.
<?php private $SQL = array( 'BEMUTATO_ID_SZERINT' => 'select b.*, f.azonosito, count(k.bemutato_id) as kedvencek_szama from bemutato b left join kedvenc k on b.id = k.bemutato_id inner join felhasznalo f on b.felhasznalo_id = f.id where b.id = ? group by b.id', ); public function bemutato_adatai($bemutato_id) { $result = $this->db->query( $this->SQL['BEMUTATO_ID_SZERINT'], array( $bemutato_id ) ) ->row_array(); $result['publikus'] = $result['publikus'] ? TRUE : FALSE; $result['cimkek'] = $this->bemutato_cimkek($result['id']); $cimketomb = array(); foreach ($result['cimkek'] as $cimke) { $cimketomb[] = $cimke['cimke']; } $result['cimkeszoveg'] = implode(', ', $cimketomb); return $result; } ?>
Először lekérdezzük a bemutató adatait a bemutato és kedvenc táblából, majd a publikus mező értékét logikai értékre képezzük le, végül lekérdezzük a bemutatóhoz tartozó címkéket is, amelyeket egy vesszővel elválasztott szövegként rakunk a bemutató adatai mellé.
A módosítás az egyik legösszetettebb művelet az alkalmazásban, ugyanis három táblát is érint. Ez lehetőséget ad a tranzakciókezelés demonstrálására is.
<?php public function modosit($bemutato_id, $cim, $leiras, $publikus, $cimkeszoveg) { $this->db->trans_start(); $this->db->update( 'bemutato', array( 'cim' => $cim, 'leiras' => $leiras, 'publikus' => $publikus, ), array( 'id' => $bemutato_id, ) ); $this->db->delete('bemutato_cimke', array( 'bemutato_id' => $bemutato_id, )); $cimkek = explode(',', $cimkeszoveg); foreach ($cimkek as $cimke) { $cimke = strtolower(trim($cimke)); $q = $this->db->get_where('cimke', array( 'cimke' => $cimke, )); if ($q->num_rows() > 0) { $sor = $q->row_array(); $cimke_id = $sor['id']; } else { $this->db->insert('cimke', array('cimke' => $cimke)); $cimke_id = $this->db->insert_id(); } $this->db->insert('bemutato_cimke', array( 'bemutato_id' => $bemutato_id, 'cimke_id' => $cimke_id, )); } return $this->db->trans_complete(); } ?>
Először elindítjuk a tranzakciót, majd ezt követően az update() függvényével módosítjuk a bemutató megfelelő adatait a paraméterként érkező értékekkel. Ezt követően a bemutato_cimke kapcsolótáblában az összes olyan bejegyzést töröljük, amely az adott bemutatóhoz tartozik. Majd végigmegyünk az újonnan kapott címkéken és mindegyikre megnézzük, szerepelt-e már a címke táblában. Ha igen, akkor megjegyezzük az azonosítóját. Ha nem szerepelt, akkor előbb beszúrjuk a cimke táblába, majd az új azonosítót eltároljuk. A címke azonosítóját aztán a bemutató azonosítójával egyetemben beszúrjuk a bemutato_cimke kapcsolótáblába. Ha minden hibátlanul megtörtént jóváhagyjuk a tranzakciót. Mindegyik adatbázis-műveletet a CodeIgniter kényelmes Active Record függvényeivel készíttettük.
A vezérlőnek egy űrlapfeldolgozáskor kicsit több feladata van, hiszen el kell döntenie, hogy olyan eset van-e, amikor először járunk az űrlapon és csak az adatbázisbeli értékeket kell megjeleníteni, vagy már küldtünk adatot és el kell végeznie a kliensről érkező adatok ellenőrzését, feldolgozását. Ez egy tipikus műveletet, és mint ilyen a CodeIgniter egy függvénykönyvtárat küld a felesleges vagy mechanikus részletek elkerülésére segítségül. Ez a Form_validation függvénykönyvtár, amely által az űrlapkezelés egy része paraméterezési kérdéssé válik. Nézzük meg a használatát az ide vágó vezérlő kódján keresztül.
<?php public function bemutato_szerkeszt($bemutato_id) { //TODO sessionbol toltendo $felhasznalo_id = 1; $this->load->helper('form'); $this->load->library('form_validation'); $szabalyok = array( array( 'field' => 'cim', 'label' => 'Cím', 'rules' => 'trim|required', ), array( 'field' => 'leiras', 'label' => 'Leírás', 'rules' => 'trim|required', ), array( 'field' => 'publikus', 'label' => 'Publikus', 'rules' => '', ), array( 'field' => 'cimkek', 'label' => 'Címkék', 'rules' => 'trim', ), ); $this->form_validation->set_rules($szabalyok); if ($this->form_validation->run() != FALSE) { $this->bemutato_model->modosit( $bemutato_id, set_value('cim'), set_value('leiras'), set_value('publikus') == 'publikus', set_value('cimkek') ); redirect("bemutato/szerkeszt/{$bemutato_id}"); } else { $bemutato = $this->bemutato_model->bemutato_adatai($bemutato_id); $this->load->view('bemutato_szerkeszt', array( 'bemutato_id' => $bemutato_id, 'bemutato' => $bemutato, )); } } ?>
A vezérlő paraméterként (azaz URL-ben megadva) kapja meg a szerkesztendő bemutató azonosítóját. A validációs függvénykönyvtár betöltése után első lépésként megadjuk, hogy milyen nevű mezőket várunk az űrlapról (field), hogyan jelenjen meg a hibaüzenetben (label), és ezeken milyen ellenőrzést kell elvégezni (rules). (Ezek leírása a CodeIgniter dokumentációjában részletesen megtalálható.) A set_rules() paranccsal adjuk meg ezeket az űrlapellenőrző modulnak. A run() metódust hamis értékkel tér vissza, ha nem talál felküldött adatokat, vagy ha a felküldött adatok között a megadott validációs szabályoknak megfelelően hibás értéket talál. Így ezzel a függvénnyel azt is el tudjuk dönteni, hogy először járunk-e az oldalon, vagy netalán már többedszerre. Ha először járunk vagy hibásak az adatok (else ág), akkor meg kell jelenítenünk az űrlapot, de előtte adatbázisból lekérdezzük a kiválasztott bemutatót. Ha azonban az adatok ellenőrzése során nem történt hiba, akkor a modell segítségével módosítja az adatbázist, és az oldalt átirányítja az adott bemutató adatait megjelenítő oldalra.
Új kép feltöltésekor újdonság lehet képek kezelése, a fájlfeltöltés is, de a mi alkalmazásunkban ezen kívül a képekből kétféle méretű bélyegképet is készítünk, amiket mind a fájlrendszerbe, mind az adatbázisban eltárolunk. Természetesen nincsen szükség mindkét helyen tárolni az adatokat, ezt csupán demonstrációs célból tesszük így.
A nézet a views/uj_kep.php fájlban található. Egy űrlapot tartalmaz, amelyen keresztül egy fájl feltöltésére adunk lehetőséget. Kivitelezése nagyon hasonló az előzőekben részletezetekkel, így ennek kódját itt most nem jelenítjük meg. Annyit érdemes megjegyezni, hogy a Form Helperben van egy függvény, amely a fájlfeltöltéshez szükséges űrlapnyitóelemet generálja, a form_open_multipart() függvény. A fájlfeltöltésért felelős mezőt a form_upload() függvény generálja.
Kicsit megszakítva az eddigi elemzési sorrendet, folytassuk a vezérlővel. A Form_validation függvénykönyvtárt itt nem érdemes használni, ugyanis csak egyetlen POST-olt adatunk van, a fájl maga, amit ezen a könyvtáron keresztül nem tudunk ellenőrizni. A fájllal ugyanakkor a meglétének és méretének ellenőrzésén túl még számos teendő lenne: megfelelő nevet adni neki, kijelölni hova mentsük, stb. Szerencsére a CodeIgniterben a fájlok kezelésére is kapunk egy kényelmes felületet a File_upload függvénykönyvtár személyében, amellyel a fájlfeltöltések kezelése paraméterezési kérdéssé alakul át.
<?php public function uj_kep($bemutato_id) { //TODO sessionbol toltendo $felhasznalo_id = 1; $this->load->helper('form'); $config = array( 'upload_path' => APPPATH.'fajlok/eredeti/', 'allowed_types' => 'jpg|png|gif', 'overwrite' => FALSE, 'encrypt_name' => TRUE, ); $this->load->library('upload', $config); $feltoltesi_hibak = null; if ( !$_FILES || ! $this->upload->do_upload('ujkep')) { $feltoltesi_hibak = $this->upload->display_errors('<li>', '</li>'); $bemutato = $this->bemutato_model->bemutato_adatai($bemutato_id); $this->load->view('uj_kep', array( 'bemutato_id' => $bemutato_id, 'bemutato' => $bemutato, 'feltoltesi_hibak' => $feltoltesi_hibak, )); } else { $feltoltesi_adatok = $this->upload->data(); $this->bemutato_model->feltolt_kep($bemutato_id, $feltoltesi_adatok); redirect("bemutato/szerkeszt/{$bemutato_id}"); } } ?>
A vezérlőben első lépésként felparaméterezzük a feltöltéseket kezelő modult ($config tömb). Ebben megadjuk, hogy milyen MIME típusokat fogadjon el, hova másolja a feltöltött fájlt, névegyezéskor ne írja felül a meglévőt, valamint azt, hogy generáljon neki valamilyen véletlenszerű nevet. A do_upload() paranccsal megpróbálja a rendszer a konfiguráció szerint végrehajtani a feldolgozást. Ha nem sikerül, akkor a display_errors() függvénnyel lekérdezzük a hibákat ($feltoltesi_hibak változóba, itt is felsorolásként), lekérjük a bemutató adatait, és megjelenítjük a nézetet. Akkor is így cselekszünk, ha nem volt fájlfeltöltés, azaz ha pl. először mentünk az oldalra. Ha azonban a feldolgozás sikeres volt, akkor lekérdezzük a data() paranccsal a feltöltött fájl adatait, majd ezekkel meghívjuk a modellt, és végül visszamegyünk az adott bemutató adatait megjelenítő oldalra (átirányítás).
A modell felelős a bélyegképek előállításáért és a fájlok adatbázisba mentéséért.
<?php private $SQL = array( 'UJ_KEP_SORSZAM' => 'select max(k.sorszam)+1 uj_sorszam from kep k where k.bemutato_id = ?', ); public function feltolt_kep($bemutato_id, $fajl_adatok) { $this->load->library('image_lib'); $nagy_fajlnev = $this->atmeretezes($fajl_adatok['full_path'], 'nagy'); $belyegkep_fajlnev = $this->atmeretezes($fajl_adatok['full_path'], 'kicsi'); $nagy_fajl = file_get_contents($nagy_fajlnev); $belyegkep_fajl = file_get_contents($belyegkep_fajlnev); $sorszam_result = $this->db->query( $this->SQL['UJ_KEP_SORSZAM'], array($bemutato_id) )->row_array(); $sorszam = $sorszam_result['uj_sorszam']; if (!$sorszam) { $sorszam = 1; } $result = $this->db->insert('kep', array( 'kep' => $nagy_fajl, 'kepfajl' => $nagy_fajlnev, 'belyegkep' => $belyegkep_fajl, 'belyegkepfajl' => $belyegkep_fajlnev, 'cim' => $fajl_adatok['client_name'], 'sorszam' => $sorszam, 'aktiv' => 1, 'bemutato_id' => $bemutato_id, 'mime_tipus' => $fajl_adatok['file_type'], )); return $result ? $this->db->insert_id() : FALSE; } ?>
Első lépésként előállítja a két bélygkép állományát az atmeretezes() privát függvénnyel. Sajnos a CodeIgniter adatbázis-elérési absztrakciós rétege nem képes a BLOB-ok kezelésére, így a tárolandó fájlokat egy-egy változóba olvassuk be (file_get_contents()). (Természetesen nem kötelező használnunk a CodeIgniter absztrakciós rétegét. A query() parancs helyett nyugodtan írhatunk natív mysqli vagy PDO kódot is. Az előbbi esetben a kapcsolatazonosítót a $conn = $this->db->conn_id; paranccsal szerezhetjük meg.) Új sorszám generálása után, ami egy újabb adatbázisművelet, az adatokat beszúrjuk a kep táblába.
Az átméretezéshez szerencsére megint csak egy hasznos kis CodeIgniter könyvtár áll rendelkezésünkre, ami a feldolgozást megint csak paraméterezéssé egyszerűsíti, ez pedig az Image Manipulation Library.
<?php private function atmeretezes($forras_fajlnev, $meret) { $this->image_lib->clear(); switch ($meret) { case 'nagy': $width = 1024; $height = 768; break; case 'kicsi': $width = 100; $height = 67; break; } $utvonal = APPPATH . 'fajlok/' . $meret . '/'; $belyegkep_fajlnev = $utvonal . basename($forras_fajlnev); $config = array( 'image_library' => 'gd2', 'source_image' => $forras_fajlnev, 'new_image' => $belyegkep_fajlnev, 'maintain_ratio' => TRUE, 'width' => $width, 'height' => $height, ); $this->image_lib->initialize($config); if ($this->image_lib->resize()) { return $belyegkep_fajlnev; } return FALSE; } ?>
Első lépésként beállítjuk az átméretezés paramétereit: melyik képszerkesztő függvénykönyvtárat használja, hol található a forráskép, hova és milyen néven generálja az új képet, megtartsa-e átméretezés közben az oldal hosszarányait, mi legyen az új szélesség és magasság. Ezt követően a konfigurációs paramétereket betöltve (initialize()) a képet átméretezzük (resize()), minden ezzel járó bonyodalmat a keretrendszerre bízva.
Az alkalmazásban vannak olyan funkciók, amelyeknek nincsen nézete, csak egy vezérlőből és egy modellből állnak. Ilyen például többek között a kedvenccé tétel, vagy ennek visszavétele, a képek előre-, hátramozgatása. Az ilyen jellegű feladatok közül a bemutató törlését emeljük ki.
Ezt a funkciót tárolt eljárással oldottuk meg, adatbázisoldalra bízva az ezzel kapcsolatos többlépéses folyamatot. A modellben egyetlen újdonság a tárolt eljárás hívása, amely tulajdonképpen egy speciális SQL utasítás (call), és amelyet a query() parancson keresztül hívhatunk meg. A megvalósításban láthatjuk, hogy hogyan adjuk át a törlendő bemutató azonosítóját.
<?php public function bemutato_torol($bemutato_id) { return $this->db->query( 'CALL sp_bemutato_torlese(?)', array( $bemutato_id )); } ?>
A vezérlő ezekben az esetekben nagyon egyszerűen néz ki. A paraméterek begyűjtése és a modell meghívása után általában átirányít valamelyik oldalra, ahogy ez a bemutató törlésénél is történik.
<?php public function torol($bemutato_id) { $felhasznalo_id = 1; //TODO sessionbol a felh_id $this->bemutato_model->bemutato_torol($bemutato_id); redirect("bemutato/lista/{$felhasznalo_id}"); } ?>
Érdemes az egész alkalmazást átnézni olyan módon, ahogy fentebb tettük. Megjelenítve egy oldalt, vagy csak a nézeteket böngészve, megnézhetjük, hogy egy adott hivatkozás melyik vezérlőnek melyik metódusát hívja. A vezérlőből pedig látjuk, hogy az melyik modellnek melyik függvényére támaszkodik, és melyik nézetet hívja meg végül. A vezérlők, modellek szinte mindegyike hasonló sablonra épül, így a fent kiemeltektől eltérő logikával ritkán találkozhatunk.