Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: prosta strona wykonana obiektowo - kilka pytań
Forum PHP.pl > Forum > PHP > Object-oriented programming
marcinek37
Postanowiłem porzucić strukturalne PHP i zacząć pisać obiektowo. Trudno się przestawić, dlatego postanowiłem poprosić Was o pomoc. Oceńcie moją pracę. Poniższy skrypt jest niesamowicie minimalistyczny, abym mógł zrozumieć moje błędy. Zacznę od przedstawienia plików:

index.php
  1. <?php
  2. include('info_functions.php'); // funkcje dla modułu
  3. include('info_theme.php'); // wizualizacja modułu
  4. include('view.php'); // wizualizacja całej strony
  5.  
  6. $info = new Info;
  7. $info->id = 1; // wartość będzie pobierana z $_GET
  8. $info->InfoOpened();
  9. $params = $info->InfoPrepare();
  10.  
  11. $content = new InfoTheme; // umieszczanie wyciągniętych informacji z bazy do wizualizacji modułu
  12. $page_content = $content->InfoView($params);
  13.  
  14. $page = new Page;
  15. $page->meta_title = 'konkretny tytul';
  16. $page->theme = 'one';
  17. $page->content = $page_content;
  18. echo $page->PageShow();
  19. ?>



info_functions.php:
  1. <?php
  2. class Info{
  3. public $id;
  4. public $title;
  5. public $text;
  6.  
  7.  
  8. public function InfoOpened(){
  9. // naliczanie odsłon w bazie danych
  10. }
  11.  
  12.  
  13. public function InfoPrepare(){
  14. // dane rzecz jasna będą wyciągane z bazy danych
  15. if($this->id == 1){
  16. $this->title = 'tytul materialu';
  17. $this->text = 'tekst materialu';
  18. }
  19. elseif($this->id == 2){
  20. $this->title = 'tytul materialu 2';
  21. $this->text = 'tekst materialu 2';
  22. }
  23. return $this;
  24. }
  25.  
  26.  
  27. }
  28. ?>



info_theme.php:
  1. <?php
  2. class InfoTheme{
  3. public $title;
  4. public $text;
  5.  
  6.  
  7. public function InfoView($params){
  8. return'<table>
  9. <tr>
  10. <td>tytuł: '.$params->title.'</td>
  11. </tr>
  12. <tr>
  13. <td>tekst: '.$params->text.'</td>
  14. </tr>
  15. </table>';
  16. }
  17.  
  18.  
  19. }
  20. ?>



view.php
  1. <?php
  2. class Page{
  3. public $meta_title = 'standardowy tytul';
  4. public $theme = 'two';
  5. public $content;
  6.  
  7.  
  8. private function PageStart(){
  9. $view .= '<html>
  10. <head>
  11. <title>'.$this->meta_title.'</title>
  12. </head>
  13. <body bgcolor="'.($this->theme == 'one' ? '#43FEFE' : '#56FE77').'">';
  14. return $view;
  15. }
  16.  
  17.  
  18. private function PageEnd(){
  19. $view .= '</body>
  20. </html>';
  21. return $view;
  22. }
  23.  
  24.  
  25. public function PageShow(){
  26. $view .= $this->PageStart();
  27. $view .= $this->content;
  28. $view .= $this->PageEnd();
  29. return $view;
  30. }
  31.  
  32.  
  33. }
  34. ?>



1. czy powyższy skrypt jest zgodny z modelem MVC?
2. czy nazwy klas, atrybutów, metod są zrozumiałe? czy przyzwyczailiście się do innych standardów?
3. czy tak skonstruowana strona będzie działać szybko? czy trzeba coś ułatwić?
4. czy macie jeszcze jakieś spostrzeżenia?
lukasz1985
ad2. Ciężko się to czyta. Z kilku powodów:

- lepiej gdybyś nazywał pliki z klasami tak według nazwy klasy np w "Info.php" klasa "Info"
- nie ma potrzeby ponawiać w nazwach metody nazwy klasy - nie "Page->ShowPage", wystarczy "Show"
- preferuję Javowy styl nazewnictwa (interCaps) - pierwsze słowa w nazwach metod i zmiennych od małej litery czli "show", a nie "Show" natomiast nazwy klas z dużej (CamelCaps) - nie jest to rzecz jasna obowiązkowe.

Nie powinieneś zwracać kodu HTML tak jak to robisz. Lepiej gdybyś ładował szablony z plików HTML i wyświetał używając buforowania wyjścia (output buffering).

marcinek37
tego zdania nie potrafię zrozumieć, możesz napisać, do dokładnie miałbym zmienić?
"lepiej gdybyś nazywał pliki z klasami tak według nazwy klasy np w "Info.php" klasa "Info" "

co do reszty dziękuję za wskazówki - dostosuję się do nich
kod HTML mimo to wolę trzymać w PHP

a jak z resztą punktów? idę w dobrym kierunku, czy popełniłem jakiś diametralny błąd?
Turson
Cytat(marcinek37 @ 16.01.2014, 22:09:20 ) *
kod HTML mimo to wolę trzymać w PHP

I to jest błąd. Oddzielaj HTML od PHP
lukasz1985
Tam jest literówka. Chodzi o to, ze mając klasę "Info" nie trzymał jej w pliku o nazwie "info_functions.php" tylko w pliku o nazwie "Info.php".

Natomiast, jeśli będziesz trzymał kod PHP w funkcjach i zwracał tak jak w przypadku klasy "page" i metody "PageStart" to będziesz miał problemy później z edycją kodu HTML w programach do edycji HTML'a. Poza tym trochę nie odpowiada to modelowi MVC bo trzymasz logikę i widok w klasie, zamiast podzielić kod wyświetlający od kodu wyświetlanego. Ja swoim frameworku zrobiłem to tak:

  1. // Pobiera widok dla określonego modułu, który później jest wyświetlany echem: echo $this->pobierz($modul, $zmienne);
  2. public function pobierz($nazwa, $zmienne = array()) {
  3. extract($zmienne); // Zamienia klucze tablicy na zmienne, które będą widoczne w zaraz "includowanym" pliku widoku.
  4. include $this->modul . $nazwa . '.php';
  5. $widok = ob_get_contents();
  6.  
  7. return $widok;
  8. }





Reszty za bardzo się doczytać nie mogę - tutaj jeszcze uwaga odnośnie nazewnictwa metod - powinny one być czasownikami w trybie rozkazującym, a nie rzeczownikami.

Co do wydajności powiedzieć trzeba, że na tym etapie to nie ma większego znaczenia. Zagadnienia cacheowania, które w PHP są głównym tematem optymalizacji stają się istotne w późniejszych etapach rozwoju systemu natomiast pisanie wydajnych algorytmów to w PHP temat prawie nieobecny chyba, że ktoś robi coś naprawdę głupiego.

Popraw kod tak, żeby dało się go ogarnąć to spróbuję powiedzieć coś więcej.
marcinek37
index.php
  1. <?php
  2. include('info.php'); // funkcje dla modułu
  3. include('info_theme.php'); // wizualizacja modułu
  4. include('view.php'); // wizualizacja całej strony
  5.  
  6. $info = new Info;
  7. $info->id = 1; // wartość będzie pobierana z $_GET
  8. $info->countInput();
  9. $params = $info->prepare();
  10.  
  11. $content = new InfoTheme; // umieszczanie wyciągniętych informacji z bazy do wizualizacji modułu
  12. $page_content = $content->show($params);
  13.  
  14. $page = new Page;
  15. $page->meta_title = 'konkretny tytul';
  16. $page->theme = 'one';
  17. $page->content = $page_content;
  18. echo $page->show();
  19. ?>


info.php
  1. <?php
  2. class Info{
  3. public $id;
  4. public $title;
  5. public $text;
  6.  
  7.  
  8. public function countInput(){
  9. // naliczanie odsłon w bazie danych
  10. }
  11.  
  12.  
  13. public function prepare(){
  14. // dane rzecz jasna będą wyciągane z bazy danych
  15. if($this->id == 1){
  16. $this->title = 'tytul materialu';
  17. $this->text = 'tekst materialu';
  18. }
  19. elseif($this->id == 2){
  20. $this->title = 'tytul materialu 2';
  21. $this->text = 'tekst materialu 2';
  22. }
  23. return $this;
  24. }
  25.  
  26.  
  27. }
  28. ?>


info_theme.php
  1. <?php
  2. class InfoTheme{
  3. public $title;
  4. public $text;
  5.  
  6.  
  7. public function show($params){
  8. return'<table>
  9. <tr>
  10. <td>tytuł: '.$params->title.'</td>
  11. </tr>
  12. <tr>
  13. <td>tekst: '.$params->text.'</td>
  14. </tr>
  15. </table>';
  16. }
  17.  
  18.  
  19. }
  20. ?>


view.php
  1. <?php
  2. class Page{
  3. public $meta_title = 'standardowy tytul';
  4. public $theme = 'two';
  5. public $content;
  6.  
  7.  
  8. private function showStartHTML(){
  9. $view .= '<html>
  10. <head>
  11. <title>'.$this->meta_title.'</title>
  12. </head>
  13. <body bgcolor="'.($this->theme == 'one' ? '#43FEFE' : '#56FE77').'">';
  14. return $view;
  15. }
  16.  
  17.  
  18. private function showEndHTML(){
  19. $view .= '</body>
  20. </html>';
  21. return $view;
  22. }
  23.  
  24.  
  25. public function show(){
  26. $view .= $this->showStartHTML();
  27. $view .= $this->content;
  28. $view .= $this->showEndHTML();
  29. return $view;
  30. }
  31.  
  32.  
  33. }
  34. ?>



1. zmieniłem nazwę pliku - czy jest dobrze?
2. zmieniłem nazwy metod - czy jest dobrze?
3. czy nazwy plików info_theme.php oraz view.php są akceptowalne?
4. jak pewnie zauważyłeś, kod HTML mam w dwóch ww. plikach; staram się zrozumieć wykorzystanie Twojej metody pobierz(), ale nie jestem w stanie; poza tym metoda ta będzie wykorzystywana w dziesiątkach plików, więc trzeba byłoby stworzyć dodatkowy plik, np. main.php
możesz funkcję tę uprościć i pokazać na przykładzie pliku info_theme.php jej działanie?
zawsze mnie martwiło umieszczanie kodu HTML w sugerowany przez Was sposób, bo przecież każdy może odgadnąć ścieżkę pliku i wejść do niego, poznając go od wewnątrz
lukasz1985
No więc tak...

Co do nazw metod - znacznie lepiej.
Pozostaje nazwa pliku "view.php", która powinna brzmieć "page.php" albo klasa "Page" powinna otrzymać nazwę "View". Osobiście wolałbym żeby w rezultacie mieć klasę "View" w pliku "view

.php"

Masz problemy z nadawaniem właściwych, ścisłych i jednoznacznych nazw i to trochę jest gubiące.
  1.  
  2. include('info.php'); // funkcje dla modułu
  3.  
  4. include('info_theme.php'); // wizualizacja modułu
  5.  
  6. include('view.php'); // wizualizacja całej strony
  7.  
  8.  
  9. /////// dobzre jest robić nawiasy przy inicjalizacji nowych obiektów
  10. //$info = new Info;
  11. $info = new Info();
  12.  
  13. $info->id = 1; // wartość będzie pobierana z $_GET
  14.  
  15. $info->countInput();
  16. ////// niepotrzebnie przypisujesz ten sam obiekt do nowej zmienne
  17. //$params = $info->prepare(); // mogłoby być po prostu $info->prepare();
  18. $info->prepare();
  19.  
  20. ///// czemu $content, a nie $theme ?
  21. //$content = new InfoTheme; // umieszczanie wyciągniętych informacji z bazy do wizualizacji modułu
  22. $theme = new InfoTheme();
  23.  
  24. ///// znów nazwa sugerująca coś innego - metoda nie powinna się nazywać "get" albo, lepiej "load"?
  25. //$page_content = $theme->show($info);
  26. $page_content = $theme->load($info);
  27.  
  28.  
  29. $page = new Page;
  30.  
  31. $page->meta_title = 'konkretny tytul';
  32.  
  33. $page->theme = 'one';
  34.  
  35. $page->content = $page_content;
  36.  
  37. echo $page->show();




Trochę zbędne wydaje mi się tworzenie osobnych klas dla widoku i "theme'u". Osobny widok powinien być osobną formą
reprezentacji informacji.

marcinek37
zrobiłem wszystko dokładnie tak, jak zalecałeś
faktycznie kod jest wyraźniejszy:

index.php
  1. <?php
  2. include('info.php'); // funkcje dla modułu
  3. include('info_theme.php'); // wizualizacja modułu
  4. include('view.php'); // wizualizacja całej strony
  5.  
  6. $info = new Info();
  7. $info->id = 1; // wartość będzie pobierana z $_GET
  8. $info->countInput();
  9. $info->prepare();
  10.  
  11. $theme = new InfoTheme(); // umieszczanie wyciągniętych informacji z bazy do wizualizacji modułu
  12. $view_content = $theme->load($info);
  13.  
  14. $view = new View();
  15. $view->meta_title = 'konkretny tytul';
  16. $view->theme = 'one';
  17. $view->content = $view_content;
  18. echo $view->show();
  19. ?>


info.php
  1. <?php
  2. class Info{
  3. public $id;
  4. public $title;
  5. public $text;
  6.  
  7.  
  8. public function countInput(){
  9. // naliczanie odsłon w bazie danych
  10. }
  11.  
  12.  
  13. public function prepare(){
  14. // dane rzecz jasna będą wyciągane z bazy danych
  15. if($this->id == 1){
  16. $this->title = 'tytul materialu';
  17. $this->text = 'tekst materialu';
  18. }
  19. elseif($this->id == 2){
  20. $this->title = 'tytul materialu 2';
  21. $this->text = 'tekst materialu 2';
  22. }
  23. return $this;
  24. }
  25.  
  26.  
  27. }
  28. ?>


info_theme.php
  1. <?php
  2. class InfoTheme{
  3. public $title;
  4. public $text;
  5.  
  6.  
  7. public function load($params){
  8. return'<table>
  9. <tr>
  10. <td>tytuł: '.$params->title.'</td>
  11. </tr>
  12. <tr>
  13. <td>tekst: '.$params->text.'</td>
  14. </tr>
  15. </table>';
  16. }
  17.  
  18.  
  19. }
  20. ?>


view.php
  1. <?php
  2. class View{
  3. public $meta_title = 'standardowy tytul';
  4. public $theme = 'two';
  5. public $content;
  6.  
  7.  
  8. private function showStartHTML(){
  9. $view .= '<html>
  10. <head>
  11. <title>'.$this->meta_title.'</title>
  12. </head>
  13. <body bgcolor="'.($this->theme == 'one' ? '#43FEFE' : '#56FE77').'">';
  14. return $view;
  15. }
  16.  
  17.  
  18. private function showEndHTML(){
  19. $view .= '</body>
  20. </html>';
  21. return $view;
  22. }
  23.  
  24.  
  25. public function show(){
  26. $view .= $this->showStartHTML();
  27. $view .= $this->content;
  28. $view .= $this->showEndHTML();
  29. return $view;
  30. }
  31.  
  32.  
  33. }
  34. ?>



1. oddzieliłem widok modułu od widoku całej strony, bo planuję później dostosować system do takiej struktury:
katalog główny: plik info.php (aktualnie index.php)
katalog modules, a w nim katalog info, a w nim class.info.php (aktualnie info.php)
katalog theme, a w nim modules, a w nim info_view.php (aktualnie view.php) + view.php

czy taka struktura jest w porządku? wiadomo, że jak dobrze skonstruuję moduł podstron, będę pracował nad aktualnościami, mini sklepem itp.

2. jeśli ww. struktura pozwala na lepsze poukładanie widoków, to tylko powiedz jak, a od razu to zrobię

3. czy możemy wrócić do funkcji pobierz() i moich wcześniejszych uwag związanych z funkcją?

// godzina pisania w tym wątku dała mi więcej niż kilka godzin czytania w internecie artykułów...
lukasz1985
Natomiast, jeśli chodzi o ładowanie widoków z zewnętrznych plików z HTML'em to sprawa wygląda tak, że mam następującą klasę:

  1. class Widok {
  2.  
  3. public function pobierz($sciezka, $zmienne = array()) {
  4. // Załadowanie zmiennych z tablicy $zmienne
  5. extract($zmienne);
  6.  
  7. include $sciezka;
  8. $tresc = ob_get_contents();
  9.  
  10. return $tresc;
  11. }
  12.  
  13. public function wyswietl($nazwa, $zmienne = array()) {
  14. echo $this->pobierz($nazwa, $zmienne);
  15. }
  16.  
  17.  
  18. }



i teraz gdybym miał taki plik HTML "przyklad.html":

  1. Dzisiaj jest: <br>
  2. <?php echo $data; ?>
  3.  
  4. A oto komunikat: <br>
  5. <?php echo $komunikat; ?>
  6.  
  7. </body>
  8. </html>
  9.  



to mógłbym zrobić w swoim kodzie coś takiego:
  1. $widok = new Widok();
  2. $zmienne = array("data" => "19.02.2014", "komunikat" => "Witaj swiecie");
  3.  
  4. $widok->wyswietl("przyklad.html", $zmienne);
  5.  


.. i uzyskałbym w rezultacie html, w którym odpowiednie zmienne zostały wypełnione danymi z tablicy $zmienne:

  1. Dzisiaj jest: <br>
  2. 19.02.2014
  3.  
  4. A oto komunikat: <br>
  5. Witaj swiecie
  6.  
  7. </body>
  8. </html>
  9.  



Oczywiście to jest dość prosty model takiego systemu. Weź pod uwagę, ze te widoki można zagnieżdzać w sobie używając metody "pobierz" klasy Widok, która zwraca sparsowany HTML, który z kolei można włożyć do innej tablicy i znów użyć jako parametru aby wyswietlić inny widok.


Na temat metody "pobierz":

To co wymaga pewnej uwagi to funkcja "extract" w metodzie "pobierz". Co ona robi? Zamienia klucze tablicy na nazwy zmiennych, przykład:

  1. $arr = array("a" => 5, "b" => 10);
  2. extract ($arr);
  3. echo $a; // wypisze 5.
  4.  


Kolejno widzisz


ta linia powoduje uruchomienia buforowania wyjścia - oznacza to, ze cokolwiek, co "zaincludujesz" nie zostanie od razu wypisane na ekranie. A includowane są po prostu pliki html ze wstawkami kodu php (jak w przykładzie z plikiem "przyklad.html". Pod te zmienne w includowanych plikach wstawiane są wartości (kod php jest parsowany) ale zawartość, jak już powiedziałem, nie jest wyświetlana, ale zapisywana w buforze.

I tutaj pojawia się funkcja

jej zadanie jest dość proste - zwraca wszystko co jest w buforze.

Na końcu wyłączam buforowanie wyjścia i czyszczę bufor


i zwracam zwartość zapisaną do zmiennej "tresc"



Wybacz ale na dzisiaj tyle, jestem już zmęczony. Cieszę sie, że ten temat Ci pomógł smile.gif
marcinek37
1. bardzo Ci dziękuję - wyjaśniłeś mi niesamowicie dużo
2. Twoje wskazówki zmusiły mnie do zmiany struktury plików i mocnej ingerencji w jej wnętrzę, aby nie narobił się bałagan; poniżej przedstawiam nowe wersje plików; jeśli będzie Ci wygodniej, podaję linka do ich pobrania: http://www.speedyshare.com/7Z6Nt/php5.rar (spokojnie, bez wirusów smile.gif )

index.php
  1. <?php
  2. include('main.php'); // "silniczek"
  3. include('view/view.php'); // wizualizacja całej strony
  4.  
  5. // tu będzie kod sprawdzający poprawność zmiennej $_GET['m']
  6. $module = $_GET['m'] ? $_GET['m'] : 'info';
  7. include('modules/'.$module.'/'.$module.'.php');
  8. ?>


main.php
  1. <?php
  2. // łączenie z bazą danych
  3.  
  4. class LoadView{
  5.  
  6. public function load($path, $settings = array()){
  7. extract($settings);
  8. include('view/html/'.$path.'.php');
  9. $content = ob_get_contents();
  10. return $content;
  11. }
  12.  
  13. }
  14. ?>


modules/info/class.Info.php
  1. <?php
  2. class Info{
  3. public $id;
  4. public $title;
  5. public $text;
  6.  
  7.  
  8. public function countInput(){
  9. // naliczanie odsłon w bazie danych
  10. }
  11.  
  12.  
  13. public function prepare(){
  14. // dane rzecz jasna będą wyciągane z bazy danych
  15. if($this->id == 1){
  16. $this->title = 'tytul materialu';
  17. $this->text = 'tekst materialu';
  18. }
  19. elseif($this->id == 2){
  20. $this->title = 'tytul materialu 2';
  21. $this->text = 'tekst materialu 2';
  22. }
  23. return $this;
  24. }
  25.  
  26.  
  27. }
  28. ?>


modules/info/info.php
  1. <?php
  2. include('modules/info/class.Info.php'); // Controller (wg wzorca MVC)
  3. include('modules/info/info_theme.php'); // View (wg wzorca MVC)
  4.  
  5. $info = new Info();
  6. $info->id = 1; // wartość będzie pobierana z $_GET
  7. $info->countInput();
  8. $info->prepare();
  9.  
  10. $theme = new InfoTheme(); // umieszczanie wyciągniętych informacji z bazy do wizualizacji modułu
  11. $theme->title = $info->title;
  12. $theme->text = $info->text;
  13. $view_content = $theme->load();
  14.  
  15. $view = new View();
  16. $view->meta_title = 'konkretny tytul';
  17. $view->theme = 'one';
  18. $view->content = $view_content;
  19. echo $view->show();
  20. ?>


modules/info/info_theme.php
  1. <?php
  2. class InfoTheme{
  3. public $title;
  4. public $text;
  5.  
  6. public function load(){
  7. $view = new LoadView();
  8. $settings = array(
  9. 'title' => $this->title,
  10. 'text' => $this->text
  11. );
  12. return $view->load('info', $settings);
  13. }
  14.  
  15.  
  16. }
  17. ?>


view/view.php
  1. <?php
  2. class View{
  3. public $meta_title = 'standardowy tytul';
  4. public $theme = 'one';
  5. public $content;
  6.  
  7. public function show(){
  8. $view = new LoadView();
  9. $settings = array(
  10. 'meta_title' => $this->meta_title,
  11. 'color' => $this->theme == 'one' ? '#43FEFE' : '#56FE77',
  12. 'content' => $this->content
  13. );
  14. return $view->load($this->theme, $settings);
  15. }
  16.  
  17. }
  18. ?>


view/html/one.php
  1. <html>
  2. <head>
  3. <title><? echo $meta_title; ?></title>
  4. </head>
  5. <body bgcolor="<? echo $color; ?>">
  6.  
  7. <? echo $content; ?>
  8.  
  9. </body>
  10. </html>


view/html/info.php
  1. <table>
  2. <tr>
  3. <td style="background-color: #FFFFFF">tytuł: <? echo $title; ?></td>
  4. </tr>
  5. <tr>
  6. <td>tekst: <? echo $text; ?></td>
  7. </tr>
  8. </table>


3. trochę uprościłem Twoją funkcję i pozmianiałem nazwy, aby się w tym nie pogubić
4. czy nadane przeze mnie nazwy plików, klas, metod i atrybutów są zrozumiałe? czy sugerujesz jakieś zmiany?
5. czy dobrze zrobiłem, że rozszerzenia plików .html zmieniłem na .php? po prostu nie chcę, aby ktoś odgadł ścieżkę plików i zobaczył cząstki kodu HTML
6. wspomniałeś wcześniej, że bez sensu jest tworzenie dwóch wizualizacji - dla całej strony i dla modułu
tego nie jestem w stanie zrozumieć; dzięki temu edytując jeden plik, zmienię wszystko dookoła contentu, a tak będę musiał zmieniać kilkanaście plików albo jeszcze więcej - chyba że jest jakiś ciekawszy sposób
lukasz1985
Co do nazw plików, wciąż mam zastrzeżenia. Ale nie to jest teraz ważne. Ogólnie rzecz biorąc Twój sposób podejścia do wzorca MVC nie odpowiada pewnym standardom i mylisz kilka pojęć. Ale to nic, pewnie nie zaglądałeś jeszcze pod maskę żadnego frameworka.
Postaram się wytłumaczyć na czym to wszystko polega w skrócie:

Otóż, jak już pewnie wiesz - w modelu MVC mamy do czynienia z trzema elementami:
- Modelem - a więc warstwą, która bezpośrednio wykonuje wszystkie operacje na danych w bazie lub w innej postaci (której na razie w swoim kodzie nie masz)
- Widokiem - a więc warstwą prezentacji - plikami HTML (lub ogólniej szablonami, które w rezultacie są wyświetlane jako HTML) w przypadku stron i aplikacji internetowych. (który nie jest klasą, tak jak w Twoim przypadku, gdzie za widok odpowiedzialna jest klasa View.php, a nie zwykły plik HTML lub jakiś szablon)
oraz
- Kontrolerem - a więc warstwą, która łączy widok i model dostarczając danych z modelu do widoku w celu umożliwienia prezentacji właściwych informacji

Aby to wszystko połączyć w całość, trzeba utworzyć pewien system, najlepiej złożony z klas (jak już sam wiesz, programowanie strukturalne to przeżytek).

Zacząć należy od samej koncepcji. Wiadomo, do aplikacji musi być jakiś punkt wejścia - zazwyczaj jest to po prostu plik index.html w którym ładowany jest silnik i parsowane są dane z zapytania (zmienna $_GET) i na tej podstawie jest ładowany określony kontroler. Te dwa zadania (parsowanie $_GET i wyznaczanie kontrolera do załadowania) to w istocie tak zwany "routing", który dalej przedstawię w bardzo uproszczonej formie.
Wyznaczony kontroler, który w naszym przykładzie będzie klasą jest ładowany i wykonywana określona metoda, wyznaczana najczęściej także w procesie routingu




Przykładowo założmy, że w zapytaniu istnieją dwie zmienne: "controller" i "action" - moduł jaki ma być załadowany i akcja, jaką ten moduł ma wykonać, tak, że w rezultacie zapytanie do serwera ma formę:

"www.example.com/index.php?controller=jakisKontroller&action=jakasakcja"

To w index.php uzyskamy coś takiego:

  1. ////////////////////////////////////////
  2. // Tutaj będzie kod ładujący silnik.
  3. ////////////////////////////////////////
  4.  
  5. // Parsowanie danych z get:
  6. // Nazwa kontrolera i akcji
  7. $controllerName = $_GET["controller"];
  8. $actionName = $_GET["action"];
  9.  
  10. // Routing:
  11. // Tablica routingu, w przyszłości warto umieścić ją w innym pliku php i ladować w tym miejscu.
  12. // W tablicy routingu znajdują się informacje na temat jaki kontroler jest w jakim plilku oraz nazwa
  13. // klasy w tym pliku którą należy utworzyć
  14. $routes = array(
  15. "klienci" => array (
  16. "file" => "klienci.php",
  17. "class" => "Clients",
  18. "actions" => array(
  19. "kontakt" => "contact",
  20.  
  21. )
  22. ),
  23.  
  24. "uslugi" => array (
  25. "file" => "services.php",
  26. "class" => "Services",
  27. "actions" => array(
  28. "wyswietl" => "show"
  29. )
  30. )
  31. )
  32.  
  33. // Odczytywanie nazwy pliku z klasą kontrolera z tablicy routingu
  34. $fileName = $routes[$controllerName]["file"];
  35. // Odczytywanie nazwy klasy kontrolera z tablicy routingu na podstawie nazwy kontrollera
  36. $className = $routes[$controllerName]["class"]();
  37. // Odczytywanie nazwy metody kontrolera z tablicy routingu na podstawie nazwy akcj
  38. $methodName = $routes[$kontrollerName][$actionName];
  39.  
  40. // Koniec routingu
  41.  
  42. // Ładowanie klasy kontrolera, tworzenie instancji klasy kontrolera i wykonanie metody na tej instancji.
  43. include "/controllers/" . $fileName ;
  44. $controller = new $className();
  45. $controller->$methodName();
  46.  



Najważniejsze, żebyś przyjrzał się tablicy routingu i jak wygląda odczytywanie nazw: pliku, klasy i akcji kontrolera. Zauważ też jak tworzę nowe obiekty - przy pomocy zmiennych, za którymi stawiam "()" (tak, tak można w PHP).

Jest to przykład banalny w zasadzie. W rzeczywistości routing jest nieco bardziej zaawansowany. Dodatkowo poza wymienionymi danymi w $_GET[] często są jeszcze inne parametry - sama nazwa kontrolera i akcji często może nie wystarczyć. Bo przypuścmy, że chcemy na przyklad pokazać pojedynczą usługę. Wtedy musielibyśmy stworzyć na przykład metodę showOne, która przyjmuje jakiś
identyfikator usługi (np. liczbę). Wtedy w get musielibyśmy mieć nazwe kontrolera, nazwę akcji i parametr, np:

"www.example.com/index.php?controller=jakisKontroller&action=jakasakcja&id=1"

Jednak na razie pozostanę przy tym co jest, bo chodzi aby omówić mechanizm, jak to zazwyczaj działa.


Kolejnym etapem jest zdefiniowanie abstrakcyjnej klasy kontrolera, z której dziedziczą wszystkie konkretne kontrolery.
Kontroler powinien móc robić dwie rzeczy, o których już mówiłem:
- posługiwać się modelem
- wyświetlać widok

Do wyświetania widoku użyjemy prostej metody, która będzie korzystała z buforowania wyjścia, natomiast model narazie
będzie tylko polem wewnątrz klasy kontrolera, które będzie inicjalizowane w konstruktorze klas pochodnych (konkretnych kontrolerach):


  1. abstract class Controller {
  2. private $model;
  3.  
  4. protected function displayView($view, $variables) {
  5. echo $this->getView($view, $variables);
  6. }
  7.  
  8. protected function getView($view, $variables){
  9. // Załadowanie zmiennych z tablicy $variables
  10. extract($variables);
  11.  
  12. include $this->$view;
  13. $contents = ob_get_contents();
  14.  
  15. return $contents;
  16. }
  17.  
  18. }
  19.  



Teraz możemy utworzyć już przykładowy kontroler (klienci.php):

  1. class Clients extends Controller() {
  2. public function contact() {
  3. this->displayView("contact_form.php"); // a w pliku contact_form.php - na przykład formularz kontaktowy.
  4. }
  5.  
  6.  
  7. }



Można teraz wywołać zapytanie:

www.example.com/index.php?controller=klienci&action=kontakt

co spowoduje wyświetlenie formularza kontaktowego.





Natomiast w kwestii modelu jest pewna dowolność, która zależy od tego jak zaawansowana ma być strona/aplikacja, którą tworzymy. W prostych przypadkach może to być zwykła klasa łącząca z bazą danych:

http://pastebin.com/fmDGtWZT


W bardziej złożonych przypadach modele będą klasami z metodami pozwalającymi na wykonanie bardziej skomplikowanych zadań i prawdopodobnie cache'ującymi wyniki, a ich polem będzie odwołanie do instancji takiej klasy jak powyżej.

Mając przykładowo tę klasę "Services", w sytuacji gdy posługujemy się modelem w formie takiej klasy, umożliwiającej dostęp do bazy danych, będzie ona wyglądała tak:

  1.  
  2. class Services extends Controller{
  3. __construct() {
  4. $this->model = new Database();
  5. }
  6.  
  7. public function show() {
  8. $all = $this->model->getAll(); // pobiera wszystkie rekordy
  9. this->loadView("services_list.php", $all); // wyswietla widok z listą wszystkich usług
  10. }
  11.  
  12.  
  13. }
  14.  



marcinek37
przepraszam, że odpisuję dopiero teraz, ale naprawdę nie miałem możliwości zrobić tego wcześniej;
tutaj jest poprzednia wersja plików: http://www.speedyshare.com/7Z6Nt/php5.rar

to, co napisałeś, staram się ułożyć w głowie, ale nie potrafię, bo oparłeś to na przykładzie, którego nie mam jak sprawdzić, dlatego spróbujmy dostosować mój kod do schematu MVC.

mamy model i widok, które są łączone w kontrolerze

wychodzi na to, że kontroler to tak naprawdę plik index.php, który deflautowo ładuje plik "modules/info/info.php"
model to "modules/info/info.php", który odwołuje się do klasy Info()
a widok to klasa View() oraz InfoTheme()

wszystko jest w osobnych plikach i jest chyba proste do zarządzania

1. jeśli źle myślę, czy możesz mi napisać, co mam w takim razie zmienić?
2. jeśli nazwy plików, klas, metod i atrybutów Ci nie odpowiadają, to po prostu napisz, a je zmienię - chcę przyjąć jak najbardziej oczywiste i akceptowalne standardy nazewnictwa
3. czy dobrze zrobiłem, że rozszerzenia plików .html zmieniłem na .php? po prostu nie chcę, aby ktoś odgadł ścieżkę plików i zobaczył cząstki kodu HTML
4. wspomniałeś wcześniej, że bez sensu jest tworzenie dwóch wizualizacji - dla całej strony i dla modułu
tego nie jestem w stanie zrozumieć; dzięki temu edytując jeden plik, zmienię wszystko dookoła contentu, a tak będę musiał zmieniać kilkanaście plików albo jeszcze więcej - chyba że jest jakiś ciekawszy sposób

doszedłem już tak daleko, że zależy mi, aby w końcu zrozumieć ideę MVC na najprostszym moim przykładzie; dużo przeczytałem na ten temat także na przyklejonym temacie na forum, ale nadal mam trudności z zastosowaniem tej wiedzy do mojego banalnego kodu - a jak w nim tego nie wykonam, to chyba nigdzie
lukasz1985
Co do nazewnictwa - plik "main.php" zawiera klasę "LoadView". Dwa błędy: plik nie ma nazwy klasy, którą zawiera. Każda klasa powinna być zawarta w osobnym pliku nazwanym według jej własnej nazwy.
Drugim błędem jest to, że klasę nazwałeś czasownikiem "Load view" ("załaduj widok"). Jest to nieprawidłowe ponieważ instancje klasy są postrzegane jako obiekty. Prawidłowo powinno być "ViewLoader" ("ładowacz widoków"). To samo dotyczy pewnych

Kolejnym błędem jest to, że w tej klasie masz tylko metodę, a klasa nie zawiera żadnych pól z danymi. To trochę moja wina bo przedstawiłem Ci swoją wersję klasy "Widok", nie mówiąc o tym, że to okrojona wersja. Metoda mogłaby by być zwykłą funkcją lub ewentualnie, metodą statyczną.

Tak na marginesie: czasami piszę "funkcja w klasie" - jest to po prostu inne określenie na słowo "metoda".

Kolejną sprawa: "widok" we wzorcu MVC nie jest klasą. Widok, jest jedynie reprezentacją i jedynie kodem HTML ze wstawkami PHP w miejscach gdzie mają być wyświetlane zmienne. Chodzi o to, że Twoja klasa "View" z punktu widzenia takiego podejścia ściśle rzecz biorąc - nie ma racji bytu.
Kontroler ładuje widok, ale nie przez tworzenie nowej instancji klasy "View".

Elementy reprezentacji są dzielone na pliki HTMLowe (nie ważne jest tutaj rozszerzenie pliku bo i tak później można dostęp z zewnątrz regulować przez plik .htaccess). Później można je w sobie zagnieżdzać.

Teraz chodzi o to, że musimy mieć jakąś klasę, która reprezentuje kontroler. Kontroler, który będzie mógł załadować widok samodzielnie, poprzez metodę zabezpieczoną ("protected"). To samo dotyczy modelu. Tutaj mamy do czynienia z rzeczami banalnymi więc nie będą to jakieś wielkie klasy, a abstrakcyjna klasa modelu w tym momencie jest pusta.

Przykład jest w tym pliku:

https://dl.dropboxusercontent.com/u/40322040/MVC.zip

Przeanalizuj sobie ten kod dobrze.

Chodzi o to, zebyś zrozumiał, ze każda akcja w kontrolerze wiąże się z pobieraniem danych z modelu i wyświetlaniem ich w widoku (który nie jest klasą). Natomiast komunikacja w drugą stronę odbywa się poprzez przesyłanie danych formularzy i zmiennych zapytania. Mając takie parametry można wykonywać różne operacje na modelach (usuwać czy dodawać klientów).
marcinek37
przepraszam, że odpisuję dopiero teraz, ale byłem zajęty lekturą wszystkich tematów z adresu: Temat: MVC
na tej podstawie stworzyłem łopatologiczne definicje oraz przerobiłem skrypt (dla ułatwienia można go pobrać tutaj: http://www.speedyshare.com/FHVGe/mvc.rar)
Twoje przykłady dużo mi dały, ale robiłem wszystko, aby to uprościć

router: sprawdza $_GET, $_POST, $_SESSION, $COOKIE i na tej podstawie włącza kontroler
model: ma charakter abstrakcyjny, zajmuję się przetworzeniem informacji, które pojawią się na stronie, wykonuje też najrozmaitsze polecenia, łączy się z bazą danych, obrabia pliki .xml itp.
widok: zajmuje się prezentacją informacji
kontroler: przyjmuje żądanie, wywołuje model, otrzymuje od niego informacje i przekazuje do widoku; kontroler nadaje sens modelowi; jest komunikatorem między modelem a widokiem, tylko w nim powinno dochodzić do kontaktu między modelem a widokiem

index.php
  1. <?php
  2. include_once('classes/controller.class.php');
  3.  
  4. $controller = new Controller();
  5. $controller->select();
  6. ?>


classes/controller.class.php
  1. <?php
  2. class Controller{
  3. public $module;
  4.  
  5. public function __construct(){
  6. $module = $_GET['m'] ? $_GET['m'] : 'info'; // prymitywne sprawdzanie zmiennej $_GET['m'], której zadaniem będzie wyznaczanie, jaki moduł z katalogu "modules" ma się ładować
  7. $this->module = $module;
  8. }
  9.  
  10. public function select(){
  11. include_once('classes/viewloader.class.php'); // jak próbowałem to włożyć do __autoload(), wyskakiwał błąd
  12. include_once('view/view.php'); // ładowanie generalnej wizualizacji strony (elementy niezmienialne, czyli top, menu, stopka)
  13. include_once('modules/'.$this->module.'/'.$this->module.'.controller.php');
  14. }
  15.  
  16. }
  17. ?>


classes/viewloader.class.php
  1. <?php
  2. class ViewLoader{
  3. // w przyszłości znajdą się jakieś parametry
  4.  
  5. public function load($path, $settings = array()){
  6. extract($settings);
  7. include('view/html/'.$path.'.php');
  8. $content = ob_get_contents();
  9. return $content;
  10. }
  11.  
  12. }
  13. ?>


modules/info/info.class.php
  1. <?php
  2. class Info{
  3. public $id;
  4. public $title;
  5. public $text;
  6. public $theme = 'one';
  7.  
  8. public function countInput(){
  9. // naliczanie odsłon w bazie danych
  10. }
  11.  
  12. public function prepare(){
  13. // dane rzecz jasna będą wyciągane z bazy danych
  14. if($this->id == 1){
  15. $this->title = 'tytul materialu';
  16. $this->text = 'tekst materialu';
  17. }
  18. elseif($this->id == 2){
  19. $this->title = 'tytul materialu 2';
  20. $this->text = 'tekst materialu 2';
  21. }
  22. return $this;
  23. }
  24.  
  25. public function load(){
  26. $view = new ViewLoader();
  27. $settings = array(
  28. 'title' => $this->title,
  29. 'text' => $this->text
  30. );
  31. return $view->load('info', $settings);
  32. }
  33.  
  34. }
  35. ?>


modules/info/info.controller.php
  1. <?php
  2. include_once('modules/info/info.class.php');
  3.  
  4. $info = new Info();
  5. $info->id = 1; // wartość będzie pobierana z $_GET
  6. $info->countInput();
  7. $info->prepare();
  8. $infoContent = $info->load();
  9.  
  10. $view = new View();
  11. $view->meta_title = $info->title;
  12. $view->theme = $info->theme;
  13. $view->content = $infoContent;
  14. echo $view->show();
  15. ?>


view/view.php
  1. <?php
  2. class View{
  3. public $meta_title = 'standardowy tytul';
  4. public $theme = 'one';
  5. public $content;
  6.  
  7. public function show(){
  8. $view = new ViewLoader();
  9. $settings = array(
  10. 'meta_title' => $this->meta_title,
  11. 'color' => $this->theme == 'one' ? '#43FEFE' : '#56FE77',
  12. 'content' => $this->content
  13. );
  14. return $view->load($this->theme, $settings);
  15. }
  16.  
  17. }
  18. ?>


view/html/one.php
  1. <html>
  2. <head>
  3. <title><? echo $meta_title; ?></title>
  4. </head>
  5. <body bgcolor="<? echo $color; ?>">
  6.  
  7. <? echo $content; ?>
  8.  
  9. </body>
  10. </html>


view/html/info.php
  1. <table>
  2. <tr>
  3. <td style="background-color: #FFFFFF">tytuł: <? echo $title; ?></td>
  4. </tr>
  5. <tr>
  6. <td>tekst: <? echo $text; ?></td>
  7. </tr>
  8. </table>



1. uprościłem routing, w index.php jest ładowana klasa, która sprawdza zmienną $_GET['m'] i na tej podstawie ładuje odpowiedni kontroler, którego kształt będzie całkowicie niezależny od innych modułów
2. być może pewne rzeczy robię pod górkę, możesz mi na to zwrócić uwagę, dla mnie jest ważne, aby sam schemat katalogów, nazewnictwo było zrozumiałe dla innych programistów oraz cała aplikacja była zgodna z MVC
3. jak pewnie zauważyłeś, najpierw pobieram kod HTML contentu, wprowadzam do niego wartości, a następnie content wprowadzam do ogólnego zarysu strony - można to zrobić prościej? ale w ten sposób, abym mógł szybko zmieniać kod HTML w jednym miejscu
4. wiem, że mógłbym nie tworzyć klasy View(), ale utworzyłem ją specjalnie, aby w prosty sposób kontrolować parametry ładowanego szablony; w przeciwnym razie musiałbym powtarzać ten sam kod w wielu miejscach
lukasz1985
Może zacznę od tego:

3. jak pewnie zauważyłeś, najpierw pobieram kod HTML contentu, wprowadzam do niego wartości, a następnie content wprowadzam do ogólnego zarysu strony - można to zrobić prościej? ale w ten sposób, abym mógł szybko zmieniać kod HTML w jednym miejscu

Właśnie widzisz, jest smile.gif

Polega to na tym, że pobierasz najpierw jeden widok z jakimiś tam zmiennymi i zapisujesz go do innej zmiennej:

  1. // Posługując sie Twoją klasą "ViewLoader"
  2. $loader = new ViewLoader();
  3. $parametry = array(.. tu jakieś parametry ...);
  4. $tresc = $loader->load("strona", $parametry);


I przesłaniu jej jako parametr przy ładowaniu innej strony:

  1. // Kontynuacja...
  2.  
  3. $szablon = $loader->load("szablon", array("strona" => $tresc));
  4. echo $szablon;


EDIT: Zauważ, że możesz w ten sposób zagnieżdżać w sobie fragmenty HTMLa jak tylko chcesz. Po prostu pobierasz szablon do jednej zmiennej i tą zmienną przesyłasz jako parametr przy ładowaniu
innego szablonu. Z kolei ten szablon znów możesz wykorzystać przy ładowaniu kolejnego szablonu, i tak w kółko.
Zauważ też, że możesz załadować kilka plików HTMLi i potem przy ładowaniu głownego szablonu wykorzystać je jako poszczególne elementy na stronie:

  1. // Posługując sie Twoją klasą "ViewLoader"
  2.  
  3. $loader = new ViewLoader();
  4. $naglowek = $loader->load("naglowek");
  5. $stopka = $loader->load("stopka");
  6. $parametry = array(
  7. "naglowek" => $naglowek,
  8. "stopka" => $stopka
  9. );
  10.  
  11. $strona = $loader->load("strona", $parametry);



Co do reszty...robisz to trochę naopak. Najpierw ładujesz kontroler i w nim parsujesz zmienne zapytania GET, POST i tam odczytujesz sesje itp.
Chodzi o to, żeby to zrobić odwrotnie - na podstawie zmiennych zapytania - określić który kontroler załadować.

marcinek37
1. cyt: "na podstawie zmiennych zapytania - określić który kontroler załadować."
dlatego dodałem to w __construct(), oczywiście równie dobrze mógłbym to dodać przed deklaracją klasy Controller() - grunt, że dzięki mojemu rozwiązaniu zostanie załadowany odpowiedni kontroler - dobrze piszę? bo może znowu coś źle zrozumiałem :/
2. nie umiem wykorzystać Twojego kodu związanego z szablonami

w klasie Info mam metodę load, która wykorzystuje ViewLoader
wynik idzie do kontrolera, który z kolei wprowadza go do szablonu za pośrenictwem ViewLoader
wychodzi na to samo
lukasz1985
1. Właśnie o to chodzi - że najpierw masz:

  1. new Controller();


a w nim w __construct masz logike. Natomiast chodzi o to, żeby to odwrócić:


  1. $modul = $_GET["m"];
  2.  
  3. $kontroler;
  4. if($modul == "klienci"){
  5. $kontroler = new KontrollerKlientow();
  6. }
  7.  
  8. if($modul == "kontakt")
  9. {
  10. $kontroler = new KontrollerKontaktu();
  11.  
  12. }
  13. $kontroler->select();
  14.  


Jak widzisz - w moim przypadku - najpierw odczytuję nazwę modułu z $_GET i na podstawie tej
nazwy tworzę nowy kontroler - jeśli KontrollerKlientow lub KontrollerKontaktu.

ad 2. No tak, rzeczywiście wychodzi na to samo1. Właśnie o to chodzi - że najpierw masz:

  1. new Controller();


a w nim w __construct masz logike. Natomiast chodzi o to, żeby to odwrócić:


  1. $modul = $_GET["m"];
  2.  
  3. $kontroler;
  4. if($modul == "klienci"){
  5. $kontroler = new KontrollerKlientow();
  6. }
  7.  
  8. if($modul == "kontakt")
  9. {
  10. $kontroler = new KontrollerKontaktu();
  11.  
  12. }
  13. $kontroler->select();
  14.  


Jak widzisz - w moim przypadku - najpierw odczytuję nazwę modułu z $_GET i na podstawie tej
nazwy tworzę nowy kontroler - jeśli KontrollerKlientow lub KontrollerKontaktu.

ad 2. No tak, rzeczywiście wychodzi na to samo.
marcinek37
1. tutaj chyba efekt też będzie taki sam - bo i tak metoda select() uruchamia kontroler na podstawie $_GET i nic tego nie zmieni
2. kombinuję nad tym, aby w tych plikach szablonowych, obok zwykłych zmiennych dodać np. include, ale jeszcze nie mam pomysłu jak to zrobić

co myślicie o tym rozwiązaniu?
http://lukasz-socha.pl/php/mvc-w-praktyce-...artykulow-cz-2/

żeby content zawsze był osobnym plikiem, w którym na górze i na dole będą includowane inne pliki? dzięki temu klasę ViewLoader odpalałbym jedynie raz
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.