Saját sablonrendszer II.: a kezelő és a sablonok
2005. nov. 22. (kedd), 23:36, D. keze nyománOpen Source, Saját kód, Webfejlesztés, Ismeretterjesztő
Az egyszerűbb követhetőség kedvéért a teljes kód, valamint egy példasablon már most letölthető, megtekinthető.
Első rész:
Második rész:
A sablonkezelő részei
Sablonkezelőnkben az elvi felépítést tekintve az alábbiakra lesz szükségünk:
- a teljes sablonállomány betöltése (tpl_file_betolt())
- a sablonállomány egy részének betöltése, így tetszőleges számú sablont tárolhatunk egyetlen sablonállományban (tpl_sablon_betolt())
- a sablonban lévő elemek cseréje (tpl_elem_csere())
- a sablon feltételes részeinek kezelése (tpl_feltetelkezelo())
- a sablon ismétlődő részeinek kezelése (tpl_metszet_betolt(), tpl_csere_metszetben())
- a kész sablon kiadása (tpl_kimenet())
- hibakezelés (tpl_hiba())
A sablonkezelő osztály, mint megvalósítás
A sablonkezelőt objektum orientált megközelítésben fogjuk létrehozni, a PHP5 el(nem)terjedtségére való tekintettel még a PHP 4 szerint.
class sablonkezelo
{
function tpl_file_betolt($sablon_file) {}
function tpl_sablon_betolt($melyik) {}
function tpl_metszet_betolt($melyik) {}
function tpl_csere_metszetben($tmb_adat, $melyik) {}
function tpl_feltetelkezelo($tmb_adat, $str) {}
function tpl_elem_csere($tmb_adat, $str) {}
function tpl_hiba($kod) {}
function tpl_kimenet() {}
} 1. A sablonállomány betöltése
function tpl_file_betolt($sablon_file)
{
if (@file_exists($sablon_file)) {
$this->tpl_file = $sablon_file;
$this->tpl_tartalom = file_get_contents($this->tpl_file);
} else {
$this->tpl_hiba('nincs_tpl_file:'.$sablon_file);
}
} A sablonállomány betöltése rém egyszerű: ha megvan a szükséges állomány, tartalmát beolvassuk, ha nincs, jöhet a hibakezelés.
2. A sablonállomány egy részének betöltése
function tpl_sablon_betolt($melyik)
{
if ( !empty($this->tpl_tartalom) ) {
$start_pos = strpos($this->tpl_tartalom, "[SABLON:$melyik]")
+ strlen("[SABLON:$melyik]"); // start tag hossza
$stop_pos = strpos($this->tpl_tartalom, "[/SABLON:$melyik]");
$this->sablon = trim(substr($this->tpl_tartalom, $start_pos, $stop_pos-$start_pos));
} else {
$this->tpl_hiba('ures_tpl_file');
}
} A teljes állománytartalomból ki kell választanunk azt a sablont, amelyikre szükségünk van. Ezt a [SABLON:valami][/SABLON:valami] elemek határolják, így az állomány tartalmából (amely egy string, vagyis karakterláncolat) kivágjuk azt a részt, amelyik [SABLON:valami] kezdetű, és [/SABLON:valami] végű.
3. A sablonban lévő elemek cseréje
function tpl_elem_csere($tmb_adat, $str)
{
foreach ($tmb_adat as $mezo => $ertek) {
$str = str_replace('{'.$mezo.'}', $ertek, $str);
}
return $str;
} A sablonban az elemeket kapcsos zárójelek fogják közre (számtalan más sablonkezelő rendszerhez igazodva ezzel), ezek képviselik a "geometriai alakzatokat". A csere során a kapott adattömb kulcsainak megfelelő elemeket a tömbkulcshoz tartozó értékre cseréljük a célstringben (amely nem feltétlenül az egész sablont jelenti!). Például ha a tömbben az 'ertek1' => 'Érték 1' kulcs-érték pár szerepel, akkor a szövegben az {ertek1} elemet Érték 1-re cseréljük.
Megjegyzések: nyilvánvaló, hogy
az összes {ertek1} a célstringben Érték 1-re cserélődik,
a célszöveg célszerű megválasztásával befolyásolható, hogy ne az egész pl. sablonban cserélődjenek az ugyanolyan elemek,
ne adjuk két eltérő elemnek ugyanazt a nevet egyazon sablonban, sablonrészben, hisz ilyenkor az utóbb definiált értékre cserélődik mind
a szövegben normálisan is kapcsos zárójelek közt lévő, nem elem jellegű részek nem fognak cserélődni, mivel a tömbben nincs megfelelő kulcsuk,
a 4-es pont fordítva is igaz, a tömbben nem, a sablonban szereplő elemek kapcsos zárójelben, eredeti alakjukban megjelennek a kimeneten, így azonnal látható, ha valamit elbaltáztunk (a feltételes szerkezetekről lásd alább)
4. a sablon feltételes részeinek kezelése
Sablonunkban könnyen lehetnek olyan elemek, melyekről nem tudjuk előre, vajon meg kell-e jelenítenünk őket: elég az adminisztrációs linkekre gondolnunk, melyek csak jogosult felhasználóknak tűnnek elő.
function tpl_feltetelkezelo($tmb_adat, $str)
{
while ( preg_match('/^.*[IF:(w+)](.*)[/IF:1].*$/sm', $str) ) {
$feltetel = preg_replace('/^.*[IF:(.+)](.*)[/IF:1].*$/sm', '$1', $str);
$str = preg_replace(
"/[IF:$feltetel](.*)[/IF:$feltetel]/sm",
(array_key_exists($feltetel, $tmb_adat) ? '$1' : ''), $str);
// van ilyen adat -> vezerlo if-et kivesszuk, sablon marad
// nincs ilyen adat -> kivesszuk az egesz felteteles reszt
}
return $str;
}A célstringben megkeressük az összes feltételes szerkezetet, megnézzük, hogy az adattömbben van-e hozzájuk tartozó érték, ha van, akkor megjelenítjük őket, ha nincs, akkor kivesszük az egész feltételes részt.
A sablonban a feltételesen megjelenítendő részeket [IF:ertek]{valami} és {ertek}[/IF:ertek] határolja, ahol az IF-ben lévő 'ertek' a megjelenítendő rész bármelyik eleme lehet. Egy-egy IF felismerésére reguláris kifejezést használunk, az összes begyűjtéséhez a while vezérlési szerkezettel lépkedünk végig a célstringen, melynek minden ciklusában a következő történik:
- ha van megfelelő tartalom, a sablonhoz tartozó [IF:ertek] és [/IF:ertek] stringeket kiszedjük, a közöttük lévő, feltételes részt azonban benn hagyjuk a sablonban, így a továbbiakban a hagyományos módon érvényesül
- ha nincs tartalom, az egész IF-es részt IF-estől, közbezárt részestől kiszedjük, így nem érvényesül a továbbiakban.
Ezt a
(array_key_exists($feltetel, $tmb_adat) ? '$1' : '')feltétel valósítja meg.
5. A sablon ismétlődő részeinek kezelése
Képzeljünk el egy listát, melynek minden eleme HTML-t tekintve ugyanolyan, csak a tartalmuk változik elemenként. A lista hosszát dinamikus lapoknál jellemzően nem tudjuk, így a sablonállományban nem tudjuk, hány elemet írjunk. A megoldás egy olyan, egyszer definiált "al-sablon", amelyet a lista minden eleméhez felhasználunk - ezt neveztem el metszetnek.
A metszeteket egyrészt meg kell találni a sablonban, másrészt ki kell "vágni" őket a teljes sablonból, harmadrészt a bennük lévő elemeket cserélni kell.
function tpl_metszet_betolt($melyik)
{
if ( !empty($this->sablon) ) {
$start_pos = strpos($this->sablon, "[METSZET:$melyik]")
+ strlen("[METSZET:$melyik]"); // start tag hossza
$stop_pos = strrpos($this->sablon, "[/METSZET:$melyik]");
$this->metszetek[$melyik] = substr($this->sablon, $start_pos, $stop_pos-$start_pos);
} else {
$this->tpl_hiba('nincs_sablon');
}
}A metszet betöltése elvét tekintve megegyezik a sablonok betöltésével, pusztán a tisztább, átláthatóbb kód érdekében tartottam meg őket külön.
function tpl_csere_metszetben($tmb_adat, $melyik)
{
// metszetben felteteles reszek feldolgozasa
$this->metszetek[$melyik] = $this->tpl_feltetelkezelo($tmb_adat, $this->metszetek[$melyik]);
// elemek csereje (nincs tartalom -> metszet sablonjat adjuk meg)
if ( !isset($this->metszet_tartalmak[$melyik]) ) {
$this->metszet_tartalmak[$melyik] = $this->metszetek[$melyik];
$this->metszet_tartalmak[$melyik] = $this->tpl_elem_csere($tmb_adat, $this->metszet_tartalmak[$melyik]);
} else {
$this->metszet_tartalmak[$melyik] .= "n".$this->tpl_elem_csere($tmb_adat, $this>metszetek[$melyik]);
}
}A metszetek kezelését két részre bontottam: a $this->metszetek tömb a metszetek sablonrészeit gyűjti, míg a $this->metszet_tartalmak a már kész, feldolgozott, teljes listákat.
Metszeten belül is lehetségesek feltételek, ezeket előre feldolgozzuk, ezáltal is kímélve az erőforrásokat.
Lehetséges, hogy még nincs metszettartalom (hisz első ízben még nincs), ekkor a sablonrészt adjuk meg metszetnek, és lecseréljük benne az elemeket.
Minden ezt követő alkalommal azonban már csak megragadjuk a metszetsablont, annak alapján lecseréljük a benne lévő elemeket, és hozzáadjuk az addigi metszettartalomhoz (ezáltal lesz végül a metszettartalom adott metszet sokszorozása, adatokkal feltöltve).
A sablon minden egyes metszetén ez eljátszható.
A preg_match meglehetősen erőforrás-igényes függvény, így meg kell kerülni, ha nagy számú ismétlődő elemünk van - mint pl. több, hosszú select űrlapmező.
A tpl_select metódus ezt hivatott szolgálni:
function tpl_select($tmb_adat, $selected, $prefix = "ntt")
{
$str = '';
foreach ($tmb_adat as $ertek => $cimke) {
$str .= ($str=='' ? '' : $prefix)."<option value="$ertek""
.($ertek==$selected ? ' selected="selected"' : '').">$cimke</option>";
}
return $str;
}Működése értelemszerű: a választási lehetőségeket adja vissza stringként.
6. A kész sablon kiadása
Itt már örülünk, a sablonkezelés oroszlánrészén már túl vagyunk.
function tpl_kimenet()
{
if ( !empty($this->metszetek) ) {
foreach($this->metszetek as $m_nev => $m_tartalom) {
$this->sablon = preg_replace(
"/[METSZET:$m_nev].*[/METSZET:$m_nev]/smU",
(!empty($this->metszet_tartalmak[$m_nev]) ? $this->metszet_tartalmak[$m_nev] : ''),
$this->sablon);
}
}
return $this->sablon;
}A kimenetben megnézzük, hogy volt(ak)-e a sablonunkban metszet(ek), és ha volt(ak), elvégezzük a tartalomra cserélést, majd az egész sablont - amely most már adatokkal feltöltött (X)HTML kódot jelent - kimenetre küldjük.
7. Hibakezelés
function tpl_hiba($kod)
{
die($kod);
}Ennél egyszerűbb nem is lehetne: ha a futást veszélyeztető hibát észlelünk, agyonütjük a scriptet, ne szaladjon tovább.
Nyilván felmerül a kérdés, miért nem rakok be egyszerűen egy die()-t oda, ahol rendellenes működés történt. Tervezési hiba volna, hisz lehetséges, hogy a későbbiekben komolyabb hibakezelés kellhet (a kódban turkálásról ugye beszéltünk a cikk első részében, mégha nem is ilyen kontextusban), másrészt logikailag is szerencsésebb elkülöníteni a hibakezelést.
Kérdésként vetődhet még fel, hogy miért szüntetjük meg a script futását, ha pl. hibaüzenet mellett folytathatnánk a generálást. A sablonkezelő rendszer részeihez külső felhasználó normális esetben nem fér hozzá, így csak a fejlesztés során követhetünk el hibát - akkor meg jobb, ha rögvest megkapjuk a méltó jutalmat.
Végszó
A sablonkezelő a fenti példákban egy XHTML-"sablonnyelv" hibridet dolgoz fel, de értelemszerűen bármilyen leírónyelvhez, tulajdonképp bármilyen szöveghez használható általános cserélőként.
Figyeljünk rá, hogy az str_replace() lényegesen gyorsabban fut le, mint a preg_replace(), így előbbit érdemes minden lehetséges helyen használni. Aki az elején elmulasztotta volna, itt is leszedheti a teljes kódot, illetve a példasablont is. A sablonkezelőt a nyílt forráskód szellemében bátran felhasználhatjátok, cserébe jelöljetek meg forrásként.
A cikk-kettősben tehát egy röpke pillanat erejéig betekintést nyerhettünk a tervezési mintákba, megismerkedtünk a sablonokkal, előnyeikkel-hátrányaikkal, majd egy primitív, de rugalmas sablonkezelőt építettünk.
A fentiekben olvasható első kódpublikációm, így örömmel fogadom a kiegészítéseket, tanácsokat, javításokat - és persze kérdéseitek, tapasztalaitok más/saját sablonkezelővel kapcsolatban is!
-
(#427)jurij2005-11-29 20:36:12
köszönöm.
-
Az, hogy a nevem mit jelent, teljességgel offtopic a sablonokkal foglalkozó cikkek megjegyzései közt.
Egyébként kitalált név. -
Hát, ha szigorúan vasszük, akkor ide kötődik kissé. Ha csak a témát tekintjük, akkor viszont elég erősen eltértünk a témától.
Jurij! Szerintem itt még senki sem szólt rá másra, hogy ne írjon, és ezután sem fog. ;)
Itt pedig olvashatsz az internetes viselkedésről, szokésokról. Én magam is elég lámer voltam cirka egy éve, de hidd el, hamar bele szokik az ember. Például ez a icq dolog nálam is előjött..
:) -
Az off-topic (vagy off) azt jelenti, hogy nem kötődik a témához, nem idevaló (bár én ezen a ponton vitába szállnék Molroth-tal).
-
"De ez itt ismét OFF-topic."
ezt sem tudom mi is: Off topic. hát ennyire
vagyok.
Lehet, hogy nem kellene ide írogatnom, szóljatok rám.
azért ha még annyit megmondtok, hogy az off topic mit jelent megköszönöm. -
Pedig szerintem egyszerű a megoldás. ;)
De ez itt ismét OFF-topic. -
igyekszem :-)
(kerestem a Dúalon név jelentését, de mindenhol
a te nick nevedet adta ki a gép. Így aztán egyetértek Molroth hozzászólásával! ) -
Az igazán profiktól (magyar élvonal) azért elég messze vagyok, de amit összeszedtem, az minden a netről van - bárki előtt nyitott az út!
-
[quote=Bandi]
Maradjunk abba, hogy mit tud Dúalon. :)
Maximálisan egyetértek. Dúalont kivéve itt mindenki zsenge kezdő.. ^^ -
Maradjunk abba, hogy mit tud Dúalon. :)
-
na ezért irigyellek benneteket!
hogy ti miket tudtok! -
Kicsit sok benne az én szerepem, de amúgy részemről teljesen rendben van. Kösz a hivatkozást!
-
Dúalon, ha akarod, már meg is nézheted a hírt a NetSchoolon. Mivel csak holnap jelenik meg, még lehet rajta faragni, ha úgy gondolod.
-
Főleg a gesztus tetszik. ;)
-
Molroth, azért ne borulj le! De örülök, hogy tetszik. :)
Bandi, a hírhez szinte engedélyt sem kell kérned, egy az egyben viszont légyszíves ne emeld át. -
Kitehetem a NetSchoolra? =] :sor:
-
Áááá, istencsászár vagy! :leborul:
