Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Widok MVP
Forum PHP.pl > Forum > PHP > Object-oriented programming
marcio
Hej doszedlem do wniosku ze w moim fw widok, ogolnie warstwa prezentacji jako szablon to za malo, w sensie gdy zajdzie potrzeba na wyswietlenie innego fotmatu pdf,xml,text,csv czy json bedzie lipa i albo nie da rady albo bede musial kombinowac w kontrolerze i robic rzeczy ktorych nie powinno sie robic.

Wiec postanowilem poszerzyc fw o nowa funkcjonalnosc, oprocz widokow jako szablony mozna uzywac klasy.

Jednak nie wiem zabardzo jak to najlepiej rozwiazac do tej pory doszedlem do 2 sposobow:
-Osobna klasa widoku na kazdy format czyli News_View_Html,News_View_Xml itp...itd...
-Jedna ogolna klasa News_View implementujaca interfejs IView ktory ma metody: renderHtml,renderXml,renderJson itp..itd

Moze pokaze pseudo kod to bedzie bardziej wiadomo o co mi chodzi:
  1. interface IView {
  2.  
  3. public function render();
  4.  
  5. }
  6.  
  7. //lub
  8.  
  9. interface IView {
  10.  
  11. public function renderHtml();
  12. public function renderText();
  13. public function renderXml();
  14. public function renderJson();
  15. public function renderPdf();
  16.  
  17. }
  18.  

I potem reszta:
  1. <?php
  2.  
  3. class News_View_Html extends Html_View implements IView {
  4.  
  5. public function render() {
  6.  
  7. return $this -> Layout('news', 'components', 'admin');
  8.  
  9. }
  10.  
  11. }
  12.  
  13. class News_View_Xml extends Xml_View implements IView {
  14.  
  15. public function render() {
  16.  
  17. //implemenctacja dla xml'a
  18.  
  19. }
  20.  
  21. }
  22.  
  23. //lub jedna klasa z metodami dla kazdego typu widoku dziedziczaca po bazowej klasie view + interfejs
  24.  
  25. class News_View extends View implements IView {
  26.  
  27. //implementacja assign'ow
  28. public function setVars($vars) {
  29.  
  30. $this -> view -> AddVars($vars);
  31.  
  32. }
  33.  
  34.  
  35. public function renderHtml() {
  36.  
  37. $this -> view -> Layout('news', 'components', 'admin');
  38.  
  39. }
  40.  
  41.  
  42. public function renderXml() {
  43.  
  44. //implementacja dla xml'a
  45.  
  46. }
  47.  
  48.  
  49. //potem implementacja dla text,pdf i json jesli jest potrzeba jak nie to puste metody
  50.  
  51. }
  52.  
  53.  
  54. //klasa kontrolera
  55.  
  56. class News extends Controller implements IController {
  57.  
  58. public function Index() {
  59.  
  60. //pobieranie news'ow z modelu
  61. //ustawiane widoku dla url index.php/Home,Index,Index,html
  62.  
  63. $args = $this -> routing -> getParams();
  64.  
  65. switch($args) {
  66.  
  67. case 'html':
  68. //teraz tak albo osobna klasa dla kazdego widoku albo jedna klasa ze wszystkimi metodami
  69. $view = $this -> News_View_Html;
  70. $view -> AddVars($news);
  71. return $view -> render();
  72.  
  73. //lub
  74.  
  75. $view = this -> News_View;
  76. $view -> setVars($news);
  77. return $view -> renderHtml();
  78.  
  79. break;
  80.  
  81. case 'xml':
  82. //tutaj to samo tylko ze w formacie xml
  83. break;
  84.  
  85. }
  86.  
  87. }
  88.  
  89. }
  90. ?>

Kod pisany na szybko z palca wiec moze sa jakies bledy, to ma byc tylko taki ogolny zarys.

Chodzi o to ktore rozwiazanie jest lepsze, a moze zadne z nich i proponujecie inne...?
Crozin
Pomysł z renderXXX() jest kompletnie bez sensu... narzuca mi jakieś pierdoły w momenci gdy ich nie chcę (np. chcę udostępnić jedynie w formacie HTML/JSON, ale XML już nie chcę). W dodatku mija się to kompletnie z ideą interfejsów jak i jakąkolwiek wygodą użytkowania - dodaj sobie nowy format.
marcio
No to wtedy zostawiamy pista metode nie musimy wcale jej implementowac, ewentualnie moge rozbic kazdy format na 1 interfejs i implementowac te interfejsy ktore potrzebuje.

A mozesz rozwinac twoja odpowiedz bo nie rozumiem za bardzo...
Cytat
dodaj sobie nowy format.

questionmark.gif

Mam trzymac wszystko w osobnych klasach, czyli sposob nr 1?
Crozin
Cytat
No to wtedy zostawiamy pista metode nie musimy wcale jej implementowac
Tak, to jest bardzo dobry sposób - będziesz chciał dodać nowy format (np. INI) i co? Będziesz dodawał do każdej już istniejącej klasy nową, pustą metodę - by pasowało do interfejsu?
marcio
No fakt masz racje, nie wpadl bym na to.
To pozostac przy 1 sposobie, czyli kazdy typ = nowa klasa
proponujesz cos innego?
Lub moze rozbic to tak, wydaje sie wygodne:
  1. interface IView_Html {
  2.  
  3. public function renderHtml();
  4.  
  5. }
  6.  
  7. interface IView_Xml {
  8.  
  9. public function renderXml();
  10.  
  11. }
  12.  
  13. //itp...

Wtedy zostajemy przy 2 sposobie lekko zmodyfikowanym.
Nie bedzie pustych metod, latwo mozna dodac nowy format!
Crozin
Kombinujesz jak koń pod górę... Pierwszy sposób jest dobry - przecież te obiekty nie skończą się na jednej metodzie.
dariuszp
Jak na mój gust to i tak będziesz stosował określony format do konkretnych treści. Chyba że sobie to wszystko jakoś ustandaryzujesz. Nie wiem czy dobrym pomysłem nie było by wprowadzenie warstwy usług w kontrolerze. W końcu dane w określonym formacie które dostaniesz i tak będziesz musiał wg specyficznych zasad wygenerować (trzeba załadować inny szablon dla strony HTML, inny dla pliku XML a żaden np by zaserwować komuś pdf).

Jeżeli mam rację to powinieneś pomyśleć o wzorcu Service Layer. Tylko nie popełnij błędu mojego kolegi który tworzył właściwie 3 wersje kontrolera strony. Warstwa usług ma tylko przetworzyć otrzymane dane na określony format by je później zaprezentować użytkownikowi (jako plik do ściągnięcia, stronę do oglądnięcia itp). Jeżeli dobrze ją rozdzielisz to potem ktokolwiek (lub Ty sam) może szybko dopisać kolejne usługi (może SOAP ?, punkt dostępu AJAX ?) . Tylko gdy wyświetlasz treści na stronie to trzeba sprecyzować jaka usługa może być użyte (taką informację możesz przekazać w url, zapisać w module itp, w końcu np dany moduł może prezentować dane w json i html więc musi odrzucić prośbę o xml).
marcio
Cytat
Jak na mój gust to i tak będziesz stosował określony format do konkretnych treści. Chyba że sobie to wszystko jakoś ustandaryzujesz. Nie wiem czy dobrym pomysłem nie było by wprowadzenie warstwy usług w kontrolerze. W końcu dane w określonym formacie które dostaniesz i tak będziesz musiał wg specyficznych zasad wygenerować (trzeba załadować inny szablon dla strony HTML, inny dla pliku XML a żaden np by zaserwować komuś pdf).

To by robily klasy ktore podalem wyzej, a oczywiscie oczekiwany typ widoku bylby w url ;]

Moze rozwin odpowiedz, bo nie bardzo wiem co mi chcesz powiedziec ;p
dariuszp
Typ widoku można przekazywać na różne sposoby. Np mój kolega po fachu trzyma je w URL ale zawsze je maskuje poprzez mod_rewrite. Spotkałem się z przypadkiem gdzie korzystano ze zmiennych środowiskowych przez co po prostu dostawałeś parametr w tablicy superglobalnej $_SERVER. Metoda jest mało ważna.

I owszem. Chodzi mi o takie klasy jak podałeś wyżej. Teoretyzowanie mi nie wychodzi więc może podam jak ja to wykonałem u siebie.

Projekt który tworzę jest podzielony na moduły do tego stopnia że nie ma w nim nic co by było wpisane na sztywno. Nie licząc domyślnego moduły. Jego obecność jest obowiązkowa. Domyślny moduł jest widokiem panelu administracyjnego. Każdy moduł ma część administracyjną i publiczną. Na jednej stronie może być kilka modułów. Moduły mogą być umieszczane na różnych stronach. Każda "instancja" modułu na stronie ma własną konfigurację.

Przy realizacji tego natrafiłem na parę problemów. Czasami musiałem uzyskać jakieś dane poprzez AJAX. Problem leżał tu że... nie za bardzo było się gdzie po nie odwołać. Witryna przechodziła cały proces od zaczytania danych z określonych źródeł, przez wykonanie całej logiki, załadowanie szablonów i wyświetlenie tego. Musiałem pisać obejście specjalnie dla AJAX w tym procesie. W końcu chciałem by moduł zwrócił określone dane z pominięciem głównego szablonu strony.
Później doszła generacja plików XML. Oprócz XML zastosowałem również JSON. Krew mnie zalała jak na potrzeby projektu musiałem dorobić widok gdzie właściwie NIC nie było wyświetlane, po prostu output był wysyłany na zewnętrzny serwer poprzez socket.

Wtedy stwierdziłem że jak tak dalej pójdzie to wyłysieję, dostanę nerwicy i kto wie co jeszcze. W skrócie podobny problem co u Ciebie. Od kolegi zapożyczyłem rozwiązanie i zastosowałem Service Layer (jeżeli nie pomyliłem nazwy). Dlatego je zaproponowałem w Twoim wypadku.

Teraz moduł ma dopisaną listę takich usług jakie obsługuje. Jeżeli jest to JSON i XML to prosząc usługę AJAX dostaje po prostu 404. A działa to w ten sposób:

- ładowane są dane np z bazy danych
- moduł robi co trzeba i przygotowuje ArrayObject z tymi danymi. Stosuje określone nazewnictwo i zasady więc taka tablica zawsze ma podobną postać. Po prostu jej fragmenty mogą istnieć bądź nie.
- następnie wywoływana jest żądana usługa do której jest przekazywany ArrayObject.
- na wyjściu spodziewaj się danych w formacie XML, TXT, JSON lub cokolwiek sobie zaplanowałeś w usłudze.

Do usługi jest właściwie przekazywane końcowe sterowanie aplikacją. Jej wykonanie kończy wyświetlenie danych w jakiejkolwiek formie. Przykładowo usługa HTTP ładujesz szablon strony, wykonuje moduły a ich kod wynikowy umieszcza w odpowiednich miejscach na stronie.
Usługa AJAX ładuje zawsze pojedynczy moduł, przygotowuje dane i surowo je wyświetla tak byś mógł je odebrać ze skryptu.
Usługa JSON po prostu przerabia array object na json i wyświetla.
Tak samo z XML.
Usługa Socket odbiera od żądania login, hasło i adres serwera. Jak login i hasło się zgadzają to wysyła dane na zewnętrzny serwer.

Właściwie cokolwiek sobie wymyślisz, wystarczy stworzyć odpowiednią usługę i jazda. Później w jednym miejscu zmieniasz sposób postępowania dla XML, strony WWW czy ściągania plików. Bardzo przejrzysta i fajna metoda.
marcio
Cytat
Projekt który tworzę jest podzielony na moduły do tego stopnia że nie ma w nim nic co by było wpisane na sztywno. Nie licząc domyślnego moduły. Jego obecność jest obowiązkowa. Domyślny moduł jest widokiem panelu administracyjnego. Każdy moduł ma część administracyjną i publiczną. Na jednej stronie może być kilka modułów. Moduły mogą być umieszczane na różnych stronach. Każda "instancja" modułu na stronie ma własną konfigurację.

o0 to mamy prawie tak samo;] moze bylbys tak mily i wymienilibysmy sie kodem, albo jego czescia, no chyba ze to komercyjny projekt...

Co do reszty to juz rozumiem snitch.gif zrobie to na klasach i tyle winksmiley.jpg

Jednak chcialem zapytam o jeszcze jedna rzecz zwiazana tez troche z widokami, ogolnie chodzi mi o cache component.
Mam juz cache zapytan, jednak chcialem zrobic tez cache no calego komponentu news akcji show gdzie jako tako news'y sa dodawane rzadko.

Jednak nie wiem gdzie to zaimplementowac ;p, w sensie na poziomie klasy View zaimplementowac cache interpretowanych szablonow(koncowych widokow), na poziome klasy wczytujacej komponenty i pluginu ktora poprostu jesli by znalazla w juz gotowy widok nie inicjowalaby nawet klasy komponentu tylko zwracala widok, czy moze poprostu w kontrolerze w akcji robic cos takiego:
  1. class News extends Controller {
  2.  
  3. public function Show() {
  4.  
  5. if($this -> cache -> cacheExists('NewsShow')) {
  6.  
  7. //wczytujemy cache
  8.  
  9. }
  10.  
  11. else {
  12.  
  13. //generujemy widok normalnie snitch.gif
  14.  
  15. }
  16.  
  17. }
  18.  
  19. }

Crozin
Ten ostatni sposób jest: do pochlastania się smile.gif

Ciężko określić jednoznacznie, jednak prawdopodobnie gdzieś przed wywołaniem samego komponentu (np. tuż przed fragmentem odpowiadającym za odpalenie komponentu) - dzięki temu powinieneś mieć możliwość utworzenia w miarę prostego interfejsu, niezaśmieconymi pobocznymi (np. cache) pierdołami.

Jednak: TO ZALEŻY
marcio
Hmmm czyli albo w klasie systemu szablonow zrobic tak ze przed interpretowaniem sprawdzic czy istnieje komponent w cache jesli to tak to go zwracamy.
Jeszcze lepiej jednak byloby to zrobic w klasie Plugin ktora odpala komponenty/pluginy i ich dana akcje wtedy sprawdzalbym czy cache istnieje i nie byloby problemu, nawet obiekt komponentu wtedy nie bylby inicjalizowany, duzo bym zaoszczedzil ;p

p.s czemu ostatnia metoda jest zla?
phpion
Ja bym wykorzystał takie rozwiązanie:
  1. class News {
  2. public $id;
  3. public $title;
  4. }
  5.  
  6. abstract class News_Renderer {
  7. public $news;
  8.  
  9. abstract public function render();
  10. }
  11.  
  12. class News_Renderer_HTML extends News_Renderer {
  13. public function render() {
  14. return '<h3>'.$this->news->title.' (id='.$this->news->id.')</h3>';
  15. }
  16. }
  17.  
  18. class News_Renderer_CSV extends News_Renderer {
  19. public function render() {
  20. return $this->news->title.';'.$this->news->id
  21. }
  22. }

W kodzie wykorzystujesz to tak:
  1. $news = new News();
  2. $news->id = 1;
  3. $news->title = 'Moj news';
  4.  
  5. $renderer = new News_Renderer_HTML();
  6. $renderer->news = $news;
  7.  
  8. echo $renderer->render();

Jeśli wolisz możesz przekazywać obiekt $news to metody render() jako parametr. Jak Ci wygodniej.
zegarek84
od siebie zaproponował bym rozwiązanie podobne [lub takie samo] @dariuszp i @phpion, z tym, że szablony dla formatów tekstowych przechowywałbym w plikach... obiekt renderujący widok formatów tekstowych zaopatrzył w metodę zbliżoną do:
  1. private function getWynik($file) {
  2. include $file;
  3. $this->wynik = ob_get_contents();
  4. return $this->wynik;
  5. }
tak - akurat tutaj prywatną jeśli stosuje się "podszablony" - więc może być publiczna - inna render() powinna odpowiadać za całokształt... - i tutaj przechwytywanie kontekstu...

plik inkludowany z kolei w postaci np:
Kod
<?php /* @var $this Html */ //to dla podpowiadania składni ?>
ble ble <?php echo this->var('zmienna');?>

includowany plik ma dostęp do metod obiektu - dzięki czemu można sprawdzać, czy zmienna istnieje i ją zwrócić, zwrócić null lub gdzieś komunikat przechwycić czy cokolwiek - zależy jak ta metoda jest napisana... [fakt - można tutaj pobierać bezpośrednio zmienne a w razie ich braku mieć w obiekcie gettera - jak kto woli]

oczywiście wysłanie odpowiedniego header'a w zależności od implementacji klasy... inny format np. pdf -> metody do renderowania te same ale oczywiście klasa inaczej zbudowana...

przy niekonwencjonalnych widokach dla formatów tekstowych nic nie stoi na przeszkodzie by dopisać kilka funkcji wewnątrz - zwłaszcza w php 5.3 te anonimowe funkcje są dosyć wygodne ;]
dariuszp
Akurat to co tworze ma charakter komercyjny i nie mogę się podzielić.

Idea jest prosta. W module ma być zaszyta informacja a jakiej postaci potrafi on zwrócić dane. Jeżeli to HTML to powinien on mieć dostarczony szablon do uzupełnienia a później zwrócić kod wynikowy. Jeżeli to JSON, powinien on zebrać dane do tablicy, zakodować do formatu i wyświetlić itp.
Sam dla przykładu by nie tworzyć 10 różnych metod (i trzymać interfejs w jak najprostszej postaci) mam metody które reprezentują jakieś akcje w systemie ale... mogą zwrócić zupełnie inne dane.
Dla modułu jest ustawiany tym zwracanych danych a dalej program działa jak by nigdy nic.

Teraz pytanie - dlaczego warstwa usług ? Oto kilka przypadków które mi ona rozwiązała:

1. (ServiceHTML) Ładując stronę, mam na niej kilka modułów do wyświetlenia. Usługa musi załadować je wszystkie, ustawić ich konfigurację i wykonać. Następnie uzupełnia szablon o ich kod wynikowy.
2. (ServicePOPUP) W momencie gdy np chcę wyświetlić 1 moduł w oknie pop-up, w lightbox lub w jakikolwiek inny sposób, taki moduł powinien mieć załadowany podstawowy szablon HTML (head + body itp) by działać poprawnie. Dodatkowo powinien być załadowany jeden moduł.
3. (ServiceAJAX) Potrzeba mi podmienić na stronie fragment kodu z modułu, muszę dostać surowy kod do podmiany bez dodatków. Czasami chodzi tylko o fragment tekstu albo informację przykładowo o nowych wiadomościach.
4. (ServiceDOWNLOAD) Dane wynikowe muszę dać użytkownikowi do ściągnięcia, trzeba mu przesłać plik, odpowiednie nagłówki itp. Dodatkowo czasami trzeba mu zablokować możliwość ściągania danego pliku.
5. (ServiceSOCKET) Muszę po prostu otworzyć połączenie poprzez socket z jakimś urządzeniem i przesłać dane wg jakiegoś protokołu.
6. (ServiceWAP) Muszę wyświetlić stronę dla urządzeń mobilnych gdzie potrzebny osobny szablon, obrazki niskiej rozdzielczości itp.
7. (ServiceJSON) Gdy dane muszą być zwrócone w formacie JSON.
8. (ServiceXML) Te same dane co poprzednio ale w formacie XML.

I tak się o nic nie martwię. Użytkownik klikając w link wybiera usługę, moduł i akcję jaką chce wykonać. Jeżeli moduł obsługuję daną usługę, ustawiam tym danych wynikowych dla modułu ( $module->setOutputType($service->getOutputType()); $service->run($module); ) to wywoływana jest akcja i dalej wszystko dzieje się w zależności jaka to była usługa. Jeżeli coś jest nie tak (nie ma akcji, nie ma modułu, moduł nie obsługuje danej usługi) to loguję taką informację że coś mogło pójść nie tak i wyświetlam stronę 404.

Jedyne co Ci potrzebne to mała standaryzacja. U mnie dla przykładu usługa HTTP i WAP działają podobnie. Różnica polega na tym że kod modułów jest ładowany w oddzielne szablony gdzie moduły w obu przypadkach zwracają to samo (nie licząc obrazków, jeżeli to możliwe to ładowane są te w niższej rozdzielczości). JSON i XML podobnie. Moduł zwraca tablicę asocjacyjną która jest konwertowana na jeden z formatów przez usługę. Download jest specyficzny. Można nim ograniczać lub zezwalać na ściąganie plików (rozszerzyłem serwer o znakomity moduł xsendfile). Ostatnio kolega podrzucił mi ciekawy pomysł który może przetestuję. Zastanawiam się czy by przypadkiem nie spróbować przepuścić przez PHP plików graficznych. Usługa została by poszerzona i np w wypadku gdy dane zdjęcie było ładowane z zewnętrznego serwera - zabraniał bym to robić bądź też dokładał do niego znak wodny. Taka opcja do włączenia.
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.