Ndodheni ne: Guidat / PHP
Framework per PHP ne stilin Beje Vete

Framework për PHP në stilin “Bëje Vetë”

Nga më 26 September 2011 në PHP me 3 Komente

Libraritë për PHP sa vjen e bëhen më popullore dhe me të drejtë. Është shumë më e lehtë të kodosh aplikacione të çdo lloj madhësie nëse mbështetesh mbi një bazë të mirë dhe të provuar. Por pse mos të provoni të kodoni Librarinë tuaj për projektet tuaja personale? Nëse ju ngacmon ideja, lexoni më tej.

  • Shkarko Kodin
  • Shiko Demon

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:

  1. 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.
  2. 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.
  3. 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ë.

Fadion Dashi

Fadioni është prej shumë vitesh i apasionuar pas internetit dhe punon freelance si dizenjues dhe programues per web. Kur nuk është duke punuar, i pëlqen të shkruajë, të fotografojë, të admirojë koleskionin e tij të aparatëve fotografikë manualë dhe të kalojë kohë të bukur me miqtë.

3 Komente

  1. Egzon says:

    Faleminderit per kete guide, dhe kodin e gatshem, do te mundohem te mesoj nga kjo..

  2. Shqipetar says:

    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.

    • Fadion Dashi says:

      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.

Shkruaj një Koment