4.3. Rozszerzanie

4.3.1. 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.

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. W przypadku aplikacji konsolowej własny router mógłby obrabiać argumenty linii poleceń w celu określenia nazw kontrolerów, nazw akcji oraz opcjonalnych parametrów.

  • 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.

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.

4.3.2. 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.

4.3.2.1. 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_".

4.3.2.2. Struktura katalogów

Klasa Zend_Controller jest przechowywana 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.

4.3.3. Interfejs Routera

Interfejs Zend_Controller_Router_Interface definiuje jedynie jedną metodę:

<?php
				
  /**				
   * @param  Zend_Controller_Dispatcher_Interface
   * @throws Zend_Controller_Router_Exception
   * @return Zend_Controller_Dispatcher_Token|boolean
   */
  public function route(Zend_Controller_Dispatcher_Interface $dispatcher);

?>

Proces routingu ma miejsce tylko raz: wtedy gdy system po raz pierwszy otrzymuje żądanie. Celem routera jest wygenerowanie obiektu Zend_Controller_Dispatch_Token który określa kontroler oraz akcję z tego kontrolera. To jest przekazywane do dispatchera. Jeśli nie jest możliwe określenie mapowanie trasy do tokena to zwracana jest wartość logiczna FALSE.

Niektóre routery mogą przetwarzać dynamiczne elementy i przed zwróceniem wygenerowanego tokena potrzebują możliwości sprawdzenia czy jest możliwe jego wykonanie. Z tego powodu, metoda route() routera otrzymuje uchwyt obiektu dispatchera jako jedyny argument. Dispatcher posiada metodę isDispatchable() do sprawdzania możliwości jego wykonania.

4.3.4. Interfejs dispatchera

Zend_Controller_Front wpierw wywołuje router w celu otrzymania tokena, który będzie przekazany do dispatchera. Dispatcher uruchomi akcję (tworząc instancję kontrolera i wywołując jego akcję) a następnie zwróci logiczną wartość FALSE lub kolejny token.

Zend_Controller_Front w pętli wywołuje dispatcher dopóki zwracany jest kolejny token. Nazywamy to pętlą uruchomieniową (dispatch loop). Pozwala ona na sekwencyjne uruchamianie akcji aż wszystkie zostaną wykonane.

Interfejs Zend_Controller_Dispatcher_Interface dostarcza definicje dwóch metod:

<?php
				
/**
 * @param  Zend_Controller_Dispatcher_Token $route
 * @return boolean
 */
public function isDispatchable(Zend_Controller_Dispatcher_Token $route);

?>

Metoda isDispatchable() sprawdza czy jest możliwe uruchomienie akcji z tokena. 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_Dispatcher_Token $route
 * @return Zend_Controller_Dispatcher_Token|boolean
 */
public function dispatch(Zend_Controller_Dispatcher_Token $route);

?>

dispatch() jest metodą, która wykonuje całą pracę. Ta metoda musi uruchomić akcję kontrolera. Musi zwrócić kolejny token lub wartość logiczną FALSE aby określić czy są jeszcze jakieś akcje do uruchomienia.