Pse një Framework? Kjo është pyetja që natyrisht mund t’ju ketë lindur kur keni parë titullin e guidës. Do i përgjigjem kësaj pyetje, por realisht nuk është ajo pjesa e rëndësishme. Ajo që do tentoj me këtë guidë është t’ju jap një ide të përgjithshme se si mund të ndërtoni skeletin e nje Framework-u dhe më e rëndësishmja, si të punoni me objekte. Duke qenë se do të përdor një sërë objektesh me përgjegjësi të ndryshme dhe disa veçori të PHP-së në punën me to, jam i sigurt që guida do t’ju shërbejë në këtë aspekt. Qëllimi dhe tipi i aplikacionit në këtë rast janë elementë dytësore.
Përpara se të filloj me guidën, me duhet t’ju paralajmëroj. Ky Framework nuk është një zgjidhje e gatshme! Vërtetë mund të realizoni ndonjë faqe të shpejtë me të, por e sigurtë është që pothuajse aq shpejt mund ta realizoni pa Framework. Kini parasysh që i gjithë kodi i shkruar, aplikacioni demo dhe vetë guida janë realizuar brenda vetëm pak ditëve. Shumë zgjedhje janë bërë për arsye shpejtësie ose për ta mbajtur guidën në një nivel të aksesueshëm nga të gjithë, edhe ata me pak eksperiencë me objektet apo përgjithësisht në PHP. Gjëja e fundit që dua të bëj është t’ju jap informacion të gabuar dhe sidomos, praktikta të pa këshilluara programimi. Konsiderojeni këtë guidë si një mësim për punimin me objekte dhe idenë bazë të ndërtimit të një Framework-u, jo si një zgjidhje copy-paste.
Që tani e tutje, termit Framework do i referohem me Librari (me L kapitale). Është e vështirë të përdorësh fjalë Anglisht në kontekst Shqip me prapashtesat që gjuha jonë ka.
Çfarë është një Librari
Jam i sigurt që ata me fare pak eksperiencë në PHP, të paktën kanë dëgjuar për Librari. Sidoqoftë, le të hedhim pak dritë.
Një Librari është një shtresë abstrakte që vendoset mbi një gjuhë programimi për të thjeshtësuar përgjithësisht kodimin dhe në varësi të rastit, për të përmirësuar në një farë mënyre arkitekturën e gjuhës. Libraritë në PHP nisën të marrin moment me popullarizimin e Ruby On Rails, një Librari web për Ruby që i vendos rëndësi objekteve dhe kodimit të shpejtë. Në një këndvështrim pak të ngushtë, Libraritë për PHP janë të inspiruara nga RoR.
Modeli MVC (Model View Controller) është bërë standarti de-facto për Libraritë dhe kjo jo pa qëllim. E gjithë ideja e kodimit me objekte është ndarja e përgjegjësisë – sa më pak përgjegjësi një objekt, aq më fleksibël dhe i ripërdorshëm bëhet, por gjithashtu aq më e lehtë është të testohet. Nuk është konteksti të shpjegoj çfarë është MVC në terma të arkitekture (programimi), sepse na intereson çfarë bën në PHP. Në thelb, aplikacionet e koduara sipas MVC ndahen në 3 nivele: Modele – Thirrjet në databazë, View – HTML dhe Controller – Ndërmjetësi që thërret modelet, merr hyrjet nga vizitorët dhe i bën gati të shfaqen në faqe. Me këtë sistem, ndahet marrja e të dhënave, prezantimi dhe logjika e aplikacionit.
Krahas përmirësimit të arkitekturës duke “imponuar” (në kuptim të mirë) design patterns të testuara, Libraritë ofrojnë dhjetëra apo qindra funksione të gatshme për të kryer veprime nga ato bazë, deri në tërësisht specifike. Ajo që ofrohet varet shumë nga Libraria, por nëse i hidhni një sy Librarive popullore, do shihni që ndajnë shumë të përbashkëta më njëra tjetrën. Fundja, mjedisi ky është dhe nuk ka shumë për t’ju devijuar modelit bazë.
Pse një Librari e bërë vetë?
Pyetje me vend! Në fakt, duhet të ketë me dhjetëra Librari për PHP dhe disa nga to janë vërtetë të stabilizuara. Nëse përmend CodeIgniter, Zend Framework, CakePHP apo Symfony, flasim për komunitete të tëra që punojnë për to, i testojnë dhe sigurohen që të funksionojnë si ç’pritet në mjedise të ndryshme. Pra, pse një Librari e bërë vetë? Ka disa arsye…
Ashtu si shumë sisteme që tashmë janë publike, edhe Libraritë për PHP me siguri kanë nisur si projekte personale të një personi apo një grupi të vogël. Me përdorimin e tyre në projekte konkrete, kanë shtuar funksionalitete pa fund dhe e kanë modifikuar/përmirësuar vazhdimisht. Në një moment të caktuar kanë vendosur ta lëshojnë publikisht, për ti bërë përmirësimet dhe shtimet akoma më të shpeshta. Rezultati? Një Librari solide, e testuar dhe që përmirësohet shpesh, por me një kod masiv.
Për kuriozitetin tim personal dhe për efekt argumenti për këtë guidë, numërova (sigurisht, jo manualisht!) rreshtat e të gjithë skedarëve në Libraritë më popullore. Rezultatet sigurisht nuk janë 100% të sakta, sepse mund të ketë skedarë pa lidhje me kodin aty brenda, por marxhina e gabimit duhet të jetë e ulët dhe më e rëndësishmja, ju jep një referencë të mirë. Do habiteni! CodeIgniter ka numrin më të vogël të rreshtave me: 49.633. CakePHP vjen i dyti me 5-fish më tepër: 206.508. Symfony e treta me: 287.640. Zend Framework vjen me një numër seriozisht të frikshëm: 708.306. 700 mijë rreshta kod? Në momentin që Libraria i afrohet numrit të rreshtave të interpretuesit (Zend Engine) apo Serverit Web që e egzekuton, ka diçka që nuk shkon! Kjo s’i bën absolutisht të papërdorshme apo zgjedhje të këqija, thjeshtë limiton qëllimin e përdorimit.
Personalisht, kam eksperiencë me çdo lloj tipi kodimi në PHP. Kam koduar kod spageti, me objekte, me Librari, me ekip, pa ekip dhe ku di unë. Nëse rikujtoj mënyrat si kam punuar, seriozisht i këshilloj Libraritë në projekte të mëdha apo kur punohet në ekip. Të vendosësh standarte kodimi nuk bën fare dëm, përkundrazi siguron që i gjithë ekipi ta shkruajë kodin njësoj. Plus, mund ta zgjeroni Librarinë me module që ju përshtaten dhe do keni një kod vërtetë solid. Edhe zgjedhja e Librarisë është e rëndësishme! ZendFramework është një monolith kodi që ka një kurbë goxha të thepisur mësimi. Vërtetë e bën punën mirë, po nuk është për çdo projekt; vetë qëllimi i Zend është ta fusë PHP-në në industrinë Enterprise përmes ZF. Ndërkohë, CodeIgniter është një Librari shumë më e lehtë, që vihet në punë brenda pak minutave dhe do jeni duke koduar aplikacione me të brenda orës.
Nga ana tjetër, nëse kodoni i vetëm për faqe apo aplikacione relativisht të vogla, një Librari e gatshme mund t’ju sjellë më tepër dëme se sa përfitime. Llogarisni që kanë një kurbë mësimi të caktuar, përfshijnë funksionalitete nga të cilat 70% mund të mos i përdorni kurrë dhe përgjithësisht kanë ngarkesë ekstra mbi serverin.
Nëse tentoni të bëni një Librari të vogël vetë, mund ta zgjeroni gradualisht me funksionet që ju duhen, e njihni në majë të gishtave sepse e keni shkruar vetë dhe më e rëndësishmja, do keni mësuar aq shumë gjatë proçesit sa e justifikon të tërë punën.
Si funksionon Libraria në fjalë
Më sipër ju shpjegova modelin MVC. Për hir të guidës, e thjeshtësova idenë dhe e bëra Librarinë thjeshtë VC – pra me Controller dhe Views. Duke hequr Modelet, miksoj në Controller logjikën me të dhënat. Nuk është ndonjë humbje e madhe sidoqoftë për një Librari kaq të vogël. Për më tepër, jam i sigurt që do të dini ti shtoni vetë kur të keni mbaruar me guidën.
Libraria fuqizohet nga disa klasa bazë që ofrojnë funksionalitet kryesore. Klasat bazë janë:
- Loader – Ruan dhe servir objektet globalisht
- Router – Përfshin kontrolluesit dhe metodat e tyre në bazë të URL-së
- DB – Ndërfaqe e databazës
- Template – Ngarkon, zëvendëson pseudo-variablat dhe servir html-në
- Error – Jo tamam një klasë (thjeshtë një funksion) që gjeneron mesazhe gabimi
Pra, si ç’e shihni janë vetëm pak klasa, thjeshtë për të ofruar funksionalitetet bazë. Se ç’bën secila do ja u tregoj me detaje në vazhdim. Tani për tani ju mjafton të krijoni idenë.
Veprimet në Librari janë të ndara në klasa të ndryshme, por në thelb, gjithçka egzekutohet brenda një skedari me emrin index.php. Aty përfshihen klasat, konfigurimi, përcaktohen kontrolluesit, etj. Aplikacioni vetë ruhet në një direktori të quajtur “app” ku mbahen kontrolluesit, pamjet (views) dhe asetet (css, js, imazhe, etj). Si ç’ju thashë më sipër, një kontrollues merret me logjikën (dhe të dhënat e databazës meqë s’përdorim Model), ndërsa një view merret me paraqitjen. E gjithë ideja është për kontrolluesit të ketë një mënyrë të thjeshtë dhe automatike egzekutimi, pa bërë punë manuale. Kjo bëhet pikërisht përmes URL-së dhe Router.
URL-ja ka një format të caktuar që duhet të ndjekë për ta bërë sistemin të funksionojë. Ju thashë që Libraria e egzekuton gjithçka në një skedar index.php. Pra, kur hap indeksin e Librarisë, shkruaj faqja.com/index.php. Ajo që shkruaj pas index.php në URL është me rëndësi vitale për Librarinë sepse do të jenë parametrat e ngarkimit të Kontrolluesëve. Nëse hap faqen faqja.com/index.php/perdoruesit/, automatikisht Libraria do të ngarkojë një klasë Kontrolluesi që e ka emrin “Perdoruesit” dhe do të egzekutojë metodën home() të tij – kjo e fundit është një zgjedhje e imja dhe të gjithë kontrolluesit duhet të kenë një metodë home(). Ta zëmë se me /perdoruesit/ shfaq listën e përdoruesëve, por me /perdoruesit/shfaq/1/ dua të shfaq nga databaza përdoruesin me ID=1. Mjafton që në klasën e Kontrolluesit “Perdoruesit” të shtoj një metodë shfaq(), të marr ID-në nga UR, të marr të dhënat nga databaza dhe në fund ti vendos ato të dhëna në shabllone (template). Duket e komplikuar? Ju siguroj që s’është dhe nëse keni punuar ndonjëherë me Librari, kjo është një strukturë mjaft standarte. Sidoqoftë, do fillojnë të marrin kuptim gjërat gradualisht kur t’ju tregoj kodet për secilen klasë dhe në fund shembuj nga një faqe e vogël e gjeneruar me këtë Librari. Oh, gjithashtu index.php/ mund të fshihet shumë lehtë me mod_rewrite, gjë që unë e kam bërë në kodin e gatshëm për shkarkim. Pra, faqja.com/index.php/perdoruesit/shfaq/1/ transformohet në faqja.com/perdoruesit/shfaq/1/. Më bukur dhe më pastër pa atë index.php.
Krahas Router-it që vendos në punë Kontrolluesit, po aq të rëndësishme janë edhe të tjerat. Klasa Loader bën të mundur aksesin global të të gjitha objekteve, pa pasur nevojë të deklarohen çdo herë. Klasa DB ofron një ndërfaqe të thjeshtë ndaj databazës në mënyrë që mos të përdoren funksione direkte të një RDBMS, por metoda personale. Psh, në vend të mysql_query() shkruaj $db->query(). Klasa Template merr HTML-në e Views dhe e paraqit në faqe. Bën pak më tepër se aq në fakt, sepse kam futur opsione si: pseudo-variabla, includes dhe pseudo-konstante.
Së fundmi, diçka që i përket kodit dhe vazhdimit të guidës. I gjithë kodi është në Anglisht, sepse e kam të vështirë të miksoj Shqip (emra variablash, funksionesh dhe klasash) me Anglisht (konstruktet e gjuhës) në kode të gjata. Ju kërkoj ndjesë për këtë. Por, kushdo me njohuri fare bazë të Anglishtes do e kuptojë pa problem sepse fjalët e përdorura nuk jan të komplikuara. Kuptohet që komentet janë të gjitha në Shqip dhe të detajuara për çdo rresht, kështu që gjatë guidës do ju tregoj vetëm çfarë bën klasa në vija të përgjithshme dhe mënyra si mund ta përmirësoni.
Le të fillojmë!
Çfarë duhet për të përdorur Librarinë
Jo shumë në fakt. Mjafton një server tipik me Apache, PHP dhe MySQL. Do të duhet PHP 5 (sa më i lartë nën-versioni, aq më mirë), gjë që tashmë është bërë standart, edhe në hostet shared më të humbur. Nevojitet gjithashtu që Apache të ketë të aktivizuar modulin mod_rewrite dhe serveri të suportojë skedarë .htaccess për të rishkruar URL-të. Nëse nuk i suporton, do detyroheni të mbani pjesën “index.php” në URL.
Konfigurimi
Libraria përdor një skedar të quajtur “config.php” ku ruhen pak konstante që do të përdoren nëpër të gjithë sistemin: URI ku ndodhet Libraria, tipet e gabimeve që do të mund të gjenerohen dhe të dhënat e databazës. I kam ruajtur si konstante që mos të jetë e mundur të ndryshohen më pas, por edhe pse, nga ana vizuale kodi më ngjan më mirë. Shpesh herë, në raste të tilla përdoren edhe vektorë në formën: $conf['PATH']. Personalisht, jam përkrahës i konstanteve.
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | /* |
| 03 | Vendos nese duhet te aktivizohet DEBUG apo jo. Perdoret per raportimin |
| 04 | e gabimeve ne funksionin ErrorHandler() ne core/error.php. |
| 05 | |
| 06 | Vendose ne 0 nese aplikacioni ndodhet ne server produksioni. |
| 07 | */ |
| 08 | define('DEBUG', 1); |
| 09 | |
| 10 | //URI-ja e aplikacionit |
| 11 | define('PATH', 'http://www.faqja.com/'); |
| 12 | |
| 13 | //Konstante per tipet e gabimeve |
| 14 | define('FATAL', E_USER_ERROR); |
| 15 | define('ERROR', E_USER_WARNING); |
| 16 | define('WARNING', E_USER_NOTICE); |
| 17 | |
| 18 | //Te dhenat e serverit MySQL dhe emri i databazes |
| 19 | define('DB_HOST', 'localhost'); |
| 20 | define('DB_USER', 'root'); |
| 21 | define('DB_PASS', ''); |
| 22 | define('DB_NAME', 'framework'); |
| 23 | ?> |
Klasa Loader
Loader është një klasë e dedikuar për ngarkimin dinamik të objekteve, duke lehtësuar përdorimin e objekteve të tjerë brenda një klase. Praktikisht, quajeni një vend ku ruhen objekte të deklaruar dhe serviren nëpër aplikacion sa herë që kërkohen, pa pasur nevojën e deklarimit të tyre sërish. Është një lehtësim që i kam ofruar vetes, edhe pse realisht në një Librari kaq të vogël, mund t’ja dilja shumë mirë Loader.
Në thelb, Loader-i është një Design Pattern e quajtur Registry. Mban 2 metoda statike, store() dhe load(), njëra për të ruajtur objektet e deklaruar dhe tjetra për ti servirur ato. Çfarë janë metodat statike? Lexoni më poshtë.
Për të shpjeguar çfarë janë metodat statike, më duhet t’ju shpjegoj konceptin Klasë vs Objekt. Ndryshimi është vetëm deklarimi – i vogël por themelor. Një objekt është instanca e një klase pasi është deklaruar (në PHP me fjalën kyçe “new”). Në momentin që klasa kthehet në objekt, të gjitha metodat dhe variablat (properties) e saj bëhen të aksesueshme. Nga ana tjetër, metodat statike deklarojnë funksione që mund të përdoren direkt në nivel klase, pa e deklaruar atë. E mira këtu është se metodat statike mund të aksesohen direkt nga klasa të tjera. Në vend të selektorit të objekteve “->”, për metodat statike përdoren “::”. Psh, për Loader-in do kishim “Loader::load()”, cila do të thërriste metodën load(). E njëjta gjë vlen edhe për variablat statike në klasa.
Ajo që unë s’kam bërë në klasën Loader është implementimi i Singleton (për ta bërë RegistrySingleton) që do më siguronte që të merrja vetëm një instancë të objekteve. Jo se përtoja, por sepse është një teknikë që po konsiderohet anti-pattern dhe përgjithësisht është e shëmtuar. Në fakt, edhe vetë Registry po konsiderohet anti-pattern, sepse aksesi global i klasave rrallë është gjë e mirë. Një design pattern që e respekton filozofinë e punës me objekte është Dependency Injector, por që do e rriste nivelin e kompleksitetit në kod dhe “target-grupin” e personave që i drejtohet kjo guidë.
Kodi i klasës Loader:
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | class Loader { |
| 03 | |
| 04 | /* |
| 05 | Variabla statike qe do te mbaje listen e objekteve. Do te jete |
| 06 | kjo variabel qe nga funksionet me poshte ruan dhe ve ne perdorim |
| 07 | objektet. |
| 08 | */ |
| 09 | private static $objects = array(); |
| 10 | |
| 11 | /* |
| 12 | I kam bere __construct() dhe __clone() privat ne menyre qe te |
| 13 | ndaloj therritjen direkte te objektit apo klonimin e saj. Klasa |
| 14 | do te perdoret vetem per 2 metodat store() dhe load(), thirrjet |
| 15 | ndaj te cilave do te jete direkte, ne nivel klase, jo si objekt. |
| 16 | */ |
| 17 | private function __construct () {} |
| 18 | private function __clone () {} |
| 19 | |
| 20 | /* |
| 21 | Funksioni statik qe do te ruaje objektet dhe do ti nise ato. |
| 22 | Parametrat s'jane te paracaktuar, por dinamike. Ne kuptimin qe |
| 23 | funksioni mund te therritet si: store('db') ose store('db', 'router', 'template'). |
| 24 | */ |
| 25 | public static function store () { |
| 26 | /* |
| 27 | func_get_args() kthen numrin e parametrave te vendosura ne |
| 28 | funksion. Nese ka te pakten nje parameter, blloku egzekutohet. |
| 29 | */ |
| 30 | if (func_get_args()) { |
| 31 | /* |
| 32 | func_get_args() kthen nje vektor me te gjithe parametrat |
| 33 | dinamike qe jane percaktuar gjate therritjes se funksionit. |
| 34 | Nese perdoruesi ka shkruar: |
| 35 | |
| 36 | [ Loader::store('db', 'router'); ] |
| 37 | |
| 38 | func_get_args() do te ktheje nje vektor qe duket si: |
| 39 | |
| 40 | [ array('db', 'router'); ] |
| 41 | */ |
| 42 | $params = func_get_args(); |
| 43 | |
| 44 | /* |
| 45 | Bej nje lak (loop) ne parametra, ku supozohet qe cdo parameter |
| 46 | te jete nje emer klase. Variables $objects i shtoj nje vlere te re |
| 47 | ku ndodhet klasa e nisur (me: new) dhe si celese vete emri i klases. |
| 48 | Ne kete forme, kur te shkruaj: Loader::store('db', 'router'); variabla |
| 49 | $objects do nise klasat me te njejtat emra dhe perseri me te njejtat emra |
| 50 | do te kem mundesi ti ngarkoj me: Loader::load('db', 'router'). Ky i fundit |
| 51 | shpjegohet me poshte. |
| 52 | */ |
| 53 | foreach ($params as $param) { |
| 54 | self::$objects[$param] = new $param; |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | /* |
| 60 | Funksioni statik qe ngarkon objektet dhe i ben te gatshem per perdorim. |
| 61 | Ideja ketu eshte shume e ngjashme me funksionin store(). |
| 62 | */ |
| 63 | public static function load () { |
| 64 | if (func_get_args()) { |
| 65 | $params = func_get_args(); |
| 66 | $output = array(); |
| 67 | $last_param; |
| 68 | |
| 69 | /* |
| 70 | Bej nje lak per te gjitha parametrat dhe per secilin kthej objektin |
| 71 | e ruajtur ne variablen $objects. Funksioni array_key_exists() kontrollon |
| 72 | nese emri i objektit te percaktuar egziston si celes ne variablen $objects. |
| 73 | */ |
| 74 | foreach ($params as $param) { |
| 75 | if (array_key_exists($param, self::$objects)) { |
| 76 | $output[$param] = self::$objects[$param]; |
| 77 | |
| 78 | /* |
| 79 | Ruaj parametrin e fundit te suksesshem |
| 80 | */ |
| 81 | $last_param = $param; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | /* |
| 86 | Nese vektori $output ka vetem nje rezultat, atehere nuk kthej nje vektor me objekte, |
| 87 | por nje vektor te vetem. Perdora nje variabel qe ruan parametrin e fundit ($last_param) |
| 88 | per ta pasur te lehte thirrjen e vektorit me nje celes specifik. |
| 89 | */ |
| 90 | if (count($output) == 1) { |
| 91 | $output = $output[$last_param]; |
| 92 | } |
| 93 | |
| 94 | return $output; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | } |
| 99 | ?> |
Klasa Router
Router-i është ai që përcakton çfarë Kontrolluesi do të aktivizohet në bazë të parametrave të URL-së. Ideja është fare e thjeshtë. Çdo Kontrollues ruan një uniformitet me emrin e klasës, skedarin ku ndodhet dhe parametrin në URL. Le të themi që na nevojitet një Kontrollues që do të kryejë veprimet e shportës. Krijojmë një skedar nën “app/controller/” me emrin “shporta.php“. Brenda tij, krijojmë një klasë me emrin “Controller_Shporta“, ku pjesa “Controller_” është standart i detyrueshëm për të gjithë kontrolluesit. Në klasë krijomë 2 metoda, njëra home() dhe tjetra boshatis(). Me këtë strukturë tipike, Router-i do të bëjë punën e tij.
Nëse hapim adresën “index.php/shporta/“, automatikisht Router-i do të shikojë nëse egziston një Kontrollues me emrin “Controller_Shporta“, do ta deklarojë atë dhe do të thërrasë metodën primare home(). Kjo e fundit është e detyrueshme për çdo Kontrollues, duke qenë se egzekutohet në momentin që Kontrolluesi thërritet pa metodë. Ajo që do na duhet të bëjmë është të ofrojmë një faqe që boshatis shportën, prandaj edhe krijuam metodën boshatis(). Mjafton të vendosim parametrin e dytë në URL, duke e bërë atë “index.php/shporta/boshatis/” dhe automatikisht Router-i egzekuton metodën boashtis() të Kontrolluesit “Controller_Shporta“. Ka kuptim besoj?!
Si ç’e keni kuptuar tashmë, parametri i parë në URL përcakton Kontrolluesin, ndërsa parametri i dytë përcakton metodën që duhet të egzekutohet. Në këtë formë kam një sistem dinamik dhe me URL të bukura. Edhe nga ana kodit është mjaft e thjeshtë. Merr URL-në aktuale, e ndan në copeza në bazë të karakterit “/” dhe deklaron objektin së bashku me metodën përkatëse. Mjafton që të ndiqet e njëjta nomeklaturë dhe gjithçka funksionon pa probleme.
Si mund të përmirësohet Router-i? Në thelb, punën e bën si ç’duhet, por ka vend për përmirësime në logjikë. Mund të përdoret fare mirë REST, një teknikë që përdor metodat e kërkesave të protokollit HTTP dhe që gjen përdorim masiv në Web Services. Në po të njëjtën ide mund të përdoret për të instruktuar shfletuesin se çfarë lloj kërkese po dërgohet, në mënyrë që të kenë kuptim semantik dhe të jenë logjikisht të ndara. REST shfrytëzon metodat: GET, POST, PUT dhe DELETE. GET mund të përdoret për të shfaqur të dhëna; POST për të futur të dhëna të reja; PUT për të ndryshuar të dhëna dhe DELETE për të fshirë të dhëna. Kuptohet, kërkon më tepër punë dhe përgjithësisht e komplikon Router-in, por në fund do të keni një sistem solid që shfrytëzon HTTP si ç’duhet dhe me kuptim, ku secili veprim (Shto, Shfaq, Ndrysho dhe Fshi) përdor një metodë të caktuar të HTTP. Në fund mund ti shtoni kapje gabimesh për të servirur faqe me header 404 nëse një Kontrollues apo metodë e tij nuk egziston.
Kodi i klasës Router
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | class Router { |
| 03 | |
| 04 | /* |
| 05 | Variabel globale e klases ku do te ruhet adresa e faqes |
| 06 | */ |
| 07 | private $url = ''; |
| 08 | |
| 09 | /* |
| 10 | Funksion qe merr adresen e faqes ($_SERVER['PHP_SELF']) dhe e perpunon |
| 11 | per te nxjerre gjithcka qe ndodhet pas index.php. Pra, nese kemi adresen: |
| 12 | |
| 13 | [ http://www.faqja.com/index.php/perdoruesit/shfaq/ ] |
| 14 | |
| 15 | variabla $url do te manipulohet qe te mbetet vetem: |
| 16 | |
| 17 | [ perdoruesit/shfaq] |
| 18 | |
| 19 | Me htaccess, index.php fshihet qe url-te te jene sa me te lexueshme. |
| 20 | */ |
| 21 | public function findRoute () { |
| 22 | $url = $_SERVER['PHP_SELF']; |
| 23 | $this->url = substr($url, strrpos($url, 'index.php') + strlen('index.php')); |
| 24 | $this->url = trim($this->url, '/'); |
| 25 | return $this->url; |
| 26 | } |
| 27 | |
| 28 | /* |
| 29 | Funksioni qe percakton nga copezat e url-se se cfare Kontrolluesi po kerkohet |
| 30 | dhe cilen metode te egzekutoje. |
| 31 | */ |
| 32 | private function decodeRoute () { |
| 33 | /* |
| 34 | Me explode() kam ndare url-ne ne copeza me ndarez karakterin /. Copezat |
| 35 | bashkohen te gjithe ne nje vektor. Praktikisht: |
| 36 | |
| 37 | perdoruesit/shfaq (explode) => array('perdoruesit', 'shfaq'); |
| 38 | */ |
| 39 | $params = explode('/', $this->url); |
| 40 | /* |
| 41 | Ndertoj emrin dinamik te klases qe do te egzekutohet. Klasat e Kontrollueseve |
| 42 | fillojne gjithmone me Controller_. $params[0] kthen vleren e pare te vektorit, |
| 43 | qe eshte parametri i pare i url-se. |
| 44 | */ |
| 45 | $class = 'Controller_' . $params[0]; |
| 46 | |
| 47 | /* |
| 48 | Per siguri, kontrolloj nese klasa e Kontrolluesit qe do te tentoj te perfshij, |
| 49 | egzsiton. Kjo copez s'ka shume shance te egzekutohet sepse nese nje klase nuk |
| 50 | egziston, aplikacioni do te deshtoje qe tek __autoload() ne index.php. |
| 51 | */ |
| 52 | if (!class_exists($class)) { |
| 53 | trigger_error("Kontrolleri $class nuk egziston.", FATAL); |
| 54 | } |
| 55 | |
| 56 | /* |
| 57 | Nis objektin e Kontrolluesit |
| 58 | */ |
| 59 | $controller = new $class; |
| 60 | |
| 61 | /* |
| 62 | Pjesa e pare e if() kontrollon nese eshte vendosur nje parameter i dyte, i cili |
| 63 | sherben si funksioni qe objekti i Kontrolluesit do te egzekutoje. Per siguri, |
| 64 | kontrollojme gjithashtu nese metoda egziston ne klase, per te qene te sigurt |
| 65 | qe po egzekutojme nje metode te vlefshme. |
| 66 | |
| 67 | Nese eshte vendosur parametri i dyte, egzekutoj ate metode ne klasen e Kontrolluesit. |
| 68 | Ne te kundert, egzekutoj metoden home(), qe eshte metoda default e cdo Kontrolluesi. |
| 69 | |
| 70 | **Shenim: Kontrolli me method_exists() mund te perdoret per te kthyer gabimin 404 - |
| 71 | faqja nuk egziston. Praktikisht, nese vizitori vendos nje url e cila nuk perkthehet |
| 72 | ne Kontrollues dhe metoda, url-ja supozohet te mos egzistoje. Megjithate, une e mbajta |
| 73 | te thjeshte kodin ne kete rast dhe nuk e perfshiva kete funksionalitet. |
| 74 | */ |
| 75 | if (isset($params[1]) and method_exists($controller, $params[1])) { |
| 76 | $controller->$params[1](); |
| 77 | } else { |
| 78 | $controller->home(); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | /* |
| 83 | Funksioni qe therritet kur duhet te ngarkohet nje Kontrollues. Thjeshte sherben si nderfaqe |
| 84 | ndaj decodeRoute(). |
| 85 | */ |
| 86 | public function loadController () { |
| 87 | $this->decodeRoute(); |
| 88 | } |
| 89 | |
| 90 | /* |
| 91 | Funksioni qe therritet kur nuk eshte vendosur asnje ne url; pra ne index. Automatikisht |
| 92 | egzekutohet Kontrolluesi Controller_Main dhe metoda home(). |
| 93 | */ |
| 94 | public function loadMainController () { |
| 95 | $main = new Controller_Main; |
| 96 | $main->home(); |
| 97 | } |
| 98 | } |
| 99 | ?> |
Klasa DB
Klasa e Databazës nuk është asgjë më shumë se sa një ndërfaqe ndaj funksioneve specifike; në këtë rast të MySQL. E gjitha bëhet për të ofruar funksione gjenerike, në mënyrë që mos të përsëriten gjatë të gjithë kodit funksionet specifike. Po marr një shembull. Le të supozojmë se po ndërtoni një aplikacion që përdor MySQL dhe pa e vrarë mendjen, vini në përdorim funksionet specifike të MySQL si: mysql_query(), mysql_fetch_assoc(), mysql_num_rows(), etj. Në një moment, aplikacioni bëhet shumë intensiv dhe vendoset që databaza të kalojë në Oracle. Katastrofë! Përveç se do ju duhet të rishikoni query-t e bëra dhe ti optimizoni për Oracle, do ju duhet ti ndërroni të gjitha funksionet mysql_, në ekuivalentët Oracle (oci_). Për ta zbutur dhimbjen, vjen në ndihmë klasa e Databazës.
Në vend që të shkruaj direkt mysql_query() apo funksione të tjera specifike, kam krijuar një klasë që ofron metoda për ti thërritur. Mjafton të shkruaj $db->query(), $db->results() apo $db->num_rows() për të thërritur respektivisht mysql_query(), mysql_fetch_assoc() dhe mysql_num_rows(). Nëse një ditë do të më duhet ta migroj databazën në një sistem tjetër, mjafton të ndërroj funksionet në klasën e Databazën dhe i gjithë aplikacioni është i sigurtë. Kaq e thjeshtë, por me rëndësi kritike në momentin kur duhet.
Por, krahas portabilitetit, klasa e Databazës mund të jetë një ndërfaqe që ofron lehtësira. Mund të kodohet që ti pastrojë automatikisht parametrat në një query, të ofrojë një ndërfaqe më intuitive për futjen e të dhënave dhe çfarëdo që ju bie ndërmend. Në rastin konkret, krahas metodave query(), results() dhe num_rows(), unë kam shkruajtur: change() – për të ndërruar databazën aktive dhe clean() – për të pastruar të dhënat me mysql_real_escape_string().
Si mund të përmirësohet klasa e Databazës? Një praktikë e mirë për abstraktimin (është fjalë kjo?) e databazave është përdorimi i një design pattern të quajtur Active Record. Kuptohet, mund ti rrini strikt specifikimeve ose jo, kjo është në dorën tuaj, por përgjithësisht është ide e mirë të bëni implementimin tuaj dhe jo të merrni zgjidhje të gatshme që mund të mos funksionojnë për ju. Ideja e një shtrese abstrakte është të ndërfaqësoje gjithçka; pra të mos shkruani query në formën bazë, por ti ndërfaqësoni me funksione të veçanta: $db->select(‘emri’, ‘email’)->from(‘perdoruesit’)->where(array(‘id’=’10′));. Query-t kthehen në objekte dhe më e rëndësishmja, abstraktohen aq sa migrimi është fare i thjeshtë.
Për ta mbyllur seksionin, ju këshilloj ti hidhni një sy PDO, sepse ofron mjaftueshëm drivera për sisteme të ndryshme, funksionalitet me objekte dhe lehtësira.
Kodi i klasës DB
| PHP | | Kopjo Kodin | | ? |
| 001 | <?php |
| 002 | class DB { |
| 003 | |
| 004 | //Variabla qe do te mbaje rezultatet e kthyera nga mysql_query() |
| 005 | private $results; |
| 006 | |
| 007 | /* |
| 008 | Per lehtesi kam bere lidhjen me serverin e databazes dhe selektimin e saj |
| 009 | direkt ne __construct(). Funksioni egzekutohet ne momentin qe klasa krijohet. |
| 010 | Variablat e perdorura jane konstante te deklaruara ne config.php |
| 011 | */ |
| 012 | public function __construct () { |
| 013 | mysql_connect(DB_HOST, DB_USER, DB_PASS) or trigger_error('Nuk u krye lidhja me serverin e databazes', FATAL); |
| 014 | mysql_select_db(DB_NAME) or trigger_error('Nuk u gjend nje databaze me emrin e dhene', FATAL); |
| 015 | } |
| 016 | |
| 017 | /* |
| 018 | Nje funksion ndihmes per te nderruar databazen. Thjeshte nje thirrje e vetme |
| 019 | e funksionit mysql_select_db(). Nje shembull perdorimi: |
| 020 | |
| 021 | $db->change('emri_i_tables'); |
| 022 | */ |
| 023 | public function change ($new_db) { |
| 024 | mysql_select_db($new_db) or trigger_error('Nuk u gjend nje databaze me emrin e dhene', FATAL); |
| 025 | } |
| 026 | |
| 027 | /* |
| 028 | Funksion per te egzekutuar nje query. Kontrollohet fillimisht nese query e shkruar |
| 029 | nuk eshte bosh dhe eshte tekst para se te behet thirrja ne databaze me mysql_query(). |
| 030 | Ne fund, kontrolloj nese mysql_query() ka kthyer FALSE dhe nese po, do te thote qe |
| 031 | ka deshtuar. |
| 032 | |
| 033 | return $this eshte nje menyre per te bere method-chaining (zinxhir metodash) ne PHP. |
| 034 | Praktikisht funksioni query() kthen nje objekt dhe variabla qe e pret kete objekt, |
| 035 | mund te perdoret vete si e tille dhe te therrase metoda te vete atij objektit. |
| 036 | |
| 037 | Perdorimi i objektit $db dhe funksioneve query() dhe result() do te ngjante e tille |
| 038 | nese s'do perdorja zinxhir metodash: |
| 039 | |
| 040 | $query = $db->query('SELECT * FROM tabela'); |
| 041 | foreach ($db->results = $row) { |
| 042 | echo $row['kolona']; |
| 043 | } |
| 044 | |
| 045 | Ndersa me zinxhir metodash do te ngjante e tille: |
| 046 | |
| 047 | $query = $db->query('SELECT * FROM tabela'); |
| 048 | foreach ($query->results = $row) { |
| 049 | echo $row['kolona']; |
| 050 | } |
| 051 | |
| 052 | Ndryshimi eshte minimal, por me intuitiv sepse perdor direkt variablen $query per |
| 053 | te marre rezultatet dhe per ti bere atyre loop. |
| 054 | */ |
| 055 | public function query ($sql) { |
| 056 | if ($sql == '' or !is_string($sql)) { |
| 057 | trigger_error('SQL e shkruar nuk eshte e vlefshme', FATAL); |
| 058 | } |
| 059 | |
| 060 | $this->results = mysql_query($sql) or trigger_error('Query nuk funksionoi', FATAL); |
| 061 | |
| 062 | if (!$this->results) { |
| 063 | trigger_error('Query nuk funksionoi', FATAL); |
| 064 | } |
| 065 | |
| 066 | return $this; |
| 067 | } |
| 068 | |
| 069 | /* |
| 070 | Funksioni qe perpunon rezultatet e ktheyra nga query dhe i kthen ne forme te dhenash. |
| 071 | Fillimisht behet nje while() i cili lexon cdo rresht te kthyer dhe e fut ate ne nje |
| 072 | vektor. Me pas kontrolloj nese eshte kthyer vetem 1 rresht, te cilin e kthej si |
| 073 | nje vektor 1-dimensional. Ne te kundert, kthej direkt vektorin multi-dimensional |
| 074 | qe permban te gjithe rreshtat. |
| 075 | |
| 076 | Nese e di qe query kthen shume rreshta, do kem nje kod te tille: |
| 077 | |
| 078 | $query = $db->query('SELECT * FROM tabela'); |
| 079 | foreach ($query->results() as $row) { |
| 080 | echo $row['kol1'] . $row['kol2']; |
| 081 | } |
| 082 | |
| 083 | Nese e di qe query kthen vetem 1 rresht, nuk eshte nevoja te bej lak: |
| 084 | |
| 085 | $query = $db->query('SELECT * FROM tabela WHERE id=10 LIMIT 1'); |
| 086 | $values = $query->results(); |
| 087 | echo $row['kol1'] . $row['kol2']; |
| 088 | */ |
| 089 | public function results () { |
| 090 | $values = array(); |
| 091 | |
| 092 | while ($row = mysql_fetch_assoc($this->results)) { |
| 093 | $values[] = $row; |
| 094 | } |
| 095 | |
| 096 | if (count($values) == 1) { |
| 097 | $values = $values[0]; |
| 098 | } |
| 099 | |
| 100 | return $values; |
| 101 | } |
| 102 | |
| 103 | /* |
| 104 | Nje funksion fare i thjeshte qe kthen numrin e rreshtave te nje query me ane te |
| 105 | funksionit mysql_num_rows(). Nje shembull perdorimi: |
| 106 | |
| 107 | $query = $db->query('SELECT * FROM tabela'); |
| 108 | echo $query->num_rows(); |
| 109 | */ |
| 110 | public function num_rows () { |
| 111 | if (mysql_num_rows($this->results)) { |
| 112 | return mysql_num_rows($this->results); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | /* |
| 117 | Edhe ky eshte nje funksion i thjeshte qe pastron te dhenat qe do te futen ne databze |
| 118 | me mysql_real_escape_string(). Nje shembull perdorimi: |
| 119 | |
| 120 | $emri = $db->clean($_POST['emri'); |
| 121 | $email = $db->clean($_POST['email]); |
| 122 | $query = $db->query("INSERT INTO perdoruesit (emri, email) VALUES ('$emri', '$email')"); |
| 123 | */ |
| 124 | public function clean ($input) { |
| 125 | if ($input != '') { |
| 126 | return mysql_real_escape_string($input); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | } |
| 131 | ?> |
Klasa Template
Në PHP, Shabllonet janë të kritikuara nga disa dhe të lavdëruara nga të tjerë. Një grup i lavdëron sepse të lejojnë të shkruash HTML pa e pëzier prezantimin me logjikën PHP dhe pse skedarët janë më të lehtë të menaxhohen nga diznjues/kodues që nuk njohin PHP-në. Nga ana tjetër, ka të tjerë që i kritikojnë sepse shtojnë një nivel abstrakt që nuk nevojitet për një gjuhë që vetë është një “template” për HTML. Zgjedhja mbetet personale dhe tërësisht e varur nga qëllimi.
Ajo që unë personalisht kritikoj janë Template Engine si Smarty, që ofrojnë aq shumë sa vështirësia për ti mësuar krahasohet me vetën PHP-në. Diskutimi këtu është i ngjashëm me atë rreth Zend Framework dhe 700.000+ rreshta kod të saj. Në momentin që Shabllonet bëhen më të komplikuara se vetë gjuha, ka diçka që nuk shkon.
Klasa e shablloneve që unë kam bërë është fare minimaliste sepse niveli abstrakt është shumë i vogël. Jam mjaftuar me disa instruksione të thjeshta që janë tipike të përdoren dhe kaq. Sigurisht, disa shtime nuk i bëjnë keq, por gjithmonë duke e mbajtur në thjeshtësinë aktuale.
Direktoria e dedikuar shablloneve është “app/views/“. Aty mund të krijohen skedarë HTML (.html, .htm, .tpl apo çfarëdo) dhe brenda tyre mund të shkruhet direkt kod HTML, si në një faqe normale. I vetmi ndryshim është se brenda kodit HTML mund të përdoren disa instruksione të veçanta. Aktualisht, klasa e Shablloneve përpunon tre tipe instruksionesh:
- Pseudo-Variabla – Shkruhen midis kllapave gjarpëruese në formën: {variabla} dhe përdoren në Kontrollues për ti zëvendësuar me tekstin e duhur.
- Pseudo-Konstante – Njësoj me pseudo-variablat, por vlera e tyre është e paracaktuar. Psh, unë kam përdorur një pseudo-konstante {PATH} që vendos URL-në e aplikacionit.
- Includes – Funksion që përfshin shabllone të tjera brenda atij aktual. Shkruhet në formën: {include=skedari.html}
Me këto që ofrohen, mund të kodohet shumë mirë një design i plotë. Sigurisht, nuk ofrohet fleksibiliteti i disa Templates Engine të famshme, por pse duhet? Po ju tregoj një shembull të thjeshtë si duket një skedar shabllon.
| HTML | | Kopjo Kodin | | ? |
| 01 | <html> |
| 02 | <head> |
| 03 | <title>{titulli_faqes}</title> |
| 04 | <link rel="stylesheet" type="text/css" media="screen" href="{PATH}app/assets/styles.css" /> |
| 05 | </head> |
| 06 | |
| 07 | <body> |
| 08 | <h1>{titulli}</h1> |
| 09 | |
| 10 | {trupi} |
| 11 | |
| 12 | {include=fotot.html} |
| 13 | </body> |
| 14 | </html> |
E thjeshtë apo jo? Një dokument tipik HTML që përmban disa pseudo-variabla dhe një include. Vini re që “styles.css” ndodhet në skedarin “assets“. E kam bërë për lehtësi që skedarët CSS, JS, imazhet dhe resurse të tjera të faqes të ndodhen në një vend.
Me një dokument shabllon të ndërtuar, klasa e Shablloneve do bëjë punën e saj. Fillimisht merr skedarët e duhur HTML të shablloneve, ja lexon përmbajtjen dhe bën zëvendësimet e duhura për pseudo-variablat, pseudo-konstantet dhe includes. Në fund, kur Kontrolluesi e përcakton, e printon përmbajtjen në ekran si HTML. Kodi i prodhuar është HTML e pastër.
Si mund të përmirësohet klasa e Shablloneve? Gjëja kryesore për tu bërë është caching, sidomos nëse pretendohet të punohet në aplikacione me trafik. Mund të bëni një sistem që e ruan përmbajtjen e përpunuar të shablloneve në një skedar të caktuar (/cache/) vetëm një herë dhe më pas serviret ai skedar i gatshëm, pa kryer proçese të tjera. Mund ta lini dinamike kohën që një skedar duhet të kalojë në cache për tu konsideruar i vjetër, në mënyrë që të përcaktohen kohë të shkurtra për faqe që ndryshojnë shpesh dhe kohë të gjata për ato që ndryshojnë rrallë. Është e sigurtë që caching e ndihmon serverin duke ulur proçesimet dhe vizitorin me shpejtësi hapjeje. Gjithashtu, mund ta përmirësoni sistemin duke shtuar pak elementë të përdorshëm si kushte: if(@a is 2) apo lak: for(@a in @b). Këto të fundit janë më të vështira të kodohen se të thuhen, duke marrë parasysh që kushtet apo laket mund të jenë në disa nivele brenda njëri tjetrin. Por, me njohuritë që morrët më sipër, sidomos me proçesimin e {include}, jam i sigurt që mund ta realizoni lehtë.
Kodi i klasës së Shablloneve:
| PHP | | Kopjo Kodin | | ? |
| 001 | <?php |
| 002 | class Template { |
| 003 | |
| 004 | /* |
| 005 | Variabla qe do te mbaje te gjithe html-ne qe klasa e shablloneve |
| 006 | do te perpunoje dhe do te ktheje ne fund. |
| 007 | */ |
| 008 | private $output = ''; |
| 009 | |
| 010 | /* |
| 011 | Funksion per te ngarkuar skedaret e shablloneve. Logjika ketu eshte |
| 012 | e ngjashme me metodat load() dhe store() te klases Loader, ku funksioni |
| 013 | i merr ne menyre dinamike parametrat. func_numer_args() kthen numrin e |
| 014 | parametrave, ndersa func_get_args() kthen nje vektor me te gjithe parametrat. |
| 015 | Nje shembull perdorimi: |
| 016 | |
| 017 | $tpl->loadFiles('header', 'footer'); |
| 018 | $tpl->loadFiles('body'); |
| 019 | */ |
| 020 | public function loadFiles () { |
| 021 | if (func_num_args()) { |
| 022 | $files = func_get_args(); |
| 023 | |
| 024 | /* |
| 025 | Kam bere nje lak ne te gjithe parametrat |
| 026 | */ |
| 027 | foreach ($files as $file) { |
| 028 | /* |
| 029 | Percaktoj direktorine ku ndodhen skedaret HTML te shablloneve. |
| 030 | Gjithmone jane relative ndaj index.php qe ndodhet ne root. |
| 031 | */ |
| 032 | $file = "app/views/$file.html"; |
| 033 | /* |
| 034 | Nese skedari egziston, e marr permbajtjen e tij me file_get_contents(). |
| 035 | Kthimi nga ky i fundit eshte tekst i thjeshte. |
| 036 | */ |
| 037 | if (file_exists($file)) { |
| 038 | $this->output .= file_get_contents($file); |
| 039 | } |
| 040 | } |
| 041 | } |
| 042 | } |
| 043 | |
| 044 | /* |
| 045 | Funksioni qe zevendeson pseudo-kodet e shablloneve me tekstin e vertete. |
| 046 | |
| 047 | Parametrin $codes te funksionit e kam detyruar te jete vektor duke i shtuar |
| 048 | fjalen kyce "array" perpara. Kjo quhet Type Hinting dhe eshte i disponueshem |
| 049 | ne PHP qe nga versioni 5.1. |
| 050 | */ |
| 051 | public function parse (array $codes) { |
| 052 | /* |
| 053 | Therras funksionin parseIncludes(). Shpjegohet me poshte. |
| 054 | */ |
| 055 | $this->parseIncludes(); |
| 056 | |
| 057 | /* |
| 058 | Zevendesoj pseudo-konstanten {PATH} me konstantet PATH (e deklaruar ne config.php). |
| 059 | {PATH} e kam perdorur neper shabllone per ti bere lidhjet absolute, qe te mos kisha |
| 060 | probleme me perfshirjen e aseteve (css apo cfaredo tjeter) dhe ndertimin e lidhjeve. |
| 061 | */ |
| 062 | $this->output = str_replace('{PATH}', PATH, $this->output); |
| 063 | |
| 064 | /* |
| 065 | Bej nje lak ne te gjithe elementet e vektorit. Pritet qe formati te jete nje |
| 066 | vektor ku pseudo-kodi eshte celesi dhe teksti qe do te zevendesohet vlera. |
| 067 | |
| 068 | array('pseudo-kodi1'=>'teksti1', 'pseudo-kodi2=>'teksti2'); |
| 069 | */ |
| 070 | foreach ($codes as $find=>$replace) { |
| 071 | /* |
| 072 | Nje regular expression per te kontrolluar nese pseudo-kodi i dhene egziston |
| 073 | ne shabllon. Nese po, vazhdon zevendesimi me str_replace(). |
| 074 | */ |
| 075 | if (preg_match("|{$find}|", $this->output)) { |
| 076 | $this->output = str_replace('{' . $find . '}', $replace, $this->output); |
| 077 | } |
| 078 | } |
| 079 | } |
| 080 | |
| 081 | /* |
| 082 | Funksion qe ve ne pune funksionalitetin e pseudo-kodeve {include}. Funksionon |
| 083 | egzaktesisht si nje include() ne PHP, ku permbajtja e skedarit te bere include |
| 084 | perfshihet brenda skedarit meme. |
| 085 | */ |
| 086 | private function parseIncludes () { |
| 087 | /* |
| 088 | Nje regular expression qe kontrollon per sintaksen e include-eve: {include=skedari.html}. |
| 089 | preg_match_all(), ndryshe nga preg_match(), kthen te gjitha instancat e gjetura dhe i |
| 090 | vendos ne vektorin $matches. Formati i kthyer eshte pak jo intuitiv ne pamje te pare, por |
| 091 | ka sens ne perdorim. Nese do kisha nje skedar qe ben include 2 skedare: header.html dhe |
| 092 | footer.html, $matches do kishte egzaktesisht kete pamje: |
| 093 | |
| 094 | array ( |
| 095 | [0] => array ([0] => '{include=header.html}', [1] => '{include=footer.html}'), |
| 096 | [1] => array ([0] => 'header.html', [1] => 'footer.html') |
| 097 | ) |
| 098 | |
| 099 | Si c'e shini, krijohet nje vektor multi-dimension: 2 vektore brenda 1 vektori tjeter. |
| 100 | Vektori i pare mban pjeset e plota qe pershtaten sipas sintakses se regular expression. |
| 101 | Vektori i dyte mban vetem pjeset dinamike qe kam percaktuar ne regular expression. Eshte |
| 102 | pikerisht pjesa: (.+) qe do te thote "cdo karakter, 1 ose me shume here". |
| 103 | */ |
| 104 | if (preg_match_all('|{include=(.+)}|i', $this->output, $matches)) { |
| 105 | /* |
| 106 | $finds eshte vektori i pare, pra pjeset e plota te gjetura. Ndersa $files eshte vektori |
| 107 | i dyte, pra emrat e skedareve. |
| 108 | */ |
| 109 | $finds = $matches[0]; |
| 110 | $files = $matches[1]; |
| 111 | |
| 112 | /* |
| 113 | Nje vektor bosh ku do te ruhen permbajtjet e skedareve qe do te behen include. |
| 114 | */ |
| 115 | $replaces = array(); |
| 116 | |
| 117 | /* |
| 118 | Nje lak ne vektorin $finds ku do te perdor edhe celesat, edhe vlerat. |
| 119 | */ |
| 120 | foreach ($finds as $key=>$val) { |
| 121 | /* |
| 122 | Ndertoj adresen e skedarit qe do te behet include. Kam perdorur celesin |
| 123 | $key ne kete rresht per tu referencuar skedarit te duhur. Vektoret $finds |
| 124 | dhe $files kane renditje te njejte dhe si rrjedhim, edhe celesa te njejte. |
| 125 | */ |
| 126 | $file = 'app/views/' . $files[$key]; |
| 127 | |
| 128 | /* |
| 129 | Kontrolloj nese skedari egziston. Nese po, permbajta e tij futet ne vektorin |
| 130 | $replaces si nje element i ri vektori. Nese jo, e fshi elementin ne vektorin |
| 131 | $finds, qe ne fund te me perputhen ne numer elementet e $finds me $replaces. |
| 132 | */ |
| 133 | if (file_exists($file)) { |
| 134 | $replaces[] = file_get_contents($file); |
| 135 | } else { |
| 136 | unset($finds[$key]); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | Nje zevendesim i thjeshte me str_replace(). Ky i fundit mund ti marre parametrat |
| 142 | edhe si vektore; mjafton qe te jene te njejte ne numer. |
| 143 | */ |
| 144 | $this->output = str_replace($finds, $replaces, $this->output); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | |
| 149 | /* |
| 150 | Nje funksion nderfaqes per te kthyer output-in e faqes. Supozohet te therritet |
| 151 | ne fund te veprimeve, pasi jane therritur loadFiles() dhe parse(). |
| 152 | */ |
| 153 | public function output () { |
| 154 | return $this->output; |
| 155 | } |
| 156 | |
| 157 | } |
| 158 | ?> |
Funksioni për Kapjen e Gabimeve
Nuk ka asgjë të veçantë këtu, sepse i gjithë kodi është tipik dhe në fakt do të gjeni një version shumë të ngjashëm në manualin e PHP-së. Ajo që kam bërë është përcaktimi i një funksioni që gjeneron disa nivele të ndryshme gabimesh dhe çfarë informacioni do të shfaqë. Nëse është i tipit FATAL, do përfundojë egzekutimin. Nëse është i tipeve ERROR apo WARNING, do gjenerojë gabimet por do e lërë aplikacionin të mbarojë egzekutimin. Gjithashtu kam shtuar një konstante DEGUB që vendos nivelin e raportimit të gabimeve: 0 (e çaktivizuar) nxjerr një mesazh të shkurtër, ndërsa 1 (e aktivizuar) nxjerr një mesazh të detajuar. Ajo që kam shtuar në këtë funksion është shfaqja e rreshtit ku ka ndodhur gabimi; me siguri nuk funksionon gjithmonë si ç’duhet, por mu duk një prekje e këndshme.
Mjafton që ky funksion të vendoset si parameter i funksionit PHP set_error_handler(), dhe sa herë thërritet trigger_error(), në të vërtetë thërritet funksioni jonë. Kaq!
Këtu ka shumë vend për tu përmirësuar. PHP ofron Exceptions, që janë një mënyrë shumë me e mirë për të kapur dhe raportuar gabime. Më e bukura është se vjen në formë objekti dhe mund të zgjerohet sipas kërkesave specifike.
Kodi i funksionit të Gabimeve:
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | /* |
| 03 | Funksion per te krijuar mesazhe te personalizuar gabimi kur therritet me trigger_error(). |
| 04 | Vendoset ne index.php permes set_error_handler() |
| 05 | */ |
| 06 | function ErrorHandler ($code, $string, $file, $line) { |
| 07 | |
| 08 | /* |
| 09 | Kontrollon nese kodi i gabimit egziston ne nivelin e error_reporting. PHP perdor nje sistem |
| 10 | bitesh per te vendosur nivelin e raportimit te gabimeve dhe ne kete rast, perdora operatorin |
| 11 | Bitwise AND per te kontrolluar nese vlera binare e kodit permbahet ne vleren binare te error_reporting. |
| 12 | Nese jo, funksioni perfundon sepse nuk ka cfare te ktheje. |
| 13 | */ |
| 14 | if (!(error_reporting() & $code)) { |
| 15 | return; |
| 16 | } |
| 17 | |
| 18 | /* |
| 19 | Lexon skedarin ku ka ndodhur gabimi (kthehet nga PHP permes parametrit $file) me file(). Rezultati |
| 20 | eshte nje vektor qe permban cdo rresht ne nje element. Marr rreshtin e gabimit (e kthyer nga $line) |
| 21 | dhe 1 rresht perpara dhe 1 rresht pas tij. Ne fund, pastroj me trim() rreshtat e ri. PHP_EOL eshte nje |
| 22 | konstante e PHP qe kthen versionin e duhur te rreshtit te ri (\r\n => Win, \r => OSX, \n => Unix). |
| 23 | |
| 24 | Nese konstantja DEBUG eshte 0, ky bllok nuk konsiderohet per te mos kryer veprime shtese. |
| 25 | */ |
| 26 | if (DEBUG) { |
| 27 | $file_lines = file($file); |
| 28 | $error_line = $file_lines[$line - 2] . '<div style="background:#f0c0c0; color:#853f3f;">' . $file_lines[$line - 1] . '</div>' . $file_lines[$line]; |
| 29 | $error_line = trim($error_line, PHP_EOL); |
| 30 | } |
| 31 | |
| 32 | /* |
| 33 | Kontrollohen 3 tipet e konstanteve te gabimeve: FATAL, ERROR, WARNING. Nese DEBUG eshte 0, printoj |
| 34 | nje mesazh te thjeshte; ne te kundert printoj nje mesazh me te detajuar. Vetem per tipin e gabimit |
| 35 | FATAL e kam detyruar te mbyllet egzekutimi i aplikacionit (me exit;) sepse supozohet te jete gabim |
| 36 | i rendesishem. |
| 37 | */ |
| 38 | switch ($code) { |
| 39 | case FATAL: |
| 40 | switch (DEBUG) { |
| 41 | case 0: |
| 42 | echo 'Ndodhi nje gabim. Ju lutemi te provoni me vone.'; |
| 43 | exit; |
| 44 | case 1: |
| 45 | echo "<b>Fatal Error</b> in [$file] at Line $line |
| 46 | |
| 47 | "; |
| 48 | echo "<div style='background:#f0dddd; border:1px solid #cf9898; color:#c58080; padding:15px;'>$error_line</div> |
| 49 | "; |
| 50 | echo "<div style='background:#e6edf3; border:1px solid #a2bcd2; color:#7691a9; padding:15px;'>$string</div>"; |
| 51 | exit; |
| 52 | } |
| 53 | case ERROR: |
| 54 | switch (DEBUG) { |
| 55 | case 0: |
| 56 | echo 'Ndodhi nje gabim. Ju lutemi te provoni me vone.'; |
| 57 | break; |
| 58 | case 1: |
| 59 | echo "<b>Fatal Error</b> in [$file] at Line $line |
| 60 | |
| 61 | "; |
| 62 | echo "<div style='background:#f0dddd; border:1px solid #cf9898; color:#c58080; padding:15px;'>$error_line</div> |
| 63 | "; |
| 64 | echo "<div style='background:#e6edf3; border:1px solid #a2bcd2; color:#7691a9; padding:15px;'>$string</div>"; |
| 65 | break; |
| 66 | } |
| 67 | break; |
| 68 | case WARNING: |
| 69 | switch (DEBUG) { |
| 70 | case 0: |
| 71 | echo 'Ndodhi nje gabim i vogel, por aplikacioni do te vazhdoje te egzekutohet.'; |
| 72 | break; |
| 73 | case 1: |
| 74 | echo "<b>Fatal Error</b> in [$file] at Line $line |
| 75 | |
| 76 | "; |
| 77 | echo "<div style='background:#f0dddd; border:1px solid #cf9898; color:#c58080; padding:15px;'>$error_line</div> |
| 78 | "; |
| 79 | echo "<div style='background:#e6edf3; border:1px solid #a2bcd2; color:#7691a9; padding:15px;'>$string</div>"; |
| 80 | break; |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | return true; |
| 85 | } |
Nisja e veprimeve në index.php
Skedari index.php vë motorin në lëvizje dhe relativisht me të funksionon i gjithë sistemi. E vutë re që URL-të ishin të ndërtuara në formën: index.php/kontrolluesi/metoda/, por me .htaccess e kisha hequr pjesën “index.php” për ti bërë URL-të më të bukura. Një sistem i tillë më lejon që logjikën dhe prezantimin ta kem të ndarë, por as ta vras mendjen për përfshirjen e skedarëve, deklarimin e klasave, router-in, etj. Këto i bën të gjitha index.php dhe mua më mjafton të merrem me faqet e ndryshme dhe kodin ne Kontrollues.
Në index.php kam bërë të gjitha veprimet që do të përdoren në rend global nëpër Librari. Kam bërë include() skedarët e konfigurimit dhe funksionin e kapjes së gabimeve, kam shkruar një funksion __autoload() që bën include() automatikisht klasat, kam ngarkuar përmes Loader-it objektet që do të më duhen dhe kam nisur Router-in. E thjeshtë dhe praktike!
Kodi në index.php
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | session_start(); |
| 03 | |
| 04 | /* |
| 05 | Perfshij funksionin e gabimeve (error.php) dhe konfigurimin (config.php) |
| 06 | */ |
| 07 | require_once(__DIR__ . '/core/error.php'); |
| 08 | require_once(__DIR__ . '/config.php'); |
| 09 | |
| 10 | /* |
| 11 | __autoload() egzekutohet si tentativa e fundit per te perfshire skedaret e duhur |
| 12 | te nje klase. Eshte nje menyre e mire per te bere include/require skedaret e klasave |
| 13 | dhe per te hequr punen e bezdisshme (dhe te pamundur ne shume raste) te perfshirjes |
| 14 | manuale te klasave. Parametri $class eshte emri i klases qe po tentohet te therritet |
| 15 | nga aplikacioni. |
| 16 | */ |
| 17 | function __autoload($class) { |
| 18 | /* |
| 19 | Blloku i pare i if() egzekutohet nese emri i klases ka fjalen "controller" brenda; qe |
| 20 | do te thote se po tentohet te niset nje Kontrollues (emrat e klasave te te cileve |
| 21 | fillojne gjithmone me Controller_). Nese ai eshte rasti, percaktoj direktorine |
| 22 | e duhur ku ndodhen Kontrolluesit (/app/controller/). Konkretisht: |
| 23 | |
| 24 | Nese po therritet Kontrolluesi: "Controller_Perdoruesit", veprimet qe behen |
| 25 | me poshte jane: |
| 26 | |
| 27 | strtolower() => "controller_perdoruesit" |
| 28 | str_replace() => "controller/perdoruesit" |
| 29 | adresa e plote => __DIR__ . '/app/controller/perdoruesit.php" |
| 30 | |
| 31 | Nese emri i klases nuk permban "controller", kalohet ne bllokun e dyte ku tentohet |
| 32 | te perfshihet nje klase nga ato baze (core). |
| 33 | |
| 34 | __DIR__ eshte nje konstante qe kthen direktorine fizike te aplikacionit. |
| 35 | |
| 36 | **Shenim: Qe nga PHP 5.3, funksioni __autoload() dekurajohet ne favor te nje implementimi |
| 37 | me te mire me sp_autoload_register(). Ne te ardhmen, __autoload() mund te behet deprecated. |
| 38 | */ |
| 39 | if (stristr($class, 'controller')) { |
| 40 | $file = __DIR__ . '/app/' . str_replace('_', '/', strtolower($class)) . '.php'; |
| 41 | } else { |
| 42 | $file = __DIR__ . '/core/' . strtolower($class) . '.php'; |
| 43 | } |
| 44 | |
| 45 | /* |
| 46 | Nese skedari nuk egziston, gjenerohet nje gabim. |
| 47 | */ |
| 48 | if (!file_exists($file)) { |
| 49 | trigger_error("Klasa $file nuk u ngarkua sepse skedari perkates nuk egziston.", FATAL); |
| 50 | } |
| 51 | |
| 52 | /* |
| 53 | Perfshihet skedari. |
| 54 | */ |
| 55 | require_once($file); |
| 56 | } |
| 57 | |
| 58 | /* |
| 59 | set_error_handler() vendos funksionin baze per gabimet, qe kthehet nga trigger_error(). |
| 60 | */ |
| 61 | set_error_handler('ErrorHandler'); |
| 62 | |
| 63 | /* |
| 64 | Ruaj ne Loader klasat qe do te me duhen te perdor neper aplikacion. |
| 65 | */ |
| 66 | Loader::store('db', 'router', 'template'); |
| 67 | |
| 68 | /* |
| 69 | Ngarkoj objektin e Router. |
| 70 | */ |
| 71 | $router = Loader::load('router'); |
| 72 | |
| 73 | /* |
| 74 | Kontrolloj nese metoda findRoute() kthen nje nje url. Nese jo (blloku i pare), egzekutoj |
| 75 | loadMainController(), metode pergjegjese per Kontrolluesin kryesore (index). Nese kthehet |
| 76 | nje url (blloku i dyte), therras loadController() per te ngarkuar Kontrolluesin e duhur. |
| 77 | */ |
| 78 | if (!$router->findRoute()) { |
| 79 | $router->loadMainController(); |
| 80 | } else { |
| 81 | $router->loadController(); |
| 82 | } |
| 83 | ?> |
Controllers dhe Views
Gjatë guidës ju a kam shpjeguar se si funksionojnë Controllers dhe Views. Megjithatë, po bëj një përmbledhje të shkurtër për të sqaruar ato që mund të mos keni kuptuar, sidomos tani që e keni parë se si sistemi funksionon.
Controllers janë klasa që ruhen në direktorinë “app/controller/“. Emri i klasës fillon gjithmonë me “Controller_” dhe pjesa e dytë duhet të përputhet me emrin e skedarit që Router-i ta njohë. Psh, një Controller me emrin “Controller_Perdoruesit“, duhet që emrin e skedarit ta kete “perdoruesit.php” që të egzekutohet kur në URL vendoset “index.php/perdoruesit/“. Krahas nomeklaturës, çdo Controller duhet të ketë patjetër një metodë të quajtur home(), e cila egzekutohet nëse nuk është përcaktuar një parametër i dytë në URL. Nëse është përcaktuar, psh: “index.php/perdoruesit/shfaq/“, automatikisht Router-i kontrollon për një metodë të quajtur shfaq() në Controller-in “Controller_Perdoruesit“.
Views janë skedarë HTML që ruajnë paraqitjen e aplikacionit dhe vendosen në direktorinë “app/views/“. Brenda mund të shkruhen pseudo-variabla të cilat zëvendësohen përmes klasës së Shablloneve me tekstet e përcaktuara. Skedarët CSS, Javascript, Imazhet apo resurse të tjera që do të përdoren në shabllone, ruhen në direktorinë “app/assets/“. Në këtë formë janë të ndara midis tyre asetet nga skedarët HTML.
Shkarkoni Kodin
Për të parë se si i gjithë sistemi vihet në punë dhe disa shembuj konkretë të përdorimit të Controllers dhe Views, shkarkoni kodin në fillim të guidës. Pavarësisht se tashmë duhet ta keni krijuar mirë idene se si klasat e ndryshme funksionojnë dhe si lidhen me njëra tjetrën, shembujt konkretë duhet t’ja u sqarojnë mirë idetë.
Si mund ta përmirësoni sistemin?
Në çdo seksion ju tregova si të përmirësoni klasat e veçanta, për ti bërë më të fuqishme dhe me më tepër opsione. Megjithatë, një Librari ka plot për të shtuar.
Kryesorja do të ishin shtime ndaj klasave bazë që kryejnë funksione të përdorura shpesh. Mund të jenë klasa për: gjenerimin e formave, manipulim imazhesh, krijimin e faqeve (pagination), siguri, unit testing, etj. Të gjitha janë proçese ripetitive që të marrin kohë nëse i shkruan nga fillimi në çdo projekt, apo i kalon nga një projekt tek tjetri. Gjithashtu mund të shtohen ndihmësa që ju ndihmojnë si: dërgim email-esh, përpunimin e URL-ve, shkrimin e HTML-së, shkarkim, gjenerim/konvertim datash, etj. Kam shkruar një guidë këtu në Feniksi ku kam dhënë 3 ndihmësa të thjeshtë: Klasa Ndihmëse në PHP. PHP është pak e çuditshme për nga zgjedhja e emrave të funksioneve dhe vendosjes së parametrave të tyre. Në fund, do keni një Librari të kompletuar me klasat bazë dhe ndihmësat që përdorni më tepër.
Një shtim i mirë do të ishte një klasë abstrakte për Kontrolluesit që luan rolin e një ndërfaqeje, por edhe për të kryer veprime që të jenë të aksesueshmë nga të gjithë Kontrolluesit e tjerë. Një nga këto është edhe ngarkimi i objekteve (përmes Loader-it) në një vektor të klasës abstrakte, që të përdoret lehtësisht nga të gjitha metodat e Kontrolluesëve që zgjerojnë atë.
Shikoni kodin aktual të një Kontrolluesi në formën që e kam shkruar tani. Jam detyruar të thërras çdo objekt përmes Loader-it manualisht sa herë që me duhen dhe maksimumi që mund të bëj me këtë sistem, është të krijoj një vektor për çdo klasë që mban objektet.
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | class Controller_Celularet { |
| 03 | |
| 04 | public function home () { |
| 05 | $tpl = Loader::load('template'); |
| 06 | $db = Loader::load('db'); |
| 07 | |
| 08 | $query = $db->query("SELECT * FROM framework_celularet ORDER BY marka"); |
| 09 | foreach ($query->results() as $row) { |
| 10 | $phones .= '<a href="shfaq/' . $row['id'] . '/"><b>' . $row['marka'] . '</b> - ' . $row['modeli'] . '</a> (' . $row['vlera'] . '$)<br />'; |
| 11 | } |
| 12 | |
| 13 | $tpl->loadFiles('celularet'); |
| 14 | $tpl->parse(array( |
| 15 | 'site_title' => 'Feniksi Framework - Lista e Celulareve', |
| 16 | 'title' => 'Lista e Celulareve', |
| 17 | 'celularet' => stripslashes($phones); |
| 18 | )); |
| 19 | echo $tpl->output(); |
| 20 | } |
| 21 | |
| 22 | } |
| 23 | ?> |
Nuk është edhe aq praktike, apo jo? Duke realizuar sugjerimin që ju dhashë më sipër, të gjithë pjesën e ngarkimit të objekteve mund t’ja lëmë në përgjegjësi një klase “Controller” që të gjithë Kontrolluesit janë të detyruar ta shtojnë. Shikoni si duket shembulli më poshtë.
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | abstract class Controller { |
| 03 | |
| 04 | protected $db; |
| 05 | protected $tpl; |
| 06 | |
| 07 | public function __construct () { |
| 08 | $this->db = Loader::load('db'); |
| 09 | $this->tpl = Loader::load('template'); |
| 10 | } |
| 11 | |
| 12 | abstract protected function home (); |
| 13 | |
| 14 | } |
| 15 | |
| 16 | class Controller_Celularet extends Controller { |
| 17 | |
| 18 | public function home () { |
| 19 | $query = $this->db->query("SELECT * FROM celularet ORDER BY marka"); |
| 20 | foreach ($query->results() as $row) { |
| 21 | $phones .= '<a href="shfaq/' . $row['id'] . '/"><b>' . $row['marka'] . '</b> - ' . $row['modeli'] . '</a> (' . $row['vlera'] . '$)<br />'; |
| 22 | } |
| 23 | |
| 24 | $this->tpl->loadFiles('celularet'); |
| 25 | $this->tpl->parse(array( |
| 26 | 'site_title' => 'Feniksi Framework - Lista e Celulareve', |
| 27 | 'title' => 'Lista e Celulareve', |
| 28 | 'celularet' => stripslashes($phones) |
| 29 | )); |
| 30 | echo $this->tpl->output(); |
| 31 | } |
| 32 | |
| 33 | } |
| 34 | ?> |
Si ç’e shihni, klasa abstrakte “Controller” merr vlerën e objekteve që në __construct() – pra në momentin e krijimit – dhe i kalon në variablat e klasës. Në këtë formë, Kontrolluesit që shtojnë klasën “Controller”, kanë akses direkt tek variablat e tij dhe mund ti përdorin ato objekte pa pasur nevojën ti deklarojnë sërish. Gjithashtu, kam shkruar edhe një metodë abstrakte home() për të imponuar Kontrolluesit ta kenë atë metodë. Ky sistem sigurisht është më logjik dhe pikërisht për këtë arsye ja u tregova. Sistemi aktual nuk përdor këtë mënyrë, thjeshtë për t’ju lënë të eksperimentoni me mënyrat që ju duken më interesante.
Nëse e shihni të nevojshme, mund ti shtoni Modele Librarisë, për të ndarë logjikën nga të dhënat. Nuk do ju merrte shumë kohë sepse Modelet thërriten nga Kontrolluesit dhe mjafton një klasë e ngjashme me Loader-in apo një metodë brenda Loader-it, që specializohet në thërritjen e modeleve. Ashtu si sugjerimi për Kontrolluesit, edhe Modelet mund të kenë një klasë abstrakte që përcakton vlera apo metoda të detyrueshme.
Më poshtë kam shkruar një Model shumë të thjeshtë i cili do të përdoret në një nga Kontrolluesit shembull. Vlen vetëm për t’ju dhënë idenë.
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | abstract class Models { |
| 03 | |
| 04 | protected $db; |
| 05 | |
| 06 | public function __construct () { |
| 07 | $this->db = Loader::load('db'); |
| 08 | } |
| 09 | |
| 10 | } |
| 11 | |
| 12 | class Model_Celularet extends Models { |
| 13 | |
| 14 | public function listaCelulareve () { |
| 15 | $query = $this->db->query("SELECT * FROM celularet ORDER BY marka"); |
| 16 | return $query->results(); |
| 17 | } |
| 18 | |
| 19 | } |
Si ç’e shihni, edhe këtu kam krijuar një klasë abstrakte që merr objektin e databazës dhe ja servir të gjithë modeleve të tjerë përmes variablës $db. Klasa “Model_Celularet” zgjeron klasën abstrakte dhe mban vetëm një metodë për të shfaqur listën e celularëve. Kaq supozohet të bëjë një Model: të mbajë metoda për marrjen e të dhënave nga databaza dhe t’ja dërgojë ato Kontrolluesëve. E gjithë ideja është që të ndahen klasat që marrin të dhëna (Modelet) dhe klasat që përpunojnë të dhëna (Kontrolluesit).
Tani po ju tregoj një shembull përsëri të thjeshtë të përdorimit të Modelit të mësipërm në një Kontrollues. Teknika nuk është e përsosur sepse Loader::load(‘modeli’) nuk specializohet në ngarkimin e Modeleve. Mbani mend që Loader-i fillimisht duhet ti ruajë objektet e më pas ti servirë? Kjo s’është optimale për Modelet sepse do na duhej ti ruanim në Loader (përmes metodës store) para se ti përdorim. Megjithatë, mund të shtohet lehtë një metodë (psh: Loader::loadModel()) e specializuar për ngarkimin e Modeleve.
| PHP | | Kopjo Kodin | | ? |
| 01 | <?php |
| 02 | class Controller_Celularet { |
| 03 | |
| 04 | public function home () { |
| 05 | $model = Loader::load('Model_Celularet'); |
| 06 | |
| 07 | $results = $model->listaCelulareve(); |
| 08 | foreach ($results as $row) { |
| 09 | $phones .= '<a href="shfaq/' . $row['id'] . '/"><b>' . $row['marka'] . '</b> - ' . $row['modeli'] . '</a> (' . $row['vlera'] . '$)<br />'; |
| 10 | } |
| 11 | |
| 12 | $tpl->loadFiles('celularet'); |
| 13 | $tpl->parse(array( |
| 14 | 'site_title' => 'Feniksi Framework - Lista e Celulareve', |
| 15 | 'title' => 'Lista e Celulareve', |
| 16 | 'celularet' => stripslashes($phones) |
| 17 | )); |
| 18 | echo $tpl->output(); |
| 19 | } |
| 20 | |
| 21 | } |
Mbani mend që sa më tepër zgjerohet Libraria, aq më e ngarkuar dhe e vështirë bëhet për tu mirëmbajtur. Nëse shtoni klasa dhe ndihmësa pa fund, edhe nga ato që s’keni për ti përdorur kurrë, nuk do bëni asgjë më shumë se të kodini një Librari që i ngjan atyre të gatshme. Qëllimi është të jetë e përdorshme, jo një monolith kodi.
Përfundimi
I erdhi fundi kësaj guide të gjatë, por kjo s’do të thotë se i erdhi fundi Librarisë. Nëse keni interes, qoftë për të mësuar apo për ta përdorur për qëllimet tuaja, ju këshilloj seriozisht ta modifikoni në pafundësi. Mënyra më e mirë për të mësuar është praktika dhe kjo guidë këtë tenton të bëjë.
E gjithë Libraria, së bashku me kodin që kam shkruajtur në Controllers dhe Views, ka 777 rreshta. Duket sikur e kam bërë me qëllim! Në krahasim me 200.000+ apo 700.000+ që kanë Libraritë masive, duket si Librari lodër. Edhe nëse i shtoni funksionalitete të tjera, përfshi përmirësimet në klasa, klasa të tjera bazë dhe ndihmësa, e sigurtë është që do jetë sërisht nën 10.000 rreshta. Përfitoni një Librari të vogël, të lehtë për nga resurset dhe që e bën punën shumë mirë. Mbi të gjitha, sistemin e keni koduar vetë, e njihni shumë mirë dhe gjatë proçesit keni mësuar shumë. Vetëm kjo e fundit është një arsye e mirë për ta marrë këtë iniciativë.
Mësim të mbarë.








Faleminderit per kete guide, dhe kodin e gatshem, do te mundohem te mesoj nga kjo..
Padyshim eshte nje guide teper interesante per nivelin mesatar-avancuar te zhvilluesve ne PHP.
Megjithate mirembajtja e nje frameworku ka gjithmone nevoje per shume kohe (prandaj dhe perdoren frameworket).
Besoj SlimFramework per PHP eshte nder Frameworket me te lehte dhe praktik per perdorim.
Ne fakt, s’eshte edhe aq ide e keqe nje framework i bere vete, per aq kohe sa njohurite, deshira dhe sidomos, koha jane aty. Kur e njeh sistemin ne rrenje, e sigurte eshte qe e perdor ne maksimum.
Sidoqofte, Slim (qe permende) dhe FatFree jane lightweight sa duhet. I vetmi problem qe shoh me librarite e vogla eshte suporti ne te ardhmen dhe nese vjen nje dite qe vdesin, i bie te nderrosh komplet sistem.
Flm per komentin.