Tak więc oto rozpoczynam oficjalnie pierwszy odcinek poradnika dotyczącego pisania engine'u z prawdziwego zdarzenia dla Twojej strony WWW. Mam nadzieję, że znajdziesz w nim rozwiązanie wielu swoich problemów, a także odkryjesz nowe sposoby pisania skryptów. Opisałem tu metody, które są stosowane w znanych i chwalonych systemach for dyskusyjnych - phpBB i InvisionBoard, udoskonalone przeze mnie, oraz te, które sam opracowałem. Tak więc zapraszam do lektury.
Teoria opierania serwisów WWW na silnikach nie jest zbyt często stosowana, ale zawraca mi ona głowę od bardzo długiego czasu (dokładnie od lutego 2002 roku). Wtedy to zacząłem prace nad Forus'em - systemem for dyskusyjnych (obecnie przemianowany na FWS). Przez cały ten czas doskonaliłem jego engine (wprowadzałem nowe idee, lub chyba ze 3 razy zmieniałem całkowicie koncepcję) - jest on jakoby poligonem doświadczalnym i właśnie to powoduje, że w chwili pisania tego artykułu jest on jeszcze w takiej fazie, że nie da się go używać :). Ale nie miałem pisać o tym. Parę dni temu doszedłem do tego, że w kwestii engine'ów wymyśliłem już chyba wszystko. Pragnę się podzielić tym moim prawie rocznym doświadczeniem z Tobą, oraz stworzyć swoistą biblię dla tych, którzy zajmują się tym samym, co ja.
Stworzenie dobrego engine'u jest dość ciężką sprawą. Składa się na niego samo jądro, jakis sterownik baz danych (my go pominiemy), system zarządzania stronami, jakiś system szablonów, autoryzacja, itp. Tak więc na samym początku upewnij się, iż znasz:
- Programowanie strukturalne (czyli podstawy)
- Programowanie obiektowe (czyli klasy)
- Pojęcie o bazach MySQL
Oraz iż masz już trochę praktyki w ich stosowaniu.
Wpierw rozplanujmy sobie prace. Jest to zawsze pierwszy etap pisania bardziej zaawansowanych projektów. Nasz engine będzie oparty na klasach. Zorganizujemy je sobie podobnie do tych w engine'ach rozmaitych normalnych programów i gier. Znaczy to, że stworzymy sobie drzewo hierarchiczne, hierarchię klas itp. - jak kto woli :). Niektórzy z Was mogą nie wiedzieć, co oznaczają te dziwne terminy. Otóż zapewne wiesz, że w PHP klasy mogą dziedziczyć po sobie swoje pola i metody. Tak więc wszystko polega na tym, że jeśli jakieś dwie klasy mają jakiś element wspólny, to tworzymy klasę, w której będzie ten wspólny element, który będą dziedziczyły te dwie klasy pochodne. I jeśli wprowadzimy zmianę w tym elemencie, to będzie ona widoczna we wszystkich klasach pochodnych. Stworzyliśmy właśnie proste drzewo hierarchiczne. Łącząc kilka drzew, lub dodając do niego nowe klasy, tworzymy jeszcze większe drzewo i tak bez końca. Lecz jako że mamy do czynienia ze skryptem, nie będziemy przesadzać (i drzewa i z ilością klas :)).
Właściwe prace rozpoczniemy od stworzenia sobie drzewa katalogowego całego projektu. Będzie on zawarty w katalogu 'z-engine', w którym znajdą się: 'db', 'engine', 'sources', 'templates'. Pierwszy z nich będzie przechowywać wszystkie pliki z parametrami połączenia z bazą. Możesz tam także zamieścić instalacyjne pliki z zapytaniami SQL. W drugim będą leżały sobie pliki właściwego engine'u. Trzeci będzie przechowywał strony, oraz bloki. W czwartym znajdą się szablony i layouty.
Zajmijmy się absolutnymi podstawami - plik "basic.php". Jako, iż każde drzewo posiada swój korzeń, tak i my musimy go utworzyć deklarując klasę "obj":
<?php
class obj{
function obj(){
return 1;
}
} |
OD niej wyprowadzimy klasę, która będzie reprezentowała tak zwane "klasy SQL". Jest to pierwsze pojęcie związane całkowicie z naszym silnikiem. Oznacza ono klasę, która odpowiada tabeli w bazie danych - posiada te same pola, oraz specjalny zestaw metod do zarządzania nimi. Dzięki ujednoliceniu nazewnictwa łatwo nam będzie je obsługiwać. Oto odpowiedni kod:
class table extends obj{
function load_from_db($id){ }
function load($r){ }
function save_to_db(){ }
function update_db($id){ }
function delete_from_db($id){ }
function clear(){ }
function reset(){ }
} |
Części z nich możesz nie rozumieć, dlatego już wyjaśniam:
load_from_db($id)
Wczytuje do obiektu rekord o podanym ID
load($r)
Wczytuje do obiektu rekord z tablicy zwróconej przez mysql_fetch_assoc() lub mysql_fetch_array().
save_to_db()
Na podstawie danych w obiekcie tworzy nowy rekord i zwraca jego ID.
update_db($id)
Wykorzystuje dane w obiekcie do ich zmiany w rekordzie o podanym ID.
delete_from_db($id)
Kasuje podany rekord z bazy.
clear()
Czyści zawartość tabeli
reset()
Resetuje zawartość rekordu |
Klasy SQL bardzo będą się przydawały w codziennym pisaniu, dlatego polecam ich stosowanie dla poszczególnych komponentów serwisu, np. dla newsów, artykułów itp.
Klasa 'input' będzie obsługiwała system wejścia PHP, który (jak wiadomo) jest inny co roku :). Jej zadaniem będzie wczytanie do siebie wszystkich danych z URL'i, formularzy, oraz ustalenie adresu IP delikwenta. Oto kod:
class input extends obj{
var $input;
function load_input_variables(){
/*
Wczytuje dane do bufora wejsciowego enginu -> pamietaj, iz tablic $_POST,
$_GET, $_SERVER nie trzeba przenosic recznie do metody.
*/
if(is_array($_POST)){
for(reset($_POST); $key = key($_POST); next($_POST)){
if(is_array($_POST[$key])){
for(reset($_POST[$key]); $k_key = key($_POST[$key]); next($_POST[$key])){
$this -> input[$key][$k_key] = $_POST[$key][$k_key];
}
}else{
$this -> input[$key] = $_POST[$key];
}
}
}
if(is_array($_GET)){
for(reset($_GET); $key = key($_GET); next($_GET)){
if(is_array($_GET[$key])){
for(reset($_GET[$key]); $k_key = key($_GET[$key]); next($_GET[$key])){
$this -> input[$key][$k_key] = $_GET[$key][$k_key];
}
}else{
$this -> input[$key] = $_GET[$key];
}
}
}
// Upewniamy sie, ze adres IP jest poprawny
$this -> input['IP_ADDRESS'] = preg_replace("/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/", "\\1.\\2.\\3.\\4", $_SERVER['REMOTE_ADDR']);
}
} |
Do dyspozycji programisty oddamy jeszcze dwie przydatne klasy. Pierwsza ma za zadanie zmierzyć czas generowania strony:
class timer extends obj{
var $starttime;
function start_timer(){
$mtime = explode (' ', microtime());
$this -> starttime = $mtime[1] + $mtime[0];
}
function end_timer(){
$mtime = explode(' ', microtime());
return round(($mtime[1] + $mtime[0] - $this -> starttime), 5); // Jeśli chcesz zwiekszyć dokładnośc wyniku, pobaw się drugim parametrem funkcji round() :).
}
} |
Dzięki niej będziemy mogli sprawdzać, jak dobrze całość jest zoptymalizowana, czyli jak szybko to wszystko funkcjonuje. Czas jest mierzony od momentu rozpoczęcia inicjowania jądra do momentu zakończenia pracy.
Często mogą także pojawić się błędy. Dlatego do dyspozycji oddałem też klasę "error", która obsługuje trzy ich rodzaje: błędy systemu, ostrzeżenia, oraz zwykłe komunikaty w stylu "News został dodany". Proszę z łaski swojej nie zwracać uwagi na jakość wyglądu tychże komunikatów... :)
class error extends obj{
function general_error($message){
?>
<html>
<head>
<title>Z-Engine Błąd</title>
</head>
<body>
<table width='100%' height='85%' align='center'>
<tr>
<td valign='middle'>
<table align='center' cellpadding="4" style="padding: 2px; border: 2px solid yellow;">
<tr>
<td width="100%" align="center" nowrap="nowrap">
Błąd: <?php echo $message; ?>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
<?php
die();
}
function general_warning($message){
?>
<html>
<head>
<title>Z-Engine Ostrzeżenie</title>
</head>
<body>
<table width='100%' height='85%' align='center'>
<tr>
<td valign='middle'>
<table align='center' cellpadding="4" style="padding: 2px; border: 2px solid yellow;">
<tr>
<td width="100%" align="center" nowrap="nowrap">
Ostrzeżenie: <?php echo $message; ?>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
<?php
}
function general_info($message){
?>
<html>
<head>
<title>Z-Engine Komunikat</title>
</head>
<body>
<table width='100%' height='85%' align='center'>
<tr>
<td valign='middle'>
<table align='center' cellpadding="4" style="padding: 2px; border: 2px solid yellow;">
<tr>
<td width="100%" align="center" nowrap="nowrap">
<?php echo $message; ?>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
<?php
}
}
?> |
No i tak oto zakończyliśmy prace nad plikiem 'basic.php', który jest sercem naszego engine'u. Mam nadzieję, że wyjaśniłem wiele kwestii i moje wskazówki pomogą Ci w codziennej pracy. W następnym odcinku napiszemy sobie "prosty" system szablonów, a w kolejnych stworzymy jądro systemu, mechanizm obsługi stron i bloków, oraz system generowania menu.
Artykuł reedytowany