You are on page 1of 42

Ajax dla zaawansowanych.

Architektura i najlepsze
rozwizania
Autor: Shawn M. Lauriat
Tumaczenie: Radosaw Meryk
ISBN: 978-83-246-1585-8
Tytu oryginau: Advanced Ajax:
Architecture and Best Practices
Format: 168x237, stron: 392
Dowiedz si:
Jak tworzy rozbudowane i idealnie dopasowane do potrzeb interfejsy?
Jak zapewni uniwersalno, skalowalno oraz atwo eksploatacji?
Jak zaprojektowa architektur aplikacji?

Ajax (skrt od ang. Asynchronous JavaScript and XML) to niezwykle popularna


technologia tworzenia serwisw internetowych, w ktrej poczono kilka sprawdzonych
technik. Dziki tej zintegrowanej technologii do niezbdnego minimum zostaa
ograniczona ilo danych przesyanych pomidzy serwerem a oknem przegldarki
uytkownika. Nie tylko to przysporzyo Ajaksowi zwolennikw jest on take bardzo
dobrym narzdziem do tworzenia interaktywnych serwisw internetowych. Sprawdza
si rwnie przy przeprowadzaniu weryfikacji danych oraz rysowaniu wykresw
w czasie rzeczywistym. Dziki asynchronicznym wywoaniom umoliwia szybsz
interakcj z uytkownikiem, a poszczeglne sekcje mog by wywoywane
indywidualnie, dziki czemu aplikacja sprawia wraenie bardziej dynamicznej.
Ksika Ajax dla zaawansowanych. Architektura i najlepsze praktyki to idealna
lektura dla programisty, ktry mia ju przyjemno pracowa z Ajaksem. Podjto tu
wszystkie zagadnienia niezbdne do tworzenia dynamicznych aplikacji, niezalenie
od uytych narzdzi i technologii. Na praktycznych przykadach przedstawiono sposoby
wykorzystania Ajaksa do tworzenia rozbudowanych interfejsw w przegldarce
dla aplikacji internetowych, ze szczeglnym uwzgldnieniem ich uniwersalnoci,
moliwoci wielokrotnego wykorzystania kodu, skalowalnoci oraz atwoci
eksploatacji. Podrcznik wskazuje zarwno sytuacje, w ktrych Ajax jest przydatny,
jak i takie, w ktrych jego wybr nie speni oczekiwa uytkownika.
Wydawnictwo Helion
ul. Kociuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl

Planowanie interfejsw Ajaksa


Debugowanie, walidacja i optymalizacja kodu
Tworzenie skalowalnych interefejsw
Architektura aplikacji po stronie serwera oraz klienta
Bezpieczestwo aplikacji internetowych
Projektowanie gier

Poznaj wicej niezwykych moliwoci Ajaksa!

Spis treci

Podzikowania ............................................................................................................................... 11
O autorze ......................................................................................................................................... 13
Wprowadzenie ...................................................................................................... 15
0.1. Ajax znaczenie skrtu ............................................................................. 16
0.1.1. Asynchroniczny ................................................................................ 17
0.1.2. JavaScript ........................................................................................... 17
0.1.3. XML .................................................................................................... 18
0.2. Cele niniejszej ksiki .................................................................................. 19
0.3. Wymagania wstpne potrzebne do studiowania tej ksiki ................. 23
Rozdzia 1. Uyteczno ........................................................................................................... 27
1.1. Interfejs czy pokaz ........................................................................................ 28
1.1.1. Implementacja .................................................................................. 30
1.2. Oczekiwania uytkownikw ...................................................................... 32
1.3. Wskaniki i inne formy kontaktu z uytkownikami .............................. 34
1.3.1. Throbber ............................................................................................ 34
1.3.2. Wskaniki postpu ........................................................................... 37
1.3.3. Komunikaty dla uytkownikw wywietlane w ptli ............... 40
1.4. Znaczniki semantyczne ............................................................................... 47
1.4.1. Lepsza dostpno ........................................................................... 48
1.4.2. atwo uytkowania ...................................................................... 50
5

Spis treci

1.4.3. atwiejsza pielgnacja .....................................................................51


1.4.4. atwiejsze przetwarzanie ................................................................52
1.5. Co czy style CSS z jzykiem JavaScript ..................................................55
Rozdzia 2. Dostpno ..............................................................................................................61
2.1. Wymagania WCAG i wytyczne sekcji 508 ................................................62
2.1.1. WCAG .................................................................................................63
2.1.2. Wytyczne sekcji 508 ..........................................................................71
2.2. Czytniki ekranu mog obsugiwa wywoania Ajax ...............................73
2.2.1. Zastpowanie treci ..........................................................................74
2.2.2. Walidacja formularzy .......................................................................75
2.3. Dyskretny Ajax ..............................................................................................77
2.4. Projektowanie z uwzgldnieniem zasad dostpnoci ............................79
2.4.1. Projektowanie aplikacji o wysokim kontracie ............................79
2.4.2. Interfejs z moliwoci powikszania fragmentw ekranu .......81
2.4.3. Kontrolki, do ktrych atwo dotrze ..............................................83
2.5. WAI-ARIA .......................................................................................................84
Rozdzia 3. Architektura aplikacji po stronie klienta .........................................................87
3.1. Obiekty i wyzwalanie zdarze ...................................................................88
3.1.1. Obsuga zdarze obiektw wbudowanych ..................................90
3.1.2. Obiekty JavaScript ............................................................................92
3.2. Wzorzec projektowy Model-Widok-Kontroler ......................................108
3.2.1. Model ................................................................................................109
3.2.2. Widok ................................................................................................113
3.2.3. Kontroler ..........................................................................................122
3.3. Projektowanie aplikacji sterowanych zdarzeniami ...............................125
3.3.1. Zalety wykorzystanej architektury ..............................................126

Spis treci

Rozdzia 4. Debugowanie kodu po stronie klienta ........................................................... 129


4.1. Walidacja, walidacja, walidacja ................................................................ 130
4.1.1. Walidator zestawu znacznikw ................................................... 132
4.1.2. Walidator CSS ................................................................................. 133
4.1.3. Ekstraktor semantyczny ................................................................ 134
4.2. Narzdzia w przegldarkach i wtyczki .................................................. 135
4.2.1. Konsola ............................................................................................. 135
4.2.2. Internet Explorer ............................................................................ 136
4.2.3. Firefox ............................................................................................... 141
4.2.4. Opera ................................................................................................ 147
4.2.5. Safari ................................................................................................. 149
4.3. Profilowanie kodu JavaScript ................................................................... 152
4.3.1. Rozpoznawanie wskich garde ............................................... 154
4.4. Testy jednostkowe ...................................................................................... 158
4.4.1. Asercje .............................................................................................. 160
4.4.2. Konfiguracja testu .......................................................................... 161
4.4.3. Waciwy test .................................................................................. 164
4.4.4. Obiekty-atrapy ................................................................................ 166
4.4.5. Zestawy testw ............................................................................... 169
Rozdzia 5. Optymalizacja wydajnoci ................................................................................ 173
5.1. Wydajno bazy danych ........................................................................... 174
5.1.1. Schemat ............................................................................................ 175
5.1.2. Zapytania ......................................................................................... 179
5.2. Zajto pasma i opnienia ..................................................................... 181
5.2.1. Pasmo ............................................................................................... 183
5.2.2. Opnienia ...................................................................................... 187

Spis treci

5.3. Pami podrczna .......................................................................................190


5.3.1. System plikw .................................................................................191
5.3.2. Pami ...............................................................................................193
5.3.3. Uzupenienie implementacji .........................................................200
5.4. Wykorzystanie HTTP/1.1 ...........................................................................202
5.4.1. If-Modified-Since ............................................................................205
5.4.2. Range ................................................................................................206
5.5. Profilowanie kodu PHP .............................................................................209
5.5.1. Debuger APD ...................................................................................209
5.5.2. Xdebug ..............................................................................................213
Rozdzia 6. Skalowalne i atwe do pielgnacji aplikacje Ajaksa ....................................219
6.1. Oglne praktyki ..........................................................................................220
6.1.1. Uycie procesora .............................................................................221
6.1.2. Zuycie pamici ..............................................................................223
6.2. Wiele prostych interfejsw ........................................................................227
6.2.1. Modularno ....................................................................................227
6.2.2. Pne adowanie .............................................................................230
6.3. Rozbudowane interfejsy ............................................................................233
6.3.1. Aplikacje monolityczne ..................................................................233
6.3.2. Wstpne adowanie ........................................................................237
Rozdzia 7. Architektura aplikacji po stronie serwera ......................................................239
7.1. Projektowanie aplikacji dla wielu interfejsw .......................................240
7.2. Wzorzec projektowy Model-Widok-Kontroler ......................................244
7.2.1. Model ................................................................................................244
7.2.2. Kontroler ..........................................................................................254
7.2.3. Widok ................................................................................................263
7.3. Wykorzystanie wzorca Fabryka
wraz z mechanizmem obsugi szablonw ..............................................269

Spis treci

Rozdzia 8. Bezpieczestwo aplikacji internetowych ...................................................... 275


8.1. HTTPS .......................................................................................................... 277
8.1.1. Po co uywa HTTPS? ................................................................... 277
8.1.2. Bezpieczestwo a wydajno ....................................................... 279
8.2. Ataki typu SQL Injection .......................................................................... 280
8.2.1. Nie uywaj opcji magic_quotes ................................................... 281
8.2.2. Filtrowanie ....................................................................................... 282
8.2.3. Instrukcje preparowane ................................................................ 284
8.3. XSS ................................................................................................................ 285
8.3.1. Unieszkodliwianie zestawu znacznikw ................................... 286
8.3.2. Unieszkodliwianie adresw URL ................................................ 291
8.4. CSRF ............................................................................................................. 292
8.4.1. Sprawdzanie adresu, z ktrego pochodzi danie ................... 294
8.4.2. Przesyanie dodatkowego nagwka .......................................... 296
8.4.3. Pomocnicze, losowe tokeny .......................................................... 297
8.5. Nie ufaj uytkownikowi ............................................................................ 300
8.6. Nie ufaj serwerowi ..................................................................................... 302
Rozdzia 9. Dokumentacja ..................................................................................................... 307
9.1. Dokumentowanie kodu jest potrzebne .................................................. 308
9.1.1. Odtwarzanie projektu we wasnej pamici ............................... 308
9.1.2. Uatwienie nauki ............................................................................ 310
9.1.3. Uwaajcie na autobusy .................................................................. 311
9.2. Dokumentowanie interfejsu API ............................................................. 311
9.2.1. phpDocumentor ............................................................................. 312
9.2.2. JSDoc ................................................................................................ 320

10

Spis treci

9.3. Wewntrzna dokumentacja programisty ...............................................325


9.3.1. Standardy kodowania ....................................................................327
9.3.2. Przewodniki programowania .......................................................332
9.3.3. Przewodniki stylu ...........................................................................333
Rozdzia 10. Projektowanie gier ..............................................................................................335
10.1. Inny rodzaj bezpieczestwa ......................................................................337
10.1.1. Walidacja ..........................................................................................338
10.1.2. Logika serwerowej strony aplikacji .............................................340
10.2. Pojedynczy gracz ........................................................................................343
10.2.1. Podwjne buforowanie ..................................................................343
10.3. Tryb czasu rzeczywistego wielu graczy .............................................348
10.3.1. Odpowiedzi w postaci strumieni .................................................349
10.3.2. Element event-source grupy WHATWG ....................................354
10.3.3. Animacje z wykorzystaniem technik przewidywania ..............356
Rozdzia 11. Wnioski .................................................................................................................359
11.1. Pamitaj o uytkownikach ........................................................................360
11.2. Projektuj z myl o przyszoci .................................................................361
11.3. Programuj z myl o przyszoci ..............................................................362
Bibliografia ...................................................................................................................................365
Dodatek A

Zasoby ....................................................................................................................369

Dodatek B

OpenAjax ..............................................................................................................373
Zgodno ze standardem ....................................................................................374
Rejestracja przestrzeni nazw ..............................................................................377
Zarzdzanie zdarzeniami ...................................................................................378

Skorowidz .....................................................................................................................................381

7
Architektura aplikacji
po stronie serwera

W tym rozdziale:
Q 7.1. Projektowanie aplikacji dla wielu interfejsw

240

Q 7.2. Wzorzec projektowy Model-Widok-Kontroler

244

Q 7.3. Wykorzystanie wzorca Fabryka wraz z mechanizmem


obsugi szablonw

269

239

240

Rozdzia 7. Architektura aplikacji po stronie serwera

rojektowanie rozbudowanych aplikacji internetowych zazwyczaj koncentruje si na programach dziaajcych po stronie klienta. Ma to sens, poniewa wikszo nowych technologii stosowanych w aplikacjach internetowych
koncentruje si wok obiektu JavaScript XMLHttpRequest. Jednak projektowanie aplikacji po stronie serwera w dalszym cigu zasuguje na co najmniej tyle
uwagi, ile wymagao ono przed upowszechnieniem si aplikacji sterowanych
daniami Ajax. Technologie po stronie serwera nie tylko musz w dalszym
cigu wspiera operacje adowania kompletnych stron, ale take obsugiwa
zapytania majce na celu aktualizacj lub odczytywanie informacji w tle.
Aplikacje tej natury wymagaj dostatecznie elastycznej architektury, tak aby
mona byo ograniczy adowanie danych i obiektw oraz wykonywanie operacji dla potrzeb obsugi biecego dania. Zapewnienie tego samego poziomu
kontroli autoryzacji i zestawu funkcji niezalenie od tego, jaka cz aplikacji
aduje si dla okrelonego typu dania, jest nie lada wyzwaniem.

7.1. Projektowanie aplikacji dla wielu interfejsw


Aplikacje sterowane daniami Ajax wymagaj dostpu do danych i zestawu funkcji
przy uyciu co najmniej dwch formatw odpowiedzi: XHTML i XML lub JSON.
Spenienie tego wymagania stwarza konieczno zapewnienia moliwoci wyprowadzania tych samych danych bd wynikw wywoa do dwch rnych zestaww szablonw to implikuje potrzeb elastycznego mechanizmu obsugi szablonw. Potrzebna jest rwnie wystarczajco elastyczna architektura aplikacji
taka, ktra gwarantuje, e dla wybranego dania nie bd wykonywane zbdne
operacje.
Aplikacja nie powinna zajmowa si pobieraniem metadanych strony, struktur
nawigacyjnych, czy te obsug uprawnie zwizanych z dozwolonymi kontrolkami interfejsu tylko po to, aby zwrci prost list w odpowiedzi na wywoanie
XMLHttpRequest. Potrzebna jest jednak pewna struktura tworzca krgosup aplikacji,
a take mechanizm zapewniajcy zaadowanie konfiguracji, nawizanie poczenia
z baz danych, zarzdzanie buforowaniem, przesyaniem komunikatw, uwierzytelnianiem, autoryzacj oraz adowaniem zasobw.
Dziki naleytej abstrakcji logiki w aplikacji mona zapewni atwiejsze dynamiczne adowanie funkcji i ich wielokrotne wykorzystywanie w aplikacji. Dziki
temu atwiejsza jest rwnie praca z kodem aplikacji, poniewa funkcje i metody
maj znacznie bardziej zwize definicje. Zaniedbania w odpowiedniej abstrakcji

Projektowanie aplikacji dla wielu interfejsw

241

logiki aplikacji niezwykle utrudniaj pielgnacj. W przypadku koniecznoci aktualizacji logiki powielonej w kilku miejscach aplikacji trzeba to robi wielokrotnie.
Problemy s szczeglnie dotkliwe, kiedy w powielonym kodzie znajduje si bd,
ktrego poprawienie ma istotne znaczenie na przykad luka w zabezpieczeniach.
Rozwamy funkcje biblioteczne jzyka PHP md5() i sha1(). S to narzdzia do
tworzenia skrtw (ang. hash) pozwalajce na generowanie tokenw wykorzystywanych w plikach cookie, sesjach, operacjach weryfikacji rde przesyania,
nazwach plikw oraz innych miejscach wymagajcych pozornie losowych, ale
spjnych unikatowych identyfikatorw. Uywa si ich zwaszcza wtedy, gdy istnieje zagroenie, e napastnikom uda si odgadn wspomniane identyfikatory
i wykorzysta do realizacji rnych celw. Programici zazwyczaj korzystaj z tych
funkcji bezporednio, poniewa wywoanie sha1($text) zajmuje bardzo mao
miejsca i nie pogarsza czytelnoci kodu.
Moe si jednak zdarzy, e programista w wywoaniach funkcji nie poda argumentu salt1. W takim przypadku w kilku plikach rdowych naley wprowadzi
zmiany w sposb, ktry moe nieco skomplikowa kod trzeba bowiem zaadowa parametry globalne i zarzdza ich wartociami. W niektrych zastosowaniach
narzdzi generowania skrtw trzeba posugiwa si losow wartoci argumentu
salt, w innych argument ten jest prekonfigurowany na przykad podczas generowania skrtw hase, aby zapewni ich spjno. We wszystkich wynikajcych
std scenariuszach w funkcjach naley uwzgldni wicej kodu, ktrego tam by
nie powinno.
class User
/* ...
public
if

extends DBO {
*/
function set($field, $value) {
($field == 'password') {
global $config;
$salt = $config['settings']['salt'];
$hash = sha1($string . $salt);
return parent::set($field, $hash);
} else {
return parent::set($field, $value);
}

}
/* ... */
}

Wicej informacji na temat generowania skrtw z argumentem salt mona znale w rozdziale 8.,
Bezpieczestwo aplikacji internetowych.

242

Rozdzia 7. Architektura aplikacji po stronie serwera

Powysza definicja metody User::set() przecia bazow definicj DBO::set().


Ma to na celu ustawienie wartoci pola password na skrt hasa zamiast jego wartoci tekstowej. Zaprezentowany sposb daje dostp do globalnych ustawie konfiguracyjnych umoliwiajcych skorzystanie z predefiniowanych wartoci argumentu salt, ale obsuga tego argumentu i mechanizmu generowania skrtw
wymaga miejsca wewntrz metody User::set(). Praktyka ta nie ma sensu w pokazanym kontekcie i zaciemnia pierwotne przeznaczenie metody zwaszcza jeli
w jej obrbie zachodzi potrzeba implementacji dodatkowych funkcji w dalszej
fazie projektowania. Oto kolejna wersja metody set:
class User
/* ...
public
if

extends DBO {
*/
function set($field, $value) {
($field == 'password') {
return parent::set(
$field,
Utilities::hashWithSalt($value)
);
} else {
return parent::set($field, $value);
}

}
/* ... */
}

Powysza definicja metody User::set() wymaga jedynie wywoania statycznej


metody Utilities::hashWithSalt(), co sprawia, e jest ona znacznie atwiejsza do
pielgnacji. W przypadku zmiany logiki zwizanej z generowaniem skrtw zmieni trzeba tylko definicje wewntrz klasy Utilities. Dziki temu, e jest tylko
jedno miejsce wprowadzania zmian, zyskujemy pewno, e wymagana zmiana
bdzie wprowadzona we wszystkich fragmentach kodu, w ktrych wykorzystano
funkcje generowania skrtw.
Oddzielenie logiki aplikacji od warstwy obsugi danych i prezentacji jest niezwykle pomocne w przypadku wszystkich aplikacji internetowych, a w szczeglnoci
w przypadku aplikacji internetowych sterowanych wywoaniami Ajaksa. Na przykad logika zaimplementowana w wielu miejscach aplikacji przyczynia si do
powstania zalenoci prezentacji od metod przechowywania danych. Zdarza si
rwnie, e logika aplikacji, a nawet kod obsugi danych, staj si zalene od interfejsu prezentowanego uytkownikom.
Zapisywanie kodu obsugi zapyta do baz danych i otrzymywanych wynikw
bezporednio w wyjciu XHTML prowadzi do powstania liniowego wyjcia o stru-

Projektowanie aplikacji dla wielu interfejsw

243

kturze siatkowej (ang. grid-based) z bardzo ograniczonym zestawem funkcji. Poniewa kod niezbdny do zarzdzania zapytaniami i zwracanymi wynikami ju
skazi kod zwizany z generowaniem zbioru znacznikw strony, dodanie bardziej zoonych interfejsw jest znacznie trudniejsze do zaimplementowania, nie
mwic ju o pielgnacji.
W przypadku elementw interfejsu sterowanych daniami Ajax problem mieszania interfejsu z logik aplikacji wzrasta proporcjonalnie do liczby formatw
wyniku, jakie powinna obsugiwa aplikacja XML, JSON lub oba te formaty.
Ca logik, ktra powinna trafi do pierwotnej postaci kodu XHTML, naley teraz
zdublowa w kadej z metod wyniku obsugujcych dania Ajax. Jeli w tym
momencie, w zwizku z wywoaniami z poziomu kodu generujcego kady z formatw wyjcia, zdarzy si sytuacja podobna do problemu logiki generowania skrtw opisanego poprzednio, rozwizanie problemu zajmie sporo czasu i wysiku.
Na przykad aplikacja moe wywietla informacje uytkownika z wykorzystaniem nastpujcego kodu:
<div id="userinfo">
<?php
$query = 'SELECT `id`, `login`, `nazwisko`, `email`, `utworzono`
FROM `uzytkownicy` where `id` = ' . $id;
if ($result = mysqli_query($query)) {
if ($user = mysqli_fetch_assoc($result)) {
// Wywietlenie informacji o uytkowniku z wykorzystaniem tablicy asocjacyjne.j
} else {
// Uytkownika nie znaleziono.
}
} else {
// Bd zapytania.
}
?>
</div>

W powyszym kodzie jest kilka problemw wszystkie one mogyby si pojawi w kodzie odpowiedzi zarwno w formacie XML, jak i JSON. Na podstawie
samego kodu programici nie s w stanie stwierdzi, czy dla zmiennej $id zastosowano jakiekolwiek mechanizmy filtrowania bd unieszkodliwiania znakw
sterujcych. Jeli wystpi bd zapytania, programista ma jedynie moliwo obsuenia i wywietlenia komunikatu o bdzie w tym konkretnym punkcie wyniku.
W tym momencie spora cz wyniku moga ju dotrze do przegldarki uytkownika. Kod generujcy poprzedni wynik musi zaoy powodzenie wykonania
zapytania. Problem komplikuje nastpujca kwestia: co si stanie, jeli aplikacja

244

Rozdzia 7. Architektura aplikacji po stronie serwera

musi obsuy inne mechanizmy obsugi baz danych? Zapytanie oraz kod bezporednio komunikujcy si z baz danych nie moe pozosta wewntrz kodu renderowania wyniku, jeli ma by zachowana jego atwo pielgnacji oraz uyteczno.

7.2. Wzorzec projektowy Model-Widok-Kontroler


Wzorzec projektowy MVC jest jednym z czciej uywanych sposobw rozdzielania od siebie warstw logiki aplikacji, danych i prezentacji. Jak opisano w rozdziale 3., Architektura aplikacji po stronie klienta, wzorzec ten oddziela logik
obsugi danych od logiki biznesowej oraz logiki prezentacji. Dziki temu na przykad wiele czci aplikacji moe wspdzieli jedn implementacj logiki.
Opisany wczeniej problem wstawianych w kodzie zapyta do bazy danych
i logiki biznesowej nigdy nie moe si zdarzy w aplikacji korzystajcej z modelu
MVC, poniewa warstwa prezentacji z samej definicji nie ma sposobu na to, by
pozna metod przechowywania danych, nie mwic ju o uyciu zapyta i bibliotek funkcji specyficznych dla tej metody przechowywania danych. Kady z formatw wyniku, ktry naley zaimplementowa w aplikacji (XHTML, XML, JSON, itd.),
wspdzieli t sam logik pobierania danych dziki uyciu tych samych obiektw,
a nie bezporednich wywoa. Jeli zachodzi potrzeba modyfikacji dowolnego
elementu tej logiki na przykad jeli aplikacja wymaga obsugi innego silnika
bazy danych, mona zmodyfikowa odseparowan logik bez wpywu na kod prezentacji lub nawet logiki biznesowej. Ten sam problem dotyczy odseparowania
funkcji generowania skrtw, opisanej we wczeniejszej czci tego rozdziau.

7.2.1. Model
Logika obsugi danych w aplikacji standardowo koncentruje si wok interakcji
z pamici masow aplikacji, zazwyczaj z baz danych. W rzeczywistoci jednak
nie ma to wielkiego znaczenia, poniewa sama logika obsugi danych jest niezalena od typu pamici masowej. Logika obsugi danych powinna dotyczy tylko
takich operacji, jak uprawnienia uytkownikw, obsuga bdw oraz zalenoci.
Aby to zapewni, mona wykorzysta mechanizm dziedziczenia w celu usunicia
metod obsugi pamici masowej z logiki obsugi danych.

Wzorzec projektowy Model-Widok-Kontroler

245

Podobnie jak kady obiekt w wikszoci jzykw obiektowych rozszerza klas


lub podklas klasy Object2, wszystkie obiekty danych w aplikacji mog rozszerza
klas bazow DBO (opisujc obiekty obsugi bazy danych). Klasa ta zawiera cay kod
niezbdny do tworzenia, odczytywania, aktualizowania i usuwania rekordw:
/**
* Klasa bazowa obiektw obsugi bazy danych.
*/
class DBO {
// Klucze gwne niektrych tabel maj inne nazwy.
public $pk = 'id';
// Nazwa tabeli bazy danych.
protected $table;
// Nazwa tabeli bazy danych po unieszkodliwieniu znakw sterujcych.
private $table_mysql;
// Tablica asocjacyjna pl tabeli do przechowywania ich wartoci.
protected $fields = array('id' => null);
// Tablica z kluczami $fields po poddaniu ich operacji unieszkodliwiania tylko do
// wewntrznego uytku.
private $fields_mysql;
// Tablica opisujca ograniczenia dotyczce typu i rozmiaru poszczeglnych pl.
protected $fields_constraints = array();
// Tablica pl zaktualizowanych w okrelonym egzemplarzu.
protected $updated = array();
// Flaga decydujca o tym, czy naley wywoa metod insert() czy update() w momencie
// wywoania metody save().
protected $inserted = false;
/**
* Jeli pole istnieje, metoda zwraca jego biec warto,
* w innym przypadku zwraca false.
*/
public function get($var) {
if (array_key_exists($var, $this->fields)) {
return $this->fields[$var];
} else {
return null;
}
}
/**
* Jeli pole istnieje, metoda aktualizuje warto i oznacza jego miejsce w
* zaktualizowanej tablicy, tak by skrypt aktualizacyjny mia informacje o polach, ktre ma
* przetwarza.
2

W jzyku PHP rol klasy Object spenia klasa stdClass.

246

Rozdzia 7. Architektura aplikacji po stronie serwera

*/
public function set($field, $value) {
if (array_key_exists($field, $this->fields)) {
if ($this->fields[$field] != $value) {
// Zgoszenie wyjtku.
if ($this->meetsFieldConstraints($field, $value)) {
$this->fields[$field] = $value;
$this->updated[$field] = true;
} else {
return false;
}
}
return true;
} else {
return false;
}
}
/**
* Sprawdzenie ogranicze pola w celu stwierdzenia, czy
* podana warto spenia wymagania. Metoda zwraca
* true, jeli warunki ogranicze s spenione lub
* zgasza wyjtek w przeciwnym przypadku.
*/
protected function meetsFieldConstraints($field, $value) {
// Jeli nie zdefiniowano ogranicze, to nie ma czego sprawdza.
if (isset($this->fields_constraints[$field])) {
// Sprawdzenie typu.
if (isset($this->fields_constraints[$field]['type'])) {
Utilities::assertDataType(
$this->fields_constraints[$field]['type'],
$value
);
}
// Sprawdzenie rozmiaru.
if (isset($this->fields_constraints[$field]['size'])) {
Utilities::assertDataSize(
$this->fields_constraints[$field]['size'],
$value
);
}
}
return true;
}
/**
* Metoda pomocnicza umoliwiajca ustawienie wielu pl
* na raz dziki uyciu tablicy asocjacyjnej.

Wzorzec projektowy Model-Widok-Kontroler

*/
public function setAssoc($array) {
if (is_array($array)) {
foreach ($array as $field => $value) {
$this->set($field, $value);
}
} else {
return false;
}
}
/**
* Metoda save() sprawdza flag inserted w celu podjcia decyzji o tym, czy wstawia
* nowy rekord, czy te aktualizowa istniejcy.
*/
public function save() {
if ($this->inserted) {
return $this->update();
} else {
return $this->insert();
}
}
/**
* Usunicie rekordu na podstawie jego klucza gwnego.
*/
public function delete() {
$statement = $this->database->prepare(
'DELETE FROM ' . $this->table_mysql . ' WHERE '
. $this->fields_mysql[$this->pk] . ' = ?'
);
if ($statement->execute(array($this->fields[$this->pk]))) {
$this->inserted = false;
return true;
} else {
return false;
}
}
/**
* Ustawienie zaktualizowanych pl rekordu na nowe wartoci.
*/
protected function update() {
if (!in_array(true, $this->updated)) {
return true;
}
$qry = 'UPDATE ' . $this->table_mysql . ' SET ';
$f = false;
foreach ($this->updated as $field => $value) {

247

248

Rozdzia 7. Architektura aplikacji po stronie serwera

if (!$f) {
$f = true;
} else {
$qry .= ', ';
}
$qry .= $this->fields_mysql[$field] . ' = ? ';
}
$qry .= ' WHERE ' . $this->fields_mysql[$this->pk] . ' = ? ';
$statement = $this->database->prepare($qry);
// Pobiera zaktualizowane wartoci pl i dodaje klucz gwny
// dla klauzuli WHERE.
$parameters = array_push(
array_intersect_key($this->fields, $this->updated),
$this->fields[$this->pk]
);
if ($statement->execute($parameters)) {
return true;
} else {
return false;
}
}
/**
* Wprowadzenie biecych wartoci do nowego rekordu bazy danych.
*/
public function insert() {
$qry = 'INSERT INTO ' . $this->table_mysql . ' ('
. implode(', ', $this->fields_mysql)
. ') VALUES ('
. str_repeat('?,', count($this->fields) - 1) . '?)';
$statement = $this->database->prepare($qry);
if ($statement->execute($this->fields)) {
$this->inserted = true;
$this->fields[$this->pk] = mysql_insert_id();
return true;
} else {
$GLOBALS['messenger']->addError(
$this->database->errorInfo()
);
return false;
}
}
/**
* Alias metody DBO::select($pk, $id);
*/
public function load($id) {
$fields = array($this->pk);

Wzorzec projektowy Model-Widok-Kontroler

$values = array($id);
return $this->select($fields, $values);
}
/**
* Wybr rekordu na podstawie tablicy pl w celu porwnania
* z tablic wartoci.
*/
public function select($fields, $values) {
global $config;
if (is_array($fields) && is_array($values)) {
$qry = 'SELECT ('
. implode(', ', $this->fields_mysql)
. ') FROM ' . $this->table_mysql . ' WHERE ';
$f = false;
foreach ($fields as $i => $field) {
if (isset($this->fields_mysql[$field])) {
if (!$f) {
$f = true;
} else {
$qry .= ' AND ';
}
$qry .= $this->fields_mysql[$field] . ' = ? ';
}
}
$statement = $this->database->prepare($qry);
if ($statement->execute($values)) {
if ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$this->fields = $row;
$this->inserted = true;
return true;
}
} else {
$error = $statement->errorInfo();
$GLOBALS['messenger']->add($error[2], 'error');
}
}
return false;
}
/**
* Poniewa rozszerzenie PDO nie unieszkodliwia identyfikatorw tabeli i pl,
* ta metoda tworzy prywatn, nieszkodliw i ujt w cudzysw kopi
* identyfikatorw tabeli i pl do wykorzystania w zapytaniach SQL.
*/
protected function escapeIdentifiers() {
$this->table_mysql = $this->escapeTable($this->table);
foreach ($this->fields as $field => $value) {
$this->fields_mysql[$field] = $this->escapeIdentifier($field);

249

250

Rozdzia 7. Architektura aplikacji po stronie serwera

}
}
/**
* Nazwy tabel podlegaj rnym ograniczeniom.
* Dodatkowo w bazie danych MySQL nazwy tabel nie mog koczy si spacj ani
* zawiera znakw "/", "\" lub "."
*/
protected function escapeTable($string) {
// Nazwy tabel w systemie MySQL podlegaj nieco innym
// ograniczeniom.
$temp = preg_replace('/[\/\\.]/D', '', $string);
$temp = str_replace('`', '``', $temp);
return '`' . trim($temp) . '`';
}
/**
* W nazwach pl naley unieszkodliwi wszystkie lewe apostrofy (ang. backtick).
*/
protected function escapeIdentifier($string) {
return '`' . str_replace('`', '``', $string) . '`';
}
/**
* Kiedy obiekt wywoujcy podaje identyfikator ID, naley wywoa metod DBO::load()
* w celu zaadowania rekordu.
*/
public function __construct($id = null) {
global $controller;
$this->database = $controller->getDatabaseHandle();
if (!is_null($id)) {
$this->load($id);
}
$this->escapeIdentifiers();
}
}

Zaprezentowana klasa DBO implementuje wszystkie podstawowe metody niezbdne do zarzdzania odpowiednikami rekordw bazy danych w postaci obiektw danych w PHP. Klasa zamyka w sobie bezporednie operacje z baz danych
potrzebne do zarzdzania indywidualnymi rekordami. Dziki wykorzystaniu rozszerzenia PDO ich pisanie jest znacznie atwiejsze, zwaszcza kiedy aplikacja
wymaga zapewnienia przenonoci do innych systemw baz danych. Dla zapyta rwnie naleaoby zapewni mechanizm obsugi konkretnych baz danych.
Przenono aplikacji do innych systemw baz danych bez wielkiego wpywu na

Wzorzec projektowy Model-Widok-Kontroler

251

architektur aplikacji oraz logik w warstwie modelu implementacji wzorca MVC


mona zapewni dziki wykorzystaniu biblioteki generowania zapyta, biblioteki
zapisanych wstpnie zapyta lub dowolnej innej metody abstrakcji kodu SQL.
Zazwyczaj caa warstwa obsugi bazy danych jest umieszczona pomidzy obiektami danych a operacjami z baz danych w klasach dostpu do danych. Modyfikacje schematu, obsuga dodatkowych silnikw baz danych, a nawet przechowywanie
danych poza bazami danych s dziki temu o wiele atwiejsze. W niniejszej ksice nie
opisano tego zagadnienia, gwnie dlatego, aby na architekturze strony serwerowej
aplikacji sterowanych wywoaniami Ajaksa skoncentrowa si w tym rozdziale.

Dziki rozszerzeniom tego obiektu fragmenty warstwy modelu w aplikacji mog


zawiera dokadnie tyle logiki, ile potrzeba bez koniecznoci zamiecania kodu rdowego kodem obsugi pamici masowej. Dziki temu mona implementowa
dodatkowe wasnoci, takie jak zamieszczona poniej klasa Session bdca rozszerzeniem klasy DBO. Jej celem jest umoliwienie dostpu do danych sesji za porednictwem tego samego oglnego interfejsu przy jednoczesnym zapewnieniu zarzdzania rekordem sesji w bazie danych:
// W aplikacji naley wczy ten plik tylko raz
// i rozpocz sesj tylko raz.
session_start();
/**
* Klasa Session zarzdza danymi w tabeli user_sessions ,
* przy czym jej podstawowym przeznaczeniem jest zarzdzanie sesj PHP.
* Struktur t mona by rwnie dobrze wykorzysta do zapisania wszystkich danych sesji
* w tabeli bazy danych, zamiast uywania wbudowanych mechanizmw obsugi sesji jzyka PHP,
* bez koniecznoci zmian w interfejsie obiektowym.
*/
class Session extends DBO {
public $pk = 'session';
// Odwoanie do zmiennej superglobalnej $_SESSION.
protected $session;
// Tabela czca dane uytkownikw z tablic $_SESSION.
protected $table = 'user_sessions';
protected $fields = array(
'user' => null,
'session' => null
);

252

Rozdzia 7. Architektura aplikacji po stronie serwera

/**
* Odtworzenie identyfikatorw sesji poprawia bezpieczestwo, zwaszcza w przypadku
* wywoania po pomylnym zalogowaniu z wykorzystaniem danych identyfikacyjnych.
*/
public function regenerate() {
session_regenerate_id(true);
$this->fields[$this->pk] = session_id();
return $this->save();
}
/**
* Metoda Session::get() przecia metod DBO::get() w celu
* obsugi przezroczystego pobierania informacji
* z sesji.
*/
public function get($key) {
if ($key == 'id') {
return session_id();
} else if ($key == 'user') {
return $this->fields['user'];
} else if (isset($this->session[$key])) {
return $this->session[$key];
} else {
return false;
}
}
/**
* Metoda Session::set() przecia metod DBO::set() w celu
* obsugi przezroczystego ustawiania informacji
* dotyczcych sesji.
*/
public function set($key, $value) {
if ($key == 'id') {
return false;
} else if ($key == 'user') {
$this->fields['user'] = $value;
} else {
$this->session[$key] = $value;
}
return true;
}
/**
* Poniewa warto klucza gwnego pochodzi z dania
* (za porednictwem sesji w przegldarce), metoda Session::load
* powinna umoliwia automatyczn obsug przekazywanych danych.
*/

Wzorzec projektowy Model-Widok-Kontroler

253

public function load() {


return parent::load($this->fields[$this->pk]);
}
/**
* Przecienie konstruktora w celu utworzenia referencji do
* zmiennej superglobalnej $_SESSION.
*/
public function __construct() {
global $_SESSION;
parent::__construct();
$this->session = $_SESSION;
}
}

Klasa Session ma stosunkowo prost implementacj, poniewa jej nadrzdna


klasa DBO implementuje wszystko, co jest potrzebne do zarzdzania odpowiadajcym jej rekordem bazy danych. Poniewa egzemplarze klasy Session rwnie
mog mie prosty interfejs obiektowy, uwierzytelnianie uytkownika na podstawie
jego sesji i adowanie danych tego uytkownika do obiektu User mona wykona za
pomoc poniszego prostego kodu umieszczonego wewntrz metody klasy User:
if ($this->session->load()) {
if ($userid = $this->session->get('user')) {
if ($this->load($userid)) {
//Uwierzytelniony uytkownik z prawidow sesj.
} else {
// Nieprawidowy lub przeterminowany rekord sesji zwracajcy niepoprawny
// identyfikator uytkownika.
}
} else {
// Uytkownik z anonimow sesj.
}
} else {
// Uytkownik z cakowicie now sesj.
}

cilejsza kontrola bdw, rejestrowanie i informowanie uytkownika o wykonywanych operacjach poprawiyby elegancj tego kodu, trzeba jednak podkreli, e w tym przykadzie logika interakcji z egzemplarzem klasy Session, majca
na celu okrelenie jej stanu z dokadnoci do czterech stopni szczegowoci, zaja
tylko pierwsze dwa wiersze. Dane sesji mona by zapisa z wykorzystaniem wbudowanych metod obsugi sesji jzyka PHP, niestandardowej tabeli bazy danych,

254

Rozdzia 7. Architektura aplikacji po stronie serwera

tymczasowych plikw XML lub po prostu w pamici. Metody obsugi danych nie
maj wpywu na interfejs obiektowy dostpu do danych, a dziki abstrakcji operacje zapisu i odczytu danych, niezalenie od ich docelowej lokalizacji, s trywialne.

7.2.2. Kontroler
Kontroler w architekturze MVC zawiera ca logik aplikacji. W jego obrbie jest
cay kod dotyczcy interakcji z obiektami. Kontroler obsuguje rwnie potrzebne
operacje oraz wszystkie inne dziaania wymagane przez aplikacje, ktre nie mieszcz si w zakresie zarzdzania danymi oraz logiki prezentacji. Do jego zada nale
testy autoryzacyjne zwizane z operacjami (za te, ktre dotycz danych, jest odpowiedzialna warstwa modelu), adowanie zasobw oraz caa logika biznesowa zwizana z operacjami. Kontroler aduje rwnie zasoby wymagane przez warstw
Widok oraz przekazuje dane i zasoby potrzebne do renderowania strony.
7.2.2.1. Zagniedone kontrolery
W celu zapobieenia koniecznoci kodowania tego samego kodu architektury
w kadym obiekcie kontrolera aplikacja moe zawiera obiekt centralnego kontrolera, ktrego zadaniem jest wykonywanie tych wsplnych zada. Kady kontroler zagniedony wewntrz centralnego kontrolera skupia si tylko na tej logice,
ktra go dotyczy. Dziki temu znacznie atwiejsze jest rwnie wprowadzanie
modyfikacji w architekturze, w przypadku kiedy zachodzi taka potrzeba na dalszych etapach projektowania.
Centralny kontroler w przykadzie zamieszczonym poniej jest maksymalnie
ograniczony i spenia jedynie rol solidnej osi dla aplikacji. Jego zadaniem jest
zainicjowanie obsugi dania, utworzenie poczenia z baz danych oraz skonfigurowanie rodowiska dla pozostaej czci aplikacji. W dalszej kolejnoci centralny kontroler wyznacza kontroler wymagany dla wybranego dania, aduje go
i przekazuje do niego dania w celu wykonania kodu zwizanego z wybranym
fragmentem zestawu funkcji aplikacji.
Struktura ta w maksymalnym stopniu oddziela logik zwizan z sam architektur od waciwej logiki aplikacji, dziki czemu kod staje si bardziej czytelny
i atwiejszy do pielgnacji. W ten sposb aplikacja moe zaadowa poszczeglne
funkcje w miar potrzeb zamiast adowania duych fragmentw kodu dla kadego dania.

Wzorzec projektowy Model-Widok-Kontroler

255

Zaprezentowana poniej klasa CentralController nie obsuguje logiki potrzebnej do zarzdzania daniami w wikszym stopniu ni jest to konieczne do zaadowania waciwego kontrolera dla tego obszaru funkcji aplikacji. Dziki zagniedaniu kontrolerw w ten sposb logika szkieletu aplikacji moe pozosta we
wasnej, centralnej klasie, natomiast kady z kontrolerw podrzdnych obsuguje
swoj wasn logik. Dziki tej dodatkowej warstwie abstrakcji klasy s znacznie
bardziej czytelne. Poza tym (oraz dziki prostej klasie Utilities lub dostpnej
globalnie bibliotece funkcji) kada klasa zawiera tylko tyle kodu, ile jest niezbdne
do spenienia swojej roli.
class CentralController {
// Alias tablicy konfiguracyjnej.
protected $config;
// Odwoania do zmiennych globalnych.
protected $raw_get;
protected $raw_post;
protected $raw_request;
protected $raw_headers;
// Kontroler dla wybranego fragmentu.
protected $controller;
// Odwoanie do biecego uytkownika.
protected $user;
public function handleRequest($get, $post = null, $request = null) {
$this->raw_get = $get;
$this->raw_post = (is_null($post)) ? array() : $post;
$this->raw_request = (is_null($request)) ? array() : $request;
try {
$this->loadUser();
$this->loadController();
$this->passTheBuck();
} catch (Exception $e) {
// Strona z komunikatem o bdzie krytycznym.
}
}
protected function loadUser() {
try {
Utilities::loadModel('User');
$this->user = new User();
$this->user->authenticate();
} catch (Exception $e) {
exit($e->getMessage());
}
}

256

Rozdzia 7. Architektura aplikacji po stronie serwera

protected function loadController() {


global $controllers;
// Jeli nie okrelono kontrolera, wykorzystanie kontrolera domylnego.
$controller_key = 'default';
if (isset($this->raw_get['c'])
&& isset($controllers[$this->raw_get['c']])) {
$controller_key = $this->raw_get['c'];
}
// Wyszukanie kontrolera lub zgoszenie wyjtku.
$controller_path = 'controllers/'
. $controllers[$controller_key]['filename'] . '/'
. $controllers[$controller_key]['filename'] . '.php';
// Sprawdzenie, czy plik istnieje przed jego zaadowaniem na wypadek, gdyby jego lokalizacja
// zmienia si od chwili generowania listy dostpnych kontrolerw.
if (!file_exists($controller_path)) {
throw new Exception('Kontrolera nie znaleziono');
}
// Zaadowanie pliku i utworzenie egzemplarza obiektu kontrolera.
include $controller_path;
$this->controller = new $controllers[$controller_key]['class']();
return true;
}
/**
* Prosta metoda do adowania nagwkw dania HTTP.
* Zwraca dan warto, jeli ona istnieje.
*/
public function getHeader($key) {
// Pne adowanie nagwkw apache.
if (!isset($this->raw_headers)) {
$this->raw_headers = apache_request_headers();
}
if (isset($this->raw_headers[$key])) {
return $this->raw_headers[$key];
} else {
return false;
}
}
protected function passTheBuck() {
$this->controller->handleRequest(
$this->raw_get,
$this->raw_post,
$this->raw_request
);
}
public function getDatabaseHandle() {

Wzorzec projektowy Model-Widok-Kontroler

257

if (!isset($this->database)) {
$this->loadDatabase();
}
return $this->database;
}
protected function loadDatabase() {
$this->database = new PDO(
$this->config['database']['dsn'],
$this->config['database']['username'],
$this->config['database']['password'],
$this->config['database']['options']
);
}
public function display() {
$this->controller->display();
}
public function __construct() {
include 'configuration.php';
$this->config = $config;
}
}

Ten kontroler moe rwnie rozwizywa problem dostarczania co najmniej


jednego tokenu dla kadego zagniedonego kontrolera. Powinien on by unikatowy dla sesji uytkownika i zakodowany z wykorzystaniem losowego cigu z klasy
Utilities. Praktyka ta pozwala na to, aby zagniedone kontrolery sprawdzay
obecno tokenu walidacyjnego waciwego dla operacji, dla ktrej naley zapewni autoryzacj. W rezultacie napastnikom jest o wiele trudniej przeprowadzi
atak CSRF.
Logika danego fragmentu aplikacji jest oddzielona od logiki pozostaych fragmentw dziki wykorzystaniu implementacji obiektu CentralController, przy czym
kady fragment uzyskuje dodatkow warstw zabezpiecze. Zamieszczone poniej
zmienne i metody dodane do obiektu CentralController umoliwiaj sprawdzanie
tokenu walidacyjnego za porednictwem wymaganego sposobu (zmiennej POST
lub nagwka HTTP) poprzez wywoanie pojedynczej metody obiektu CentralCon
troller:
// Flaga typu Boolean wskazujca na to, czy token walidacyjny jest prawidowy.
protected $validated = false;
protected $validation_token;

258

Rozdzia 7. Architektura aplikacji po stronie serwera

/**
* Pobranie tokenu waciwego dla biecego obszaru aplikacji,
* ale tylko wtedy, gdy uytkownik przeszed z innego obszaru.
*/
protected function generateValidationToken($area) {
// Pobranie ostatnio przegldanego obszaru zapisanego w sesji.
$last_viewed = $this->user->session->get('last_viewed_area');
// Ponowne wygenerowanie tokenu i zastosowanie go dla sesji,
// w przypadku, gdy jest to obszar rny od biecego.
if ($area != $last_viewed) {
$session = $this->user->session->get('id');
$this->validation_token = Utilities::generateToken($area . $session);
$this->user->session->set('last_viewed_area', $area);
}
}
/**
* Walidacja tokenu z nagwkami dania.
*/
public function validateHeader() {
return $this->validateToken(
$this->getHeader(
$this->config['settings']['validation']
)
);
}
/**
* Walidacja tokenu z danymi POST.
*/
public function validatePost() {
if (isset($this->raw_post[$this->config['settings']['validation']])) {
return $this->validateToken(
$this->raw_post[$this->config['settings']['validation']]
);
}
}
/**
* Sprawdzenie, czy biecy token oraz token dania s ze sob zgodne.
*/
public function validateToken($test) {
return ($test === $this->validation_token);
}

Teraz obiekt CentralController na podstawie biecego dania musi jedynie


wywoa metod w celu wygenerowania tokenu do wykorzystania w metodach
walidacyjnych. Poniewa w zwizku z tym token bdzie zaleny od kontrolera,

Wzorzec projektowy Model-Widok-Kontroler

259

dobrym rozwizaniem bdzie wykorzystanie klucza dla kontrolera w tablicy asocjacyjnej skonfigurowanej wczeniej. Metoda loadController() moe nastpnie wywoa metod generowania tokenu po tym, gdy pomylnie utworzy egzemplarz kontrolera z wykorzystaniem tego samego klucza:
protected function loadController() {
global $controllers;
// Jeli nie okrelono kontrolera, wykorzystanie kontrolera domylnego.
$controller_key = 'default';
if (isset($this->raw_get['c'])
&& isset($controllers[$this->raw_get['c']])) {
$controller_key = $this->raw_get['c'];
}
// Wyszukanie kontrolera lub zgoszenie wyjtku.
$controller_path = 'controllers/'
. $controllers[$controller_key]['filename'] . '/'
. $controllers[$controller_key]['filename'] . '.php';
// Sprawdzenie, czy plik istnieje przed jego zaadowaniem na wypadek, gdyby lokalizacja
// zmienia si od chwili generowania listy dostpnych kontrolerw.
if (!file_exists($controller_path)) {
throw new Exception('Kontrolera nie znaleziono');
}
// Wygenerowanie tokenu walidacji dania.
$this->generateValidationToken($controller_key);
// Zaadowanie pliku i utworzenie egzemplarza obiektu kontrolera.
include $controller_path;
$this->controller = new $controllers[$controller_key]['class']();
return true;
}

Teraz, kiedy obiekt CentralController potrafi obsuy wstpne danie, zainicjowa poczenie z baz danych, podj prb zaadowania i uwierzytelniania
uytkownika, zaadowa w dynamiczny sposb kontroler ze zbioru zagniedonych kontrolerw oraz zapewni dostpn globalnie prost ochron przed atakami
CSRF, zagniedone kontrolery mog dziaa na bazie tej warstwy zgodnie z wasnymi wymaganiami.
Zagniedony kontroler, zamieszczony poniej, wykonuje tylko jedn operacj: poczenie formularza rejestracyjnego uytkownika z obiektem User w celu
utworzenia rekordu w bazie danych po tym, gdy uytkownik wprowadzi wszystkie potrzebne informacje. Wszystkie elementy z warstwy bazy danych s oddzielone
od widoku, kontroler jedynie zaopatruje widok w dane i obsuguje jego odpowiedzi.

260

Rozdzia 7. Architektura aplikacji po stronie serwera

Umieszczenie niektrych fragmentw kodu bazowego, niespecyficznych dla procesu rejestracji, miaoby wicej sensu w nadrzdnej klasie Controller, ktr poniszy
kontroler mgby rozszerza. Jednak dla zapewnienia przejrzystoci opisu w tym
rozdziale kontroler ten zdefiniowano w postaci pojedynczej klasy:
Utilities::loadModel('User');
class RegistrationController {
// Odwoania do zmiennych globalnych.
protected $raw_get;
protected $raw_post;
protected $raw_request;
// Utworzenie odwoania do obiektu uytkownika.
protected $user;
protected $userinfo = array(
'login' => null,
'name' => null,
'email' => null,
'password' => null
);
// Obiekt obsugujcy wyjcie.
protected $view;
// Sposb odpowiedzi na danie.
protected $method;
/**
* Pobranie metody dania z widoku, utworzenie egzemplarza silnika
* renderowania, ustawienie kontekstu renderowania do katalogu, w ktrym znajduje si ten plik,
* odfiltrowanie dania i podjcie prby utworzenia rekordu uytkownika na podstawie
* danych dania.
*/
public function handleRequest($get, $post = null, $request = null) {
// Utworzenie silnika renderowania.
$this->method = View::getMethodFromRequest($get);
$this->view = View::getRenderingEngine($this->method);
$this->view->setContext(dirname(__FILE__));
// Zastosowanie filtra dla dania.
$this->filterRequest($get, $post, $request);
// prba utworzenia nowego rekordu uytkownika z wykorzystaniem filtrowanego dania.
$this->createUser();
}
/**
* Akceptacja danych dania tylko wtedy, gdy obiekt CentralController dokona walidacji
* wartoci nagwka lub zmiennej post w przypadku operacji adowania penej strony (przesanie
* formularza).
*/

Wzorzec projektowy Model-Widok-Kontroler

public function filterRequest($get, $post = null, $request = null) {


global $controller;
if ($controller->validateHeader()
|| ($this->method == View::METHOD_XHTML
&& $controller->validatePost())) {
$this->raw_get = $get;
$this->raw_post = (is_null($post)) ? array() : $post;
$this->raw_request = (is_null($request)) ? array() : $request;
} else {
return false;
}
}
/**
* Prba utworzenia rekordu uytkownika w przypadku, gdy istniej wszystkie pola.
* Przekazanie komunikatw o bdach do obiektu Messenger.
*/
protected function createUser() {
global $messenger;
$this->user = new User();
if ($this->getUserInfo()) {
$errors_found = false;
foreach ($this->userinfo as $field => $value) {
try {
$this->user->set($field, $value);
} catch (Exception $e) {
if (!$errors_found) {
$errors_found = true;
}
$messenger->add($e->getMessage(), $field);
}
}
if (!$errors_found) {
try {
$this->user->save();
} catch (Exception $e) {
$messenger->add($e->getMessage(), 'error');
}
}
}
}
/**
* Pobranie wartoci pl wszystkich uytkownikw z dania post
* pod warunkiem, e haso i jego potwierdzenie
* s ze sob zgodne.
*/

261

262

Rozdzia 7. Architektura aplikacji po stronie serwera

public function getUserInfo() {


global $messenger;
foreach ($this->userinfo as $field => $value) {
if (isset($this->raw_post[$field])) {
if ($field == 'login' && User::loginExists($value)) {
$messenger->add('Nazwa uytkownika jest zajta', 'login');
continue;
} else if ($field == 'password') {
if (!isset($this->raw_post['password_confirm'])
|| $this->raw_post[$field]
!= $this->raw_post['password_confirm']) {
$messenger->add(
'Haso i jego potwierdzenie musz by ze sob zgodne',
'password'
);
continue;
}
}
$this->userinfo[$field] = $this->raw_post[$field];
}
}
return in_array(null, $this->userinfo);
}
/**
* Wywietlenie wyjcia mechanizmu renderowania z wykorzystaniem
* odpowiedniego szablonu dla wybranego formatu dania.
*/
public function display() {
switch ($this->method) {
case View::METHOD_JSON:
$this->view->setTemplate('json.php');
break;
case View::METHOD_XML:
$this->view->setTemplate('xml.php');
break;
case View::METHOD_XHTML:
default:
$this->view->setTemplate('index.php');
break;
}
$this->view->display();
}
}

Pokazany powyej obiekt RegistrationController obsuguje logik rejestracji


uytkownika zgodnie z wczeniejszym opisem. Prba utworzenia rekordu uytkownika jest wykonywana tylko wtedy, gdy w formularzu wprowadzono wszystkie

Wzorzec projektowy Model-Widok-Kontroler

263

niezbdne pola. Obiekt sprawdza, czy podana nazwa uytkownika nie jest zajta,
i przekazuje informacje o bdach do obiektu Messenger. Bdy s podzielone na
kategorie odpowiadajce polom, ktrych dotyczy dany bd (lub s zaliczone do
oglnej kategorii bdw metody User::save()). Dziki temu warstwa widoku moe
odpowiednio obsuy wszystkie komunikaty i bdy. Wasno t mona rwnie
zaimplementowa w postaci oglnej warstwy usug. Dziki temu bez koniecznoci
przepisywania kodu inne obiekty mog wykona te same testy. Takie rozwizanie
powinno sprawdzi si zwaszcza w przypadku bardziej zoonych aplikacji.
Metoda wywietlania sprawdza sposb obsugi dania i przypisuje odpowiedni szablon do silnika renderowania. Kontekst renderowania jest przypisany
do katalogu skryptu bezporednio po utworzeniu mechanizmu renderowania
w obsudze dania. Dziki temu, jeli zachodzi taka potrzeba, kontroler moe
do niego przypisa zmienne w innych metodach.
Teraz, kiedy w aplikacji wystpuje model i kontroler, widok bdzie obsugiwa
tylko te zadania, ktre s specyficzne dla niego samego. Bdzie on przekazywa
dane za porednictwem metod GET i POST oraz za porednictwem nagwkw
w wywoaniach Ajaksa. Do realizacji tego celu nie jest potrzebna wiedza na temat
sposobu interakcji z innymi elementami w pozostaych warstwach. Widok musi
jedynie pyta egzemplarz obiektu Messenger o komunikaty oraz informacje o bdach, tak by mona byo podj decyzj o tym, co naley wywietli w wyniku.

7.2.3. Widok
W pokazanej przykadowej architekturze widok skada si z silnika renderowania,
szablonw oraz architektury po stronie klienta opisanej w rozdziale 3. Podobnie jak
w przypadku warstwy modelu, te klasy i szablony zawieraj niewiele kodu zwizanego z innymi warstwami aplikacji.
Dziki wyranemu odseparowaniu od aplikacji internetowej dziaajcej po stronie serwera aplikacja dziaajca po stronie klienta moe istnie jako niemal cakowicie odrbna aplikacja. Aplikacja po stronie klienta wykorzystuje j jedynie jako
dostpny interfejs API. Taki podzia zapewnia rwnie niezwyk elastyczno
zarwno aplikacji dziaajcej po stronie klienta, jak i serwera, poniewa jedynym
wymaganiem jest utrzymanie spjnych interfejsw obiektw.

264

Rozdzia 7. Architektura aplikacji po stronie serwera

7.2.3.1. Renderowanie
Programici mog uy PHP jako jzyka obsugi szablonw przykad takiego
uycia jzyka PHP zaprezentowano w tym podpunkcie. Wikszo mechanizmw
obsugi szablonw zawiera bogaty zbir narzdzi do unieszkodliwiania, przetwarzania w ptli oraz grupowania zbioru znacznikw w logiczne fragmenty. W tym
przykadzie ograniczono si do niezbdnego minimum w celu pokazania, e nawet
niezwykle prosty silnik renderowania, wykorzystujcy jzyk PHP w roli swojego
jzyka obsugi szablonw, w dalszym cigu wymaga abstrakcji niezbdnej do dziaania warstwy widoku aplikacji.
Klasa RenderingEngine, zamieszczona poniej, implementuje zasadnicze wasnoci mechanizmu renderowania. Mona do niej przypisa zmienne, ktre w momencie wywietlania zostan udostpnione szablonom. Klasa moe zmieni kontekst tak,
aby szablony mogy wcza pliki bez koniecznoci znajomoci cieek dostpu do
nich. Klasa przesya dodatkowe nagwki odpowiedzi, cho domylnie nie obsuguje
adnych nagwkw, poniewa jej jedynym przeznaczeniem jest umoliwienie rozszerzania jej przez inne klasy:
class RenderingEngine {
// Katalog bazowy.
protected $context = '.';
// Nazwa katalogu zawierajcego szablony.
protected $templates = 'templates';
// Nazwa pliku szablonu do wywietlenia.
protected $template = 'index.php';
// Zapewnienie moliwoci jawnego przesyania zmiennych do szablonu.
protected $variables = array();
/**
* Ustawienie katalogu bazowego, skd maj by wczane pliki.
*/
public function setContext($path) {
$this->context = $path;
}
/**
* Przesonicie domylnego katalogu szablonw.
*/
public function setTemplatesDirName($dir) {
$this->templates = $dir;
}
/**
* Metoda suca do przekazania zmiennych do szablonu, dziki czemu

Wzorzec projektowy Model-Widok-Kontroler

265

* szablon nie musi zajmowa si ustawianiem wartoci zmiennych


* jest to zadanie kontrolera.
*/
public function setVariable($key, $value) {
$this->variables[$key] = $value;
}
/**
* Przesonicie domylnej nazwy szablonu.
*/
public function setTemplate($filename) {
$this->template = $filename;
}
/**
* Zmiana do przypisanego kontekstu, dziki czemu wywoania wczania plikw
* wykonane z poziomu szablonu nie wymuszaj od szablonu
* znajomoci wasnej cieki.
*/
public function display() {
// Zapisanie informacji o biecym katalogu roboczym w zmiennej tymczasowej.
$cwd = getcwd();
chdir($this->context);
$template_path = $this->templates . DIRECTORY_SEPARATOR . $this->template;
if (file_exists($template_path)) {
$this->sendHeaders();
include $template_path;
chdir($cwd);
} else {
chdir($cwd);
throw new Exception(
'Szablon "' . $template_path . '" nie istnieje.'
);
}
}
}

Powysza implementacja Widoku tworzy niezwykle prosty interfejs do wykorzystania go z poziomu Kontrolera. Klasa RegistrationController z wczeniejszej
czci niniejszego rozdziau wykorzystywaa silnik renderowania poprzez wywoanie nastpujcych czterech metod w rnych punktach przetwarzania:
$this->view->setContext(dirname(__FILE__));
$this->view->setTemplate('index.php');
$this->view->sendHeaders();
$this->view->display();

266

Rozdzia 7. Architektura aplikacji po stronie serwera

Trzecia z metod sendHeaders() wystpuje jako oddzielna metoda, poniewa klasa RenderingEngine moe zawiera zagniedone egzemplarze do renderowania szablonw, ktre wywietlaj jeden fragment caego wyniku. Prba wysania
dodatkowych nagwkw podczas tej czynnoci nie tylko zakoczyaby si porak,
ale take zgoszeniem bdu przez rodowisko PHP jest to bowiem niedozwolona operacja.
Kiedy silnik renderowania wywietla szablony, robi to za pomoc instrukcji
include. Powoduje to uruchomienie szablonw w kontekcie samego silnika szablonw z dostpem do tablicy variables w obrbie obiektu. Szablony uzyskuj take
moliwo wywoywania metod obiektu. W ten sposb powstaje bardzo wygodny
zasig deklaracji metod unieszkodliwiania cigw znakw i formatowania.
7.2.3.2. Szablony
Zaprezentowana architektura nie tylko znacznie uatwia renderowanie wyniku
w wielu formatach, ale take w przypadku formatu XHTML strona ma posta
interfejsu w formie zakadek i krokw. Warstwy Kontrolera i Modelu nic o tym nie
wiedz i nie musz wiedzie, poniewa wszystkie operacje mog by obsuone
przez szablony, a architektura aplikacji po stronie klienta zmienia si w caoci
w interfejs sterowany daniami Ajax. Kiedy uytkownik wypeni biecy zbir
pl, aplikacja po stronie klienta wysya danie Ajax na serwer i ustawia te specyficzne pola. Dziki temu mona obsuy wszystkie bdy przed umoliwieniem
uytkownikowi przejcia do nastpnej zakadki.
Same szablony w wikszoci przypadkw zawieraj bardzo niewiele kodu PHP,
poniewa istniej one gwnie po to, by tworzyy zestaw znacznikw obsugujcy
dane i funkcje aplikacji. Gwny szablon strony rejestracyjnej zawiera zaledwie
kilka fragmentw kodu PHP. Jeli przegldarka nie obsuguje Ajaksa i wymusza
wykorzystanie adowania penej strony, wwczas jedna ze stron ma za zadanie
wybr zagniedonego szablonu na podstawie biecego kroku:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Rejestracja uytkownika</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<script type="text/javascript" src="../includes/main.lib.js"></script>
<script type="text/javascript" src="../includes/ajax.lib.js"></script>
<script type="text/javascript" src="../includes/effects.lib.js"></script>
<script type="text/javascript" src="controllers/default/javascripts/default.js"></

Wzorzec projektowy Model-Widok-Kontroler

267

<script>
</head>
<body>
<h1>Przykad prostej rejestracji <acronym title="Interfejs
uytkownika">UI</acronym></h1>
<div class="demo">
<ol id="registration_tabs" class="navigation_tabs">
<li<?php if ($step == 1) { ?> class="selected"<?php } else if ($step > 1)
{ ?> class="completed"<?php } ?>>
<a href="./?step=1">Konto</a>
<span class="status">
(<?php if ($step == 1) {
echo 'w trakcie';
} else if ($step > 1) {
echo 'wykonany';
} ?>)
</span>
</li>
<li<?php if ($step == 2) { ?> class="selected"<?php } else if ($step > 2)
{ ?> class="completed"<?php } ?>>
<a href="./?step=2">Profil</a>
<span class="status">
(<?php if ($step < 2) {
echo 'niekompletny';
} else if ($step == 2) {
echo 'w trakcie';
} else if ($step > 2) {
echo 'wykonany';
} ?>)
</span>
</li>
<li<?php if ($step == 3) { ?> class="selected"<?php } ?>>
<a href="./?step=3">Potwierdzenie</a>
<span class="status">
(<?php if ($step < 3) {
echo 'niekompletny';
} else if ($step == 3) {
echo 'w trakcie';
} else if ($step > 3) {
echo 'wykonany';
} ?>)
</span>
</li>
</ol>
<?php
if ($step == 3) {
include 'step3.php';
} else if ($step == 2) {
include 'step2.php';

268

Rozdzia 7. Architektura aplikacji po stronie serwera

} else {
include 'step1.php';
}
?>
</div>
</body>
</html>

Jeszcze mniej skomplikowane s szablony odpowiedzi Ajaksa, poniewa w ogle


nie zawieraj formatowania i speniaj wycznie rol posacw dla aplikacji po
stronie serwera. W zalenoci od tego, czy aplikacja korzysta z formatu XML czy
JSON, wykorzystuje ona jeden z dwch poniszych szablonw:
<?php
global $messenger;
$messages = $messenger->getQueue();
echo "[\n";
for ($i = 0; $i < count($messages); $i++) {
if ($i > 0) {
echo "\n,";
}
echo '{"type":"',
$this->escape($messages[$i]->type),
'","',
$this->escape($messages[$i]->content),
'"}';
}
echo "\n]";
?>

Szablon formatu JSON wywietla ca zawarto szablonu za pomoc instrukcji


echo format JSON zawiera tak mao znakw, e prba oddzielenia go od wyjcia
PHP pogorszyaby tylko jego czytelno i utrudnia pielgnacj:
<?php
global $messenger;
$messages = $messenger->getQueue();
?><?xml version="1.0"?>
<messages>
<?php for ($i = 0; $i < count($messages); $i++) { ?>
<message type="<?php echo $this->escape($messages[$i]->type); ?>">
<?php echo $this->escape($messages[$i]->content); ?>
</message>
<?php } ?>
</messages>

Wykorzystanie wzorca Fabryka wraz z mechanizmem obsugi szablonw

269

W szablonie dotyczcym formatu XML zestaw znacznikw jest jednak znacznie bardziej obszerny. W dalszym cigu zawiera on niewiele znacznikw PHP, ale
jest czytelny. Wyjcie zarwno w formacie JSON, jak i XML renderuje si tylko
wtedy, gdy zostanie skonsumowane przez kod JavaScript w aplikacji po stronie
klienta i w zwizku z tym nie wymaga adnej dodatkowej logiki z kontrolera.
Pomimo wszystko wprowadzenie danych do tego wyjcia nie wprowadza dodatkowych komplikacji do szablonw bd mechanizmu obsugi szablonw. Jedynym
wymaganiem jest to, aby przed renderowaniem kontroler by zdolny do obsugi
logiki niezbdnej do odczytania danych i przypisania ich do mechanizmu obsugi
szablonw.

7.3. Wykorzystanie wzorca Fabryka


wraz z mechanizmem obsugi szablonw
Na podstawie przekazanych parametrw we wzorcu Fabryka, pokazanym na
rysunku 7.1, wykorzystano oglny interfejs w celu utworzenia egzemplarza wybranej klasy podrzdnej obiektu. W kontekcie mechanizmu obsugi szablonw Fabryka
powinna zwrci obiekt mechanizmu szablonw, ktry byby skonfigurowany dla
okrelonego formatu odpowiedzi na przykad XHTML. Kod dajcy utworzenia egzemplarza tego obiektu nie musi zna typu zwracanego obiektu szablonu.
Powinien jedynie wiedzie, w jaki sposb obsugiwa obiekty szablonw.
Powysza struktura bazuje na oglnym interfejsie do kadego z obiektw zarzdzajcych szablonami i renderowaniem dla wybranego formatu wyjcia. Dziki
zaprojektowaniu architektury w ten sposb dowolna cz aplikacji internetowej
moe obsugiwa wyjcie do innego formatu bez koniecznoci modyfikacji logiki
poszczeglnych komponentw. Klasa View implementuje metody abstrakcyjne
wykorzystywane w klasie RegistrationController zaprezentowanej w poprzednim punkcie:
class View {
public static $METHOD_KEY = 'm
ethod';
public static $METHOD_JSON = 'json';
public static $METHOD_XHTML = 'xhtml';
public static $METHOD_XML = 'xml';
protected static $loaded = array();

270

Rozdzia 7. Architektura aplikacji po stronie serwera

RYSUNEK 7.1. Diagram przepywu danych dla wzorca Fabryka przypisanego do mechanizmw
obsugi szablonw

Wykorzystanie wzorca Fabryka wraz z mechanizmem obsugi szablonw

271

/**
* Abstrakcja w celu pobrania klucza mechanizmu renderowania z dania.
*/
public static function getMethodFromRequest($request) {
return (isset($request[self::METHOD_KEY]))
? $request[self::METHOD_KEY] : self::METHOD_XHTML;
}
/**
* Zwraca egzemplarz mechanizmu renderowania dla okrelonego klucza.
* Domylnym formatem jest XHTML, jeli dany format nie jest obsugiwany.
*/
public static function getRenderingEngine($method) {
global $views;
if (self::loadRenderingEngine($method)) {
return new $views[$method]['class']($request);
} else if (self::loadRenderingEngine(self::$METHOD_XHTML)) {
return new $views[self::$METHOD_XHTML]['class']($request);
}
throw new Exception('Nie udao si zaadowa mechanizmu renderowania');
}
/**
* aduje waciwy mechanizm renderowania dla podanego klucza.
*/
protected static function loadRenderingEngine($key) {
global $views;
// Czy prba adowania nastpia wczeniej?
if (isset(self::$loaded[$key])) {
return self::$loaded[$key];
}
// W innym przypadku sprawdzenie obecnoci pliku.
if (isset($views[$key])) {
$path = 'views' . DIRECTORY_SEPARATOR
. $views[$key]['filename'] . '.php';
// Wczenie pliku, jeli jest dostpny.
if (file_exists($path)
&& is_readable($path) && include $path) {
// Ustawienie flagi wskazujcej na powodzenie operacji
// w celu uniknicia powielania tych samych informacji przy nastpnym daniu
// dla tego samego widoku.
return self::$loaded[$key]
= class_exists($views[$key]['class']);
}
}

272

Rozdzia 7. Architektura aplikacji po stronie serwera

// Nie udao si znale mechanizmu renderowania ani si do niego odwoa zwrcenie


// wartoci false.
return false;
}
}

Powysza implementacja wzorca Fabryka oferuje tylko dwie metody w ramach


publicznego interfejsu obiektu, trzecia metoda (chroniona) wystpuje jedynie
w celu uniknicia dublowania kodu w metodzie View::getRenderingEngine().
Kady kontroler moe obsugiwa wiele formatw wyniku poprzez wywoanie
View::getRenderingEngine(View::getMethodFromRequest($this->raw_get));. Instrukcja
ta powoduje utworzenie egzemplarza wymaganego mechanizmu renderowania.
Kady mechanizm renderowania jest rozszerzeniem klasy RenderingEngine
zaprezentowanej w poprzednim punkcie. Klasa XHTMLRenderingEngine, zamieszczona poniej, rozszerza klas RenderingEngine, ktra zaimplementowaa oglne
zmienne obiektw oraz metody wykorzystywane przez wszystkie mechanizmy renderowania w tej aplikacji. W mechanizmie renderowania specyficznym dla formatu
XHTML pozostao do zaimplementowania tylko to, co jest dla niego specyficzne:
class XHTMLRenderingEngine extends RenderingEngine {
protected $headers = array(
'Content-Type' => 'application/xml+xhtml'
);
/**
* Metoda suca do przesyania formatu text/html do przegldarek, ktre nie obsuguj XHTML.
*/
protected function sendHeaders() {
global $controller;
$accept = $controller->getHeader('Accept');
if (!$accept
|| strpos($accept, $this->headers['Content-Type'])) {
$this->headers['Content-Type'] = 'text/html';
}
return parent::sendHeaders();
}
/**
* Krtszy sposb unieszkodliwiania encji XHTML.
*/
public function escape($string) {
return Utilities::escapeXMLEntities($string);
}
}

Wykorzystanie wzorca Fabryka wraz z mechanizmem obsugi szablonw

273

Powysza klasa mogaby rwnie implementowa metody generowania znacznikw html i body, a take bloku head wykorzystywanego przez wszystkie szablony
XHTML. Pozwolioby to na uniknicie dublowania znacznikw w kadym z nich.
Klasa RenderingEngine dla formatw RSS lub Atom mogaby zawiera metody formatowania znacznikw czasu w sposb wymagany przez kad z tych specyfikacji.
Poniewa klasa-fabryka RenderingEngine obsuguje dostpne w tablicy mechanizmy renderowania, aplikacja moe z atwoci obsugiwa niestandardowy format
dla wybranego kontrolera wystarczy, aby po utworzeniu egzemplarza tego kontrolera przez obiekt CentralController zosta wpisany na list obiekt RenderingEngine
tego kontrolera. Dziki temu kontroler obsugi raportw moe wykorzystywa ten
sam mechanizm szablonw co pozostaa cz aplikacji w celu generowania wyjcia bezporednio w formacie Microsoft Excel, PDF, SVG lub innych dowolnych
formatach alternatywnych, ktre nie s wymagane w innych czciach aplikacji.

You might also like