Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: O 3 warstwach słów kilka
Forum PHP.pl > Forum > PHP > Pro > Archiwum Pro
wwosik
Próbując zrozumieć model 3-warstwowy czy MVC doszedłem wreszcie do prostego rozwiązania, które mnie jako tako satysfakcjonuje. Niestety, źródła traktujące o aplikacjach wielowarstwowych w php są jakoś takie skąpe, zabrało mi to trochę czasu. Ale skoro już tę "wiedzę" zdobyłem postanowiłem się nią podzielić w formie krótkiego artykułu.

Ponieważ jednocześnie przymierzam się do ewentualnego napisania pracy licencjackiej na ten temat, będę wdzięczny za wszelkie krytyczne i pozytywne komentarze. Tekst na pewno nie jest wolny od błędów czy to merytorycznych czy edycyjnych, więc proszę mnie nie zjeżdżać z błotem.

Zaznaczam, że tekst jest napisany całkowicie samodzielnie. Można go używać jednak proszę raczej nie cytować (brakuje w nim np. przypisów, kto jest autorem danego wzorca projektowego, itd., itp., czyli formy). W każdym przypadku proszę jednak wspomnieć o niżej podpisanym.

Wojtek Wosik

PS Tekst nie ma na celu nikogo pouczać, czy wywyższać się. Napisany jest przez laika dla laików.
------------------------------


Ten artykuł jest przeznaczony dla zaawansowanych użytkowników php, znających zagadnienia obiektowości, a także system styli Smarty, którzy pragną zaznajomić się z wielowarstwowym modelem aplikacji webowej opartej na wzorcach projektowych.
Zostanie zaprezentowany bardzo prosty system CMS na wzór Wikipedii.

Wielowarstwowy model aplikacji
Typowe aplikacje php zawierają przemieszany kod HTML oraz php zawierający instrukcje połączenia z bazą danych, obróbkę danych i wreszcie ich wyświetlenie. Jest to racjonalne w przypadku bardzo prostych skryptów, jednak w miarę wzrostu aplikacji jej utrzymywanie, usuwanie błędów oraz rozbudowa stają się bardzo trudne. Dlatego zalecane jest rozdzielenie elementów odpowiadających za poszczególne czynności systemu.

W projekcie zastosowano 3-warstwowy model aplikacji, gdzie całość systemu została podzielona na obszar kontroli (przyjmowanie poleceń od użytkownika i sterowanie przebiegiem programu), prezentacji (wyświetlanie danych np. w postaci HTML) i danych (pozyskiwanie i zapisywanie informacji do bazy danych), przy czym elementy różnych obszarów powinny jak najrzadziej ze sobą współpracować.

Specyfika modelu webowego i php
Aplikacje oparte na HTTP działają odmiennie od typowych programów uruchamianych na lokalnym komputerze (w tym appletów Javy) lub w technologii server-client. Podstawową różnicą jest fakt, że aplikacja webowa wraz z każdym działaniem użytkownika uruchamiana na nowo. Następnie obsługuje ona to żądanie i kończy swoją aktywność. Nie jest zatem możliwy dialog między użytkownikiem a aplikacją. Drugim, niemniej ważnym problemem jest konieczność ponownej aktywacji połączeń z bazą danych, wczytywania konfiguracji, itd.

Oczywiście, pewnym obejściem wyżej wymienionych problemów są sesje, jednak inicjalizacja skryptu w php musi być minimalna - nie można pozwolić sobie na powolność appletów Javy.

Aplikacja CMS
Jak wspomniano, zaprezentowany system CMS będzie opierał się na modelu Wikipedii, czyli statycznych stron, które identyfikowane są przez ich tytuły. Każdą z takich stron użytkownik może obejrzeć lub edytować, podając jej tytuł lub klikając na odpowiedni link na innej stronie.

W implementacji zostanie zastosowany php w wersji 5.x, baza MySQL, standardowy HTML oraz CSS, a także system styli Smarty.

Inicjalizacja i obsługa polecenia
Przed obsłużeniem polecenia musi zostać wykonana inicjalizacja systemu, czyli wczytanie pakietów kodu składających się na system, nawiązanie połączenia z bazą danych oraz przygotowanie warstwy prezentacji poprzez podanie katalogu zawierającego style. Po tych wstępnych czynnościach można przystąpić do obsługi poleceń użytkownika.

Niektórzy autorzy zalecają, by dla każdej czynności powołać osobny plik .php, jednak ja postanowiłem pozostać przy wzorcu kontrolera Front Controller, czyli przy pliku index.php, który na podstawie parametru cmd podejmuje decyzję o wykonywanej akcji.. Akcje będą reprezentowane przy pomocy wzorca poleceń Command, czyli obiektów które na podstawie przekazanych im przez kontroler danych wykonują odpowiednie czynności i nakazują warstwie prezentacji ich wyświetlenie.

Obiekty Command mogą też tworzyć i wywoływać inne obiekty typu Command, jeśli konieczne jest wykonanie kolejnej czynności - jeśli np. po zapisaniu obiektu do bazy chcemy go wyświetlić. Dzięki temu, w skrypcie aktualizującym bazę danych nie trzeba powielać kodu odpowiedzialnego za wyświetlanie.

Schemat działania kontrolera wygląda następująco
1. Wczytywane są dane z wejścia,
2. na ich podstawie tworzone jest obiekt polecenia Command,
3. które jest następnie wykonywane, przejmując odpowiedzialność za resztę działania aplikacji.

Wczytywanie danych
Dane wejściowe w php są przechowywane w superglobalnych tablicach $_GET, $_POST, $_REQUEST, jednak w celu rozdzielenia warstwy danych od warstwy kontroli wprowadzony jest obiekt kontenera DataContainer. Takie podejście umożliwi np. stosowanie filtrów i zwiększy bezpieczeństwo systemu.

Obiekt DataContainer mógłby sam wczytywać dane z wejścia HTTP, jednak zgodnie
z zasadą rozdzielenia zadań, będzie, zgodnie ze swoją nazwą, tylko przechowywał dane. Jest to zatem swego rodzaju wrapper dla tablicy asocjacyjnej.

DataContainer
#variables : array()
dane
getValue(string $var) : mixed
zwraca wartość $variables[$var] lub false jeżeli variables[$var] nie istnieje
setValue(string $var, mixed $value)
ustawia wartość $variables[$var]
removeValue(string $var)
usuwa wartość $variables[$var]
getCopy() : DataContainer
zwraca kopię obiektu

W definicji obiektu DataContainer nie ma więc nic zaskakującego oprócz metody getCopy() zwracającej jego kopię. Wykorzystywane jest to, gdy polecenie wywołuje inne polecenie, jednak nie chce, by polecenie wywoływane zmieniło oryginalne dane.

Skoro istnieje obiekt przechowujący dane, należy go wypełnić danymi HTTP. W tym celu skonstruowany jest obiekt httpInputProcessor, który uzupełnia podany mu kontener.

httpInputProcessor
fill(DataContainer $dc)
wypełnia DataContainer danymi z wejścia HTTP

Tworzenie obiektu polecenia

Odpowiednie polecenie jest tworzone na podstawie parametru cmd przekazanego przez użytkownika. Procesem tym może zająć się sam kontroler, ale lepiej będzie wydelegować to zadanie do fabryki poleceń CommandFactory, zgodnie ze wzorcem Factory. Dzięki temu kontroler nie musi nic wiedzieć o poleceniu.

CommandFactory
getCommand(string $cmd) : Command
na podstawie $cmd zwraca odpowiednie polecenie

Jeżeli liczba poleceń byłaby duża, można zaimplementować system wczytujący odpowiedni plik w zależności od $cmd, jednak w tym przypadku wystarczy prosta instrukcja switch.

Wykonywanie polecenia
Skoro kontroler dysponuje już odpowiednim poleceniem nakazuje jego wykonanie przekazując mu dane pozyskane z wejścia. Obiektowi Command wystarczy więc jedna publiczna metoda execute.

Command
execute(DataContainer $dc)
wykonuje odpowiednie polecenie na podstawie danych zawartych w kontenerze

Konstrukcja kontrolera
Fabryka poleceń CommandFactory musi być dostępna kontrolerowi. Można ją uczynić Singletonem, jednak lepiej będzie ją po prostu przekazać kontrolerowi jako parametr. Kontroler wygląda więc następująco

httpController
__construct(CommandFactory $cf)
inicjalizuje kontroler przypisaną mu fabryką poleceń
run()
uruchamia przebieg programu

Wykonywanie polecenia
Każde polecenie dysponuje wiedzą przekazaną w kontenerze przez kontroler. Wykonanie zadań wymaga jeszcze dostępu do bazy danych oraz sposobu wyświetlenia swoich danych. Odpowiednie czynności będą wykonywane przez interfejsy do warstwy danych - klasa database, oraz do warstwy prezentacji - klasa viewLayer, które zostaną zdefiniowane później na podstawie potrzeb zgłaszanych przez polecenia.

Obsługa systemu będzie korzystała z trzech obiektów poleceń odpowiedzialnych odpowiednio za wyświetlenie, edycję oraz zapisanie strony do bazy. Ich zadania wyglądają następująco:

viewCommand (wyświetlanie strony)
1. pobierz stronę z bazy o zadanym tytule lub pokaż błąd
2. nakaż warstwie prezentacji wyświetlenie widoku strony i przekaż mu jej zawartość

editCommand (edycja strony)
1. pobierz stronę z bazy o zadanym tytule lub utwórz pustą stronę o tym tytule
2. nakaż warstwie prezentacji wyświetlenie widoku edycji strony
i przekaż mu jej zawartość

updateCommand (zapisz stronę)
1. pobierz stronę z bazy o zadanym tytule lub utwórz pustą stronę o tym tytule
2. ustaw parametry strony zgodnie z danymi przekazanymi w kontenerze
3. zapisz stronę do bazy
4. stwórz i uruchom polecenie viewCommand

Wyżej opisane polecenia potrzebują przede wszystkim obiektu reprezentującego stronę. Obiekt ten będzie zawierał tytuł strony i jej zawartość. Aby ułatwić współpracę z bazą danych dodatkowo zawarte będzie pole id, które jednoznacznie zidentyfikuje stronę w bazie.

Page
#id : integer
identyfikator
#content : string
zawartość strony
#title : string
tytuł
getter/setter accessors
metody pozyskujące i zmieniające powyższe wartości
getAsArray()
tworzy tablicę asocjacyjną z powyższymi wartościami

Funkcja getAsArray zwraca stronę w postaci tablicy asocjacyjnej. Tablica ta jest następnie przekazywana warstwie prezentacyjnej. W ten sposób, warstwa prezentacyjna nie musi nic widzieć o istnieniu obiektów klasy Page.

Dostęp do danych
Dostęp do bazy danych umożliwia obiektom Command interfejs do bazy danych - klasa database. Zgodnie z oczekiwania zawartymi w specyfikacji poleceń musi ona zawierać następujące metody publiczne:

database
getThisOrNewPage(string $title) : Page
zwraca stronę o podanym tytule - tę zawartą w bazie lub nową, pustą
getThisOrErrorPage(string $title) : Page
zwraca stronę o podanym tytule - tę zawartą w bazie lub informującą o błędzie
storePage(Page $page)
zapisuje stronę do bazy

Aby wykonać te polecenia klasa database będzie uzupełniona o kilka prywatnych metod:
#getPage(string $title) : Page
jeżeli strona o zadanym tytule jest zawarta w bazie zwraca ją, jeśli nie to wartość null
#makePageFromSQL(array $sqlData) : Page
tworzy nową stronę wypełniając ją danymi zawartymi w rekordzie $sqlData
#storeNewPage(Page $page)
zapisuje nową stronę do bazy - wywoływana przez storePage
#updatePage(Page $page)
aktualizuje stronę już zawartą w bazie - wywoływana przez storePage

Metody klasy database mogą samodzielnie wykonywać i obsługiwać odpowiednie polecenia SQL, jednak lepiej to zadanie zlecić specjalizowanej klasie, podanej jej przy inicjalizacji. database będzie zatem uzupełniona o jeszcze dwa składniki
init($db)
inicjalizuje bazę danych
static #db
przechowuje obiekt obsługujący zapytania do bazy

Obiekt db zawarty w klasie database może pochodzić np. z repozytorium PEAR, może być też samodzielnie napisany, ale musi przede wszystkim obsługiwać następujące polecenia:

db
query(string $string)
wykonuje zapytanie do bazy
fetchRow()
zwraca pobrany przez ostatnie zapytanie rekord danych w postaci tablicy nazwa pola => wartość pola

Należy zwrócić uwagę, że obiekt db wcale nie musi obsługiwać bazy danych typu MySQL, ale np. plik tekstowy lub XML, o ile tylko potrafi przetwarzać polecenia wydane w SQL.

Wyświetlanie
Jedynym interfejsem z systemu do warstwy prezentacji jest klasa viewLayer, która decyduje
o sposobie wyświetlenia danych. W najprostszym przypadku będzie opisana wzorcem Delegate zawierając obiekt odpowiedzialny za wyświetlanie danych i działając jako przekaźnik między nim a pozostałymi warstwami systemu

viewLayer
static #presenter
obiekt odpowiedzialny za wyświetlanie danych
init(presenter $presenter)
inicjalizacja warstwy prezentacji odpowiednim obiektem
display(string $cmd, array $content)
nakazuje obiektowi $presenter wyświetlenie zawartości $content według metody $cmd

Ponieważ do prezentacji zastosowane będą style Smarty potrzebny będzie obiekt SmartyPresenter spełniający rolę interfejs do obiektu Smarty.

SmartyPresenter
#Smarty
obiekt odpowiedzialny za wyświetlanie danych
setFolder(string $folder)
ustawia folder styli obiektu $Smarty na $folder, a także przypisuje zmiennej stylu templateFolder wartość $folder
display(string $cmd, array $content)
przekazuje obiektowi $Smarty zawartość tablicy $content, a następnie wywołuje jego metodę display z nazwą pliku w postaci $cmd.tpl

Przebieg programu
Przebieg programu jest więc następujący
1. Inicjalizacja klasy database skonfigurowanym obiektem db oraz klasy viewLayer skonfigurowanym obiektem SmartyPresenter
2. Wywołanie kontrolera httpController
a. Stworzenia kontenera danych DataContainer i wypełnienie go danymi HTTP przez httpInputProcessor
b. Wytworzenie przez fabrykę CommandFactory odpowiedniego polecenia Command
c. Przekazanie poleceniu Command danych zawartych w DataContainer
i wywołanie polecenia
3. Wykonanie poleceń Command
a. wykonanie zapytań do bazy danych poprzez interfejs database
b. wyświetlenie strony poprzez interfejs viewLayer lub utworzenie nowego polecenia Command i jego wywołanie

Podsumowanie
Nakreślony w artykule model programu nie jest oczywiście ani jedynym ani zwłaszcza idealnym wzorcem tego typu aplikacji. Niemniej, powinien ułatwić początek osobom próbującym pisać aplikacje klasy Enterprise.
ActivePlayer
1. smarty to nie system styli. czytam dalej...
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.