Aplikacja internetowa przedstawia w postaci wielu powiązanych stron WWW różnego rodzaju dane. Metoda organizacji danych oraz sposoby uzyskiwania dostępu do danych ze skryptu PHP wpływają w znacznym stopniu na proces implementacji aplikacji.
Artykuł omawia pięć przykładowych metod uzyskiwania dostępu do danych w skryptach PHP: pliki tekstowe, funkcje grupy mysqli_, klasę PEAR::DB, własną implementację aktywnych rekordów oraz aktywne rekordy generowane przez aplikację Propel.
Standardem przechowywania danych jest obecnie
SQL. Rozwiązania wykorzystujące pliki tekstowe
zostały w znacznym stopniu wyparte. Głównym
powodem jest fakt, że implementacja operacji
sortowania, wyszukiwania, filtrowania, dodawania
czy usuwania danych w oparciu o pliki jest zadaniem
trudnym i złożonym.
Wykonanie tych samych zadań
przy użyciu języka SQL jest znacznie prostsze, bardziej
efektywne i w mniejszym stopniu podatne na błędy.
Jako zaletę rozwiązań opartych o pliki tekstowe
wymieniłbym łatwość implementacji oraz to, że
pliki tekstowe możemy w łatwy sposób edytować.
Ma to znaczenie w początkowej fazie nauki PHP.
Pliki tekstowe stosuję we wczesnej fazie pracy nad
aplikacją do wypełniania bazy danych rekordami.
Przygotowanie skryptów, które wprowadzą zawartość
plików tekstowych do bazy, zajmuje kilka
chwil, a dzięki temu:
- od początku pracy nad aplikacją dysponuję bazą
danych wypełnioną rekordami, - baza danych jest poprawna (w przypadku
wstawiania danych za pomocą formularzy lub
przy użyciu aplikacji np. phpMyAdmin łatwo jest
zakłócić poprawność bazy), - po zmianie struktury bazy danych w skryptach
wstawiających dane wystarczy wykonać drobne
poprawki; dzięki temu mogę modyfikować
model bazy, nie martwiąc się o wstawione dane.
Moim zdaniem programowanie aplikacji
internetowych wymaga biegłości w operowaniu
zarówno plikami tekstowymi, jak i bazami danych.
Wprawdzie ukończona aplikacja będzie działała
wyłącznie w oparciu o bazę danych, jednak
w procesie tworzenia i debugowania aplikacji pliki
tekstowe często okazują się przydatne.
Narzędzia do przetwarzania plików tekstowych i pracy z bazami danych
Do przetwarzania plików tekstowych nie
stosuję żadnych specyficznych narzędzi. W razie
konieczności przygotowuję dedykowane skrypty
PHP. Koniecznymi operacjami, jakie należy opanować,
są:
- wyszukiwanie plików z folderu (funkcja
{stala}glob(){/stala}), - odczytywanie całego pliku ({stala}file(){/stala}, {stala}file_get_contents(){/stala}),
- zapisywanie całego pliku ({stala}file_put_contents(){/stala}),
- przetwarzanie pliku linia po linii,
- wyszukiwanie fragmentów w plikach tekstowych
(wyrażenia regularne), - \”krojenie\” pliku tekstowego na kolumny
({stala}explode(){/stala}, {stala}split(){/stala}), - zmiana struktury pliku (np. wymiana wierszy,
wymiana kolumn, dodanie wierszy, dodanie
kolumn).
W przypadku baz danych sprawa jest bardziej
skomplikowana. Tutaj bez narzędzi trudno efektywnie
pracować.
phpMyAdmin
Aplikacja phpMyAdmin pozwala na edycję
zawartości i struktury bazy danych oraz wydawanie
zapytań SQL i przygotowywanie zrzutu
bazy danych. Jest bardzo przydatna podczas
debugowania przebiegu operacji wstawiania czy
usuwania rekordów. phpMyAdmin jest niezbędny
już od pierwszych chwil pracy nad nową aplikacją.
Oto zadania, które zawsze wykonuję przy użyciu
phpMyAdmina:
- sprawdzenie poprawności utworzenia nowej bazy danych,
- programowanie zapytań SQL,
- debugowanie operacji wstawiania i usuwania rekordów,
- eksport wypełnionej bazy danych.
DBDesigner
Drugim programem, niezbędnym na etapie projektowania
bazy, jest DBDesigner. Mimo że zawiera
błędy, a jego rozwój został wstrzymany kilka lat
temu, DBDesigner jest najlepszą darmową aplikacją
do edycji modelu ERD na platformę Windows.
DBDesignerem wykonuję model ERD oraz czasami
(tj. gdy nie stosuję Propela) skrypt SQL tworzący
tabele modelu.
Propel
Trzecim narzędziem jest Propel: aplikacja do
generowania aktywnych rekordów. Wykorzystanie
Propela powoduje zaoszczędzenie wielu godzin
żmudnej pracy. Aplikacja ta generuje kod PHP,
którego ręczne przygotowanie zajmuje dużo
czasu.
Podstawowe informacje na temat Propela,
w tym proces instalacji, tutorial oraz przekształcanie
modelu ERD opracowanego DBDesignerem
do formatu Propela poznasz z artykułów \”Propel.
Tutorial\” oraz
\”DBDesigner i Propel\”.
Interfejs programowy dostępu do bazy danych
W skrypcie PHP należy wykonać operacje wyszukiwania,
pobierania czy wstawiania danych do
bazy. Programiści mówią o interfejsie programowym
dostępu do bazy danych lub o funkcjach API
(ang. Application Program Interface) dostępu do
bazy danych.
Podstawową metodą – w odniesieniu do
bazy MySQL – jest wykorzystanie funkcji rodziny
{stala}mysql_xxx(){/stala} lub {stala}mysqli_xxx(){/stala}. Funkcje
{stala}mysqli_xxx(){/stala} – w skrócie MySQL Improved
– są nowszą wersją {stala}funkcji mysql_xxx(){/stala}.
Drugim rozwiązaniem, które opiszę, będzie użycie
klasy PEAR::DB. Następnie pokażę, jak ręcznie
zaimplementować aktywne rekordy. Wreszcie na
koniec przedstawię rozwiązanie wykorzystujące
aplikację Propel.
Aplikacja: Książki Agaty Christie
Opisane aplikacje przedstawiają szczegółowe
informacje na temat twórczości Agaty Christie.
Dane do przykładu pobrałem z witryny http://
www.agathachristie.com.
Dane
Folder agatha-christie-files zawiera 86 plików tekstowych:
4-50-from-paddington.txt
a-caribbean-mystery.txt
after-the-funeral.txt
a-murder-is-announced.txt
and-then-there-were-none.txt
...
Każdy plik tekstowy opisuje jedną książkę
Agaty Christie. Na przykład w pliku five-little-pigs.
txt zawarte są dane dotyczące książki \”Five Little
Pigs\”:
TITLE|Five Little Pigs
YEAR|1943
TYPE|Novel
BEST|no
DETECTIVE|Hercule Poirot
METHOD|Poison
Z pliku tego wynika, że utwór \”Five Little Pigs\”
został opublikowany w 1943 roku (YEAR|1943).
Jest to powieść (TYPE|NOVEL), w której występuje
Herkules Poirot (DETECTIVE|Hercule Poirot). W zagadce
kryminalnej rozwiązanej przez Herkulesa
Poirot ktoś został otruty (METHOD|Poison). Utwór
ten nie należy do kanonu najlepszych dzieł pisarki
(BEST|no).
Wszystkie pliki mają identyczną strukturę.
W niektórych z nich pole DETECTIVE jest puste, zaś
pole METHOD zawiera kilka wartości oddzielonych
przecinkami.
Na przykład opis utworu \”And Then There Were
None\”, zawarty w pliku and-then-there-were-none.
txt, jest następujący:
TITLE|And Then There Were None
YEAR|1939
TYPE|Novel
BEST|yes
DETECTIVE|
METHOD|Blow to the Head,Gunshot,Poison,Strangling
Wynika z niego, że w treści pojawiają się cztery
metody popełnienia morderstwa: uderzenie w głowę
(Blow to the Head), zastrzelenie (Gunshot),
otrucie (Poison) oraz uduszenie (Strangling).
Jedynymi poprawnymi wartościami pola BEST
są wyrazy yes oraz no. Pole TYPE może przyjmować
jedną z dwóch wartości: Novel (powieść)
lub Stories (zbiór opowiadań). Pola METHOD oraz
DETECTIVE mogą być puste.
Przykładowa aplikacja
Do artykułu dołączonych jest pięć przykładowych
aplikacji. Każda z nich jest wykonana w oparciu
o identyczny szablon XHTML/CSS (wykonanie
szablonu w programie GIMP opisałem w artykule
\”GIMP. Tworzenie szablonów stron WWW\”, Magazyn INTERNET, 4/2007) i ma identyczną funkcjonalność.
Przykłady te stosują:
- pliki tekstowe (bez baz danych)
- funkcje {stala}mysqli_xxx(){/stala}
- klasę PEAR::DB
- własną implementację aktywnych rekordów
- aktywne rekordy generowane przez aplikację Propel
Od strony funkcjonalnej, aplikacja posiada
w menu głównym następujące podstrony:
- strona główna
- strona prezentująca wszystkie utwory
- strona prezentująca wszystkich detektywów
- strona prezentująca wszystkie metody
- strona prezentująca wszystkie typy
- strona prezentująca wszystkie roczniki
oraz podstrony zawierające szczegółowe zestawienia:
- szczegółowe informacje na temat wybranej książki
- zestawienie książek z udziałem wybranego detektywa
- zestawienie książek z wybraną metodą morderstwa
- zestawienie książek wybranego typu
- zestawienie książek z wybranego rocznika
Adresy URL
Omawiane aplikacje stosują zmienną
{stala}$_GET[\’id\’]{/stala} do wskazania rodzaju podstrony.
Oto poprawne wartości:
1 – strona błędu
2 – strona główna
3 – wszystkie utwory
4 – wszyscy detektywi
5 – wszystkie metody
6 – wszystkie typy
7 – wszystkie roczniki
8 – właściwości wybranego utworu
9 – utwory z wybranym detektywem
10 – utwory z wybraną metodą zbrodni
11 – utwory wybranego typu
12 – utwory z wybranego rocznika
W przypadku podstron o wartościach od 8 do
12 niezbędna jest druga zmienna {stala}$_GET[\’id2\’]{/stala}
identyfikująca wybrany rekord z bazy danych. Na
przykład adres:
index.php?id=4
wskazuje stronę prezentującą zestawienie wszystkich
detektywów. Natomiast adres:
index.php?id=8&id2=6
wskazuje stronę zawierającą szczegółowe informacje
o książce \”And Then There Were None\”. Książka
ta ma identyfikator w bazie danych równy 6.
Baza danych
Model
Bazę danych, wykorzystaną w aplikacji, należy
zaprojektować w programie DBDesigner.
Rysunek przedstawia gotowy model bazy danych. Model
ten zawiera sześć tabel:
- novel (tabela przechowująca książki)
- detective (tabela przechowująca detektywów)
- method (tabela przechowująca metody zbrodni)
- ntype (tabela przechowująca typy książek)
- pyear (tabela przechowując lata publikacji)
- novel_has_method (tabela relacji wiele do wielu, która łączy utwory z metodami).
W modelu tym występują trzy relacje typu
jeden do wielu. Relacje te łączą tabele:
- pyear i novel (rok publikacji i utwór) kluczem pyear_id
- ntype i novel (typ utworu i utwór) kluczem ntype_id
- detective i novel (detektyw i utwór) kluczem detective_id
Skrypt SQL tworzący bazę danych
Do utworzenia bazy danych stosuję skrypt SQL.
W skrypcie tym umieszczam zapytania tworzące
bazę danych oraz konto dostępu:
DROP DATABASE IF EXISTS achristie;
CREATE DATABASE achristie;
GRANT
INSERT, DELETE, UPDATE, SELECT
ON
achristie.*
TO
achadm@localhost
IDENTIFIED BY
\'murder\';
FLUSH PRIVILEGES;
USE achristie;
#
# tutaj należy wkleić kod SQL
# tworzący tabele bazy danych
#
Poniżej wklejam kod SQL tworzący tabele bazy
danych. Kod ten pobieram albo z aplikacji DBDesigner
(przycisk SQL Create Script), albo z pliku
schema.sql wygenerowanego przez Propela.
Gdy skrypt SQL jest gotowy, przygotowuję
skrypt .bat, który ułatwia wykonanie skryptu SQL:
c:\mysql\bin\mysql -uroot -pAX1BY2CZ3
W ten sposób, w każdej chwili mogę pojedynczym
kliknięciem utworzyć nową, pustą bazę
danych achristie. Po wykonaniu skryptu .bat,
sprawdź, czy została utworzona poprawna baza
danych. Wykorzystaj do tego aplikację phpMyAdmin.
Szkielet aplikacji
Bez względu na metodę dostępu do danych,
aplikacja przyjmuje identyczną postać. Składa się
ona z pięciu etapów:
- dołączanie bibliotek/inicjalizacja
- strona domyślna
- walidacja
- przetwarzanie
- przetwarzanie szablonu zaznaczonych na listingu 1.
/*
* DOŁĄCZANIE BIBLIOTEK/INICJALIZACJA
*
*/
require_once \'Smarty.class.php\';
$s = new Smarty;
...
/*
* STRONA DOMYŚLNA
*
*/
$action = 1;
if (empty($_GET)) {
$_GET[\'id\'] = \'2\';
}
/*
* WALIDACJA
*
*/
if (isset($_GET[\'id\']) && str_ievpifr($_
GET[\'id\'], 2, 12)) {
...
}
/*
* PRZETWARZANIE
*
*/
switch ($action) {
...
}
/*
* PRZETWARZANIE SZABLONU
*
*/
$s->assign(\'action\', $action);
$s->display(\'index.tpl\');
Każda z aplikacji w sposób szczegółowy sprawdza
poprawność danych pobieranych z tablicy
{stala}$_GET{/stala}. Podobieństwa i różnice opisanych aplikacji
przedstawię na przykładzie podstron o adresach:
index.php?id=3
index.php?id=8&id2=xxx.
Pierwsza z nich to strona prezentująca zestawienie
wszystkich książek, a druga - szczegółowe
dane wybranej książki.
mysqli_xxx
Rozwiązanie stosujące interfejs {stala}mysqli_xxx(){/stala} jest dość rozwlekłe. Można je uprościć,
wprowadzając klasę DBA występującą w kolejnym
rozwiązaniu. W przypadku pobierania wszystkich
rekordów z tabeli (listing 4) pojawia się zapytanie:
SELECT * FROM novel
case 3:
$novels = array();
$sql = \'SELECT * FROM novel\';
$result = mysqli_query($link, $sql);
while ($row = mysqli_fetch_assoc($result)) {
array_push($novels, $row);
}
$s->assign(\'novels\', $novels);
break;
którego wynik jest wielokrotnie przetwarzany w pętli while. Zgromadzony wynik przekazujemy
do szablonu metodą {stala}assign(){/stala}. Z racji na użycie
metody {stala}mysqli_fetch_assoc(){/stala} w szablonie
mamy dostęp do tablic asocjacyjnych (notacja
z \"kropką\"):
{section name=i loop=$novels}
{$novels[i].title}
{/section}
Taki szablon jest znacznie czytelniejszy od poprzedniego.
Natomiast uzyskanie szczegółowych danych wymaga najpierw wydania
jednego zapytania ustalającego dane konkretnej książki (z racji użycia klauzuli
n.novel_id = xxx wynikiem zapytania jest jeden rekord) oraz jednego
zapytania o listę metod popełniania zbrodni pojawiających się w utworze.
Wyniki drugiego z zapytań są przetwarzane wielokrotnie.
Dodawanie rekordów do tak przygotowanej aplikacji można realizować
za pośrednictwem plików tekstowych lub przygotować panel administracyjny
zapewniający bezpośredni dostęp do bazy danych.
case 8:
$sql = \'SELECT n.novel_id, n.title, n.best, d.detective_id, d.detective, nt.ntype_id, nt.ntype, y.pyear_id, y.pyear FROM novel as n, detective as d, ntype as nt, pyear as y WHERE n.detective_id = d.detective_id AND n.ntype_id = nt.ntype_id AND n.pyear_id = y.pyear_id AND n.novel_id =\' . $_GET[\'id2\'];
$result = mysqli_query($link, $sql);
$novel = mysqli_fetch_assoc($result);
$s->assign(\'novel\', $novel);
$numer = $_GET[\'id2\'];
$methods = array();
$sql = \"SELECT method_id, method FROM method LEFT JOIN novel_has_method USING(method_id) LEFT JOIN novel USING(novel_id) WHERE novel_id = {$numer} ORDER BY method\";
$result = mysqli_query($link, $sql);
while ($row = mysqli_fetch_assoc($result)) {
array_push($methods, $row);
}
$s->assign(\'methods\', $methods);
break;
PEAR::DB
Kolejne rozwiązanie stosuje klasę PEAR::DB. W oparciu o PEAR::DB
implementujemy klasę DBA, która ukrywa wszystkie operacje dotyczące bazy
danych. Oczywiście identyczne rozwiązanie (tj. klasę DBA) możemy przygotować
stosując interfejs {stala}mysqli_xxx(){/stala}.
Wyświetlenie listy wszystkich książek wymaga wywołania jednej metody
klasy DBA, co ilustruje listing 6. Szablon jest identyczny jak poprzednio:
{section name=i loop=$novels}
{$novels[i].title}
{/section
case 3:
$novels = $db->DBA_novel_get_all();
$s->assign(\'novels\', $novels);
break;
W przypadku szczegółowych danych książki wywołujemy dwie metody
klasy DBA (listing 7). Najpierw wyznaczamy szczegółowe dane książki, a następnie
listę metod pojawiających się w powieści.
case 8:
$novel = $db->DBA_novel_get_data($_GET[\'id2\']);
$s->assign(\'novel\', $novel);
$methods = $db->DBA_method_get_methods_of_the_novel($_GET[\'id2\']);
$s->assign(\'methods\', $methods);
break;
Z uwagi na dostrojenie zapytań SQL do konkretnych potrzeb oraz przesunięcie
kodu związanego z SQL do klasy DBA, otrzymany kod jest wyjątkowo
zwięzły. Niestety odbywa się to kosztem dodatkowej pracy: klasa DBA liczy
około 700 linii kodu.
Aktywne rekordy
Kolejnym etapem usprawniania aplikacji jest przygotowanie klas aktywnych
rekordów. Opracowując samodzielnie aktywne rekordy, możemy dostosować je
do własnych potrzeb. Jednak ilość pracy, jaką należy włożyć w przygotowanie
klas, jest stosunkowo duża.
W przypadku stosowania własnoręcznie implementowanych aktywnych
rekordów z powodu efektywności rezygnuję z generowania tablic obiektów.
Zamiast tego, w przypadku wyświetlania wszystkich rekordów z tablicy, stosuję
technikę skopiowaną z poprzedniego rozwiązania (listing 8 oraz listing 6).
Szablon też będzie w tym przypadku identyczny.
case 3:
$novels = $db->DBA_novel_get_all();
$s->assign(\'novels\', $novels);
break;
Natomiast pobierając dane dotyczące konkretnej książki (listing 9), tworzę
kolejno obiekty: {stala}$novel{/stala}, {stala}$detective{/stala}, {stala}$ntype{/stala}, {stala}$pyear{/stala}, oraz tablicę
{stala}$methods{/stala} (ponownie: technika skopiowana z poprzedniego rozwiązania).
Szablon przyjmie postać:
{$novel->Ftitle}
case 8:
$novel = new Novel($db);
$novel->findId($_GET[\'id2\']);
$s->assign(\'novel\', $novel);
$detective = new Detective($db);
$detective->findId($novel->Fdetective_id);
$s->assign(\'detective\', $detective);
$ntype = new Ntype($db);
$ntype->findId($novel->Fntype_id);
$s->assign(\'ntype\', $ntype);
$pyear = new Pyear($db);
$pyear->findId($novel->Fpyear_id);
$s->assign(\'pyear\', $pyear);
$methods = $db->DBA_method_get_methods_of_the_novel($_GET[\'id2\']);
$s->assign(\'methods\', $methods);
break;
Ftitle jest publicznym polem rekordu {stala}$novel{/stala}.
Pięć przykładowych aplikacji
Pliki tekstowe
W tym rozwiązaniu dane są przechowywane
wyłącznie w plikach tekstowych. Baza danych nie
jest zatem w ogóle wykorzystywana. Na potrzeby aplikacji przetwarzam dane pliki tekstowe i generuję
pliki o rozszerzeniu .log. W plikach tych jedna linijka
opisuje jeden rekord, separatorem kolumn jest znak
tabulacji, zapisywany w PHP jako \"\\t\". Na przykład
w pliku detectives.log przechowuję dane o wszystkich
detektywach:
1|Miss Marple
2|Hercule Poirot
3|Unknown
4|Tommy and Tuppence Beresford
zaś w pliku novels-fk.log dane o wszystkich
utworach:
1|4.50 from Paddington|no|Miss Marple| 1|Novel|1|1957|1
...
24|Five Little Pigs|no|Hercule Poirot|
2|Novel|1|1943|22
...
Dla zwiększenia czytelności, w powyższych
listingach zamiast znaku tabulacji użyłem pionowej
kreski |.
W plikach o rozszerzeniu .log po wartości
każdego pola umieściłem wartość klucza obcego,
będącego identyfikatorem wybranego rekordu. Na
przykład z wiersza:
24|Five Little Pigs|no|Hercule Poirot|2|Novel|1|1943|22
wynika, że Herkules Poirot ma identyfikator 2
(w pliku detectives.log), typ Novel ma identyfikator
1 (w pliku ntypes.log), zaś rocznik 1943 ma identyfikator
22 (w pliku pyears.log). Powieść \"Five Little
Pigs\" ma identyfikator 24.
Krótko mówiąc, pliki .log zawierają zrzut bazy
danych zapisany w pliku tekstowym. Wykonywanie
operacji wstawiania/usuwania, z racji na dużą ilość
redundancji, byłoby w takim formacie szalenie niewygodne.
Jednak dzięki obecności obcych kluczy
w opisywanej aplikacji możemy odczytywać minimalną
ilość plików i nie musimy nic wyszukiwać.
Wyświetlenie wszystkich danych z tabeli jest
widoczne na listingu 2. Sprowadza się ono do
odczytania pliku ({stala}file_get_contents(){/stala}),
pokrojenia odczytanego pliku znakiem tabulacji
({stala}string2VArray(){/stala}) oraz przekazania pokrojonych
danych do szablonu ({stala}metoda assign(){/stala}
obiektu {stala}$s{/stala}).
case 3:
$p = file_get_contents(\'dane/novels-fk.log\');
$d = string2VArray($p, \"\t\");
$novels = $d[2];
$s->assign(\'novels\', $novels);
break;
W szablonie tytuły kolejnych książek są dostępne
jako:
{section name=i loop=$novels[0]}
{$novels[1][i]}
{/section}
W celu wyświetlenia szczegółowych danych
powieści należy najpierw ustalić numer wybranego
utworu (zmienna {stala}$nr{/stala}). Po przekazaniu do szablonu
zestawu wszystkich powieści (tablica {stala}$novels{/stala})
oraz numeru {stala}$nr{/stala}, szablon zawiera wszystkie
potrzebne dane. Oczywiście takie rozwiązanie nie
sprawdzi się przy dużej liczbie rekordów, ponieważ
do szablonu przekazujemy całą tabelę powieści.
case 8:
$nr = $_GET[\'id2\'] - 1;
$s->assign(\'novels\', $novels);
$s->assign(\'nr\', $nr);
$p = file_get_contents(\'dane/novels-methods-fk.log\');
$d = string2VArray($p, \"\t\");
$methods_filtered = array(array(), array());
for ($i = 0; $i < $d[0]; $i++) {
if ($d[2][1][$i] == $_GET[\'id2\']) {
array_push($methods_filtered[0], $d[2][3][$i]);
array_push($methods_filtered[1], $d[2][2][$i]);
}
}
$s->assign(\'methods\', $methods_filtered);
break;
Następnie należy do szablonu przekazać listę
metod użytych w wybranej powieści. W tym celu odczytujemy
plik novels-methods-fk.log i na podstawie
jego zawartości filtrujemy metody zbrodni. Tablicę
{stala}$methods_filtered{/stala} otrzymaną po przefiltrowaniu
przekazujemy do szablonu metodą {stala}assign(){/stala}.
W rozwiązaniu tym kluczową rolę odgrywa
struktura plików tekstowych. W przypadku usunięcia
obcych kluczy z pliku .log, poszczególne przypadki
będą nieco bardziej skomplikowane. Dodawanie
danych do tak przygotowanej witryny wymaga:
- dodania nowych plików tekstowych w oryginalnym
formacie (tj. jeden plik tekstowy na jedną
książkę)
- przetworzenia wszystkich plików tekstowych do
zestawu ośmiu plików .log
- wymiany plików .log w aplikacji
Rozwiązanie takie sprawdzi się pod warunkiem,
że operacja dodawania danych jest wykonywana
dość rzadko i przez jedną dość kompetentną
w dziedzinie PHP osobę.
Propel
Najwygodniejsze jest bez wątpienia rozwiązanie
stosujące aplikację Propel. W tym przypadku
ręcznie przygotowujemy wyłącznie kod aplikacji
bez żadnych dodatkowych klas czy bibliotek.
W klasach wygenerowanych przez Propel ewentualnie
wprowadzamy poprawki. W omawianej
aplikacji dodałem w klasie NovelPeer metodę
{stala}getNovelsByMethod(){/stala} oraz w klasie Method-Peer metodę {stala}getMethodsByNovel(){/stala}.
W celu wydrukowania pełnej liczby książek
należy utworzyć nowe kryteria i wywołać statyczną
metodę doSelect klasy NovelPeer. Ilustruje to
listing 10. W szablonie dostęp do danych uzyskujemy
za pośrednictwem metod {stala}getXXX(){/stala}:
case 3:
$criteria = new Criteria;
$criteria->addAscendingOrderByColumn(NovelPeer::TITLE);
$novels = NovelPeer::doSelect($criteria);
$s->assign(\'novels\', $novels);
break;
{section name=i loop=$novels}
{$novels[i]->getTitle()}
{/section}
Wyświetlenie szczegółowych danych książki
wymaga utworzenia czterech osobnych obiektów:
{stala}$novel{/stala}, {stala}$detective{/stala}, {stala}$ntype{/stala}, {stala}$pyear{/stala} oraz
tablicy obiektów {stala}$methods{/stala}. Obiekt {stala}$novel{/stala} tworzymy. wywołując metodę statyczną
{stala}retrieveByPk(){/stala}, obiekty {stala}$detective{/stala},
{stala}$ntype{/stala} i {stala}$pyear{/stala} są obcymi kluczami. Do ich
utworzenia stosujemy metody {stala}getDetective(){/stala},
{stala}getNtype(){/stala} i {stala}getPyear(){/stala}. Natomiast tablicę
metod popełniania zbrodni w danej książce
zwraca ręcznie (tj. metoda taka nie jest wygenerowana
przez Propel) przygotowana metoda
{stala}getMethodsByNovel(){/stala}. Przypadek ten ilustruje
listing 11.
case 8:
$novel = NovelPeer::retrieveByPK($_GET[\'id2\'])
$s->assign(\'novel\', $novel);
$detective = $novel->getDetective();
$s->assign(\'detective\', $detective);
$ntype = $novel->getNtype();
$s->assign(\'ntype\', $ntype);
$pyear = $novel->getPyear();
$s->assign(\'pyear\', $pyear);
$methods = MethodPeer::getMethodsByNovel($novel->getNovelId());
$s->assign(\'methods\', $methods);
break;
Podsumowanie
Opisanych pięć rozwiązań jest podsumowaniem
metod, jakie od kilku lat stosuję osobiście. Każda
z technik była pewnym krokiem naprzód.
Obecnie uważam, że najbardziej korzystne
jest rozwiązanie wykorzystujące Propel. Tak jak
wprowadzenie szablonów Smarty pozwoliło
na uporządkowanie kodu pod względem cech
prezentacyjnych, tak Propel skutecznie oddziela
logikę aplikacji od dostępu do danych. Sądzę, że
liczba błędów w aplikacji przygotowywanej w taki
sposób będzie mniejsza niż w przypadku pozostałych
metod. Nie bez znaczenia jest też ilość pracy:
aktywne rekordy dostajemy za darmo.
Ostatni z przedstawionych przykładów ma
jasną, trójdzielną strukturę:
- prezentacja w szablonie Smarty
- logika w skrypcie PHP
- dostęp do danych w klasach generowanych przez Propel
Przykład ten wymagał napisania około 400
linii kodu PHP. Każdy z opisanych przykładów ma
cechę, którą nazywam \"przezroczystością PHP\".
Rolą skryptu PHP jest jedynie:
- ustalenie wybranej podstrony
- pobranie danych z bazy (lub a plików tekstowych)
- przekazanie danych do szablonu
- przetworzenie szablonu.
Skrypt PHP nie ingeruje w dane, nie modyfikuje
ich. Co otrzyma od klas dostępu do danych, to
przekazuje do szablonu. Jednym ze skutków jest
to, że rozwój aplikacji jest liniowy. Dodanie nowej
podstrony wymaga:
- w skrypcie index.php: dodania jednego przypadku w etapie walidacji i jednego przypadku w etapie przetwarzania (listing 1)
-
dodania jednego przypadku w szablonie.
Nie ma tutaj efektu spaghetti: miejsca zmian
i ich wpływ na całą aplikację jest jasny. Nie powstają
luki w bezpieczeństwie. Można powiedzieć
w terminologii algorytmiki, że dodawanie nowych
podstron ma złożoność O(3n).
Nie bez znaczenia jest również fakt, że aplikacja
może być rozwijana na każdym poziomie.
Wymiana szaty graficznej sprowadza się do zmiany
szablonu, rozwój bazy danych polega na modyfikacji
modelu, ponownym wygenerowaniu klas przez
Propel i wprowadzeniu poprawek w aplikacji,
dodawanie podstron \"ma złożoność\" O(3n), a dane
zawarte na witrynie modyfikujemy, zmieniając
zawartość bazy danych. Czego chcieć więcej?