Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Zalążek frameworka z MVC
Forum PHP.pl > Forum > PHP > Pro > Archiwum Pro
Prph
Witam wszystkich,

Ostatnio zacząłem czytać o obiektowym php5, MVC, frameworkach i postanowiłem zacząć pisać kod, który byłby dla mnie wygodny oraz co ważne - nadawał się do powtórnego wykorzystania.

Opiszę to co do tej pory zrobiłem. Proszę Was, znaczniej bardziej doświadczonych programistów, o uwagi na temat mojego podejścia.

Zaznacze, że swoją pracę oparłem do tej pory na artykule Frameworki z php.pl, framework CakePHP oraz o wypowiedzi forumowiczach w topikach MVC.

Charakterystyka ogólna:
Drzewo projektu

Kod
|- actions/ (tu wrzucam kontrolery w postaci class.NazwaKontrolera.php)
|- conf/
|- models/ (tu będą modele, do tej pory nic tam nie mam)
|- lib/
|  +-- core/ - tu są moje pliki wykorzystywane w całej aplikacji
|        +-- class.Dispatcher.php
|        +-- class.Controller.php
|- templates/ - szablony Smarty  
|
|- index.php


Oczywiście nie jest to finalny widok drzewa projektu. Ale nie to jest ważne.

Jak działa mój framework?
index.php uruchamia Dispatcher, który parsuje URL. Jeżeli następuje zgłoszenie index.php/Akcja1/Parametr1/Parametr2/, to dispatcher załącza kontroler Akcja1, a następnie kontroler tej akcji wywołuje na sobie (w zasadzie na potomku) metodę Parametr1 z parametrem Parametr2.

Przykład: strona.pl/index.php/Uzytkownik/Pokaz/1234. Dispatcher przekaże kontrolę do kontrolera Uzytkownik. Ten natomiast włączy metodę Pokaz(1234);

Generalnie wygląda to właśnie tak. Z tego co czytałem, to chyba całkiem logiczne rozwiązanie. Oczywiście mogę się mylić...

Pliki
  1. Plik index.php
  2. <?php
  3. require_once('conf/conf.Core.php');
  4. require_once('lib/core/Logger/class.Logger.php'); // to nas nie interesuje - zwyklu silnik logowania
  5. require_once('lib/core/class.Dispatcher.php');
  6.  
  7. try
  8. {
  9. Logger::register('file:///tmp/log.core.txt');
  10.  
  11. $objDispatcher = new Dispatcher();
  12. $objDispatcher->handle();
  13. }
  14. catch (Exception $exception)
  15. {
  16. echo $exception->getMessage();
  17. }
  18.  
  19. ?>



  1.  Plik class.Dispatcher.php
  2.  
  3. <?php
  4.  
  5. require_once('lib/core/class.Controller.php');
  6. require_once('Smarty.class.php');
  7.  
  8. class Dispatcher
  9. {
  10.  
  11. private $_strUrlParameters; // zawiera parametry url, np: dla strona.pl/index/a/b/c/d zawiera a/b/c/d/
  12. private $_arrUrlParameters; // tutaj parametry juz są w tablicy 
  13.  
  14. public function __construct()
  15. {
  16. $strUrlParameters = $_SERVER['PATH_INFO'];
  17. $this->_strUrlParameters = substr($strUrlParameters, 1);
  18. }
  19.  
  20. public function handle()
  21. {
  22. $this->_parsePath(); // zapusuje parametry url do tablicy
  23.  
  24. $strControllerName = $this->_getControllerName(); // zwroci pierwszy elementy w tablicy parametrow (indeks w tablicy oczywiscie 0)
  25. //Następnie tworzy kontroler na podstawie nazwy akcji i go uruchamia
  26. $strControllerFile = 'actions/class.'.$this->_getControllerName().'Controller.php'; 
  27. if(file_exists($strControllerFile))
  28. {
  29. require_once($strControllerFile);
  30. $strControllerClasName = $this->_getControllerName().'Controller';
  31. $objController = new $strControllerClasName($this->_getParametersForController());
  32. $objController->run();
  33.  
  34. $objSmarty = new Smarty();
  35. $objSmarty->tempalte_dir = 'templates';
  36. $objSmarty->compile_dir = 'var';
  37.  
  38. $objSmarty->assign($objController->getModelData()); // kontroler powinien zwrocic dane dla Smarty.
  39. $objSmarty->display($objController->getView());
  40. }
  41. else
  42. {
  43. throw new Exception('Core, dispatcher: Action '.$strActionToRun.' does not exist');
  44. }
  45. }
  46.  
  47. private function _parsePath() // rozbija parametry url na tablice
  48. {
  49.  $arrParameters = explode('/', $this->_strUrlParameters);
  50.  if($arrParameters[count($arrParameters) - 1] == '')
  51.  array_pop($arrParameters);
  52.  
  53.  $this->_arrUrlParameters = $arrParameters;
  54. }
  55.  
  56. private function _getControllerName() // zwraca nazwe akcji ktora nalezy wykonac - tutaj to pierwsza pozycja w tablicy zawierajacej parametry url
  57. {
  58. $strControllerName = $this->_arrUrlParameters[0];
  59.  
  60. if(strlen($strControllerName))
  61. return $strControllerName;
  62. else
  63. throw new Exception('Core, dispatcher: There is not action to run');
  64. }
  65.  
  66. private function _getParametersForController()
  67. {
  68. $intNumOfPathParameters = count($this->_arrUrlParameters);
  69. $arrPrametersForController = array();
  70.  
  71. for($i = 1; $i < $intNumOfPathParameters; $i++)
  72. $arrPrametersForController[] = $this->_arrUrlParameters[$i];
  73.  
  74. return $arrPrametersForController;
  75. }
  76. }
  77.  
  78. ?>


  1. Plik class.Controller.php
  2. <?php
  3.  
  4. require_once('Collection/class.Collection.php');
  5.  
  6. class Controller
  7. {
  8. private $_strControllerName = null; // nazwa kontrolera - wyorzystuje sie do wybrania widoku i modelu
  9. private $_arrControllerParameters = array(); // parametry dla kontrolera. jezeli url wygladal nastepujaco: strona.pl/index.php/Uzytkownik/Pokaz/12345 to zawiera tablice [Pokaz][1234]
  10.  
  11. protected $_strPageName; // tytul strony (dla htmla)
  12.  
  13. private $_objModelsCollection; // kolekcja modeli, dziala jak tablica asocjacyjna - malo wazne
  14. private $_strView; // widok - tutaj to nazwa szablonu smarty
  15.  
  16. private $_arrViewVars = array();
  17.  
  18. public function __construct($arrParameters = null)
  19. {
  20. if(!isset($this->_strControllerName)) // pobiera nazwe swojej klasy - wykorzystuje sie to do wyboru widoku oraz modelu
  21. {
  22. $arrResults = array();
  23.  
  24. if(!preg_match('/(.*)Controller/i', get_class($this), $arrResults))
  25. {
  26. die('Controller: Can not get my class name');
  27. }
  28.  
  29. $this->_strControllerName = $arrResults[1];
  30. }
  31.  
  32. if(count($arrParameters))
  33. $this->_arrControllerParameters = $arrParameters;
  34.  
  35. $this->_objModelsCollection = new Collection();
  36.  
  37. $this->_objModelsCollection->addItem($this->_strControllerName.'Model');
  38. $this->_strView = $this->_toLowerCase($this->_strControllerName).'.tpl';
  39. }
  40.  
  41. public function getModelData() // zwraca tablice dla Smarty->assign()
  42. {
  43. if(count($this->_arrViewVars))
  44. return $this->_arrViewVars;
  45. }
  46.  
  47. public function getView() // zwraca szablon dla smarty->display();
  48. {
  49. return $this->_strView;
  50. }
  51.  
  52. public function run() // sedno kontrolera. Jezeli przekazano dodatkowy parametr w url, np: strona.pl/index.php/Uzytkownik/Pokaz to wywola sie funkcja Pokaz, jezeli nie wywola funckje domysla, ktora defi
    niuje juz potomek tej klasy
  53. {
  54. if($this->_getMethodToRun())
  55. {
  56. if(is_callable(array($this, $this->_getMethodToRun()), $this->_getParameterForMethod()))
  57. call_user_func_array(array($this, $this->_getMethodToRun()), $this->_getParameterForMethod());
  58. }
  59. else
  60. $this->runDefaultMethod();
  61. }
  62.  
  63. protected function _setPageTitle($strTitle)
  64. {
  65. $this->_strPageTitle = $strTitle;
  66. $this->_setVar('TITLE', $strTitle);
  67. }
  68.  
  69. protected function _setVar($strVarName, $mixValue)
  70. {
  71. $this->_arrViewVars[$strVarName] = $mixValue;
  72. }
  73.  
  74. private function _toLowerCase($strString)
  75. {
  76. return strtolower(substr($strString, 0, 1)).substr($strString, 1);
  77. }
  78.  
  79. private function _getMethodToRun() // sprawdza parametr url, czy ma wykonac jakas metode - patrz wyzej
  80. {
  81. if(isset($this->_arrControllerParameters))
  82. {
  83. if(method_exists($this, $this->_arrControllerParameters[0]))
  84. return($this->_arrControllerParameters[0]);
  85. }
  86. return false;
  87. }
  88.  
  89. private function _getParameterForMethod() // zwraca tablice parametrow dla metody ktora nalezy wykonac. Jezeli url wygldal: 
    strona.pl/index.php/uzytkownik/pokaz/12345 to wykona metode pokaz kontrolera uzytkownik przekazujac do pokaz() 12345.
  90. {
  91. if(isset($this->_arrControllerParameters))
  92. {
  93. if(count($this->_arrControllerParameters) > 1)
  94. {
  95. $intNumOfParameters = count($this->_arrControllerParameters);
  96. $arrPrametersForMethod = array();
  97.  
  98. for($i = 1; $i < $intNumOfParameters; $i++)
  99. $arrPrametersForMethod[] = $this->_arrControllerParameters[$i];
  100.  
  101. return $arrPrametersForMethod;
  102. }
  103. }
  104. }
  105. }
  106.  
  107. ?>


  1. Plik class.HelloController.php - przykładowy kontroler dla akcji Hello
  2. <?php
  3.  
  4. // To jest przykladowy kontroler. Jak widac mozna wywolac strone tak: strona.pl/index.php/Hello/Hi albo Hello/Bye. Mozna przekazac argument: Hello/Bye/Adrian przez co akcja porzegna Adriana.
  5.  
  6. class HelloController extends Controller
  7. {
  8. public function hi()
  9. {
  10. $this->_setPageTitle('Witamy Cię');
  11. $this->_setVar('HEADER', 'Witaj, to działa!');
  12. }
  13.  
  14. public function bye($strName)
  15. {
  16. $this->_setPageTitle('Pa pa pa');
  17. $this->_setVar('HEADER', 'Pa pa '.$strName.', do następnego razu!');
  18. }
  19.  
  20. public function runDefaultMethod()
  21. {
  22. $this->hi();
  23. }
  24. }
  25.  
  26. ?>


Będę Wam ogromnie wdzięczny za uwagi. Pozdrawiam serdecznie,
Adrian.
sf
Brak komentarzy, miales jakis pomysl, ale nie kazdy rozumie/nie ma czasu analizowac co dana metoda robi - toteż analiza ogólna pomysłu jest dość trudna.

Pozatym nie bardzo rozumiem Twoje nazewnictwo, z ktorego wynika, ze kontroler i akcja to jest to samo... wg. mnie kontroler powinien byc jeden i to on wywoluje odpowiednia akcje. Dla mnie tutaj kontrolerem jest klasa Dispatcher.

Co do wyjątki w index.php, tutaj mi się wydaje, że lepiej zarejestrować globalny ( http://php.net/set_exception_handler ) .

Pozatym zamiast count + for lepiej uzyc foreach winksmiley.jpg

Po co dajesz die? Skoro używasz wyjątków to się tego trzymaj.

Wielu można funkcji użyc do sprawdzenia czy string jest pusty... ale wydaje mi się, że lepiej używać funkcji, które do tego służą.. czyli empty, a nie strlen.
matid
Cytat(sf @ 2006-03-04 18:07:56)
Pozatym nie bardzo rozumiem Twoje nazewnictwo, z ktorego wynika, ze kontroler i akcja to jest to samo... wg. mnie kontroler powinien byc jeden i to on wywoluje odpowiednia akcje. Dla mnie tutaj kontrolerem jest klasa Dispatcher.

Akcja przy zastosowaniu wzorca MVC jest częścią kontrolera, natomiast sam Dispatcher jest częścią wzorca Front Controller (przynajmniej w tym kodzie, bo generalnie Front Controller powinien korzystać z Dispatchera w celu obsługi żądania, ale założenia są podobne).
Prph
Diękują za uwagi.

Co do die() - faktycznie nie powinno go tam być. To dlatego, że wzorowałem się na CakePHP a tam było die() winksmiley.jpg

Wydaje mi się, że Kontroler to klasa danej akcji. To tak jakby moduł naszej aplikacji. Tzn jeżeli mamy akcję Użytkownik to wydaje mi się, że (nazwijmy je) podakcje typu PokażProfil, Usuń, Edytuj powinny być w tym kontrolerze Użytkownik, np:

  1. <?php
  2.  
  3. class Użytkownik
  4. {
  5. PokażProfil();
  6. Usuń();
  7. Edytuj();
  8. }
  9. ?>


Dlaczego Dispatcher? Dlatego, że był obecny w CakePHP. Nie wiem - nazwać go FrontController? Może faktycznie jest to lepszy pomysł. Z drugiej strony - przecież on rozbija URL i na podstawie tego odpala kontroler danej akcji.

Czekam na dalsze uwagi. Pozdrawiam serdecznie, Adrian.
Fipaj
Ten pomysł z Dispatcherem mi się nie podoba.

Czemu nie zrobić tak, jak w większości frameworków? index.php/Kontroler/Akcja/Parametr/Parametr/...?
Prph
Ale przeciez w praktyce to tak działa winksmiley.jpg

Ale chyba źle rozumiem pojęcie akcji i kontrolera. Może mi ktoś powiedzieć, co powinienem zmienić w swoim toku myślenia?

Pozdrawiam, Adrian.
matid
We wzorcu MVC kontroler to część aplikacji odpowiedzialna za modyfikacje danych, pobiera on dane wprowadzane przez użytkownika, modyfikuje model i wymusza aktualizację widoku. Nie należy mylić kontrolera we wzorcu MVC z wzorcem Application Controller, bo ten drugi umiejscawia kontroler między modelem a widokiem, co nie jest zgodne z założeniami MVC.

Implementując kontroler w MVC mamy dwie drogi do wyboru: wzorzec Page Controller i Front Controller.

Page Controller - dla każdej strony należącej do witryny tworzymy osobny kontroler. Dekoduje on dane wprowadzane przez użytkownika, tworzy obiekty modelu i wywołuje operacje na modelu, a następnie określa, którego widoku należy użyć i przekazuje mu informacje modelu. Raczej stosowany w aplikacjach o prostej logice działania.

Front Controller - obsługuje wszystkie żądania przesyłane do witryny, a następnie tworzy obiekt polecenia abstrakcyjnego, który następnie wykonuje (można je nazwać akcjami). Traktuje on polecenia jako klasy, a nie strony jak to jest w przypadku wzorca Page Controller. Często łączony z wzorecem Intercepting Filter, bardziej złożony od wzorca Page Controller, ale również bardziej elastyczny.

Pozostaje jeszcze wzorzec Application Controller, ale to już nieco inna bajka winksmiley.jpg

Jaki wzorzec zastosujesz - to zależy od ciebie. Ja mogę polecić lekturę PoEAA i POSA
Ociu
Ja sobie podzieliłem to tak:
Kod
framework
     - Web
            - Services
            - ActionChain
            - Controller
            - FrontController
            - Sockets
            - Request


Idzie to tak:
Request pobiera dane z urla o akcji. Odpalany jest Controller, który ładuje bloki menu. Controller odpala FrontController, który znowu korzysta z ActionChain. ActionChain ładuje plik xml z dostępnymi akcjami. Co do parsowania urla. Stwórz obsługę filtrów, i tam wsadź sobie URLFilter.

pozdrawiam
Prph
Teraz raz jeszcze przeczytalem Wprowadzenie do MVC z portalu. Patrzac na Twoje rozwiazanie i idee przedstawione w artykule stwierdzam, ze juz zupelnie mvc nie rozumiem winksmiley.jpg

Mozesz przedstawic zarys Twojej aplikacji - napisz jakie klasy, metody i co robia. Kodu nie potrzebuje.

Kolejna sprawa... Przeczytalem ze Kontroler wlacza widok a on wywoluje model. Jakos nie rozumiem :/ Jak widok ma wywylac metode? Widok to obiekt? A jak na koncu wyswietlane sa dane?

Pozdrawiam, Adrian.
hwao
W ogole brak Ci widoku w tym frameworku.

Glupio jest troche to wszytko skladac w php poniewaz calosc nie dziala w trybie "ciaglym" tylko jest odpalana po requestach, wiec moze sie okazac ze czesc zalozen MVC wogole nie pasuje do zastosowana WEB'owych.

Dlatego ciezko moim zdaniem jest znalesc kompromis miedzy paroma czynnikami, ilosc klas, modele, akcje, kontroler, widok i szybkosc pisania(ze nie pisze sie 'nie potrzebnych klas' - chociaz roznie z tym nie potrzebnym jest, to zalezy od tego jakiego framework'a potrzebujesz)
matid
U mnie to wygląda tak:

index.php:
W tym pliku tworzone są obiekty HttpRequest i HttpResponse (ktore następnie dodaje do kontenera HttpContext dla ułatwienia). Następnie tworzony jest obiekt FrontController i wywoływana jest jego metoda processRequest( iHttpRequest $Request, iHttpResponse $Response);
Oczywiście wszystkie metody tutaj zawarte można umieścić w obiekcie ApplicationBootstrap, albo czymś podobnym.

FrontController.class.php:
Metoda processRequest tworzy obiekt RequestHelper (patrz niżej). Po pobraniu od RequestHelpera polecenia, FrontController wywołuje metodę processCommand( iCommand $Command). Metoda ta tworzy nowy obiekt FilterManager. A następnie rejestruje w nim kolejne filtry, jednym z nich jest ExecutionFilter, który przyjmuje jako parametr polecenia do wykonania.

RequestHelper.class.php:
Klasa ta udostępnia metodę getCommand. Tworzony jest obiekt CommandFactory i RequestDispatcher. Wywoływana jest metoda CommandFactory->createCommand, która przyjmuje zwracane przez RequestDispatcher nazwę modułu i nazwę polecenia.

ExecutionFilter.class.php:
Klasa ta jest wykonywana przez FilterManager. Wywołuję na poleceniu (Command) metodę execute(), następnie tworzy obiekt ViewFactory, który na podstawie danych dostarczonych przez obiekt Command tworzy nowy widok. Operacje zawarte w obiekcie widoku są wykonywane, następnie dane zwrócone przez widok przekazywane są obiektowy HttpResponse.

Sama przykładowe polecenie wygląda tak:
  1. <?php
  2.  
  3. class IndexCommand extends BaseCommand
  4. {
  5. public function execute()
  6. {
  7. $Title = new String( 'Blank Index Page' );
  8. $Body = new String( 'Lorem ipsum <span class="red">dolor</span> sit amet, consectetuer adipiscing elit. Praesent in eros egestas velit congu
    e elementum. Morbi varius ante non nibh. Mauris urna ipsum, ultricies vitae, inte
    rdum egestas, molestie ac, mauris. Maecenas fermentum mollis arcu. Integer alique
    t nulla et velit. Suspendisse nibh urna, egestas faucibus, ullamcorper sit amet, 
    volutpat id, nibh. Ut arcu lorem, dignissim et, auctor et, suscipit sed, sapien. 
    Praesent sollicitudin, mauris in auctor adipiscing, diam orci ullamcorper turpis,
     non gravida augue mi vitae ante. Cras accumsan risus eget est. Maecenas iaculis,
     ligula at consequat adipiscing, ante urna eleifend velit, non tincidunt lacus ma
    uris at nulla. Nulla semper dictum felis. Fusce feugiat. Sed consequat mauris ut 
    libero gravida adipiscing. Aliquam et eros non tellus auctor euismod. Maecenas ti
    ncidunt massa nec dolor. Suspendisse potenti. Suspendisse tincidunt, tortor in po
    suere tempor, risus sapien pellentesque massa, eget ultrices ante nibh a eros. Et
    iam quis risus a turpis viverra mattis. Mauris a wisi.'
     );
  9. $Message = new Message( $Title, $Body );
  10.  
  11. $this->addModel( new String( 'Message' ), $Message );
  12.  
  13. $this->setViewHandle( new ViewHandle( new String( 'MessageView' ) ) );
  14. }
  15. }
  16.  
  17. ?>


A widok MessageView tak:
  1. <?php
  2.  
  3. class MessageView extends XHTMLView
  4. {
  5. public function execute()
  6. {
  7. $XML = new MessageXMLHelper( $this->getModel( new String( 'Message' ) ) );
  8.  
  9. $XSL = new DOMDocument;
  10. $XSL->load( OF_TEMPLATE_DIR . 'Message.xsl' );
  11.  
  12. $XSLTProcessor = new XSLTProcessor;
  13. $XSLTProcessor->importStylesheet( $XSL );
  14.  
  15. $XHTML = $XSLTProcessor->transformToDoc( $XML->execute() );
  16. $XHTML->formatOutput = true;
  17.  
  18. $this->addDocument( $XHTML );
  19. }
  20. }
  21.  
  22. ?>


Nie jest to może najprostsze rozwiązanie, ale myślę, że jest poprawne w duchu MVC.
Prph
Witam winksmiley.jpg

Widzę, że niechętnie podchodzicie do mojej prośby winksmiley.jpg Ok, to spróbuję inaczej.

Znalazłem pewnien artykuł o budowie frameworka z wykorzystaniem MVC. Troche z niego wyciągnąłem winksmiley.jpg

Prosze mi jednak opisać, co powinno wydarzyć się w przypadku zgłoszenia rządania HTTP. Proszę podać co jest klasą, co zwykłą metodą. Co klasa robi itp winksmiley.jpg

Np:

Rządanie obsługuje Kontroler (klasa). Rozbija URI, mapuje (domyślam się, że chodzi tu kojarzenie akcji z URI z klasą akcji) itd...

Pozdrawiam,
Adrian.
Ociu
Widze, że czekasz aż ktoś rzuci kodem.
  1. <?php
  2. public class Context {
  3. public $module;
  4. public $parameters = array();
  5. public $action;
  6.  
  7. # inne metody
  8. }
  9.  
  10. public class URLFilter {
  11. public function perform( Context $context ) {
  12. $path = explode('/', $_SERVER['PATH_INFO']);
  13.  
  14. $context->module = $path[0];
  15. $context->action = $path[1];
  16. unset($path[0]);
  17. unset($path[1]);
  18. $context->parameters = array_values($path);
  19. }
  20. }
  21.  
  22. public class FrontController {
  23. public function __construct() {}
  24. public function runApplication( Context $context ) {
  25. # uruchamianie aplikacji
  26. }
  27. }
  28. ?>
To jak zbudowana jest aplikcja, zależy od Ciebie.
Możesz mieć tak:
  1. <?php
  2. public class DeleteModel {
  3. public function news() { /* Usuwanie newsa */ }
  4. public function article() { /* Usuwanie artykułu */ }
  5. }
  6. ?>

Możesz mieć jeszcze inaczej...
Prph
Przejrzałem kod Phienda, poczytałem dokumentacje, znowu przejrzałem pół Internetu i napisalem zupełnie od nowa framework. Ma jeszcze kupe błędów, jest mało spójny, ale mam wrażenie, że ogólne założenie udało mi się zrealizować.



Ale zastanawiam się teraz, jak zrealizować łańcuchy akcji. Może jakieś sugestie?
hwao
czemu u Ciebie akcja wlacza Model i Widok? ohmy.gif ph34r.gif

Przy takim zalozeniu nie potrzebny Ci chyba lancuch ackji
Prph
Yyyyy? A to co ma włączać model i widok?
Ludzie czym do ... jest akcja? Bo dla mnie to ona jest jak kontroler - zwyczajnie steruje.

  1. <?php
  2.  
  3. class SayHelloAction extends Action
  4. {
  5.  
  6. public function __construct()
  7. {
  8. $this->_setModel('HelloModelDao'); // z rodzica Action, metoda "odnajduje model i towrzy jeg obiekt
  9. $this->_setView('SayHelloHtmlView'); // j.w.
  10.  
  11. parent::__construct();
  12. }
  13.  
  14. public function run()
  15. {
  16. $arrAnswer = $this->_getModel()->getMessageForUser(); // wykonuje metode na tym modelu
  17. $this->_getView()->display($arrAnswer); // i w zasadzie przekazuje dane dla widoku
  18. }
  19. }
  20.  
  21. ?>


To nie jest poprawne rozwiązanie? I czy ono kłuci się z MVC?
sf
Hyh, w ostatnim php Solutions jest fajnie napisane o frameworku. Wladowali tam kupe przykladowych wzorcow. Polecam.
mike
Cytat(Prph @ 2006-03-10 07:54:17)
Yyyyy? A to co ma włączać model i widok?
Ludzie czym do ... jest akcja? Bo dla mnie to ona jest jak kontroler - zwyczajnie steruje.

Błąd.

Mylisz pojęcia i znaczenia. Do sterowania jest kontroler, to on na podstawie żądania uruchamia akcję.
A ona ma za zadanie tylko i wyłącznie wykonać jakąś operację i kropka. Akcja niczym nie steruje.
Martio
Cytat(mike_mech @ 2006-03-10 10:09:42)
Hyh, w ostatnim php Solutions jest fajnie napisane o frameworku. Wladowali tam kupe przykladowych wzorcow.

(...)

Mylisz pojęcia i znaczenia. Do sterowania jest kontroler, to on na podstawie żądania uruchamia akcję.

Mam ten artykuł z php Solutions. Jest dobry, ale nic nie mówi o najważniejszym dla nas w tym przypadku wzorcu: MVC.

Ja również w innym wątku nie mogłem się doprosić wyjaśnienia co to jest akcja i za co opowiada!

Wzorzec MVC mówi o modelu, widoku i kontrolerze. Teoretycznie w tym wzorcu nie ma mowy o akcji...

Kod
$kontroler = new KontrolerMVC();
$model = new ModelMVC();
$widok = new WidokMVC();

$kontroler->wybierzModel($_GET['model']);
$widok->dane($model->pobierz());
$widok->wyswietl($model->widok());


To rozwiązanie jest dobre dla jednej operacji (akcji). Jednak jak powinna wyglądać implementacja tego wzorca w przypadku wielu akcji do realizacji?

Taki przykład. Do realizacji jest:
- zapisanie danych z formularza
- wyświetlenie właściwego komunikatu (czy powiodło się?)
- pobranie danych do wyświetlenia na stronie

Mamy 3 operacje. Jak powinna teraz wyglądać implementacja tego wzorca. Tylko proszę nie odsyłać mnie do żadnego frameworka. Widziałem już dziesiątki przykładów, jednak żaden nie trzymał się zasad wzorca MVC.
hawk
Żaden nie trzymał się zasad wzorca MVC? Bo to zależy, jak autor danego frameworka interpretuje te zasady. Jakby było to takie oczywiste, to by był tylko jeden framework i koniec dyskusji. Zamiast tego masz wiele frameworków, i jeszcze więcej koncepcji implementacji tego wzorca. Więc nie pytaj "czy to jest zgodne z MVC", tylko "dlaczego to jest zrobione tak, a nie inaczej". I dlaczego to, że jakiś framework nie trzyma się zasad MVC, jest wadą?

Ale wracając do twojego przykładu:
1. Na początku będzie jakaś implementacja kontrolera, pewnie FrontController, która w jakiś sposób dojdzie do wniosku, którą akcję chce wykonać.
2. Akcja pobierze dane z formularza (albo bezpośrednio z $_POST, albo z jakiegoś HttpRequesta, albo jeszcze inaczej), stworzy odpowiedni obiekt warstwy modelu i za jego pomocą wykona update
3. Teraz albo akcja oddaje sterowanie do kontrolera, który dochodzi do wniosku, że należy wykonać odpowiedni widok, lub akcja sama odpala potrzebny widok.
4. Widok wypluwa HTMLa.

Muszę się też przyczepić do modelu. Po prostu szał mnie bierze, kiedy widzę $model = new Model(). Model to jest warstwa, a nie klasa. Rysujesz sobie, co aplikacja ma robić, i tworzysz z tego ładny diagram klas, bez żadnego HTTP, HTML, kontrolerów, akcji i całego tego balastu.
Martio
Cytat
I dlaczego to, że jakiś framework nie trzyma się zasad MVC, jest wadą?


Oczywiście, że nie jest to wada. I nie uważam, że odejście od wzorca MVC stawia dany framework na gorszej pozycji. Wzorzec ma jedynie ułatwiać pewne sprawy, jednak nie zastosotwanie danego wzorca nie powoduje od razu, że aplikacja jest źle zaprojektowana...

Cytat
Teraz albo akcja oddaje sterowanie do kontrolera, który dochodzi do wniosku, że należy wykonać odpowiedni widok, lub akcja sama odpala potrzebny widok.


I tutaj jest problem. Jak wykonać to praktycznie? Model zwraca odpowiedź: dodano dane do bazy danych lub nie. I na podstawie tej odpowiedzi kontroler uruchamian kolejną akcję? Jak to wygląda w praktyce (prosty, łatwy przykład w kodzie)?

Cytat
Muszę się też przyczepić do modelu. Po prostu szał mnie bierze, kiedy widzę $model = new Model(). Model to jest warstwa, a nie klasa. Rysujesz sobie, co aplikacja ma robić, i tworzysz z tego ładny diagram klas, bez żadnego HTTP, HTML, kontrolerów, akcji i całego tego balastu.


Właśnie dzięki takiemu forum jak to mamy możliwość porpawiania swoich błędów i uczyć się od najlepszych.

A więc jak to będzie wyglądało? Co masz na myśli mówiąc wartwa? Model to warstwa czyli metoda klasy kontrolera?
Vengeance
Akcja to po prostu część kontrolera. Ponieważ kontroler jest jeden, to zawarcie w tej jednej klasie wszystkich możliwych operacji czyniłoby go strasznie nieczytelnym. Dlatego wydzielono coś co nazwano akcją - i są to najczęśćiej kolejne klasy, odpalane przez kontroler.

Obrazowo, gdyby przyjąć, że akcja to NIE kolejna klasa:
  1. <?php
  2. class Kontroler
  3. {
  4.  // robi cos waznego, co powtaza sie w kazdym wywolaniu
  5.  // a nastepnie na podstawie $_GET['action'] uruchamia odpowiednia metode
  6.  public function __construct()
  7.  { }
  8.  
  9.  // nasza pierwsza akcja
  10.  public function ShowNews() { }
  11.  
  12.  // nasza druga akcja
  13.  public function DeleteNews() { }
  14. }
  15. ?>
Martio
Dobra, ale jak obiektowo w takim bądź razie zakodować łańcuszek akcji. Np. index.php?akcja=news

I teraz są do wykonania akcje:
- sprawdz czy zalogowany
- pobierz newsa
- pobierz dane do menu

I jak bedzie wygladalo to obiektowo?

I pytanko jeszcze: gdzie umiescic informacje czy dostep do newsow ma byc otwarty czy dostepny, po zalogowaniu?
DeyV
W takim momencie okazuje, się, że wydzielenie z kontrolera akcji jest bardzo przydatne.
Dlaczego?
Ponieważ to kontoler sprawdza 1 element twojego łańcuszka - tj. czy user jest zalogowany. To on również, po przekonaniu sie, czy akcja, którą user chce wywołać, wymaga logowania, powinien zezwolić na jej załadowanie, lub też wyświetlić okno logowania.

A co robi w tym przypadku "akcja"?
Uruchamia jakąś klasę modelu, która pobiera newsy, i przekazuje ją (lub tylko same dane) do widoku.
To ta akcja musi również pamiętać o załadowaniu danych do menu.
Aby to jednak ułatwić - wiele osob dochodzi do wniosku, że niektóre z elementów strony są wykonywane zawsze, stąd pozwalają akcjom na dziedziczenie po sobie, w celu uruchomienia włąśnie takich elementów jak menu.

A gdzie przehowywywać informację o tym, czy określona akcja ma wymagać autoryzacji? To już Twoja decyzja - większość programistów decyduje sie na przygotowanie plików konfiguracyjnych dla każdej akcji, w których określają takie rzeczy.
Prph
No proszę... Najpierw ktoś mówi, że akcja nie steruje, a później inni, że to ona ładuje model i przekazuje dane do wisoku tongue.gif

A co do samych metod dostępu.
1. Kontroler ładuje plik konfiguracyjny akcji.
2. Dowiaduje sie z niego, ze akcja wymaga autoryzacji, a co więcej, dostęp mają tylko użytkownicy z grupy Przyjaciele.
3. Kontroler dokonuje autoryzacji - sprawdza czy należymy do Przyjaciele
4. Jak tak, to odpala akcje, jak nie, to jakies inne zdarzenie winksmiley.jpg

Tak to mniej wiecej wyglada np. w Phiend.

Pozdrawiam.
DeyV
Pamiętaj o jednej rzeczy.

MVC nie mówi, jak ma być zbudowany kontoler. Dla tego wzorca ważne jest tylko jedno - zapytania i operacje na danych nie są umieszczane w kodzie html.
Stąd wyraźne rozgraniczenie na model i widok.

Natomiast to, jak tymi 2 elementami będzie zarządzać kontroler, tu już swobodę mają wszyscy.
Przy czym przyznaję, że model kontolera zaproponowany przez Phiend jest w tej chwili jednym z najpopularniejszych, i sprawdza się najlepiej.

Dodam że jego autor, hawk, nazwał go MVCR, gdzie R oznacza router, czyli mechanizm automatycznie zarządzający ładowaniem elementów kontrolera, czyli naszych akcji w oparciu o dane od usera.
Prph
No dobrze, a teraz juz troche idac w przyszlosc...
Powiedzmy, ze mam juz moj framework. Index.php wlacza FrontKontroler, on zas wybiera akcje. Ta sobie wczytuje model, wykonuje metode na modelu i dane przekazuje do widoku. Jeszcze w tej samej akcji widok wyswietla dane, czyli do kontrolera nic juz nie wraca.

A tak na tym zbudowac standadowa www? Z naglowkiem, menu i stopka? Moznaby oczywiscie tak:

Kod
--------------------------
        HEAD
--------------------------
                  | menu   |
                  |        |
  Kontroler       |        |
                  |        |
--------------------------
stopka
----------------------------
                    


W srodku wykonałby sie kontroler no i w zasadzie to by jakos zadzialalo. A jak teraz ustawic dynamiczny tytul strony?

A moze tak:

index.php:
1. Kontroler. -> zachowaj dane w widoku w zmiennej np. $BODY.
2. Ustaw menu, title strony
3. Odpal Smarty, zeby zbudowalo strone.

Pozdrawiam,
Adrian.
Martio
Chciałbym jeszcze powrócić do akcji. Powiedzmy, że nasza aplikacja składa się z takich komponentów:

- publikacje:
-> newsy
-> felietony
-> poradniki

- galeria:
-> sport
-> muzyka

Jak robić to na klasy? Czy dobrym rozwiązaniem jest takie coś:

Wyświetlenie felietonów

  1. <?php
  2.  
  3. class WyswietlFelieton interface KontrolerAkcji {
  4. function __construct() {
  5. $akcja = new Publikacje();
  6. return $akcja->pokaz('felieton');
  7. }
  8. }
  9.  
  10. class Publikacje interface Model {
  11. function pokaz {}
  12. function edytuj {}
  13. function dodaj {}
  14. function usun{}
  15. }
  16.  
  17. class Galeria interface Model {
  18. function pokaz {}
  19. function edytuj {}
  20. function dodaj {}
  21. function usun{}
  22. }
  23.  
  24. ?>


Kontroler akcji zwraca dane do widoku, który zażądał pobrania danych.

Czy takie rozwiązanie jest prawidłowe? Tworzę sobie wszystkie klasy modelu mojej aplikacji czyli informacje, galerie, forum. Przy uruchamianiu aplikacji pozostaje mi stworzenie kontrolerow.

Akcja to jest jedna czynność. A jak nazywa się zbiór akcji?

Powiedzmy, że kontroler akcji musi zrealizować kilka czynności. Niech będzie zapisanie nowego newsa oraz wyświetlenie pozostałych. Czy to będzie tak wyglądało:

  1. <?php
  2.  
  3. class ZapiszNewsa interface KontrolerAkcji {
  4. function __construct() {
  5. $akcja = new Publikacje();
  6. $akcja->zapisz('news', $dane);
  7. return $akcja->pokaz('news');
  8. }
  9. }
  10.  
  11. ?>
aleksander
  1. <?php
  2. class Kontroler
  3. {
  4. public function __construct()
  5. {
  6. if( isset( $_GET['akcja'] ) )
  7. {
  8. $sAkcja = (string)$_GET['akcja'];
  9. } else {
  10. $sAkcja = 'defaultowa_akcja';
  11. }
  12.  
  13. $oAkcja = new $sAkcja();
  14. $oAkcja->wykonaj()
  15. }
  16. }
  17.  
  18. class PokazNewsy
  19. {
  20. public function wykonaj()
  21. {
  22. $oModel = new ModelNewsow();
  23. $aNewsy = $oModel->pobierzNewsy();
  24. foreach( $aNewsy as $aNews )
  25. {
  26. echo 'tytul: ' . $aNews['tytul']; //itd...
  27. }
  28. }
  29. }
  30.  
  31. class ModelNewsow
  32. {
  33. public function pobierzNewsy()
  34. {
  35. return array( array( 'title' => 'przykladowy tytul' ) );
  36. }
  37. }
  38. ?>
najprostrzy przyklad MVC. Kontroler moze uruchmiac tylko jedna akcje jak chcesz zrobic łańcuch akcji to:
  1. <?php
  2. class Kontroler
  3. {
  4. public function __construct()
  5. {
  6. if( isset( $_GET['akcja'] ) )
  7. {
  8. $sAkcja = (string)$_GET['akcja'];
  9. } else {
  10. $sAkcja = 'defaultowa_akcja';
  11. }
  12. while( !is_null( $sAkcja ) )
  13. {
  14. $oAkcja = new $sAkcja();
  15. $sAkcja = $oAkcja->wykonaj();
  16. }
  17. }
  18. }
  19.  
  20. class PokazNewsy
  21. {
  22. public function wykonaj()
  23. {
  24. $oModel = new ModelNewsow();
  25. $aNewsy = $oModel->pobierzNewsy();
  26. foreach( $aNewsy as $aNews )
  27. {
  28. echo 'tytul: ' . $aNews['tytul']; //itd...
  29. }
  30. return 'nazwa_nastepnej_akcji lub null jezeli akcja ma byc ostatnia';
  31. }
  32. }
  33.  
  34. class ModelNewsow
  35. {
  36. public function pobierzNewsy()
  37. {
  38. return array( array( 'title' => 'przykladowy tytul' ) );
  39. }
  40. }
  41. ?>
Prph
Ha! A to całkiem ciekawe rozwiązanie.

A jeszcze lekko zmieniając temat - jak przechowywać konfigurację? Globalna tablica, a może zworzec rejestru? Co stosujecie?
hwao
zalezy jakie to dane...

konfiguracja aplikacji define, dane aplikacji czesto rejestr etc.
Prph
Cytat(hwao @ 2006-03-11 17:34:17)
konfiguracja aplikacji define, dane aplikacji czesto rejestr etc.

dane aplikacji, czyli? Np. konfigi akcji?
hwao
Cytat(Prph @ 2006-03-11 20:07:57)
Cytat(hwao @ 2006-03-11 17:34:17)
konfiguracja aplikacji define, dane aplikacji czesto rejestr etc.

dane aplikacji, czyli? Np. konfigi akcji?

Sciezki, debugowanie pewnych rzeczy etc
hawk
Cytat(Martio @ 2006-03-11 14:04:49)
  1. <?php
  2.  
  3. class WyswietlFelieton interface KontrolerAkcji {
  4.   function __construct() {
  5.     $akcja = new Publikacje();
  6.     return $akcja->pokaz('felieton');
  7.   }
  8. }
  9.  
  10. class Publikacje interface Model {
  11.   function pokaz {}
  12.   function edytuj {}
  13.   function dodaj {}
  14.   function usun{}
  15. }
  16.  
  17. class Galeria interface Model {
  18.   function pokaz {}
  19.   function edytuj {}
  20.   function dodaj {}
  21.   function usun{}
  22. }
  23.  
  24. ?>

Nie powinno być interface Model, ani extends Model. Ba, słowo Model nie powinno się w ogóle pojawić w kodzie php. Bo Model to warstwa, tak samo jak Widok. Nie robimy klasy Widok, bo nie wiadomo, co niby miałaby robić. Nie robimy klasy HTML z metodami wyświetlTo(), wyświetlTamto() itd. Tak samo nie robimy klasy Model z wieloma metodami, bo to kolejny przykład god class.

Nie chcę krytykować, bo zaprojektowanie Modelu jest chyba trudniejsze niż sam wzorzec MVC. Np. twoja klasa Publikacje też robi za dużo. Należałoby rozbić to na kontener publikacji, odpowiedzialny za wyszukiwanie (wg. różnych kryteriów) oraz samą klasę Publikacja, odpowiadającej zawsze jednemu rekordowi w bazie danych. Całość należałoby wepchnąć we wzorzec UnitOfWork, w celu zarządzania transakcjami, i dorzucić wzorzec IdentityMap, w celu zapewnienia, że nie będą po aplikacji krążyły dwie instancje klasy Publikacja odpowiadające temu samemu rekordowi. Jak widać, jest to skomplikowane... a jak już się uda to wszystko obiektowo zaprojektować, cały system zaczyna się walić, bo relacyjnej bazy danych nie da się odwzorować za pomocą obiektów. Takie zapytania jak "pokaż tytuły 10 publikacji o największej liczbie komentarzy" lub "usuń wszystkie publikacje starsze niż 1 rok" nie pasują do stworzonej klasy Publikacja.

@Prph: Wyświetlanie "standardowej strony www" z naglówkiem, stopką itd. nie jest wspierane specjalnie przez samo MVC. Generalnie stałe elementy strony przynależą do Widoku. Można dziedziczyć klasy Widoku po jakiejś klasie bazowej, która potrafi wygenerować stałe elementy strony. Można przekazać do instancji klasy Widoku obiekt odpowiedzialny za layout strony, tak, aby obiekt Widoku mógł się "wkleić" w miejsce przeznaczone na content.

A co do konfiguracji, to oczywiście zależy. Najpierw odróżnij konfigurację całej aplikacji (uwierzytelnianie, połączenie z DB, itd) od konfiguracji pojedynczej akcji (np. prawa dostępu). To drugie czasem umieszczane jest w samej akcji (np. stare Mojavi). Najlepiej jest mieć system, który obsługuje różne źródła danych (pliki ini, tablice, XML, itd), wymieniając tylko obiekt odpowiedzialny za wczytywanie pliku z konfiguracją.
eMartio
Postanowiłem odejść od wzorca MVC, realizując aplikację wg własnego podziału na 5 warstw. Uważam takie rozwiązanie za najbardziej optymalne w przypadku moich potrzeb.

Warstwa kontroli analizuje żądanie HTTP, następnie tworzy instancję odpowiedniej klasy warstwy poleceń, która realizuje wszystkie zadania w warstwie logiki biznesowej. Wynik przetwarzania trafia do warstwy kontroli, która analizuje dane i uruchamia odpowiedni widok.

W mojej aplikacji warstwa danych będzie zawierała dwie klasy: ObsługaBazyDanych oraz ObsługaPlikuXML. Z tych interfejsów będzie korzystała warstwa logiki biznesowej. Warstwa Logiki Biznesowej będą to klasy modelujące problemy, które system ma rozwiązywać. Np. dodanie nowego użytkownika, sprawdzenie czy login jest wolny, itp.

Warstwa Prezentacji będą to klasy odpowiedzialne za zaprezentowanie danych na zewnątrz systemu czyli html, rss, e-mail, pdf. Warstwa Kontroli może wygenerować więcej niż jedną prezentację, np. wysłanie danych e-mail, wyświetlenie strony WWW.

Warstwa kontroli ma za zadanie, jak sama nazwa wskazuje, kontrolowanie funkcjonowania systemu czyli analizować dane wejściowe i wyjściowe czy pracę warstwy poleceń.

Budowanie nowej aplikacji na bazie mojego frameworka to tworzenie klas warstwy logiki biznesowej, w której będą wszystkie możliwe akcje do wykonania oraz klas warstw poleceń, które inicjują poszczególne metody klas warstw logiki biznesowej. Jedno żądanie HTTP może wymagać zrealizowania kilku zadań (akcji).


Ociu
Ja podzieliłem sobie kontroler na FrontController i BlockController. Jeżeli framework ma być udostępniony dla innych ludzi, oni sami powinni wybrać jak powinna być zbudowana aplikacja. Dlatego, gdy już wersja będzie dostępna publicznie, tworzę przykładowy FrontController i BlockController. Narzucam jednak metody dla tych klas. Niestety framework, który piszę jest przeznaczony pod php4, więc nie moge skorzystać z interfejsów. Dlatego od tego jest dokumentacja. Tak samo jest z sterownikiem bazy danych i szablonami. Stworzyłem za to klasę ViewRendener, która tworzy klasę View funkcją eval" title="Zobacz w manualu php" target="_manual. Pomógł ten kod wyciągnięty z komentarzy:
  1. <?php
  2. $clazz = "class SomeClass { var \$value = 'somevalue'; function show() { echo get_class(\$this);}}";
  3.  
  4. eval($clazz);
  5.  
  6. $instance = new SomeClass;
  7.  
  8. // Here output 'somevalue';
  9. echo $instance->value;
  10.  
  11. echo "<br>";
  12.  
  13. //Here output 'someclass'
  14. $instance->show();
  15. ?>


Po modyfikacji wyszło coś takiego:
  1. <?php
  2. function Render() {
  3.  
  4. if( empty( $this->sCore ) ) {
  5.  
  6. $error = 'A template system has not been specified';
  7.  
  8. trigger_error($error, E_USER_ERROR);
  9.  
  10. return false;
  11. }
  12.  
  13. if( empty( $this->sSystemFile ) ) {
  14.  
  15. $error = 'A template system files has not been specified';
  16.  
  17. trigger_error($error, E_USER_ERROR);
  18.  
  19. return false;
  20. }
  21.  
  22. if( empty( $this->sDir ) ) {
  23.  
  24. $error = 'A template dir has not been specified';
  25.  
  26. trigger_error($error, E_USER_ERROR);
  27.  
  28. return false;
  29. }
  30.  
  31. eval(' class View extends ' . $this->sCore . ' { } ');
  32.  
  33. $oView = new View;
  34.  
  35. return $oView;
  36.  
  37. }
  38. ?>


Cały schemat aplikacji wygląda mniej więcej tak (wraz z przykłądowym FrontControllerem):
Controller -> tworzenie obiektu Services, który zawiera obiekty HttpRequest i User -> Controller przekazuje dane do FrontControllera -> FrontController sprwadza w ActionChain czy dana akcja jest zdefiniowana -> pobieranie configa i sprawdzanie w User czy dany użytkownik ma prawo korzystać z akcji - Tak: Akcja jest uruchamiana, która uruchamia model, przekazując wynik danych z modelu do widoku; Nie: wyrzucana jest strona z błędem. -> FrontController przekazuje wynik akcji do Kontroler głownego, który tworzy stronę tak:
  1. <?php
  2. function compose () {
  3.  
  4. $config = new Config;
  5. $config->initialize('Main');
  6.  
  7. $layer = $this->view->Render();
  8.  
  9.  
  10. $layer->assign('Title', $config->get('site_title'));
  11.  
  12. $layer->assign('MenuLeft', $this->Controllers['BlockController']->getRightSide());
  13. $layer->assign('MenuRight', $this->Controllers['BlockController']->getLeftSide());
  14.  
  15. $layer->assign('content', $this->Controllers['FrontController']->run());
  16.  
  17. $layer->parse('index.tpl');
  18.  
  19. }
  20. ?>


pozdrawiam
eMartio
Analizując wszystkie wątki o MVC na tym forum zgłupiałem! Jedni twierdzą, że X rozwiązanie jest prawidłowe, inni zaś, że kłuci się to z wzorcem MVC. Są na tym forum znakomici programiści, którzy stworzyli całkiem niezłe frameworki oparte na tym wzorcu. Mam do Was wielką prośbę. Czy możecie zrobić malutkie podsumowanie poprzez odpowiedz na poniższe pytania:

1. Co to jest akcja, gdzie się ją implementuje?
2. Co to jest łańcuszek akcji, gdzie się go implementuje?
3. Czy wartswa logiki biznesowej należy do modelu? Są dwie klasy: jedna odpowiedzialna za obsługę plików xml (odczyt, zapis, edycja), druga klasa za akcji: zapisanie danych z formularza $_POST w pliku XML (korzystając z klasy do obsługi pliku). Czy obie te klasy to model?
4. Jak należy zbudować aplikacje wg zasady: jak najmniej kodu? Jedna stała, uniwersalna klasa dla kontrolera, kilka klas modelu ze wszystkimi możliwymi operacjami, kilka klas widoku. I teraz jak na podstawie np. linku domena.pl/news.php wykonac nastepujace czynnosci: pokazac wszystkie newsy z dnia dzisiejszego (klasa: news, metoda: pokaz), usunac wszystkie z dni poprzednich (klasa: news, metoda: usun), pokazac wszystkie komentarze (klasa: komentarz, metoda: pokaz). Skad system ma wiedziec, ze na podstawie krotkiej informacji pobranej z URL 'news' ma wykonac 3 zadania (X zadan i jakie to zadania)?

Prosze o precyzyjna odpowiedz. Po przeczytaniu wszystkich watkow o MVC na tym forum juz calkowicie zglupialem sad.gif
Vengeance
"Czy możecie zrobić malutkie podsumowanie"
Nie nie możemy. Dlaczego?
Wzorzec obiektowy zawsze pozostanie wzorcem, schematem....
a wszystkie rozwiązania przedstawione w tym i innym wątku to implementacje. Każda jest inna, ale nie ma lepszych i gorszych.

Dlatego nie patrz na to, jak inni rozwiązują daną kwestie bo rzeczywiście zgłupiejesz! Znasz podstawowe zasady rządzące danym wzorcem, teraz pomyśl nad własną implementacją. Przecież, gdybyś miał robić tak jak ktoś inny, a nie tak jak Ci wygodnie to po co pisać na nowo - lepiej wykorzystać gotowe rozwiązanie.

Także to czy jest akcja, czy jest klasą czy metodą, czy kontrolery są bardziej rozproszone oraz jak działa widok/model to implementacja. MVC mówi tylko o tym, że mają być 3 warstwy (tak samo jak cebula ma warstwy i tort ma warstwy - ogry też je mają i MVC również. Ale MVC nie jest jak ciasto z kremem) ;P
hawk
Cytat(Vengeance @ 2006-03-13 15:38:35)
MVC mówi tylko o tym, że mają być 3 warstwy (tak samo jak cebula ma warstwy i tort ma warstwy - ogry też je mają i MVC również. Ale MVC nie jest jak ciasto z kremem) ;P

Kremówki to jednakowoż jedno z najlepszych ciastek; ja to wiem!
aleksander
@eMartio: http://php.pl/wortal/artykuly/php/architek...wadzenie_do_mvc
eMartio
Cytat(aleksander @ 2006-03-13 15:44:31)

Znam to. Czytalem. Szkoda, ze artykul z 2004... Tema stary, ale cos nowego mozna byloby zredagowac...
Prph
Przeczytajcie na końcu!. Jak rozwiąć Widok?

Witam,

ja rozwizalem to tak:

w index.php laduje FrontController. Pierwsza czynnoscia jaka on wykona bedzie wydobycie informacji, jaka akcje powinien odpalic. Tym zajmie sie obiekt klasy URL. za pomoca metod getActionName() i getParameters() zwraca odpowiednie dane.

Majac juz nazwe akcji, Front kontroler wczytuje konfiguracje dla akji. Uruchamiany jest takze mechanizm autoryzacji, jezeli w konfiguracji bylo podane ze akcja jej wymaga.

Po przejsciu testow, zwyczajnie ladowana jest klasa, tworzony obiekt i wykonywana na nim metoda run();

Co robi run()?
1. Wybiera dla siebie model.
2. Wybiera dla siebie widok.
3. Wykonuje jakies dzialania
4. zwraca albo true, kiedy akcja sie powiedzie, albo tablice parametrow dla kolejnej akcji, albo false jak cos sie popsuje.

Znowu wszystko wraca do kontrolera. Ten czyta konfig czy ma wykonac nastepna akcje i czy ma jej przekazac parametry. Dane zwrotne z poprzedniej akcji ma, wiec reszta juz jest banalna.

Framment kontrolera glownego:
  1. <?php
  2. try
  3. {
  4. do
  5. {
  6. $this->_loadConfig($sActionName);
  7.  
  8. if(Registry::doesEntryExist($sActionName, 'useAuth'))
  9. if(Registry::getEntry($sActionName, 'useAuth'))
  10. $this->_checkAuth($sActionName);
  11.  
  12. $oAction = $this->_prepareAction($sActionName, $aParametersForAction);
  13.  
  14. $mResult = $oAction->run();
  15.  
  16. if(Registry::doesEntryExist($sActionName, 'nextAction'))
  17. {
  18. $sActionName = Registry::getEntry($sActionName, 'nextAction');
  19. $bContinueChain = true;
  20.  
  21. if(Registry::doesEntryExist($sActionName, 'passParameters'))
  22. $aParametersForAction = $mResult;
  23. }
  24. else
  25. $bContinueChain = false;
  26. }
  27. while($mResult && $bContinueChain);
  28. }
  29. catch(Exception $oException)
  30. {
  31. throw $oException;
  32. }
  33.  
  34. ?>


Metoda prywatna _prepareAction($name) po prostu sprawdza czy pliki akcji istnieja, dlaczaje, tworzy obiekt i go zwraca.

Przykladowa akcja:
  1. <?php
  2.  
  3. class DefaultExampleAction extends Action
  4. {
  5. public function run()
  6. {
  7. $this->_setModel('ExampleModel');
  8. $this->_setView('DefaultExampleHtmlView');
  9.  
  10. $aParameters = $this->_getParameters();
  11.  
  12. $aAnswer = $this->_getModel()->getExampleMethod();
  13.  
  14. $aData = $aAnswer;
  15. $aData['PARAMETERS'] = $aParameters;
  16.  
  17. $this->_getView()->display($aData);
  18. return true;
  19. }
  20. }
  21.  
  22. ?>


Klasa ta dziedziczy po Action. Wszystkie akcje we frameworku musza z niej dzidziczyc.. No chyba ze ktos woli swoje pisac winksmiley.jpg
  1. <?php
  2. $this->getParameters();
  3. ?>

Jezeli podano url postaci strona.pl/JakasAkcja/Opcja1/Opcja1/ to klasa nadrzedna Action zwroci w tej chwili tablice array(Opcja1, Opcja2). Parametry przekazywane sa do konstruktora Action we Front kontrolerze.

  1. <?php
  2.  
  3. $this->_setModel('ExampleModel');
  4. $this->_setView('DefaultExampleHtmlView');
  5.  
  6. ?>


Co robia te metody nadrzednej klasy Action? Tworza obiekty Modelu i widoku, dostepne pozniej za pomoca:
  1. <?php
  2. $this->_getModel();
  3. $this->_getView();
  4.  
  5. ?>


Klasa widoku
  1. <?php
  2.  
  3. class DefaultExampleHtmlView extends HtmlView
  4. {
  5. public function __construct()
  6. {
  7. $this->_setTemplate('DefaultExample');
  8. parent::__construct();
  9. }
  10. }
  11. ?>

Klasa dziedziczy z HtmlView, ktora u mnie w zasadzie jest jakby aliasem do SmartyView.


Klasa SmartyView|
  1. <?php
  2.  
  3. class SmartyView
  4. {
  5. private $_oSmarty;
  6. protected $sTemplateName;
  7.  
  8. public function __construct()
  9. {
  10. $this->_oSmarty = new Smarty();
  11. $this->_oSmarty->tempalte_dir = PATH_TEMPLATES;
  12. $this->_oSmarty->compile_dir = PATH_VAR;
  13. }
  14.  
  15. public function display($aData)
  16. {
  17. $this->_oSmarty->assign($aData);
  18. $this->_oSmarty->display($this->sTemplateName);
  19. }
  20.  
  21. protected function _setTemplate($sTemplateName)
  22. {
  23. $this->sTemplateName = $sTemplateName.'.tpl';
  24. }
  25. }
  26.  
  27. ?>


No to tak wyglada to z grubsza. Przepraszam, za brak komentarzy, ale zaraz musze isc na basen :/

Ktos powie, ze to nie tak, ze inaczej, po co tak... MVC mowi - masz miec model i widok, ktore nie sa w jednym miejscu. A kontroler ma pomoc im nawiazac miedzy nimi wspolprace.

Wydaje mi sie, ze moje rozwiazanie jest calkiem wygodne. Oczywiscie sporo brakuje tutaj do funkcjonalnosci. Obecnie mecze mechanizm autoryzacji, obslugi sesji itp.

Pozdrawiam, Adrian.

Jak rozwiązać widok?

Teraz, kiedy napisałem już większość bazowego kodu, stanął przede mną problem rozsądnego rozwiązania klasy widoku.

Chciałbym aby moj Framework był przystosowany do różnego rodzau widoków. Oczywiste jest, że najczęściej wykorzystam widok HTML, ale może się także pojawić widok samego pliku - tzn. akcja w wyniku swojego działania zwróci plik, chociażby obraz który zostanie wysłany wraz zodpowienim nagłówkiem do odbiorcy (prawdopodobnie przeglądarki).

W związku z tym zasada jest taka:

FrontController->Akcja->wykonaj czynnosci->uruchom widok.

Do przeglądarki nie tarfi nic, jeżeli akcja niczego takiego nie zwróci. Jest to normalne rozwiązanie, a do tego dosyć skuteczne, bo wiadome jest, że aby do przeglądarki wysłać naglówki to nic innego wcześniej nie może być wysłanego.

Problem pojawia się przy widoku HTML. Wiadomo przecież, że zwrócenie tabelek z newsami, czy fotek z galerii nie wystarczy - trzeba to ubrać w nagłówek, stopkę, tytuł strony itp.
Bez senu jest, aby szablon newsow zawierał to wszystko w sobie.

Rozwiązanie nasuwa się dosyć szybko - niech każdy widok HTML dziedziczy po jakimś widoku, który ubierze zwrócone dane z akcji w nagłowej, stopkę itp. I to by działało całkiem sprawie.

Jest jedno ale. Mój framework będzie dawał możliwość łańcuchów akcji. A działało to będzie tak:

Front Kontrole->Akcja->wykonaj
jeżeli w konfigu podano, ze ma wykonac nasteona akcje to ja wykonuje itd.

Jezeli tylko otatnia akcja jest widokiem HTML, a poprzednie to tylko działanie na modelu to jest ok:

Akcja ZapiszNewsa
zapisuje newsa w bazie. Nic sie nie wyswietla - nie korzysta z zadnego widoku.
Nastepna akcja to PokazNewsy

PokazNewsy
Wyswietla newsy z bazy danych

W rezultacie na ekranie pojawia sie wynik PokazNewsy - i tylko ten wynik.

Ale możliwe jest aby wykonac kilka akcji z widokiem. W takim przypadki nie ma mowy, aby obie akcje wyswietlały całą stronę...

Jak można to rozwiązac?

Pozdrawiam, Adrian.
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.