Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Routing we wlasnym MVC
Forum PHP.pl > Forum > PHP > Object-oriented programming
brzoza91
Witam
Chciałbym stworzyć routing, do własnej implementacji MVC. Na początku przedstawię strukturę katalogów mojego projektu.

W folderze app, będą pliki danego projektu. Każdy contoller będzie miał swój plik .yml w którym będą zapisane zasady routingu.

przykładowy plik index.yml
prefiks oznacza wpisany adres url, contoller i action wiadomo.

  1. index:
  2. prefix: /index/index/
  3. controller: Index
  4. action: index
  5.  
  6. show:
  7. prefix: /index/show/
  8. controller: Index
  9. action: show
  10.  
  11. add:
  12. prefix: /index/add/
  13. controller: Index
  14. action: add


i tak działa mój routing:
1. ktoś wpisuje adres na mojej stronie np. index/show/40
2. sprawdzane jest czy istnieje dany controller o nazwie index, akcja show i parametr 40
3. jesli tak to uruchamiany jest odpowiednia akcja z contollera

Routing.php
  1. class Routing
  2. {
  3. private $action;
  4. private $controller;
  5. private $parameter;
  6. private $routingFile;
  7. private $path=array();
  8.  
  9. function __construct()
  10. {
  11. $this->createPath();
  12. $this->loadRoutingFile($this->path['contoller']);
  13. $path = $this->searchRouting($this->path['contoller'] . '/' . $this->path['action'], $this->
  14. routingFile);
  15.  
  16. $this->setAction($path);
  17. $this->setContoller($path);
  18. }
  19.  
  20. private function searchRouting($search, $array)
  21. {
  22. $array = (object)($array);
  23. $search = trim($search, '/');
  24.  
  25. foreach ($array as $routing) {
  26. $r = trim($routing['prefix'], '/');
  27. if ($r == $search) {
  28. return $routing;
  29. }
  30. }
  31. header('Location: index.php');
  32. return false;
  33. }
  34.  
  35. private function loadRoutingFile($name)
  36. {
  37. require_once ('/libs/yaml/Yaml.php');
  38. if (isset($name)) {
  39. $this->routingFile = Yaml::parse('c:/wamp/www/framework/app/routing/' . $name .
  40. '.yml');
  41. } else {
  42. $this->routingFile = Yaml::parse('c:/wamp/www/framework/app/routing/index.yml');
  43. }
  44. }
  45.  
  46. private function createPath()
  47. {
  48. $url = explode('/', trim($_GET['url'], '/'));
  49.  
  50. if (!isset($url[0])) {
  51. $url[0] = 'index';
  52. }
  53. if ($url[0] == "") {
  54. $url[0] = 'index';
  55. }
  56.  
  57. if (!isset($url[1])) {
  58. $url[1] = 'index';
  59. }
  60. if ($url[1] == "") {
  61. $url[1] = 'index';
  62. }
  63.  
  64. if(isset($url[2]) && filter_var($url[2],FILTER_SANITIZE_NUMBER_INT,FILTER_VALIDATE_INT)){
  65. $this->parameter=$url[2];
  66. }
  67. $this->path['contoller'] = $url[0];
  68. $this->path['action']= $url[1];
  69. }
  70.  
  71. public function getAction()
  72. {
  73. return $this->action;
  74. }
  75.  
  76. public function getContoller()
  77. {
  78. return $this->controller;
  79. }
  80.  
  81. public function getParameter()
  82. {
  83. return $this->parameter;
  84. }
  85.  
  86. public function setAction($path)
  87. {
  88. $this->action = $path['action'];
  89. }
  90.  
  91. public function setContoller($path)
  92. {
  93. $this->controller = $path['controller'];
  94. }
  95.  
  96.  
  97. }

Bootstrap.php
  1. require_once 'core/Routing.php';
  2. class Bootstrap
  3. {
  4. public function __construct(){
  5. $routing=new Routing();
  6.  
  7. require_once('/app/controllers/'.$routing->getContoller().'Controller.php');
  8. $contoller=$routing->getContoller().'Controller';
  9. $action=$routing->getAction().'Action';
  10.  
  11. $page=new $contoller();
  12. $page->$action($routing->getParameter());
  13. }
  14.  
  15. }



Chciałbym aby mój kod był uniwersalny i był elastyczny dla wielu różnych projektów. Chciałbym też aby został napisany o dobre praktyki OOP. Głównie to właśnie zależy mi na wysokiej jakości kodu. Co myślicie o takim rozwiązaniu tego zagadnienia? Wszystko działa w tym routingu. Jednak bardzo mi zależy aby pisać przemyślany kod. Gdzie popełniłem jakiś błąd projektując ten routing ?
r4xz
kilka uwag, jeśli chcesz własny, a jednak przemyślany to:

1. prefix - troch nietrafna nazwa, lepiej to zarezerwuj na przypadki typu: w głównym routingu importujesz jakiś poboczny i dajesz prefix "/admin" (przykładowo) i wtedy na początku każdego url w tamtym routingu dodawany jest "/admin/dalsza_czesc_url"
2. podziel to na sekcje
Kod
[nazwa routingu]:
   url: /przyklad-z-akcja/nazwa-akcji/con:controller/page-id-:page
   defaults:
      action: show
      controller: products
   require:
      page: [0-9]+

użytkownik wpisuje:
1. /przyklad-z-akcja/nazwa-akcji/congruszka/page-id-3
ma controller: gruszka, action: show, page: 3
2. /przyklad-z-akcja/nazwa-akcji/con/page-id-300
ma controller: products, action: show, page: 300
3. /przyklad-z-akcja/nazwa-akcji/congruszka/page-id-
ma 404, bo page nie jest liczbą

--------edit--------
czytaj i czerp pomysły (najlepsza metoda):
symfony
cakephp
codeigniter
...
brzoza91
Cytat(r4xz @ 13.01.2013, 17:43:05 ) *
użytkownik wpisuje:
1. /przyklad-z-akcja/nazwa-akcji/congruszka/page-id-3
ma controller: gruszka, action: show, page: 3
2. /przyklad-z-akcja/nazwa-akcji/con/page-id-300
ma controller: products, action: show, page: 300
3. /przyklad-z-akcja/nazwa-akcji/congruszka/page-id-
ma 404, bo page nie jest liczbą


nie bardzo rozumiem co miałeś na myśli w /przyklad-z-akcja/

czy powinienem użyć namespace w takim projekcie ?
!*!
Zapisz ścieżkę do aplikacji w jakiejś stałej, ponieważ teraz jak przeniosę pliki aplikacji do innego folderu, to cały FW się wysypie.
Co skłoniło Cie, aby użyć yaml zamiast tablic?

@up
r4xz - chyba miał na myśli standardowe podejście do routingu (które imo mało kiedy się praktykuje).

Cytat
czy powinienem użyć namespace w takim projekcie ?

Jeśli tego nie zrobiłeś do tej pory to źle tongue.gif bez NS się nie ruszysz, szczególnie w FW.
brzoza91
okej, tą ścieżkę i autoloader miałem w planach smile.gif

wiesz jestem początkujący, więc gdy zobaczyłem pliki yml. w symfony2 uznałem, że jest to fajne i przejrzyste. Po prostu spodobało mi się to
r4xz
Cytat(brzoza91 @ 13.01.2013, 18:14:20 ) *
uznałem, że jest to fajne i przejrzyste

za 3 (czysty strzał) lata jak wejdzie (do powszechnego użytku) PHP 5.4, to tablice będą bardzo czytelne (choć teraz jak je umiejętnie pisać to też są bardzo czytelne)... no ale to taki mały offtopic smile.gif

co do '/przyklad-z-akcja/nazwa-akcji' faktycznie niefortunnie teraz ja dobrałem tekst, chodziło mi że jest to jakiś stały tekst, jaki musi zawierać (a nawet od którego musi się zaczynać) adres, równie dobrze mogłem napisać '/aaa/bbb'

i nie wiem które to standardowe, ale nie miałem na myśli:
/:controller/:action/:param1 etc. etc.
brzoza91
tworząc każdy nowy projekt będę tworzył nowe pliki w folderze app. W folderze core nic nie będę zmieniał/dodawał.

tak więc mam użyć namespace tylko dla folderu app, czy dla wszystkich folderów ?

w pliku core/Contoller.php dodalem
  1. namespace Core\Contoller;


w pliku app\controllers\IndexController.php
  1. namespace App\Controllers\IndexController;
  2. use \Core\Controller as Controller;


czy dobrze to zaplanowałem ?
!*!
Poczytaj o standardach PSR-0/1/2 tam masz wyjaśnione jak i co nazwać. Pierwsze słowa w NS to nazwa aplikacji do jakiej należy, dzięki temu unikniesz kolizji z innymi aplikacjami np. forum innego autora.
brzoza91
okej, poczytam.

ale gdy użyje takich przestrzeni nazw jak wyżej opisałem, dostaję taki błąd

Fatal error: Class 'App\Contollers\IndexContoller\Controller' not found in C:\wamp\www\framework\app\controllers\IndexController.php on line 13
!*!
Ponieważ oprócz NS musisz napisać autoloader(napisz go po przeczytaniu o PSR).

klasa.php

  1. <?php
  2. namespace App\Core\Klasa


index.php
  1. <?php
  2. use App\Core\Klasa;
  3. $o = new Klasa;


lub jak masz kolizje w przestrzeni nazw to

  1. use App\Core\Klasa as Cos;
  2. $o = new Cos;
brzoza91
a gdzie byście umieścili obiekt klasy PDO ?
w klasie Contoller czy w klasie Model ?

Wiem, że to Model pracuje z bazą danych. ale czy umieszczenie go w Contoller nie było by wygodniejsze ?

!*!
Cytat(brzoza91 @ 14.01.2013, 00:26:01 ) *
Wiem, że to Model pracuje z bazą danych. ale czy umieszczenie go w Contoller nie było by wygodniejsze ?

Nie, ponieważ kontroler ma kierować "ruchem" poszczególnych elementów aplikacji, a nie zajmować się tym co mają wykonać. Kontroler wysyła i odbiera dane z modelu, aby na ich podstawie móc przekazać je do widoku i na tym w zasadzie kończy się jego zadanie.
Tzw. dobudówka na PDO to może być część pakietu Core lub Lib, patrząc na Twoje drzewo katalogów, to będzie /core/libs
brzoza91
Cytat(brzoza91 @ 14.01.2013, 00:26:01 ) *
a gdzie byście umieścili obiekt klasy PDO ?
w klasie Contoller czy w klasie Model ?

Wiem, że to Model pracuje z bazą danych. ale czy umieszczenie go w Contoller nie było by wygodniejsze ?


nie do końca dobrze opisałem to o co mi chodzi, wiec poprawię pytanie smile.gif

chciałem zapytać się czy mogę konstruować zapytanie w controller. przykład niżej:

  1. class IndexContoller{
  2. public function index()
  3. {
  4. $users = new User(); //jaki tam model
  5. $users->pdo->query('select * from users'); //czy poprawne jest definiowanie tu zapytań
  6. }
  7. }
markonix
Cytat(brzoza91 @ 16.01.2013, 14:44:35 ) *
  1. class IndexContoller{
  2. public function index()
  3. {
  4. $users = new User(); //jaki tam model
  5. $user->pdo->query('select * from users'); //czy poprawne jest definiowanie tu zapytań
  6. }
  7. }

Co zawiera obiekt $user?
Po co dajesz do $users obiekt User skoro go nie wykorzystujesz?

Ogólnie źle - zapytania, działania na plikach itp w modelu.
W kontrolerze wywoływanie odpowiednich obiektów i metod oraz walidacje, kontrole dostępu, przekierowania.
W widoku wyświetlenie zmiennych, pętle, proste instrukcje.

To tak w skrócie.
brzoza91
Cytat(markonix @ 16.01.2013, 14:50:08 ) *
Co zawiera obiekt $user?
Po co dajesz do $users obiekt User skoro go nie wykorzystujesz?


tu pomyłka była w nazwie obiektu. chodzi o ten sam obiekt

!*!
@up przeczytaj raz jeszcze mój ostatni post i to co napisał markonix "obrazkowo" ma to wyglądać tak:

  1. class ControllerA
  2. {
  3. public function index()
  4. {
  5. $getUser = new User();
  6. $arr = $getUser-> getAll(); // tworzysz zmienną ze zwróconą wartością, aby ją sprawdzić i później przesłać
  7. if(is_array($arr)) // sprawdzasz zmienną, a nie odwołanie bezpośrednie, zadanie dla Ciebie, aby wyjaśnić dlaczego tongue.gif
  8. {
  9. $w = new View($arr) // wysyłasz dane do widoku
  10. }
  11. }
  12.  
  13. }


Model:

  1. class User
  2. {
  3. public function getAll()
  4. {
  5. $PDO = // połączenie z bazą danych
  6. // jeśli chcesz coś z tymi danymi zrobić, np wywalić cokolwiek z tablicy, posortować to też w modelu i TYLKO w nim
  7. return $PDO->fetchAll() // zwracasz wynik jaki otrzymałeś od bazy z powrotem do kontrolera
  8. }
  9.  
  10. }



Widok:

  1. class View
  2. {
  3. public function __construct($arr)
  4. {
  5. // tu robisz cokolwiek z danymi z tablicy
  6. echo $arr['name'];
  7. // tak jak napisał markonix, w widoku mogą znaleźć się pętle ify czy cokolwiek co zapewni ze widok ma być taki a nie inny, ALE nie rób w nim nie wiadomo jakich obliczeń, działań na bazie czy plikach (poza tym aby je wczytać ;))
  8. }
  9.  
  10. }
brzoza91
Cytat(!*! @ 17.01.2013, 10:21:36 ) *
  1. class ControllerA
  2. {
  3. public function index()
  4. {
  5. $getUser = new User();
  6. $arr = $getUser-> getAll(); // tworzysz zmienną ze zwróconą wartością, aby ją sprawdzić i później przesłać
  7. if(is_array($arr)) // sprawdzasz zmienną, a nie odwołanie bezpośrednie, zadanie dla Ciebie, aby wyjaśnić dlaczego tongue.gif
  8. {
  9. $w = new View($arr) // wysyłasz dane do widoku
  10. }
  11. }
  12.  
  13. }


wcześniej miałem prawie identycznie to zaprojektowane.
tylko doszedłem wtedy do pewnego problemu. W jednej akcji kontrolera potrzebuję wszystkich użytkowników, więc tak będzie to wyglądać(mowa jest o akcjach w kontrolerze)

  1. public function AAction(){
  2. $getUser = new User();
  3. $arr = $getUser->getAll();
  4. }




W powyższy sposób pobrałem wszystkich userów. A co zrobić jeśli chcę pobrać wszystkich userów z jakim s warunkiem+ JOIN z inna tabelą

markonix
Utwórz nową metodę która pobierze "wszystkich userów z jakim s warunkiem+ JOIN z inna tabelą ".

Jeżeli to jakiś warunek klasowy np. chcesz pobrać wszystkich o określonej grupie to warto nazwę grupy ustalać jako argument metody, żeby była bardziej elastyczna i nie robić niepotrzebnych metod typu getAllAdminUser, getAllXyzUsers tylko getUsersByType($type = 'normal)
sazian
wiem że pewnie teraz zostanę zbesztany ale co tam tongue.gif

W większości przypadków nie widzę potrzeby używania modelu(no może poza tym że wzorzec tak nakazuje) ponieważ w znacznej większości metody modelu to po prostu SELECT * FROM tabela i zapisanie tego do tablicy.
W ten sposób powstaje cała masa metod z których w wiekowości korzystam tylko raz, oczywiście można by je odpowiednio parametryzować ale w końcu dojdzie do takiej sytuacji że prawie całe zapytanie będę zapisywał w parametrach funkcji. Ja wolę napisać dokładnie takie zapylanie jakiego potrzebuję bezpośrednio w kontrolerze i nie muszę się przejmować jak chcę dodać kolejnego jojn'a że coś innego przestanie działać prawidłowo czy tak wydajnie jak by mogło. Poza tym dla mnie taki zapis w brew pozorom jest bardziej czytelny.
Znając życie pojawi się argument że modele łatwiej poddaje się testom. On ok ale co można testować jeśli w metodzie modelu mamy tylko zapytanie do SQL i nic więcej ?

Oczywiście stosuję modele ale tylko wtedy gdy wiem że kod w dokładnie takiej postaci będę potrzebował w kilku akcjach i nie jest pojedyncze zapytanie ale coś co wymaga dodatkowej obróbki po stronie php. Moim zdaniem to właśnie powinien robić model - obrabiać złożone dane, a nie robić każdego select'a do bazy



Inną rzeczą którą często robię(a co zapewne wiele osób uzna za bardzo błędne) jest przekazywanie uchwytu do wyniku zapytania do widoku - oczywiście zakładając że wynik zapytania ma być tylko wyświetlony, a nie poddawany dalszej obróbce.
Czyli np. $vies->dane=$this->db->query('SELECT ....');
i dopiero w widoku robię pętlę while do listowania wyniku.
Dlaczego tak ?
Dla mnie odpowiedź jest prosta, jeśli chciałbym zrobić pętlę while w modelu czy w kontrolerze musiałbym zapisać te wszystkie dane do tablicy, tablicę przekazać do widoku i na koniec w widoku stworzyć drugą pętlę która te dane wyświetli. Czyli muszę zadeklarować dodatkową tablicę i użyć dwóch pętli, a przy moim rozwiązaniu używam tylko jednej pętli
hind
Cytat(sazian @ 17.01.2013, 23:34:40 ) *
wiem że pewnie teraz zostanę zbesztany ale co tam tongue.gif

W większości przypadków nie widzę potrzeby używania modelu(no może poza tym że wzorzec tak nakazuje) ponieważ w znacznej większości metody modelu to po prostu SELECT * FROM tabela i zapisanie tego do tablicy.
[...]

No właśnie nie... bo dane z modelu powinny zostać opakowane w obiekty (encje) i te encje są dopiero kwintesencją modeli (a na modele przy okazji można nałożyć mappery).
!*!
sazian - tylko że w Twoim wypadku to już nie jest MVC, tylko samo VC, więc cała "magia" używania tego się sypie. Zakładając że chcesz zmienić odwołania do bazy, normalnie wymieniasz model na inny, a u Ciebie? Grzebiesz w kontrolerze? Czyli robisz dwie rzeczy zamiast jednej?
brzoza91
no dobrze teraz wiem, że musi być w modelu obsługa bazy danych.

jednak nie wiem, jak dalej rozwiązać pobieranie danych z bazy danych. Do każdej akcji kontolera potrzebuję praktycznie innego zestawu danych z tej samej tabeli w bazie(czasami join, grupowanie, sortowania, sumy + where). Tak więc wystąpić może nieskończenie wiele różnych zapytań do bazy. A przecież nie będę w modelu tworzył tak wielkiej ilości funkcji.
!*!
Odpowiedź już padła, zakładając że masz dobry router a uri wygląda tak:

Cytat
/user/all

Przekazać masz wartość nr 2 czyli "all"

  1. $model = new User('tu wartość jaką masz przekazać czyli all');


lub uri jakieś inne

Cytat
/user/all/name/stefan

Zakładając że wybierasz przez router tylko "all" i "stefan"

  1. $model = new User('tu wartość jaką masz przekazać czyli all', 'stefan');


itd.

Oczywiście wszytko musisz rozplanować, sprawdzić 40 razy który wariant będzie dobry. Tak czy inaczej, jeśli masz klasę URI która pobiera tą wartość, to przez Router na nich operujesz odsyłając do kontrolera i metody z parametrami.

Jeśli chcesz przekazywać parametry, to może Cie zainteresować ta funkcja call_user_func_array
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.