# MVC Ausnahmen

## Einführung

Die MVC Komponenten im Zend Framework verwenden einen Front Controller, was bedeutet das alle Anfragen zu einer bestimmten Site über einen einzelnen Eintrittspunkt laufen. Als Ergebnis treten alle Ausnahmen eventuell am Front Controllerauf, was es Entwicklern erlaubt diese an einem einzelnen Ort zu behandeln.

Trotzdem enthalten Ausnahmemeldungen und Backtrace Informationen oft sensitive Systeminformationen, wie SQL Anweisungen, Dateiorte, und andere. Um zu helfen die eigene Site zu schützen, werden standardmäßig alle Ausnahmen von `Zend_Controller_Front` gefangen und im Antwortobjekt registriert; zusätzlich zeigt das Antwortobjekt die Ausnahmemeldungen standardmäßig nicht an.

## Behandeln von Ausnahmen

Verschiedene Mechanismen sind bereits in die MVC Komponenten eingebaut um die Behandlung von Ausnahmen zu erlauben.

- Standardmäßig ist das Error Handler Plugin registriert und aktiv. Dieses Plugin wurde erstellt um folgendes zu behandeln:

  - Fehler durch fehlende Controller oder Aktionen

  - Fehler die in Actioncontrollern auftreten

  Es arbeitet als `postDispatch()` Plugin und prüft ob eine Dispatcher, Actioncontroller oder andere Ausnahme aufgetreten ist. Wenn das so ist, leitet es an den Error Handler Controller weiter.

  Dieser Handler deckt die meisten Ausnahmesituationen ab, und behandelt fehlende Controller und Aktionen taktvoll.

- `Zend_Controller_Front::throwExceptions()`

  Durch die Übergabe eines boolschen `TRUE` Wertes an diese Methode, kann dem Front Controller mitgeteilt werden das, statt der Ansammlung der Ausnahmen im Antwortobjekt oder der Verwendung des Error Handler Plugin's, man diese Ausnahmen selbst behandeln will. Als Beispiel:

      $front->throwExceptions(true);
      try {
          $front->dispatch();
      } catch (Exception $e) {
          // Ausnahmen selbst behandeln
      }

  Diese Methode ist möglicherweise der einfachste Weg um eigene Ausnahmebehandlungen hinzuzufügen die den vollen Umfang der möglichen Ausnahmen der Front Controller Anwendung behandeln.

- `Zend_Controller_Response_Abstract::renderExceptions()`

  Durch die Übergabe eines boolschen `TRUE` Wertes an diese Methode kann dem Antwortobjekt mitgeteilt werden das es Ausnahmenachrichten und Backtrace darstellen soll, wenn es selbst dargestellt wird. In diesem Szenario wird jede Ausnahme die an der Anwendung auftritt angezeigt. Das wird nur in nicht-produktiven Umgebungen vorgeschlagen.

- `Zend_Controller_Front::returnResponse()` und `Zend_Controller_Response_Abstract::isException()`.

  Durch die Übergabe eines boolschen `TRUE` an `Zend_Controller_Front::returnResponse()`, wird `Zend_Controller_Front::dispatch()` die Antwort nicht darstellen, aber diese stattdessen zurückgeben. Sobald man die antwort hat, kann diese getestet werden um zu sehen ob irgendwelche Ausnahmen gefangen wurden indem die `isException()` Methode verwendet, und die Ausnahme über die `getException()` Methode empfangen wird. Als Beispiel:

      $front->returnResponse(true);
      $response = $front->dispatch();
      if ($response->isException()) {
          $exceptions = $response->getException();
          // Ausnahme behandeln ...
      } else {
          $response->sendHeaders();
          $response->outputBody();
      }

  Der primäre Vorteil welche diese Methode über `Zend_Controller_Front::throwExceptions()` bietet ist, das Sie es erlaubt die Antwort wahlweise darzustellen nachdem die Ausnahme behandelt wurde. Das fängt jede Ausnahme in der Controllerkette, im Gegensatz zum Error Handler Plugin.

## MVC Ausnahme die auftreten können

Die verschiedenen MVC Komponenten -- Anfragen, Router, Dispatcher, Actioncontroller, und Antwortobjekte -- können alle gelegentlich Ausnahmen werfen. Einige Ausnahmen können wahlweise überschrieben werden und andere werden Verwendet um dem Entwickler anzuzeigen das die eigene Struktur der Anwendung überdacht werden sollte.

Einige Beispiele:

- `Zend_Controller_Dispatcher::dispatch()` wird standardmäßig eine Ausnahme werfen wenn ein ungültiger Controller angefragt wird. Es gibt zwei empfohlene Wege um damit umzugehen.

  - Den useDefaultControllerAlways Parameter setzen.

    Im eigenen Frontcontroller, oder dem eigenen Dispatcher, die folgende Anweisung hinzufügen:

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

        // oder

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

    Wenn dieses Flag gesetzt ist, wird der Dispatcher den Standardcontroller und die Standardaktion verwenden statt eine Ausnahme zu werfen. Der Nachteil dieser Methode ist das jegliche Schreibfehler die ein Benutzer macht wenn er auf die Site zugreift, trotzdem aufgelöst werden und die Homepage angezeigt wird, was bei der Optimierung von Suchmaschienen verherenden Schaden anrichten kann.

  - Die Ausnahme die von `dispatch()` geworfen wird, ist eine `Zend_Controller_Dispatcher_Exception` die den Text 'Invalid controller specified' enthält. Eine der Methoden die in der vorhergehenden Sektion beschrieben wurden können verwendet werden um die Ausnahme zu fangen und dann zu einer generellen Fehlerseite oder der Homepage umzuleiten.

- `Zend_Controller_Action::__call()` wird eine `Zend_Controller_Action_Exception` geworfen wenn eine nicht existierende Aktion einer Methode nicht dargestellt werden kann. Normalerweise wird es gewünscht sein in Fällen wie diesen eine Standardaktion im Controller zu verwenden. Wege um das zu tun beinhalten:

  - Eine Subklasse von `Zend_Controller_Action` erstellen und die `__call()` Methode überschreiben. Als Beispiel:

        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('Ungültige Methode');
            }
        }

    Das obige Beispiel fängt jede nicht definierte Aktionsmethode ab die aufgerufen wird und leitet Sie zur Standardaktion im Controller um.

  - Eine Subklasse von `Zend_Controller_Dispatcher` erstellen und die `getAction()` Methode überschreiben um zu prüfen ob die Aktion existiert. Als Beispiel:

        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;
            }
        }

    Der obige Code prüft ob die angefragte Aktion in der Controllerklasse existiert ; wenn nicht wird die Aktion auf die Standardaktion resetiert.

    Diese Methode ist nützlich, weil Sie die Aktion transparent ändert bevor diese letztendlich dargestellt wird. Trotzdem bedeutet es auch, das Schreibfehler in der URL trotzdem richtig dargestellt werden, was für die Optimierung von Suchmaschinen nicht gut ist.

  - Verwenden von `Zend_Controller_Action::preDispatch()` oder `Zend_Controller_Plugin_Abstract::preDispatch()` um eine ungültige Aktion zu identifizieren.

    Durch das Erstellen einer Subklasse von `Zend_Controller_Action` und dem modifizieren von `preDispatch()`, können alle eigenen Controller geändert werden damit Sie an andere Aktionen weiterleiten oder umleiten bevor die Aktion letztendlich dargestellt wird. Der Code hierfür schaut ähnlich wie der Code für das Überschreiben von `__call()` aus, der oben schon angezeigt wurde.

    Alternativ kann diese Information in einem globalen Plugin geprüft werden. Das hat den Vorteil das es unabhängig von Actioncontroller ist; wenn die eigene Anwendung aus einer Reihe von Actioncontrollern besteht, und nicht alle von der gleichen Klasse abgeleitet sind, kann diese Methode Kontinuität in der Handhabung der verschiedenen Klassen bringen.

    Als Beispiel:

        class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
        {
            public function preDispatch(Zend_Controller_Request_Abstract $request)
            {
                $front      = Zend_Controller_Front::getInstance();
                $dispatcher = $front->getDispatcher();
                $class      = $dispatcher->getControllerClass($request);
                if (!$controller) {
                    $class = $dispatcher->getDefaultControllerClass($request);
                }

                $r      = new ReflectionClass($class);
                $action = $dispatcher->getActionMethod($request);

                if (!$r->hasMethod($action)) {
                    $defaultAction  = $dispatcher->getDefaultAction();
                    $controllerName = $request->getControllerName();
                    $response       = $front->getResponse();
                    $response->setRedirect('/' . $controllerName
                                          . '/' . $defaultAction);
                    $response->sendHeaders();
                    exit;
                }
            }
        }

    In diesem Beispiel wird geprüft ob die angefragte Aktion im Controller vorhanden ist. Wenn dem nicht so ist, wird auf die Standardaktion im Controller umgeleitet und die Ausführung des Sktipts sofort beendet.