Część 2. Budujemy własny framework MVC w PHP. Front Controller i kontrolery pomocnicze

W poprzednim artykule opisałem część konfiguracyjną i inicjowanie skryptu, więc tym razem zajmiemy się wszystkimi odwołaniami, które htaccess kieruje do pliku index.php.

O koncepcji modelu MVC można poczytać w Internecie więc nie będę przytaczał tu teorii związanej z tym zagadnieniem, zajmiemy sie za to implementacją tego modelu w naszym skrypcie. Jedyną ważną informacją o której warto wspomnieć to wybór „routingu” jaki nasz front controller będzie implementował. W tym przykładzie posłużę się najprostszym standardowym routingiem:

  • URL: http://domena.pl/wartosc1/wartosc2 – oznacza, że „wartosc1″ to nasz kontroler, a „wartosc2″ to nasza akcja wewnątrz kontrolera.
  • URL: http://domena.pl/wartosc1 – oznacza, że „wartosc1″ to kontroler a akcja kontrolera nie istnieje
  • URL: http://domena.pl/ – oznacza, że ani kontroler ani akcja nie istnieją

Plik index.php wygląda następująco:


<?php

// inicjujemy klase filtrowania danych z
// maksymalnymi restrykcjami
// szczegoly w osobnym artykule..
$filter_ALL = new Application_InputFilter();

// inicjujemy Front Controller z wyborem
// standardowego routingu opisanego powyzej
$frontController = Application_Router_Standard_Route::getInstance();

// tworzymy obiekt odpowiedzialny za poprawne
// rozpoznanie wszystkich danych przychodzacych do skryptu
$httprequest     = new Application_Router_Standard_HttpRequest();

// przekazujemy kontrole do FrontControllera ktory zajmie
// sie dalszymi akcjami
$frontController->dispatch( $httprequest );

?>

To wszystko w pliku index.php! Ten plik zakończył już swój udział w procesie, teraz kontrolę nad działaniem skryptu przejmuje główny kontroler. Jak zapewne zauważyłeś, instancja obiektu FrontControllera tworzona jest w oparciu o singleton. Przyczyna jest prosta, obiekt tej klasy możę istnieć tylko jeden i wzór projektowy singleton idealnie się do tego nadaje. Następnie zajmujemy się przygotowaniem danych przekazywanych do skryptu.

Plik httprequest.php wygląda następująco:


<?php

defined('APP_PATH') or die();

class Application_Router_Standard_HttpRequest
{

	private $_requestParams = array ();

   	private $_requestParams = array ();
   	public function __construct()
   	{
		global $filter_ALL;

		// odczytaj dane przychodzace z URL
		$address1 	= parse_url( $_SERVER['REQUEST_URI'] );
		$path 	= explode('/', trim($address1['path'],'/') );

		// wyczysc 2 pierwsze elementy istotne dla
		// wybranego routingu: controller i action
		$parameters1['controller'] = $filter_ALL->process( str_replace('-','',strtolower($path[0])) );
		$parameters1['action'] = $filter_ALL->process( str_replace('-','',strtolower($path[1])) );

		// rozbijamy dane i wczytujemy do tablicy
		array_shift($path);
		array_shift($path);
		$path = array_chunk($path, 2);
		for( $i=0;$i<count($path);$i++ )
		{
			if( !isset($path[$i][1]) ) { $path[$i][1] = 1; }
			$parameters1[ $path[$i][0] ] = $path[$i][1];
		}
		$this->_requestParams = array_merge($_REQUEST,$parameters1);

		// czyscimy GET, POST, REQUEST zmuszajac do uzywania
		// funkcji set() i get()
		$_REQUEST 	= array();
		$_POST 		= array();
		$_GET 		= array();
   	}

	public function getParam($paramName)
   	{
		if( isset($this->_requestParams[$paramName]) )
      		{
       			return $this->_requestParams[$paramName];
      		}
      		else
      		{
      			return false;
      		}
   	}

	public function setParam($paramName,$paramValue)
   	{
		$this->_requestParams[$paramName] = $paramValue;
   	}
}

?>

Przykładowu adres URL

/admin/news/id/10/delay?sort=desc&session_id=12345

i interpretacja zmiennych. Obiekt _requestParams zawiera:

  • _requestParams[‚controller’] = admin
  • _requestParams[‚action’] = news
  • _requestParams[‚id’] = 10
  • _requestParams[‚delay’] = 1
  • _requestParams[‚sort’] = desc
  • _requestParams[‚session_id’] = 12345

Warto zwrócić uwagę na zmienną $delay której została przypisana wartość 1 ponieważ w URL nie miała przypisanej żadnej wartości. [ad#post]To rozwiązanie wymaga zmiany w przypadku zastosowań produkcyjnych, dla celów prezentacyjnych w tym artykule jest idealne.

Zwróć również uwagę, że jedynymi zmiennymi, które zostały wyczyszczone z wszystkiego oprócz znaków alfanumerycznych są controller i action o pozostałych zmiennych przekazanych do skryptu musimy pamiętać sami, czy to czyszcząc je w obiekcie HttpRequest w metodach set() i get() czy też bezpośrednio w kontrolerach lub modelach.

Przechodzimy zatem do FrontController’a.


<php

class Application_Router_Standard_Route implements Application_Interface_FrontController
{

   	private $controllerFileNamePrefix;
   	private $controllerFileNameSufix;
  	private static $instance;

   	private function __construct()
   	{
     		$this->_controllerFileNamePrefix = APP_PATH.'_controllers/';
   		$this->_controllerFileNameSufix = '.php';
   	}

  	private function __clone()
  	{
		trigger_error('Clone zabroniony', E_USER_ERROR);
  	}

  	public function __wakeup()
  	{
    		trigger_error('Deserialaze zabronione', E_USER_ERROR);
  	}

   	public static function getInstance()
   	{
    		if (!self::$instance instanceof self)
    		{
      			self::$instance = new self;
    		}
    		return self::$instance;
  	}

	// funkcja odpowiedzialna za zaladowanie odpowiedniego kontrolera
	// przekazujemy jej obiekt $request z danymi wejsciowymi
   	public function dispatch(Application_Router_Standard_HttpRequest $request)
   	{
      		// sklepjamy nazwe kontrolera
      		$controllerClassName	= 'controllers_'.$request->getParam('controller');

      		// jesli kontroler nie jest pusty sprawdzamy czy istnieje
      		if ( $request->getParam('controller') != '' )
      		{
			// jesli zadany kontroler istnieje w systemie, ladujemy go
      			if( file_exists($this->_controllerFileNamePrefix . $request->getParam('controller') . $this->_controllerFileNameSufix) )
      			{
	         		$controllerClass = new $controllerClassName($request,0);
      			}
			// jesli kontroler nie istnieje, ladujemy domyslny kontroler
      			else
      			{
      				$controllerClass = new Controllers_Content($request,1);
      			}
      		}
		// zmienna kontrolera jest pusta wiec urachamiamy domyslny kontroler
      		else
      		{
      			$this->dispatch_default_controller($request,2);
      		}

   	}

	// uruchamia domyslny kontroler na zadanie
   	public function dispatch_default_controller(Application_Router_Standard_HttpRequest $request, $action_dispatch)
   	{
   		global $myconf;
   		$default_controller = 'controllers_'.$myconf['default_controller'];
		$request->setParam('controller',$myconf['default_controller']);

		// ladujemy domyslny kontroler i przekazujemy muobiekt $request
		// $action_dispatch opcjonalnie pozwala na uruchomienie specyficznej akcji wewnatrz tego kontrolera
		$controllerClass 	= new $default_controller($request,$action_dispatch);
	}

}

?>

Gotowe! Ta bardzo uproszczona wersja głównego kontrolera, w porównaniu do gotowych produkcyjnych frameworków takich jak Zend Framework, w jasny sposób pozwala zrozumieć zasadę działania kontrolerów i routerów.

Zatem podsumowując, powyższy FrontController sprawdza czy w adresie przekazano żądanie do konkretnego kontrolera-pomocnika, jeśli tak, FrontController sprawdzi czy w naszym systemie taki kontroler został utworzony (plik musi istnieć) i załaduje go tworząc nowy obiekt. W przypadku gdy nie przekazano żądania załadowania kontrolera-pomocnika lub gdy żądany kontroler-pomocnik nie istnieje, FrontController załaduje domyślny kontroler w systemie.

Wcześniej wspomniałem, że opisywany tu system jest dość szczególny, otóż jego szczególność polega na tym, że domyślny kontroler, który używam to autorski system CMS i wszystkie zapytania kierowane są bezpośrednio do tego kontrolera CMS. System CMS wraz z kontrolerem potrzebnym do jego implementacji przedstawię w kolejnych częściach tego artykułu.

Na poziomie pliku index.php możemy dowolnie decydować o wyborze dowolnego routera. Do celów prezentacyjnych implementacja standardowego routera wydaje się najlepsza ale należy pamiętać, że jest ich dużo więcej i każdy z nich ma swoje zastosowanie. Aby dodać nowy router wystarczy utworzyć nowy plik i wgrać go do folderu routerów a następnie zainicjować FrontController zgodny z interfejsem Application_Interface_FrontController:

<?php
defined('APP_PATH') or die();

interface Application_Interface_FrontController
{
   public function dispatch(Application_Router_Standard_HttpRequest $request);
   public function dispatch_default_controller(Application_Router_Standard_HttpRequest $request,$action_dispatch);
}
?>

Naturalnym krokiem byłoby również stosowanie interfejsów dla Application_Router_Standard_HttpRequest aczkolwiek stosowane są tam jedynie 2 metody set() i get(), które z powodzeniem można zastąpić metodami magicznymi _set() i _get().

Zapraszam do kolejnej części opisującej kontrolery w modelu MVC.

źródło: zegarki esprit, olejekrycynowy.info, zarty.eu, olejek rycynowy, praca w domu

Powiązane wpisy:

Powiązane słowa kluczowe:

  • php controller
  • controller php
  • własny framework mvc
  • front controller php
  • php front controller
  • mvc dispatch

2 Responses to Część 2. Budujemy własny framework MVC w PHP. Front Controller i kontrolery pomocnicze

  1. Psyho99 says:

    Nie do końca przydatny tutorial.
    Autorze gdzie wyjaśniłeś jak wygląda klasa Application_InputFilter() ???

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>