Projektując aplikację internetową szczególną uwagę należy poświęcić adresom URL. Proponowane rozwiązanie, w którym adresy URL są umieszczone w bazie danych w tabeli url oraz w kolumnach url, centralizuje zarządzanie przestrzenią URL-i. Metoda taka uniezależnia adresy od silnika i pozwala na ich edycję w panelu administracyjnym.
Adresy URL
Jak powinny wyglądać adresy URL aplikacji internetowej?
Czy mają odzwierciedlać strukturę bazy danych?
A może strukturę informacji serwisu WWW?
Kto za nie odpowiada: projektant szablonu HTML,
programista czy osoba odpowiedzialna za bazę danych?
Jak uchronić się przed dezaktualizacją adresów
w przypadku przebudowy witryny?
Istota problemu przestrzeni adresowej w aplikacji
internetowej wynika stąd, że adresy URL są
widoczne z zewnątrz. Posługują się nimi internauci
dodając linki zwrotne oraz wyszukiwarka. Możemy
wymienić silnik aplikacji, strukturę bazy danych czy
nawet stosowaną platformę sprzętowo-programową
(z systemem operacyjnym i językiem programowania
włącznie). Zmiany te nie są widoczne na
zewnątrz aplikacji. Wszystkie linki prowadzące do
serwisu pozostaną poprawne, pozycja w wyszukiwarce
nie zmieni się. Jeśli jednak wymienimy adresy
URL, to wieloletni wysiłek włożony w pozycjonowanie
witryny może przepaść.
Adresy URL należy uwzględnić w aplikacji już
we wczesnych fazach projektu, tak by stały się one
integralną częścią, a nie doklejonym dodatkiem. Należy
zwrócić uwagę na to, by adresy URL:
- były \”ładne\” i zrozumiałe,
- nie odzwierciedlały struktury bazy danych,
- nie zawierały identyfikatorów całkowitoliczbowych,
- nie wiązały aplikacji z językiem programowania.
Od strony implementacyjnej ważne jest, by
umożliwić edycję adresów URL w panelu administracyjnym
witryny oraz ułatwić współpracę na linii
projektant szablonu – programista silnika.
Struktura adresu
Adresy:
{stala}http://example.net/index.php?id=192&dz=php{/stala}
{stala}http://example.net/php/art/192/{/stala}
zawierają liczby będące najprawdopodobniej jakimiś
identyfikatorami w bazie danych (np. 110, 192,
809). Takie rozwiązanie powoduje, że już wymiana
rekordu (np. usunięcie i ponowne dodanie identycznego
rekordu) dezaktualizuje adres URL.
Jeśli podstrony identyfikujemy specjalnie preparowanymi
napisami, a nie liczbami całkowitymi, np.:
{stala}http://example.net/index.php?art=semantyka-html{/stala}
{stala}http://example.net/php/art/semantyka-html.php{/stala}
otrzymujemy rozwiązanie wygodniejsze. Jednak z racji
na wystąpienie rozszerzenia .php. wymiana stosowanego
języka (np. z PHP na Python) spowoduje
zmianę adresów URL lub wymusi sztuczne
stosowanie rozszerzeń .php w aplikacji napisanej
w innym języku.
Rozwiązaniem najbardziej uniwersalnym są adresy
stosujące rozszerzenie .html:
{stala}http://example.net/php/art/semantyka-html.html{/stala}
oraz adresy pozbawione rozszerzenia:
{stala}http://example.net/php/art/semantyka-html{/stala}
W obu przypadkach zestaw adresów dotyczących
wybranego artykułu może zostać rozbudowany
o kolejne podstrony:
{stala}http://example.net/php/art/semantyka-html/images.html{/stala}
{stala}http://example.net/php/art/semantyka-html/page-1.html{/stala}
{stala}http://example.net/php/art/semantyka-html/edytuj.html{/stala}
Udostępnianie adresów URL w panelu administracyjnym
Wiedząc, jaka ma być struktura adresu, zastanówmy
się, kto ma podjąć decyzję o adresie konkretnej
podstrony. Dodajemy do witryny artykuł zatytułowany
Programowanie obiektowe w PHP. Kto powinien
nadać adres URL nowej podstrony? Jak ten
adres powinien wyglądać?
Osobą odpowiedzialną za adres podstrony powinien
być redaktor wprowadzający nowy artykuł do
bazy danych lub administrator całego serwisu. Ustalenie adresu URL nowego artykułu musi odbywać się
z poziomu panelu administracyjnego, bez ingerencji
w kod aplikacji (np. pliki .php czy .htaccess).
Zatem w odpowiednim formularzu obok treści
artykułu redaktor wprowadzi adres:
{stala}http://example.net/obiekty{/stala}
Może jednak powinien użyć jednego z poniższych
adresów:
{stala}http://example.net/artykuly/obiekty{/stala}
{stala}http://example.net/art/php/obiekty{/stala}
{stala}http://example.net/php/art/obiekty{/stala}
A może w miejsce napisu obiekty należy użyć
skróconego tytułu:
{stala}http://example.net/programowanie-obiektowe-w-php{/stala}
{stala}http://example.net/artykuly/programowanie-obiektowe-w-php{/stala}
{stala}http://example.net/art/php/programowanie-obiektowe-w-php{/stala}
{stala}http://example.net/php/art/programowanie-obiektowe-w-php{/stala}
Ta decyzja nie powinna wiązać się z modelem
bazy danych ani silnikiem aplikacji. Powinna
to być niezależna decyzja redaktora lub administratora,
wolna od ograniczeń wynikających z kodu
aplikacji.
Adres podstrony serwisu powinien być dowolnym
napisem, np.:
{stala}http://example.net/c/obiekty{/stala}
{stala}http://example.net/arts/super/mocne/programowanie-obiektowe-w-php{/stala}
{stala}http://example.net/a/b/c/nowy-art{/stala}
Jeśli adresy działów (tj. {stala}/c/{/stala}, {stala}/arts/super/mocne/{/stala}, {stala}/a/b/c/{/stala}) oraz nazwa artykułu (np. obiekty, programowanie-
obiektowe-w-php i nowy-art) są dowolnymi
napisami, to artykuły będziemy mogli przenosić
pomiędzy działami, bez dezaktualizacji adresów
URL.
Ustalenie adresu URL bieżącego dokumentu
Działanie skryptu index.php
rozpoczynamy od ustalenia
folderu bazowego oraz adresu
URL bieżącego żądania
HTTP. Zadania te realizują
funkcje {stala}url_manage_get_root-dir(){/stala} oraz {stala}url_manage_get_request_url(){/stala} przedstawione na listingach 1 i 2 ( ).
Po wywołaniu:
$root_dir = url_manage_get_rootdir();
$adr = url_manage_get_request_url($root_dir);
zmienna {stala}$root_dir{/stala} zawiera ścieżkę do skryptu index.
php, zaś {stala}$adr{/stala} adres bieżącego żądania. Adres
{stala}$adr{/stala} zwracany przez funkcję {stala}url_manage_get_request_url(){/stala} nie zawiera (jako przedrostka) ścieżki
{stala}$root_dir{/stala}. Dzięki temu przykład można umieścić
w dowolnym folderze.
Walidacja
Domyślną stroną jest strona błędu 404:
$action = 1;
Walidacja sprowadza się do przeszukania tabeli
url w poszukiwaniu adresu {stala}$adr{/stala}:
$c = new Criteria;
$c->add(UrlPeer::URL, $adr);
$adres = UrlPeer::doSelectOne($c);
Jeśli podany adres zostanie znaleziony, to ustalamy
zmienną {stala}$action{/stala} oraz – w przypadku podstrony
mundialu – tworzymy obiekt {stala}$mundial{/stala}:
if ($adres) {
$action = $adres->getAkcja();
if ($adres->getInfo()) {
$mundial = MundialPeer::retrieveByPK ($adres->getInfo());
}
}
Przekazywanie danych do szablonu
Dane do szablonu przekazujemy w instrukcji switch,
sterowanej zmienną {stala}$action{/stala}. W poszczególnych przypadkach
tworzymy odpowiednie obiekty i przekazujemy
je do szablonu. Na koniec trafiają tam także zmienne
{stala}$action{/stala} i {stala}$root_dir{/stala}. Tak wygląda obsługa akcji 5
(zwycięzcy wybranego mundialu). Istnieje tu obiekt
$mundial utworzony na etapie walidacji:
$s = new Smarty;
switch ($action) {
...
case 5:
$s->assign(\'mundial\', $mundial);
$c = new Criteria;
$c->add(MiejscePeer::MUNDIAL _ ID,$mundial->getMundialId());
$c->addAscendingOrderByColumn(MiejscePeer::NUMER);
$zwyciezcy = MiejscePeer::doSelect($c);
$s->assign(\'zwyciezcy\', $zwyciezcy);
break;
...
}
$s->assign(\'action\', $action);
$s->assign(\'root_dir\', $root_dir);
$s->display(\'index.tpl\');
Odbieranie danych w szablonie
Do szablonu trafiają zmienne {stala}$action{/stala} i {stala}$root_dir{/stala}
oraz dodatkowe obiekty, zależne od wybranej podstrony.
Jeśli podstrona dotyczy konkretnego mundialu,
to przekazywany jest obiekt {stala}$munial{/stala}. Adresy
URL hiperłączy powstają na podstawie obiektu
$mundial oraz zmiennej {stala}$root_dir{/stala}:
{if $mundial}
{/if}Użycie zmiennej {stala}$root_dir{/stala} pozwala na umieszczenie
przykładów w dowolnym folderze.
Wymiana adresów
Przykładowa aplikacja nie ma panelu administracyjnego.
Adresy URL należy modyfikować korzystając
z phpMyAdmin-a. Jeśli w tabeli url wymienimy
adres:
{stala}/1986-meksyk/grupy.html{/stala}
na
{stala}/1986-meksyk/ala/ma/kota/grupy.html{/stala}
to zestawienie grup mundialu z 1986 roku będzie
dostępne pod nowym adresem (adres ten trzeba
ręcznie wpisać w przeglądarce).
Zmiana taka nie wymaga żadnych modyfikacji
ani w pliku .htaccess, ani w kodzie PHP. Stary adres
automatycznie stanie się niedostępny.
URL-e w bazie danych
Bezpośrednią konsekwencją tego, że URL-e edytujemy
w panelu administracyjnym aplikacji, jest konieczność
zapisywania ich w bazie danych. Dodatkowo,
w celu ułatwienia pracy osoby odpowiedzialnej za
szablon HTML, warto zdefiniować stały mechanizm
uzyskiwania adresów URL różnych zasobów.
Korzystając z Propela obiekty, które mają własne
podstrony, wzbogacamy o pole url. Załóżmy,
że w bazie znajdują się tabele artykul, kategoria,
tag, do których dostęp zapewniają klasy Artykul,
Kategoria, Tag. Jeśli każda z tych tabel ma kolumnę
url, wówczas po utworzeniu nowych obiektów
i przekazaniu ich do szablonu:
$s = new Smarty;
$art = ArtykulPeer::retrieveByPK(7);
$s->assign(\'art.\', $art);
projektant układu HTML w jasny sposób uzyskuje
adresy hiperłączy do konkretnych podstron:
getUrl()}\">{$art->getTytul()}
Podobnie w przypadku tabel kategoria oraz tag:
$s = new Smarty;
$kat = KategoriaPeer::retrieveByPK(55);
$tag = TagPeer::retrieveByPK(123);
$s->assign(\'kat\', $kat);
$s->assign(\'tag\', $tag);
getUrl()}\">{$kat->getNazwa()}
getUrl()}\">{$tag->getTag()}
Takie rozwiązanie minimalizuje komunikację pomiędzy
zespołem odpowiedzialnym za silnik a zespołem
odpowiedzialnym za szablon HTML. Projektanci
szablonu otrzymują jedynie informacje o zestawie
obiektów, które trafią do szablonu. Każdy
z obiektów zawiera metodę {stala}getUrl(){/stala}, która zwraca
pełny URL danego zasobu.
Walidacja adresów URL
Ostatnim zagadnieniem, które należy rozważyć, jest
walidacja adresów URL. Jeśli wszystkie adresy są
zawarte w bazie danych, to proces walidacji będzie
sprowadzał się do przeglądania bazy danych
w poszukiwaniu zadanego adresu. To zadanie uprościmy,
wprowadzając do modelu bazy danych dodatkową
tabelę o nazwie url. Tabela ta będzie zawierała
wszystkie adresy URL stosowane w aplikacji.
Dzięki temu walidacja adresów sprowadzi się
do przeglądania tej jednej tabeli. Dodatkowo, jeśli
wprowadzimy indeks unikalny, to zagwarantujemy
unikalność adresów URL we wszystkich działach
serwisu.
Przykład
Opisana metoda zarządzania przestrzenią adresów
URL zostanie przedstawiona na przykładzie, który
prezentuje wyniki meczów mistrzostw świata.
Witryna ta zawiera trzy podstrony:
- index.html – strona główna,
- mundiale.html – wszystkie mundiale,
- panstwa.html – wszystkie państwa,
oraz podstrony każdego mundialu:
- {$mundial_url}/index.html – zwycięzcy mundialu,
- {$mundial_url}/grupy.html – grupy mundialu,
- {$mundial_url}/faza-grupowa.html – rozgrywki fazy grupowej,
- {$mundial_url}/1-8.html – 1/8 finału,
- {$mundial_url}/1-4.html – 1/4 finału,
- {$mundial_url}/1-2.html – mecze półfinałowe,
- {$mundial_url}/final.html – finał,
- {$mundial_url}/maly-final.html – mecz o trzecie miejsce.
Przechowywanie URL-i w bazie danych
Model bazy danych jest przedstawiony na rysunku.
Z punktu widzenia zarządzania adresami URL istotnymi
jego fragmentami są: tabela url oraz kolumna
url tabeli mundial.
Tabela url ma kolumny:
- url (VARCHAR(255), UNIQUE) – adres URL,
- akcja (INTEGER) – numer wybranej akcji,
- info (INTEGER) – identyfikator mundialu, którego akcja dotyczy.
Będzie ona zawierała wszystkie adresy URL,
które są stosowane w aplikacji. Każdy adres jest
oznaczony akcją – to na jej podstawie wypełniamy
szablon danymi. Podstrony dotyczące konkretnego
mundialu zawierają także – w polu info – identyfikator
mundialu:
index.html (akcja 2)
mundiale.html (akcja 3)
panstwa.html (akcja 4)
1986-meksyk/index.html (akcja 5, mundial = 1)
1986-meksyk/grupy.html (akcja 6, mundial = 1)
...
1986-meksyk/maly-final.html (akcja 12, mundial = 1)
2006-niemcy/index.html (akcja 5, mundial = 6)
2006-niemcy/grupy.html (akcja 6, mundial = 6)
...
2006-niemcy/maly-final.html (akcja 12, mundial = 6)
Tabela mundial ma unikalną kolumnę url, która
ułatwi pracę nad szablonem HTML. Po przekazaniu
obiektu do szablonu:
$s = new Smarty;
$mundial = MundialPeer::retrieveByPK(3);
$s->assign(\'mundial\', $mundial);
hiperłącza wykonujemy wywołując metodę {stala}getUrl(){/stala}:
getUrl()}\">{$mundial ->getLokalizacja()}
Zarówno kolumna url tabeli mundial, jak i kolumna
url tabeli url są unikalne. Redundancję
polegającą na powieleniu tej samej wartości url
w dwóch miejscach bazy danych możemy usunąć
wprowadzając relację pomiędzy tabelami mundial
oraz url.
Przyjazne URL-e
Obsługę przyjaznych URL-i wykonujemy stosując
moduł {stala}mod_rewrite{/stala}. W pliku .htaccess umieszczamy
wpisy, które wszystkie zapytania HTTP prześlą
do skryptu index.php:
DirectoryIndex index.php
RewriteEngine on
RewriteRule ^[a-z0-9\-/]+\.html$ index.php
RewriteRule ^[a-z0-9\-/]+$ index.php
Podsumowanie
Proponowane rozwiązanie ma następujące cechy:
- wszystkie adresy URL są w tabeli url,
- każda tabela, której rekordy mają własne podstrony, ma kolumnę url,
- moduł mod_rewrite przekazuje zapytania do skryptu index.php bez żadnego parsingu adresu URL,
- adres bieżącego dokumentu jest ustalany w skrypcie index.php na podstawie zmiennej {stala}$_SERVER-[\’REQUEST_URI\’]{/stala}.
Takie podejście ma kilka zalet. Po pierwsze
przestrzeń adresów URL przestaje być związana
z silnikiem aplikacji, modelem danych i szablonem.
Pozwala to na wymianę kodu aplikacji oraz modyfikowanie
bazy danych bez dezaktualizacji adresów
URL.
Po drugie adresy nie odzwierciedlają modelu
danych. Każdy artykuł może zostać umieszczony
w dowolnym dziale, adresy URL działu i artykułu
nie stoją w żadnej relacji. Na przykład artykuł
o adresie:
{stala}http://example.net/php/wyrazenia-regularne-pcre/{/stala} możemy umieścić w dziale:
{stala}http://example.net/php/{/stala} lub przenieść do działu:
{stala}http://example.net/wyrreg/{/stala}
Adres artykułu (tj. {stala}/php/wyrazenia-regularne-pcre/{/stala}) nie zmienia się po przeniesieniu artykułu
do innego działu.
Kolejna cecha to zupełna eliminacja adresów
ze znakiem zapytania, np.:
index.php?id=123&dzial=php
Takie adresy nie są nigdzie w aplikacji stosowane.
Nie pojawiają się ani w bazie danych,
ani w skryptach PHP, ani nawet w plikach .htaccess.
Nie trzeba dokumentować poszczególnych
zmiennych URL (np. id, dzial) ani uzgadniać ich
roli. W związku z tym proces walidacji ulega także
istotnemu uproszczeniu. Cała walidacja sprowadza
się do jednego zapytania SQL: czy w tabeli
url istnieje podany adres URL? Nie badamy
poszczególnych zmiennych, co z jednej strony
upraszcza kod, a z drugiej daje gwarancję
poprawności walidacji.
Kolejną zaletą jest to, że aplikacja dysponuje
pełnym zestawem własnych URL-i. Ułatwi to nie tylko
przygotowywanie mapy sitemap.xml, ale także
obsługę przekierowań 301, gdybyśmy zdecydowali,
że niektóre adresy należy jednak wymienić.
Ostatnią cechą, która będzie widoczna już od
początkowej fazy pracy nad aplikacją, jest uproszczenie
stosowania przyjaznych URL-i w szablonie
HTML. Za każdym razem, gdy projektant szablonu
zechce wykonać hiperłącze, będzie on po prostu
wywoływał metodę {stala}getUrl(){/stala}, np.:
getUrl()}\">{$user->getImie()}{$user->getNazwisko()}
getUrl()}\">{$towar->getNazwa()}
getUrl()}\">{$zamowienie->getNumer()}