Klasa Zend_Controller
została zbudowana w sposób
bardzo elastyczny. Można ją rozwijać rozszerzając klasy istniejące
lub pisząc nowe klasy implementujące interfejsy
Zend_Controller_Router_Interface
oraz
Zend_Controller_Dispatcher_Interface
lub rozszerzając
klasy Zend_Controller_Request_Abstract
,
Zend_Controller_Response_Abstract
, oraz
Zend_Controller_Action
.
Powodami dla których warto implementować nowy router lub dispatcher mogą być:
Istniejący w Zend Framework system routingu URI nie jest kompatybilny. Np. gdy chcemy go zintegrować z istniejącą witryną która używa swoich własnych konwencji routingu, które nie są kompatybilne z mechanizmem routingu dostarczanym przez Zend Framework.
Potrzebujesz zaimplementować routing dla czegoś zupełnie
innego. Klasa Zend_Controller_Router
działa
jedynie z adresami URI. Jest prawdopodobne że chciałbyś
użyć wzorca MVC do opracowania innego typu aplikacji, np.
aplikacji konsolowej lub aplikacji z GUI. W przypadku
aplikacji konsolowej własny obiekt żądania
mógłby obrabiać argumenty linii poleceń.
Mechanizm dostarczany przez
Zend_Controller_Dispatcher
nie jest
kompatybilny. Domyślna konfiguracja przyjmuje taką
konwencję, że kontrolery są klasami, a akcje metodami
tych klas. Bądź co bądź, jest wiele innych sposobów
wykonania tego. Przykładem może być takie rozwiązanie,
w którym kontrolery są katalogami a akcje plikami w
tych katalogach.
Chciałbyś dostarczyć dodatkowe możliwości które będą
odziedziczone przez wszystkie kontrolery. Na przykład
Zend_Controller_Action
nie jest domyślnie
zintegrowany z Zend_View
. Jednak mógłbyś
rozszerzyć swój własny kontroler aby to robił i
zapewnienie takiej funkcjonalności nie wymagałoby
modyfikowania dostarczonych klas Zend_Controller_Router
oraz
Zend_Controller_Dispatcher
.
Chciałbyś zalogować wyjątki aplikacji gdy zostają złapane
i przekierować do ogólnej strony błędu. Rozszerzając
Zend_Controller_Response_Http
, możesz
zmodyfikować metodę __toString()
aby
sprawdzić zarejestrowane wyjątki, zalogować je, a potem
przekierować do strony błędu.
Proszę być ostrożnym podczas nadpisywania znaczących części systemu, sczególnie
wtedy gdy jest to dispatcher. Jedną z zalet klasy Zend_Controller
jest to że wprowadza ona ogólne konwencje budowy aplikacji. Jeżeli odejdziemy
zbyt daleko od tych konwencji, możemy stracić część tych zalet. Jednak
jest wiele różnych zapotrzebowań i jedno rozwiązanie nie jest w stanie spełnić
ich wszystkich więc dowolność jest zapewniona gdy jest potrzebna.
Kiedy rozszerzasz którekolwiek klasy Zend_Controller powinieneś użyć takich samych konwencji w nazywaniu i przechowywaniu plików. Takie postępowanie spowoduje to, że inny programista który jest zaznajomiony z Zend Framework będzie w stanie łatwo zrozumieć Twój projekt.
Klasy ładowane przez Zend Framework są nazywane wg tej samej konwencji, każda z nich jest poprzedzona przedrostkiem "Zend_". Zalecamy abyś nazywał wszystkie swoje klasy w analogiczny sposób, np. jeśli Twoja firma nazywa się Widget Inc., to prefiksem mogłoby być "Widget_".
Klasy Zend_Controller
są przechowywane w taki
sposób:
/library /Zend /Controller Action.php Dispatcher.php Router.php
Kiedy rozszerzasz klasy Zend_Controller
, zalecane
jest aby nowa klasa była przechowywana w identyczny sposób z
uwzględnieniem własnego prefiksu. To spowoduje że będą one łatwe
do znalezienia i zrozumienia dla kogoś kto przegląda kod Twojego
projektu.
Na przykład struktura projektu firmy Widget Inc., który implementuje jedynie własny router mogłaby wyglądać w ten sposób:
/library /Zend /Widget /Controller Router.php README.txt
Pamiętaj, że w tym przykładzie Widget/Controller/
ma taką samą strukturę jak Zend/Controller/
kiedy
tylko jest to możliwe. W tym przypadku definiuje on klasę
Widget_Controller_Router
, która może być klasa
rozszerzającą lub zastępującą klasę Zend_Controller_Router
implementującą Zend_Controller_Router_Interface
.
Zwróć także uwagę na to, że w powyższym przykładzie plik
README.txt
został umieszczony w katalogu
Widget/Controller/
. Zend zaleca abyś dokumentował
swoje projekty dostarczając klientom osobne testy oraz dokumentację.
Jakkolwiek, zalecamy Ci abyś także tworzył prosty plik
README.txt
w katalogu swojej klasy
aby wyjaśnić zmiany oraz zasady jej działania.
Zend_Controller_Front implementuje kontroler frontowy. Dodatkowo ta klasa jest singletonem, co oznacza, że podczas wywołania będzie dostępna tylko jedna instancja tej klasy.
Aby rozszerzyć tę klasę, musisz przynajmniej nadpisać metodę
getInstance()
:
class My_Controller_Front extends Zend_Controller_Front { public static function getInstance() { if (null === self::$_instance) { self::$_instance = new self(); } return self::$_instance; } }
Nadpisanie metody getInstance() gwarantuje to, że wywołania metody
Zend_Controller_Front::getInstance()
będą zwracać
instancję twojej nowej podklasy zamiast instancji
Zend_Controller_Front -- jest to szczególnie przydatne dla
niektórych alternatywnych routerów oraz klas pomocników widoków.
Oprócz metody getInstance() jest jeszcze wiele metod, ktore możesz nadpisać:
/** * Resetuje wszystkie właściwości instancji obiektu * * Głownie używane do teestowania; może być użyta do łańcuchowego * wywoływania kontrolerów frontowych. * * @return void */ public function resetInstance(); /** * Wygodna funkcjonalność, wywołuje setControllerDirectory()->setRouter()->dispatch() * * W PHP 5.1.x, wywołanie metody statycznej nigdy nie tworzy $this -- więc * metoda run() może być aktualnie wywołana po ustawieniu kontrolera frontowego * * @param string|array $controllerDirectory Ścieżka do klas kontrolerów * Zend_Controller_Action lub tablica tych ścieżek * @return void * @throws Zend_Controller_Exception jeśli wywołana z instancji obiektu */ public static function run($controllerDirectory); /** * Dodaje ścieżkę kontrolerów na stos ścieżek kontrolerów * * Jeśli $args jest łańcuchem znaków, używany jest on jako klucz odpowiadający * danej ścieżce * * @param string $directory * @param mixed $args Argument opcjonalny; jeśli wartość jest łańcuchem znaków * używany jest on jako klucz tablicy * @return Zend_Controller_Front */ public function addControllerDirectory($directory, $args = null); /** * Ustawia ścieżkę kontrolerów * * Ustawia ścieżkę kontrolerów w celu przekazania jej do obiektu * uruchamiającego. Może być tablicą ścieżek lub łańcuchem znaków * zawierającym pojedynczą ścieżkę. * * @param string|array $directory Ścieżka do klas kontrolerów * Zend_Controller_Action lub tablica tych ścieżek * @return Zend_Controller_Front */ public function setControllerDirectory($directory); /** * Pobiera ścieżkę kontrolerów * * Pobiera ustawioną ścieżkę kontrolerów * * @return string|array */ public function getControllerDirectory(); /** * Ustawia nazwę domyślnego kontrolera (niesformatowany łańcuch znaków) * * @param string $controller * @return Zend_Controller_Front */ public function setDefaultController($controller); /** * Pobiera nazwę domyślnego kontrolera (niesformatowany łańcuch znaków) * * @return string */ public function getDefaultController(); /** * Ustawia nazwę domyślnej akcji (niesformatowany łańcuch znaków) * * @param string $action * @return Zend_Controller_Front */ public function setDefaultAction($action); /** * Pobiera nazwę domyślnej akcji (niesformatowany łańcuch znaków) * * @return string */ public function getDefaultAction(); /** * Ustawia klasę/obiekt żądania * * Ustawia obiekt żądania. Obiekt żądania przechowuje środowisko żądania * * Jeśli podano nazwę klasy, zostanie utworzona jej instancja * * @param string|Zend_Controller_Request_Abstract $request * @throws Zend_Controller_Exception jeśli podano nieprawidłową klasę żądania * @return Zend_Controller_Front */ public function setRequest($request); /** * Zwraca obiekt żądania. * * @return null|Zend_Controller_Request_Abstract */ public function getRequest(); /** * Ustawia klasę/obiekt routera * * Ustawia obiekt routera. Router jest odpowiedzialny za mapowanie żądania * do nazwy kontrolera oraz akcji * * Jeśli podana jest nazwa klasy, tworzona jest instancja routera z * wszystkimi parametrami zarejestrowanymi za pomocą {@link setParam()} * lub {@link setParams()}. * * @param string|Zend_Controller_Router_Interface $router * @throws Zend_Controller_Exception jeśli podano nieprawidłową klasę kontrolera * @return Zend_Controller_Front */ public function setRouter($router); /** * Zwraca obiekt routera. * * Tworzy instancję obiektu Zend_Controller_Router jeśli żaden nie jest ustawiony. * * @return null|Zend_Controller_Router_Interface */ public function getRouter(); /** * Ustawia bazowy adres URL dla żądań * * Używana do ustawienia bazowej części adresu URL dla REQUEST_URI w celu * określenia ścieżki PATH_INFO, itp. Przykłady: * - /admin * - /myapp * - /subdir/index.php * * Zauważ, że ten adres URL nie powinien zawierać całego adresu URI. Nie używaj: * - http://example.com/admin * - http://example.com/myapp * - http://example.com/subdir/index.php * * Jeśli przekazana jest wartość null, może być ona wykryta (domyślnie). * * @param string $base * @return Zend_Controller_Front * @throws Zend_Controller_Exception dla zmiennej $base która nie jest łańcuchem znaków */ public function setBaseUrl($base = null); /** * Pobiera obecnie ustawiony bazowy URL * * @return string */ public function getBaseUrl(); /** * Ustawia obiekt uruchamiający. Jest on odpowiedzialny za * pobranie obiektu Zend_Controller_Request_Abstract, tworzenie instancji * kontrolera, oraz wywoływanie metody akcji tego kontrolera. * * @param Zend_Controller_Dispatcher_Interface $dispatcher * @return Zend_Controller_Front */ public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher); /** * Zwraca obiekt uruchamiający. * * @return Zend_Controller_DispatcherInteface */ public function getDispatcher(); /** * Ustawia klasę/obiekt odpowiedzi * * Ustawia obiekt odpowiedzi. Obiekt odpowiedzi jest pojemnikiemn na * odpowiedź akcji oraz nagłówki. Użycie jest opcjonalne. * * Jeśli podano nazwę klasy, zostanie utworzona jej instancja * * @param string|Zend_Controller_Response_Abstract $response * @throws Zend_Controller_Exception jeśli klasa odpowiedzi jest nieprawidłowa * @return Zend_Controller_Front */ public function setResponse($response); /** * Zwraca obiekt odpowiedzi. * * @return null|Zend_Controller_Response_Abstract */ public function getResponse(); /** * Dodaje lub modyfikuje parametr do użycia przez kontrolery akcji * * @param string $name * @param mixed $value * @return Zend_Controller_Front */ public function setParam($name, $value); /** * Ustawia parametry do przekazania do konstruktorów kontrolerów akcji * * @param array $params * @return Zend_Controller_Front */ public function setParams(array $params); /** * Odbiera pojedynczy parametr ze stosu parametrów kontrolera * * @param string $name * @return mixed */ public function getParam($name); /** * Odbiera parametry wywołania kontrolera * * @return array */ public function getParams(); /** * Czyści stos parametrów kontrolera * * Domyślnie czyści wszystkie parametry. Jeśli podana została nazwa parametru, * czyści tylko ten parametr; jeśli podana została tablica nazw, czyści * wszystkie podane parametry. * * @param null|string|array pojedynczy klucz lub tablica kluczy dla parametrów do wyczyszczenia * @return Zend_Controller_Front */ public function clearParams($name = null); /** * Rejestruje wtyczkę. * * @param Zend_Controller_Plugin_Abstract $plugin * @return Zend_Controller_Front */ public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin); /** * Wyrejestrowuje wtyczkę. * * @param Zend_Controller_Plugin_Abstract $plugin * @return Zend_Controller_Front */ public function unregisterPlugin(Zend_Controller_Plugin_Abstract $plugin); /** * Ustawia czy wyjątki napotkane w pętli uruchomieniowej mają być wyrzucane * czy mają być łapane w obiekcie odpowiedzi * * Domyślnym zachowaniem jest złapanie ich w obiekcie odpowiedzi; wywołaj * tę metodę aby spowodować wyrzucenie ich * * @param boolean $flag Domyślnie ma wartość true * @return boolean Zwraca obecne ustawienie */ public function throwExceptions($flag = null); /** * Ustawia czy {@link dispatch()} ma zwracać odpowiedź bez wcześniejszego * renderowania danych wyjściowych. Domyślnie dane wyjsciowe są renderowane * a metoda dispatch() nie zwraca nic. * * @param boolean $flag * @return boolean Zwraca obecne ustawienie */ public function returnResponse($flag = null); /** * Uruchamia żądanie HTTP do kontrolera/akcji. * * @param Zend_Controller_Request_Abstract|null $request * @param Zend_Controller_Response_Abstract|null $response * @return void|Zend_Controller_Response_Abstract Zwraca obiekt odpowiedzi jeśli returnResponse() zwraca true */ public function dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);
Zastosowaniem kontrolera frontowego jest ustawienie środowiska żądania, dopasowanie tras dla żądania, oraz uruchomienie wszystkich akcji. Ostatecznie przechowuje on odpowiedzi i następnie je zwraca.
Głównym powodem rozszerzenia kontrolera frontowego może być potrzeba zmiany logiki dla jednej z metod dostępowych (na przykład, aby załadować inny domyślny router lub obiekt uruchamiający, lub aby określić inną logikę obsługi ścieżek kontrolerów), lub potrzeba zmiany działania routingu lub procesu uruchomienia.
Klasa abstrakcyjna Zend_Controller_Request_Abstract
definiuje garść przydatnych metod:
/** * @return string */ public function getControllerName(); /** * @param string $value * @return self */ public function setControllerName($value); /** * @return string */ public function getActionName(); /** * @param string $value * @return self */ public function setActionName($value); /** * @return string */ public function getControllerKey(); /** * @param string $key * @return self */ public function setControllerKey($key); /** * @return string */ public function getActionKey(); /** * @param string $key * @return self */ public function setActionKey($key); /** * @param string $key * @return mixed */ public function getParam($key); /** * @param string $key * @param mixed $value * @return self */ public function setParam($key, $value); /** * @return array */ public function getParams(); /** * @param array $array * @return self */ public function setParams(array $array); /** * @param boolean $flag * @return self */ public function setDispatched($flag = true); /** * @return boolean */ public function isDispatched(); }
Obiekt żądania jest pojemnikiem dla środowiska żądania. Łańcuch kontrolerów jedynie potrzebuje wiedzieć jak określić i odebrać nazwę kontrolera, akcji, opcjonalne parametry oraz status uruchomienia. Domyślnie, obiekt żądania będzie szukał swoich parametrów używając kluczy kontrolera i akcji w celu określenia kontrolera i akcji.
Interfejs Zend_Controller_Router_Interface
definiuje
jedynie jedną metodę:
<?php /** * @param Zend_Controller_Request_Abstract $request * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Request_Abstract */ public function route(Zend_Controller_Request_Abstract $request); ?>
Proces routingu ma miejsce tylko raz: wtedy gdy system po raz pierwszy otrzymuje żądanie. Celem routera jest określenie kontrolera, akcji, opcjonalnych parametrów na podstawie żądania i przekazanie ich do obiektu żądania. Obiekt żądania jest wtedy przekazywany do dispatchera. Jeśli nie jest możliwe określenie mapowanie trasy do tokena to router nie powinien nic zrobić z obiektem żądania.
Zend_Controller_Front
wpierw wywoła router aby określić
pierwszą uruchamialną akcję w żądaniu. Wtedy wchodzi on pętlę
uruchomieniową.
W pętli, wpierw ustawia flagę uruchomienia obiektu żądania, a następnie uruchamia żądanie (tworzy instancję kontrolera, wywołuje jego akcję). Jeśli metoda akcji (lub metody pre/postDispatch plugina) resetuje flagę uruchomienia obiektu żądania, kontroler frontowy wykona następna iterację pętli uruchomieniowej z akcją, która jest ustawiona w obiekcie żądania. To pozwala na uruchamianie akcji sekwencyjnie, aż do momentu gdy wszystkie potrzebne zostaną uruchomione.
Interfejs Zend_Controller_Dispatcher_Interface
dostarcza definicje dwóch metod:
<?php /** * @param Zend_Controller_Request_Abstract $request * @return boolean */ public function isDispatchable(Zend_Controller_Request_Abstract $request); ?>
Metoda isDispatchable()
sprawdza czy jest możliwe
uruchomienie akcji z żądania. Jeśli jest to możliwe, zwraca ona
wartość TRUE
. W przeciwnym wypadku zwraca wartość
FALSE
. Decyzja o tym czy jest możliwe uruchomienie
akcji została pozostawiona klasie implementującej interfejs. W
domyślnej implementacji klasy Zend_Controller_Dispatcher
oznacza to sprawdzenie, czy plik kontrolera istnieje, czy klasa
istnieje w tym pliku oraz czy wewnątrz klasy istnieje żądana akcja.
<?php /** * @param Zend_Controller_Request_Abstract $route * @return Zend_Controller_Request_Abstract */ public function dispatch(Zend_Controller_Request_Abstract $request); ?>
dispatch()
jest metodą, która wykonuje całą pracę. Ta
metoda musi uruchomić akcję kontrolera. Musi obiekt żądania.
Kontroler akcji obsługuje różne akcje aplikacji. Ta klasa abstrakcyjna zapewnia poniższe metody:
/** * @param Zend_Controller_Request_Abstract $request Obiekt żądania * @param Zend_Controller_Response_Abstract $response Obiekt odpowiedzi * @param array $args Opcjonalna tablica asocjacyjna ustawień * konfiguracyjnych */ public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $args = array()); /** * @return void */ public function init(); /** * @return Zend_Controller_Request_Abstract */ public function getRequest(); /** * @param Zend_Controller_Request_Abstract $request * @return self */ public function setRequest(Zend_Controller_Request_Abstract $request); /** * @return Zend_Controller_Response_Abstract */ public function getResponse(); /** * @param Zend_Controller_Response_Abstract $response * @return self */ public function setResponse(Zend_Controller_Response_Abstract $response); /** * @return array */ public function getInvokeArgs(); /** * @return mixed */ public function getInvokeArg($name); public function preDispatch(); public function postDispatch(); /** * @param string $methodName * @param array $args */ public function __call($methodName, $args); /** * @param null|Zend_Controller_Request_Abstract $request Opcjonalny obiekt * żądania do użycia * @param null|Zend_Controller_Response_Abstract $response Opcjonalny obiekt * odpowiedzi do użycia * @return Zend_Controller_Response_Abstract */ public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);
Konstruktor rejestruje obiekty żądania i odpowiedzi w obiekcie, tak
samo rejestruje tablicę dodatkowych argumentów konfiguracyjnych. Ta
ostatnia tablica składa się z parametrów zarejestrowanych w
kontrolerze frontowym za pomocą metod setParam()
lub
setParams()
. Kiedy zostanie to zrobione, konstruktor
przekazuje obsługę do metody init()
.
Chociaż możesz nadpisać konstruktor, polecamy umieszczenie całej
obsługi inicjalizacyjnej do metody init()
aby upewnić się
że obiekty żądania i odpowiedzi zostaną prawidłowo zarejestrowane.
Argumenty konfiguracyjne przekazane do konstruktora są potem
dostępne za pomocą metod getInvokeArg()
oraz
getInvokeArgs()
. Zalecane jest użycie argumentów
konfiguracyjnych do przekzania takich obiektów jak widok,
autentykacja/autoryzacja, lub obiekt rejestru. Na przykład:
$front = Zend_Controller_Front::getInstance(); $front->setParam('view', new Zend_View()) ->setControllerDirectory($config->controller->directory); $response = $front->dispatch(); // W przykładowej akcji kontrolera: class FooController extends Zend_Controller_Action { protected $_view = null; public function init() { $this->_view = $this->getInvokeArg('view'); } }
Kiedy akcja jest uruchamiana, możliwe jest wykonanie określonego
kodu przed i po akcji za pomocą metod preDispatch()
oraz postDispatch()
, odpowiednio. Domyślnie są one
puste i nic nie robią.
Metoda __call()
obsługuje każdą niezarejestrowaną akcję
w klasie. Domyślnie wyrzuca ona wyjątek jeśli akcja nie jest
zdefiniowana. To powinno wystąpić jedynie gdy metoda akcji domyślnej
nie jest zdefiniowana.
Domyślną konwencją nazewnictwa dla metod akcji jest lowercaseAction,
gdzie 'lowercase' określa nazwę akcji, a 'Action' określa, że ta
metoda jest metodą akcji. Dlatego wywołanie adresu
http://framework.zend.com/foo/bar
uruchomi akcję
FooController::barAction()
.
Kontrolery akcji mogą być także użyte jako kontrolery stron. Najbardziej typowym przykładem użycia może być:
$controller = new FooController( new Zend_Controller_Request_Abstract(), new Zend_Controller_Response_Abstract() ); $controller->run();
Użycie kontrolera frontowego i akcji | |
---|---|
Rekomendujemy użycie kombinacji kontrolera frontowego i kontrolera akcji zamiast kontrolera strony aby zachęcić do pisania aplikacji, które mają współdziałać. |
Obiekt odpowiedzi zbiera zawartość i nagłówki z różnych wywołanych akcji i zwraca je klientowi. Posiada on takie metody:
/** * @param string $name Nazwa nagłówka * @param string $value Wartość nagłówka * @param boolean $replace Określa czy nagłówki o nazwie takiej samej jak * zarejestrowane mają być zastępowane * @return self */ public function setHeader($name, $value, $replace = false); /** * @return array */ public function getHeaders(); /** * @return void */ public function clearHeaders(); /** * Sends all headers * @return void */ public function sendHeaders(); /** * @param string $content * @return self */ public function setBody($content); /** * @param string $content * @return self */ public function appendBody($content); /** * @return string */ public function getBody(); /** * echoes body content * @return void */ public function outputBody(); /** * @param Exception $e * @return self */ public function setException(Exception $e); /** * @return null|Exception */ public function getException(); /** * @return boolean */ public function isException(); /** * @param boolean $flag * @return boolean */ public function renderExceptions($flag = null); /** * @return string */ public function __toString();
Metoda setBody()
zastąpi całość zawartości strony;
zalecamy używanie zamiast niej metody appendBody()
.
Metoda __toString()
powinna renderować całą zawartość
i wysyłać wszystkie nagłówki.
Obiekt odpowiedzi jest także miejscem w którym wyjątki kontrolera
akcji są ostatecznie wyłapywane i rejestrowane (o ile zostało
to włączone za pomocą
Zend_Controller_Front::throwExceptions()
). Metoda
isException()
powinna zwrócić wartość logiczną
oznaczającą czy to się zdarzyło czy nie. Metoda
renderExceptions()
powinna być użyta aby sprawdzić
czy metoda __toString()
zrenderuje dane o wyjątku jeśli
jakikolwiek wyjątek został złapany.