Zapewne nie raz i nie dwa spotkałeś się z koniecznością łączenia php z kodem html. Często takie sytuacje powodują, że prace stają się trudne a błędy mnożą się w zatrważającym tempie.
W takich przypadkach nie przychodzi nic innego jak rozdzielenie kodu php i html. Wbrew pozorom nie jest to aż tak trudne, co postaram się Wam udowodnić, a przy okazji wybrać najlepszą formę.
Najprostszym sposobem na ominięcie łączenia kodu php i html jest włączenie plików za pomocą funkcji include() np:
<?
include "top.php";
$zmiena = $_GET['kto'];
echo $zmienna;
include "bottom.php";
?>
Jak widać na powyższym przykładzie włączone są poszczególne pliki odpowiadające za wygląd poszczególnych sekcji strony. Rozwiązanie te jest proste, ale niezbyt praktyczne. Gdy chcemy wygenerować część kodu html przy pomocy php staje się to bardzo niewygodne np:
<?
$kolor = ( isset($_GET['kolor']) ? $_GET['kolor'] : "czarny" );
include "templates/". $kolor ."/top.php";
// itd.
?>
Metoda ta stanowczo wysiada, jeśli idzie o generowanie kodu html na bierząco np:
plik top.php
<?
echo "Witaj $kto";
?>
plik index.php
<?
$kto = $_COOKIE['kto'];
include "top.php";
?>
Taki sposób jest na swój sposób i przejrzysty, ale jeśli idzie o kilkukrotne przetwarzanie skryptu można stwierdzić, że to staje się błędnym kołem.
Kolejnym sposobem, który wykorzystałem parę razy jest przypisanie do funkcji php kodu html np:
plik szablony.php
<?
function top()
{
echo '<img src="images/top.jpg">';
}
?>
plik index.php
<?
include "szablony.php";
top();
// dalszy kod wykorzystujący funkcje z pliku szablony.php
?>
Jest to już rozwiązanie w miarę logiczne i umożliwia prostsze korzystanie z wielu opcji kolorystycznych, wystarczy tylko zmienić odrobinę funkcję top():
<?php
function top($kolor)
{
echo $kolor.'/images/top.jpg';
}
?>
Jest to już dobre rozwiązanie i umożliwia tworzenie bardziej złożonych kodów. Niestety uniemożliwia niektóre rzeczy. Takim klasycznym przykładem jest generowanie listy wyboru z wartością "selected" lub grupy elementów typu checkbox lub radio.
<?php
function lista()
{
echo'
<select name="trudny_wybor">
<option value=0'. $selected[0] .'> Wartość zerowa</option>
<option value=1'. $selected[1] .'> Wartość pierwsza</option>
<option value=2'. $selected[2] .'> Wartość druga</option>
</select>';
}
?>
Aby ominąć ten kłopot trzeba wygenerować listę w samym skrypcie, ponieważ w żadnym systemie szablonów nie można czegoś takiego zrobić (chyba, że w Smarty). Ten problem jednak nie skreśla tej metody, bo dopiero obsługa wielu opcji językowych jest trudna. Trzeba definiować zmienne globalne w każdej funkcji bądź użyć stałych itp..
Trzecim sposobem, który jest już w miarę rozbudowany, są szablony obsługiwane przy pomocy klas. Właśnie tym sposobem postaram się zająć.
Po pierwsze klasy w php znacznie ułatwiają pracę i są wygodne. Teraz trzeba zająć się założeniami naszej klasy:
- musi obsługiwać wiele plików na raz
- pliki powinny być identyfikowane przy pomocy 'uchwytów'
- musi istnieć możliwość wstawiania szablonu w szablon
- szybkie działanie
Teraz pierwszy szkic naszej klasy:
- konstruktor, który stawia ścieżkę
- tworzenie tablicy z plikami
- możliwość ustawienia jednego pliku
- funkcja, która zapamiętuje znaczniki
- zapamiętanie jednego znacznika i wartości
- oczyszczenie tablic z danymi
- funkcja zmieniająca znaczniki
- zwracanie kodu
- wyświetlenie kodu
W ten sposób mamy już ogólny obraz naszej klasy. Nie pozostaje nic innego jak zabrać się za jej tworzenie.
Pierwsze początki. Byłbym wdzięczny za zachowanie komentarza, chociaż jest to uzależnione od Waszej dobrej woli ;).
<?php
/******************************************************************************
* miniTemplate v. 1.2
* Prosta klasa obsługi szablonów oparta na wzorze Splatch'a (splatch@wp.pl)
******************************************************************************/
class template
{
var $template = array();
var $files = array();
// Jak zauważyliście klasa posiada tylko dwa atrybuty - tablicę z wartościami wszystkich znaczników i z nazwami plików.
// Teraz prosty kontruktor klasy, który ustawi ścieżkę do szablonów.
function template($dir)
{
if($dir == "")
{
$this -> set_root("");
}
else
{
$this -> set_root($dir);
}
return true;
}
// Poniżej znajduje się funkcja, która umożliwi stworzenie pełnej ścieżki do plików. W ten sposób nie musimy mozolnie definiować nazw plików jak poniżej:
// $tmp = new template(moj/katalog/i/plik.tpl);
// $tmp -> assign('znacznik', $wartosc);
// $tmp -> parse();
// Widzicie, że sposób ten jest kompletnie spalony, bo utrudnia obsługę wielu plików. Dzięki zastosowaniu konstruktora i poniższej funkcji możemy łatwiej i szybciej definiować nazwy plików wraz z ich uchwytami. Dodatkowo nie jest już problemem operowanie na trzech plikach na raz.
function set_root($dir)
{
if($dir != "")
{
if(!is_dir($dir))
{
die("Template: nie można znaleść ścieżki do szablonu.");
}
else
{
$this -> root = $dir;
}
}
}
Aby móc coś zrobić trzeba ustalić nazwy plików oraz ich uchwyty. W ten prosty sposób będziemy mogli zdefiniować od razu kilka plików. Działanie funkcji jest przedstawione poniżej:
$tmp -> set_files(
array('wiersz' => 'wiersz.tpl',
'kolumna'=> 'kolumna.tpl)
);
Jak widzicie nie ma w tym nic trudnego i można od razu zorientować się o co chodzi, jest to rozwiązanie szybkie i praktyczne.
function set_files($files)
{
if(!is_array($files))
{
die("Template: funkcja set_files przyjmuje tylko tablice!");
}
else
{
while(list($tpl_id,$file) = @each($files))
{
$this -> files[$tpl_id] = $this -> root ."". $file;
}
}
}
Powyższe funkcja korzysta z pętli - gdy podamy tablicę jest ona przepisywana do tablicy 'files'. Od razu jest tworzona pełna ścieżka do pliku wraz z rozszerzeniem: '$this -> root ."". $file'.
Poniżej mamy jeszcze jedną dunkcję, która umożliwia wstawienie tylko jednego pliku. Jest to uproszczona funkcja set_files() - zauważcie, że podobieństwo od razu rzuca się w oczy - wnętrze pętli while i funkcji set_file() jest identyczne. Możnaby zastosować inne rozwiązanie i w pętli while powyżej napisać:
<?php
while(list($tpl_id,$file) = @each($files))
{
$this -> set_file($tpl_id,$file);
}
?>
Ale my postanowiliśmy nie korzystać z tego rozwiązania, bo może trochę opóźnić załadowanie strony. Jak już wcześniej obiecywałem funkcja set_file():
function set_file($tpl_id,$file)
{
$this -> files[$tpl_id] = $this -> root ."". $file;
}
Dodatkowo w obydwu funkcjach działa pewne oszustwo, takie backdory ;) - zamiast nazwy pliku można dać set_file('_plik','../plik.tpl') wierzcie mi - czasami się przydaje i mozna dokładniej pokatalagować pliki szablonów :). Spowoduje to wczytanie pliku z katalogu nadrzędnego.
Teraz zajmiemy się funkcją, która umożliwi wstawianie wartości do jednego pliku przy pomocy tablicy. Jeśli nie podamy tablicy funkcja zwróci błąd, a w przeciwnym wypadku zapisze wartość w tablicy 'template'. Pamiętajcie, że trzeba zacząć przeglądać tablicę od początku i stąd zastosowana funkcja reset(), abyśmy w jakimś skrajnym przypadku nie ominęli połowy znaczników.
function assign_vars($tpl_id,$values)
{
if(!is_array($values))
{
die("Template: funkcja assign_vars() przyjmuje wartsci tylko w tablicy.");
}
else
{
reset($values);
while(list($tag,$value) = @each($values))
{
$this -> template[$tpl_id][tag][$tag] = $value;
}
}
return true;
}
Skoro już mamy funkcję operującą na pliku warto napisać też to i owo odnośnie pojedyńczego wstawienia.
function assign_var($tpl_id,$tag, $value)
{
if(empty($tag))
{
die("Template: w funkcji assign_var() musisz podać nazwę znacznika!");
}
$this -> template[$tpl_id][tag][$tag] = $value;
return true;
}
Teraz bardzo ważna sprawa - jeśli chcemy używać funkcji assign_vars() w pętli trzeba oczyszczać tablice, ponieważ jeśli tego nie zrobimy otrzymamy tylko jeden wynik zamiast zamierzonej ilości. Dlatego konieczne jest oczyszczenie tablic. Posłużymy się do tego funkcją unset().
function clear($tpl_id)
{
unset($this -> template[$tpl_id]);
unset($this -> template[$tpl_id][tag]);
unset($this -> template[$tpl_id][tag][]);
}
Aby móc zamieniać znaczniki na ich wartości trzeba je zamienić. W tym celu posłużymy się funkcją str_replace(). Oczywiście jeśli ktoś chce może sobie spokojnie zastosować funkcję obsługującą wyrażenia regularne, nie mniej w tej wersji klasy jest to zbyteczne.
function compile($tpl_id,$code)
{
while(list($key, $value) = @each($this -> template[$tpl_id][tag]))
{
$tag = '{'.$key.'}';
if(!strstr($code, $tag))
{
die("Template: nie można znaleść znacznika $tag w pliku");
}
$code = str_replace($tag, $value, $code);
}
return $code;
}
Jak zauważyliście najpierw sprawdzamy czy w całości jest podany znacznik, a dopiero potem podmieniamy to, co trzeba.
Teraz przychodzi kolej na funkcję zwracającą kod. Zapewne zastanowicie się dlaczego tylko zwracającą kod? Otóż gdy zastosujemy już teraz funkcję print() lub echo() będzie nie możliwe wstawianie jednego szablonu w drugi. Zauważcie też, że dopiero tutaj jest wczytywany plik szablonu, aby odciążyć samą klasę. W niektórych przypadkach można spotkać się ze znacznym obciążeniem funkcji kompilującej lub samego konstruktora, co potrafi spowolnić działnie całego systemu.
function pparse($tpl_id)
{
if(empty($this -> files[$tpl_id]))
{
die("Template: tablica <i>files</i> dla wartości <i>$tpl_id</i> jest pusta.");
}
$file = $this -> files[$tpl_id];
$code = @implode("\n",@file($file));
$gcode = $this-> compile($tpl_id,$code);
$gcode = str_replace("\n\n", $gcode);
return($gcode);
}
Jak zauważyliście dopuszczamy możliwość, że tablica files dla uchwytu $tpl_id jest pusta.
Funkcja pparse zawiera w sobie trzy kroki:
1. $file = $this -> files[$tpl_id]; - jeśli tablica files nie jest pusta pobieramy z niej pełną ścieżkę do pliku,
2. $code = @file($file); - wczytujemy cały plik jako tablicę,
3. $gcode = $this-> compile($tpl_id,$code); - zmieniamy znaczniki na wcześniej podane wartości.
Ostatecznym krokiem jest wydrukowanie zawartości plku. Do tego celu posłuży nam funkcja parse(). Jej budowa jest prosta i myślę, że nie trzeba nic tutaj tłumaczyć.
function parse($tpl_id)
{
print($this -> pparse($tpl_id));
}
} // koniec klasy
Teraz proste zastosowanie klasy:
plik templates/header.tpl
<HTML>
<HEAD>
{META}
<META HTTP-EQUIV="Content-type" CONTENT="text/html; charset={KODOWANIE}">
<TITLE>{TITLE}</TITLE>
<LINK REL="stylesheet" HREF="templates/style.css" TYPE="text/css">
</HEAD>
<BODY bgcolor="{BGCOLOR}">
plik templates/bottom.tpl
<span class=stopka>Strona wygenerowana przy pomocy systemu szablonów!<br>
<a href="{LINK}" target=_self>Strona www</a>
</span>
</BODY>
</HTML>
plik templates/index.tpl
{HEDER}
{TRESC}
{BOTTOM}
plik test.php
<?
include "teplates.php";
$template = new template('templates/');
$template -> set_files(
array('index' => 'index.tpl',
'header' => 'header.tpl',
'bottom' => 'bottom.tpl'
));
$template -> assign_vars('header',
array('TITLE' => "Spokojnie, to tylko test!",
'META' => "",
'BGCOLOR' => '#f0f0f0',
'KODOWANIE' => 'iso-8859-2'
)
);
$template -> assign_var('bottom','LINK',"http://www.komandor.brod.pl/~splatch/");
$template -> assign_vars('index',
array('HEADER', $template -> pparse('header'),
'TRESC', "Tutaj powinna znajdować się właściwa treść strony :)",
'BOTTOM', $template -> pparse('bottom')
)
);
$template -> parse('index');
?>
Możliwe, że w krótce wprowadzę obsługę bloków. Oczywiście nie stoi nic na przeszkodzie, aby samemu wprowadzić zmiany i jeśli się uda Wam coś zmienić to piszcie, z przyjemnością poprawię się!
A teraz, gdy macie już wszystko prześledzone bierzcie się do roboty - twórzcie dynamicznie generowany kod, opcje wyboru i wszystko co możecie! |