) musi zawierać odnośniki do wszystkich stron z miniaturami. Jej konstrukcja nie jest skomplikowana. Odnośniki są generowane w pętli for, a każdy z nich ma postać:
Jak widać, zmienia się tu wartość parametru pid, natomiast wartością iid jest indeks aktualnie wyświetlanego obrazu. Dzięki temu obraz w pełnej rozdzielczości wyświetlany w dolnej komórce tabeli będzie się zmieniał tylko na wyraźne życzenie użytkownika przeglądającego galerię, gdy kliknie on wybraną miniaturę. Równie prosta jest konstrukcja ostatniego, dolnego wiersza witryny (trzeciej warstwy
). Powinien się w nim znaleźć jedynie znacznik
powodujący wyświetlenie
wybranego przez użytkownika obrazu. Nazwa pliku z tym obrazem jest pobierana z tablicy $dir (jej indeks jest zapisany w zmiennej $iId) i przypisywana pomocniczej zmiennej $imgName. Następnie konstruowany jest znacznik
w postaci:
który jest wysyłany do przeglądarki za pomocą instrukcji echo.
Rozdział 6. ♦ Grafika i obrazy
325
Przetwarzanie grafiki Tworzenie obrazu W celu utworzenia nowego obrazu w pamięci należy wykorzystać funkcję imagecreate3 truecolor , której jako argumenty przekazuje się szerokość i wysokość obrazu. Schematyczne wywołanie ma zatem postać: imagecreatetruecolor(szerokość, wysokość)
Funkcja zwraca identyfikator obrazu (wartość typu resource), który pozwala na dalsze operacje. Gdy obraz utworzony za pomocą imagecreatetruecolor nie będzie już potrzebny, powinien zostać usunięty z pamięci za pomocą wywołania imagedestroy. Funkcja imagedestroy zwraca wartość true, jeśli operacja się powiedzie, lub false w przeciwnym wypadku. Konstrukcja skryptu (dla przykładowego obrazu o rozmiarach 200×100 pikseli) powinna być więc następująca:
W przypadku gdy chcemy wczytać obraz z pliku graficznego, należy użyć funkcji dedykowanej dla danego formatu graficznego4: imagecreatefromgif — dla formatu GIF, imagecreatefromjpeg — dla formatu JPEG, imagecreatefrompng — dla formatu PNG.
Każda z nich przyjmuje jako argument nazwę pliku graficznego (o ile konfiguracja środowiska na to pozwala, może być to też ciąg wskazujący URL obrazu). Wczytanie zawartości przykładowego pliku o nazwie obraz1.jpg, który znajduje się w katalogu /var/www/images, osiągniemy przez wykonanie instrukcji: $img = imagecreatefromjpeg("/var/www/images/obraz1.jpg");
a gdy ten obraz znajduje się w katalogu c:\www\obrazy\, dzięki instrukcji: $img = imagecreatefromjpeg("c:\\www\\obrazy\\obraz1.jpg");
lub: $img = imagecreatefromjpeg("c:/www/obrazy/obraz1.jpg");
3
Można także użyć funkcji imagecreate, zaleca się jednak korzystanie z imagecreatetruecolor.
4
Dostępne są także funkcje dla mniej popularnych formatów, np. gd2, bmp, xpm itp. Ich opis można znaleźć w dokumentacji PHP.
326
PHP5. Praktyczny kurs
Zapisywanie plików graficznych Obraz utworzony w pamięci (niezależnie od tego, jak ta czynność została wykonana) może być w prosty sposób zapisany do pliku lub wysłany do standardowego wyjścia (np. przeglądarki). W zależności od wybranego formatu należy użyć jednej z dedykowanych funkcji: imagegif — dla formatu GIF, imagejpeg — dla formatu JPEG, imagepng — dla formatu PNG.
Każda z nich przyjmuje dwa argumenty — pierwszy określa obraz, natomiast drugi nazwę pliku. Jeśli parametr określający nazwę pliku zostanie pominięty, obraz jest wysyłany do standardowego wyjścia. Aby na przykład zapisać na dysku plik typu JPEG o nazwie image.jpg utworzony z obrazu wskazywanego przez zmienną $img, zastosujemy konstrukcję: imagejpeg($img, "image.jpg");
aby natomiast wysłać taki obraz do przeglądarki — konstrukcję5: imagejpeg($img);
Funkcje imagejpg oraz imagepng mogą też przyjmować trzeci argument określający jakość pliku wynikowego. W przypadku imagejpg jest to wartość od 0 do 100, przy czym 0 oznacza największą kompresję i największą utratę jakości (ale najmniejszy rozmiar pliku wynikowego), natomiast 100 — najmniejszą kompresję i najmniejszą utratę jakości (ale największy rozmiar pliku wynikowego). W przypadku imagepng jest to wartość od 0 do 9, gdzie 0 oznacza brak kompresji, a 9 największą kompresję6. Jeżeli stosowany jest trzeci argument, a obraz powinien być wysłany do przeglądarki, jako nazwy pliku (drugi argument) należy użyć wartości null, np.: imagejpeg($img, null, 85);
Kolory Wiele funkcji operujących na obrazie wymaga podania w postaci argumentu koloru, który ma być użyty (np. funkcja rysująca linie wymaga podania koloru linii). Aby jednak móc skorzystać z danego koloru, niezbędne jest wcześniejsze jego zaalokowanie, które odbywa się przez wywołanie funkcji imagecolorallocate. Jej wywołanie ma postać: imagecolorallocate($obraz, czerwony, zielony, niebieski)
gdzie $obraz to identyfikator zwrócony przez jedną z funkcji tworzących obraz, natomiast pozostałe argumenty określają poszczególne składowe koloru w formacie RGB. Mogą być one podawane w postaci dziesiętnej lub szesnastkowej. Wybrane kolory 5
W takim przypadku niezbędne byłoby również wcześniejsze wysłanie odpowiedniego nagłówka HTTP.
6
Ten argument jest dostępny od PHP w wersji 5.1.2. Począwszy od wersji 5.1.3, jest też możliwe stosowanie czwartego argumentu określającego filtry, jakie mają być zastosowane do obrazu.
Rozdział 6. ♦ Grafika i obrazy
327
i odpowiadające im składowe RGB zostały przedstawione w tabeli 6.1. Funkcja imagecolorallocate zwraca identyfikator koloru lub wartość false (w wersjach PHP poniżej 5.1.3 — –1), jeśli jej działanie nie zakończyło się powodzeniem. Przykładowo, uzyskanie indeksu koloru czerwonego dla obrazu wskazywanego przez zmienną $img będzie wymagało wywołania w postaci: $czerwony = imagecolorallocate($img, 255, 0, 0);
lub: $czerwony = imagecolorallocate($img, 0xFF, 0, 0);
Tabela 6.1. Wybrane kolory i odpowiadające im składowe RGB Kolor
Składowa R
Składowa G
Składowa B
Beżowy
245
245
220
Biały
255
255
255
Błękitny
0
191
255
Brązowy
139
69
19
0
0
0
Czerwony
255
0
0
Ciemnoczerwony
192
0
0
0
0
139
169
169
169
0
100
0
Fiolet
238
130
238
Koralowy
255
127
80
Niebieski
0
0
255
Oliwkowy
128
128
0
Purpurowy
128
0
128
Srebrny
192
192
192
70
130
180
128
128
128
Czarny
Ciemnoniebieski Ciemnoszary Ciemnozielony
Stalowoniebieski Szary
0
255
0
Żółtozielony
192
255
62
Żółty
255
255
0
Zielony
Jeśli chcemy wypełnić wybranym kolorem pewien obszar obrazu, można skorzystać z funkcji imagefill, której wywołanie ma postać: imagefill($obraz, wspx, wspy, kolor);
gdzie $obraz określa obraz, wspx i wspy — współrzędną punktu, od którego rozpocznie się procedura wypełniania, a kolor — kolor wypełnienia. Aby na przykład w obrazie wskazywanym przez zmienną $img wypełnić kolorem niebieskim obszar rozpoczynający się od punktu o współrzędnych (15, 25), należy zastosować instrukcję:
328
PHP5. Praktyczny kurs $niebieski = imagecolorallocate($img, 0, 0, 0xFF); imagefill($img, 15, 25, $niebieski);
Informacje o obrazie Jeśli chcemy otrzymać informację o rozmiarach znajdującego się w pamięci obrazu, możemy skorzystać z funkcji imagesx (z ang. image size x) i imagesy (z ang. image size y). Pierwsza z nich zwraca szerokość, a druga wysokość obrazu. W obu przypadkach jako argument należy podać odnośnik do obrazu. Jeśli zatem utworzyliśmy lub wczytaliśmy z pliku obraz, który jest identyfikowany przez zmienną $img, to jego rozmiary możemy uzyskać za pomocą przykładowych wywołań: $szerokosc = imagesx($img); $wysokosc = imagesy($img);
Równie przydatną funkcją jest getimagesize, która (nieco wbrew swojej nazwie) zwraca wiele przydatnych informacji, a nie tylko rozmiary obrazu. Jej wywołanie ma postać: getimagesize(nazwa_pliku[, $tablica]);
gdzie nazwa_pliku określa nazwę pliku, z którego dane chcemy odczytać (może to być zarówno plik lokalny, jak i zdalny), a tablica — opcjonalną tablicę, w jakiej zostaną zapisane dodatkowe informacje (w obecnej wersji PHP zapisywane są w niej jedynie niektóre znaczniki APP z plików JPG). Wartością zwracaną przez getimagesize jest tablica o następującej zawartości: indeks 0 — szerokość obrazu w pikselach, indeks 1 — wysokość obrazu w pikselach, indeks 2 — określenie typu pliku (dostępne wartości zostały
zaprezentowane w tabeli 6.2), indeks 3 — ciąg znaków zapisany w postaci height="wysokość" width="szerokość", który określa rozmiary obrazu, klucz mime — ciąg znaków określający typ mime pliku. klucz channels — liczba kanałów (3 dla RGB, 4 dla CMYK). klucz bits — liczba bitów, na których zapisywany jest kolor (z czego
bezpośrednio wynika maksymalna możliwa liczba kolorów w obrazie).
Generowanie grafiki Na obrazie utworzonym lub wczytanym w sposób opisany w sekcji „Tworzenie obrazu” można za pomocą odpowiednich funkcji wykonywać różne operacje graficzne. Na kolejnych stronach omówione zostanie kilka z nich. Funkcje te pozwalają m.in. na rysowanie linii, łuków, prostokątów, wielokątów i elips.
Rysowanie linii Do rysowania linii służy funkcja imageline, której wywołanie ma postać: imageline($obraz, xp, yp, xk, yk, kolor)
Rozdział 6. ♦ Grafika i obrazy
329
Tabela 6.2. Typy plików rozpoznawane przez funkcję getimagesize Typ
Wartość
GIF
1
JPEG
2
PNG
3
SWF
4
PSD
5 6
BMP 7
7
TIFF_II
TIFF_MM
8
8 9
JPC JPEG_2000
9
JP2
10
JPX
11
JB2
12
SWC
13
IFF
14
WBMP
15
XBM
16
9
17
ICO
gdzie: $obraz — to określenie obrazu, xp — współrzędna x początku linii, yp — współrzędna y początku linii, xk — współrzędna x końca linii, yk — współrzędna y końca linii, kolor — określenie koloru.
Należy przy tym pamiętać, że współrzędne lewego górnego rogu to (0, 0). Przykładowo, aby utworzyć obraz o białym tle i o rozdzielczości 100×30 pikseli, który by zawierał dwie przekątne (pierwszą koloru zielonego, a drugą czerwonego), i zapisać go na dysku pod nazwą obraz1.jpg, należy wykonać kod widoczny na listingu 6.4.
7
Kolejność bajtów zgodna z formatem Intela (z ang. intel byte order).
8
Kolejność bajtów zgodna z formatem Motoroli (z ang. motorola byte order).
9
Począwszy od PHP w wersji 5.3.0.
330
PHP5. Praktyczny kurs
Listing 6.4. Rysowanie przekątnych
Obraz jest tworzony za pomocą funkcji imagecreatetruecolor. Ta funkcja zwraca zasób będący odwołaniem do obrazu (czy też identyfikatorem obrazu). A zatem w dalszej części skryptu obraz będzie reprezentowany przez zmienną $img. W uproszczeniu często mówi się po prostu o obrazie $img. Następnie za pomocą funkcji imagecolorallocate alokowane są trzy kolory: biały, zielony i czerwony. Wyniki działania funkcji (identyfikatory kolorów) są przypisywane zmiennym $bialy, $zielony i $czerwony. Obszar obrazu jest wypełniany kolorem białym dzięki funkcji imagefill, a przekątne są rysowane przez funkcję imageline. Na zakończenie obraz jest zapisywany do pliku o nazwie obraz1.jpg (odpowiada za to funkcja imagejpg) oraz usuwany z pamięci (odpowiada za to funkcja imagedestroy).
Rysowanie prostokątów Do rysowania prostokątów służą funkcje imagerectangle i imagefilledrectangle, których wywołanie ma postać: imagerectangle($obraz, xp, yp, xk, yk, kolor) imagefilledrectangle($obraz, xp, yp, xk, yk, kolor)
gdzie: $obraz — to określenie obrazu, xp — współrzędna x lewego górnego rogu, yp — współrzędna y lewego górnego rogu, xk — współrzędna x prawego dolnego rogu, yk — współrzędna y prawego dolnego rogu, kolor — określenie koloru.
Pierwsza funkcja tworzy sam kontur prostokąta, a druga prostokąt wypełniony kolorem. Aby zatem utworzyć obraz o białym tle i o rozdzielczości 100×100 pikseli, który by zawierał cztery ułożone w szachownicę prostokąty w kolorach niebieskim i zielonym (w tym dwa wypełnione własnym kolorem i dwa wypełnione kolorem tła), oraz zapisać go na dysku pod nazwą obraz1.jpg, należy wykonać kod widoczny na listingu 6.5.
Rozdział 6. ♦ Grafika i obrazy
331
Listing 6.5. Tworzenie prostokątów
Rysowanie wielokątów Do rysowania wielokątów służą funkcje imagepolygon i imagefilledpolygon, których wywołanie ma postać: imagepolygon($obraz, $punkty, ile, kolor) imagefilledpolygon($obraz, $punkty, ile, kolor)
gdzie: $obraz — to określenie obrazu, $punkty — tablica zawierająca współrzędne kolejnych punktów, ile — liczba wierzchołków, kolor — określenie koloru.
Tablica punkty musi być zbudowana w taki sposób, że klucze 0 i 1 zawierają współrzędne x i y pierwszego wierzchołka, klucze 2 i 3 — drugiego wierzchołka, 4 i 5 — trzeciego wierzchołka itd. Funkcja imagepolygon rysuje sam kontur wielokąta, natomiast imagefilledpolygon wielokąt wypełniony wskazanym kolorem. Aby na przykład uzyskać obraz o białym tle i o rozdzielczości 320×200 pikseli, który by zawierał sześciokąt wypełniony kolorem żółtym, należy wykorzystać kod widoczny na listingu 6.6. Listing 6.6. Tworzenie sześciokąta wypełnionego kolorem żółtym
332
PHP5. Praktyczny kurs imagejpeg($img, "obraz1.jpg"); imagedestroy($img); ?>
Rysowanie elips Do rysowania elips służą funkcje imageelipse i imagefilledelipse, których wywołanie ma postać: imageelipse($obraz, xc, yc, xw, xh, kolor) imagefilledelipse($obraz, xc, yc, xw, xh, kolor)
gdzie: $obraz — to określenie obrazu, xc — współrzędna x środka elipsy, yc — współrzędna y środka elipsy, xw — szerokość elipsy (średnica pozioma), xh — wysokość elipsy (średnica pionowa), kolor — określenie koloru.
Funkcja imageelipse rysuje sam kontur, natomiast imagefilledelipse elipsę wypełnioną wskazanym kolorem. Aby na przykład uzyskać obraz o białym tle i o rozdzielczości 320×200 pikseli, który by zawierał czarny okrąg oraz elipsę wypełnioną kolorem niebieskim, należy wykorzystać kod widoczny na listingu 6.7. Listing 6.7. Rysowanie elips
Rysowanie wycinków elips Do rysowania wycinków elips służą funkcje imagearc i imagefilledarc, których wywołanie ma postać: imagearc($obraz, xc, yc, xw, xh, k1, k2, kolor) imagefilledarc($obraz, xc, yc, xw, xh, k1, k2, kolor, styl)
gdzie:
Rozdział 6. ♦ Grafika i obrazy
333
$obraz — to określenie obrazu, xc — współrzędna x środka elipsy, yc — współrzędna y środka elipsy, xw — szerokość elipsy (średnica pozioma), xh — wysokość elipsy (średnica pionowa), k1 — kąt (w stopniach) określający linię początkową, k2 — kąt (w stopniach) określający linię końcową, kolor — określenie koloru, styl — styl wypełnienia.
Pierwsza z nich rysuje sam łuk, druga wycinek elipsy zgodnie ze stylem przekazanym w argumencie styl. Argument ten składa się z następujących stałych, które można łączyć przez sumę bitową (operator |): IMG_ARC_PIE — wycinek, którego końce są połączone łukiem, standardowo
wypełniony zadanym kolorem. IMG_ARC_CHORD — wycinek, którego końce połączone są linią prostą,
standardowo wypełniony zadanym kolorem. IMG_ARC_NOFILL — wycinek nie będzie wypełniany kolorem. IMG_ARC_EDGED — w połączeniu z IMG_ARC_NOFILL powoduje, że zostanie
wykreślony pełny kontur wycinka. Opcje IMG_ARC_PIE i IMG_ARC_CHORD wzajemnie się wykluczają, czyli nie mogą być użyte jednocześnie. Wygenerowanie obrazu widocznego na rysunku 6.4 osiągniemy zatem, stosując kod zaprezentowany na listingu 6.8. Rysunek 6.4. Wykorzystanie różnych opcji funkcji imagefilledarc
Listing 6.8. Różne sposoby rysowania wycinków elips
110, 100, 80, 80, 0, 90, $czerwony); 90, 100, 80, 80, 90, 180, $czerwony); 90, 90, 80, 80, 180, 270, $czerwony); 110, 90, 80, 80, 270, 360, $czerwony);
334
PHP5. Praktyczny kurs imagefilledarc($img, imagefilledarc($img, imagefilledarc($img, imagefilledarc($img, IMG_ARC_EDGED);
310, 290, 290, 310,
100, 80, 80, 0, 90, $niebieski, IMG_ARC_PIE); 100, 80, 80, 90, 180, $niebieski, IMG_ARC_CHORD); 90, 80, 80, 180, 270, $niebieski, IMG_ARC_NOFILL); 90, 80, 80, 270, 360, $niebieski, IMG_ARC_NOFILL |
imagejpeg($img, "obraz1.jpg"); imagedestroy($img); ?>
Przetwarzanie obrazów Nakładanie filtrów Wśród funkcji przetwarzających obrazy znajduje się imagefilter, która nakłada na nie jeden z dostępnych filtrów. Jej wywołanie ma postać: imagefilter($obraz, filtr[, arg1[, arg2[, arg3]]])
Argument $obraz określa obraz poddawany zmianie, filtr — rodzaj filtra (dostępne wartości zostały podane w tabeli 6.3), natomiast arg1, arg2, arg3 i arg4 to parametry niezbędne do działania niektórych filtrów. Funkcja zwraca wartość true, jeśli jej działanie zakończyło się sukcesem, lub false w przeciwnym razie. Tabela 6.3. Filtry dostępne dla funkcji imagefilter Nazwa filtra
Opis
IMG_FILTER_NEGATE
Inwersja kolorów.
IMG_FILTER_GRAYSCALE
Konwersja obrazu do odcieni szarości.
IMG_FILTER_BRIGHTNESS
Zmiana poziomu jasności obrazu. Do jego ustalenia należy wykorzystać argument arg1.
IMG_FILTER_CONTRAST
Zmiana kontrastu obrazu. Do ustalenia kontrastu należy wykorzystać argument arg1.
IMG_FILTER_COLORIZE
Działanie podobne do IMG_FILTER_GRAYSCALE z tą różnicą, że istnieje możliwość ustalenia koloru bazowego. Kolor ten należy podać w formacie RGB, gdzie R to wartość arg1, G — wartość arg2, B — wartość arg3, a kanał alfa (przezroczystość) — arg4 (dostępny od PHP 5.2.5).
IMG_FILTER_EDGEDETECT
Uwypuklenie krawędzi w obrazie.
IMG_FILTER_EMBOSS
Wytłoczenie obrazu.
IMG_FILTER_GAUSSIAN_BLUR
Rozmycie obrazu metodą Gaussa.
IMG_FILTER_SELECTIVE_BLUR
Rozmycie obrazu.
IMG_FILTER_MEAN_REMOVAL
Uwypuklenie krawędzi.
IMG_FILTER_SMOOTH
Wygładzenie (zmiękczenie, rozmycie) obrazu. Parametr arg1 pozwala na określenie poziomu intensywności efektu.
IMG_FILTER_PIXELATE
Pikselizacja obrazu. Argument arg1 określa rozmiar bloku, natomiast arg2 ustawiony na true (domyślnie false) włącza przetwarzanie zaawansowane (większe wygładzenie obrazu wynikowego).
Rozdział 6. ♦ Grafika i obrazy
335
Można zatem napisać przykładowy skrypt, który podczas wywoływania w wierszu poleceń będzie otrzymywał nazwę pliku graficznego w formacie JPG oraz wartość całkowitą, a także wykona zmianę poziomu kontrastu tego pliku. Działający w ten sposób kod został zaprezentowany na listingu 6.9. Listing 6.9. Zmiana poziomu kontrastu
Skalowanie W celu przeskalowania obrazu do zadanych rozmiarów można użyć funkcji imagecopyresized lub imagecopyresampled. W rzeczywistości pobierają one określony parametrami wycinek z obrazu źródłowego i wstawiają go w miejsce obrazu docelowego (również określone parametrami). Jeśli wyznaczone obszary obrazu źródłowego i docelowego nie będą takie same, nastąpi odpowiednie przeskalowanie. Różnica pomiędzy imagecopyresized a imagecopyresampled jest taka, że druga z nich podczas skalowania dokonuje interpolacji pikseli, dzięki czemu uzyskuje się dokładniejszy efekt. Obie funkcje przyjmują identyczne zestawy argumentów, a ich wywołania mają postać: imagecopyresized($obraz_docelowy, $obraz_źródłowy, xd, yd, xs, ys, wd, hd, ws, hs); imagecopyresampled($obraz_docelowy, $obraz_źródłowy, xd, yd, xs, ys, wd, hd, ws, hs);
gdzie: $obraz_docelowy — określa obraz docelowy, $obraz_źródłowy — określa obraz źródłowy, xd — współrzędna x lewego górnego rogu kopiowanego obszaru w obrazie
docelowym,
336
PHP5. Praktyczny kurs yd — współrzędna y lewego górnego rogu kopiowanego obszaru w obrazie
docelowym, xs — współrzędna x lewego górnego rogu kopiowanego obszaru w obrazie
źródłowym, ys — współrzędna y lewego górnego rogu kopiowanego obszaru w obrazie
źródłowym, wd — szerokość kopiowanego obszaru w obrazie docelowym, hd — wysokość kopiowanego obszaru w obrazie docelowym, ws — szerokość kopiowanego obszaru w obrazie źródłowym, hs — wysokość kopiowanego obszaru w obrazie źródłowym.
Jak by to wyglądało w praktyce? Załóżmy na przykład, że mamy plik o nazwie obraz. jpg zawierający obraz o rozdzielczości 800×600 pikseli. Chcielibyśmy powiększyć jego środkowy fragment o wielkości 160×120 pikseli do rozdzielczości 400×300 (chodzi zatem o wykonanie operacji przedstawionej schematycznie na rysunku 6.5) i tak przetworzoną część zapisać w pliku o nazwie obraz2.jpg.
Rysunek 6.5. Schemat skalowania obrazu
Kolejność wykonywanych czynności będzie zatem następująca: 1. Wczytanie obrazu z pliku obraz1.jpg za pomocą funkcji imagecreatefromjpeg. 2. Utworzenie nowego obrazu za pomocą funkcji imagecreatetruecolor. 3. Przeskalowanie wycinka obrazu oryginalnego za pomocą funkcji imagecopyresampled. 4. Zapisanie przeskalowanego obrazu do pliku obraz2.jpg za pomocą funkcji imagejpeg. 5. Usunięcie obrazów z pamięci za pomocą funkcji imagedestroy.
Pozostaje jeszcze ustalenie argumentów funkcji imagecopyresampled. Nie jest to trudne. Ponieważ przeskalowany fragment będzie zajmował cały obszar obrazu docelowego, współrzędne lewego górnego wierzchołka to 0,0, a rozdzielczość to 400×300
Rozdział 6. ♦ Grafika i obrazy
337
pikseli. Długość i szerokość powiększanego fragmentu obrazu źródłowego też jest nam znana — to 160×120. Pozostaje więc ustalenie współrzędnych lewego górnego wierzchołka tego obszaru. Można to wyliczyć ze wzorów: x = (szerokość obrazu źródłowego - szerokość powiększanego wycinka) / 2 y = (wysokość obrazu źródłowego - wysokość powiększanego wycinka) / 2
co w tym przypadku daje x = 320 i y = 240. Kod skryptu wykonującego przedstawione zadania został zaprezentowany na listingu 6.10. Listing 6.10. Skrypt skalujący środkową część obrazu do zadanej rozdzielczości
Obracanie Do obracania obrazów służy funkcja imagerotate, której wywołanie ma postać: imagerotate($obraz_źródłowy, kąt_obrotu, kolor_tła, ignoruj_przezroczyste);
Obraca ona obraz wskazywany przez $obraz_źródłowy o kąt kąt_obrotu, wypełniając ewentualne powstałe przy tym puste obszary kolorem kolor_tła. Ustawienie argumentu ignoruj_przezroczyste na wartość inną niż 0 powoduje, że fragmenty z oznaczoną przezroczystością będą ignorowane (wartość domyślna to 0; argument wprowadzony w PHP 5.1.0). Funkcja zwraca przetworzony obraz. Aby zatem obrócić obraz wskazywany przez zmienną $img o 45 stopni (wypełniając puste obszary kolorem białym), a obraz wynikowy przypisać zmiennej $obraz, należałoby wykonać instrukcje: $bialy = $imagecolorallocate($img, 255, 255, 255); $obraz = imagerotate($img, 45, $bialy);
Ćwiczenia do samodzielnego wykonania Ćwiczenie 20.1. Zmodyfikuj skrypt generujący galerię z listingu 6.2 w taki sposób, aby zachowywał się prawidłowo, gdy w katalogu przeznaczonym do przechowywania obrazów:
338
PHP5. Praktyczny kurs
a) nie ma żadnego pliku, b) zapisane są pliki o rozszerzeniach innych niż jpg, gif, png, c) znajdują się podkatalogi. Ćwiczenie 20.2. Napisz taki skrypt realizujący galerię obrazów, żeby każdy obraz mógł zawierać podpis pobierany z pliku tekstowego o nazwie zgodnej z nazwą pliku z obrazem, ale o rozszerzeniu txt. Ćwiczenie 20.3. Napisz skrypt, który będzie wykonywał skalowanie obrazu z pliku graficznego typu JPG (lub innego) do zadanej rozdzielczości. Nazwa pliku oraz rozdzielczość powinny być podawane w postaci argumentu. Ćwiczenie 20.4. Napisz skrypt skalujący wszystkie obrazy typu JPG (lub innego) zapisane w danym katalogu o zadaną wartość procentową. Ćwiczenie 20.5. Napisz skrypt wykonujący konwersje pomiędzy różnymi formatami plików graficznych.
Rozdział 7.
Obsługa sieci Lekcja 21. Połączenia, poczta i FTP Tablica $_SERVER PHP udostępnia wiele informacji dotyczących aktualnie obsługiwanego połączenia oraz serwera, na którym jest uruchomione. Z niektórych z nich już korzystaliśmy. Odczytywane były na przykład: adres IP urządzenia, z którego nawiązane zostało połączenie, dane dotyczące przeglądarki, argumenty wiersza poleceń itp. Czas jednak usystematyzować te informacje. Wszystkie tego typu dane są zebrane w tablicy $_SERVER, której kolejne klucze zawierają różne informacje. Jest to tablica superglobalna, czyli dostępna w każdym miejscu skryptu. Jej standardowa zawartość została przedstawiona w tabeli 7.1. Trzeba jednak wspomnieć, że różne wersje PHP uruchamiane w różnych środowiskach systemowych mogą zawierać również dodatkowe wpisy. Przykładowy skrypt wyświetlający informacje zawarte w całej tablicy $_SERVER (nazwy kluczy wraz z odpowiadającymi im wartościami) jest widoczny na listingu 7.1, a efekt jego działania został zaprezentowany na rysunku 7.1. Tabela 7.1. Zawartość tablicy $_SERVER Klucz
Znaczenie
Przykładowa wartość
PHP_SELF
Ścieżka dostępu do bieżącego skryptu względem katalogu głównego serwera WWW.
/index.php
argv
Tablica zawierająca argumenty przekazane z wiersza poleceń. Pierwszym argumentem jest nazwa pliku wykonywalnego PHP. W przypadku gdy skrypt został wywołany nie z wiersza poleceń, ale za pomocą metody GET, tablica zawiera część ciągu URL wywołującego skrypt, począwszy od znaku ?.
Tablica
340
PHP5. Praktyczny kurs
Tabela 7.1. Zawartość tablicy $_SERVER — ciąg dalszy Klucz
Znaczenie
Przykładowa wartość
argc
Liczba argumentów zawartych w tablicy argv.
Liczba całkowita
GATEWAY_INTERFACE
Numer wersji interfejsu CGI wykorzystywanego przez serwer.
CGI/1.1
SERVER_ADDR
Adres IP serwera, na którym uruchomiono skrypt.
SERVER_NAME
Nazwa serwera, na którym pracuje PHP.
www.mojadomena.com, localhost
SERVER_SOFTWARE
Ciąg znaków zawierający identyfikator serwera, który jest wysyłany w nagłówkach HTTP.
Microsoft-IIS/5.0, Apache/2.0.54
SERVER_PROTOCOL
Nazwa i wersja protokołu użytego w odwołaniu do bieżącej strony.
HTTP/1.0
REQUEST_METHOD
Nazwa metody użytej w zapytaniu.
GET, HEAD, POST, PUT
REQUEST_TIME
Czas, w którym rozpoczęło się zapytanie (podany jako znacznik czasu Uniksa). Dostępny od PHP 5.1.0.
Liczba całkowita
QUERY_STRING
Część ciągu URL wywołującego skrypt, począwszy od znaku ? (o ile taki występuje).
pid=1&iid=10
DOCUMENT_ROOT
Katalog główny dla bieżącego skryptu.
/var/www/html, c:\Inetpub\wwwroot\
HTTP_ACCEPT
Wartość nagłówka HTTP Accept:, o ile jest obecny.
text/xml,application/xml, application/xhtml+xml
HTTP_ACCEPT_CHARSET
Wartość nagłówka HTTP Accept-Charset:, o ile jest obecny.
iso-8859-1,*,utf-8, ISO-8859-2, utf-8;q=0.7,*;q=0.7
HTTP_ACCEPT_ENCODING
Wartość nagłówka HTTP Accept-Encoding:, o ile jest obecny.
gzip, deflate
HTTP_ACCEPT_LANGUAGE
Wartość nagłówka HTTP Accept-Language:, o ile jest obecny.
pl,en-us;q=0.7,en;q=0.3
HTTP_CONNECTION
Wartość nagłówka HTTP Connection:, o ile jest obecny.
Keep-Alive
HTTP_HOST
Wartość nagłówka HTTP Host:, o ile jest obecny.
www.mojadomena.com, localhost
HTTP_REFERER
Wartość nagłówka HTTP Referer:, o ile jest obecny. Adres URL strony, z której nastąpiło odwołanie do strony bieżącej.
Adres URL
HTTP_USER_AGENT
Wartość nagłówka HTTP User-Agent:, o ile jest obecny. Określenie przeglądarki, która wysłała zapytanie.
Mozilla/5.0 (Windows; U; Windows NT 5.0; pl; rv:1.8) Gecko/20051111 Firefox/1.5
HTTPS
Wartość niepusta, o ile skrypt został wywołany za pomocą protokołu HTTPS.
-
Rozdział 7. ♦ Obsługa sieci
341
Tabela 7.1. Zawartość tablicy $_SERVER — ciąg dalszy Klucz
Znaczenie
Przykładowa wartość
REMOTE_ADDR
Adres IP, z którego nawiązano bieżące połączenie.
10.0.0.23
REMOTE_HOST
Nazwa domenowa komputera, z którego nawiązano bieżące połączenie. Istnienie pola zależy od konfiguracji serwera WWW.
pc1.mojadomena.com
REMOTE_PORT
Port, z którego nawiązano bieżące połączenie.
Liczba całkowita
SCRIPT_FILENAME
Bezwzględna ścieżka dostępu do bieżącego skryptu.
/var/www/html/index.php, c:\Inetpub\www\index.php
SERVER_ADMIN
Wartość pola konfiguracyjnego SERVER_ADMIN serwera WWW (dla serwera Apache).
root@localhost
SERVER_PORT
Port używany przez serwer WWW do komunikacji.
80
SERVER_SIGNATURE
Ciąg znaków opisujący wersję serwera oraz nazwę hosta wirtualnego, o ile taka opcja została włączona.
Apache/2.0.54 (Fedora) Server at pc24. mojadomena.com Port 80
PATH_TRANSLATED
Ścieżka dostępu do aktualnego skryptu w systemie plików serwera.
/var/www/html/index.php, c:\www\index.php
SCRIPT_NAME
Nazwa aktualnie wykonywanego skryptu.
/index.php
REQUEST_URI
Identyfikator URI użyty w żądaniu dostępu do bieżącej strony.
/index.php
PHP_AUTH_DIGEST
Zawartość nagłówka Authorization: w przypadku korzystania z serwera Apache i uwierzytelniania typu Digest HTTP Authentication.
-
PHP_AUTH_USER
Nazwa użytkownika w przypadku uwierzytelniania HTTP.
-
PHP_AUTH_PW
Hasło użytkownika w przypadku uwierzytelniania HTTP.
-
AUTH_TYPE
Rodzaj uwierzytelniania HTTP.
-
PATH_INFO
/dane/pliki Część ścieżki dostępu do skryptu znajdująca się w adresie URL za nazwą pliku ze skryptem, ale przed znakiem ?.
ORG_PATH_INFO
Oryginalna wartość PATH_INFO przed przetworzeniem przez PHP.
Listing 7.1. Wyświetlenie zawartości tablicy $_SERVER $val){ echo "$key = $val
"; } ?>
/dane/pliki
342
PHP5. Praktyczny kurs
Rysunek 7.1. Przykładowa zawartość tablicy $_SERVER
Adresy IP Znajomość adresu IP, z którego nadeszło połączenie z witryną, pozwala nie tylko na śledzenie ruchu na stronie i zapisywanie go w logu, tak jak to było prezentowane w przykładzie 3.25 z lekcji 12., ale również na przykład na kontrolę dostępu do strony. Możliwe są tu różne warianty, np. można zablokować połączenia z niektórych adresów lub też dopuszczać połączenia jedynie z wybranych adresów. Spróbujmy wykonać tego typu przykład. Powstanie skrypt, który pozwoli blokować wybrane adresy IP. W przypadku wykrycia zablokowanego adresu użytkownikowi zamiast treści witryny będzie wyświetlany komunikat o braku dostępu, np. taki, jaki jest widoczny na rysunku 7.2. Kod strony wyświetlającej taki komunikat będzie zapisywany w oddzielnym pliku o nazwie bannedip.html. Rysunek 7.2. Blokada wybranych adresów IP
Sam skrypt, którego kod został zaprezentowany na listingu 7.2, ma bardzo prostą konstrukcję. Wszystkie zablokowane adresy należy umieścić w osobnym pliku o nazwie bannedip.txt (każdy adres w osobnym wierszu). Należy przy tym zwrócić uwagę, aby
Rozdział 7. ♦ Obsługa sieci
343
był on zapisany w lokalizacji niedostępnej z poziomu WWW, tak aby jego treść nie mogła być w prosty sposób poznana przez osoby postronne. Tak więc pierwszym zadaniem skryptu jest wczytanie zawartości pliku bannedip.txt, co jest wykonywane przez funkcję file. Listing 7.2. Kontrola dostępu do witryny ze względu na adres IP
Jak wiadomo z rozdziału 4. (lekcja 11.), wynikiem działania file jest tablica zawierająca w następujących po sobie komórkach kolejne odczytane wiersze. Adres IP, z którego nadeszło połączenie, jest odczytywany z tablicy $_SERVER i zapisywany w zmiennej $ip. Zmienna ta jest następnie wykorzystywana jako pierwszy z argumentów funkcji array_search. Drugim argumentem jest tablica $ips_arr. Działanie tej funkcji będzie takie, że jeśli odnajdzie ona w tablicy $ips_arr wartość wskazywaną przez $ip, to zwróci nazwę klucza, pod którym ta wartość się znajduje, a w przeciwnym razie wartość false. Jeśli więc wynik działania array_search będzie różny od false, wczytana zostanie treść pliku bannedip.html, jeśli natomiast wartość ta będzie równa false, kod PHP zakończy działanie i wyświetlona zostanie właściwa treść strony. W równie prosty sposób możemy napisać skrypt, który będzie wczytywał różne wersje witryny w zależności od tego, z jakiego adresu IP nadeszło połączenie. Dane zapiszemy w trzech plikach: ips1.txt, ips2.txt i ips3.txt, a każdy z nich będzie przechowywał adresy przynależne do trzech różnych grup. Każda taka grupa będzie miała przypisaną swoją stronę startową: index1.php, index2.php i index3.php. W przypadku gdy adres, z którego nadeszło połączenie, nie będzie zapisany w żadnym z wymienionych plików, zostanie wczytana treść strony index0.php. Działający w opisany sposób skrypt został zaprezentowany na listingu 7.3. Listing 7.3. Różne wersje strony ze względu na adres IP
344
PHP5. Praktyczny kurs } return false; } $ips1 = file("/var/wwwdata/ips1.txt", FILE_IGNORE_NEW_LINES); $ips2 = file("/var/wwwdata/ips2.txt", FILE_IGNORE_NEW_LINES); $ips3 = file("/var/wwwdata/ips3.txt", FILE_IGNORE_NEW_LINES); $ip = $_SERVER['REMOTE_ADDR']; if(check($ips1, $ip)){ include "./index1.php"; } else if(check($ips2, $ip)){ include "./index2.php"; } else if(check($ips3, $ip)){ include "./index3.php"; } else{ include "./index0.php"; } ?>
Dane z plików są wczytywane za pomocą funkcji file i umieszczane w tablicach $ips1, $ips2 i $ips3, natomiast adres IP jest pobierany z tablicy $_SERVER i umieszczany w zmiennej $ip. Za sprawdzenie, czy adres zapisany w $ip znajduje się w którejś z tablic, odpowiada funkcja check. Zwraca ona wartość true, jeśli adres został znaleziony, lub false w przeciwnym razie. Dzięki temu za pomocą złożonej instrukcji warunkowej if else może zostać wczytany odpowiedni plik z właściwą treścią strony. Treść wybranego pliku jest dołączana za pomocą instrukcji include. Funkcja check przyjmuje dwa argumenty. Pierwszym z nich ($arr) jest tablica, która będzie przeszukiwana, drugim ($ip) — wartość poszukiwana (czyli adres IP). Ponieważ funkcja file w przypadku niepowodzenia zwraca wartość false, a więc istnieje możliwość, że wartością parametru $arr nie będzie prawidłowa tablica, dlatego w funkcji check najpierw jest wykonywana instrukcja if($arr), a dalsze czynności są wykonywane jedynie wtedy, gdy wynikiem jej działania jest true (czyli kiedy $arr jest różne od false). Sama weryfikacja danych jest wykonywana analogicznie do poprzedniego przykładu. Warto w tym miejscu zwrócić uwagę, że taki skrypt można nieco usprawnić. W zaprezentowanej wersji najpierw są bowiem wczytywane dane z wszystkich trzech plików zawierających adresy IP, a dopiero potem wykonywane jest badanie, czy poszukiwany adres znajduje się w którymś z plików. Jeśli zatem adres pobrany z tablicy $_SERVER występuje na przykład już w pierwszym pliku (ips1.txt), to treść pozostałych dwóch (ips2.txt i ips3.txt) była pobierana na próżno. Sposób modyfikacji przykładu pozostanie jednak ćwiczeniem do samodzielnego wykonania.
Rozdział 7. ♦ Obsługa sieci
345
Jak rozpoznać przeglądarkę? Jednym z problemów, z jakimi spotykają się webmasterzy, jest rozpoznanie typu przeglądarki internetowej, którą wykorzystuje osoba przeglądająca stronę. Jak to zrobić? Jak wiadomo z przykładu 3.25 z rozdziału 3., informacje tego typu można odnaleźć w tablicy $_SERVER pod indeksem 'HTTP_USER_AGENT'. Analizując zapisany tam ciąg znaków, z dużym prawdopodobieństwem można stwierdzić, z którym produktem ma się do czynienia. Ograniczmy się przy tym do rozpoznawania jedynie tych najpopularniejszych: Chrome Firefox Internet Explorer Opera
Powstanie funkcja, która będzie zwracała ciąg znaków zawierający nazwę rozpoznanej przeglądarki lub też ciąg nieznana, jeśli rozpoznanie nie będzie możliwe. Rozpoznawanie będzie się odbywało za pomocą przeszukiwania ciągów znaków odczytanych z tablicy $_SERVER. Przyjmiemy przy tym następujące zasady: Jeśli zostanie odnaleziony ciąg znaków opera, wówczas uznamy,
że mamy do czynienia z przeglądarką Opera. Jeśli zostanie odnaleziony ciąg znaków msie, wówczas uznamy,
że mamy do czynienia z przeglądarką Internet Explorer. Jeśli zostanie odnaleziony ciąg znaków firefox, wówczas uznamy,
że mamy do czynienia z przeglądarką Mozilla Firefox. Jeśli zostanie odnaleziony ciąg znaków chrome, wówczas uznamy,
że mamy do czynienia z przeglądarką Google Chrome. Działająca w opisany sposób funkcja jest widoczna na listingu 7.4. Jej działanie rozpoczyna się od sprawdzenia, czy w tablicy $_SERVER istnieje klucz o nazwie HTTP_USER_ AGENT. Niektóre przeglądarki można bowiem skonfigurować w taki sposób, aby nie wysyłały ciągów identyfikacyjnych, a w takiej sytuacji nie będzie w tej tablicy wymienionego klucza. Jeśli klucza nie będzie, zwrócony zostanie ciąg nieznana oznaczający, że rozpoznanie się nie powiodło. Listing 7.4. Funkcja rozpoznająca typ przeglądarki
346
PHP5. Praktyczny kurs } else if(strpos($browser_info, "msie") !== false){ return("msie"); } else if(strpos($browser_info, "firefox") !== false){ return("firefox"); } else if(strpos($browser_info, "chrome") !== false){ return("chrome"); } else{ return("nieznana"); } } echo getBrowserType(); ?>
Jeśli jednak klucz HTTP_USER_AGENT jest obecny, jego zawartość jest odczytywana i zapisywana w zmiennej $browser_info. Wcześniej (za pomocą funkcji strtolower) jest też dokonywana konwersja ciągu, tak aby wszystkie litery zostały zamienione na małe, co ułatwi dalsze operacje. Ciąg zapisany w $browser_info jest przeszukiwany za pomocą znanej z lekcji 8. funkcji strpos w złożonej instrukcji warunkowej if...else. Równie łatwo można uzyskać dane o rodzaju systemu operacyjnego wykorzystywanego przez danego użytkownika serwisu. Taka informacja jest również zawarta w ciągu znajdującym się w tablicy $_SERVER pod indeksem HTTP_USER_AGENT. Jeśli odnajdziemy w nim podciąg: windows, linux lub mac, będziemy mogli z dużym prawdopodobieństwem określić typ systemu operacyjnego jako Windows, Linux lub MacOS. Funkcja wykonująca takie zadanie będzie miała postać analogiczną do przedstawionej na listingu 7.4 getBrowserType i została zaprezentowana na listingu 7.5. Podobnie jednak jak w przypadku poprzedniego przykładu należy pamiętać, że wyniki działania takiej funkcji mogą być zafałszowane przez przeglądarki wysyłające nieprawdziwe dane identyfikacyjne. Listing 7.5. Funkcja rozpoznająca typ systemu operacyjnego
Rozdział 7. ♦ Obsługa sieci
347
} else{ return("nieznany"); } } echo getSystemType(); ?>
Połączenie FTP PHP oferuje szereg funkcji pozwalających na pełną obsługę protokołu FTP. Nie ma potrzeby, aby je tutaj wszystkie omawiać, warto jednak omówić przykład pokazujący, jak za pomocą PHP pobrać plik z serwera FTP, a także jak wysłać taki plik. Pierwszą czynnością, którą należy wykonać, jest nawiązanie połączenia za pomocą funkcji ftp_ connect. Przyjmuje ona trzy argumenty, a jej schematyczne wywołanie ma postać: ftp_connect(host[, port[, timeout]])
Parametr host określa nazwę lub adres IP serwera, z którym chcemy się połączyć, port to port, na którym pracuje usługa FTP, natomiast timeout określa czas (w sekundach), po jakim próba wykonywania operacji FTP ma zostać przerwana, o ile nie uzyska się odpowiedzi. Standardowo przyjmowane wartości dla domyślnych argumentów port i timeout to odpowiednio 21 i 90. Funkcja ftp_connect zwraca identyfikator połączenia (wartość typu resource), którym należy się posługiwać przy wykonywaniu dalszych operacji, lub też false, jeśli połączenia nie udało się nawiązać. Po nawiązaniu połączenia należy wykonać procedurę logowania. Służy do tego funkcja ftp_login o schematycznej postaci: ftp_login (ftpid, użytkownik, hasło)
ftpid to identyfikator połączenia zwrócony przez funkcję ftp_connect, a użytkownik i hasło to ciągi znaków określające nazwę użytkownika oraz hasło. Logowanie należy
wykonać nawet wówczas, jeśli łączymy się z serwerem FTP oferującym dostęp anonimowy. W takim wypadku jako nazwę użytkownika należy podać ciąg anonymous, a jako hasło — adres e-mail. Niektóre serwery akceptują także podanie zamiast hasła pustego ciągu znaków. Pobranie pliku z serwera osiągamy dzięki funkcji: ftp_fget(ftpid, plik_lokalny, plik_zdalny, tryb[, pozycja])
gdzie: ftpid — identyfikator połączenia. plik_lokalny — nazwa pliku (na dysku lokalnym), w którym będą
zapisywane odczytane dane. plik_zdalny — nazwa pliku do pobrania (wraz ze ścieżką dostępu).
348
PHP5. Praktyczny kurs tryb — tryb transmisji. Może przyjmować wartość FTP_ASCII (dla transmisji tekstowej) lub FTP_BINARY (dla transmisji binarnej). pozycja — opcjonalny parametr pozwalający na wznowienie zerwanej
wcześniej transmisji. Określa pozycję w pliku, od której powinny być wysyłane dane. Po zakończeniu transmisji połączenie należy zamknąć za pomocą funkcji ftp_close, której wywołanie ma postać: ftp_close(ftpid)
Przykładowy skrypt, który pobiera wskazany plik z wybranego serwera FTP, został przedstawiony na listingu 7.6. Dla uproszczenia wszystkie parametry połączenia zostały podane bezpośrednio w kodzie skryptu. Listing 7.6. Pobranie pliku z serwera FTP
Rozdział 7. ♦ Obsługa sieci
349
Kod rozpoczyna się od ustawienia zmiennych pomocniczych: $host — przechowuje nazwę lub adres IP serwera FTP. $user — przechowuje nazwę użytkownika. $pass — przechowuje hasło użytkownika. $plik_lokalny — przechowuje nazwę pliku lokalnego (wraz ze ścieżką
dostępu), czyli pliku, który zostanie zapisany na dysku. $plik_zdalny — przechowuje nazwę pliku zdalnego (wraz ze ścieżką
dostępu), czyli pliku, który będzie pobierany z serwera. Następnie (za pomocą funkcji ftp_connect) ma miejsce próba połączenia z serwerem wskazanym przez wartość zmiennej $host. Jeśli ta próba zakończy się sukcesem, zmiennej $ftpid zostanie przypisany identyfikator połączenia, w przeciwnym wypadku otrzyma ona wartość false, a skrypt zakończy działanie, wyświetlając na ekranie konsoli stosowny komunikat. Jeśli połączenie udało się nawiązać, jest wywoływana wykonująca procedurę logowania funkcja ftp_login, której w postaci argumentów został przekazany identyfikator połączenia $ftpid oraz wartości zmiennych $user i $pass. Jeżeli wartością zwróconą przez ftp_login będzie true, oznacza to, że procedura zakończyła się sukcesem, jest więc wyświetlany komunikat z informacją o tym fakcie i rozpoczyna się wykonywanie dalszych instrukcji skryptu. Jeśli jednak zwróconą wartością jest false, oznacza to, że logowanie się nie udało, połączenie jest zatem zamykane za pomocą funkcji ftp_ close, a skrypt kończy działanie. Po prawidłowym zalogowaniu można przystąpić do pobrania pliku, co jest wykonywane za pomocą funkcji ftp_get. W tym przypadku pobierany jest plik o nazwie plik.zip znajdujący się w katalogu głównym serwera FTP — / i jest on zapisywany na dysku jako plik o tej samej nazwie w katalogu bieżącym — ./ (tym, z którego został uruchomiony skrypt). Jeśli wartością zwróconą przez ftp_get jest true, oznacza to, że pobranie zakończyło się sukcesem, jeśli natomiast wartością tą jest false, oznacza to, że wystąpił błąd (np. nie ma takiego pliku na serwerze FTP). W obu przypadkach jest wyświetlany odpowiedni komunikat. Na zakończenie połączenie z serwerem FTP jest zamykane za pomocą wywołania funkcji ftp_close.
Wysyłanie poczty Z poziomu PHP można również wysyłać listy elektroniczne — służy do tego funkcja mail. Aby móc z niej skorzystać, należy wcześniej odpowiednio skonfigurować środowisko (konkretnie chodzi o ustawienie odpowiednich parametrów w pliku php.ini). W przypadku pracy w systemie Linux oraz niestandardowej lokalizacji programu Sendmail należy zmiennej sendmail_path przypisać ścieżkę do tego programu lub też innej aplikacji zajmującej się wysyłaniem poczty. Z reguły jednak domyślne ustawienia są właściwe i nie wymagają modyfikacji.
350
PHP5. Praktyczny kurs
Jeśli natomiast korzystamy ze środowiska Windows, należy przypisać odpowiednie wartości następującym zmiennym: SMPT — określa nazwę lub adres IP serwera SMTP. SMTP_PORT — określa port, na którym działa serwer SMTP. SENDMAIL_FROM — określa, co będzie wpisane w polu FROM wysyłanego listu.
Należy zatem skorzystać z usługi SMTP (serwera pocztowego) udostępnianego przez niektóre wersje systemu Windows (z reguły wersje Professional) lub też zainstalować inny produkt spełniający takie zadanie, np. PostCast Server (dostępny pod adresem http://www.postcastserver.com) lub Mercury Mail Transport System (dostępny pod adresem http://www.pmail.com). Serwer pocztowy działa standardowo na porcie nr 25. Wywołanie funkcji mail ma następującą postać: mail(do, temat, treść[, nagłówki[, parametry]])
Parametry do, temat i treść określają odpowiednio: adres odbiorcy, temat listu oraz jego treść. Opcjonalny parametr nagłówki pozwala na dodanie do listu dodatkowych nagłówków SMTP. Argument parametry umożliwia przekazanie dodatkowych parametrów do programu pocztowego. Funkcja zwraca wartość true, jeśli wysłanie listu zakończyło się powodzeniem, lub false w przeciwnym razie. Jeśli więc na adres uzytkownik@nazwa.domeny chcielibyśmy wysłać list o temacie test i zawartości zapisanej w zmiennej $tresc, powinniśmy zastosować wywołanie: mail("uzytkownik@nazwa.domeny", "test", $tresc);
Stosując tę technikę, można napisać skrypt, który na nasz adres e-mail będzie wysyłał na przykład opinie użytkowników o witrynie. Podobny skrypt był prezentowany w rozdziale 2., w lekcji 15. Wtedy opinie były zapisywane w pliku tekstowym. Formularz będzie podobny jak w tamtym przypadku, pojawi się w nim tylko dodatkowe pole o nazwie temat, w którym będzie można wpisywać temat listu. Kod HTML zostanie też oddzielony od kodu PHP. Tym razem kod formularza będzie miał zatem postać widoczną na listingu 7.7, który po wczytaniu do przeglądarki będzie wyglądał jak na rysunku 7.3. Dane będą natomiast wysyłane do skryptu o nazwie sendmail.php za pomocą metody POST. Temat listu będzie przekazywany w parametrze o nazwie temat, a treść — w parametrze tresc. Listing 7.7. Kod HTML formularza
Moja strona WWW Wyraź swoją opinię:
Rysunek 7.3. Formularz służący do wysyłania wiadomości
Kod PHP zajmujący się odbieraniem danych z formularza i formowaniem z nich listu elektronicznego został przedstawiony na listingu 7.8. Najpierw jest sprawdzane, czy w tablicy $_POST znajdują się klucze temat i tresc, to znaczy, czy do skryptu zostały przekazane wymagane dane. Gdyby danych nie było, jedynym efektem działania skryptu byłoby wyświetlenie informacji o ich niekompletności. Jeśli jednak dane będą obecne, są przypisywane zmiennym $temat i $tresc. Następnie wykonywane jest sprawdzenie, czy ich zawartością nie są puste ciągi znaków, co oznaczałoby, że użytkownik nie wypełnił któregoś z pól formularza. W takiej sytuacji również jest wyświetlana informacja o braku danych (warto samodzielnie dodać także instrukcje ograniczające maksymalną długość danych, np. tak jak w skrypcie z listingu 4.12; można także pomyśleć o szczegółowej weryfikacji danych, np. ograniczeniu ich wyłącznie do liter, cyfr i znaków przestankowych). Jeśli jednak zarówno temat, jak i treść listu zostały przesłane, są przekazywane funkcji mail wraz z adresem, pod który mają być wysłane (jest on zapisany w zmiennej $do). Listing 7.8. Skrypt wysyłający e-mail
352
PHP5. Praktyczny kurs $tresc = $_POST['tresc']; if($temat == "" || $tresc == ""){ echo "Niekompletne dane"; } else{ if(mail($do, $temat, $tresc)){ echo "List został wysłany."; } else{ echo "Wystąpił błąd. List nie został wysłany."; } } } else{ echo "Niekompletne dane"; } ?>
Ćwiczenia do samodzielnego wykonania Ćwiczenie 21.1. Napisz skrypt, który będzie dopuszczał połączenia jedynie z wybranych adresów IP zapisanych w pliku tekstowym. Ćwiczenie 21.2. Zmodyfikuj kod z listingu 7.3 tak, aby treść plików tekstowych zawierających adresy IP była wczytywana tylko wtedy, gdy jest to rzeczywiście konieczne. Ćwiczenie 21.3. Napisz skrypt, który będzie wczytywał różne wersje witryny w zależności od tego, jakiej przeglądarki używa odwiedzająca ją osoba. Ćwiczenie 21.4. Na podstawie kodu z listingu 7.6 napisz taki skrypt, który będzie pobierał plik z serwera FTP i w którym parametry połączenia oraz nazwy pliku zdalnego i lokalnego będą podawane w wierszu poleceń podczas wywoływania skryptu. Ćwiczenie 21.5. Zmodyfikuj skrypt wysyłający wiadomości e-mail ze strony WWW w taki sposób, aby liczba wysyłanych wiadomości była ograniczona do trzech w ciągu jednej sesji. Ćwiczenie 21.6. Zmodyfikuj skrypt wysyłający wiadomości e-mail ze strony WWW w taki sposób, aby pozwalał na wysyłanie listów na dowolne adresy e-mail wprowadzane w odpowiednim polu formularza.
Rozdział 8.
Współpraca z bazami danych Lekcja 22. Podstawy baz danych MySQL i SQLite Gdy tworzymy serwis internetowy, musimy się zastanowić, w jaki sposób przechowywać w nim niezbędne dane. W lekcjach 12.i 16. znalazły się przykłady skryptów, takich jak skrypt logowania, licznik, generowanie odnośników, w których dane były przechowywane w plikach na dysku. Jak jednak zostało wtedy wspomniane, wykorzystywanie plików dyskowych ma kilka wad: jest niezbyt wygodne, mało wydajne, występują problemy z synchronizacją, a wraz ze wzrostem ilości przechowywanych danych rośnie też znacznie skomplikowanie skryptów. Dlatego też ogromna większość serwisów internetowych jest oparta na bazach danych. Najczęściej są to bazy relacyjne, a do ich obsługi wykorzystywane są systemy zarządzania relacyjnymi bazami danych RDBMS (z ang. Relational Database Management System). Co prawda dokładne omówienie tej tematyki wykracza poza ramy niniejszej publikacji1, nie oznacza to jednak, że nie można nauczyć się, przynajmniej w podstawowym zakresie, współpracy PHP z bazami danych. Najbardziej znany RDBMS współpracujący z PHP to rozwijany na zasadzie open source MySQL. Jest to bardzo szybki i obecnie jeden z najpopularniejszych serwerów baz danych dostępny na licencji GPL, choć możliwe jest również zakupienie licencji komercyjnej. Korzystać będziemy z wersji darmowej (określanej jako community edition), której każdy może używać bez wnoszenia opłat. Jest ona dostępna dla bardzo wielu systemów operacyjnych, takich jak 1
Czytelnicy, którzy chcieliby pogłębić swoją wiedzę na ten temat, mogą sięgnąć na przykład do publikacji PHP i MySQL dla każdego (http://helion.pl/ksiazki/phsqdk.htm), a także SQL. Ćwiczenia praktyczne (http://helion.pl/ksiazki/cwsqw2.htm) i MySQL. Darmowa baza danych. Ćwiczenia praktyczne (http://helion.pl/ksiazki/cwmsqd.htm).
354
PHP5. Praktyczny kurs
Linux, Windows, MacOS, FreeBSD, Solaris, AIX i in. Pliki z pakietem dystrybucyjnym można pobrać ze strony producenta http://www.mysql.com, są one także zawarte w pakiecie XAMPP. Na wymienionej stronie WWW dostępne są także pełne kody źródłowe bazy. Jeśli nie chcemy korzystać z pełnego systemu obsługi baz, jakim jest MySQL (co wymaga instalowania w systemie dodatkowego oprogramowania RDBMS), a jedynie zapoznać się z technikami bazodanowymi, możemy skorzystać z wbudowanej w PHP5 obsługi bazy SQLite. W większości wypadków na początku przygody z bazami danych powinien on w pełni zaspokoić nasze potrzeby. W dalszej części rozdziału zostanie pokazane, jak posługiwać się zarówno MySQL, jak i SQLite, najpierw jednak dowiedzmy się nieco więcej o relacyjnych bazach danych.
Tabele, klucze i relacje Tabele Pytanie, które trzeba sobie zadać przed rozpoczęciem pracy z bazami danych, brzmi: w jaki sposób dane są przechowywane w bazie? Otóż w systemach relacyjnych baz danych (jakimi są MySQL i SQLite) dane przechowywane są w tabelach. Pomiędzy tymi tabelami, a dokładniej między zawartymi w nich danymi, występują wiążące je relacje2. Jak zatem wygląda pojedyncza tabela z danymi? Każdy, kto widział kiedykolwiek dowolny arkusz kalkulacyjny, może ją sobie z łatwością wyobrazić. Tabela składa się z wierszy i kolumn. Każdy wiersz opisuje jeden rekord, a kolumna jego właściwości. Załóżmy na przykład, że chcielibyśmy przechowywać w bazie dane dotyczące osób, a interesuje nas zapamiętanie imion, nazwisk oraz dat urodzenia. Należałoby zatem utworzyć tabelę zawierającą trzy kolumny. W pierwszej kolumnie byłyby zapisywane dane dotyczące imion, w drugiej nazwisk, a w trzeciej dat urodzenia osób. Przykładowy wygląd takiej tabeli zobrazowano na rysunku 8.1. Tabela ta ma trzy wiersze zawierające dane dotyczące trzech różnych osób. Rysunek 8.1. Przykładowa tabela zawierająca dane dotyczące osób
Klucze Jeśli zastanowimy się nad strukturą tabeli przedstawionej na rysunku 8.1, zapewne dostrzeżemy, że ma ona pewną wadę. Jest ona na tyle poważna, że w praktycznym zastosowaniu użycie takiej tabeli powodowałoby bardzo duże problemy. Otóż ta realizacja nie pozwala na jednoznaczną identyfikację konkretnej osoby. Może się bowiem 2
Ściśle rzecz ujmując, w teorii relacyjnych baz danych relacją jest sama tabela. W książce przyjęto jednak uproszczoną terminologię, dzięki czemu osoby początkujące będą mogły łatwiej przyswoić sobie prezentowaną wiedzę.
Rozdział 8. ♦ Współpraca z bazami danych
355
zdarzyć, że w bazie trzeba będzie zapisać dane dwóch Janów Kowalskich urodzonych 1 stycznia 1970 roku. Użycie struktury z rysunku 8.1 spowodowałoby powstanie dwóch rekordów (wierszy) o takich samych danych, których nie byłoby można rozróżnić (sytuacja taka została przedstawiona na rysunku 8.2). Rysunek 8.2. Nieprawidłowa struktura tabeli nie pozwala na rozróżnienie niektórych rekordów
Oczywiście w praktyce nie wolno do takiej sytuacji dopuścić, a zatem już podczas projektowania tabeli trzeba stworzyć taką strukturę kolumn, aby każdy rekord (zestaw danych) mógł być jednoznacznie zidentyfikowany. W przypadku tabeli przechowującej dane osób moglibyśmy na przykład wprowadzić dodatkową kolumnę zawierającą PESEL danej osoby, który przynajmniej teoretycznie jest unikalny i niepowtarzalny. Najczęściej jednak do tabeli wprowadza się dodatkowe, abstrakcyjne (niemające odzwierciedlenia w rzeczywistych danych) pole identyfikujące każdy wiersz. Mogłoby się ono nazywać na przykład OsobaId. Tabela zawierająca takie pole została przedstawiona na rysunku 8.3. Jak widać, tym razem bez problemów można rozróżnić dwóch Janów Kowalskich, mimo że obaj urodzili się w tym samym dniu. Rysunek 8.3. Utworzenie dodatkowej kolumny pozwala jednoznacznie zidentyfikować każdy rekord
Taka kolumna zawierająca wartość jednoznacznie identyfikującą każdy rekord nazywana jest kluczem podstawowym lub głównym (z ang. primary key). Ogólnie rzecz biorąc, kluczem możemy nazwać dowolnie wybrany zestaw kolumn, czyli np. Imie, Nazwisko. To również jest klucz, niemniej klucz podstawowy powinien zawsze jednoznacznie identyfikować każdy wiersz tabeli, a te dwie kolumny nie spełniają tego warunku. O tym, która kolumna (lub kolumny) będzie kluczem, decyduje programista tworzący bazę. Klucze pozwalają zaś na budowanie relacji między tabelami.
Relacje Wiadomo już, że dane w bazie są przechowywane w tabelach. Logika podpowiada, że takie tabele muszą być ze sobą w jakiś sposób powiązane, inaczej nie będzie z nich większego pożytku. Jeśli prowadzimy na przykład sklep internetowy, a w bazie znajdują się tabele zawierające dane o klientach i o dokonanych przez nich zamówieniach, musi istnieć takie powiązanie, które jednoznacznie przypisze zamówienie do klienta. Oznacza to, że pomiędzy tymi tabelami musi istnieć tzw. relacja. W praktyce takie powiązanie zostanie zrealizowane przez wprowadzenie do jednej z tabel tzw. klucza obcego. Schematycznie sytuacja taka została przedstawiona na rysunku 8.4.
356
PHP5. Praktyczny kurs
Rysunek 8.4. Umieszczenie klucza obcego w tabeli opisującej zamówienia
Mamy tu dwie tabele, jedna opisuje klientów, druga — zamówienia. Obie mają klucze podstawowe, w pierwszej jest to kolumna KlientId, w drugiej — ZamówienieId. W tabeli opisującej zamówienia znajduje się również klucz obcy KlientId, czyli nic innego jak klucz podstawowy z tabeli opisującej klientów. Dzięki takiej strukturze wiemy, że zamówienie o identyfikatorze 1 zostało złożone przez Jana Kowalskiego 25 stycznia 2012 roku, a zamówienie o numerze 3 — 22 kwietnia 2012 roku przez Andrzeja Nowaka. A zatem powtarzając raz jeszcze: KlientId jest kluczem podstawowym w tabeli opisującej klientów, ale kluczem obcym w tabeli opisującej zamówienia. W ten sposób pomiędzy tymi dwiema tabelami powstała relacja. W teorii projektowania relacyjnych baz danych wyróżnia się trzy podstawowe typy relacji: jeden do jednego, jeden do wielu, wiele do wielu.
W przypadku relacji jeden do jednego jednemu rekordowi (wierszowi) z tabeli X odpowiada dokładnie jeden rekord z tabeli Y. Z taką sytuacją moglibyśmy mieć do czynienia na przykład wtedy, gdyby w jednej tabeli zapisać imiona i nazwiska osób, a w innej numery PESEL (rysunek 8.5). Jak wiadomo, każda osoba ma przypisany unikalny numer PESEL, a zatem będzie tu występowała relacja jeden do jednego. Oczywiście od razu należałoby przyjąć założenie, że nie będziemy przechowywać danych obywateli innych państw, którzy nie posiadają PESEL-u.
Rysunek 8.5. Pomiędzy tymi tabelami występuje relacja jeden do jednego
W przypadku relacji jeden do wielu jednemu rekordowi (wierszowi) z tabeli X może odpowiadać jeden lub więcej rekordów z tabeli Y, ale — uwaga — jednemu wierszowi z tabeli Y odpowiada dokładnie jeden z tabeli X. Z taką sytuacją już mieliśmy do czynienia. Spójrzmy ponownie na rysunek 8.4. Znajdują się na nim dwie tabele, z któ-
Rozdział 8. ♦ Współpraca z bazami danych
357
rych pierwsza opisuje klientów, a druga złożone przez nich zamówienia. Jeden klient może złożyć wiele zamówień, ale jedno zamówienie może należeć tylko do jednego klienta. A zatem jest to relacja jeden do wielu (lub też odwrotnie — wiele do jednego). Przypadek trzeci jest bardziej złożony. Otóż relacja wiele do wielu ma miejsce, kiedy jednemu wierszowi z tabeli X może odpowiadać wiele wierszy z tabeli Y oraz jednemu wierszowi z tabeli Y — wiele wierszy z tabeli X. Kiedy możemy mieć do czynienia z taką sytuacją? Na przykład wtedy, gdy chcemy zapisać w bazie dane dotyczące książek i autorów. Wiadomo bowiem, że każda książka może mieć jednego lub więcej autorów, ale także każdy autor mógł napisać jedną lub wiele książek. Jest to więc typowa relacja wiele do wielu. Pytanie, jak taką sytuację odzwierciedlić w praktyce w bazie danych? Osobom, które nie miały dotychczas do czynienia z tworzeniem baz danych, może się nasunąć pomysł, aby stworzyć dwie tabele — Książki i Autorzy — i w pierwszej umieścić dwie lub trzy kolumny odwołujące się do drugiej. Tabele miałyby więc postać taką, jak na rysunku 8.6. Rysunek 8.6. Błędne rozwiązanie problemu relacji wiele do wielu
Tabela Autorzy ma zatem trzy pola: AutorId, Imię, Nazwisko, natomiast tabela Książki — pięć pól: KsiążkaId, Tytuł, Autor1Id, Autor2Id, Autor3Id. Takie rozwiązanie, choć spotykane w praktyce, jest jednak bardzo niedobre i należy go zdecydowanie unikać. Po pierwsze większość książek ma tylko jednego autora, a zatem w większości przypadków pola Autor2Id i Autor3Id pozostaną puste. Po drugie może się zdarzyć sytuacja wyjątkowa, kiedy to jakaś książka będzie miała więcej niż trzech autorów i wtedy nie będziemy w stanie zapisać ich wszystkich w bazie. Dlatego też w przypadku wystąpienia relacji wiele do wielu należy stosować dodatkową tabelę pomocniczą łączącą te, między którymi relacja występuje. Prawidłowe rozwiązanie jest zatem takie jak na rysunku 8.7. Rysunek 8.7. Prawidłowe rozbicie relacji wiele do wielu na trzy tabele
Tabela Autorzy ma taką samą postać jak w poprzednim przypadku, natomiast tabela Książki tym razem ma tylko dwa pola — KsiążkaId (w którym będzie przechowywany unikalny identyfikator) oraz Tytuł. Za połączenia autorów i książek odpowiada natomiast całkiem nowa tabela o nazwie KsiążkiAutorzy. Ma ona dwa pola, w pierwszym, czyli KsiążkaId, zapisywane są identyfikatory książek z tabeli Książki (jest to więc klucz obcy), w drugim, czyli AutorId — identyfikatory autorów z tabeli Autorzy (a zatem to również jest klucz obcy). Dzięki takiemu układowi możemy bez problemu odzwierciedlić sytuację, gdy jeden autor napisał wiele książek i gdy jedna książka ma więcej niż jednego autora. Taka przykładowa sytuacja została przedstawiona na rysunku
358
PHP5. Praktyczny kurs
8.8. Zwróćmy także uwagę, że w tabeli KsiążkiAutorzy obie kolumny zawierają klucze obce, a zatem obie one razem będą stanowić klucz podstawowy (o ile dane w tabeli nie zawierają błędów, a para KsiążkaId, AutorId jednoznacznie wyznacza dany wiersz tabeli KsiążkiAutorzy). Rysunek 8.8. Baza książek i autorów zawierająca przykładowe dane
Widać wyraźnie, że w bazie zapisano dane dwójki autorów: Orsona Scotta Carda3 oraz Kathryn Kidd4 (tabela Autorzy) oraz trzech książek — Gra Endera, Lovelock i Paradise Vue (tabela Ksiażki). Tabela KsiążkiAutorzy zawiera informacje pozwalające dopasować książki do autorów i odwrotnie — autorów do książek. Z danych w niej zawartych wynika, że: Książka Gra Endera o identyfikatorze 1 ma jednego autora o identyfikatorze 1,
a zatem jest nim Orson Scott Card. Książka Lovelock o identyfikatorze 2 ma dwoje autorów o identyfikatorach 1 i 2,
a zatem są nimi Orson Scott Card oraz Kathryn Kidd. Książka Paradise Vue o identyfikatorze 3 ma jednego autora o identyfikatorze 2,
a zatem jest nim Kathryn Kidd.
Bazy danych a PHP Wiadomo z lekcji 1., że gdy serwer WWW współpracuje z PHP, proces pobrania pojedynczej strony składa się z czterech etapów (rysunek 1.2): wysłania przez przeglądarkę żądania do serwera, pobrania przez serwer strony z dysku lub pamięci i przesłania jej do aparatu
wykonawczego PHP, przetworzenia strony przez PHP i odesłania jej z powrotem do serwera, wysłania przez serwer wygenerowanej ostatecznie strony do przeglądarki. 3
4
Wartym rozważenia może być zagadnienie, jak postępować w sytuacji, gdy autor posługuje się dwoma imionami. Czy zapisywać je oba w jednej kolumnie, tak jak w przedstawionym przykładzie (zmieniając ewentualnie nazwę kolumny na Imiona), czy też zastosować inne rozwiązanie? Dokładniej Kathryn Helms Kidd (w jednym polskich wydań książki Lovelock błędnie wydrukowano jej nazwisko jako Kathryn Kerr).
Rozdział 8. ♦ Współpraca z bazami danych
359
Gdy jednak korzystamy z baz danych, cała procedura wydłuża się o dodatkowe etapy. Sytuację taką schematycznie zobrazowano na rysunku 8.9. Jak widać, tym razem do przejścia jest aż sześć kroków: Wysłanie przez przeglądarkę do serwera żądania pobrania strony o danym
adresie (1). Pobranie przez serwer żądanej strony z dysku (lub pamięci). Jeśli jest to
zwykły plik, serwer wysyła jego treść do przeglądarki (6). Jeśli jest to plik o rozszerzeniu .php (lub innym podanym podczas konfiguracji serwera), serwer wysyła do modułu PHP żądanie przetworzenia danych znajdujących się w pliku (2). Przetworzenie otrzymanych danych przez moduł PHP, czyli wykonanie
zawartego w pliku skryptu. Jeśli skrypt nie wykonuje operacji na bazie danych, następuje wysłanie danych do serwera (5). Jeśli skrypt wykonuje operacje na bazie danych, zostaje nawiązane połączenie z bazą i wysłanie zapytań (3). Przetworzenie zapytań przez serwer bazy danych i zwrócenie ich wyniku
do modułu PHP (4). Wysłanie przez moduł PHP przetworzonych danych do serwera WWW (5). Wysłanie przez serwer przetworzonych danych do przeglądarki (6).
Rysunek 8.9. Schemat przetwarzania danych podczas korzystania z PHP i bazy danych
Instalacja systemu bazy danych Instalacja SQLite Obsługa SQLite jest wbudowana we współczesne wersje PHP i nie wymaga dodatkowych czynności instalacyjnych. Użytkownicy systemu Windows muszą jedynie sprawdzić, czy w pliku konfiguracyjnym php.ini znajdują się następujące linie (dokładnie w przedstawionej kolejności): extension=php_pdo.dll extension=php_sqlite.dll
Jeżeli ich nie ma, należy je dodać. Jeżeli są ujęte w komentarz (poprzedzone znakami średnika), należy komentarze usunąć (po dokonaniu zmian trzeba zrestartować serwer WWW).
360
PHP5. Praktyczny kurs
Do zarządzania bazą na przykład przygotowania danych można natomiast użyć działającego w wierszu poleceń klienta sqlite. Jest on dostępny w internecie pod adresem http://www.sqlite.org (w Linuksie klient SQLite często jest dostępny standardowo i nie wymaga pobierania dodatkowych danych). Aby uruchomić klienta, należy go skopiować do wybranego katalogu oraz wydać polecenie: sqlite nazwa_pliku;
gdzie nazwa_pliku to określenie pliku z bazą danych. Jeśli pliku określonego przez nazwa_pliku nie będzie na dysku, zostanie on utworzony — powstanie w ten sposób nowa baza. Po uruchomieniu klienta (rysunek 8.10) można w nim wykonywać polecenia i zapytania SQL (takie, jakie zostaną zaprezentowane w kolejnej lekcji). Rysunek 8.10. Uruchomienie klienta SQLite
Instalacja MySQL Ze względu na istnienie wielu wersji serwera MySQL dostępnego dla wielu różnych systemów operacyjnych nie można dokładnie opisać wszystkich możliwych rodzajów instalacji. Nie jest to jednak proces skomplikowany i zostanie pokazany dla wersji 5.5 (najnowszej dostępnej w trakcie powstawania książki) w systemach Windows i Linux. W przypadku innych wersji MySQL oraz innych systemów operacyjnych czynność ta wygląda jednak bardzo podobnie i z pewnością nikomu nie sprawi najmniejszych problemów. W razie wątpliwości należy zajrzeć do dokumentacji pakietu dostępnej na stronach serwisu http://www.mysql.com.
Windows Plik instalacyjny pakietu można znaleźć pod adresem http://www.mysql.com. Będzie miał nazwę mysql-wersja-win32.msi (lub podobną), gdzie wersja to określanie wersji serwera, np. mysql-5.5.8-win32.msi. Po uruchomieniu pliku pojawi się standardowe okno instalatora Windows, który przeprowadzi nas przez dalsze czynności. Do wyboru są trzy typy instalacji: Instalacja standardowa (Typical) — pozwala na zainstalowanie serwera,
klienta oraz kilku innych narzędzi. Instalacja pełna (Complete) — pozwala na zainstalowanie wszystkich
składników pakietu. Instalacja użytkownika (Custom) — pozwala na dokładne ustalenie,
które ze składników pakietu mają być zainstalowane.
Rozdział 8. ♦ Współpraca z bazami danych
361
W przypadku wybrania instalacji użytkownika pojawi się nowe okno dialogowe umożliwiające wybór poszczególnych składowych pakietu. W tym przypadku możliwa jest również zmiana katalogu docelowego, w którym będzie zainstalowany pakiet MySQL. Po wybraniu żądanych opcji należy kliknąć Next, co spowoduje wyświetlenie (podobnie jak w przypadku dwóch poprzednich typów instalacji) okna podsumowującego wybór. Procedura instalacyjna rozpocznie się po kliknięciu przycisku Install. Jeśli na ostatnim ekranie instalatora (rysunek 8.11) zaznaczy się opcję Launch the MySQL Instance Config Wizard (lub o podobnej treści, np. Configure the MySQL server now — zależy to od konkretnej wersji instalatora), po kliknięciu przycisku Finish zostanie uruchomiony kreator konfiguracji, warto więc z takiej możliwości skorzystać (kreator konfiguracji może być jednak również wywoływany z menu startowego w dowolnym innym momencie). Rysunek 8.11. Okno kończenia instalacji
Uruchomienie kreatora konfiguracji powoduje wyświetlenie ekranu tytułowego, na którym należy kliknąć przycisk Next. Zostanie wtedy wyświetlone okno dialogowe umożliwiające wybór opcji konfiguracyjnych (rysunek 8.12). W przypadku pierwszej instalacji możliwe są dwa tryby pracy: Detailed Configuration — przeznaczony dla zaawansowanych użytkowników,
którzy chcą mieć możliwość szczegółowej ingerencji w ustawienia serwera. Standard Configuration — przeznaczony dla osób, dla których wystarczające
będą standardowe ustawienia. Rysunek 8.12. Okno wyboru trybu pracy konfiguratora
362
PHP5. Praktyczny kurs
Przy pierwszej instalacji w zupełności wystarczą standardowe ustawienia serwera, wybierzmy zatem opcję Standard Configuration (opis konfiguracji w trybie Detailed Configuration można znaleźć w dokumentacji MySQL). Aplikację konfiguracyjną można również wywoływać, uruchamiając plik MySQL InstanceConfig.exe. Znajduje się on w katalogu, w którym został zainstalowany serwer, w podkatalogu bin. Konfigurator jest dostępny także z poziomu menu startowego: Wszystkie programy/MySQL/MySQL Server 5.5/MySQL Server Instance Config Wizard. Po wybraniu opcji Standard Configuration i kliknięciu przycisku Next na ekranie pojawi się kolejne okno dialogowe (rysunek 8.13), które pozwala na podjęcie decyzji, czy MySQL ma być uruchamiane jako usługa systemowa (Windows Service), czy też jako samodzielna aplikacja. Wybór jest dowolny i zależy tylko od naszych preferencji. Domyślnie trybem pracy jest usługa systemowa. Zaznaczenie opcji Launch the MySQL Server automatically powoduje, że usługa MySQL będzie uruchamiana automatycznie wraz ze startem systemu. Z listy rozwijanej Service Name można również wybrać nazwę, jaką usługa będzie miała w systemie. Omawiane okno dialogowe zawiera też opcję Include Bin Directory in Windows PATH, której zaznaczenie powoduje dodanie do zmiennej środowiskowej PATH ścieżki dostępu do plików binarnych MySQL, dzięki czemu będą one mogły być wygodnie wywoływane w wierszu poleceń. Rysunek 8.13. Rejestrowanie MySQL jako usługi systemowej
Kolejne okno dialogowe, widoczne na rysunku 8.14, pozwala na ustawienie hasła dla konta administratora (użytkownika root) bazy danych. Hasło należy wprowadzić w polu tekstowym Enter the root password (New Root password) oraz potwierdzić w polu Retype the password (Confirm). Jeżeli zaznaczymy pole wyboru Enable root access from remote machines, administrator bazy będzie mógł logować się z komputerów zdalnych (np. innego komputera w sieci lokalnej lub przez internet). Jeśli nie ma potrzeby administracji serwerem z innych lokalizacji, dobrze jest pozostawić tę opcję wyłączoną — zwiększy to bezpieczeństwo serwera. Zaznaczenie opcji Create An Anonymous Account pozwala na założenie konta anonimowego, które będzie umożliwiało logowanie do bazy osobom nieposiadającym konta. Ze względów bezpieczeństwa zakładanie takiego konta również nie jest jednak polecane.
Rozdział 8. ♦ Współpraca z bazami danych
363
Rysunek 8.14. Konfiguracja konta administratora
Po kliknięciu przycisku Next na ekranie pojawi się kolejne okno, tym razem jedynie informujące, że wybór opcji został zakończony i kreator może przystąpić do konfiguracji serwera. Należy kliknąć przycisk Execute, co spowoduje wykonanie skryptów konfiguracyjnych oraz wyświetlenie okna podsumowującego konfigurację (rysunek 8.15). W tym (ostatnim już) oknie należy kliknąć Finish, co zakończy proces konfiguracji. W katalogu MySQL zostanie zapisany plik tekstowy my.ini zawierający opcje konfiguracyjne. W razie potrzeby można ten plik edytować ręcznie za pomocą dowolnego edytora tekstowego. Rysunek 8.15. Proces konfiguracji został zakończony
Linux Użytkownicy Linuksa są w lepszej sytuacji niż korzystający z systemu Windows, gdyż wiele popularnych dystrybucji zawiera pakiety MySQL, które zwykle są standardowo instalowane, a jeśli nie, można je zawsze doinstalować, korzystając z dołączonego do
364
PHP5. Praktyczny kurs
systemu instalatora. Instalacja za pomocą graficznego menedżera pakietów przebiega podobnie jak w przypadkach Apache’a i PHP (opisywanych w rozdziale 1). Należy odszukać pakiety opisane jako The MySQL server and related files (rysunek 8.16) i MySQL Client programs and shared libraries (lub podobnie; oba powinny być dostępne w sekcji Serwery), a następnie rozpocząć standardową procedurę instalacyjną. Rysunek 8.16. Wybór pakietu serwera MySQL
Po jej ukończeniu zaleca się nadanie hasła dla konta administratora bazy danych (użytkownika root). W tym celu najpierw trzeba uruchomić serwer (zostało to opisane w punkcie „Uruchamianie serwera”), a następnie w konsoli wydać polecenie: mysqladmin –u root password hasło
Jeśli na przykład hasłem użytkownika root ma być abh126WL, należy wydać polecenie: mysqladmin –u root password abh126WL
Po jego wykonaniu wszelkie czynności administracyjne wymagające logowania do serwera będą wymagały podawania hasła. Uwaga! Polecenie w tej postaci zadziała tylko wtedy, jeśli hasło administratora nie zostało nadane (lub zostało usunięte). Aby zmienić istniejące hasło użytkownika root (które na przykład zostało podane w trakcie instalacji), należy użyć polecenia: mysqladmin –u root –pstare_hasło password nowe_hasło
np.: mysqladmin –u root –pabh126WL password xjwE7401AP
W przypadku instalacji w trybie tekstowym należy w konsoli wydać jedno z poleceń (w zależności od tego, z jakiego instalatora chcemy skorzystać i jaki jest dostępny w używanej dystrybucji systemu): sudo apt-get install mysql-server
lub: sudo aptitude install mysql-server
lub: sudo yum install mysql-server
Rozdział 8. ♦ Współpraca z bazami danych
365
W trakcie instalacji może się pojawić okno pozwalające na wprowadzenie hasła administratora (użytkownika root), np. takie jak na rysunku 8.17. Jeśli tak będzie, hasło należy oczywiście podać. Jeżeli okno się nie pojawi, hasło należy nadać samodzielnie po zakończeniu instalacji pakietu (tak jak zostało to opisane wyżej). Rysunek 8.17. Nadawanie hasła dla konta administratora przy instalacji w trybie tekstowym
XAMPP Gdy korzysta się z pakietu XAMPP, nie trzeba wykonywać żadnych dodatkowych czynności instalacyjnych. Baza MySQL jest standardowo dostępna w tym pakiecie. Należy jedynie nadać hasło administratora (użytkownika Root) bazy danych, co zostało opisane w rozdziale 1. w lekcji 2.
Obsługa serwera MySQL Uruchamianie serwera W systemie Windows Jeżeli pakiet MySQL został zainstalowany jako uruchamiana automatycznie usługa systemowa, nie ma potrzeby wykonywania żadnych dodatkowych czynności. Serwer będzie uruchamiany automatycznie przy starcie systemu i zamykany, również automatycznie, podczas kończenia jego pracy. W przypadku gdy MySQL zostało zainstalowane jako usługa systemowa, która ma być uruchamiana ręcznie, serwer może zostać uruchomiony za pomocą komendy: net start nazwa_usługi
gdzie nazwa_usługi jest nazwą usługi MySQL wybraną podczas konfiguracji pakietu. Domyślnie jest to MySQL, a zatem standardowo usługa może zostać uruchomiona za pomocą komendy: net start MySQL
366
PHP5. Praktyczny kurs
Jeśli nie zainstalowaliśmy MySQL jako usługi systemowej, a chcielibyśmy jednak, aby serwer pracował w taki sposób, należy wydać polecenie: mysqld --install
Tak zainstalowana usługa będzie uruchamiana automatycznie podczas startu systemu. Jeśli usługa ma być uruchamiana manualnie, należy ją zainstalować, wydając polecenie: mysqld --install-manual
Usunięcie usługi mysql (niezależnie od tego, w jaki sposób została zainstalowana) odbywa się przez wydanie polecenia: mysqld --remove
Należy pamiętać, że instalacja MySQL jako usługi nie oznacza równoczesnego uruchomienia tej usługi. Uruchomienie następuje albo w sposób automatyczny przy starcie systemu, albo ręczny po wydaniu polecenia net start mysql, tak jak zostało to opisane powyżej. Do uruchamiania usług można także wykorzystać graficzne narzędzie Usługi (Panel sterowania/Narzędzia administracyjne/Usługi). Serwer bazy danych może być także uruchamiany jako samodzielna aplikacja. W takim przypadku w wierszu poleceń należy wydać komendę: mysqld --console
lub po prostu mysqld
W pierwszym przypadku wiadomości diagnostyczne będą wypisywane wprost na ekranie konsoli. Po uruchomieniu serwer przechodzi w stan oczekiwania na połączenia. Domyślnie do połączeń jest wykorzystywany protokół tcp/ip, a serwer nasłuchuje standardowo na porcie 3306.
W systemie Linux Aby uruchomić serwer MySQL, można skorzystać z graficznego menedżera usług. Robi się to na takiej samej zasadzie, jak było to w przypadku serwera Apache. Ponieważ ta procedura została opisana w lekcji 2., nie ma potrzeby, aby ją ponownie przytaczać. Uruchomienie serwera z wiersza poleceń można osiągnąć przez wywołanie skryptu mysql lub mysqld (zależy to od używanej wersji systemu) znajdującego się w lokalizacji /etc/init.d/ z parametrem start, wydając na przykład komendę: sudo /etc/init.d/mysql start
lub: sudo /etc/init.d/mysqld start
W przypadku systemów zawierających narzędzie service, można również (jeśli usługa ma nazwę mysql) użyć polecenia: sudo service mysql start
Rozdział 8. ♦ Współpraca z bazami danych
367
lub (jeśli usługa ma nazwę mysqld): sudo service mysqld start
Korzystając z pakietu XAMPP Gdy korzysta się z oprogramowania XAMPP, serwer bazy danych można uruchomić, wywołując panel kontrolny (tak jak było to opisywane w rozdziale 1.), a następnie klikając przycisk Start w wierszu MySql. Napis na przycisku zmieni się na Stop, pojawi się także słowo running na zielonym tle.
Kończenie pracy serwera W systemie Windows Jeśli serwer został zainstalowany jako usługa zarządzana automatycznie, będzie również automatycznie kończył pracę wraz z zamknięciem systemu. Jeśli chcemy wymusić zakończenie pracy usługi serwera wcześniej, należy wydać w wierszu polecenie: net stop nazwa_usługi
np.: net stop MySQL
Jeśli serwer został zainstalowany jako samodzielna aplikacja i uruchomiony ręcznie, do zakończenia jego pracy należy użyć programu mysqladmin. W przypadku gdy zgodnie z wcześniejszym opisem zostało założone konto administratora, należy wydać polecenie (oczywiście na innej konsoli niż ta, w której został uruchomiony serwer): mysqladmin --user=root --password=hasło shutdown
lub alternatywnie: mysqladmin -u root -phasło shutdown
W systemie Linux W standardowych warunkach serwer kończy działanie wraz z końcem pracy systemu. Jeśli jednak chcemy wymusić zakończenie pracy serwera, należy użyć programu mysqladmin, skryptu mysql (lub mysqld) lub narzędzia service (o ile jest dostępne). W pierwszym przypadku wydaje się polecenie: mysqladmin --user=root --password=hasło shutdown
lub alternatywnie: mysqladmin -u root -phasło shutdown
Skryptu mysql (mysqld) używa się analogicznie jak w przypadku uruchamiania serwera, zmieniając jedynie komendę start na stop: sudo /etc/init.d/mysql stop
lub: sudo /etc/init.d/mysqld stop
368
PHP5. Praktyczny kurs
Podobnie jest z narzędziem service. Używa się polecenia: sudo service mysql stop
lub: sudo service mysqld stop
Oczywiście należy skorzystać z jednego z przedstawionych sposobów, a nie wszystkich trzech jednocześnie.
Korzystając z pakietu XAMPP Gdy korzysta się z oprogramowania XAMPP, serwer bazy danych można zatrzymać, wywołując panel kontrolny (tak jak było to opisywane w rozdziale 1.), a następnie klikając przycisk Stop w wierszu MySql. Napis na przycisku zmieni się na Start, zniknie także widniejące obok słowo running.
Przygotowanie serwera do pracy Komunikacja z serwerem Aby połączyć się z serwerem i móc wykonywać czynności związane z obsługą baz, potrzebny jest program klienta. Razem z serwerem MySQL dostępny jest działający w wierszu poleceń program mysql (można także użyć innych narzędzi, np. działającego w trybie graficznym klienta MySQL Query Browser czy bardziej zaawansowanej aplikacji MySQL Workbench; oba narzędzia dostępne pod adresem http://www.mysql.com). W dalszej części książki będzie opisywany klient pracujący w trybie tekstowym, jako że dostępny jest on standardowo w każdej wersji pakietu MySQL. Nic jednak nie stoi na przeszkodzie, aby używać klienta graficznego. Wydawane komendy będą w obu przypadkach takie same. Aby uruchomić program klienta i połączyć się z serwerem, należy wydać jedną z komend: mysql –u root --password=hasło mysql –u root -phasło
lub mysql –u root –p
Ponieważ w ostatnim przypadku hasło użytkownika root nie zostało podane w wywołaniu, pojawi się monit o jego podanie. Po chwili nastąpi nawiązanie połączenia z serwerem (rysunek 8.18) i będzie można wykonywać zapytania i inne czynności związane z zarządzaniem, takie jak tworzenie i usuwanie baz, tworzenie, usuwanie i modyfikacja kont użytkowników oraz tabel, a także wydawanie zapytań w języku SQL. Należy zwrócić uwagę, że komendy wydawane serwerowi muszą się kończyć znakiem średnika. Jeśli ten warunek nie będzie spełniony, polecenie nie zostanie przesłane do serwera, klient będzie bowiem czekał na wprowadzenie dalszych danych.
Rozdział 8. ♦ Współpraca z bazami danych
369
Rysunek 8.18. Nawiązanie połączenia z serwerem MySQL
Zarządzanie bazami danych Serwer MySQL może jednocześnie obsługiwać wiele baz danych. W niektórych wersjach standardowo po zainstalowaniu dostępna jest baza o nazwie test służąca do testowania konfiguracji. Można ją wykorzystywać w przykładach prezentowanych w kolejnych lekcjach. Nic nie stoi jednak na przeszkodzie, aby utworzyć własną bazę danych (jeśli bazy testowej nie ma, jest to wręcz konieczne). W celu wykonania tej czynności należy (oczywiście w kliencie mysql po nawiązaniu połączenia z serwerem, tak jak zostało to opisane w poprzednim podpunkcie) wydać polecenie create database w schematycznej postaci: create database nazwa_bazy;
Polecenie musi być zakończone znakiem średnika. Jeśli chcemy na przykład utworzyć bazę o nazwie testphp, napiszemy: create database testphp;
Po kliknięciu przycisku Enter serwer potwierdzi utworzenie bazy, wyświetlając tekst: Query OK, 1 row affected (0.05 sec)
Można teraz wydawać kolejne polecenia (widoczne określenie 0.05 sec odnosi się do czasu, w jakim zostało wykonane polecenie, i oczywiście może mieć inną wartość). W celu usunięcia bazy z serwera należy wykorzystać komendę drop database w postaci: drop database nazwa_bazy;
Jeśli chcielibyśmy usunąć utworzoną przed chwilą bazę testphp, należałoby napisać: drop database testphp;
Odpowiedź serwera będzie podobna jak w poprzednim przypadku, np.: Query OK, 0 rows affected (0.08 sec)
370
PHP5. Praktyczny kurs
Utworzenie konta użytkownika Podczas instalacji i konfiguracji MySQL utworzone zostało jedynie konto administratora — root. Ponieważ użytkownik ten ma pełne prawa do wykonywania wszelkich czynności związanych z zarządzaniem serwerem, ze względów bezpieczeństwa konto to nie powinno być używane podczas zwykłej pracy. Do codziennej pracy należy utworzyć inne konto (lub konta) oraz przydzielić mu odpowiednie prawa. Służy do tego polecenie GRANT. Nie będzie tu omawiana pełna składnia tego polecenia i związane z nim opcje, gdyż nie jest to potrzebne, a dokładny opis zająłby zbyt wiele miejsca. Przedstawiona zostanie jednak uproszczona postać komendy, która w zupełności wystarczy do dalszej pracy. Ogólnie polecenie to można przedstawić jako: GRANT prawa ON obiekt TO użytkownik [IDENTIFIED BY 'hasło']
Oznacza ono: nadaj użytkownikowi użytkownik prawa (uprawnienia, przywileje) prawa do obiektu obiekt, nadając mu jednocześnie hasło hasło. Nadanie hasła jest przy tym opcjonalne. Praw, które można nadać użytkownikowi, jest bardzo wiele i mogą się one odnosić do różnych obiektów, całych baz danych, pojedynczych tabel czy nawet ich kolumn. Do dalszej pracy wystarczy, jeśli utworzymy użytkownika o nazwie php, nadając mu dowolne hasło (w dalszej pracy będzie używane hasło test, które oczywiście należy zmienić na bardziej skomplikowane), i nadamy mu pełne prawa do utworzonej poprzednio bazy danych o nazwie testphp. Utworzenie takiego użytkownika osiągniemy, wydając serwerowi polecenie: GRANT ALL ON testphp.* TO php IDENTIFIED BY 'test';
Od tej chwili podczas wywoływania klienta mysql można będzie posługiwać się kontem php zamiast kontem administratora (root), czyli wydawać w konsoli polecenie: mysql –u php -ptest
Utworzone konto użytkownika może zostać usunięte za pomocą polecenia DROP USER o schematycznej postaci: drop user nazwa_użytkownika;
W wersjach do 5.0.2 przed wydaniem tego polecenia należy najpierw odebrać użytkownikowi jego prawa, wykorzystując instrukcję: REVOKE all ON *.* FROM nazwa_użytkownika;
W wersjach, począwszy od 5.0.2, wystarczy samo: drop user nazwa_użytkownika;
Wybór bazy danych Jak wiadomo, na serwerze może istnieć wiele baz danych tworzonych za pomocą polecenia CREATE DATABASE. Ponieważ każda baza jest niezależna i może zawierać takie
Rozdział 8. ♦ Współpraca z bazami danych
371
same elementy jak każda inna, serwer musi wiedzieć, z którą bazą aktualnie chcemy pracować. Takiego wyboru można dokonać po zalogowaniu się do serwera, za pomocą instrukcji use w postaci: use nazwa_bazy;
Jeśli zatem chcemy pracować z bazą o nazwie testphp, musimy wydać polecenie: use testphp;
Aby wykonanie tego polecenia zakończyło się sukcesem, dany użytkownik musi oczywiście mieć prawa dostępu do tej bazy, inaczej zgłoszony zostanie komunikat o błędzie. Jeśli na przykład powyższe polecenie wydał użytkownik o nazwie user1, który nie ma praw do bazy testphp, serwer wyświetli komunikat podobny do: ERROR 1044: Access denied for user 'user1'@'%' to database 'testphp'
Wyboru bazy można także dokonać w wierszu poleceń, podając nazwę podczas wywoływania aplikacji klienta mysql (przy założeniu, że chcemy używać bazy o nazwie testphp, logując się jako użytkownik php), np.: mysql -u php –ptest testphp
W przypadku gdy żadna baza nie została wybrana, przy próbie wykonania zapytania (utworzenia tabeli, pobrania danych itp.; patrz lekcja 23.) serwer zgłosi błąd, co objawi się w postaci komunikatu podobnego do: ERROR 1046: No database selected
Lekcja 23. Podstawy SQL Czym jest SQL? SQL, czyli Structured Query Language, to strukturalny język zapytań, który umożliwia wykonywanie wszelkich operacji na relacyjnych bazach danych. Jest to język uniwersalny stosowany praktycznie w każdym popularnym systemie relacyjnych baz danych, zarówno w systemach komercyjnych, takich jak DB2, MS SQL czy Oracle, jak i tych rozwijanych na zasadach wolnego oprogramowania, np. PostgreSQL czy niektóre wersje MySQL. SQL realizuje trzy różne typy zadań, w związku z tym występujące w nim instrukcje można podzielić na trzy grupy: DDL — język definiowania danych (z ang. Data Definition Language)
umożliwiający definicję struktury danych, DML — język manipulacji danymi (z ang. Data Manipulation Language)
umożliwiający pobieranie i modyfikowanie danych, DCL — język kontroli danych (z ang. Data Control Language)
umożliwiający kontrolę dostępu do danych.
372
PHP5. Praktyczny kurs
W poprzedniej lekcji pojawiły się podstawowe instrukcje należące do DCL. Umożliwiały one m.in. zarządzanie kontami użytkowników i administratora. W tej lekcji pojawią się instrukcje należące do DDL i DML, które pozwolą na tworzenie struktury bazy oraz na wprowadzanie do niej danych i zarządzanie nimi. W SQL nie są rozróżniane wielkie i małe litery, można ich więc używać zamiennie według własnego uznania. W książce została przyjęta konwencja, że elementy języka SQL będą zapisywane wielkimi literami. Przykłady z niniejszej lekcji opierają się na wykorzystaniu pracującego w wierszu poleceń klienta MySQL, istnieje jednak również możliwość wykonywania ich za pomocą klienta SQLite dostępnego pod adresem http://www.sqlite.org.
Obsługa tabel Jak wiadomo z lekcji 22., dane w bazie są przechowywane w tabelach. Trzeba zatem poznać konstrukcje języka SQL, które pozwalają na manipulację tymi strukturami danych. Aby sprawnie posługiwać się bazą danych, trzeba wiedzieć, jak tworzyć, modyfikować i usuwać tabele. Dopiero potem można zacząć z nich korzystać, czyli nauczyć się, jak wprowadzać dane do bazy oraz jak je z niej pobierać.
Tworzenie tabel Do tworzenia tabel służy instrukcja CREATE TABLE w schematycznej postaci: CREATE TABLE nazwa_tabeli ( nazwa_kolumny_1 typ_kolumny_1 [atrybuty], nazwa_kolumny_2 typ_kolumny_2 [atrybuty], ... nazwa_kolumny_N typ_kolumny_N [atrybuty], )
Nazwa tabeli może zawierać dowolne znaki, które mogą wystąpić w nazwie pliku w systemie operacyjnym, na którym działa serwer MySQL, z wyjątkiem /, \ i .. Jeśli jednak zawiera jakieś znaki specjalne (w tym spacje) lub jest słowem zastrzeżonym dla konstrukcji języka (np. select, create), powinna być jednak ujęta w znaki ‘ (lewy apostrof), np. ‘select‘. Taka instrukcja utworzy tabelę w bieżącej bazie. Nazwa bazy może być jednak podana jawnie. Należy wtedy użyć konstrukcji nazwa_bazy.nazwa_tabeli lub ‘nazwa_bazy‘. ‘nazwa_tabeli‘. Podobne zasady dotyczą nazw kolumn. Typ kolumny określa typ danych, które będzie ona mogła przechowywać, np. łańcuch znaków, liczby itp. Typy danych zostaną omówione w następnej części lekcji. W nazwach tabel i kolumn mogą występować zarówno małe, jak i wielkie litery, jednak nie są one rozróżniane. W dalszych przykładach zostało przyjęte, że nazwy tabel są pisane tylko małymi literami, a kolumn — małymi i wielkimi, wyłącznie za pomocą liter alfabetu łacińskiego (bez polskich znaków) oraz bez spacji (spacje w razie potrzeby będą zastępowane znakami podkreślenia).
Rozdział 8. ♦ Współpraca z bazami danych
373
Dla treningu spróbujmy teraz utworzyć prostą tabelę klient, która będzie zawierała dwie kolumny. Pierwsza kolumna o nazwie Indeks będzie przechowywała liczby całkowite (typ danych INTEGER), a druga o nazwie Nazwa — ciągi o maksymalnej długości 20 znaków (typ VARCHAR(20)). Instrukcja SQL tworząca taką tabelę ma postać: CREATE TABLE klient( Indeks INTEGER, Nazwa VARCHAR(20) );
Każda kolumna może mieć dodatkowe atrybuty. Najczęściej spotykane to: PRIMARY KEY, NOT NULL, AUTO_INCREMENT, INDEX, UNIQUE. Atrybut PRIMARY KEY oznacza, że dana kolumna jest kluczem podstawowym (głównym). Jednocześnie wymusza to zapis unikalnych wartości w kolejnych wierszach (jest to zrozumiałe, skoro klucz podstawowy musi jednoznacznie identyfikować każdy wiersz). Atrybut NOT NULL oznacza, że w danej kolumnie nie mogą znajdować się wartości puste, czyli że każdy jej wiersz musi zawierać jakąś wartość. Próba zapisania wartości pustej zakończy się niepowodzeniem. Atrybut AUTO_INCREMENT ma zastosowanie jedynie do kolumn, które przechowują wartości całkowite. Podczas wstawiania nowego wiersza wartość takiej kolumny może być automatycznie zwiększana o jeden. Kolumny tego typu automatycznie otrzymują też atrybut NOT NULL (czyli nie mogą zawierać wartości pustych; każdy wiersz musi zawierać jakąś wartość). Atrybut INDEX oznacza, że dana kolumna będzie indeksowana. Indeks to specjalna dodatkowa struktura porządkująca dane w kolumnie, a tym samym zwiększająca szybkość niektórych operacji. Klucze podstawowe są indeksowane automatycznie. Atrybut UNIQUE oznacza, że dane w kolumnie będą musiały być unikatowe, tzn. nie będą mogły istnieć dwa wiersze o takiej samej zawartości. Użycie atrybutu UNIQUE powoduje jednoczesne utworzenie indeksu dla kolumny. Jeśli zatem w naszej przykładowej tabeli klient kolumna Indeks miałaby być kluczem podstawowym, należałoby zastosować konstrukcję: CREATE TABLE klient( Indeks INTEGER NOT NULL PRIMARY KEY, Nazwa VARCHAR(20) );
Formalnie kolumna będąca kluczem podstawowym powinna być, tak jak w powyższym przykładzie, deklarowana z atrybutem NOT NULL, jeśli jednak atrybut ten zostanie pominięty, serwer doda go automatycznie. Nie zostanie zatem zgłoszony błąd, jeśli zastosowana zostanie forma skrócona w postaci: CREATE TABLE klient( Indeks INTEGER PRIMARY KEY, Nazwa VARCHAR(20) );
374
PHP5. Praktyczny kurs
Gdybyśmy chcieli, aby kolumna Indeks była kluczem podstawowym z generowanymi automatycznie wartościami, a Nazwa nie mogła zawierać wartości pustych, należałoby zastosować konstrukcję: CREATE TABLE klient( Indeks INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, Nazwa VARCHAR(20) NOT NULL );
W przypadku gdy klucz podstawowy miałby się składać z więcej niż jednej kolumny, jego definicja powinna wyglądać inaczej. Schematycznie tego typu konstrukcja ma postać: CREATE TABLE nazwa_tabeli ( nazwa_kolumny_1 typ_kolumny_1 [atrybuty], nazwa_kolumny_2 typ_kolumny_2 [atrybuty], ... nazwa_kolumny_n typ_kolumny_n [atrybuty], PRIMARY KEY (kolumna_x, kolumna_y, ..., kolumna_z) )
Jeśli na przykład tabela ma trzy kolumny: Identyfikator o typie INTEGER oraz Imie i Nazwisko o typie VARCHAR i wszystkie one mają tworzyć klucz podstawowy, instrukcja SQL tworząca tę tabelę będzie miała postać: CREATE TABLE klient( Identyfikator INTEGER NOT NULL, Imie VARCHAR(20) NOT NULL, Nazwisko VARCHAR(20) NOT NULL, PRIMARY KEY(identyfikator, imie, nazwisko) );
Modyfikacja tabel Jeśli konieczna jest zmiana struktury już istniejącej tabeli, należy skorzystać z polecenia ALTER TABLE w postaci: ALTER TABLE nazwa_tabeli zmiana1[, zmiana2[, ...,[zmianaN]]]
Można w ten sposób dodawać i usuwać kolumny, modyfikować ich typy i nazwy, dodawać bądź usuwać indeksy itp. Zmiany wprowadza się w podobny sposób jak przy tworzeniu tabeli. Każdy z ciągów zmiana jest w istocie poleceniem do wykonania. Lista dostępnych poleceń zależna jest od systemu bazy danych. Często spotykane polecenia to: ADD [COLUMN] definicja_kolumny [FIRST | AFTER nazwa_kolumny] — dodaje nową kolumnę, a opcje FIRST i AFTER pozwalają na umiejscowienie
jej w strukturze tabeli. ADD [COLUMN] (definicja_kolumny) — dodaje nową kolumnę. ADD INDEX [nazwa] [typ] (kolumna1, kolumna2,...,kolumnaN) — tworzy nowy indeks o nazwie nazwa i typie typ, będą do niego należały
kolumny wymienione w nawiasie okrągłym.
Rozdział 8. ♦ Współpraca z bazami danych
375
ADD [CONSTRAINT [symbol]] PRIMARY KEY [typ] (kolumna1, kolumna2,..., kolumnaN) — dodaje do tabeli klucz podstawowy, na który będą się składały
kolumny wymienione w nawiasie okrągłym. ADD [CONSTRAINT [symbol]] UNIQUE [nazwa] [typ] (kolumna1, kolumna2,..., kolumnaN) — dodaje do tabeli nowy unikalny indeks, na który będą się składały
kolumny wymienione w nawiasie okrągłym. ADD [CONSTRAINT [symbol]] FOREIGN KEY [nazwa] (kolumna1, kolumna2,..., kolumnaN) [definicja odniesienia] — dodaje do tabeli nowy klucz obcy,
na który będą się składały kolumny wymienione w nawiasie okrągłym. ALTER [COLUMN] nazwa SET DEFAULT wartość — specyfikuje wartość domyślną dla kolumny nazwa. ALTER [COLUMN] nazwa DROP DEFAULT wartość — usuwa wartość domyślną dla kolumny nazwa. CHANGE [COLUMN] stara_nazwa definicja_nowej_kolumny [FIRST | AFTER nazwa] — zmienia kolumnę stara_nazwa na zdefiniowaną przez definicja_ nowej_kolumny. Opcje FIRST i AFTER pozwalają na umiejscowienie nowej
kolumny w strukturze tabeli. [DEFAULT] CHARACTER SET nazwa [COLLATE collation_name] — ustala domyślny zestaw znaków dla tabeli. Opcja COLLATE pozwala na zdefiniowanie
obowiązujących dla danej strony kodowej reguł porównywania i sortowania. CONVERT TO CHARACTER SET nazwa [COLLATE collation_name] — wykonuje konwersję zestawu znaków (strony kodowej). Opcja COLLATE pozwala na
zdefiniowanie obowiązujących dla danej strony kodowej reguł porównywania i sortowania. MODIFY [COLUMN] definicja_kolumny [FIRST | AFTER nazwa] — modyfikuje strukturę kolumny, nie pozwala jednak na zmianę jej nazwy. Opcje FIRST i AFTER pozwalają na umiejscowienie nowej kolumny w strukturze tabeli. DROP [COLUMN] nazwa — usuwa kolumnę nazwa. DROP PRIMARY KEY — usuwa z tabeli klucz podstawowy. DROP INDEX nazwa — usuwa indeks nazwa. DROP FOREIGN KEY nazwa — usuwa klucz obcy nazwa. ORDER BY nazwa — pozwala na ustawienie wierszy w porządku określonym przez dane w kolumnie nazwa. RENAME [TO] nowa_nazwa — zmienia nazwę tabeli na nowa_nazwa.
Zmiany tego typu wykonywane są w taki sposób, że najpierw powstaje tymczasowa kopia tabeli, w której wprowadzana jest żądana zmiana. Następnie, jeśli operacja zakończy się powodzeniem, stara tabela jest usuwana, a kopia otrzymuje jej nazwę.
376
PHP5. Praktyczny kurs
Spróbujmy teraz wykonać kilka przykładów, aby przekonać się w praktyce, jak działają niektóre z wymienionych wyżej konstrukcji języka. Utwórzmy tabelę ksiazki, wykorzystując do tego celu instrukcję SQL w postaci: CREATE TABLE ksiazki( KsiazkaId INTEGER, AutorId INTEGER, Tytul VARCHAR(15) );
Szybko zauważymy, że nie zdefiniowaliśmy tutaj klucza podstawowego, którym powinna być kolumna KsiazkaId. Trzeba zatem naprawić to niedopatrzenie, wydając polecenie: ALTER TABLE Ksiazki ADD PRIMARY KEY (KsiazkaId);
Po jego wydaniu na ekranie zobaczymy komunikat: mysql> ALTER TABLE Ksiazki ADD PRIMARY KEY (KsiazkaId); Query OK, 0 rows affected (0.13 sec) Records: 0 Duplicates: 0 Warnings: 0
Oznacza to, że operacja zakończyła się sukcesem. Gdybyśmy chcieli, aby pole KsiazkaId automatycznie zwiększało swoją wartość podczas dodawania rekordów do tabeli, czyli aby miało przypisany modyfikator AUTO_INCREMENT, musielibyśmy wykonać polecenie: ALTER TABLE Ksiazki MODIFY COLUMN KsiazkaId INTEGER AUTO_INCREMENT;
Mogłoby się także okazać, że pole Tytul jest zbyt krótkie i trzeba by zwiększyć jego maksymalną długość do 45 znaków. Taka modyfikacja również nie stanowi problemu, wystarczy instrukcja SQL w postaci: ALTER TABLE Ksiazki MODIFY COLUMN Tytul VARCHAR(45);
Nic nie stoi również na przeszkodzie, aby do tabeli dodać kolumnę ISBN o długości 10 znaków: ALTER TABLE Ksiazki ADD COLUMN ISBN VARCHAR(10);
Usuwanie tabel Tabele usuwa się za pomocą instrukcji DROP TABLE o schematycznej postaci: DROP TABLE nazwa1, nazwa2, ..., nazwaN
Przykładowo usunięcie tabeli o nazwie Klient wykonamy, wydając polecenie: DROP TABLE Klient
Natomiast równoczesne usunięcie dwóch tabel o nazwach Klient i Zamowienie uzyskamy, stosując konstrukcję: DROP TABLE Klient, Zamowienie
Rozdział 8. ♦ Współpraca z bazami danych
377
Typy danych w kolumnach Każda kolumna tabeli w bazie danych ma przypisany typ, który określa rodzaj danych, jakie mogą być w niej przechowywane. Występujące w SQL typy danych można podzielić na trzy główne rodzaje: typy liczbowe, typy daty i czasu, typy łańcuchowe.
Każdy rodzaj to z kolei od kilku do kilkunastu typów i podtypów — nie będą tu jednak omawiane wszystkie możliwe kombinacje, a jedynie te najpopularniejsze, które przydadzą się w dalszych lekcjach (w tym typ specjalny NULL).
Typy liczbowe Typy liczbowe możemy podzielić na dwa rodzaje — całkowitoliczbowe oraz zmiennoprzecinkowe. Zgodnie z nazwami służą one do reprezentowania wartości całkowitych oraz zmiennoprzecinkowych (zmiennopozycyjnych, rzeczywistych). Typy całkowitoliczbowe zostały przedstawione w tabeli 8.1. Tabela 8.1. Wybrane typy liczbowe występujące w SQL Liczba zajmowanych bajtów
Typ
Zakres wartości
Opis
INTEGER
Od –2 147 483 648 (–231) do 2 147 483 647(231 – 1) dla liczb ze znakiem i od 0 do 4 294 967 295 (232 – 1) dla liczb bez znaku
4
Reprezentacja zwykłych wartości całkowitoliczbowych. Można również stosować synonim INT.
FLOAT
Od –3.402823466E+38 do 3.402823466E+38
4
Liczby zmiennoprzecinkowe pojedynczej precyzji.
DOUBLE
Od –1.7976931348623157E+308 do 1.7976931348623157E+308
8
Liczby zmiennoprzecinkowe podwójnej precyzji. Można również stosować synonimy DOUBLE PRECISION i REAL.
DECIMAL
Zmienna
Zmienna
Wartości z separatorem dziesiętnym. W wersjach przed 5.0.3 przechowywane jako łańcuch znaków. Zarówno całkowita maksymalna liczba znaków, jak i liczba znaków po separatorze dziesiętnym mogą być określane przez dodatkowe parametry. Można również stosować synonimy DEC, NUMERIC i FIXED.
378
PHP5. Praktyczny kurs
Przy tych typach dozwolone jest stosowanie modyfikatorów UNSIGNED oraz ZEROFILL. Pierwszy z nich oznacza, że wartość ma być traktowana jako liczba bez znaku (czyli niedopuszczalne są wartości ujemne). Drugi powoduje, że jeżeli liczba cyfr w danej wartości jest mniejsza od maksymalnej liczby wyświetlanych znaków, wolne miejsca zostaną dopełnione zerami. Zastosowanie atrybutu ZEROFILL powoduje, że automatycznie zostanie również zastosowany UNSIGNED.
Typy daty i czasu Typy pozwalające na reprezentację daty i czasu zostały zebrane w tabeli 8.2. Dane tych typów będą wyświetlane w formatach przedstawionych w kolumnie Opis tabeli, mogą być natomiast zapisywane w bazie przy użyciu różnych formatów. W przypadku typów DATE, DATETIME i TIMESTAMP dopuszczalne są formaty: Ciąg znaków RRRR-MM-DD GG:MM:SS i RR-MM-DD GG:MM:SS. Pomiędzy
składowymi daty oraz pomiędzy składowymi czasu mogą występować dowolne znaki przestankowe, prawidłowe są zatem zapisy: 2012-05-20 20:12:55, 2012.05.20 20-12-55, 2012*05*20 20%12%55. Ciąg znaków RRRR-MM-DD i YY-MM-DD. Pomiędzy składowymi daty mogą
występować dowolne znaki przestankowe, prawidłowe są zatem zapisy: 2012-05-20, 2012.05.20, 12*05*20. Ciąg znaków RRRRMMDDGGMMSS i RRMMDDGGMMSS. Pomiędzy składowymi nie mogą
występować żadne znaki przestankowe, cały ciąg musi zaś reprezentować poprawną datę i czas. W związku z tym prawidłowe są zapisy: 20120520201255, 120520201255 — oba interpretowane jako 2012-05-20 20:12:55. Ciąg znaków RRRRMMDD i RRMMDD. Pomiędzy składowymi nie mogą występować
żadne znaki przestankowe, cały ciąg musi zaś reprezentować poprawną datę. W związku z tym prawidłowe są zapisy: 20120520, 120520 — oba interpretowane jako 2012-05-20. Wartość liczbowa zapisana jako RRRRMMDDGGMMSS, RRMMDDGGMMSS, RRRRMMDD lub RRMMDD, o ile reprezentuje poprawną datę i/lub czas.
W przypadku typu TIME są dopuszczalne następujące formaty: Ciąg znaków D GG:MM:SS. Ciąg D reprezentuje dni i może przyjmować
wartości od 0 do 34. Możliwe są również warianty skrócone w następujących postaciach: GG:MM:SS, GG:MM, D GG:MM:SS, D GG:MM, D GG i SS. Poprawne są w związku z tym zapisy: 12:52:24, 12:52, 24. Ciąg znaków GGMMSS. Pomiędzy składowymi nie mogą występować żadne
znaki przestankowe, cały ciąg musi zaś reprezentować poprawny czas. Poprawne są w związku z tym zapisy: 125224, 182931. Wartość liczbowa zapisana jako GGMMSS, o ile reprezentuje poprawny czas. Możliwe są również alternatywne zapisy w postaci: SS, MMSS, GGMMSS.
W przypadku typu YEAR są dopuszczalne następujące formaty: Ciąg znaków w formacie RRRR. Dopuszczalny zakres to 1901 – 2155.
Rozdział 8. ♦ Współpraca z bazami danych
379
Tabela 8.2. Typy daty i czasu Typ
Dopuszczalne wartości
Liczba zajmowanych bajtów
DATE
Od 1000-01-01 do 9999-12-31
3
Typ przeznaczony do reprezentacji daty. Wartości będą pobierane z bazy i wyświetlane w formacie RRRR-MM-DD.
DATETIME
Od 1000-01-01 00:00:00 do 9999-12-31 23:59:59
8
Typ przeznaczony do reprezentacji daty i czasu. Wartości będą pobierane z bazy i wyświetlane w formacie RRRR-MM-DD GG:MM:SS.
TIMESTAMP
Zależne od dodatkowych opcji
4
Typ przeznaczony do reprezentacji znacznika czasu.
TIME
Od -838:59:59 do 838:59:59
3
Typ przeznaczony do reprezentacji czasu. Wartości będą pobierane z bazy i wyświetlane w formacie GG:MM:SS lub GGG:MM:SS.
YEAR
Od 1901 do 2155
1
Typ przeznaczony do reprezentacji lat. Wartości będą pobierane z bazy i wyświetlane w formacie RRRR. Wartości tego typu są zapisywane na jednym bajcie.
Opis
Ciąg znaków w formacie RR. Dopuszczalny zakres to 00 – 99. Ciągi od 00 do 69 są interpretowane jako lata 2000-2069, natomiast od 70 do 99 jako
1970 – 1999. Wartość liczbowa w formacie RRRR. Dopuszczalny zakres to 1901 – 2155. Wartość liczbowa w formacie RR. Dopuszczalny zakres to 1 – 99. Wartości od 1 do 69 są interpretowane jako lata 2001 – 2069, natomiast ciągi od 70 do 99 jako 1970 – 1999.
Jeśli w którymkolwiek z wymienionych przypadków zostanie podana wartość, która nie może zostać zinterpretowana jako poprawny argument danego typu, w bazie będzie ona interpretowana jako wartość specjalna: dla typu DATE — 0000-00-00, dla typu DATETIME — 0000-00-00 00:00:00, dla typu TIMESTAMP — 00000000000000, dla typu TIME — 00:00:00, dla typu YEAR — 0000.
Typy łańcuchowe Typy łańcuchowe służą do przechowywania zarówno ciągów znaków, jak i danych binarnych. Można je podzielić na cztery grupy:
380
PHP5. Praktyczny kurs typy CHAR i VARCHAR, typy BINARY i VARBINARY, typy BLOB i TEXT, typy ENUM i SET.
Typy CHAR i VARCHAR Typy CHAR i VARCHAR służą do przechowywania łańcuchów znakowych, czyli tekstów. Oba wymagają podania długości łańcucha za nazwą typu w nawiasie okrągłym, czyli: CHAR(długość)
i VARCHAR(długość),
gdzie długość oznacza liczbę znaków5. Jeśli chcemy na przykład utworzyć kolumnę, która będzie mogła przechowywać do 20 znaków, należy zastosować konstrukcję: CHAR(20)
lub VARCHAR(20)
W przypadku typu CHAR cała kolumna w bazie danych będzie miała długość wskazaną parametrem długość. Jeśli zapisywane dane będą miały mniej znaków, pozostałe miejsca zostaną uzupełnione spacjami z prawej strony. Zarówno te spacje, jak i te znajdujące się na początku tekstu (!) będą usuwane podczas pobierania danych. Parametr długość może przyjmować wartości od 0 do 2556. W przypadku typu VARCHAR każdy wiersz kolumny ma zmienną długość wynikającą z liczby znaków zapisywanego łańcucha (plus 1 bajt niezbędny do zapisania liczby znaków łańcucha). Parametr długość może przyjmować następujące wartości (dla bazy MySQL): od 1 do 255 w wersjach przed 4.0.2, od 0 do 255 w wersjach od 4.0.2 oraz od 0 do 65 535, począwszy od wersji 5.0.3. W wersjach przed 5.0.3 podczas zapisywania danych do bazy usuwane są spacje z początku i końca tekstu. Począwszy od wersji 5.0.3, spacje te nie są usuwane ani podczas zapisu, ani podczas odczytu. W przypadku próby zapisania w wierszu kolumny większej liczby znaków, niż wynika to z wartości parametru długość, nadmiarowa liczba bajtów zostanie obcięta oraz wygenerowane będzie ostrzeżenie.
Typy BINARY i VARBINARY Typy BINARY i VARBINARY są podobne do CHAR i VARCHAR z tą różnicą, że przechowują łańcuchy bajtów, a nie znaków. Typ BINARY definiuje się w postaci: BINARY(długość) 5
W wersjach wcześniejszych niż 4.1 — liczbę bajtów.
6
W przypadku wersji wcześniejszych niż 3.23 — od 1 do 255.
Rozdział 8. ♦ Współpraca z bazami danych
381
Natomiast typ VARBINARY w postaci VARBINARY(długość)
Pozostałe właściwości są analogiczne. Należy jedynie zwrócić uwagę, że parametr długość w tym przypadku oznacza liczbę bajtów, a nie liczbę znaków.
Typy BLOB i TEXT Typy BLOB i TEXT służą do przechowywania dużej ilości danych. Typ BLOB (z ang. Binary Large Object) do przechowywania ciągów binarnych, natomiast TEXT — tekstowych. Oba typy dzielą się na cztery podtypy, które różnią się od siebie maksymalną wielkością danych, które mogą być za ich pomocą zapisane.
Typy ENUM i SET Typ ENUM jest typem wyliczeniowym pozwalającym ograniczyć zbiór wartości, który będzie mógł być przechowywany w danej kolumnie. Dopuszczalne wartości definiuje się w nawiasie okrągłym za nazwą typu, oddzielając je od siebie znakami przecinka, schematycznie: ENUM('wartość1', 'wartość2', ..., 'wartośćN')
W tak określonej kolumnie w pojedynczym wierszu będzie mogła się znaleźć tylko jedna z zadeklarowanych wartości (lub wartość NULL). Maksymalna liczba wartości w typie ENUM to 65 535. Przykładowo w kolumnie zdefiniowanej jako: ENUM('jeden', 'dwa', 'trzy')
będą dopuszczalne jedynie ciągi znaków: jeden, dwa i trzy. Typ SET jest również typem wyliczeniowym, który definiowany jest w analogiczny sposób jak ENUM, czyli: SET('wartość1', 'wartość2', ..., 'wartośćn')
W tym jednak przypadku każdy wiersz kolumny będzie mógł zawierać dowolny podzbiór zdefiniowanych wartości oddzielonych od siebie przecinkami. Czyli po zdefiniowaniu kolumny jako: SET('jeden', 'dwa')
w każdym wierszu będą mogły być zarówno wartości jeden i dwa, jak i jednocześnie jeden, dwa. Maksymalna liczba wartości możliwa do zadeklarowania w typie SET to 64.
Typ NULL Typ null jest typem specjalnym. Wartość typu null (zapisywana jako ciąg znaków null7) jest wartością pustą, czyli oznacza po prostu brak wartości. Jak to rozumieć? 7
Nie oznacza to jednak, że w tabeli bazy danych faktycznie jest zapisywany ciąg znaków null. To, w jaki sposób wartość ta jest reprezentowana wewnątrz bazy, jest uzależnione od danego serwera bazy danych.
382
PHP5. Praktyczny kurs
Jeśli mamy na przykład kolumnę przechowującą wartości całkowite, może się zdarzyć, że dla części wierszy właściwa wartość będzie nieznana, wtedy takie wiersze będą zawierały właśnie wartość specjalną null.
Zapytania Wprowadzanie danych Tabele utworzone w sposób opisany na wcześniejszych stronach trzeba w jakiś sposób wypełnić danymi. Służy do tego występująca w kilku wersjach instrukcja INSERT INTO. Omówiona zostanie jednak tylko jej podstawowa, najczęściej wykorzystywana postać, która całkowicie wystarcza do wykonywania tego typu zadań. Typowe wywołanie INSERT INTO ma postać: INSERT [INTO] tabela [(kolumna1, kolumna2, ..., kolumnaN)] VALUES (wartość1, wartość2, ..., wartośćN)
Powoduje ona wprowadzenie do tabeli nowego wiersza, w którym w polu kolumna1 została zapisana wartość wartość1, w polu kolumna2 — wartość wartość2 itd. Jak będzie to wyglądało w praktyce? Załóżmy, że w bazie istnieje tabela Klienci utworzona za pomocą instrukcji: CREATE TABLE klienci( KlientId INTEGER PRIMARY KEY, Imie VARCHAR(25), Nazwisko VARCHAR (25), Adres VARCHAR (60) );
i chcielibyśmy zapisać w niej nowy wiersz, np. dane Jana Kowalskiego zamieszkałego przy ulicy Klonowej 24 w Poznaniu, któremu został nadany identyfikator 1. Należałoby zastosować w takiej sytuacji instrukcję INSERT INTO w postaci: INSERT INTO klienci (KlientId, Imie, Nazwisko, Adres) VALUES (1, 'Jan', 'Kowalski', 'Klonowa 24, Poznań');
W celu zwiększenia czytelności można ją rozbić na kilka wierszy, np.: INSERT INTO klienci (KlientId, Imie, Nazwisko, Adres) VALUES (1, 'Jan', 'Kowalski', 'Klonowa 24, Poznań');
Zauważmy, że wszystkie wprowadzane ciągi znaków zostały ujęte w apostrofy (można także użyć znaków cudzysłowu). Jest to niezbędne, aby zapytanie zostało wykonane prawidłowo. Nie ma natomiast potrzeby ujmowania w znaki apostrofu wartości liczbowych. Nazwy kolumn w instrukcji INSERT są opcjonalne i w sytuacji gdy wprowadzamy wartości wszystkich pól, można je pominąć. A zatem powyższa instrukcja mogłaby mieć również postać: INSERT INTO Klienci VALUES (1, 'Jan', 'Kowalski', 'Klonowa 24, Poznań');
Rozdział 8. ♦ Współpraca z bazami danych
383
Istnieje również możliwość zapełnienia tylko niektórych kolumn. Jeśli na przykład nie znamy adresu, a w kolumnie Adres dopuszczalne są wartości puste NULL, można wydać polecenie: INSERT INTO Klienci VALUES (1, 'Jan', 'Kowalski', NULL);
lub INSERT INTO Klienci (KlientId, Imie, Nazwisko) VALUES (1, 'Jan', 'Kowalski');
Zwróćmy uwagę, że w pierwszym przypadku nie trzeba było podawać nazw kolumn, ponieważ wstawialiśmy wartości do wszystkich pól, ale w przypadku drugim były one niezbędne. Co by się jednak stało, gdybyśmy spróbowali wykonać pierwszą z powyższych instrukcji, ale pole Adres byłoby polem wymaganym (czyli zostałoby zdefiniowane jako NOT NULL)? Otóż zostałby zgłoszony komunikat o błędzie podobny do zaprezentowanego na rysunku 8.19.
Rysunek 8.19. Próba wprowadzenia wartości NULL do kolumny zdefiniowanej jako NOT NULL
Jest jednak sytuacja, w której do pola w kolumnie zdefiniowanej jako NOT NULL można wprowadzić wartość pustą. Są to pola typu AUTO_INCREMENT. W takim wypadku wprowadzenie wartości typu NULL jest nie tylko możliwe, ale czasem niezbędne do prawidłowego działania. Załóżmy, że tabela klienci została stworzona za pomocą nieco zmodyfikowanej instrukcji CREATE w postaci: CREATE TABLE klienci( KlientId INTEGER PRIMARY KEY AUTO_INCREMENT, Imie VARCHAR(25), Nazwisko VARCHAR (25), Adres VARCHAR (60) )
Jak zostało to wspomniane wyżej, pola typu AUTO_INCREMENT automatycznie uzyskują atrybut NOT NULL. Jednak instrukcja INSERT powinna mieć w tym przypadku postać: INSERT INTO klienci VALUES (NULL, 'Jan', 'Kowalski', 'Klonowa 24, Poznań');
Wprowadzana do kolumny KlientId wartość NULL zostanie bowiem automatycznie zamieniona na kolejną wartość całkowitą. W przypadku pierwszego wiersza będzie to 1, w przypadku drugiego — 2 itd. Nie oznacza to jednak, że do pola AUTO_INCREMENT nie można wprowadzić własnej wartości. Jeśli po wykonaniu powyższej instrukcji chcemy wprowadzić do bazy dane Andrzeja Nowaka zamieszkałego przy ulicy Lipowej 50 we Wrocławiu i chcemy jednocześnie nadać mu określony identyfikator, np. 25, to nic nie stoi na przeszkodzie, aby wykonać instrukcję: INSERT INTO klienci VALUES (25, 'Andrzej', 'Nowak', 'Lipowa 50, Wrocław');
384
PHP5. Praktyczny kurs
Musimy jednak pamiętać, że kolejny wiersz korzystający z funkcji AUTO_INCREMENT będzie miał już numer 26 (o jeden większy niż maksymalna ostatnio zapisana wartość w kolumnie). Warto również wiedzieć, że kolejność wprowadzania danych w instrukcji INSERT nie musi być taka sama jak struktura kolumn w tabeli. Prawidłowa jest zatem również konstrukcja: INSERT INTO klienci (Nazwisko, Imie, Adres, KlientId) VALUES ('Kowalski', 'Jan', 'Klonowa 24, Poznań', NULL);
W przypadku kolumn z atrybutem AUTO_INCREMENT można też pominąć wartość takiej kolumny przy wprowadzaniu danych. Wtedy zachowanie będzie takie samo, jak gdyby została użyta wartość NULL. Prawidłowa byłaby zatem też instrukcja: INSERT INTO klienci (Imie, Nazwisko, Adres) VALUES ('Jan', 'Kowalski', 'Klonowa 24, Poznań');
Oczywiście przy tego typu konstrukcji (zgodnie z podanymi wcześniej zasadami) konieczne jest wymienienie nazw kolumn w pierwszej części zapytania.
Pobieranie danych Dane zapisane w tabelach bazy można pobierać za pomocą instrukcji SELECT. Posiada ona wiele opcji i klauzul dodatkowych, przedstawiona zostanie zatem tylko jej podstawowa postać, która schematycznie wygląda następująco: SELECT kolumna1, kolumna2, ..., kolumnaN FROM tabela [WHERE warunek] [ORDER BY kolumna1, kolumna2, ..., kolumnaN [ASC | DEC]]
Oznacza ona: pobierz wartości wymienionych kolumn z tabeli tabela spełniających warunek warunek, a wyniki posortuj względem kolumn wymienionych w klauzuli ORDER BY rosnąco (ASC) lub malejąco (DESC). Aby zobaczyć, jak w praktyce działają proste zapytania typu SELECT, utworzymy tabelę przechowującą dane o osobach: imię, nazwisko oraz rok i miejsce urodzenia. Wykorzystamy do tego celu instrukcję CREATE TABLE w postaci: CREATE TABLE osoba ( Id INTEGER PRIMARY KEY, Imie VARCHAR(25), Nazwisko VARCHAR(35), Rok_urodzenia YEAR, Miejsce_urodzenia VARCHAR(35) );
Za pomocą serii instrukcji INSERT wprowadzimy teraz do tak utworzonej tabeli przykładowe dane, w sumie 10 wierszy. Będzie za to odpowiedzialna instrukcja SQL przedstawiona na listingu 8.1.
Rozdział 8. ♦ Współpraca z bazami danych
385
Listing 8.1. Instrukcje SQL wstawiające przykładowe dane do tabeli osoba INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT
INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO
Osoba Osoba Osoba Osoba Osoba Osoba Osoba Osoba Osoba Osoba
VALUES VALUES VALUES VALUES VALUES VALUES VALUES VALUES VALUES VALUES
(1, 'Adam', 'Kowalski', 1964, 'Bydgoszcz'); (2, 'Adam', 'Nowak', 1972, 'Szczecin'); (3, 'Andrzej', 'Kowalski', 1986, 'Nidzica'); (4, 'Arkadiusz', 'Malinowski', 1986, 'Kielce'); (5, 'Andrzej', 'Malinowski', 1989, 'Kielce'); (6, 'Krzysztof', 'Nowicki', 1986, 'Bydgoszcz'); (7, 'Kacper', 'Adamczyk', 1971, 'Kielce'); (8, 'Kamil', 'Andrzejczak', 1971, 'Radom'); (9, 'Krzysztof', 'Arkuszewski', 1989, 'Szczecin'); (10, 'Kamil', 'Borowski', 1976, 'Skierniewice');
Pobranie wszystkich wierszy tabeli Najprostsza instrukcja SELECT pozwoli na pobranie wszystkich wierszy zawartych w tabeli. Będzie ona miała postać: SELECT * FROM osoba;
Efekt działania tego polecenia jest widoczny na rysunku 8.20. Widać zarówno to, że faktycznie wyświetlone zostały wszystkie dane wprowadzone uprzednio do tabeli osoba, jak i to, iż kolejność wierszy jest taka, w jakiej zostały wprowadzone do bazy. Rysunek 8.20. Efekt działania instrukcji wyświetlającej wszystkie wiersze tabeli Osoba
Sortowanie wyników Gdybyśmy chcieli, aby wyniki zostały posortowane, należałoby użyć dodatkowej klauzuli ORDER BY. W najprostszym przypadku sortowanie może się odbywać względem jednej kolumny. Domyślnie jest to sortowanie w porządku rosnącym (czyli domyślnie stosowana jest opcja ASC). Porządek sortowania można zmienić na malejący, stosując opcję DESC. Jeśli zatem chcemy wyświetlić wszystkie wiersze tabeli posortowane względem nazwiska w porządku alfabetycznym rosnącym, powinniśmy zastosować konstrukcję: SELECT * FROM osoba ORDER BY Nazwisko
lub: SELECT * FROM osoba ORDER BY Nazwisko ASC
386
PHP5. Praktyczny kurs
W przypadku gdybyśmy chcieli uzyskać sortowanie względem kolumny Nazwisko, ale w porządku malejącym, należy zastosować konstrukcję: SELECT * FROM osoba ORDER BY Nazwisko DESC
Sortowanie może się również odbywać względem większej liczby kolumn. Możemy sobie na przykład zażyczyć, żeby tablica została posortowana najpierw względem nazwiska, a następnie względem roku urodzenia. Zadanie takie zostanie zrealizowane przez instrukcję SELECT w postaci: SELECT * FROM osoba ORDER BY Nazwisko, Rok_urodzenia;
Pobieranie zawartości wybranych kolumn Jeżeli chcemy wyświetlić zawartość jedynie niektórych kolumn z wybranej tabeli, ich nazwy należy umieścić za słowem SELECT, oddzielając je znakami przecinka. Gdyby zatem interesowały nas jedynie imiona i nazwiska osób, należałoby wykonać polecenie: SELECT Imie, Nazwisko FROM osoba;
Uzyskamy wtedy efekt widoczny na rysunku 8.21. Rysunek 8.21. Pobranie z tabeli zawartości wybranych kolumn
Oczywiście tak pobrana zawartość kolumn może być również sortowana na takich samych zasadach jak opisane wyżej. Jeśli więc chcemy posortować wyniki względem kolumny Nazwisko w porządku rosnącym, powinniśmy skorzystać z polecenia: SELECT Imie, Nazwisko FROM osoba ORDER BY Nazwisko;
Jeśli natomiast chcielibyśmy posortować wyniki względem kolumny Imie w porządku malejącym, powinniśmy skorzystać z instrukcji: SELECT Imie, Nazwisko FROM osoba ORDER BY Imie DESC;
Zmiana nazw kolumn w wynikach zapytania W pewnych sytuacjach przy pobieraniu danych oryginalne nazwy kolumn tabeli mogą być niewygodne i trzeba będzie je zmienić. Taka zamiana może zostać wykonana w bardzo prosty sposób, jeśli występujące w zapytaniu SELECT nazwy kolumn zastąpimy sekwencjami o schematycznej postaci: nazwa_kolumny AS alias
Rozdział 8. ♦ Współpraca z bazami danych
387
gdzie nazwa_kolumny to nazwa oryginalnej kolumny, a alias to nazwa, jaka ma się pojawić w wynikach zapytania. Jeśli wykonamy na przykład instrukcję: SELECT Imie AS 'Imię', Nazwisko, Rok_urodzenia AS 'Rok urodzenia', Miejsce_urodzenia AS Miasto FROM osoba;
to w efekcie otrzymamy takie dane jak na rysunku 8.22.
Rysunek 8.22. Zmiana nazw kolumn w wynikach zapytania
Selektywne pobieranie danych Pobieranie całej zawartości tabeli stosuje się rzadko. W praktyce najczęściej interesuje nas pewien podzbiór danych. Otrzymanie określonego zestawu wierszy zapewni nam klauzula WHERE instrukcji SELECT. Należy za nią umieścić warunek, jaki muszą spełniać wiersze, aby znalazły się w wynikach zapytania. Warunek w klauzuli WHERE może zawierać operatory relacyjne przedstawione w tabeli 8.3 oraz logiczne z tabeli 8.4. Tabela 8.3. Operatory relacyjne w MySQL Operator Opis
Przykład
=
Operator równości. Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest równy argumentowi znajdującemu się z prawej, lub FALSE (FAŁSZ) w przeciwnym razie. Uwaga! Jeżeli jeden z argumentów (lub oba) równy jest NULL, wynikiem działania jest również NULL.
Id=10, Nazwisko=’Kowalski’
<=>
Operator równości. Działa podobnie jak =, z tą różnicą, że jeśli jednym z argumentów jest NULL, wynikiem porównania jest FALSE, a jeśli oba argumenty są równe NULL, wynikiem jest TRUE.
Adres<=>NULL
<>
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest różny od tego z prawej, lub FALSE (FAŁSZ) w przeciwnym razie.
Id<>2, Nazwisko<>’Kowalski’
!=
Takie samo znaczenie jak <>.
Id!=2, Nazwisko!=’Kowalski’
<
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest mniejszy od tego z prawej, lub FALSE (FAŁSZ) w przeciwnym razie.
Id<10
388
PHP5. Praktyczny kurs
Tabela 8.3. Operatory relacyjne w MySQL — ciąg dalszy Operator Opis
Przykład
>
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest większy od tego z prawej, lub FALSE (FAŁSZ) w przeciwnym razie.
Id>10
<=
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest mniejszy od tego z prawej lub mu równy, lub FALSE (FAŁSZ) w przeciwnym razie.
Id<=10
>=
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest większy od tego z prawej lub mu równy, lub FALSE (FAŁSZ) w przeciwnym razie.
Id>=10
IS NULL
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest równy NULL, lub FALSE (FAŁSZ) w przeciwnym razie.
Adres IS NULL, Id IS NULL
IS NOT NULL
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest różny od NULL, lub FALSE (FAŁSZ) w przeciwnym razie.
Adres IS NOT NULL, Id IS NOT NULL
BETWEEN N AND M
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony ma wartość z przedziału od N do M, lub FALSE (FAŁSZ) w przeciwnym razie.
Id BETWEEN 10 AND 20
NOT BETWEEN N AND M
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony nie ma wartości z przedziału od N do M, lub FALSE (FAŁSZ) w przeciwnym razie.
Id NOT BETWEEN 10 AND 20
IN
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony jest równy jednej z wartości wymienionych w nawiasie okrągłym za operatorem, lub FALSE (FAŁSZ) w przeciwnym razie.
Id IN(1, 3, 5), Nazwisko IN(’Kowalski’, ’Nowak’)
NOT IN
Zwraca wartość TRUE (PRAWDA), jeśli argument znajdujący się z lewej strony nie jest równy jednej z wartości wymienionych w nawiasie okrągłym za operatorem, lub FALSE (FAŁSZ) w przeciwnym razie.
Id NOT IN(1, 3, 5), Nazwisko NOT IN(’Kowalski’, ’Nowak’)
Tabela 8.4. Operatory logiczne w MySQL Operator Opis
Przykład
AND
Iloczyn logiczny. Zwraca wartość TRUE wtedy i tylko wtedy, gdy oba argumenty mają wartość TRUE. W każdym innym przypadku zwraca wartość FALSE.
Imie=’Jan’ AND Nazwisko=’Kowalski’
&&
Iloczyn logiczny. Znaczenie takie samo jak AND.
Imie=’Jan’ && Nazwisko=’Kowalski’
OR
Suma logiczna. Zwraca wartość TRUE, gdy przynajmniej jeden z argumentów ma wartość TRUE. W każdym innym przypadku zwraca wartość FALSE.
Imie=’Jan’ OR Imie=’Andrzej’
||
Suma logiczna. Znaczenie takie samo jak OR.
Imie=’Jan’ || Imie=’Andrzej’
Rozdział 8. ♦ Współpraca z bazami danych
389
Tabela 8.4. Operatory logiczne w MySQL — ciąg dalszy Operator Opis
Przykład
XOR
Logiczna różnica symetryczna (logiczna alternatywa wykluczająca). Zwraca wartość TRUE, gdy oba argumenty mają różne wartości logiczne, oraz wartość FALSE, gdy mają takie same.
Kolumna1 XOR Kolumna2, Pole XOR 64
NOT
Negacja logiczna. Zmienia wartość argumentu na przeciwną. Jeśli wartością argumentu było TRUE, wynikiem będzie FALSE, jeśli natomiast było to FALSE, wynikiem będzie TRUE.
NOT Aktywny
!
Negacja logiczna. Znaczenie takie samo jak NOT.
! Aktywny
Oprócz przedstawionych w powyższych tabelach operatorów relacyjnych i logicznych stosunkowo często wykorzystywane są także dwie funkcje operujące na ciągach znaków — są to LIKE i NOT LIKE. Wywołanie funkcji LIKE ma postać: wyrażenie LIKE wzorzec
Zwraca ona wartość TRUE, jeśli wyrażenie pasuje do wzorca, lub wartość FALSE, jeśli nie pasuje. Jako wyrażenie zazwyczaj jest stosowana nazwa kolumny. Argument wzorzec może zawierać dwa znaki specjalne. Pierwszy z nich to %, który zastępuje dowolną liczbę znaków, drugi znak specjalny zastępujący dokładnie jeden znak to _ (podkreślenie). Oznacza to, że do przykładowego wzorca Jan% będą pasowały ciągi Jan, Janusz, Janek, Janowski itp., a do wzorca Warszaw_ — ciągi Warszawa, Warszawy, Warszawo itp. Funkcja NOT LIKE ma postać: wyrażenie NOT LIKE wzorzec
i działa odwrotnie do LIKE, czyli zwraca wartość TRUE, jeśli wyrażenie nie jest zgodne ze wzorcem, lub wartość FALSE w przeciwnym razie. Spróbujmy teraz wykonać kilka praktycznych przykładów wykorzystujących niektóre z opisanych operatorów i funkcji. Operować będziemy na wcześniej stworzonej i wypełnionej danymi tabeli osoba. Pobierzmy zatem wszystkie wiersze tabeli osoba, które w polu Nazwisko mają zapisaną wartość Kowalski, czyli odczytajmy dane wszystkich osób o nazwisku Kowalski. Takie zadanie wykonane zostanie przez instrukcję: SELECT * FROM osoba WHERE Nazwisko='Kowalski';
Efekt działania został zaprezentowany na rysunku 8.23. W klauzuli WHERE został wykorzystany operator =. Rysunek 8.23. Efekt działania instrukcji pobierającej dane osób o nazwisku Kowalski
390
PHP5. Praktyczny kurs
Wykorzystajmy teraz operator większości do pobrania listy osób urodzonych po roku 1985. Zapytanie SQL będzie miało postać: SELECT * FROM osoba WHERE Rok_urodzenia > 1985;
Efekt jego działania został przedstawiony na rysunku 8.24. Analogiczny efekt moglibyśmy również osiągnąć, wykorzystując operator >= w postaci: SELECT * FROM osoba WHERE Rok_urodzenia >= 1986;
Rysunek 8.24. Efekt działania zapytania pobierającego listę osób urodzonych po roku 1995
Aby w wyniku zapytania uzyskać wartości pól z danego zakresu, można użyć dwóch operatorów porównywania oraz operatora logicznego lub też skorzystać z operatora BETWEEN. Załóżmy, że chcemy pobrać listę osób o identyfikatorach z przedziału 3 – 6. Można w takim wypadku wykonać zapytanie w postaci: SELECT * FROM osoba WHERE Id >= 3 AND Id <= 6;
lub SELECT * FROM osoba WHERE Id BETWEEN 3 AND 6;
Efekt działania takiego zapytania został przedstawiony na rysunku 8.25. Rysunek 8.25. Efekt działania zapytania pobierającego dane z określonego przedziału
Jeśli chcielibyśmy, aby w wynikach zostały uwzględnione wartości z pewnego zbioru, a nie przedziału, moglibyśmy użyć zarówno serii instrukcji warunkowych połączonych operatorami logicznymi, jak i operatora IN. Działanie będzie takie samo, jednak ta druga możliwość pozwala na prostszy i dużo czytelniejszy zapis instrukcji. Jeśli zatem chcemy otrzymać dane osób o identyfikatorach 3, 5 i 7, możemy zastosować instrukcję: SELECT * FROM osoba WHERE Id=3 OR Id=5 OR Id=7;
lub też: SELECT * FROM osoba WHERE Id IN(3, 5, 7);
Efekt działania (w obu przypadkach będzie identyczny) został przedstawiony na rysunku 8.26.
Rozdział 8. ♦ Współpraca z bazami danych
391
Rysunek 8.26. Pobranie danych, dla których wartość pola Id należy do określonego zbioru
Odwrotnością IN jest NOT IN, które pozwala na pobranie danych nienależących do wymienionego zbioru. Również i w tym wypadku możliwe jest użycie ekwiwalentu w postaci serii instrukcji warunkowych połączonych operatorami logicznymi. Jeśli zatem chcemy wyświetlić dane osób o identyfikatorach różnych od 1, 3, 5, 7 i 9, możemy skorzystać z instrukcji: SELECT * FROM osoba WHERE Id <> 1 AND Id <> 3 AND Id <> 5 AND Id <> 7 AND Id <> 9;
bądź: SELECT * FROM osoba WHERE Id NOT IN(1, 3, 5, 7, 9);
Efekt ich działania został zaprezentowany na rysunku 8.27. Rysunek 8.27. Efekt działania instrukcji wykorzystującej operator NOT IN
Funkcja LIKE (można ją traktować jako operator działający na ciągach znaków) pozwoli na pobranie z tabeli wierszy, których wybrane pola pasują do zdefiniowanego wzorca. Gdybyśmy chcieli na przykład poznać dane wszystkich osób, których imiona zaczynają się od ciągu Ka, powinniśmy zastosować instrukcję: SELECT * FROM osoba WHERE Imie LIKE 'Ka%';
Efekt działania tego polecenia jest widoczny na rysunku 8.28. Rysunek 8.28. Wyświetlenie wierszy zgodnych z przekazanym wzorcem
Oczywiście warunek w klauzuli WHERE nie musi ograniczać się do danych pobieranych z jednej kolumny, można stosować warunki złożone połączone operatorami logicznymi. Jeśli na przykład interesowałyby nas dane osób, których imiona zaczynają się na literę A, urodzonych po roku 1970 w Kielcach lub w Szczecinie, należałoby zastosować konstrukcję: SELECT * FROM osoba WHERE Imie LIKE 'A%' AND Rok_urodzenia > 1970 AND Miejsce_urodzenia IN ('Kielce', 'Szczecin');
392
PHP5. Praktyczny kurs
Efekt działania tego zapytania został zaprezentowany na rysunku 8.29.
Rysunek 8.29. Efekt działania zapytania złożonego
Modyfikacja danych Dane zapisane w tabelach mogą być zmieniane i modyfikowane. Służy do tego instrukcja UPDATE, która ma ogólną postać: UPDATE tabela SET kolumna1=wartość1, kolumna2=wartość2, ..., kolumnaN=wartośćN [WHERE warunek]
Oznacza ona: w tabeli tabela, w wierszach spełniających warunek warunek, zmień pole kolumna1 na wartość1, kolumna2 na wartość2 itd. Klauzula WHERE jest opcjonalna i może zostać pominięta — w takiej sytuacji zmianie ulegną wszystkie wiersze znajdujące się w tabeli. Zastosowanie przykładowej instrukcji: UPDATE osoba SET Imie='Jan';
spowoduje zamianę imion wszystkich osób, których dane zapisane są w tabeli, na ciąg Jan (rysunek 8.30). Zawsze należy więc pamiętać o zastosowaniu odpowiedniego warunku, w przeciwnym razie można osiągnąć efekty niezgodne z założeniami. Oczywiście może się też zdarzyć, że chcemy zmienić wszystkie wartości w danej kolumnie, wtedy taka instrukcja będzie jak najbardziej poprawna. Rysunek 8.30. Zastosowanie skróconej wersji instrukcji UPDATE spowodowało zamianę wszystkich wartości w kolumnie Imie
Najczęściej jednak aktualizuje się jeden lub kilka wierszy, stosowanie klauzuli WHERE jest więc konieczne. Jeśli okaże się na przykład, że w tablicy zapisaliśmy błędny rok urodzenia Andrzeja Malinowskiego, któremu w tabeli został przypisany identyfikator 5, i trzeba zamienić wartość 1989 na 1988, należałoby zastosować instrukcję: UPDATE osoba SET Rok_urodzenia=1988 WHERE id=5;
Został tu zastosowany warunek id=5, ponieważ pole id jest kluczem podstawowym jednoznacznie identyfikującym każdy rekord.
Rozdział 8. ♦ Współpraca z bazami danych
393
Można też jednocześnie zmodyfikować kilka pól w danym wierszu. Gdybyśmy chcieli zmienić imię, rok i miejsce urodzenia osoby o identyfikatorze 8, moglibyśmy zastosować instrukcję: UPDATE osoba SET Nazwisko='Andrzejewski', Rok_urodzenia=1990, Miejsce_urodzenia='Kielce' WHERE Id=8;
Tym samym Kamil Andrzejczak urodzony w 1971 roku w Radomiu stanie się Kamilem Andrzejewskim, urodzonym w 1990 roku w Kielcach. W warunku klauzuli WHERE można używać operatorów i funkcji opisanych w sekcji „Selektywne pobieranie danych”, można zatem jednocześnie wskazać więcej niż jeden rekord do modyfikacji. Jeśli zatem wykryjemy, że osoby o identyfikatorach 4 i 5 mają błędnie przypisane miejsce urodzenia, którym nie są Kielce, ale Radom, do modyfikacji wykorzystamy instrukcję: UPDATE osoba SET Miejsce_urodzenia='Radom' WHERE id=4 OR id=5;
lub UPDATE osoba SET Miejsce_urodzenia='Radom' WHERE ID IN(4,5);
Podobnie jeśli odkryjemy, że w wielu wierszach tabeli zostało nieprawidłowo zapisane np. nazwisko Kowalski — i zamiast niego widnieje ciąg Kowalksi — możemy takie niedopatrzenie szybko naprawić, wykonując instrukcję: UPDATE osoba SET Nazwisko='Kowalski' WHERE Nazwisko='Kowalksi';
Usuwanie danych Wiadomo już, jak dodawać dane do tabeli, jak je pobierać i modyfikować. Do omówienia została jeszcze równie ważna kwestia usuwania wierszy z tabel. Służy do tego instrukcja DELETE o schematycznej postaci: DELETE FROM tabela WHERE warunek
Oznacza ona: usuń z tabeli tabela wszystkie wiersze spełniające warunek warunek. Jeśli warunek zostanie pominięty, zostaną usunięte wszystkie wiersze z tabeli. Jeśli zatem zastosujemy instrukcję: DELETE FROM osoba;
po jej wykonaniu tabela osoba nie będzie zawierała żadnych danych. Taką konstrukcję należy stosować z uwagą, gdyż serwer nie wygeneruje żadnego ostrzeżenia czy dodatkowego pytania. Wpisanie powyższej konstrukcji i zatwierdzenie jej klawiszem Enter spowoduje natychmiastowe skasowanie danych! Warunek w klauzuli WHERE ma taką samą postać jak opisywany podczas omawiania instrukcji SELECT. Jeśli zatem chcemy usunąć z tabeli osoba dane osoby o identyfikatorze 5, zastosujemy konstrukcję: DELETE FROM osoba WHERE Id=5;
394
PHP5. Praktyczny kurs
Jeśli chcemy usunąć kilka rekordów o różnych identyfikatorach, zastosujemy zapewne operator IN. Jeśli na przykład skasowane mają zostać dane osób o id równym 3, 5 i 7, wykorzystamy zapewne instrukcję: DELETE FROM osoba WHERE ID IN (3, 5, 7);
W celu usunięcia danych osób o identyfikatorach należących do określonego przedziału, np. 4 – 8, skorzystamy z zapytania wykorzystującego operator BETWEEN w postaci: DELETE FROM osoba WHERE ID BETWEEN 4 AND 8;
lub też z zapytania wykorzystującego operatory relacyjne <= i >= oraz operator logiczny AND: DELETE FROM osoba WHERE ID >= 4 AND ID <= 8;
Nic nie stoi również na przeszkodzie, aby usunąć z tabeli dane wszystkich osób o nazwisku Kowalski: DELETE FROM osoba WHERE Nazwisko='Kowalski';
lub też wszystkich Kowalskich i Nowaków: DELETE FROM osoba WHERE Nazwisko='Kowalski' OR Nazwisko='Nowak';
Lekcja 24. PHP i bazy danych PHP doskonale współpracuje z MySQL i SQLite, podobnie jak z innymi systemami baz danych. Obsługa tych baz może być realizowana na dwa sposoby — pierwszy jest tradycyjny, proceduralny, polegający na wywoływaniu funkcji obsługujących połączenia, a drugi obiektowy. W tej lekcji zajmiemy się podejściem proceduralnym, a w kolejnej — obiektowym.
PHP i MySQL Nawiązywanie połączenia Do nawiązania połączenia z bazą danych służy funkcja mysql_connect. Ma ona schematyczną postać: mysql_connect(["host"[, "użytkownik"[, "hasło"[, nowe_połączenie[, flagi]]]]])
Parametr host to ciąg znaków określający nazwę lub adres IP serwera, na którym znajduje się baza danych (w naszym przypadku będzie to localhost), użytkownik to ciąg znaków określający nazwę użytkownika, natomiast hasło to ciąg znaków określający hasło użytkownika. Funkcja zwraca identyfikator nawiązanego połączenia, jeśli udało się je nawiązać, lub wartość false w przeciwnym razie. Parametr host może zawierać określenie portu, na którym oczekuje na połączenia serwer MySQL (port domyślny to 3306). W takim wypadku będzie miał on postać: nazwa_hosta:numer_portu
Rozdział 8. ♦ Współpraca z bazami danych
395
np.: localhost:3306
Jeśli w jednym skrypcie funkcja mysql_connect zostanie wywołana więcej niż raz z tymi samymi parametrami, a połączenie pomiędzy wywołaniami nie zostanie zerwane, domyślnie nie nastąpi nawiązanie nowego połączenia. W takim przypadku jest po prostu zwracany identyfikator połączenia już istniejącego. To zachowanie może być zmodyfikowane przez ustawienie parametru nowe_połączenie na true (standardowo parametr ten ma wartość false). Wtedy każde wywołanie mysql_connect będzie tworzyło nowe połączenie. Ostatni parametr — flagi — umożliwia modyfikację domyślnych zachowań protokołu komunikacyjnego, nie będziemy go jednak wykorzystywać w przykładach. Jeśli parametry host, użytkownik i hasło zostaną pominięte, przyjęte zostaną następujące wartości domyślne: dla parametru host — ciąg znaków localhost:3306, dla parametru użytkownik — jeśli PHP pracuje w trybie safe_mode,
jest to ciąg znaków określający użytkownika będącego właścicielem procesu serwera, w przeciwnym razie jest to użytkownik domyślny, dla parametru hasło — pusty ciąg znaków.
To zachowanie może być zmienione przez modyfikację zmiennych w pliku php.ini: mysql.default_port — określa domyślny port, mysql.default_host — określa domyślną nazwę lub adres serwera MySQL, mysql.default_user — określa domyślną nazwę użytkownika, mysql.default_password — określa domyślne hasło.
Jeśli zatem na serwerze lokalnym localhost jest uruchomiony serwer MySQL i istnieje na nim konto użytkownika o nazwie php, któremu zostało przypisane hasło test (jest to konfiguracja utworzona w poprzednich lekcjach), to połączenie może zostać wykonane za pomocą instrukcji: mysql_connect("localhost", "php", "test");
Kończenie połączenia Połączenie otwarte za pomocą funkcji mysql_connect zostanie zamknięte automatycznie, gdy skrypt zakończy działanie lub gdy zostanie wywołana funkcja mysql_close. Dobrym obyczajem programistycznym jest jawne zamykanie połączenia za pomocą tej funkcji. Schematyczne wywołanie mysql_close ma postać: mysql_close([identyfikator])
gdzie identyfikator to identyfikator połączenia, które ma zostać zamknięte, zwrócony wcześniej przez funkcję mysql_connect. Jeżeli parametr identyfikator nie zostanie podany, zamknięte zostanie ostatnio otwarte połączenie. Funkcja mysql_close zwraca wartość true, jeżeli operacja przez nią wykonywana zakończyła się sukcesem, lub false w przeciwnym razie.
396
PHP5. Praktyczny kurs
Wybór bazy Po nawiązaniu połączenia z serwerem należy wybrać bazę. Służy do tego funkcja mysql_select_db, która jest odpowiednikiem polecenia use database wykorzystywanego we wcześniejszych rozdziałach przy pracy z klientem mysql. Wywołanie mysql_ select_db ma schematyczną postać: mysql_select_db("nazwa_bazy"[, identyfikator])
gdzie nazwa_bazy to nazwa bazy, do której będą wysyłane zapytania, a identyfikator to identyfikator połączenia zwrócony przez funkcję mysql_connect. Jeśli identyfikator połączenia nie zostanie podany, wykorzystane będzie ostatnio otwarte połączenie. Spróbujmy zatem wykonać prosty skrypt, który przetestuje, czy komunikacja między PHP a MySQL działa prawidłowo. Został on zaprezentowany na listingu 8.2. Listing 8.2. Skrypt testujący połączenie z serwerem baz danych '); } else{ echo 'Połączenie z bazą danych zostało nawiązane...
'; } if(!mysql_select_db('testphp', $db_lnk)){ echo 'Wystąpił błąd podczas wyboru bazy danych: test
'; } else{ echo 'Została wybrana baza danych: test
'; } if(!mysql_close($db_lnk)){ echo 'Wystąpił błąd podczas zamykania połączenia z serwerem MySQL...
'; } else{ echo 'Połączenie z serwerem MySQL zostało zamknięte...
'; } ?>
We wszystkich dalszych przykładach zostało przyjęte założenie, że na serwerze MySQL (uruchomionym na komputerze lokalnym localhost) zostało założone konto o nazwie php i haśle test, które pozwala na wykonywanie wszystkich operacji wymaganych przez dany skrypt, oraz że operacje te są wykonywane w bazie o nazwie testphp.
Funkcja mysql_connect wykonuje tu połączenie z serwerem MySQL pracującym na komputerze lokalnym localhost, logując użytkownika php korzystającego z hasła test. Wynik działania funkcji jest przypisywany zmiennej $db_lnk. W zależności od tego, czy został zwrócony identyfikator nawiązanego połączenia czy też wartość false oznaczająca niepowodzenie połączenia, do przeglądarki jest wysyłany stosowny komunikat. Jeśli połączenie z bazą danych zakończy się sukcesem, wykonywana jest funkcja mysql_select_db wybierająca bazę danych o nazwie testphp. Również w tym przy-
Rozdział 8. ♦ Współpraca z bazami danych
397
padku jest sprawdzane, czy operacja została wykonana poprawnie, a do przeglądarki wysyłana jest odpowiednia informacja. Na końcu skryptu połączenie z bazą jest zamykane przez wywołanie funkcji mysql_close. Jeśli uruchomimy skrypt, a wszystkie operacje zostaną wykonane bez błędów, na ekranie zobaczymy widok jak na rysunku 8.31. Ponieważ (jak wiadomo z wcześniejszych opisów) niepodanie identyfikatora w wywołaniach mysql_select_db i mysql_close powoduje wykonanie operacji na ostatnio otwartym połączeniu, obie funkcje można również wywołać bez podawania argumentu $db_lnk. Rysunek 8.31. Nawiązanie połączenia z bazą danych
Wykonywanie zapytań Zapytania są wysyłane do bazy za pomocą funkcji mysql_query, której w postaci argumentów należy podać treść zapytania oraz (opcjonalnie) identyfikator połączenia. Schematycznie takie wywołanie ma postać: mysql_query("zapytanie"[, identyfikator])
Jeżeli nie będzie podany identyfikator, zostanie wykorzystane ostatnio otwarte połączenie. Ciąg tworzący zapytanie nie powinien być zakończony znakiem średnika. Wartość zwracana przez funkcję jest zależna od typu zapytania. Jeśli było to zapytanie pobierające dane (np. typu SELECT), wartością zwracaną jest identyfikator zasobów (pozwalający na dalsze przetwarzanie danych), o ile wykonanie zapytania zakończyło się sukcesem, lub false, jeżeli ta operacja się nie powiodła. W przypadku pozostałych typów zapytań (modyfikujących dane) zwracaną wartością jest true, jeśli zapytanie było poprawne, lub false w przeciwnym razie.
Zapytania pobierające dane W przypadku zapytań typu SELECT funkcja mysql_query zwraca identyfikator zasobów, który może zostać następnie użyty do odczytu danych zwróconych przez zapytanie. Istnieje kilka funkcji odczytujących takie dane, najpopularniejsze to mysql_fetch_row i mysql_fetch_array. Pomocna może być także funkcja mysql_num_rows, która pozwala na określenie, ile wierszy znajduje się w wynikach zapytania. Każde wywołanie funkcji mysql_fetch_row zwraca kolejny wiersz z tabeli będącej wynikiem zapytania. Dane zwracane są w tablicy, w której kolejne komórki zawierają dane z kolejnych kolumn tabeli wynikowej. Jeśli zostaną odczytane wszystkie wiersze, funkcja zwraca wartość false. Oznacza to, że wszystkie wyniki zapytania mogą zostać odczytane w pętli while o schematycznej postaci:
398
PHP5. Praktyczny kurs while($arr = mysql_fetch_row($result)){ //instrukcje przetwarzające wyniki }
gdzie $arr to tablica, do której będą zapisywane dane z kolejnych wierszy, a $result to zmienna zawierająca identyfikator zasobów zwrócony przez funkcję mysql_query. Drugi wariant to użycie funkcji mysql_num_rows do pobrania liczby wierszy z tabeli wynikowej oraz pętli typu for. Taka konstrukcja miałaby schematyczną postać: $count = mysql_num_rows($result); for($i = 0; $i < $count; $i++){ //instrukcje przetwarzające wyniki }
Funkcja mysql_fetch_array działa podobnie, z tą różnicą, że zwraca tabelę asocjacyjną, w której kluczami są nazwy kolumn tabeli zwróconej przez zapytanie. Zobaczmy najpierw, jak w praktyce odczytać dane z wybranej tabeli, wykorzystując do tego funkcję mysql_fetch_row. Niech będzie to wykorzystywana w lekcji 23. tabela osoba. Odpowiedni kod został zaprezentowany na listingu 8.3. Listing 8.3. Odczytanie zawartości tabeli osoba
Odczyt danych z bazy '; echo ''; exit; } if(!mysql_select_db('testphp')){ mysql_close(); echo 'Błąd podczas wyboru bazy danych: testphp
'; echo ''; exit; } $query = 'SELECT * FROM osoba'; if(!$result = mysql_query($query, $db_lnk)){ mysql_close(); echo 'Wystąpił błąd: nieprawidłowe zapytanie...
'; echo ''; exit; } ?>
Rozdział 8. ♦ Współpraca z bazami danych
399
Id | Imię | Nazwisko | Rok urodzenia | Miejsce urodzenia |
"; echo "$row[0] | "; echo "$row[1] | "; echo "$row[2] | "; echo "$row[3] | "; echo "$row[4] | "; echo ""; } ?>
'; } ?>
Połączenie z bazą jest nawiązywane w sposób omówiony na wcześniejszych stronach. Następnie zmiennej $query jest przypisywany ciąg znaków tworzący treść zapytania SQL: SELECT * FROM osoba
które pobiera wszystkie wiersze z tabeli osoba (zwróćmy uwagę, że nie jest ono zakończone znakiem średnika). Zapytanie jest wysyłane do serwera przez wywołanie funkcji mysql_query, a rezultat jej działania jest przypisywany zmiennej $result. Jeżeli wynikiem jest false, oznacza to błąd w zapytaniu i skrypt kończy działanie. W przeciwnym razie uzyskane wyniki są pobierane w pętli while. Jest w niej wywoływana funkcja mysql_fetch_row, która pobiera kolejne wiersze tabeli wynikowej i zwraca je w postaci tablicy. Pod indeksem 0 tej tablicy znajduje się wartość z kolumny Id, pod 1 — z kolumny Imie, pod 2 — z kolumny Nazwisko itd. Innymi słowy, kolejność kolumn w tablicy $row będzie taka sama jak zwrócona przez wysłane zapytanie. Odczytane wyniki są umieszczane w tabeli HTML w celu ich sformatowania. Po uruchomieniu skryptu powinniśmy zobaczyć w przeglądarce widok taki, jak zaprezentowany na rysunku 8.32. Jeżeli do pobierania danych wykorzystamy funkcję mysql_fetch_array, zamiast indeksów kolumn będziemy mogli wykorzystać ich nazwy. To znaczy (zakładając, że zmienna $row zawiera wynik działania funkcji mysql_fetch_array), będzie można stosować konstrukcję o schematycznej postaci: $row['nazwa_kolumny']
400
PHP5. Praktyczny kurs
Rysunek 8.32. Zawartość tabeli Osoba wyświetlona w przeglądarce
Pętla while pobierająca dane będące wynikiem zapytania mogłaby mieć zatem również postać przedstawioną na listingu 8.4. Pozostała część skryptu pozostanie bez zmian. Listing 8.4. Wykorzystanie funkcji mysql_fetch_array do pobrania danych "; echo "
{$row['Id']} | "; echo "
{$row['Imie']} | "; echo "
{$row['Nazwisko']} | "; echo "
{$row['Rok_urodzenia']} | "; echo "
{$row['Miejsce_urodzenia']} | "; echo "