Professional Documents
Culture Documents
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?
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
Spis treci
Spis treci
Spis treci
10
Spis treci
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
244
269
239
240
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.
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
extends DBO {
*/
function set($field, $value) {
($field == 'password') {
return parent::set(
$field,
Utilities::hashWithSalt($value)
);
} else {
return parent::set($field, $value);
}
}
/* ... */
}
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
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.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.
245
246
*/
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.
*/
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
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);
$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
}
}
/**
* 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
251
252
/**
* 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.
*/
253
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
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.
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
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;
}
}
258
/**
* 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);
}
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
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).
*/
261
262
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
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
265
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
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"></
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
} else {
include 'step1.php';
}
?>
</div>
</body>
</html>
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.
270
RYSUNEK 7.1. Diagram przepywu danych dla wzorca Fabryka przypisanego do mechanizmw
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
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.