Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Integracja z innym skryptem + MVC
Forum PHP.pl > Forum > PHP > Object-oriented programming
lukasamd
Witam,
mam własny skrypt zintegrowany z forum na phpBB3, postanowiłem przerobić go aby był bardziej przenośny i mógł posłużyć do tworzenia innych serwisów dopiętych do for tego typu.
Wszystko było na funkcjach i strukturalnie, postanowiłem zamknąć ile się da w OOP, przez przypadek wyszło że warto od razu wszystko odseparować.

Skrypt ma przyjazne adresy, lecz nie mam żadnego routera, bo faktycznie zajmuje się tym .htaccess. Nie mam zamiaru puszczać wszystkich żądań (np. takich, które zwracają 404) na jeden plik. Są określone moduły i nie ma problemu aby wypisać je w htaccesie, wszystkie wymienione będą jednak iść przez główny plik.

Wygląda on obecnie tak:

  1. define('IN_PHPBB', true);
  2.  
  3. require_once 'core.php';
  4.  
  5. $module = request_var('module', '');
  6. if (!array_key_exists($module, $modules))
  7. {
  8. $module = 'news';
  9. }
  10.  
  11. $view = loader::view($modules[$module]['view']);
  12. $view->model = loader::model($modules[$module]['model']);
  13. $view->model->db = $db; // obsluga bazy danych z phpbb3
  14. $view->urls = new urls(true); // Osobna klasa sluzaca do budowy linkow przyjaznych lub zwyklych
  15.  
  16.  
  17. include DIR_MODULES . $module . '.php';


W pliku core.php są wykonywane takie operacje:
- includowanie głównych plików phpBB3
- start sesji (obiekt $user)
- dołączenie podstawowych stałych i klas


Jest to pierwsza moja zabawa w OOP+MVC i to chyba niestety widać.
Tworzony widok rozszerza klasę bazową, podobnie jest z modelem (klasy te posiadają metody, które są potrzebne we wszystkich modułach).
Sięgnąłem do odpowiedniej lektury i wynika z niej, że to widok ma odwoływać się do modelu. Uznałem więc, że najlepiej będzie zrobić właśnie tak - obiekt modelu jako część widoku.
Includowany na sam koniec moduł można by uznać za "pseudo-kontrolery", które mają np. taką zawartość:

  1. $id = request_var('id', 0);
  2.  
  3. if (!$id)
  4. {
  5. $view->urls->zeroDuplicate('index');
  6.  
  7. $countNews = $view->model->countNews();
  8. $news = $view->model->getNewsByPage();
  9.  
  10. while ($row = $db->sql_fetchrow($news)) //#1
  11. {
  12. $view->renderNews($row);
  13. }
  14. $db->sql_freeresult($news);
  15.  
  16. $view->urls->generatePagination($page, $config['portal_news_display'], $countNews, 3, '', 'index');
  17. }
  18. else
  19. {
  20. $news = $view->model->getNewsById($id);
  21.  
  22. if (!$news) die();
  23. $view->urls->zeroDuplicate('news', $news);
  24. $view->model->updateNewsReadsById($id);
  25.  
  26. $view->renderNews($news);
  27. }
  28.  
  29. $view->renderPage();


Jak widać, tak naprawdę w nich z modelu do widoku trafiają dane, które są potrzebne. Wg. tego co czytałem, kontroler nie powinien zachowywać się jako taki pośrednik, więc coś tu nie gra. Poza tym fragment który zaznaczyłem jako #1 - czy to ma sens leżeć w takim miejscu? Nawet gdyby to było w widoku, jakoś nie pasuje. Wydaje mi się, że powinienem poprzez model zwrócić tutaj gotową tablicę z danymi a nie uchwyt do wyniku zapytania, no ale pewności nie mam.

Kolejna rzecz, w bazowej klasie modelu mam metodę getPanels, która ma za zadanie pobrać zawartość sidebara na stronie na potrzeby metody renderPage klasy bazowej widoku. Najlepiej aby pobrane dane od razu zostały zawartością widoku. Niestety przy takiej konstrukcji dostęp do jego właściwości mam dopiero po użyciu słowa kluczowego global, a to już jest chyba zupełnie bez sensu. Nie mam natomiast pojęcia jak metoda modelu mogłaby się odwołać do właściwości widoku (o ile to nie jest równie bez sensu, bo chyba powinien zwracać wyniki zamiast modyfikować widok, z tym że zastanawiam się nad tym po co 2 razy te same dane trzymać). Tak samo wygląda zresztą sprawa z obiektami które daje mi do dyspozycji phpBB3 - muszę używać global. No chyba że by je przypisać do tych moich obiektów, bo o ile dobrze rozumiem, bez użycia słowa clone nie będę tutaj niczego marnotrawił (tak z głównym pliku wygląda sprawa z $db).

Nie wiem również, czy zamykać i to i to w obiekcie kontrolera (tworzony z modułu + bazowy) czyli np.:
  1. $controller->view = loader::view($modules[$module]['view']);
  2. $controller->model = loader::model($modules[$module]['model']);


Z tym, że jeżeli przeniosę zapytania z modelu do widoków, to albo używam global, albo będą one wyglądać raczej tak:

  1. $news = parent::model->getNewsById($id);


Czyli znowu kontroler robi się pośrednikiem pomiędzy widokiem a modelem...
Zyx
Masz rację, zwracanie uchwytu do zapytania to nienajlepszy pomysł. Treść if ... else powinna być właśnie częścią widoku, aczkolwiek nie cała. W drugim bloku masz przecież:

Kod
$view->model->updateNewsReadsById($id);


Czy jest to w jakikolwiek sposób związane z wyświetlaniem? Nie, zatem zostawiamy w kontrolerze. Ja to wszystko implementuję w ten sposób, że szablony itd. daję pod widok, czyli dopiero widok decyduje o tym, czy w ogóle korzystać z szablonów, a jeśli tak, to jakich i jak wstawić do nich dane.

Nawiasem mówiąc MVC to nie alfa i omega. Jest jeszcze mnóstwo innych wzorców smile.gif.
lukasamd
No tak, ale wstyd byłoby go nie znać, a bez praktyki nie da rady smile.gif


Wczoraj cały wieczór to męczyłem. Z tym parent to się wygłupiłem bo przecież gdy mam np. $view->model->costam(), to rodzicem modelu jest jego klasa bazowa a nie widok... Problem globalnych obiektów z phpBB3 rozwiązałem w taki sposób:

main/view.php
  1. (...)
  2. public function __construct()
  3. {
  4. global $auth, $user;
  5.  
  6. $this->user = $user;
  7. $this->auth = $auth;
  8. (...)


Zamieniłem też moduł na kontroler i przykładowy wygląda tak:

controllers/controllerNews.php
  1. class controllerNews extends controller
  2. {
  3. public function baseAction()
  4. {
  5. $this->index();
  6. }
  7.  
  8.  
  9. public function index()
  10. {
  11. $this->page = (isset($_GET['page']) && $_GET['page'] > 0) ? $_GET['page'] : 1;
  12.  
  13. $this->view = loader::view('News');
  14. $this->view->urls = new urls(true);
  15. $this->view->urls->zeroDuplicate('index');
  16.  
  17. $this->view->model = loader::model('News');
  18.  
  19. $this->view->renderNewsList($this->page);
  20. $this->view->renderPage();
  21. }
  22.  
  23.  
  24. public function view()
  25. {
  26. $this->id = request_var('id', 0);
  27.  
  28. $this->view = loader::view('News');
  29. $this->view->urls = new urls(true);
  30. $this->view->model = loader::model('News');
  31.  
  32. $this->view->renderNews($this->id);
  33. $this->view->model->updateNewsReadsById($this->id);
  34. $this->view->renderPage();
  35. }
  36. }


Tutaj mam pytanko, jak widać obie metody ładują ten sam widok i ten sam model - warto to wrzucać do konstruktora tego kontrolera?
Nie wykluczam oczywiście, że w wypadku innych kontrolerów coś takiego nie będzie możliwe, więc nie wiem czy to minimalizacja, czy robienie bałaganu.

Jak widać, w wypadku akcji index (czyli strona główna, lista newsów) jest z url'i wywoływana metoda zeroDuplicate - sprawdza ona, czy adres jest poprawny i jeżeli nie, robi redirecta.
To samo zachodzi również w drugiej akcji, ale obecnie jest zaszyte w view->renderNews() bo... aby sprawdzić czy adres newsa jest prawidłowy, muszę mieć jego dane, a one są pobierane przez widok właśnie w tej metodzie:

views/viewNews.php
  1. public function renderNews($id)
  2. {
  3. $content = $this->model->getNewsById($id);
  4. if ($content)
  5. {
  6. $this->urls->zeroDuplicate('news', $content);


No i teraz mam dylemat... w sumie myślę, że powinienem to jednak przenieść do kontrolera i dzięki if'owi wybierać, jeżeli znaleziono news:
- sprawdzam adres
- wyświetlam newsa
- aktualizacja licznika czytań
Jeżeli nie ma to z głównej klasy widoku wywołuję metodę np. renderError (której de facto nie ma jeszcze, ale właśnie uznałem że tak by można zrobić).
Czyli mniej więcej tak by to wyglądało:

  1. public function view()
  2. {
  3. $this->id = request_var('id', 0);
  4.  
  5. $this->view = loader::view('News');
  6. $this->view->urls = new urls(true);
  7. $this->view->model = loader::model('News');
  8.  
  9. $this->content = $this->view->model->getNewsById($this->id); #1
  10. if ($this->content)
  11. {
  12. $this->view->urls->zeroDuplicate('news', $content); #2
  13. $this->view->renderNews($this->id);
  14. $this->view->model->updateNewsReadsById($this->id); #1
  15. }
  16. else
  17. {
  18. $this->view->renderError('notFound');
  19. }
  20. $this->view->renderPage();
  21. }


Tutaj pojawia się pytanie o sam styl - zaznaczone jako #1 - dane te są potrzebne widokowi i pobierane z niego, ale jak widać dzieje się to w kontrolerze. Oczywiście to działa, ale czy nie kłóci się z żadną zasadą? Czy nie powinienem tego pobierać / aktualizować (aktualizacja licznika czytań) poprzez kontroler? Obecnie jak widać nie ma on innego dostępu do modelu, jak przez widok, chyba lepszym rozwiązaniem będzie zrobić jednak tak:

  1. $this->model = loader::model('News');
  2. $this->view->model = $this->model;


Podobnie ma się sprawa z url'ami oznaczonymi w kodzie jako #2 - widok musi korzystać z jego metod, tego jestem pewien. Czy jednak sprawdzanie adresu przez kontroler powinno się odbywać właśnie za pośrednictwem widoku, czy lepiej zrobić tak samo jak w powyższym kodzie, czyli dać url'e najpierw do kontrolera, a potem do widoku? Tutaj nie wiem jak wygląda sprawa wydajności, czy nie tracę na takim rozwiązaniu? W sumie jeżeli tak zrobię, to chyba mam jakieć MVCU smile.gif

Ogólnie rzecz biorąc, są obiekty które potrzebne mogą być w modelu, widoku, kontrolerze. Nie wiem czy umieszczać je w jednej z tych części i przez nią używać, czy wrzucać do każdej w której są potrzebne, acz to drugie rozwiązanie wydaje mi sie strasznie bałaganiarskie.
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.