Oracle i PHP w praktyce

Piotr Listosz

Firma Oracle dość późno zorientowała się, jak wielką
popularnością wśród programistów cieszy się język PHP. Jednak – jak
mówi przysłowie – lepiej późno niż wcale. Zwrot w kierunku systemów
linuksowych wymusił bliższe zainteresowanie się językiem PHP, wywodzącym się
z tego właśnie środowiska. Jest to informacja, która najbardziej powinna
ucieszyć… leniwych programistów, gdyż PHP to jeden z najprostszych
skryptowych języków programowania. Komuś, kto już wie co nieco na temat
programowania, wystarczy kilka dni spędzonych na studiowaniu składni i możliwości
PHP, by zacząć pisać programy, które da się wykorzystać w praktyce (nie
chodzi tu bynajmniej o programy typu „Hello World”). Do tej pory
Oracle dość intensywnie korzystał z Javy, jednak ta – w przeciwieństwie
do PHP – nie jest językiem łatwym do opanowania. Dopiero po dokładnym
zapoznaniu się z kilkoma tomami pod tytułem „Java dla
zaawansowanych”, będziemy mogli stwierdzić, że… znamy podstawy
Javy. Oczywiście możliwości Javy są dużo większe niż PHP, jednak ten
ostatni język, intensywnie rozwijany przez społeczność „open
source”, stara się doścignąć swój pierwowzór. (Można by się
sprzeczać, czy pierwowzorem PHP był C++, czy Java, jednak faktem jest, że
obiektowa składnia najnowszej wersji języka – PHP 5 – niemal
niczym nie różni się od składni Javy). Jeszcze do niedawna PHP pozwalał
jedynie na tworzenie skryptów wykonywanych po stronie serwera (tj.
interpretowanych przez serwer HTTP i odsyłanych do przeglądarki w postaci kodu
HTML), lecz po pojawieniu się biblioteki PHP-GTK, w języku tym można również
tworzyć wieloplatformowe aplikacje desktopowe. Oczywiście aplikacje można także
konstruować w Javie, jednak celem tego tekstu nie jest bynajmniej dowodzenie wyższości
jednego języka programowania nad innym.

W 28 numerze Oracle’owych PLOUG’tek ukazał się
artykuł pani Ewy Palarczyk, w którym zostały przedstawione teoretyczne
podstawy wykorzystania w PHP bazy danych Oracle. Komuś, kto chciałby zacząć
programować w PHP, ale niezbyt dobrze wie jak wykorzystać informacje zebrane w
tym artykule, przydałby się z pewnością jakiś praktyczny, niezbyt
skomplikowany, acz użyteczny przykład. Zaprezentowanie takiego właśnie przykładu,
to zadanie niniejszego tekstu.

Nie tylko MySQL

Język PHP nierozerwalnie kojarzy się z systemem obsługi
relacyjnych baz danych MySQL. Jest to bardzo szybka, łatwa w obsłudze i
– co najważniejsze – darmowa baza danych. Głównie ostatnia z
wymienionych cech zadecydowała o tym, że baza ta została automatycznie
zaakceptowana przez programistów PHP, poszukujących nietrudnych i tanich metod
tworzenia oprogramowania. Nie należy zatem oczekiwać, że ktoś, kto chce pisać
skrypty
w języku PHP (nie tylko na własny użytek, ale również
w celach zarobkowych) sięgnie po drogi, komercyjny produkt, jakim jest RDBMS
Oracle. Jednak, z drugiej strony często zdarza się, że administrator (lub użytkownik)
mający dostęp do firmowej bazy danych Oracle i potrafiący posługiwać się tą
bazą, programuje w PHP z użyciem MySQL, bo albo nie wie, że może korzystać
z Oracle’a, bądź… boi się zderzenia komercji z bezpłatnym, a zatem
pozornie gorszym, środowiskiem. (Słowo „pozornie” jest tu
zdecydowanie jak najbardziej na miejscu, gdyż posługując się przykładem PHP
można dowieść, że produkt bezpłatny wcale nie musi być gorszy). W tym
drugim przypadku nie ma sensu korzystać z bazy danych MySQL, która mimo
iż jest bardzo szybka, to jest zdecydowanie mniej bezpieczna niż Oracle, a także
charakteryzuje się skromniejszymi możliwościami (na przykład jeszcze do
niedawna serwer MySQL nie potrafił obsługiwać transakcji). Jak przekonamy się
za chwilę, wykorzystanie Oracle’a w PHP nie jest niczym trudnym.

Optymalizacja – przyjemność, czy uciążliwy obowiązek?

Przeciętny użytkownik programu komputerowego po
kilkuminutowym oczekiwaniu na rezultaty wydanego polecenia, bądź to naciśnie
klawisze Ctrl+Alt+Del, bądź chwyci za telefon i wezwie administratora. Oczywiście
żaden szanujący się administrator nie przyzna się, że od roku nie zrobił
nic, aby pomóc optymalizatorowi bazy danych; powie raczej, że „kernel się
zabendował” lub wymyśli inną tajemniczą nazwę z serii tych, od których
bledną użytkownicy. Optymalizacja jest zadaniem systemu Oracle, jednak aby
zadanie to mogło zostać wykonane poprawnie, użytkownik musi dostarczyć
systemowi informacji na temat obiektów bazy danych. Informacje te, to nic
innego jak dane statystyczne dotyczące systemu (a konkretnie tabel, indeksów i
klastrów), na podstawie których optymalizator kosztowy Oracle może wybierać
najbardziej optymalne plany wykonania zapytań SQL. Plany, o których mowa, określają
ścieżki dostępu do tabel i metody ich połączeń, charakteryzujące się
najniższym kosztem. Licząc te koszty, Oracle bierze pod uwagę liczbę bloków,
na których system musi operować, liczbę odczytów jakie trzeba wykonać w
celu przeniesienia bloków z dysku do pamięci, ilość pamięci jaka może być
potrzebna do przetworzenia danych oraz koszt przesłania danych do użytkownika.

W przypadku dużych baz danych zbieranie statystyk jest
zadaniem bardzo obciążającym system. Dlatego też nie powinno być ono
wykonywane w z góry ustalonych, regularnych odstępach czasu (użytkownicy z
pewnością nie byliby zadowoleni z takiego rozwiązania). Na ogół bazy danych
rozrastają się w sposób liniowy. Firma, która cieszy się ugruntowaną
pozycją rynkową, ma raczej stałych kontrahentów, a co za tym idzie, wystawia
co miesiąc mniej więcej tę samą ilość faktur, czy składa w przybliżeniu
tyle samo zamówień. Zdarzają się też tabele, w których gromadzone są
pewne dane stałe lub prawie stałe (na przykład plan kont, dane ewidencyjne
pracowników, itp.). Takie informacje pozwalają doświadczonym administratorom
przewidywać, w jakich odstępach czasu należy zbierać statystyki
(optymalizator korzystający z nieaktualnych statystyk, jest tak samo skuteczny
jak jego brak). Z drugiej strony, nawet „leniwie” przyrastająca
baza danych może nagle zmienić swój charakter (z pewnością takie tendencje
wykazują w okresie świątecznym bazy danych obsługujące hurtownie zabawek).
Jak zatem widać, ustalenie kiedy należy zbierać statystyki, nie jest wcale
rzeczą prostą.
W zasadzie jedynym sposobem na poradzenie sobie z tym zadaniem jest
monitorowanie gromadzonych statystyk. Tu jednak rzecz znów się komplikuje. Bieżące
statystyki przykrywają te stare, a zatem chcąc je monitorować, należy
utworzyć dodatkową tabelę i przed uruchomieniem polecenia zbierającego nowe
statystyki, przepisywać do wspomnianej tabeli te istniejące do tej pory. Nowe
wartości statystyczne należy porównywać z poprzednimi (na przykład tymi, które
zostały zebrane w poprzednim miesiącu). Jeśli są one podobne, nie ma
potrzeby dokonywania następnej analizy przez kilka kolejnych miesięcy. Jeśli
statystyki bardzo się różnią, może być konieczne ponowne ich sprawdzenie
za tydzień. Jeżeli wartości różnią się nieznacznie, powinno się
zaplanować analizowanie tabeli co miesiąc. Z czasem stanie się oczywiste, jak
często powinien być analizowany każdy segment bazy danych. O ile utworzenie
nowej tabeli i przepisanie danych to sprawy banalne,
o tyle ich porównywanie nie jest już takie proste. Ponieważ tych danych
statystycznych jest całkiem sporo, na pewno przyda się dobry kalkulator lub…
znajomość PHP. Dzięki temu językowi nie tylko będziemy mogli wyświetlić
dane w ładnie sformatowanej tabelce, ale także będziemy mogli umieścić je
na wykresie, a w takim przypadku porównanie jest już rzeczą niezmiernie prostą.
Oczywiście, część przedstawionych powyżej zadań można wykonać
w programie SQLPlus, a wykresy utworzyć w Excelu. To prawda, jednak prawdą
jest również to, że budując rozwiązanie w PHP, możemy nie tylko przygotować
program idealnie dostosowany do potrzeb użytkownika, ale również doskonale
zaznajomić się z mechanizmami, które chcemy opisać. Excel na pewno nie udostępni
nam takiej elastyczności jak język programowania, a także nie zaoferuje
takich wartości poznawczych. Oczywiście, w ostatecznej wersji utworzony
program można sprowadzić do kilku kliknięć myszką, by nie różnił się on
zbyt mocno od rozwiązania, jakie można przygotować w Excelu (obsługa
dzisiejszych systemów informatycznych składa się głównie z kliknięć myszką.
Im mniej jest tych kliknięć, tym system jest bardziej „przyjazny dla użytkownika”).

Wiemy już zatem, że chcemy zbudować system, którego celem
będzie zbieranie statystyk odnoszących się do segmentów bazy danych (dla
uproszczenia zajmiemy się jedynie tabelami) oraz porównywanie tych statystyk.
Wiemy także, że do realizacji tego zadania chcemy wykorzystać język PHP.
Zatem nie pozostaje nam nim innego, jak zabrać się do pracy.

Kilka słów na temat konfiguracji

Interpreter PHP dostarczany jest wraz z plikiem
konfiguracyjnym php.ini. Domyślnie w pliku tym obsługa bazy danych
Oracle jest wyłączona. Chcąc ją uaktywnić, należy usunąć średnik sprzed
wiersza: extension=php_oci8.dll (w systemach Windows) lub extension=php_oci8.so
(w systemach Linux).

W ten sposób do języka zostanie dołączona dodatkowa
biblioteka OCI8 (w nomenklaturze PHP zwana „rozszerzeniem”), która
pozwoli korzystać z systemu Oracle. Plik php_oci8.* powinien znajdować się w
podkatalogu wyszczególnionym w dyrektywie extension_dir. Jeśli dokładnie
przyjrzymy się nazwom bibliotek, które możemy dołączać do rdzenia języka
PHP, zauważymy plik php_oracle.dll (lub php_oracle.so). Jest to starsza
biblioteka, która ma dużo mniej możliwości niż OCI8, a poza tym nie jest
zbyt elastyczna. Zatem, jeśli tylko jest to możliwe, powinniśmy używać
bibliotek php_oci8.*. Ponadto tworząc wykresy, będziemy korzystać z funkcji
graficznych, a te znajdują się w bibliotece php_gd.dll (lub php_gd.so w
przypadku systemów linuksowych). Jest to zatem druga biblioteka, którą musimy
włączyć w pliku php.ini. Kolejna sprawa to czas, w ciągu którego
musi zakończyć się działanie skryptu. Czas ten zdefiniowany jest przy pomocy
dyrektywy max_execution_time i domyślnie wynosi 30 sekund. Oczywiście w
przypadku przetwarzania dużych segmentów, skrypt będzie musiał działać o
wiele dłużej. Ustawiając powyższą dyrektywę na wartość 0, pozwolimy na
dowolnie długie działanie skryptu.

Na ogół domyślne ustawienia pozostałych dyrektyw znajdujących
się w pliku php.ini, są w pełni zadowalające. Jak zatem widać,
przystosowanie PHP do współpracy z systemem Oracle nie powinno nastręczyć żadnych
trudności. Jeśli PHP działa jako moduł serwera Apache (lub jakiegokolwiek
innego serwera HTTP), to po dokonaniu modyfikacji w pliku php.ini, należy
ponownie uruchomić ten serwer. Nie trzeba tego robić w przypadku, gdy PHP
pracuje jako niezależny program CGI. Jednak ze względu na małą efektywność
takiego rozwiązania, jest ono stosowane dość rzadko. Teraz, gdy możemy już
połączyć się z bazą danych, spróbujmy naszkicować co mamy do zrobienia.

Algorytm

Algorytm, zgodnie z którym będzie działał tworzony przez
nas skrypt, przedstawiony jest na Rysunku 1. Nietrudno zauważyć, że algorytm
ten jest stosunkowo prosty. Taki będzie również kod PHP, przy pomocy którego
będziemy starali się zmaterializować ten algorytm. Po wykonaniu zbierającego
statystyki polecenia ANALYZE TABLE i przepisaniu wygenerowanych przez nie danych
do odrębnej tabeli, będziemy mogli porównywać informacje statystyczne
przedstawione zarówno w formie tabelarycznej, jak
i w postaci graficznej. Porównania tego typu dadzą nam pogląd na to, jak często
należy zbierać statystyki segmentów bazy danych. Mając przed oczami
algorytm, spróbujmy przystąpić do pisania kodu PHP.


Rys. 1.
Algorytm tworzonego skryptu PHP

Skrypt – dane wejściowe

Skrypty pisane w PHP wykonywane są po stronie serwera, co
oznacza, że wywołując stronę utworzoną w tym języku, wydajemy serwerowi
HTTP polecenie jej interpretacji
i odesłania do przeglądarki rezultatów powstałych w wyniku tej operacji, będących
de facto zwykłym kodem HTML. Jak zatem nietrudno się domyślić, skrypty PHP
mogą być umieszczane w kodzie HTML (interpreter PHP nie będzie przetwarzał
takiego kodu, lecz odeśle go do przeglądarki w niezmienionej postaci). Co więcej,
kod HTML można również wstawiać do skryptów, formatując w ten sposób
rezultaty przez nie wyświetlane w oknie przeglądarki.

Aby interpreter mógł rozpoznać skrypt PHP, ten ostatni
musi być umieszczony pomiędzy parami znaczników (otwierającym i zamykającym).
Takich par jest kilka, lecz najpopularniejsza to:

<?php
kod php
?>

Za znacznikiem otwierającym można umieścić program główny
(tj. fragment sterujący działaniem skryptu), mający za zadanie wywoływanie
funkcji, których definicje znajdują się w dalszej części skryptu. Można
także przyjąć wariant odwroty, tj. najpierw umieścić w kodzie definicje
funkcji, a dopiero po nich program główny. Jak widać, PHP pozwala tu na dość
dużą swobodę. Oczywiście, oprócz programowania strukturalnego, PHP umożliwia
tworzenie kodu z wykorzystaniem obiektów. Jednakże do wersji PHP 5 obiektowość
ta była nieco wymuszona i w niczym nie przypominała tej znanej z C++, czy Javy
(brak było wielu mechanizmów typowych dla programowania obiektowego, takich
jak na przykład hermetyzacja). Dopiero
w wersji PHP 5 (która obecnie znajduje się w fazie testów) zupełnie
zmieniono tę część języka, wzorując się na mechanizmach dostępnych w
Javie. Ponieważ tworząc nasz skrypt posłużymy się wersją PHP 4.2.2 (w
chwili obecnej jest to wersja najbardziej popularna), wykorzystamy strukturalny
styl programowania.

Zanim przejdziemy do omawiania skryptu, warto przypomnieć w
jaki sposób realizowana jest wymiana informacji pomiędzy przeglądarką a
serwerem HTTP i jak te mechanizmy są zaimplementowane w PHP. Dane do serwera
przekazywane są z użyciem dwóch metod: POST oraz GET. W dużym uproszczeniu
można powiedzieć, że przy pomocy tej pierwszej zazwyczaj przesyła się dane
z formularzy HTML, druga zaś służy do przesyłania informacji dołączanych
do adresów URL w postaci par nazwa-wartość. Łańcuch składający się z
takich par nosi nazwę query string. W języku PHP dostępne są
struktury zwane tablicami superglobalnymi, w których między innymi
przechowywane są dane otrzymane przy pomocy metod POST oraz GET. Tablice
superglobalne są widziane w dowolnym miejscu skryptu, bez konieczności ich
wcześniejszej deklaracji, stąd właśnie bierze się ich nazwa. Trzeba również
wspomnieć, iż w PHP występują dwa rodzaje tablic: indeksowane numerycznie
oraz asocjacyjne. W tych drugich każdy element składa się z par nazwa-wartość.
Tablice superglobalne są właśnie takimi tablicami asocjacyjnymi. Dane przesyłane
przy pomocy metody POST umieszczane są w tablicy $_POST, zaś dane przekazywane
z udziałem metody GET, w tablicy $_GET (w języku PHP nazwa każdej zmiennej
poprzedzona jest znakiem $). Na przykład, jeżeli do przekazania danych użyjemy
formularza HTML, korzystającego z metody POST, wówczas w skrypcie stanie się
dostępna tablica $_POST, w której poszczególnych elementach znajdą się
nazwy pól formularza oraz wartości wprowadzone do tych pól. Dzięki takim
mechanizmom, odczytanie w skrypcie danych wpisanych przez użytkownika do
formularza jest zadaniem banalnym. Po tych krótkich informacjach teoretycznych
możemy przejść do omawiania części sterującej naszego skryptu, a także do
funkcji wywoływanych w tej części. Kod części sterującej przedstawiony
jest w Listingu 1.

Listing 1. Część sterująca skryptu PHP

<?php

    $link = Polacz("system","oracle","oradb");

    if(!isset($_GET["opcja"]))
        switch ($_POST["akcja"])
        {
               
case "":
                       
FormularzSchemat($link);
                       
break;
               
case "Wybór tabel":
                       
FormularzTabele($link);
                       
break;
   
                        
case "Wykresy i dane":
   
                                        
WykresyDane($link);
                       
break;
   
                        
default:
        }
   
     else
   
          switch
($_GET["opcja"])
        {
   
                         
case "dane":
   
                                        
PokazDane($link);
   
                                        
break;
               
case "wykres":
                       

PokazWykres($link);
   
                                        
break;
               
default:
        }
...

Na początku skryptu wywoływana jest funkcja Polacz(), której
zadaniem jest nawiązanie połączenia z bazą danych Oracle. Kod tej funkcji
zaprezentowany jest w Listingu 2.

Listing 2. Kod funkcji nawiązującej połączenie z bazą danych

function Polacz($uzytkownik, $haslo, $baza)
   
{
   
            
$link = @OciLogon("system","oracle","oradb");
   
            
if(!$link)
         {
               
print "Nie mozna zalogowac sie do RDBMS Oracle!<BR>";
               
$error = OciError();
               
print "Błąd: ".$error["code"]." Opis:
".$error["message"];
               
exit;
         }
         else
         return $link;
}

Do nawiązania połączenia wykorzystywana jest funkcja
biblioteki OCI8 OciLogon(), której argumenty to nazwa logowania użytkownika,
jego hasło oraz nazwa lokalnej instancji bazy danych lub identyfikator bazy
danych, zapisany w pliku tnsnames.ora. Znak @ umieszczony przed nazwą
funkcji zapobiega wyświetlaniu komunikatów
o błędach (o ile takie wystąpią), które domyślnie pojawiają się w oknie
przeglądarki. Takie komunikaty są zazwyczaj niezrozumiałe dla użytkownika
serwisu, dlatego warto zablokować ich wyświetlanie, a użytkownikowi
zaprezentować komunikat, który będzie dla niego choć trochę
„przyjazny”. Tak właśnie zostało zrobione w powyższej funkcji. W
przypadku poprawnego połączenia z bazą danych, funkcja OciLogon() zwróci
identyfikator tego połączenia, który można wykorzystywać z dalszej części
skryptu, podczas odwołań do bazy danych (identyfikator ten jest przekazywany
do kodu wywołującego funkcję Polacz()). Jeżeli połączenie nie powiedzie się,
OciLogon() zwróci wartość false. Otrzymana wartość jest testowana przy
pomocy instrukcji warunkowej if. Jeśli połączenie nie zostanie nawiązane, użytkownik
otrzyma na ekranie odpowiednią informację, a skrypt zakończy działanie (kod
i komunikat o błędzie można przechwycić, dzięki wywołaniu funkcji OciError(),
która zwraca tablicę asocjacyjną o elementach „code” i „message”,
reprezentujących odpowiednio numer błędu i jego opis).

Bezpośrednio za wywołaniem funkcji Polacz() umieszczona
jest instrukcja warunkowa if, która testuje wartość elementu
„opcja” tablicy $_GET. Jak wspomnieliśmy wcześniej, w tablicy tej
przesyłane są dane przekazywane przy pomocy metody GET, a ta w naszym skrypcie
zostanie wykorzystana do przekazywania danych z łańcucha query string.
Skąd się bierze ten łańcuch, dowiemy się podczas analizy drugiej części
skryptu. Bezpośrednio po uruchomieniu skryptu wartość elementu
„opcja” nie jest ustawiona (w takim przypadku wykorzystana w
skrypcie funkcja wbudowana PHP isset() zwróci wartość false), a zatem wykona
się pierwsza część instrukcji if. Wewnątrz niej umieszczona jest instrukcja
wielokrotnego wyboru switch, testująca wartość elementu „akcja”,
znajdującego się w tablicy $_POST, a zatem w tej tablicy, do której trafiają
dane wysyłane przy pomocy metody POST. Przy pierwszym uruchomieniu skryptu
wartość ta oczywiście nie jest ustawiona, w związku z czym wykona się
pierwszy blok case (tu również moglibyśmy wykorzystać funkcję isset(), lecz
porównanie z pustym łańcuchem znaków jest w tym przypadku łatwiejsze do
realizacji). Jak zatem wynika z powyższej analizy, po uruchomieniu skryptu po
raz pierwszy, zostanie wywołana funkcja FormularzSchemat(). Kod tej funkcji
zaprezentowany jest w Listingu 3, a rezultat jej działania na Rysunku 2.

Listing 3. funkcja wyświetlająca formularz wyboru
schematu

function FormularzSchemat($link)
    {
        $statement = OciParse($link,"SELECT DISTINCT owner FROM
dba_tables");
        OciExecute($statement, OCI_DEFAULT);

        print "<B>Wybierz schemat:</B><BR><BR>";
        print "<FORM ACTION="$PHP_SELF" METHOD="POST">";
        print "<SELECT NAME="schemat">";
        while(OciFetchInto($statement,&$tab,OCI_ASSOC))
               
print "<OPTION>".$tab["OWNER"]."<BR>";
        print "</SELECT><BR><BR>";
        print "<INPUT NAME="akcja" TYPE="submit"
               
VALUE="Wybór tabel">&nbsp;&nbsp;";
        print "<FORM>";
}


Rys. 2.
Formularz wyświetlany przez funkcję
FormularzSchemat()

Jak nietrudno zauważyć, do funkcji FormularzSchemat()
przekazywany jest identyfikator połączenia z bazą danych, który jest
wykorzystywany we wszystkich funkcjach odwołujących się do tej bazy. Jedną z
najczęściej wywoływanych funkcji bazodanowych jest OciParse(); jej zadaniem
jest przetworzenie zapytania, którego treść przekazywana jest w drugim
argumencie. Jeśli w składni zapytania nie ma błędów, wówczas powyższa
funkcja zwróci jego identyfikator, w przeciwnym przypadku zostanie zwrócona
wartość false. Identyfikator o którym mowa, stanowi argument funkcji
OciExecute(), która wykonuje zapytanie. Stała OCI_DEFAULT umieszczona w drugim
argumencie tej ostatniej funkcji, blokuje tryb automatycznego zatwierdzania
transakcji. Gdyby automatyczne zatwierdzanie było pożądane, wówczas
musielibyśmy przekazać stałą OCI_COMMIT
ON_SUCCESS. Jak widać, zapytanie skierowane do bazy danych miało za zadanie
pobranie nazw wszystkich schematów dostępnych w tej bazie (tj. nazw wszystkich
właścicieli tabel). Rezultaty takiego zapytania zwracane są przez wywoływaną
w pętli funkcję OciFetchInto(), której argumentami są: identyfikator
zapytania, tablica
w której zostanie umieszczony rezultat wykonania funkcji (tj. kolejny rekord ze
zbioru wyników) oraz stała określająca typ tej tablicy (w naszym przypadku
jest to tablica asocjacyjna). Przy każdym kolejnym wykonaniu funkcji
OciFetchInto(), zwracany jest następny rekord ze zbioru wyników. Po pobraniu
ostatniego rekordu funkcja zwróci wartość false, a tym samym pętla while
zakończy działanie. Drugi argument funkcji OciFetchInto() przekazywany jest
przez referencję (decyduje o tym znak & znajdujący się przed nazwą
zmiennej). Chcąc wyświetlić wartość umieszczoną w wybranym polu zwróconego
rekordu, należy odwołać się do nazwy elementu tablicy asocjacyjnej (np. w
elemencie $tab[„OWNER”] znajduje się wartość umieszczona w polu
„OWNER” bieżącego rekordu). Jak widać, większą część funkcji
FormularzSchemat() stanowi formularz HTML, generowany przy pomocy instrukcji
print PHP (print to instrukcja wyświetlająca na ekranie łańcuch znaków).
Analizując ten formularz, warto zwrócić uwagę na dwa elementy. Pierwszy z
nich, to wartości atrybutów znacznika FORM, drugi to nazwy elementów
formularza (pola tekstowego
i przycisku powodującego przesłanie danych do serwera). Atrybut ACTION
znacznika FORM wskazuje skrypt, który zostanie wywołany w momencie wysłania
formularza do serwera. W kodzie powyższej funkcji atrybut ten ma wartość
zmiennej $PHP SELF, pod którą kryje się ścieżka do aktualnie uruchomionego
skryptu. Wobec powyższego, po zaakceptowaniu formularza zostanie po raz drugi
wywołany ten sam skrypt. Czy zatem formularz ten również pojawi się drugi
raz? Otóż nie. Ponieważ argument METHOD znacznika FORM ma wartość POST, do
wysłania danych wprowadzonych do formularza zostanie użyta metoda POST, a
zatem dane te znajdą się w tablicy $_POST. Jak już wiemy, wartość elementu
„akcja” tej tablicy decyduje o tym, jaki fragment kodu zostanie
wykonany. Jak wynika z poniższego wiersza formularza:

print "<INPUT NAME="akcja" TYPE="submit"
VALUE="Wybór tabel">&nbsp;&nbsp;";

wartością wspomnianego elementu jest ciąg znaków
„Wybór tabel”, a zatem wykona się następujący fragment kodu
sterującego:

...
switch ($_POST["akcja"])
...

               
case "Wybór tabel":
   
                        
FormularzTabele($link);
               
break;
...

Jak więc widać, sposób przekazania zmiennych z formularza
do serwera
i odczytania ich w skrypcie jest bardzo prosty. Wystarczy odwołać się do
elementów tablicy asocjacyjnej, która odpowiada metodzie zastosowanej do przesłania
formularza (gdybyśmy posłużyli się tu metodą GET, przesłanych danych
musielibyśmy szukać
w tablicy $_GET).

Funkcja FormularzTabele() jest bardzo podobna do
przedstawionej poprzednio FormularzSchemat(). Jej zadaniem jest wyświetlenie
formularza, umożliwiającego wybór określonych tabel, należących do
wskazanego wcześniej schematu. Tabele te zostaną następnie poddane działaniu
polecenia ANALYZE TABLE, zbierającego potrzebne statystyki. Parametry polecenia
o którym mowa, również ustawiane są w funkcji FormularzTabele(). Kod tej
funkcji przedstawiony jest w Listingu 4, a rezultat jej działania na Rysunku 3.


Rys. 3.
Formularz będący rezultatem działania
funkcji FormularzTabele()

Listing 4. Kod funkcji pozwalającej na wybór tabel

function FormularzTabele($link)
   
{
   
     $statement = OciParse($link,"SELECT table_name FROM dba_tables
                   
WHERE owner='".$_POST["schemat"]."'");
   
     OciExecute($statement,
OCI_DEFAULT);

        print "<B>Wybierz tabele do analizy:</B><BR><BR>";
        print "<FORM ACTION="$PHP_SELF" METHOD="POST">";
        print "<SELECT NAME="tabele[]" MULTIPLE>";
   
          while(OciFetchInto($statement,&$tab,OCI_ASSOC))
   
                        
print "<OPTION>".$tab["TABLE_NAME"]."<BR>";
   
         print "</SELECT><BR><BR>";
   
         print "<BR><B>Wybierz parametry analizy:</B><BR><BR>";
   
         print "<FORM ACTION="$PHP_SELF" METHOD="POST">";
   
         print "<INPUT NAME="rodzaj"
TYPE="radio"
   
                        
VALUE="COMPUTE" CHECKED>Dokladna (COMPUTE)<BR>";
        print "<INPUT NAME="rodzaj" TYPE="radio"
               
VALUE="ESTIMATE">Szacunkowa (ESTIMATE)<BR>";
        print "Wartość procentowa dla analizy szacunkowej:";
        print "<INPUT NAME="procent" MAXLENGTH=5 SIZE=5><BR>";
        print "<INPUT NAME="schemat"
TYPE="hidden" VALUE={$_POST["schemat"]}>";
   
         print "<INPUT NAME="akcja"
TYPE="submit"
               
VALUE="Wykresy i dane">&nbsp;&nbsp;";
        print "<FORM>";
}

W porównaniu z funkcją FormularzSchemat(), zmieniło się
tutaj zapytanie, które tym razem z widoku dba_tables pobiera nazwy tabel należących
do wybranego wcześniej schematu (nazwa tego ostatniego przechowywana jest
w elemencie „schemat” tablicy $_POST); inny jest również formularz
HTML. Warto tu zwrócić uwagę na element SELECT o nazwie
„tabele[]”; nazwa elementu podana wraz z nawiasami
charakterystycznymi dla tablicy, pozwala umieścić w nim więcej niż jedną
wartość; taką sytuację mamy wówczas, gdy pole formularza pozwala na wybór
kilku elementów (na przykład lista wielokrotnego wyboru). W tym przypadku
odpowiedni element tablicy $_POST będzie również tablicą. Interesująca jest
również następująca linia kodu:

print "<INPUT NAME="schemat" TYPE="hidden"
VALUE={$_POST["schemat"]}>";

Jak widać, w ukrytym polu formularza przekazywana jest nazwa
wybranego wcześniej schematu, tj. wartość elementu „schemat”
tablicy $_POST. Taki zabieg ma na celu zachowanie nazwy schematu o którym mowa.
Gdybyśmy usunęli tę linię kodu, wówczas po przesłaniu formularza do
serwera, bieżące wartości zapisane w tablicy $_POST (w tym również nazwa
schematu), zostałyby zastąpione nowymi. Dzięki powyższej linii nowa wartość,
która znajdzie się w polu $_POST[„schemat”], będzie taka sama jak
ta, która była tam umieszczona do tej pory, a zatem nazwa schematu zostanie
przekazana do kolejnego wywołania skryptu.

W przypadku tej funkcji pole wejściowe „akcja”
posiada wartość „Wykresy i dane”, w związku z czym taki właśnie
łańcuch znajdzie się w elemencie $_POST[„akcja”], utworzonym po
przekazaniu formularza do serwera. Zgodnie
z ustawieniem atrybutu ACTION znacznika FORM, po zaakceptowaniu formularza
zostanie ponownie wywołany bieżący skrypt, lecz tym razem wykona się taki
oto fragment kodu sterującego wywoływaniem procedur:

...
switch ($_POST["akcja"])
...
case "Wykresy i dane":
        WykresyDane($link);
        break;
...

Zadaniem wywoływanej tu funkcji WykresyDane() jest sformułowanie
zapytania ANALYZE TABLE (na podstawie danych wprowadzonych do wcześniejszych
formularzy), a także wywołanie funkcji uruchamiającej to zapytanie
i zapisującej zebrane statystyki do tabeli bazy danych. Kod funkcji WykresyDane()
przedstawiony jest w Listingu 5.

Listing 5. Funkcja uruchamiająca zbieranie statystyk

function WykresyDane($link)
    {
        $zapytanie1="ANALYZE TABLE ";
        $zapytanie2=" ".$_POST["rodzaj"]."
STATISTICS";
        if($_POST[rodzaj]=="ESTIMATE")
            $zapytanie2=$zapytanie2." SAMPLE
".$_POST["procent"]." PERCENT";

        TworzTabele($link);
        print "<TABLE BORDER>";
        print "<TR><TD>Tabela</TD><TD>Status</TD><TD>Funkcje</TD></TR>";
        foreach($_POST["tabele"] as $tabela)
        {
           
$zapytanie=$zapytanie1.$_POST["schemat"].".".$tabela.$zapytanie2;
            if (WykonajAnalize($link,$zapytanie)==true)
               
ZapiszDane($link,$tabela);
        }
        print "</TABLE>";
}

Zapytanie ANALYZE TABLE musimy wykonać w stosunku do każdej
tabeli, wybranej przy pomocy wyświetlonego wcześniej formularza. Na początku
funkcji formułujemy „stałe” elementy zapytania, następnie
tworzymy tabelę
w której będą umieszczane zebrane statystyki (za to odpowiedzialna jest
funkcja TworzTabele(); jej kod przedstawimy za chwilę). Następnie w pętli
pobieramy nazwy wszystkich wybranych tabel, uzupełniamy zapytanie o każdą
z tych nazw i wywołujemy funkcję wykonującą to zapytanie (tj. funkcję
WykonajAnalize()). W przypadku gdy analiza zostanie wykonana poprawnie, wywołujemy
funkcję ZapiszDane(), której zadaniem jest skopiowanie danych statystycznych
do utworzonej wcześniej tabeli. Warto tu zwrócić uwagę na instrukcję pętli
foreach. Jest to instrukcja, która pobiera każdy kolejny element z podanej
tablicy i umieszcza go w zmiennej prostej (w naszym przypadku w zmiennej
$tablica). Tablica, z której pobierane są elementy to
$_POST[„tabele”]. Jak zatem widać, mamy tu do czynienia z tablicą
wielowymiarową (w elemencie tablicy asocjacyjnej $_POST umieszczona jest
tablica indeksowana numerycznie). Powstanie wewnętrznej tablicy jest efektem
umieszczenia w formularzu elementu wielokrotnego wyboru (jak pamiętamy, element
ten był tworzony przez wiersz kodu: print „<SELECT NAME=”tabele[]”
MULTIPLE>”; stąd też taka a nie inna nazwa elementu tablicy $_POST).

Zanim przejdziemy do funkcji odpowiedzialnych za wykonanie
zapytania i zarejestrowanie zebranych statystyk, przyjrzyjmy się wspomnianej
wcześniej funkcji TworzTabele(), której zadaniem jest skonstruowanie tabeli
bazy danych, gdzie zostaną umieszczone zgromadzone dane statystyczne. Kod
wspomnianej funkcji przedstawiony jest w Listingu 6.

Listing 6. Kod funkcji tworzącej tabelę bazy danych Oracle

function TworzTabele($link)
    {
        $statement = OciParse($link,"SELECT count(*) FROM dba_tables
                   
WHERE table_name='ANALIZY'");
        OciExecute($statement, OCI_DEFAULT);
        OciFetch($statement);
        $count = OciResult($statement, "COUNT(*)");
        if($count==0)
        {
               
$statement = OciParse($link,"CREATE TABLE analizy
                       
(owner varchar2(30),
                       
table_name varchar2(30),
                       
last_analyzed date,
                       
num_rows number,
                       
blocks number,
                       
empty_blocks number,
                       
avg_space number,
                       
chain_cnt number,
                       
avg_row_len number,
                       
avg_space_freelist_blocks number,
                       
num_freelist_blocks number)");
        OciExecute($statement);
    }
}

W gruncie rzeczy, nie ma w tej funkcji nic nowego. Najpierw
tworzone jest zapytanie, którego celem jest odczytanie ilości tabel o nazwie
ANALIZY, znajdujących się
w bazie danych. Taka tabela może być jedna lub może ich nie być wcale (dla
uproszczenia nie sprawdzamy nazwy schematu zakładając, że inny użytkownik
nie wpadnie na pomysł, by utworzyć tabelę o takiej właśnie nazwie). Jeśli
tabela istnieje (tj. gdy spełniony jest warunek if ($count==0)), kończymy działanie
funkcji. W przeciwnym przypadku budujemy zapytanie DDL CREATE TABLE
i wywołujemy funkcję OciExecute(), wykonującą to zapytanie. W polach tabeli
ANALIZY umieścimy jedynie niewielką część rezultatów generowanych przez
zapytanie ANALYZE TABLE. Oczywiście nic nie stoi na przeszkodzie, abyśmy
rejestrowali tu wszystkie statystyki (to zależy wyłącznie od potrzeb
administratora).

Informacje jakie będziemy zapisywać w tej tabeli to:

  • właściciel tabeli (owner),
  • nazwa tabeli (table_name),
  • data i godzina wykonania ostatniej analizy (last_analyzed),
  • ilość rekordów znajdujących się w tabeli (num_rows),
  • liczba bloków używanych przez tabelę (blocks),
  • liczba bloków nie zawierających danych (empty_blocks),
  • średni pusty obszar tabeli (avg_space),
  • ilość rekordów powiązanych (chain_cnt),
  • wyrażona w bajtach średnia długość rekordu (avg_row_len),
  • średnia wielkość wolnego obszaru na liście bloków
    dostępnych dla operacji wstawiania danych (avg_space_freelist_blocks),
  • ilość bloków na liście zawierającej bloki dostępne
    dla operacji wstawiania danych (num_freelist_blocks)

Wróćmy na chwilę do pętli foreach znajdującej się w
funkcji WykresyDane() (fragment kodu o którym mowa, umieszczony jest w Listingu
5). Nazwa kolejnej tabeli wybranej wcześniej w formularzu wstawiana jest tu do
treści zapytania ANALYZE TABLE, po czym zapytanie to przekazywane jest do
funkcji WykonajAnalize(). Kod tej ostatniej przedstawiony jest w Listingu 7.

Listing 7. Funkcja odpowiedzialna za wykonanie zapytania ANALYZE
TABLE

function WykonajAnalize($link,$zapytanie)
    {
        $statement =
OciParse($link,$zapytanie);
        $rezultat = @OciExecute($statement);
        if ($rezultat==false)
        {
            $error =
OciError($statement);
            print "Analiza nie zostala wykonana.
                   
Błąd ".$error[code]." Opis: ".$error[message];
            return false;
        }
        else
        return true;
    }

Ta krótka funkcja ma za zadanie przetworzenie zapytania (OciParse()),
jego wykonanie (OciExecute) i sprawdzenie, czy ta ostatnia operacja zakończyła
się powodzeniem. Jeśli w czasie wykonywania zapytania pojawi się błąd, wówczas
na ekranie zostanie wyświetlony jego opis (OciError()), a funkcja zwróci wartość
false. W przeciwnym przypadku zostanie zwrócona wartość true, a co za tym
idzie, w kodzie wywołującym wykona się funkcja ZapiszDane(). Jej treść
zaprezentowana jest w Listingu 8, a rezultat jej działania na Rysunku 4
(rezultat ten jest de facto wynikiem działania dwóch funkcji; nagłówek
przedstawionej tabeli HTML powstaje już w funkcji WykresyDane())

Listing 8. Kod funkcji zapisującej zebrane statystyki do tabeli
bazy danych

function ZapiszDane($link,$tabela)
    {
        $statement = OciParse($link,"SELECT
owner,
                                   
table_name,
                                   
last_analyzed,
                                   
num_rows,
                                   
blocks,
                                   
empty_blocks,
                                   
avg_space,
                                   
chain_cnt,
                                   
avg_row_len,
                                   
avg_space_freelist_blocks,
                                   
num_freelist_blocks FROM dba_tables
                       
WHERE owner='".strtoupper($_POST["schemat"]).
                       
"' and table_name='".strtoupper($tabela)."'");
        OciExecute($statement, OCI_DEFAULT);
        OciFetchInto($statement,&$tab,OCI_ASSOC);

    $statement = OciParse($link,"INSERT INTO analizy
                       
(owner,table_name,last_analyzed,num_rows,
                       
blocks,empty_blocks,avg_space,chain_cnt,
                       
avg_row_len,avg_space_freelist_blocks,
                       
num_freelist_blocks) ".
                       
"VALUES                        
('".$tab["OWNER"]."','".$tab["TABLE_NAME"]."',
                       
'".$tab["LAST_ANALYZED"]."',".$tab["NUM_ROWS"].",
                       
".$tab["BLOCKS"].",".$tab["EMPTY_BLOCKS"].",
                       
".$tab["AVG_SPACE"].",".$tab["CHAIN_CNT"].",
                       
".$tab["AVG_ROW_LEN"].",
                       
".$tab["AVG_SPACE_FREELIST_BLOCKS"].",
                       
".$tab["NUM_FREELIST_BLOCKS"].")");
        OciExecute($statement);
        print "<TR><TD>$tabela</TD><TD>OK</TD><TD><A
HREF="$PHP_SELF?
                       
tabela=$tabela&schemat={$_POST["schemat"]}&opcja=
                       
dane">Dane</A>
                       
<A HREF="$PHP_SELF?
                       
tabela=$tabela&schemat={$_POST["schemat"]}&opcja=wykres">Wykres</A>
                       
</TD></TR>";
}


Rys. 4.
Tabela generowana podczas wykonywania zapytań
ANALYZE TABLE

Przedstawiona funkcja wymaga kilku słów wyjaśnienia, gdyż
w przeciwieństwie do poprzednich, została tu wykorzystana inna metoda
przekazywania informacji do serwera HTTP. Na początku przetwarzane jest i
wykonywane zapytanie SELECT, mające na celu pobranie z widoku DBA_TABLES, niektórych
statystyk zebranych przez polecenie ANALYZE TABLE. Statystyki te dotyczą
tabeli, której nazwa przekazywana jest w argumencie funkcji ZapiszDane(). Jeśli
przyjrzymy się funkcji wywołującej (tj. funkcji WykresyDane()), zauważymy,
że jest to tabela, w stosunku do której zostało właśnie wykonane polecenie
ANALYZE TABLE. Rezultaty zapytania SELECT umieszczane są w zmiennej $tab
(wskutek wywołania funkcji OciFetchInto($statement,&$tab,OCI_ASSOC);), by
następnie posłużyć do ułożenia zapytania INSERT INTO, przepisującego
odpowiednie dane statystyczne do utworzonej wcześniej tabeli ANALIZY. Po
wprowadzeniu tych danych na ekranie pojawia się tabela HTML (której
„ramy” znajdują się w funkcji WykresyDane()), a w niej nazwa
przeanalizowanej tabeli bazy danych oraz hiperłącza Dane
i Wykres, prowadzące odpowiednio do stron z danymi statystycznymi,
podanymi w formie numerycznej i z utworzonymi na podstawie tych danych
wykresami. Do adresów w obu hiperłączach, które ponownie prowadzą do bieżącego
skryptu ($PHP_SELF), dołączony jest tzw. łańcuch query string, składający
się z par nazwa-wartość, oddzielonych od siebie znakami & (sam łańcuch query
string
od adresu URL oddzielony jest znakiem zapytania).
W łańcuchu tym, nazwom tabela, schemat oraz opcja odpowiadają wartości
zmiennych $tabela (w tej zmiennej umieszczona jest nazwa określonej tabeli),
$_POST[„schemat”] (nazwa schematu, do którego należy tabela) oraz
łańcuch znaków „dane” lub „wykres”. To te dwa
ostatnie ciągi znaków decydują o tym, jaka funkcja zostanie wywołana w części
sterującej skryptu. Ponieważ dane dołączane do adresów URL przekazywane są
do serwera HTTP przy pomocy metody GET, po kliknięciu dowolnego hiperłącza i
ponownym wywołaniu naszego skryptu, pojawi się w nim tablica asocjacyjna $_GET
(jest ona zbudowana tak samo jak tablica $_POST). Element „opcja”
tej tablicy decyduje o dalszym przebiegu skryptu:

if(!isset($_GET["opcja"]))
...
}
    else
        switch ($_GET["opcja"])
        {
            case "dane":
               
PokazDane($link);
               
break;
            case "wykres":
               
PokazWykres($link);
               
break;
            default:
        }

Jak zatem widać, po kliknięciu hiperłącza „dane„,
zostanie wywołana funkcja PokazDane(), natomiast po kliknięciu hiperłącza
wykres„, funkcja PokazWykres(). Pierwsza z nich wyświetla
tabelę HTML z danymi numerycznymi, druga zaś rysuje wykres, wygenerowany na
podstawie tych danych. Czas zatem obejrzeć rezultaty działania naszego
skryptu.

Dane wyjściowe – rezultaty działania skryptu

Kod wspomnianej wcześniej funkcji PokazDane() zaprezentowany
jest w Listingu 9.

Listing 9. Funkcja wyświetlająca zebrane dane statystyczne

function PokazDane($link)
    {
        $statement =
DaneZTabAnalizy($link,$_GET["tabela"],$_GET["schemat"]);
        print "Właściciel: <B>".$_GET["schemat"]."</B><BR>";
        print "Tabela: <B>".$_GET["tabela"]."</B><BR><BR>";
        print "<TABLE BORDER>";
        print "<TR> <TD>Ost.analiza</TD>
<TD>Rekordy</TD><TD>Bloki</TD>
            <TD>Puste bloki</TD> <TD>Śr.pusty obszar</TD>
            <TD>Rekordy powiązane</TD>
<TD>Śr.dług.rek.</TD>
            <TD>Śr.wielk.wolnego obszaru</TD>
            <TD>Ilość wolnych bloków</TD></TR>";
        while(OciFetchInto($statement,&$tab,OCI_ASSOC))
            print "<TR>
<TD>".$tab["LAST_ANALYZED"]."</TD>
            <TD>".$tab["NUM_ROWS"]."</TD>
            <TD>".$tab["BLOCKS"]."</TD>
            <TD>".$tab["EMPTY_BLOCKS"]."</TD>
            <TD>".$tab["AVG_SPACE"]."</TD>
            <TD>".$tab["CHAIN_CNT"]."</TD>
            <TD>".$tab["AVG_ROW_LEN"]."</TD>
            <TD>".$tab["AVG_SPACE_FREELIST_BLOCKS"]."</TD>
            <TD>".$tab["NUM_FREELIST_BLOCKS"]."</TD></TR>";
        print "<TABLE>";
}

Powyższa funkcja wyświetla zwykłą tabelę HTML, a w niej
dane pochodzące z tabeli bazy danych. Dane te (a w zasadzie instrukcja która
je opisuje) zwracane są z funkcji DaneZTabAnalizy(), której kod przedstawiony
jest w Listingu 10.

Listing 10. Kod funkcji DaneZTabAnalizy().

function DaneZTabAnalizy($link,$tabela,$schemat)
    {
            $statement = OciParse($link,"SELECT
owner,
                       
table_name,
                       
last_analyzed,
                       
num_rows,
                       
blocks,
                       
empty_blocks,
                       
avg_space,
                       
chain_cnt,
                       
avg_row_len,
                       
avg_space_freelist_blocks,
                       
num_freelist_blocks FROM analizy
            WHERE owner='".strtoupper($schemat).
            "' and
table_name='".strtoupper($tabela)."'");
    OciExecute($statement, OCI_DEFAULT);
    return $statement;
}

Jak widać, funkcja ta jest bardzo prosta. Po sformułowaniu
i przetworzeniu zapytania SELECT, wybierającego
z tabeli ANALIZY odpowiednie informacje, jest ono wykonywane przez funkcję
OciExecute(), której rezultat działania zwracany jest do kodu wywołującego,
czyli w przedstawionym przykładzie do kodu funkcji PokazDane(). Rezultat działania
tej ostatniej można obejrzeć na Rysunku 5.

Wyświetlenie danych numerycznych okazało się banalnie
proste. Jednak w jaki sposób narysować wykres? Czy aby nie jest to zadanie na
kilka długich wieczorów? Otóż nie. Zobaczmy jak można je uprościć,
wykorzystując niestandardowe biblioteki PHP.


Rys. 5.
Tabele ze statystykami bazy danych

Grafika w PHP

W PHP jeszcze do niedawna brakowało prostych rozwiązań,
pozwalających na tworzenie wykresów. Programista musiał posługiwać się
szeregiem funkcji graficznych,
a także wykonywać sporo skomplikowanych obliczeń. Jeśli efekt końcowy miał
być zadowalający, wymagał ogromnych nakładów pracy i czasu. Potrzeba
uproszczenia tych działań zaowocowała powstaniem biblioteki PHPlot, dzięki
której profesjonalnie wyglądający wykres można utworzyć przy pomocy kilku
linii kodu.

Najnowszą wersję biblioteki PHPlot można pobrać ze strony http://sourceforge.net/projects/phplot/
lub http://www.phplot.com/. Oprócz kodu
źródłowego znajdziemy tu również dokumentację oraz kilka prostych przykładów.
PHPlot współpracuje z PHP w wersji 3.0.2 lub późniejszej. Ponadto, jak zostało
to już wcześniej wspomniane, konieczne jest udostępnienie biblioteki GD, z której
wewnętrznie korzysta PHPlot.

Biblioteka PHPlot istnieje w postaci kodu źródłowego,
umieszczonego w pliku phplot.php, aby zatem móc z niej korzystać,
wystarczy dodać do dyrektywy include_path
w pliku php.ini, ścieżkę do katalogu w którym znajduje się ten plik,
lub podawać pełną ścieżkę do pliku phplot.php
w wywołaniu funkcji include(), dołączanym do tworzonych skryptów (funkcja ta
pozwala umieszczać w skryptach odwołania do kodu znajdującego się w innych
plikach). W tym samym katalogu, co wspomniany plik, powinny znaleźć się pliki
benjamingothic.ttf (czcionka TrueType domyślnie wykorzystywana przez
funkcje biblioteczne), phplot_data.php (plik z kodem rozszerzającym
funkcjonalność biblioteki) oraz rgb.inc.php (plik zawierający
definicje kolorów).

Zanim przystąpimy do korzystania z biblioteki PHPlot, musimy
wiedzieć, w jaki sposób skonstruować dane, które zostaną przedstawione na
wykresie. Przede wszystkim należy utworzyć tablice, w których znajdą się
etykiety osi kategorii (X), a także odpowiadające im współrzędne osi wartości
(Y), dla wszystkich serii danych prezentowanych na wykresie. Tablice o których
mowa określane są mianem tablic wartości (ang. value table); stanowią
one kolejne elementy wielowymiarowej tablicy danych (ang. data table),
reprezentującej cały wykres:

tablica_danych=array(tablica_wartosci1, tablica_wartosci2,...);

Dane gromadzone we wspomnianych tablicach mogą być
interpretowane na kilka sposobów. Najpopularniejszym
i najlepiej nadającym się do zastosowania na wykresie jest format tekst-dane
(ang. text-data), w którym dane rozmieszczane są w równych odległościach
wzdłuż osi kategorii, a każdy element tablicy danych reprezentuje jedną
pozycję na osi X. Etykiety tych pozycji muszą znaleźć się
w pierwszych elementach tablic wartości; każdy kolejny element wspomnianych
tablic odpowiada jednej wartości współrzędnej Y. Oto przykładowe dane:

$dane = array( array("Styczen",2.3,4,5,6),
        array("Luty",5,6,7,8),
        array("Marzec",8,9,10,11)
);

Powyższa tablica wielowymiarowa reprezentuje punkty:
(1,2.3), (1,4), (1,5), (1,6), (2,5), (2,6)….

Dysponując wiedzą na temat sposobu przygotowania danych, możemy
przystąpić do napisania funkcji wyświetlającej wykres. Jej kod przedstawiony
jest w Listingu 11,
a rezultaty działania na Rysunku 6

Listing 11. Funkcja odpowiedzialna za wyświetlenie wykresu

function PokazWykres($link)
    {
        include_once("phplot.php");
        $bloki_plot = array();

        $statement =
DaneZTabAnalizy($link,$_GET["tabela"],$_GET["schemat"]);
        while(OciFetchInto($statement,&$tab,OCI_ASSOC))
           
array_push($bloki_plot,array($tab["LAST_ANALYZED"],
                   
$tab["NUM_ROWS"],$tab["BLOCKS"],
                   
$tab["EMPTY_BLOCKS"],
                   
$tab["NUM_FREELIST_BLOCKS"]));

        if(!empty($bloki_plot))
    {
        $plot_bloki = new PHPlot;
        $plot_bloki->SetDataValues($bloki_plot);
        $plot_bloki->SetXLabel("Daty");
        $plot_bloki->SetYLabel("Rekordy/Bloki");
        $plot_bloki->SetTitle("Ilość rekordów/bloków (tabela:
               
".$_GET["schemat"].".".$_GET["tabela"].")");
        $plot_bloki->SetLegend(array("Rekordy","Bloki","Puste
bloki",
               
"Wolne bloki"));
        $plot_bloki->DrawGraph();
    }
    else
        print "Brak danych";
}


Rys. 6.
Wykres przedstawiający dane statystyczne w
funkcji czasu

Po dołączeniu biblioteki do funkcji kodu (pierwsza linia),
tworzona jest pusta tablica, a następnie wywoływana znana nam już funkcja
DaneZTabAnalizy(), zwracająca identyfikator zapytania SELECT, pobierającego
określone statystyki z tabeli ANALIZY. Identyfikator o którym mowa
przekazywany jest do funkcji OciFetchInto(), która wywoływana w pętli,
umieszcza w tablicy $bloki_plot dane pobierane z tabeli ANALIZY (zauważmy, że
są to jedynie wybrane dane; trudno byłoby na jednym wykresie przedstawić
wszystkie statystyki). W kolejnych liniach umieszczona jest instrukcja
warunkowa, sprawdzająca, czy została wypełniona tablica $bloki_plot. Jeśli
tak, tworzony jest obiekt PHPlot, a następnie wywoływane są metody tego
obiektu, odpowiedzialne za wskazanie danych źródłowych (SetDataValues()),
ustalenie etykiet osi X i osi Y (SetXLabel() oraz SetYLabel()), przypisanie
wykresowi tytułu (SetTitle()) oraz legendy (SetLegend()) i wreszcie za wykreślenie
samego wykresu (DrawGraph()). Biblioteka PHPlot dysponuje wieloma innymi
metodami, które pozwalają na przykład zmieniać kolory punktów danych, skalę
wykresu, typ wykresu, itp. Wszystkie metody wywołuje się w podobny sposób,
jak te przedstawione w powyższej funkcji. Więcej informacji na ten temat można
znaleźć w dokumentacji biblioteki PHPlot. Chociaż nasz program pisany jest
w sposób strukturalny, to odwołując się do kodu biblioteki PHPlot musieliśmy
posłużyć się elementami programowania obiektowego (tworzyliśmy obiekt
PHPlot, a następnie wywoływaliśmy jego metody). Było to konieczne, gdyż
biblioteka PHPlot napisana jest w sposób obiektowy.

Open source i komercja

Mimo iż zaprezentowany tu przykład był stosunkowo prosty,
to – być może – pozwolił on zorientować się, jakie efekty może
przynieść zderzenie prostego i darmowego języka PHP ze skomplikowaną,
komercyjną bazą danych Oracle. Oczywiście istnieje całe mnóstwo języków,
które potrafią korzystać z bazy Oracle, a język PHP wyposażony jest w
mechanizmy, pozwalające odwoływać się do całego szeregu innych baz danych.
Jednakże celem tego tekstu nie było dowodzenie wyższości jednego języka nad
innym, ani też przekonywanie o tym, iż określona baza danych jest lepsza od
innych. W tym przypadku najważniejsze jest to, by szukać jak najprostszych
rozwiązań, które pozwoliłyby na szybkie tworzenie bezpiecznego
oprogramowania. Wbrew powszechnemu mniemaniu, zadanie to można z powodzeniem
realizować nie tylko przy zastosowaniu produktów komercyjnych. PHP jest tego
doskonałym przykładem.

Bibliografia

  1. „PHP. Jak to zrobić”, Piotr Listosz,
    Wydawnictwo Robomatic, Wrocław 2002
  2. „Wykresy w PHPlot”, Piotr Listosz, Software
    2.0 nr 3 (marzec 2003)
  3. „Oracle i PHP”, Ewa Palarczyk,
    Oracle’owe PLOUG’tki, nr 28 (grudzień 2003)
  4. „Oracle w zadaniach”, E. Honour, P. Dalberth,
    A. Kaplan, A. Metha, tłum. P. Listosz, Wydawnictwo Robomatic, Wrocław 2001