Rozszerzanie
Wprowadzenie
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.
Konwencje
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.
Przedrostki
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_".
Struktura katalogów
Klasy Zend_Controller są przechowywane w taki
sposób:
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:
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.
Kontroler frontowy
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():
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ć:
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
*/
static public 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 Request
Klasa abstrakcyjna Zend_Controller_Request_Abstract
definiuje garść przydatnych metod:
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 Routera
Interfejs Zend_Controller_Router_Interface definiuje
jedynie jedną metodę:
]]>
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.
Interfejs dispatchera
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:
]]>
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.
]]>
dispatch() jest metodą, która wykonuje całą pracę. Ta
metoda musi uruchomić akcję kontrolera. Musi obiekt żądania.
Kontroler akcji
Kontroler akcji obsługuje różne akcje aplikacji. Ta klasa
abstrakcyjna zapewnia poniższe metody:
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:
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ć:
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
Obiekt odpowiedzi zbiera zawartość i nagłówki z różnych wywołanych
akcji i zwraca je klientowi. Posiada on takie metody:
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.