Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [skrypt] MVC podstawowy silnik + przykładowe klasy
Forum PHP.pl > Inne > Oceny
olechafm
napisałem takiego oto potworka, z założenia jest to projekt szkoleniowy, który rozwija się w miarę przyswajania nowej wiedzy, składa się z kilku elementów

proszę o krytykę, mam wrażenie, że trochę błądzę w założeniach MVC i chcę mieć pewność w których miejscach

proszę tylko nie do końca zwracać uwagę na "niefortunnie" dobrane nazwy klas i funkcji, wiem, że wymaga to staranniejszej organizacji, na razie jest tak, by __autoload łapał wszystkie potrzebne mi klasy

o dziwo potworek ten działa tak jak zakładałem .htacces przekierowuje wszystko na index.php a linki buduję np. tak www.mojastrona.pl/product/viewproduct/25



index.php - załącza ini.php, tworzy router, tworzy dispatcher, przekazuje router do dispatchera
  1. <?php
  2. include ('core/ini.php');
  3. $router=router::getInstance();
  4. $dispatcher=dispatcher::getInstance();
  5. $dispatcher->start($router);
  6. ?>


ini.php - ustala ścieżki i załącza funkcję __autoload
  1. <?php
  2. set_include_path(get_include_path().PATH_SEPARATOR."core");
  3. set_include_path(get_include_path().PATH_SEPARATOR."core/main");
  4. set_include_path(get_include_path().PATH_SEPARATOR."core/cfg");
  5. set_include_path(get_include_path().PATH_SEPARATOR."app/controllers");
  6. set_include_path(get_include_path().PATH_SEPARATOR."app/views");
  7. set_include_path(get_include_path().PATH_SEPARATOR."app/models");
  8.  
  9. function __autoload($object){
  10. require_once("c{$object}.php");
  11. }
  12. ?>


cConfigs.php - udostępnia dane
  1. <?php
  2.  
  3. class configs{
  4.  
  5. const ALLOWED_URL_CHARS="/[^A-z0-9\/\^]/";
  6. const DEFAULT_CONTROLLER="products";
  7. const DEAFAULT_ACTION="index";
  8. const DB_USER="test";
  9. const DB_PASSWORD="*******";
  10. const MYSQL="mysql:host=localhost; dbname=test";
  11. const PATH_CORE="core";
  12. const PATH_CONTROLLERS="app/controllers";
  13. const PATH_VIEWS="app/views";
  14. const ERROR_NO_PAGE="errornopage";
  15.  
  16. private static $ALLOWED_CONTROLLERS=array("start","products");
  17.  
  18. public static function getAllowed_Controllers()
  19. {
  20. return self::$ALLOWED_CONTROLLERS;
  21. }
  22. }
  23. ?>


cRouter.php - obsługa adresu wywołania (dzieli url na parametry)
  1. <?php
  2.  
  3. class router
  4. {
  5. private static $instance=false;
  6. private $url;
  7. private $routeParts;
  8. private $controller;
  9. private $action;
  10. private $parametry;
  11.  
  12. private function __construct()
  13. {
  14. $this->url=array_keys($_GET);
  15. $this->executeRouting();
  16. }
  17.  
  18. public static function getInstance()
  19. {
  20. if(self::$instance==false){
  21. self::$instance= new router();
  22. }
  23. return self::$instance;
  24. }
  25.  
  26. private function executeRouting()
  27. {
  28. $patern=configs::ALLOWED_URL_CHARS;
  29. $url=$this->url;
  30.  
  31.  
  32. if(isset($url['0']))
  33. {
  34. $route=$url['0'];
  35. $route=preg_replace($patern,"",$route);
  36. $route=str_replace("^","",$route);
  37. $routeParts=array();
  38. $routeParts=explode("/", $route);
  39.  
  40. if(isset($routeParts['0']))
  41. {
  42. $this->controller=$routeParts['0'];
  43.  
  44. if(isset($routeParts['1']))
  45. {
  46. $this->action=$routeParts['1'];
  47.  
  48. if(count($routeParts>2)){
  49.  
  50. $routePartsParam=$routeParts;
  51. array_shift($routePartsParam);
  52. array_shift($routePartsParam);
  53. $this->parametry=$routePartsParam;
  54.  
  55. }else{
  56. $this->parametry=false;
  57. }
  58.  
  59. }else{
  60. $this->action=false;
  61. $this->parametry=false;
  62. }
  63. }else{
  64. $this->controller=false;
  65. $this->action=false;
  66. $this->parametry=false;
  67. }
  68.  
  69. }
  70. }
  71.  
  72. public function getController()
  73. {
  74. return $this->controller;
  75. }
  76.  
  77. public function getAction()
  78. {
  79. return $this->action;
  80. }
  81.  
  82. public function getParametry()
  83. {
  84. return $this->parametry;
  85. }
  86. }
  87. ?>


cDispatcher.php - pobiera dane z routera i przetwarza je (sprawdza czy są poprawne i dozwolone, zwraca wartości domyślne - inicjuje właściwe kontrolery i akcje)
  1.  
  2. <?php
  3.  
  4. class dispatcher
  5. {
  6. private static $instance=false;
  7. private $controller;
  8. private $action;
  9. private $parametry;
  10.  
  11. public static function getInstance()
  12. {
  13. if(self::$instance==false){
  14. self::$instance= new dispatcher();
  15. }
  16. return self::$instance;
  17. }
  18.  
  19.  
  20. public function start($router)
  21. {
  22. $controller=$router->getController();
  23. $action=$router->getAction();
  24. $parametry=$router->getParametry();
  25. self::setParams($controller,$action,$parametry);
  26. self::dispatch();
  27. }
  28.  
  29. private function setParams($controller,$action,$parametry)
  30. {
  31. //POPRAWNY KONTROLER i AKCJA
  32. if($controller!=false && in_array($controller,configs::getAllowed_Controllers())&& $action!=false && method_exists($controller, $action))
  33. {
  34. $this->controller=$controller;
  35. $this->action=$action;
  36. if(count($parametry)>0)
  37. {
  38. $this->parametry=$parametry;
  39. }
  40. else{
  41. $this->parametry=false;
  42. }
  43. }
  44. //NIEPOPRAWNY KONTROLER LUB POPRAWNY KONTROLER I NIEPOPRAWNA AKCJA
  45. elseif(($controller!=false && !in_array($controller,configs::getAllowed_Controllers()))||($action!=false && !method_exists($controller, $action)))
  46. {
  47. $this->controller=configs::ERROR_NO_PAGE;
  48. $this->action=configs::DEAFAULT_ACTION;
  49. $this->parametry=false;
  50. }
  51. //BRAK KONTROLERA LUB POPRAWNY KONTROLER I BRAK AKCJI
  52. elseif(($controller==false)||($controller!=false && in_array($controller,configs::getAllowed_Controllers())&& $action==false))
  53. {
  54. $this->controller=configs::DEFAULT_CONTROLLER;
  55. $this->action=configs::DEAFAULT_ACTION;
  56. $this->parametry=false;
  57. }
  58. }
  59.  
  60. private function dispatch()
  61. {
  62. $controller=$this->controller;
  63. $action=$this->action;
  64. $parametry=$this->parametry;
  65.  
  66. $app = new $controller();
  67.  
  68. if($parametry!=false){
  69. $app->setParametry($parametry);
  70. }
  71.  
  72. $app->$action();
  73. }
  74. }
  75. ?>


cController.php - kontroler rodzic, ustawia parametry dla akcji, sprawdza ich ilość, ustawia model i widok
  1. abstract class controller implements interfaceBasic
  2. {
  3. protected $parametry;
  4. protected $view;
  5. protected $model;
  6. protected $viewname;
  7.  
  8. public function setParametry($parametry)
  9. {
  10. $this->parametry=$parametry;
  11. }
  12.  
  13. public function countParametry($parametry, $parNum)
  14. {
  15. if(count($parametry)!= $parNum || $parametry['0']=="")
  16. {
  17. $controller=configs::ERROR_NO_PAGE;
  18. $action=configs::DEAFAULT_ACTION;
  19. $app= new $controller;
  20. $app->$action();
  21. return $error=true;
  22. }
  23. }
  24.  
  25. protected function setModel()
  26. {
  27. $model= new model();
  28. $this->model=$model;
  29. }
  30.  
  31. protected function setView($viewname)
  32. {
  33. $widokname="view".$viewname;
  34. $widok=new $widokname();
  35. $this->widok=$widok;
  36. }
  37. }
  38. ?>


cInterfaceBasic.php - żąda funkcji index() która jest domyślną dla wszystkich kontrolerów
  1. <?php
  2. interface interfaceBasic
  3. {
  4. function index();
  5. function countParametry($parametry, $parNum);
  6. }


cProducts.php- kontroler produktów - domyślny kontroler - wywołuje akcję index() która listuje wszystkie produkty lub akcję viewproduct pokazującą pojedyńczy produkt po parametrze $id produktu
  1. <?php
  2.  
  3. class products extends controller
  4. {
  5. public $use_layout=false;
  6.  
  7. public function __construct()
  8. {
  9. parent::setModel();
  10. }
  11.  
  12. public function index()
  13. {
  14. parent::setView("products");
  15. $model=$this->model;
  16. $widok=$this->widok;
  17. $widok->setModelInView($model);
  18. $widok->render();
  19. }
  20.  
  21. public function viewproduct()
  22. {
  23. parent::setView(__FUNCTION__);
  24. $parametry=$this->parametry;
  25. $parNum=1;
  26. $error=self::countParametry($parametry,$parNum);
  27.  
  28. if($error!=true)
  29. {
  30.  
  31. $model=$this->model;
  32. $widok=$this->widok;
  33. $widok->setModelInView($model);
  34. $widok->setParamsInView($parametry);
  35. $widok->render();
  36. }
  37. }
  38. }
  39. ?>


cErrornopage.php - klasa błędu - tylko na potrzeby zaznaczenia momentu błedu
  1. <?php
  2. class errornopage extends controller implements interfaceBasic
  3. {
  4. function index()
  5. {
  6. echo "wrong site : 404";
  7. }
  8.  
  9. function __construct()
  10. {
  11.  
  12. }
  13. }
  14. ?>




cDB.php - baza danych

  1. <?php
  2. abstract class DB
  3. {
  4. private static $user;
  5. private static $password;
  6. private static $mysql;
  7. protected static $pdo;
  8.  
  9. public static function setParams()
  10. {
  11. self::$user=configs::DB_USER;
  12. self::$password=configs::DB_PASSWORD;
  13. self::$mysql=configs::MYSQL;
  14. }
  15.  
  16. public static function setConnection()
  17. {
  18. self::setParams();
  19. $user=self::$user;
  20. $password=self::$password;
  21. $mysql=self::$mysql;
  22. self::$pdo=new PDO($mysql,$user,$password);
  23. }
  24. }
  25. ?>


cModel.php - model
  1. <?php
  2.  
  3. class model extends DB
  4. {
  5. private $result;
  6.  
  7. public function __construct()
  8. {
  9. parent::setConnection();
  10. }
  11.  
  12. public function getAllProducts()
  13. {
  14. $pdo=self::$pdo;
  15. $query=$pdo->query('SELECT * FROM products ');
  16. $this->result=$query;
  17. }
  18.  
  19. public function getProduct($id)
  20. {
  21.  
  22. $pdo=self::$pdo;
  23. $query=$pdo->query("SELECT * FROM products WHERE PRODUCTID=$id")or die ('muka');
  24. $this->result=$query;
  25.  
  26. }
  27.  
  28. public function getResult()
  29. {
  30. return $this->result;
  31. }
  32. }
  33. ?>


cView.php - klasa widok - rodzic
  1. <?php
  2. abstract class view
  3. {
  4. protected $model;
  5. protected $parametry;
  6.  
  7. public function setModelInView($model)
  8. {
  9. $this->model=$model;
  10. }
  11.  
  12. public function setParamsInView($parametry){
  13. $this->parametry=$parametry;
  14. }
  15. }
  16. ?>


CViewProducts.php - widok dla listy produktów
  1. <?php
  2. class viewproducts extends view
  3. {
  4. public function render()
  5. {
  6. $model=$this->model;
  7. $model->getAllProducts();
  8. $result=$model->getResult();
  9.  
  10. while($row=$result->fetch())
  11. {
  12. echo '<hr/>
  13. <p><b>iD:</b> '.$row['PRODUCTID'].'</p>
  14. <p><b>Nazwa:</b> <a href="products/viewproduct/'.$row['PRODUCTID'].'">'.$row['PRODUCTNAME'].'</a></p>
  15. <p><b>Cena:</b> '.$row['UNITPRICE'].'</p>
  16. <p><b>Na Magazynie:</b> '.$row['UNITSINSTOCK'].'</p>';
  17. }
  18. }
  19. }
  20. ?>


cViewViewproducts.php - widok dla pojedynczego produktu
  1. <?php
  2.  
  3. class viewviewproduct extends view
  4. {
  5.  
  6. public function render()
  7. {
  8. $parametry=$this->parametry;
  9. $model=$this->model;
  10. $model->getProduct($parametry['0']);
  11. $result=$model->getResult();
  12. $row=$result->fetch();
  13.  
  14. echo '<hr/>
  15. <p><b>iD:</b> '.$row['PRODUCTID'].'</p>
  16. <p><b>Nazwa:</b> '.$row['PRODUCTNAME'].'</p>
  17. <p><b>Cena:</b> '.$row['UNITPRICE'].'</p>
  18. <p><b>Na Magazynie:</b> '.$row['UNITSINSTOCK'].'</p>';
  19. }
  20. }
  21. ?>


baza danych zapożyczona z innego przykładu

  1.  
  2. CREATE TABLE products (
  3. PRODUCTID int(11) NOT NULL AUTO_INCREMENT,
  4. PRODUCTNAME varchar(255) NOT NULL DEFAULT '',
  5. UNITPRICE varchar(255) NOT NULL DEFAULT '',
  6. UNITSINSTOCK varchar(255) NOT NULL DEFAULT '',
  7. DISCONTINUED varchar(255) NOT NULL DEFAULT '',
  8. PRIMARY KEY (PRODUCTID)
  9. ) TYPE=MyISAM;
  10.  
  11. #
  12. # Dumping data for table `products`
  13. #
  14.  
  15. INSERT INTO products VALUES (1, 'Chai', '18', '39', '0');
  16. INSERT INTO products VALUES (2, 'Chang', '19', '17', '0');
  17. INSERT INTO products VALUES (3, 'Aniseed Syrup', '10', '13', '0');
  18. INSERT INTO products VALUES (4, 'Chef Antons Cajun Seasoning', '22', '53', '0');
  19. INSERT INTO products VALUES (5, 'Chef Anton Gumbo Mix', '21.35', '0', '1');
  20. INSERT INTO products VALUES (6, 'Grandma Boysenberry Spread', '25', '120', '0');
  21. INSERT INTO products VALUES (7, 'Uncle Bob Organic Dried Pears', '30', '15', '0');
  22. INSERT INTO products VALUES (8, 'Northwoods Cranberry Sauce', '40', '6', '0');
  23. INSERT INTO products VALUES (9, 'Mishi Kobe Niku', '97', '29', '1');
  24. INSERT INTO products VALUES (10, 'Ikura', '31', '31', '0');
  25. INSERT INTO products VALUES (11, 'Queso Cabrales', '21', '22', '0');
  26. INSERT INTO products VALUES (12, 'Queso Manchego La Pastora', '38', '86', '0');
  27. INSERT INTO products VALUES (13, 'Konbu', '6', '24', '0');
  28. INSERT INTO products VALUES (14, 'Tofu', '23.25', '35', '0');
  29. INSERT INTO products VALUES (15, 'Genen Shouyu', '15.5', '39', '0');
  30.  
  31.  
Spawnm
getParametry() snitch.gif
zamiast:
set_include_path(get_include_path().PATH_SEPARATOR."core");
set_include_path(get_include_path().PATH_SEPARATOR."core/main");
daj:
set_include_path('.' . PATH_SEPARATOR . 'core'
. PATH_SEPARATOR . 'main/main'
);
olechafm
to jest akurat na żywca z książki (Programowanie Obiektowe w PHP5 autorstwa Hasina Haydera oczywiście, jakby ktoś chciał się pośmiać rolleyes.gif ) , cala funkcja __autoloda będzie inaczej napisana (się pisze właśnie) jak tylko ustalę sam ze sobą strukturę katalogów i nazewnictwo plików, i będzie miała jasno określone ścieżki bez tych elementów

set_include_path(get_include_path().PATH_SEPARATOR."core");

up
Zyx
Nie musisz ustalać sam ze sobą nazewnictwa klas, katalogów i plików, bo po co komu "n+1szy standard nazewnictwa, który nie jest z niczym kompatybilny"? Poczytaj sobie o PSR-0, zobacz na zasady nazewnictwa w Symfony 2 / Zend Framework 2 i użyj jednej z gotowych ładowarek klas dla tego standardu. Unikniesz mnóstwa niepotrzebnych problemów, a jak Ci coś fajnego wyjdzie, będziesz mógł tego z marszu używać w połączeniu z normalnymi platformami bez konieczności przepisywania kodu.

Jeśli chodzi o koncepcję i zgodność z założeniami wzorca, to zasadniczo jest OK. Oczywiście od strony rozszerzalności czy konfigurowalności masz sporo rzeczy zakodowanych na sztywno, ale to już jest cecha konkretnej implementacji, a nie zgodności ze wzorcem. Tu szczególnie przyczepię się do jednego:

Kod
<?php

class model extends DB


Hehe, od kiedy to model jest odpowiedzialny za komunikację z bazą danych? Nie bez powodu mówi się, że mechanizm dziedziczenia pozwala na tworzenie bardziej wyspecjalizowanych klas. Specjalizacja = dostosowanie bardziej ogólnej funkcjonalności do bardziej szczegółowego przypadku, a nie dodanie nowej funkcjonalności. Czy pobranie produktów jest jedną z operacji służących do komunikacji z bazą danych? Nie. Ono z tej komunikacji jedynie korzysta, czyli nie używamy dziedziczenia.
olechafm
Dzięki za odpowiedź, to była jedna z rzeczy których nie byłem pewien. A co do PSR-0 (może ktoś ma link do jakiegoś dobrego przykładu albo po ludzku napisanej dokumentacji?) to tak, oczywiście wiem, że istnieje i chętnie zacznę go używać jak tylko go dostatecznie zrozumiem, tak jak napisałem już wcześniej nie da się wszystkiego od razu robić dobrze, na początku zbyt wiele jest do nauki...

Zyx
To jest zwykły standard nazewnictwa i tam nie ma nic do rozumienia. Po prostu nazywasz klasy tak, jak jest podane i to wszystko.
olechafm
Oj jest, może wydaje się, że to oczywistości są, ale gdy wcześniej nauczyło się złych rzeczy to przestawić się później trudno, szczególnie, że jak i we wszystkich innych przypadkach, ciężko o napisaną po ludzku wersję angielskiej dokumentacji czy przykład pokazujący chociażby kilka klas itd...
Zyx
Jak sobie klikniesz w jeden z dwóch linków widocznych w mojej stopce, będziesz mieć nawet dwa przykłady pokazujące kilka klas i zgodne z tym nazewnictwem smile.gif.
olechafm
niema to jak autopromocja smile.gif tak wiem, że one tam są i przedzieram się przez nie tak samo jak i przez całego bloga wink.gif bądź co bądź są to jednakowoż przykłady bardzo złożone



Cytat
Hehe, od kiedy to model jest odpowiedzialny za komunikację z bazą danych? Nie bez powodu mówi się, że mechanizm dziedziczenia pozwala na tworzenie bardziej wyspecjalizowanych klas. Specjalizacja = dostosowanie bardziej ogólnej funkcjonalności do bardziej szczegółowego przypadku, a nie dodanie nowej funkcjonalności. Czy pobranie produktów jest jedną z operacji służących do komunikacji z bazą danych? Nie. Ono z tej komunikacji jedynie korzysta, czyli nie używamy dziedziczenia.



odnośnie jeszcze tego, rozumiem, że model ma sobie wyciągnąć z klasy DB uchwyt połączenia z bazą po prostu, ale same zapytania do bazy

  1. $query=$pdo->query('SELECT * FROM products ');
  2. $this->result=$query;


pozostają w model czy też lądują w klasie DB i przy ich użyciu model te dane pobiera?
Crozin
Czy samochód jest bardziej wyspecjalizowaną formą silnika? Nie. Samochód jedynie wykorzystuje silnik. Co więcej robi to w sposób transparenty dla użytkownika, innymi słowy dla kierowcy nie jest istotne to czy jedzie samochodem ze silnikiem benzynowym, dieslem czy hybrydą by prowadzić samochód.

Dokładnie to samo powinieneś zrobić tutaj. Model (samochód) powinien wykorzystywać połączenie z bazą danych (silnik) w celu pobrania danych. Natomiast samo połączenie z bazą danych powinno zostać przekazane modelowi w sposób transparenty dla użytkownika (widok / kontroler).

Powiedzmy w modelu będziesz miał coś takiego:
  1. class UserRepository {
  2. private $db;
  3.  
  4. public function __construct(DatabaseConnection $db) {
  5. $this->db = $db;
  6. }
  7.  
  8. public function findAll() {
  9. return $this->db->fetchArray('SELECT * FROM users;');
  10. }
  11. }
Natomiast użytkownik tego modelu będzie mógł go wykorzytać robiąc coś w stylu:
  1. $userRepository = $this->getRepository('user');
  2. $users = $userRepository->findAll();
Gdzie metoda getRepository() jest odpowiedzialna za utworzenie obiektu UserRepository i przekazanie mu połączenia z bazą danych. To tworzenie obiektów i przekazywanie im zależności jest nieco rozbudowane dlatego powinieneś się już teraz zainteresować czymś takim jak dependency injection oraz DIC.
olechafm
aaaaaaaaa sciana.gif co nie zadam pytania to dorzucacie mi jakieś nowe rzeczy do nauczenia się... nerdsmiley.png

  1. public function __construct(DatabaseConnection $db) // DatabaseConnection to jest ten kontener?


Crozin
Nie, to jest zwykły obiekt. Generalnie całego kontenera nie powinno się przekazywać - czasami jest to gdzieniegdzie konieczne ale rzadko.

Zainteresuj się tym: http://components.symfony-project.org/dependency-injection/ - naprawdę fajne narzędzie.
olechafm
dzięki, czytam też to: www.rekurencja.pl/php/symfony2/czym-jest-dependency-injection.html
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.