Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [MVP] Przepływ danych, liczba klas
Forum PHP.pl > Forum > PHP
WebCM
Piszę aplikację internetową obiektowo w Model View Presenter. Prezenter pobiera dane z modelu i przekazuje do szablonu. Nie korzystam z gotowej platformy ani ORM. Struktura jest następująca:
  1. Strona dla użytkowników (frontend)
    • Logowanie - jeśli jest niezalogowany, musi zalogować się (jak na Naszej Klasie)
    • Zobacz listę ankiet przeznaczonych dla ciebie (wybranych przez admina).
    • Wypełnij ankietę - czyli formularz.
    • Zakończ wypełnianie (w międzyczasie odpowiedzi mogą iść na serwer AJAX-em)
  2. Panel admina - konta adminów niezależne od użytkowników
    • Lista ankiet
    • Dodaj/edytuj/usuń ankietę
    • Zapisz pytanie ankiety (AJAX)
    • Dodaj/edytuj/usuń użytkownika
    • Opcje i inne podstrony

Aplikacja wykorzysta następujące komponenty:
  1. PDO dla MySQL
  2. Klasę do wyświetlania szablonów w formacie XSL
  3. Inne klasy - może do autoryzacji i innych celów

Powstają pytania:
  1. Jak rozwiązać podział na panel admina (backend) i stronę dla użytkowników (frontend)?
  2. Ile powinno być prezenterów? Czy 1 podstrona (np. edytuj ankietę) to osobny prezenter, np. PokazAnkietePrezenter, FormAnkietyPrezenter, ListaUzytkownikowPrezenter, WypelnijPrezenter gdzie akcje zależą od danych GET/POST? Czy stworzyć mniej, ale bardziej ogólnych prezenterów, np. AnkietaPrezenter z metodami wypelnij(), zakoncz(); w panelu admina: AnkietaPresenter z metodami dodaj(), edytuj(), pokaz(), usun(), pokazListe()... Akcje zależą od URL i danych GET/POST.
  3. Ile modeli? Czy 1 tabela w bazie = 1 model? To niewygodne, bo często trzeba łączyć kilka tabel na raz.
  4. Jak powinien wyglądać idealny model? Pola w tabeli = pola w klasie? W złączeniach tabel pojawią się pola z wielu tabel. Na pewno łatwiej pobrać dane do tablicy w obiekcie lub po prostu zwrócić prezenterowi.
  5. Jak prawidłowo powinny przepływać dane w aplikacji o takiej strukturze? Szukam dobrych przykładów.
  6. Jak rozwiązać problem zmiennych globalnych? Wzorzec Rejestr? A może tak przekazywać obiekty między warstwami tak, aby nie używać rejestrów ani singletonów? Przykłady: system szablonów, baza, autoryzacja - one są tworzone raz w index.php. **
  7. Wykrywanie błędów w formularzu - należy do prezentera czy modelu? Niby model powinien dbać o poprawność danych, ale na błędy powinien reagować prezenter.
  8. Obsługa błędów typu "ankieta nie istnieje" - metoda w modelu ma zwracać false, null, pustą tablicę czy wyrzucić wyjątek, np. DoNotExistException?

** Czy to MVC, czy MVP, wstrzykiwanie zależności do modelu nie jest najlepszym wyjściem. Przykład: baza danych. Kontroler i widok mają nie wiedzieć o bazie, a trzeba by na początku im wstrzyknąć obiekt PDO. Jeśli zastosujemy wzorzec Rejestr, uzależniamy modele od konkretnych rozwiązań - zakładamy, że klasa Registry istnieje i działa tak, a nie inaczej. Tak samo klasa nie musi udostępniać Singletonu (zakładamy wykorzystanie czystego PDO).
marcio
No to moze ja powiem jak to robie u siebie tez na postawie MVP.

1.)
Ja mam aplikacje podzielona na 4 czesci:
Frontcontroller czyli ten ktory odpala wszystkie inne klocki i pilnuje autoryzacje czy dany uzytkownik ma dostep do danej podstrony.Oczywiscie frontcontroller jest odpalany za pomoca dispatcher-a ktory wszysko robi na podstawie danych z router-a.
No wlasnie u mnie jest to zrobione tak ze router ustala czy mamy doczynienie z frontend-em lub backend-em za pomoca przestrzeni nazw z url.
Czyli jestli nasz url wyglada tak:
Cytat
index.php/100

To za pomoca regul wiem ze jest to url ktory dotyczny frontend-u i odwoluje sie do komponentu News i do akcji read a "100" to parametr(w tym przypadku jest to regula gdy jej nie ma oryginalny url powinien wygladac tak)
Cytat
index.php/Home,Index,read,100

Frontcotrller: Home
Akcja frontcontroller-a: Index
Akcja komponentow: default-owo Index() jako ze na naszej podstronie jest modul News ktory posiada akcje read to odpala ja a pozostale odpalaja akcje Index()

Przykladowy adres do czesci administracyjnej komponentu News wyglada tak:
Cytat
index.php/Admin,News,editNews,651

Czyli router wskazuje nam ze znajdujemy sie w przestrzeni Admin wiec odpalany jest controller Vf_News_Admin zamiast Vf_News

2.)
To zalezy od ciebie ogolnie sa 2 praktyki:
1. Albo kazdy prezenter(controller/action zalezy jak ktos to zwie) odwoluje sie tylko do jednej akcji
2. Albo kazdy prezenter(controller/action zalezy jak ktos to zwie) zajmuje sie akcjami calego modolu/komponentu

Z tym ze potem ad 2 mozesz podzielic sobie tak jak pisalem w pierwszej czesci prezenter na 2 oddzielne czyli ten odpowiadajacy za frontend i drugi za backend danego modolu.

3.)
U mnie kazdy frontcontroller/komponent/plugin/widget ma swoj model (przewaznie 1) zalezy od uzycia dzialaja one jak orm(ubogi i do przepisania) lub jako zwykly model ktory udostepnia dane metody za pomoca ktorych mozna np pobrac ostatnie 10 news-ow

4.)
Sam nie wiem nie ma takiego czegos jak idealny model kazdemu koderowi bedzie pasowal model na swoj stroj.
Jesli uzywasz ideologii orm-a to w atrybutach klasy mozna opisac wszelkie relacje pomiedzy modelami, a jak nie to masz zwykly model ktore dziedziczy po jakies klasie do obslugi bazy danych z jakism tam api i na jego podstawie robisz co chcesz.

5.)
Tzn jakies?Takiej ktora dziala na przykladzie wzorca MVP?Ano normalnie jak w mvc z tym wyjatkiem ze to nie widok pobiera dane z modelu a kontroler(prezenter) ktory potem te dane "wsadza" w widok.
Powiem szczerze nie trac czasu na uzyskanie 100%-owego MVP bo stracisz wiecej czasu na to niz na napisanie aplikacji wink.gif
Poczytaj jak to ogolnie dziala i potem zrob tak jak tobie bedzie wygodnie.

6.)
Nie jestem jakims pro ale tam gdzie potrzebuje mam zawsze metode ktora udostepnia tworzenie obiektu na podstawie singleton-a lub factory method.
Co do zmiennych globalnych po co ci one?Ja ich nie uzywam....sciezki trzymam w stalych i sa dostepne w calej aplikacji
Reszta jest w konfiguracji poszczegolnych elementow

7.)
Zalezy z jakiego punktu by to patrzec.
W teorii walidacja powinna znajdowac sie w modelu ewentualne bledy zwracamy do kontrolera(prezentera) i potem je wyswietlamy.
Z praktycznego punktu widzenia u mnie(i nie tylko) ta walidacja odbywa sie w kontrolerze bo poprostu tak jest szybciej, latwiej i jest mniej kombinowania choc nie jest to rewelacyjne podejscie, w mojej aplikacji model jest powiazany strict z baza danych(ewentualnie z innym zrodlem danych ktore oferuja wlasnie dane nic wiecej).

W takich framework-ach jakis Symfony2 walidacja odbywa sie na poziomie modelu(a tak dokladnie w klasach ktory z modelem wspolracuja jesli mnie pamiec nie myli) jednak jest to rozbudowany fw w aplikacjach ktore pisze sie samemu szkoda zachodu o to.

8.)
I jak zawsze dobra pratyka jest uzywanie wyjatkow.
U mnie mam ogolnie kilka glownych wyjatkow i tyle zamiast mase roznych wyjatkow.
Biblioteki stosuja swoje wyjatki ale juz np komponenty moga mi zwrocic 2 wyjatki:
Volta_Acl_Deny_Exception: czyli ogolnie brak dostepu do komponentu lub jego czesci(np jakies akcji)
Vf_Component_Exception: ogolny wyjatek ktory informuje o braku news-a o danym id lub cos w tym stylu

Jesli np zmienimy postac url-a to wtedy router nic nie znajdzie zatem dispatcher zwraca blad 404

To taka moja subiektywna odpowiedz mniej lub bardziej pasujaca do rzeczywistosci ;]

Pozdro.
WebCM
Jeszcze pytanie odnośnie implementacji samego modelu. Raz trzeba pobrać użytkownika po ID, innym razem po loginie i haśle. Lista użytkowników - wszystkich lub należących do wybranej grupy lub wg innych kryteriów. Jeśli wprowadzimy stronicowanie, musimy uwzględnić LIMIT x,y. Jest kilka możliwości:
  1. <?php
  2. class UserModel extends Model
  3. {
  4. protected $login; // Opcja nr 1 - dane w polach klasy
  5. protected $id;
  6. protected $password; //w postaci jawnej lub zaszyfrowanej - zależy kto szyfruje - PHP czy baza
  7. protected $osobie;
  8. protected $grupa; //obiekt lub ID
  9.  
  10. protected $data; // Opcja nr 2 - tablica
  11.  
  12. public function getUser() // Opcja nr 3 - jedna uniwersalna metoda do pobierania danych
  13. {
  14. //Badamy wszystkie pola obiektu - jeśli pole nie jest puste, to używamy go w zapytaniu
  15. //Parametry przekazujemy jako argumenty lub ustawiamy metodami set
  16. //Dane zwracamy w tablicy albo pobieramy metodami get
  17. }
  18.  
  19. public function authenticate($login,$pass) {} // Opcja nr 4 - tworzymy wiele metod pod konkretne potrzeby
  20. public function getUserById($id) {}
  21.  
  22. public function listUsers($limit,$page) { }
  23. public function listUsersByGroup($group) { }
  24.  
  25. public function insert($data) {} // Albo podajemy dane w tablicy (1), albo ustawiamy metodami set i metoda insert() nie ma argumentów (2)
  26. public function update() {}
  27. public function delete() {} //ID jako parametr czy setID()?
  28. public function setLogin() {}
  29. public function setName() {}
  30. public function setSurname() {} // Opcja nr 5 - metody get i set
  31. public function getID() {}
  32. public function getName() {}
  33. public function getSurname() {}
  34. }
aras785
a gdzie pytanie?
WebCM
Podstawowe pytanie - czym jest model? Są 2 opcje:

1. Reprezentacja obiektu np. użytkownik
Tak jak w świecie rzeczywistym - użytkownik/news/ankieta ma pewne cechy, można wykonać pewne operacje - oprócz bazodanowych zapisz() i wczytaj() znajdzie się wiele innych związanych bezpośrednio z gatunkiem obiektu. Każda ankieta = osobna instancja klasy.
  1. class User extends Model
  2. {
  3. protected $id;
  4. protected $login;
  5. protected $password;
  6. protected $about;
  7. public function __construct($id, $pass) {}
  8. public function load() {}
  9. public function save() {}
  10. public function getLogin() {}
  11. public function setLogin() {} // metody set/get dla każdej właściwości
  12. }
  13.  
  14. //Przykładowe wywołanie
  15. $user = new User('login', 'haslo'); //automatycznie pobiera z bazy
  16. $about = $user->getAbout();
  17.  
  18. $user2 = new User;
  19. $user2->setLogin('login');
  20. $user2->setPassword('haslo');
  21. $user2->save();

2. Pośrednik operacji pobierz/dodaj/usuń/edytuj - warstwa danych
Model nie reprezentuje obiektu, lecz stanowi warstwę dostępu do danych, np. użytkownika. Tworzymy tylko jedną instancję klasy.
  1. class UserModel extends Model
  2. {
  3. public function getById($id) { } // zwraca tablicę z danymi użytkownika
  4. public function authenticate($login, $pass) { } // zwraca true lub false, ewentualnie tablicę z danymi
  5. public function insert($data) {}
  6. public function update($id,$data) {}
  7. public function delete($id) {}
  8. }
  9.  
  10. //Przykładowe użycie
  11. $model = new UserModel;
  12. $data = $model->getById(5);
  13.  
  14. $model->update(44, $nowe_dane);

Co o tym myślicie? Która opcja jest właściwą implementacją modelu? Zakładamy, że nie używamy ORM.
marcio
Wedlug mnie obydwie bo daza do tego samego celu czyli operowania na jakiejs porcji danych z jakiegos zrodla.
W jaki sposob bedzie to robic klasa modelu to juz zalezy od ciebie.
destroyerr
@WebCM to ja mam teraz pytanie: podajesz dwa przykłady modelu i próbujesz każdy z nich ograniczyć do jednej klasy. Dlaczego uważasz, że "jeden model (i próbujesz to kierować na tabelę bazy danych) to jedna klasa"? Model to jest warstwa dla całej aplikacji. Poszukaj i poczytaj trochę o projektowaniu modelu, pytasz o podstawowy podział: Encja (logika) i Repozytorium (utrwalanie).
WebCM
@destroyerr: Nie wykluczam wielu klas. W platformach modele są różnie zaimplementowane. Większość oferuje automatyczne tworzenie modeli. Często stosuje się podział na klasy serwisowe (UserService), reprezentacje obiektu (UserTO) a jeśli nie korzystamy z ORM - warstwy dostępu do danych (UserDAO). Może to wyglądać tak:
  1. // w kontrolerze
  2. $service = new UserService;
  3. $user = $service->load($userID); //zwraca obiekt typu UserTO
  4. $this->view->add('szablon', $user); //jeśli UserTO ma pola publiczne, a jak ma prywatne to można przejechać Reflection
  5.  
  6. $user2 = new UserTO;
  7. $user2->setLogin('login');
  8. $service->save($user2); //dodawanie użytkownika
  9.  
  10. //w klasie serwisowej - metoda UserService::load()
  11. $dao = new UserDAO;
  12. return $dao->select($userID); //zwraca obiekt typu UserTO
  13.  
  14. //w klasie dostępu do danych
  15. return $pdo->query('...')->fetch(PDO::FETCH_CLASS, 'UserTO');

Czy jest sens tworzyć tyle podziałów w prostych aplikacjach? W platformach jest tak:

Model w Symfony - obiekt User ma metody set/get, umożliwia zapis do bazy metodą save()
Przykładowe modele w Trinity - modelem może być dowolna klasa - jak zaimplementujemy zależy od nas
Model w Kohanie - klasa User_Model przypomina klasę serwisową, metoda get_user() zwraca obiekt
Model w Zend Framework - pośrednik Mapper zwraca obiekt reprezentujący księgę gości z metodami set/get
Model w Yii - mamy 2 rodzaje modelu, oprócz tego warstwę Active Record

Najprostsza implementacja modelu opisana w artykule:

Poeksperymentujmy z MVC

Tutaj zwracamy po prostu tablicę z klasy modelu. Podobnie może wyglądać zapisywanie:
  1. public function getById($id) {} //zwraca tablicę
  2. public function insert($data) {}
  3. public function update($data) {} //można zastąpić jedną metodą save()

Nic nie stoi na przeszkodzie, aby zwracać obiekt zamiast tablicę lub skorzystać z ORM, co w konsekwencji też zwróci obiekt.

Teraz załóżmy taką sytuację - mamy ankietę, musimy pobrać pytania i odpowiedzi. Musimy stworzyć taką strukturę tablic lub obiektów, żeby ułatwić zadanie szablonom, czyli w postaci:
Kod
Ankieta
---> Pytanie 1
-------> Odpowiedź 1
-------> Odpowiedź 2
Jeśli dostęp do danych będzie możliwy wyłącznie przy pomocy get/set, pozostaje nam to zrobić w kontrolerze. Ewentualnie warstwie modelu pośredniczącej (wtedy też dostaniemy tablicę). Można tak zaprojektować system szablonów, aby przyjmował obiekty zamiast tablice i w kontrolerze już nic nie trzeba przepisywać, o ile sytuacja nie wymaga.

Która implementacja modelu jest najlepsza, co zwracać, jakie warstwy zastosować to kwestia dyskusyjna. Może znacie jeszcze lepsze przykłady?
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-2025 Invision Power Services, Inc.