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.
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.
Data: lip 24, 2009