# Wyjątki MVC

## Wprowadzenie

Komponenty MVC w Zend Framework używają kontrolera frontowego, co oznacza, że wszystkie żądania do danej strony przechodzą przez pojedynczy punkt. W rezultacie wszystkie wyjątki ostatecznie zbierane są w kontrolerze frontowym, pozwalając programiście na obsłużenie ich w jednym miejscu.

Jakkolwiek, wiadomości o wyjątkach oraz informacje o backtrace często zawierają wrażliwe informacje o systemie, jak np. zapytania SQL, informacje o lokacjach plików i wiele innych. Aby pomóc ci chronić swój serwis, domyślnie `Zend_Controller_Front` łapie wszystkie wyjątki i rejestruje je w obiekcie odpowiedzi; z kolei, obiekt odpowiedzi domyślnie nie wyświetla wiadomości o wyjątkach.

## W jaki sposób możesz obsługiwać wyjątki?

Obecnie w komponentach MVC wbudowanych jest kilka mechanizmów pozwalających na obsługę wyjątków

- Domyślnie rejestrowana i aktywna jest wtyczka obsługi błędów. Ta wtyczka została stworzona aby obsługiwać:

  - Błędy spowodowane brakującym kontrolerem lub akcją

  - Błędy występujące wewnątrz akcji kontrolerów

  Wtyczka działa w oparciu o metodę `postDispatch()`, i sprawdza czy obiekt uruchamiający, kontroler akcji, lub inny obiekt wyrzucił wyjątek. Jeśli tak, przekazuje ona żądanie do kontrolera obsługi błędu.

  Ta wtyczka obsłuży większość sytuacji, w których został wyrzucony wyjątek, a także poprawnie obsłuży brakujące kontrolery oraz akcje.

- `Zend_Controller_Front::throwExceptions()`

  Przekazująć logiczną wartość true do tej metody, możesz nakazać kontrolerowi frontowemu aby zamiast składować wyjątki w obiekcie odpowiedzi, wyrzucił je, żebyś mógł obsłużyć je samodzielnie. Na przykład:

      $front->throwExceptions(true);
      try {
          $front->dispatch();
      } catch (Exception $e) {
          // sam obsłuż wyjątki
      }

  Ta metoda jest najprawdopodobniej najłatwiejszym sposobem dodania własnej obsługi wyjątków do twojej aplikacji używającej kontrolera frontowego.

- `Zend_Controller_Response_Abstract::renderExceptions()`

  Przekazując logiczną wartość true do tej metody, możesz nakazać obiektowi odpowiedzi aby renderował on wyjątki gdy sam będzie renderowany. W takim scenariuszu, każdy wyjątek wyrzucony w twojej aplikacji będzie wyświetlony. To jest jedynie rekomendowane dla nieprodukcyjnych środowisk.

- `Zend_Controller_Front::returnResponse()` oraz `Zend_Controller_Response_Abstract::isException()`

  Przekazanie wartości logicznej true do metody `Zend_Controller_Front::returnResponse()`, spowoduje, że obiekt `Zend_Controller_Front::dispatch()` nie będzie renderował odpowiedzi, a zamiast tego ją zwróci. Gdy już masz odpowiedź, możesz sprawdzić czy są w niej wyjątki używając metody `isException()`, a następnie odebrać wyjątki używając metody `getException()`. Na przykład:

      $front->returnResponse(true);
      $response = $front->dispatch();
      if ($response->isException()) {
          $exceptions = $response->getException();
          // obsługa wyjątków ...
      } else {
          $response->sendHeaders();
          $response->outputBody();
      }

  Główną zaletą, dzięki której ta metoda umożliwia więcej niż `Zend_Controller_Front::throwExceptions()`, jest to, że możesz warunkowo wyświetlać odpowiedź po obsłudze wyjątków.

## Wyjątki MVC które możesz napotkać

Różne komponenty MVC -- obiekt żądania, router, obiekt uruchamiający, kontrolery akcji, oraz obiekt odpowiedzi -- każdy może z różnych przyczyn wyrzucać wyjątki. Niektóre wyjątki mogą być warunkowo nadpisane, a inne są używane aby wskazać programiście potrzebę poprawienia aplikacji.

Kilka przykładów:

- `Zend_Controller_Dispatcher::dispatch()` domyślnie wyrzuci wyjątek jeśli zażądano nieprawidłowego kontrolera. Są dwa zalecane sposoby na obsłużenie tego:

  - Ustawienie parametru `useDefaultControllerAlways`.

    W twoim kontrolerze frontowym, lub w obiekcie uruchamiającym, dodaj poniższą dyrektywę:

        $front->setParam('useDefaultControllerAlways', true);

        // lub
        $dispatcher->setParam('useDefaultControllerAlways', true);

    Gdy ta flaga jest ustawiona, obiekt uruchamiający, użyje domyślnego kontrolera oraz akcji zamiast wyrzucania wyjątku. Minusem użycia tej metody jest to, że jakikolwiek błąd literowy w adresie dostępowym do twojej strony spowoduje wyświetlenie strony głównej, co może źle wpłynąć na optymalizację serwisu dla wyszukiwarek internetowych.

  - Wyjątek wyrzucany przez metodę `dispatch()` jest wyjątkiem `Zend_Controller_Dispatcher_Exception` zawierającym tekst 'Invalid controller specified'. Użyj jednej z metod opisanych w poprzedniej sekcji aby złapać wyjątek, a następnie przekierować do strony błędu lub do strony głownej.

- Metoda `Zend_Controller_Action::__call()` wyrzuci wyjątek `Zend_Controller_Action_Exception` jeśli nie może uruchomić nieistniejącej metody akcji. Najczęściej będziesz chciał użyć jakiejś domyślnej akcji w kontrolerze w tego typu sprawach. Przykładowe metody za pomocą których możesz to osiśgnąć:

  - Rozszerzenie klasy `Zend_Controller_Action` i nadpisanie metody `__call()`. Na przykład:

        class My_Controller_Action extends Zend_Controller_Action
        {
            public function __call($method, $args)
            {
                if ('Action' == substr($method, -6)) {
                    $controller = $this->getRequest()->getControllerName();
                    $url = '/' . $controller . '/index';
                    return $this->_redirect($url);
                }

                throw new Exception('Invalid method');
            }
        }

    Powyższa metoda przechwytuje wszystkie wywołane niezdefiniowane akcje i przekierowuje żądanie do domyślnej akcji w kontrolerze.

  - Rozszerzenie klasy `Zend_Controller_Dispatcher` o nadpisanie metody `getAction()`, która sprawdza czy akcja istnieje. Na przykład:

        class My_Controller_Dispatcher extends Zend_Controller_Dispatcher
        {
            public function getAction($request)
            {
                $action = $request->getActionName();
                if (empty($action)) {
                    $action = $this->getDefaultAction();
                    $request->setActionName($action);
                    $action = $this->formatActionName($action);
                } else {
                    $controller = $this->getController();
                    $action     = $this->formatActionName($action);
                    if (!method_exists($controller, $action)) {
                        $action = $this->getDefaultAction();
                        $request->setActionName($action);
                        $action = $this->formatActionName($action);
                    }
                }

                return $action;
            }
        }

    Powyższy kod sprawdza czy zażądana akcja istnieje w klasie kontrolera; jeśli nie, resetuje akcję do akcji domyślnej.

    Ta metoda jest wygodna ponieważ możesz w niewidoczny sposób zmienić akcję przed ostatecznym uruchomieniem. Jednak to także oznacza, że jakikolwiek błąd literowy w adresie URL może wciąż uruchomić żądanie poprawnie, co nie jest zbyt dobre dla optymalizacji dla wyszukiwarek internetowych.

  - Użycie metody `Zend_Controller_Action::preDispatch()` lub `Zend_Controller_Plugin_Abstract::preDispatch()` do zidentyfikowania nieprawidłowych akcji.

    Rozszerzając klasę `Zend_Controller_Action` i modyfikując metodę `preDispatch()`, możesz zmodyfikować wszystkie twoje kontrolery w taki sposób, aby przenosiły one żądanie do innej akcji lub przekierowywały zamiast uruchamiać akcję. Kod wyglądałby podobnie kod nadpisujący metodę `__call()`, który został przedstawiony wyżej.

    Alternatywnie, możesz sprawdzać te informacje we wtyczce globalnej. Zaletą tego rozwiązania jest to, że kontroler akcji staje się niezależny; jeśli twoja aplikacja składa się z różnorodnych kontrolerów akcji i nie wszystkie dziedziczą z tej samej klasy, ta metoda może dodać konsekwencji w obsłudze różnych klas.

    Przykład:

        class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
        {
            public function preDispatch(Zend_Controller_Request_Abstract $request)
            {
                $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
                $controller = $dispatcher->getController($request);
                if (!$controller) {
                    $controller = $dispatcher->getDefaultControllerName($request);
                }
                $action     = $dispatcher->getAction($request);

                if (!method_exists($controller, $action)) {
                    $defaultAction = $dispatcher->getDefaultAction();
                    $controllerName = $request->getControllerName();
                    $response = Zend_Controller_Front::getInstance()->getResponse();
                    $response->setRedirect('/' . $controllerName . '/' . $defaultAction);
                    $response->sendHeaders();
                    exit;
                }
            }
        }

    W tym przykładzie sprawdzamy czy zażądana akcja jest dostępna w kontrolerze. Jeśli nie, przekierujemy żądanie do domyślnej akcji w kontrolerze, i kończymy wykonywanie skryptu.