Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Kontroler, akcje ale trochę inaczej.
Forum PHP.pl > Forum > PHP > Object-oriented programming
Joachim Peters
Witam,

Ostatnio trochę czytałem na temat MVC i tak naprawdę tylko niektóre rzeczy z tego mi się podobają, nie wiem może jeszcze nie dostrzegam za wielu plusów, ale chce zastosować u siebie coś podobnego. Napisałem sobie klasę router i request, pierwsza pobiera dane z adresu, druga z formularzy, cookies, sesji i obsługuje dane z routera, klasę FrontController, które uruchamia moduł i tworzy instancję i klasę Action, uruchamia akcję (wywołuje metodę klasy).
Kodowo wygląda to mniej więcej tak:
  1. <?php
  2.  
  3. class FrontController {
  4.  public $module;
  5.  public $action;
  6.  public $objectAction;
  7.  
  8. public $request;
  9.  
  10. public function __construct(HttpRequest $request) {
  11. $this->request = $request;
  12.  
  13. $this->module = strtolower($this->request->get('get', 0));
  14. $this->action = strtolower($this->request->get('get', 1));
  15.  
  16. $this->runModule();
  17. }
  18.  
  19. public function runModule() {
  20. if(file_exists('./modules/user/'.$this->module.'.php')) {
  21. include_once('./modules/user/'.basename($this->module.'.php'));
  22.  
  23. $module = ucfirst($this->module);
  24.  
  25. $this->objectAction = new $module();
  26. } else {
  27. throw new Exception('Nie można znaleźć plików modułu');
  28. }
  29. }
  30. }
  31.  
  32. class Action {
  33.  private $controller;
  34.  
  35.  public function __construct(FrontController $controller) {
  36. $this->controller = $controller;
  37.  
  38. $this->runAction();
  39. }
  40.  
  41. public function runAction() {
  42. if(!is_null($this->controller->objectAction)) {
  43.  $action = $this->controller->action;
  44.  
  45. $this->controller->objectAction->$action();
  46. } else {
  47. throw new Exception('Brak akcji');
  48. }
  49. }
  50. }
  51.  
  52. $controller = new FrontController($request);
  53. $action = new Action($controller);
  54.  
  55. ?>

Jak powiecie czy to rozwiązanie jest optymalne, warto to dalej kodować, czy lepiej zabrać się za kodowanie 'standardowe' - z wprowadzaniem argumentów do funkcji itd.?
bim2
Wedługmnie prawie OK. Chodzi o to, że w FrontController powinieneś dać new Action(); $oAction->$_GET['action']() cos w tym stylu, żeby to kontroller uruchamiał akcję. Aha i jeszcze opłacałoby się przenieść z akcji __construct do np SubController, dać extends SubController a w SubController dać te zmienne z Kontrollerem smile.gif
dr_bonzo
Joachim: zakoduj to tak zeby przy tworzeniu serwisu na tym frameworku robilo sie to szybko (leniwie, niech robi jak najwiecej za ciebie, np. z ta akcja, po co masz to pisac za kazdym razem, niech to taki kontroller odpala) i przyjemnie i zebys za duzo kodu nie powtarzal
Joachim Peters
Mówisz, żeby metodę wywoływać kontroler tylko co wtedy z klasą Action, która będzie pusta, no chyba, że dam dziedziczenie klasy FrontController z Action i w kontrolerze wywołać metodę z $this?
bim2
Dziedziczenie. O tym pisalem. Ja zrobiłem sobie osobne klasy Application_Action/Model/View gdzie mam __construct() { $this->oController = FrontController::getInstance(); coś w tym stylu winksmiley.jpg
Joachim Peters
Zamotałem się strasznie
  1. <?php
  2.  
  3. class SubController {
  4. public $frontController;
  5.  
  6. public function __construct(FrontController $controller) {
  7. $this->frontController = $controller;
  8. }
  9. }
  10.  
  11. class Action extends SubController {
  12. public function runAction() {
  13. if(!is_null($this->frontController->oAction)) {
  14.  $action = $this->frontController->action;
  15.  
  16. if(method_exists($this->frontController->oAction, $action)) {
  17. $this->frontController->oAction->$action();
  18. } else {
  19. throw new Exception('Moduł nie posiada takiej akcji');
  20. }
  21. } else {
  22. throw new Exception('Brak akcji');
  23. }
  24. }
  25. }
  26.  
  27. $action = new Action;
  28.  
  29. class FrontController extends Action {
  30.  public $module;
  31.  public $action;
  32.  public $oAction;
  33.  
  34. public $request;
  35.  
  36. public function __construct(HttpRequest $request) {
  37. $this->request = $request;
  38.  
  39. $this->module = strtolower($this->request->get('get', 0));
  40. $this->action = strtolower($this->request->get('get', 1));
  41.  
  42. $this->runModule();
  43. }
  44.  
  45. public function runModule() {
  46. if(file_exists('./modules/user/'.$this->module.'.php')) {
  47. include_once('./modules/user/'.basename($this->module.'.php'));
  48.  
  49. $module = ucfirst($this->module);
  50.  
  51. if(class_exists($module)) {
  52. $this->oAction = new $module();
  53.  
  54. $this->runAction();
  55. } else {
  56. throw new Exception('Nie ma modułu o takiej nazwie');
  57. }
  58. } else {
  59. throw new Exception('Nie można znaleźć plików modułu');
  60. }
  61. }
  62. }
  63.  
  64. $controller = new FrontController($request);
  65. $subController = new SubController($controller);
  66.  
  67. ?>

Wszystko się teraz rozjechało, bo są złe powiązania. Jakieś rady ? tongue.gif
Ludvik
Powiązania, jak sam zauważyłeś, są złe. Zacznijmy od tego, że Front Controller nie jest akcją - on wywołuje akcje, a to zupełnie inna relacja. Jeżeli chcesz podzielić działanie w stylu: moduł = klasa, akcja = metoda, to bym proponował stworzenie interfejsu modułu:

  1. <?php
  2. interface Module {
  3. function runAction($action);
  4. }
  5. ?>


U Ciebie za moduł robił Sub Controller, ale nazwa Module wydaje się logiczniejsza moim zdaniem, szczególnie, że używasz jej w nazwie metody runModule(). Możesz pokusić się o stworzenie klasy abstrakcyjnej, albo zastąpić nią interfejs:

  1. <?php
  2. abstract class AbstractModule implements Module {
  3. public function runAction($action) {
  4. if(method_exists($this->frontController->oAction, $action)) {
  5. $this->frontController->oAction->$action();
  6. } else {
  7. throw new NoActionException();
  8. }
  9. }
  10. }
  11. ?>


Front Controller jest ok, tylko usuń to dziedziczenie... Do tego bym odpalał obsługę żądania nie w konstruktorze, ale osobnej metodzie np. run().
Joachim Peters
Teraz mam coś takiego:
  1. <?php
  2.  
  3. class FrontController {
  4.  public $module;
  5.  public $action;
  6.  public $oAction;
  7.  
  8. public $request;
  9.  
  10. public function __construct(HttpRequest $request) {
  11. $this->request = $request;
  12.  
  13. $this->module = strtolower($this->request->get('get', 0));
  14. $this->action = strtolower($this->request->get('get', 1));
  15. }
  16.  
  17. public function run() {
  18. $this->runModule();
  19. }
  20.  
  21. private function runModule() {
  22. if(file_exists('./modules/user/'.$this->module.'.php')) {
  23. include_once('./modules/user/'.basename($this->module.'.php'));
  24.  
  25. $module = ucfirst($this->module);
  26.  
  27. if(class_exists($module)) {
  28. $this->oAction = new $module();
  29. } else {
  30. throw new Exception('Nie ma modułu o takiej nazwie');
  31. }
  32. } else {
  33. throw new Exception('Nie można znaleźć plików modułu');
  34. }
  35. }
  36. }
  37.  
  38. interface Module {
  39. public function runAction($action);
  40. }
  41.  
  42. abstract class AbstractModule implements Module {
  43. public $controller;
  44.  
  45.  public function __construct(FrontController $controller) {
  46. $this->controller = $controller;
  47. }
  48.  
  49. public function runAction($action) {
  50. if(method_exists($this->controller->oAction, $action)) {
  51. $this->controller->oAction->$action();
  52. } else {
  53. throw new NoActionException();
  54. }
  55. }
  56. }
  57.  
  58. $controller = new FrontController($request);
  59. $module  = new Module($controller);
  60.  
  61. $controller->run();
  62.  
  63. ?>


i gdzie teraz wywołać tą akcję?
Ludvik
Wywołanie akcji powinno być w metodze runModule kontrolera:

  1. <?php
  2. private function runModule() {
  3. if(file_exists('./modules/user/'.$this->module.'.php')) {
  4. include_once('./modules/user/'.basename($this->module.'.php'));
  5.  
  6. $module = ucfirst($this->module);
  7.  
  8. if(class_exists($module)) {
  9. $oModule = new $module();
  10. $oModule->runAction($this->action);
  11. } else {
  12. throw new Exception('Nie ma modułu o takiej nazwie');
  13. }
  14. } else {
  15. throw new Exception('Nie można znaleźć plików modułu');
  16. }
  17. }
  18. ?>


Następnie musisz utworzyć same akcje, które wywiedziesz z klasy AbstractModule:

  1. <?php
  2. class HelloWorldModule extends AbstractModule {
  3. public function helloWorld() {
  4.  // Tutaj wykonuje sie akcja helloWorld() modułu HelloWorldModule
  5. }
  6. }
  7. ?>


Żeby wszystko uruchomić, musisz wywołać metodę run() FrontControllera.
Joachim Peters
Mam teraz taki kod:
  1. <?php
  2.  
  3. class FrontController {
  4.  public $moduleName;
  5.  public $actionName;
  6.  
  7.  public $oModule;
  8.  
  9. public $request;
  10.  
  11. public function __construct(HttpRequest $request) {
  12. $this->request = $request;
  13.  
  14. $this->moduleName = strtolower($this->request->get('get', 0, 'htmlspecialchars'));
  15. $this->actionName = strtolower($this->request->get('get', 1, 'htmlspecialchars'));
  16. }
  17.  
  18. public function run() {
  19. $this->runModule();
  20. }
  21.  
  22. private function runModule() {
  23. if(file_exists('./modules/user/'.$this->moduleName.'.php')) {
  24. include_once('./modules/user/'.basename($this->moduleName.'.php'));
  25.  
  26. $moduleName = ucfirst($this->moduleName);
  27.  
  28. if(class_exists($moduleName)) {
  29. $this->oModule = new $moduleName();
  30. $this->oModule->runAction($this->actionName);
  31. } else {
  32. throw new Exception('Moduł <i>'.$this->moduleName.'</i> nie istnieje.');
  33. }
  34. } else {
  35. throw new Exception('Nie można znaleźć pliku modułu <i>'.$this->moduleName.'</i>.');
  36. }
  37. }
  38. }
  39.  
  40. interface Module {
  41. public function runAction($action);
  42. }
  43.  
  44. class AbstractModule implements Module {
  45. public $controller;
  46.  
  47.  public function __construct(FrontController $controller) {
  48. $this->controller = $controller;
  49. }
  50.  
  51. public function runAction($action) {
  52. if(method_exists($this->controller->oModule, $action)) {
  53. $this->controller->oModule->$action();
  54. } else {
  55. throw new Exception('Akcja <i>'.$action.'</i> nie istnieje.');
  56. }
  57. }
  58. }
  59.  
  60. $controller = new FrontController($request);
  61. $module = new AbstractModule($controller);
  62. // tutaj klasa modułu z 'extends AbstractModule
  63. $controller->run();
  64. ?>

i pokazuje błąd Fatal error: Argument 1 passed to AbstractModule::__construct() must be an object of class FrontController...on line 47
Ludvik
Trochę nie tędy droga. Zacznijmy od front kontrolera, który ma właściwości publiczne - powinny być prywatne.

Usunąłeś słowo kluczowe abstract z definicji klasy AbstractModule, która miała być abstrakcyjna. Nie możesz utworzyć instancji tej klasy, bo ona nic nie robi... Posiada tylko metodę runAction, która na podstawie nazwy akcji wywołuje odpowiednią metodę. Te metody musisz już napisać sam w klasie dziedziczącej AbstractModule.

W przypadku tej konstrukcji, nie widzę sensu w podawaniu do konstruktora klasy modułu instancji front controllera. Na nic on nie jest potrzebny w tej klasie...

Metoda runAction() w AbstractModule jest źle napisana - po co wyciągać obiekt modułu z front controllera? Przecież front controllera wywołuje tę metodę dla odpowiedniego modułu, dlatego runAction() operuje na składowych instancji, dla której została wywołana. Dlatego powinno być:

  1. <?php
  2. public function runAction($action) {
  3. if(method_exists($this, $action)) {
  4. $this->$action();
  5. } else {
  6. throw new Exception('Akcja <i>'.$action.'</i> nie istnieje.');
  7. }
  8. }
  9. ?>


Warto wyrzucić do tego jakiś lepszy wyjątek, typu NoActionException. Komunikat nic Ci tak na prawdę nie daje w kodzie. Po typie wyjątku możesz już manipulować przechwytywaniem...

Warto też do akcji jakoś przekazać obiekt żądania.
Joachim Peters
Mam teraz tak:
  1. <?php
  2.  
  3. class FrontController {
  4.  private $moduleName;
  5.  private $actionName;
  6.  
  7.  private $oModule;
  8.  
  9. private $request;
  10.  
  11. public function __construct(HttpRequest $request) {
  12. $this->request = $request;
  13.  
  14. $this->moduleName = strtolower($this->request->get('get', 0, 'htmlspecialchars'));
  15. $this->actionName = strtolower($this->request->get('get', 1, 'htmlspecialchars'));
  16. }
  17.  
  18. public function run() {
  19. $this->runModule();
  20. }
  21.  
  22. private function runModule() {
  23. if(file_exists('./modules/user/'.$this->moduleName.'.php')) {
  24. include_once('./modules/user/'.basename($this->moduleName.'.php'));
  25.  
  26. $moduleName = ucfirst($this->moduleName);
  27.  
  28. if(class_exists($moduleName)) {
  29. $this->oModule = new $moduleName();
  30. $this->oModule->runAction($this->actionName);
  31. } else {
  32. throw new Exception('Moduł <i>'.$this->moduleName.'</i> nie istnieje.');
  33. }
  34. } else {
  35. throw new Exception('Nie można znaleźć pliku modułu <i>'.$this->moduleName.'</i>.');
  36. }
  37. }
  38. }
  39.  
  40. interface Module {
  41. public function runAction($action);
  42. }
  43.  
  44. abstract class AbstractModule implements Module {
  45. public function runAction($action) {
  46. if(method_exists($this, $action)) {
  47. $this->$action();
  48. } else {
  49. throw new Exception('Akcja <i>'.$action.'</i> nie istnieje.');
  50. }
  51. }
  52. }
  53.  
  54. $controller = new FrontController($request);
  55. $controller->run();
  56. ?>


Pytanie i problemy:
1. Chciałbym jeszcze zrobić, żeby w module było można używać funkcji i danych z klasy request bez potrzeby tworzenia instancji w każdej z klas gdzie ma być to użyte.
2. Nie bardzo rozumiem, jaki sens jest w tworzeniu typu wyjątku takiego jaki podałeś?
3. Co teraz z szablonami? Chce użyć zwykłej składni PHP w HTML-u w plikach .php i czy robić do wczytywania tego jakąś osobną klasę czy w metodach modułów dawać po prostu include?
4. Czy jestem taki toporny czy mi się tylko wydaję? smile.gif
Ludvik
1. Przekaż instancję klasy HttpRequest do metody runAction, a z tej do metody wykonującej akcję.
2. Popatrz na wywołanie metody run Front Controllera. Jedyne co będziesz mógł zrobić z tymi wyjątkami, to wyrzucić je na ekran. Wyjątki powinny mieć takie typy, aby można było wywnioskować z kontekstu, czego one dotyczą. Jak jedna z Twoich akcji wyrzuci wyjątek, to nie będziesz wiedział, czy nie ma modułu, czy akcji, czy też akcja nie zrobiła czegoś źle. Komunikat na poziomie kodu źródłowego nic nie daje...
3. Zdecydowanie trzeba wyodrębnić widok, jeżeli chcesz, żeby miało to coś wspólnego z MVC. Najprostsze podejście, to zwrócenie nazwy szablonu z akcji i przekazanie jej do obiektu widoku, który dołączy odpowiedni szablon.
4. Bez przesady winksmiley.jpg Nikt nie rodzi się ze znajomością OOP, potrzeba trochę czasu, żeby poruszać się swobodnie w tym temacie. Trzeba też popełnić trochę błędów i wyciągnąć z nich wnioski winksmiley.jpg
Joachim Peters
c.d 2. Nadal nie za bardzo rozumiem, załóżmy, że mają wyrzucić się dwa wyjątki - puste, skąd mam wiedzieć co wtedy jest nie tak? Czy chodzi Ci o to, żebym dawał tyle klas wyjątków ile jest ich wystąpień, czyli do każdego zdarzenia inny wyjątek (z inną nazwą)?
c.d 3. Ok, mniej więcej wiem jakby to miało wyglądać tylko co jeżeli dana akcja będzie posiadała wiele widoków? Czy będzie trzeba składować cały kod widoku tej akcji w jednym pliku i później będzie od wczytywany podczas uruchamiania akcji ($view->get()) ?
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.