Rozdział 7. Zend_Controller

Spis treści

7.1. Wstęp
7.1.1. Wprowadzenie
7.1.2. Obiekt żądania
7.1.3. Proces Routingu
7.1.4. Proces uruchamiania
7.1.5. Obiekt odpowiedzi
7.2. Pierwsze kroki
7.2.1. Wprowadzenie
7.2.2. Konfiguracja serwera
7.2.3. Plik ładujący
7.2.4. Struktura katalogów
7.2.5. Domyślny kontroler
7.3. Rozszerzanie
7.3.1. Wprowadzenie
7.3.2. Konwencje
7.3.3. Kontroler frontowy
7.3.4. Klasa abstrakcyjna Request
7.3.5. Interfejs Routera
7.3.6. Interfejs dispatchera
7.3.7. Kontroler akcji
7.3.8. Obiekt odpowiedzi
7.4. Dostępne podklasy
7.4.1. Wprowadzenie
7.4.2. Zend_Controller_Request_Http
7.4.3. Zend_Controller_Router_Rewrite
7.4.4. Zend_Controller_Response_Http
7.4.5. Zend_Controller_Response_Cli
7.5. Action Controllers
7.5.1. Wprowadzenie
7.5.2. Inicjalizacja obiektu
7.5.3. Metody Pre-Dispatch oraz Post-Dispatch
7.5.4. Metody dostępowe
7.5.5. Metody narzędziowe
7.6. Wtyczki
7.6.1. Wprowadzenie
7.6.2. Pisanie wtyczek
7.6.3. Użycie wtyczek
7.7. Użycie konwencjonalnej modularnej struktury katalogów
7.7.1. Wprowadzenie
7.7.2. Określanie ścieżek kontrolera modułów
7.7.3. Dopasowanie tras do modułów
7.7.4. Moduł lub globalny domyślny kontroler
7.8. Wyjątki MVC
7.8.1. Wprowadzenie
7.8.2. W jaki sposób możesz obsługiwać wyjątki?
7.8.3. Wyjątki MVC które możesz napotkać
7.9. Migracja z poprzednich wersji
7.9.1. Migracja z wersji 0.6.0 do 0.8.0
7.9.2. Migracja z wersji 0.2.0 lub z poprzednich do 0.6.0

7.1. Wstęp

7.1.1. Wprowadzenie

Zend_Controller dostarcza system umożliwiający budowę witryny opartej na wzorcu MVC (Model-View-Controller - Model-Widok-Kontroler).

System Zend_Controller został zaprojektowany aby być lekkim, modułowym i rozszerzalnym. Projekt jest minimalistyczny w celu zapewnienia elastyczności i wolności użytkownikom, jednak zapewniając wystarczającą strukturę powoduje, że systemy zbudowane w oparciu o Zend_Controller będą napisane w podobnych konwencjach.

System Zend_Controller został zaimplementowany jako kilka komponentów. Nie jest wymagane posiadanie dokładnej wiedzy na temat działania wszystkich tych komponentów, jednak znajomość tego procesu może być pomocna.

  • Zend_Controller_Front instrumentuje całą przestrzeń systemu Zend_Controller. Jest interpretacją wzorca projektowego FrontController. Obiekt Zend_Controller_Front przetwarza wszystkie żądania otrzymywane przez serwer i jest ostatecznie odpowiedzialny za przekazywanie żądań do odpowiednich kontrolerów (Zend_Controller_Action).

  • Zend_Controller_Request_Abstract reprezentuje środowisko żądania i zapewnia metody do ustawienia i odebrania nazw kontrolera i akcji oraz parametrów żądania. Dodatkowo przechowuje informacje o tym, czy akcja z tego żądania została uruchomiona przez Zend_Controller_Dispatcher czy nie. Rozszerzenia do abstrakcyjnego obiektu żądania mogą być użyte aby obudować całe środowisko żądania, pozwalając routerom na wyciągnięcie informacji ze środowiska żądania w celu ustawienia nazwy kontrolera i akcji.

    Domyślnie używany jest obiekt, Zend_Controller_Request_Http, ktory zapewnia dostęp do całości środowiska żądania HTTP.

  • Interfejs Zend_Controller_Router_Interface jest używany do definiowania tras. Routing jest procesem badania środowiska żądania w celu określenia który kontroler i która akcja tego kontrolera powinny odebrać żądanie. Nazwa tego kontrolera, akcji oraz opcjonalnych parametrów są ustawione w obiekcie żądania a następnie przetwarzane przez Zend_Controller_Dispatcher_Standard. Routing przeprowadzany jest tylko raz: wtedy kiedy żądanie jest po raz pierwszy odebrane, a przed uruchomieniem pierwszego kontrolera.

    Domyślnym routerem jest Zend_Controller_Router_Rewrite.

    Domyśłny router, Zend_Controller_Router_Rewrite, pobiera adres URI, który jest określony w obiekcie Zend_Controller_Request_Http i wyciąga z niego informacje o kontrolerze, akcji oraz parametrach w oparciu o informacje z adresu url. Na przykład, adres URL http://localhost/foo/bar/key/value może być zdekodowany jako kontroler foo, akcja bar, i parametr key o wartości value.

    Zend_Controller_Router_Rewrite może być także użyty do dopasowania bezwzlędnych ścieżek; zobacz dokumentację rewrite routera aby dowiedzieć się więcej.

  • Zend_Controller_Dispatcher_Interface jest używany do definiowania klas uruchamiających. Uruchamianie jest procesem pobierania kontrolera i akcji z obiektu żądania, określania na ich podstawie pliku kontrolera, klasy oraz metody akcji w klasie kontrolera. Jeśli kontroler lub akcja nie istnieje, obsługuje on określanie domyślnych kontrolerów i akcji do uruchomienia.

    Aktualny proces uruchamiania składa się z tworzenia instancji klasy kontrolera i wywoływania metody akcji tej klasy. W przeciwieństwie do routingu, który występuje raz, uruchamianie występuje w pętli. Jeśli status uruchomienia w obiekcie żądania zostanie w którymkolwiek momencie zresetowany, pętla zostanie powtórzona, wywołując akcję która jest obecnie ustawiona w obiekcie żądania. Za pierwszym razem pętla kończy się z ustawioną flagą statusu uruchomienia obiektu żądania (jako wartość logiczna true), co kończy uruchamianie.

    Domyślną klasą uruchamiającą jest Zend_Controller_Dispatcher_Standard. Definiuje ona kontroler jako CamelCasedClasses zakończone słowem Controller, oraz metody akcji jako camelCasedMethods zakończone słowem Action: SomeFooController::barAction. W tym przypadku, odpowiadałoby to kontrolerowi o nazwie somefoo oraz akcji o nazwie bar.

  • Zend_Controller_Action jest podstawowym komponentem kontrolera. Każdy kontroler jest pojedynczą klasą, która rozszerza klasę Zend_Controller_Action, a ta klasa posiada metody które są akcjami.

  • Zend_Controller_Response_Abstract definiuje podstawową klasę odpowiedzi używaną do zbierania i zwracania odpowiedzi z kontrolerów akcji. Zbiera ona zarówno nagłówki jak i zawartość treści, oraz z tego względu, że implementuje metodę __toString(), może być bezpośrednio wyświetlona w celu wysłania wszystkich nagłówków i całej zawartości na raz.

    Domyślną klasą odpowiedzi jest Zend_Controller_Response_Http, która jest odpowiednia do użycia w środowisku HTTP.

Przestrzeń systemu Zend_Controller jest stosunkowo prosta. Żądanie jest odbierane przez obiekt Zend_Controller_Front, który wywołuje Zend_Controller_Router_Rewrite w celu określenia kontrolera (i akcji w tym kontrolerze) do uruchomienia. Zend_Controller_Router_Rewrite rozkłada adres URI na części w celu określenia nazwy kontrolera i akcji w żądaniu. Wtedy Zend_Controller_Front rozpoczyna pętlę uruchamiania akcji z kontrolerów. Uruchamia Zend_Controller_Dispatcher_Standard, przekazując mu żądanie w celu uruchomienia kontrolera i akcji, określonych w żądaniu (lub domyślnych). Gdy kontroler kończy działanie, kontrola wraca do obiektu Zend_Controller_Front. Jeśli kontroler resetując status wykonania żądania wskazał kolejny kontroler, który powinien być uruchomiony pętla kontynuuje działanie i kolejny kontroler zostaje uruchomiony. W przeciwnym wypadku proces się kończy.

7.1.2. Obiekt żądania

Obiekt żądania jest prostym obiektem przekazywanym pomiędzy obiektem Zend_Controller_Front, routerem, dispatcherem i klasami kontrolerów. Przechowuje on nazwę kontrolera, akcji oraz parametrów, które mają być przekazane do akcji, tak samo jak resztę środowiska żądania, które mogą być w środowisku HTTP, CLI lub PHP-GTK.

  • Nazwa kontrolera jest dostępna za pomocą metod getControllerName() oraz setControllerName().

  • Nazwa akcji kontrolera która ma być wywołana jest dostępna za pomocą metod getActionName() oraz setActionName().

  • Parametry, które mają być przekazane do tej akcji są asocjacyjną tablicą par klucz/wartość, ktore są dostępne za pomocą metod getParams() oraz setParams(), lub pojedynczo za pomocą metod getParam() oraz setParam().

Zależnie od typu żądania, może być dostępnych więcej metod. Domyślny obiekt żądania, Zend_Controller_Request_Http, ma na przykład metody do pobierania zażądanego adresu URI, informacji o ścieżce, parametrów $_GET oraz $_POST itp.

Obiekt żądania jest przekazywany do kontrolera frontowego, lub jeśli nie istnieje zostaje utworzona jego instancja na początku procesu uruchomiania, zanim przeprowadzany jest routing. Jest on przekazywany przez każdy obiekt w łańcuchu uruchamiania.

Dodatkowo obiekt żądania jest użyteczny przy testowaniu. Programista może utworzyć środowisko żądania, włączając w to kontroler, akcję, parametry, URI itp. i przekazać obiekt żądania do kontrolera frontowego aby testować działanie aplikacji. Gdy do tego dołożymy obiekt odpowiedzi, precyzyjne testowanie jednostkowe aplikacji MVC staje się możliwe.

7.1.3. Proces Routingu

Zanim zbudujesz swój pierwszy kontroler, powinieneś zrozumieć jak działa proces routingu który jest zaimplementowany jako Zend_Controller_Router_Rewrite. Pamiętaj, że przestrzeń działania jest podzielona na routing, który ma miejsce tylko raz oraz na proces dispatchingu, który odbywa się w pętli.

Zend_Controller_Front wywołuje Zend_Controller_Router_Rewrite (lub inny zarejestrowany router) aby zmapować adres URI do kontrolera oraz do akcji wewnątrz kontrolera. Zend_Controller_Router_Rewrite pobiera adres URI z obiektu żądania i przekazuje go do obiektów tras w jego łańcuchu; domyślnie używa on Zend_Controller_Router_Route_Module do dopasowania adresów URL. Obiekt trasy następnie rozkłada URL na części aby określić nazwę kontrolera, akcji i inne parametry z adresu URL a następnie ustawia je w obiekcie żądania.

Zend_Controller_Router_Route_Module używa bardzo prostego mapowania w celu określenia nazwy kontrolera oraz akcji wewnątrz kontrolera:

http://framework.zend.com/controller/action/
		

Pamiętaj, że powyżej pierwsza część adresu jest nazwą kontrolera, a druga część jest zawsze nazwą akcji.

Opcjonalnie w adresie URI mogą być zdefiniowane parametry które zostaną przekazane do kontrolera. Przyjmują one postać par klucz/wartość:

http://framework.zend.com/controller/action/key1/value1/
		

Jeśli w adresie URI brakuje informacji o kontrolerze lub akcji Zend_Controller_Dispatcher_Standard spróbuje pobrać wartości z parametrów obiektu żądania i jeśli nie zostaną w nim znalezione, użyje domyślnych wartości. Zarówno dla kontrolera jak i dla akcji domyślną wartością jest "index". Pokazuje to ten przykład:

http://framework.zend.com/roadmap/future/
Kontroler: roadmap
Akcja    : future

http://framework.zend.com/roadmap/
Kontroler: roadmap
Akcja    : index

http://framework.zend.com/
Kontroler: index
Akcja    : index
	    
[Notatka] Elastyczność

Jeśli chcesz bardziej elastyczne możliwości, możesz sprawdzić dokumentację Rewrite Routera.

Nazwa kontrolera, nazwa akcji w kontrolerze i parametry opcjonalne są ustawione w obiekcie żądania. Kiedy kontroler frontowy Zend_Controller_Front wchodzi w pętlę uruchomieniową, obiekt żądania będzie przekazany do obiektu Zend_Controller_Dispatcher_Standard.

7.1.4. Proces uruchamiania

Uruchamianie jest procesem pobierania obiektu żądania, Zend_Controller_Request_Abstract, wyciągania z niego nazwy kontrolera, nazwy akcji oraz opcjonalnych parametrów zawartych w nim, następnie tworzenia instancji kontrolera i uruchamiania akcji tego kontrolera. Jeśli kontroler lub akcja nie zostaną znalezione, zostaną użyte wartości domyślne. Zend_Controller_Dispatcher_Standard ustawi wartość index zarówno dla kontrolera jak i akcji, ale pozwala programiście zmienić domyślne wartości używając metod setDefaultController() oraz setDefaultAction().

Uruchamianie działa w pętli w kontrolerze frontowym. Zanim zacznie się uruchamianie, kontroler frontowy sprawdza żądanie aby określić nazwę kontrolera, akcji oraz opcjonalne parametry. Wtedy wchodzi w pętlę urchomieniową, uruchamiając żądanie.

Na początku każdej iteracji, w obiekcie żądania ustawiana jest flaga określająca czy akcja została uruchomiona. Jeśli akcja lub metoda pre/postDispatch plugina zresetuje tę flagę, pętla uruchamiania będzie kontynuowana i spróbuje uruchomić żądanie ponownie. Zmieniając nazwę kontrolera oraz/lub akcji w żądaniu i resetując flagę, programista może zdefiniować łańcuch żądań do uruchomienia.

Metodą kontrolera akcji, która kontroluje takie uruchamianie jest _forward(); wywołaj tę metodę w metodach pre/postDispatch() lub w metodach akcji podając nazwę kontrolera, akcji i opcjonalnie dodatkowe parametry które chcesz wysłać do nowej akcji:

public function myAction()
{
    // coś przetwarzaj...
    // przenieś do innej akcji, FooController::barAction(), w obecnym 
    // module:
    $this->_forward('bar', 'foo', null, array('baz' => 'bogus'));
}

7.1.5. Obiekt odpowiedzi

Obiekt odpowiedzi jest logiczną parą dla obiektu żądania. Jego zadaniem jest zebranie zawartości oraz/lub nagłówków aby mogły być one zwrócone wszystkie na raz. Dodatkowo, kontroler frontowy przekaże wszystkie złapane wyjątki do obiektu odpowiedzi, pozwalając programiście wdzięcznie obsłużyć wyjątki. Ta funkcjonalność może być nadpisana przez ustawienie Zend_Controller_Front::throwExceptions(true):

$front->throwExceptions(true);

Aby wysłać obiekt odpowiedzi, włączając w to nagłówki, użyj metody sendOutput().

$response->sendOutput();

Programiści powinni użyć obiektu odpowiedzi w ich kontrolerach akcji. Zamiast bezpośrednio wyświetlać dane wyjściowe i wysyłać nagłowki powinni przekazywać je do obiektu odpowiedzi:

// Wewnątrz akcji kontrolera:
// Ustaw nagłówek
$this->getResponse()
    ->setHeader('Content-Type', 'text/html')
    ->appendBody($content);

Dzięki temu wszystkie nagłówki są wysyłane na raz, zaraz przed wyświetleniem zawartości.

To czy wyjątek wystąpił w aplikacji możesz sprawdzić za pomocą metody isException(), a odebrać wyjątek możesz używając metody getException(). Dodatkowo możesz stworzyć własny obiekt odpowiedzi, który może przekierowywać do stron błędów, zapisywać informacje o wyjątku, ładnie formatować informacje o wyjątku (dla potrzeb programistycznych) itp.

Możesz odebrać obiekt odpowiedzi po wywołaniu metody dispatch(), lub powodując aby kontroler frontowy zwrócił obiekt odpowiedzi zamiast renderowania danych wyjściowych.

// odbierz obiekt po uruchomieniu:
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
    // zaloguj, wyślij mailem itp.
}

// lub spowoduj aby metoda dispatch() zwróciła obiekt odpowiedzi
$front->returnResponse(true);
$response = $front->dispatch();

// coś przetwarzaj...

// ostatecznie wyświetl odpowiedź
$response->sendOutput();

Domyślnie komunikaty wyjątków nie są wyświetlane. Te zachowanie może być zmienione przez wywołanie metody renderException(), lub włączenie w kontrolerze frontowym opcji throwExceptions(), tak jak pokazano niżej:

$response->renderExceptions(true);
$front->dispatch($request, $response);

// lub:
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
$response->sendOutput();

// lub:
$front->throwExceptions(true);
$front->dispatch();