Dyrektywa konfiguracyjna ForceType serwera Apache ustala typ MIME pliku. W ten sposób plik o nazwie katalog, pozbawiony rozszerzenia .php, może być wykonywany jako skrypt PHP. Umożliwia to implementację tzw. przyjaznych adresów URL.
Dyrektywy Files oraz ForceType
Przyjazne adresy URL ForceType wykorzystują dwie dyrektywy konfiguracyjne serwera Apache: Files oraz ForceType. Dyrektywa Files zawiera jeden argument: nazwę pliku, którego dotyczy.
Wpis:
...
będzie dotyczył pliku lorem.html.
Druga z dyrektyw, ForceType, ustala typ
MIME pliku. Jej parametrem jest jeden z zarejestrowanych
typów, np.:
text/plain
text/html
text/css
image/jpg
image/png
application/xhtml+xml
application/x-httpd-php
Dyrektywy konfiguracyjne:
ForceType application/x-httpd-php
spowodują, że plik o nazwie katalog (wymieniony
po dyrektywie Files) będzie traktowany
jako skrypt PHP (typ {stala}application/x-httpd-php{/stala}),
pomimo tego że nie ma rozszerzenia .php.
W ten sposób wymuszamy wykonanie
skryptu PHP o dowolnej nazwie. Skrypt taki
jest dostępny pod adresem katalog:
http://localhost/katalog
Po nazwie skryptu możemy podać dowolne
dane, korzystając nie tylko ze znaku zapytania:
http://localhost/katalog?lorem+ipsum+dolor.html
ale także z ukośnika (ang. slash /):
http://localhost/katalog/lorem/ipsum/dolor.html
Otrzymany adres wygląda tak, jak gdyby
dotyczył pliku {stala}dolor.html{/stala}. W rzeczywistości będzie
on powodował wykonanie skryptu PHP
o nazwie katalog. Do skryptu trafią dane: lorem,
ipsum, {stala}dolor.html{/stala}, umożliwiające wybór
konkretnej podstrony.
Przykład pierwszy
Najpierw przygotujemy skrypt PHP o nazwie
katalog i treści:
...
...
Następnie przygotujemy plik konfiguracyjny
{stala}.htaccess{/stala} o treści:
ForceType application/x-httpd-php
Oba pliki umieścimy w tym samym folderze.
Odwiedzimy przeglądarką skrypt katalog,
a następnie dodamy na końcu adresu napis
{stala}/lorem/ipsum/dolor/sit/amet.html{/stala}:
http://localhost/katalog/lorem/ipsum/dolor/sit/amet.html
Pomimo tego, że adres wygląda na odwołanie
do pliku {stala}amet.html{/stala} (w folderze {stala}/katalog/lorem/ipsum/dolor/sit/{/stala}), w rzeczywistości jest to
wywołanie skryptu PHP o nazwie katalog.
Witryna bez przyjaznych adresów URL
Witryna bez przyjaznych URL-i stosuje zmienne
metody GET protokołu HTTP. Zmienna id wybiera rodzaj podstrony, zaś id2 wskazuje wybrany
rekord bazy danych.
Cała witryna WWW jest wyświetlana przez
jeden skrypt index.php. Skrypt ten składa się
z trzech zasadniczych części przedstawionych
w zarysie na listingu 1:
...
//walidacja zmiennych URL
if (isset($_GET[\'id\']) && ...) {
...
} elseif (($_GET[\'id\'] == 5) && (count($_GET) == 2) && isset($_GET[\'id2\']) && str_ievpi($_GET[\'id2\']) && ($rodzina = RodzinaPeer::retrieveByPK($_GET[\'id2\']))) {
$akcja = $_GET[\'id\'];
} elseif (...)
}
//wybór stanu aplikacji
//i przekazanie zmiennych do szablonu
switch ($akcja) {
...
case 5:
$c = new Criteria;
$c->Add(PtakPeer::RODZINA_ID, $rodzina->getRodzinaId());
$c->addAscendingOrderByColumn(PtakPeer::NAZWAPOLSKA);
$ptaki = PtakPeer::doSelect($c);
$ptaki = array_1dim_to_2dim($ptaki, 3);
$s->assign(\'ptaki\', $ptaki);
$s->assign(\'rodzina\', $rodzina);
break;
...
}
//przetworzenie szablonu
$s->display(\'index.tpl\');
- walidacji zmiennych URL,
- wyboru stanu aplikacji i przekazania danych do szablonu,
- przetworzenia szablonu.
W pierwszym etapie sprawdzamy poprawność
zmiennych id oraz (w niektórych przypadkach)
id2. Przedstawiony na listingu 1 przypadek
dotyczy adresu:
index.php?id=5&id2=X
Jeśli podane zmienne URL są poprawne,
to zostanie utworzony obiekt $rodzina (na
podstawie zmiennej id2) oraz zmienna $akcja
przyjmie wartość 5.
W następnym etapie na podstawie zmiennej
$akcja pobieramy dane z bazy i przekazujemy
do szablonu. Na przykład, w przypadku
gdy {stala}$akcja{/stala} jest równa 5, z bazy danych
pobieramy wszystkie ptaki wybranej rodziny.
Do szablonu przekazujemy listę ptaków
oraz rodzinę.
W ostatnim etapie przetwarzamy szablon.
Sprowadza się to do wywołania metody {stala}display(){/stala}
klasy Smarty.
Przyjazne URL ForceType w przykładzie \”Ptaki\”
Wymuszenie wykonania PHP
Rozbudowę przykładu pt. \”Ptaki\” rozpoczynamy
od zmiany nazwy pliku z index.php na index
i utworzenia pliku {stala}.htaccess{/stala}:
ForceType application/x-httpd-php
Teraz w aplikacji będą działały adresy URL
takie same jak poprzednio, ale z zamienioną
nazwą skryptu zgodnie z tabelą 2.
Przyjazne URL w aplikacji \”Ptaki\”
Aplikacja \”Ptaki\” stosuje przyjazne adresy
URL wymienione w tabeli 3.
Każdy z adresów
rozpoczyna się od nazwy skryptu: /index/.
Fragmentem występującym po nazwie skryptu jest:
error.html
index.html
ptaki.html
rodziny.html
rodzina/bocianowate.html
ptak/batalion.html
ptak/batalion.jpg
Otrzymany w zmiennej {stala}$_SERVER[\’REQUEST_URI\’]{/stala} adres URL należy przetworzyć i na
jego podstawie wybrać odpowiednią podstronę
serwisu.
Dostęp do adresu URL bieżącego zapytania HTTP
W celu uzyskania informacji lorem, ipsum podanych
w adresie URL po nazwie skryptu:
{stala}http://localhost/katalog/lorem/ipsum/{/stala}
należy wykorzystać zmienną {stala}$_SERVER[\’REQUEST_URI\’]{/stala}. Zawiera ona adres URL przetwarzanego
zapytania HTTP. Przydatna może się
również okazać zmienna {stala}$_SERVER[\’SCRIPT_NAME\’]{/stala} zawierająca nazwę wykonywanego
skryptu PHP.
Przykład drugi
Przygotujemy skrypt PHP o nazwie index oraz
o treści:
...
...
Umieścimy obok niego plik {stala}.htaccess{/stala} o następującej
treści:
ForceType application/x-httpd-php
Podamy w przeglądarce adres skryptu index,
dołączając na końcu – po znaku ukośnika – dane:
http://localhost/index/kot/lis/pies/
Skrypt index wydrukuje na ekranie:
/index/kot/lis/pies/
/index
Pierwszy z napisów to użyty adres URL, natomiast drugi – nazwa skryptu PHP.
Przetwarzanie adresu URL
Przetwarzanie adresu URL rozpoczynamy od
wyczyszczenia, czyli usunięcia wszelkich niedozwolonych
znaków. Korzystając z wyrażenia
regularnego {stala}/[^a-zA-Z0-9\\-_\\/.?=]/{/stala} usuwamy
wszystkie znaki, które nie są literą, cyfrą lub
jednym spośród sześciu znaków {stala}-_/.?=:{/stala}
$adr = preg _ replace(\'/[^a-zA-Z0-9\- _ \/.?=]/\',\'\',$ _ SERVER[\'REQUEST _ URI\']);
Następnie, w celu uzyskania wyłącznie danych
podanych po nazwie skryptu, korzystamy
z funkcji {stala}preg_replace(){/stala}. Wywołanie:
$adr = preg _ replace(\'/^\' . preg _ quote($ _ SERVER [\'SCRIPT _ NAME\'], \'/\') . \'/\', \'\', $adr);
spowoduje, że zmienna {stala}$adr{/stala} będzie zawierała
wyłącznie dane występujące po nazwie skryptu.
Poszczególne człony uzyskujemy wywołując
funkcję {stala}explode(){/stala}:
$elementy = explode(\'/\', $adr);
foreach ($elementy as $element) {
echo $element . \"\n\";
}
Przykład trzeci
Przygotujemy plik {stala}.htaccess{/stala} wymuszający wykonanie
skryptu PHP o nazwie index. W skrypcie
index wyznaczymy poszczególne człony lorem,
ipsum, dolor, sit, {stala}amet.html{/stala} występujące
w adresie URL:
http://localhost/index/lorem/ipsum/dolor/sit/amet.html
Wykorzystamy do tego skrypt:
$adr = preg _ replace(\'/[^a-zA-Z0-9\- _ \/.?=]/\',\'\',$ _ SERVER[\'REQUEST _ URI\']);
$adr = preg _ replace(\'/^\' . preg _ quote($ _ SERVER [\'SCRIPT _ NAME\'], \'/\') . \'/\', \'\',$adr );
$elementy = explode(\'/\', $adr);
foreach ($elementy as $element) {
echo $element . \"\n\";
}
Dodatkowo w skrypcie umieścimy dwa hiperłącza:
W skrypcie umieść hiperłącza:
dane
home
ułatwią one testowanie przykładu.
Zwróćmy uwagę, że adresy względne:
index/lorem/ipsum/dolor.html
index/kot/lis.html
są niepoprawne na stronie stosującej dyrektywę
ForceType. Adres względny:
index/black/white.html
użyty na stronie WWW o adresie:
index/kot/pies.html
zostanie rozwinięty do postaci:
index/kot/index/black/white.html
Stosując dyrektywę ForceType należy korzystać
z bezwzględnych adresów URL rozpoczynających
się znakiem /.
Przykładowa witryna: Ptaki
Przykład, którym się posłużę do praktycznego
zademonstrowania przyjaznych adresów URL
ForceType to \”Ptaki wodne i błotne w Polsce\”. Witryna ta prezentuje informacje na temat ptaków
występujących w Polsce. Składa się ona z następujących
podstron:
- strona główna,
- zestawienie wszystkich ptaków zawartych w serwisie,
- zestawienie wszystkich rodzin ptaków,
- zestawienie ptaków wybranej rodziny,
- szczegółowe dane wybranego ptaka,
- strona obsługi błędu 404.
Przygotowanie danych
Wykonanie witryny rozpoczynamy od opracowania
informacji i przygotowania bazy danych.
Wynikiem pracy mają być:
- baza danych wypełniona rekordami,
- klasy dostępu do bazy danych.
Dane w formatach .txt oraz .jpg
Na początku przygotowujemy pliki tekstowe
z informacjami na temat poszczególnych ptaków.
Każdy ptak jest opisany w jednym pliku.
Wszystkie pliki tekstowe mają identyczną
strukturę. Niektóre pliki zawierają ilustracje
ptaków. Plik z ilustracją ma identyczną nazwę,
jak plik tekstowy, różni się jedynie rozszerzeniem.
Na przykład Kaczka krzyżówka jest opisana
w pliku {stala}kaczka-krzyzowka.txt{/stala}:
Nazwa polska|Kaczka krzyżówka
Nazwa łacińska|Anas Platyhnchos
Rodzina|Kaczkowate
Długość ciała|50-60cm
Masa ciała|1-2kg
...
Wszystkie pliki tekstowe mają identyczną
strukturę.
Model bazy danych i aktywne rekordy
Następnie w programie DBDesigner przygotowujemy
model bazy danych oraz generujemy
aktywne rekordy, które stanowią interfejs API
programowego dostępu do bazy danych.
Baza danych zawiera dwie tabele:
ptak oraz rodzina.
Po wykonaniu modelu generujemy klasy
PHP. Po przekształceniu pliku DBDesignera do
formatu Propela uruchamiamy generator klas.
Dla bazy danych Propel wygeneruje
dwie klasy: Ptak oraz Rodzina. Klasa Ptak będzie
miała metody:
getPtakId()
getRodzina()
setRodzina()
getPtakNazwapolska()
setPtakNazwapolska()
getPtakNazwalacinska()
setPtakNazwalacinska()
...
Zaś klasa Rodzina będzie miała metody:
getRodzinaId()
getRodzina()
setRodzina()
...
Otrzymane klasy upraszczają znacznie proces
przygotowania witryny. Na przykład wstawienie
do bazy danych dwóch rekordów: rodziny
kaczkowate i Kaczki krzyżówki sprowadzi
się do kilku linijek kodu:
$rodzina = new Rodzina();
$rodzina->setRodzina(\'kaczkowate\');
$ptak = new Ptak();
$ptak->setNazwapolska(\'Kaczka krzyżówka\');
$ptak->setRodzina($rodzina);
...
$ptak->save();
Tworzenie bazy danych
Bazę danych tworzymy skryptem SQL. Skrypt taki
składa się z dwóch etapów: utworzenia bazy danych
i konta dostępu oraz utworzenia tabel.
Za utworzenie pustej bazy danych odpowiada
skrypt {stala}base.sql{/stala}, zaś tabele tworzy wygenerowany
przez Propela skrypt {stala}schema.sql{/stala}. Uruchomienie
obu skryptów automatyzuje plik wsadowy
{stala}3-create-database.bat{/stala} o zawartości:
copy base.sql all.sql
type build\sql\schema.sql >> all.sql
c:\mysql\bin\mysql -uroot -pAX1BY2CZ3 < all.sql
W podanym skrypcie .bat napis AX1BY2CZ3 jest hasłem konta root.
Wypełnianie bazy
Kolejnym etapem przygotowania danych jest
wypełnienie bazy. Wyszukujemy wszystkie pliki
tekstowe z opisem ptaków, po czym w pętli
przetwarzamy każdy z nich:
$plks = glob(\'dane/*.txt\');
foreach ($plks as $plk) {
$p = file($plk);
$parsed = parse _ file($p);
...
$ptak = new Ptak;
$ptak->setNazwapolska($parsed[\'nazwapolska\']);
$ptak->setNazwalacinska($parsed[\'nazwalacinska\']);
...
$ptak->save();
}
Za analizę pliku tekstowego odpowiada
funkcja {stala}parse_file(){/stala}. Wynikiem jej działania jest
tablica asocjacyjna. Poszczególne elementy tablicy
odpowiadają poszczególnym polom tabeli Ptak w bazie danych.
Na przykład w tabeli Ptak występuje pole
nazwapolska. Do pola tego uzyskujemy dostęp
metodami {stala}setNazwapolska(){/stala} oraz {stala}getNazwapolska(){/stala}.
W pliku tekstowym występuje linijka:
Nazwa polska|Kaczka krzyżówka
Po wywołaniu funkcji parse_file():
$parsed = parse_file($p);
tablica {stala}$parsed{/stala} zawiera element o indeksie \'nazwapolska\' i wartości \'Kaczka krzyżówka\':
$parsed[\'nazwapolska\'] == \'Kaczka krzyżówka\'
Zatem nazwę ptaka w obiekcie $ptak ustalimy wywołaniem:
$ptak->setNazwapolska($parsed[\'nazwapolska\']);
Zrzut bazy
Po wypełnieniu bazy danych skryptem PHP
uruchamiamy aplikację phpMyAdmin i wykonujemy
zrzut zawartości bazy danych. W operacji
eksport zaznaczamy wszystkie tabele, odznaczamy
całkowicie strukturę oraz rozszerzone
dodania. Wygenerowany eksport zapisujemy
do pliku ptaki.sql.
Łącząc trzy pliki: {stala}base.sql{/stala}, {stala}schema.sql{/stala} oraz
{stala}ptaki.sql{/stala} otrzymujemy skrypt SQL, który utworzy
bazę danych wypełnioną rekordami. Plik
wsadowy zrzut-db.bat łączy podane trzy pliki
SQL w jeden plik {stala}all.sql{/stala} i wykonuje go.
Walidacja zmiennych URL i wybór podstrony serwisu
Aplikacja stosująca przyjazne URL ForceType
będzie się różniła od pierwotnej aplikacji (tj.
bez przyjaznych URL-i) wyłącznie pierwszym
etapem: walidacją zmiennych URL. Tym razem
walidacji należy poddać zmienną {stala}REQUEST_URI{/stala}.
Walidację rozpoczynamy od wyczyszczenia
zmiennej {stala}$_SERVER[\'REQUEST_URI\']{/stala} i usunięcia
nazwy skryptu:
$adr = preg _ replace(\'/[^a-zA-Z0-9\- _ \/.?=]/\',\'\', $ _ SERVER[\'REQUEST _ URI\']);
$adr = preg _ replace(\'/^\' . preg _ quote($ _ SERVER[\'SCRIPT _ NAME\'],\'/\') . \'/\', \'\', $adr );
Następnie sprawdzamy, czy została wybrana
strona główna, strona wszystkich ptaków
lub wszystkich rodzin:
if ($adr == \'/index.html\') {
$akcja = 2;
} else if ($adr == \'/ptaki.html\') {
$akcja = 3;
} else if ($adr == \'/rodziny.html\') {
$akcja = 4;
...
Jak widać, we wszystkich trzech przypadkach
walidacja sprowadzi się do testu, czy podany
adres {stala}$adr{/stala} jest jednym z napisów: /index.
html, {stala}/ptaki.html{/stala} lub {stala}/rodziny.html{/stala}. Jeśli
tak, to ustalamy odpowiednią wartość zmiennej
{stala}$akcja{/stala}.
W przypadku gdy wybrana jest podstrona
z listą ptaków rodziny (np. kaczkowate):
/index/rodzina/kaczkowate.html
wykorzystujemy wyrażenie regularne. Przechwytujemy
napis kaczkowate. Zadanie to
realizuje wyrażenie regularne {stala}/^\\/rodzina\\/([^.]+)\\.html$/{/stala}. Następnie podaną rodzinę
wyszukujemy w bazie danych (metoda {stala}doSelect(){/stala}).
W przypadku gdy zostanie odnaleziona
dokładnie jedna rodzina, ustalamy wartość
zmiennej {stala}$akcja{/stala} na 5 oraz zapamiętujemy znalezioną
rodzinę jako obiekt {stala}$rodzina{/stala}:
if (preg _ match(\'/^\/rodzina\/([^.]+)\.html$/\',$adr, $m)) {
$c = new Criteria;
$c->add(RodzinaPeer::URL, $m[1]);
$rodziny = RodzinaPeer::doSelect($c);
if (count($rodziny) == 1) {
$akcja = 5;
$rodzina = $rodziny[0];
}
}
Ostatnimi przypadkami walidacji są: adres
szczegółowych danych ptaka oraz adres
rysunku ptaka, np.:
/index/ptak/batalion.html
/index/ptak/batalion.jpg
W tym przypadku adres URL dopasowujemy
do wyrażenia regularnego:
/^\/ptak\/([^.]+)\.((html)|(jpg))$/
W ten sposób wychwycimy człon będący
nazwą ptaka (np. batalion) oraz użyte rozszerzenie
(np. .jpg). Na podstawie przechwyconej
nazwy ptaka wyszukujemy w bazie odpowiedni
rekord (metoda {stala}doSelect(){/stala}). Jeśli znaleziony
zostanie dokładnie jeden rekord, to zapamiętujemy
go w zmiennej {stala}$ptak{/stala}, po czym na
podstawie przechwyconego rozszerzenia ustalamy
{stala}$akcja{/stala} na 6 (rozszerzenie .html) lub {stala}$akcja== 7{/stala} (rozszerzenie .jpg):
if (preg _ match(\'/^\/ptak\/([^.]+)\.((html)|(jpg))$/\',$adr, $m)) {
$c = new Criteria;
$c->add(PtakPeer::URL, $m[1]);
$ptaki = PtakPeer::doSelect($c);
if (count($ptaki) == 1) {
$ptak = $ptaki[0];
if ($m[2] == \'html\') {
$akcja = 6;
} else if ($m[2] == \'jpg\') {
$akcja = 7;
}
}
}
Kompletny przebieg walidacji jest przedstawiony
na listingu 2.
$adr = preg_replace(\'/[^a-zA-Z0-9\-_\/.?=]/\', \'\', $_SERVER[\'REQUEST_URI\']);
$adr = preg_replace(\'/^\' . preg_quote($_SERVER[\'SCRIPT_NAME\'], \'/\') . \'/\', \'\', $adr);
if ($adr == \'/index.html\') {
$akcja = 2;
} else if ($adr == \'/ptaki.html\') {
$akcja = 3;
} else if ($adr == \'/rodziny.html\') {
$akcja = 4;
} else if (preg_match(\'/^\/rodzina\/([^.]+)\.html$/\', $adr, $m)) {
$c = new Criteria;
$c->add(RodzinaPeer::URL, $m[1]);
$rodziny = RodzinaPeer::doSelect($c);
if (count($rodziny) == 1) {
$akcja = 5;
$rodzina = $rodziny[0];
}
} else if (preg_match(\'/^\/ptak\/([^.]+)\.((html)|(jpg))$/\', $adr, $m)) {
$c = new Criteria;
$c->add(PtakPeer::URL, $m[1]);
$ptaki = PtakPeer::doSelect($c);
if (count($ptaki) == 1) {
$ptak = $ptaki[0];
if ($m[2] == \'html\') {
$akcja = 6;
} else if ($m[2] == \'jpg\') {
$akcja = 7;
}
}
}
Przyjazne adresy w bazie danych
Baza danych witryny stosującej przyjazne adresy
URL jest wzbogacona o kolumny url. Kolumnę
url ma zarówno tabela ptak, jak i tabela
rodzina. Kolumny url muszą być kluczami
unikalnymi. Ich wartości należy ustalić
w momencie wstawiania rekordu do bazy danych.
Dzięki temu w szablonie Smarty adres URL
każdego rekordu będzie dostępny po wywołaniu
metody {stala}getUrl(){/stala}. Hiperłącze do ptaka zawartego
w obiekcie {stala}$ptak{/stala} wstawimy:
getUrl()}.html\">{$ptak->getNazwapolska()}
Natomiast hiperłącze do rodziny zawartej
w obiekcie $rodzina przyjmie postać:
getUrl()}.html\">{$rodzina->getRodzina()}
Uwagi
Uruchamiając przykład \"Ptaki\" zawierający
implementację przyjaznych URL-i warto
zwrócić uwagę na kilka szczegółów. Po
pierwsze w przykładzie użyte są adresy bezwzględne.
Należy więc zmienić konfigurację
serwera Apache. W pliku {stala}httpd.conf{/stala} należy
podać ścieżkę dostępu do folderu głównego
przykładu:
DocumentRoot \"C:/przyklad/ptaki-przyjazne-url\"
...
Po drugie pliki tekstowe z danymi są zapisane
w kodowaniu windows-1250. Skrypt
wstawiający rekordy do bazy danych rozpoczyna
się od zapytania SET NAMES:
$con = Propel::getConnection(\'ptaki\');
$sql = \'SET NAMES cp1250;\';
$stmt = $con->createStatement();
$rs = $stmt->executeQuery($sql);
Dzięki temu konwersję znaków z windows-1250 do kodowania stosowanego w bazie danych
(utf-8) wykona serwer MySQL.
Po trzecie baza danych zawiera pliki graficzne.
Wysyłanie pliku graficznego pochodzącego
z bazy danych należy poprzedzić zmianą
nagłówka:
header(\'Content-Type: image/jpg\');
echo $ptak->getRysunek();
exit();
Na zakończenie zauważymy, że podstrony
witryny będą widoczne pod podwójnymi
URL-ami, np.:
/index/ptaki.html
/index?id=3
Jeśli chcemy, by adresy ze znakiem zapytania,
np.
index?id=3
nie były obsługiwane, walidację rozpoczniemy
od przypadku znaku zapytania:
if (preg _ match(\'/^\?/\', $adr)) {
$akcja = 1;
} else ...