2012-01-12 9 views
6

Zbudowałem pierwszą usługę sieciową na Zend Framework (1.10), a teraz szukam sposobów na ograniczenie niektórych logiki w moich kontrolerach akcji, aby było łatwiej dla mnie i reszty mojego zespołu, aby rozwinąć i utrzymać usługę.Zend Action Controller - strategia refaktoryzacji

Widzę, gdzie są możliwości refaktoryzacji, ale nie mam jasności co do najlepszych strategii. Najlepsza dokumentacja i samouczki na temat kontrolerów mówią tylko o aplikacjach na małą skalę i nie dyskutują o tym, jak wyodrębnić bardziej powtarzalny kod, który wkrada się w większą skalę.

Podstawowa struktura naszych kontrolerów akcji są:

  1. Extract wiadomość XML z organizmu żądanie - Obejmuje to sprawdzanie przed skargę specyficzne RelaxNG schematu
  2. Przygotuj odpowiedzi XML
  3. walidacji danych w komunikacie żądania (nieprawidłowe dane zgłaszają wyjątek - wiadomość jest dodawana do odpowiedzi, która jest wysyłana natychmiast)
  4. Wykonaj akcję bazy danych (wybierz/wstaw/aktualizuj/usuń)
  5. Powrót sukces lub niepowodzenie akcji, z wymaganymi informacjami

Prostym przykładem jest to akcja, która zwraca listę dostawców w oparciu o elastyczną zestawu kryteriów:

class Api_VendorController extends Lib_Controller_Action 
{ 
    public function getDetailsAction() 
    { 
     try { 
      $request = new Lib_XML_Request('1.0'); 
      $request->load($this->getRequest()->getRawBody(), dirname(__FILE__) . '/../resources/xml/relaxng/vendor/getDetails.xml'); 
     } catch (Lib_XML_Request_Exception $e) { 
      // Log exception, if logger available 
      if ($log = $this->getLog()) { 
       $log->warn('API/Vendor/getDetails: Error validating incoming request message', $e); 
      } 

      // Elevate as general error 
      throw new Zend_Controller_Action_Exception($e->getMessage(), 400); 
     } 

     $response = new Lib_XML_Response('API/vendor/getDetails'); 

     try { 
      $criteria = array(); 
      $fields = $request->getElementsByTagName('field'); 
      for ($i = 0; $i < $fields->length; $i++) { 
       $name = trim($fields->item($i)->attributes->getNamedItem('name')->nodeValue); 
       if (!isset($criteria[$name])) { 
        $criteria[$name] = array(); 
       } 
       $criteria[$name][] = trim($fields->item($i)->childNodes->item(0)->nodeValue); 
      } 

      $vendors = $this->_mappers['vendor']->find($criteria); 
      if (count($vendors) < 1) { 
       throw new Api_VendorController_Exception('Could not find any vendors matching your criteria'); 
      } 

      $response->append('success'); 
      foreach ($vendors as $vendor) { 
       $v = $vendor->toArray(); 
       $response->append('vendor', $v); 
      } 

     } catch (Api_VendorController_Exception $e) { 
      // Send failure message 
      $error = $response->append('error'); 
      $response->appendChild($error, 'message', $e->getMessage()); 

      // Log exception, if logger available 
      if ($log = $this->getLog()) { 
       $log->warn('API/Account/GetDetails: ' . $e->getMessage(), $e); 
      } 
     } 

     echo $response->save(); 
    } 
} 

Więc - wiedząc, gdzie podobieństw są w moich kontrolerach, jaka jest najlepsza strategia refaktoryzacji przy zachowaniu jej Zend-like, a także testowalności w PHPUnit?

Zastanowiłem się nad abstrakcją większej logiki kontrolera w klasie nadrzędnej (Lib_Controller_Action), ale to sprawia, że ​​testowanie jednostek jest bardziej skomplikowane w sposób, który wydaje mi się błędny.

+2

Może popchnąć wspólność do klas usług/repozytoriów? Takie klasy mogłyby być testowalne, mogły być wykorzystywane przez kontrolery i mogły uczynić kod kontrolera bardziej zwartym. –

+3

Innym podejściem byłoby zebranie wspólności w pomocników akcji. –

Odpowiedz

1

Dwa pomysły (tylko tworzenie odpowiedź od komentarzach powyżej):

  1. push wspólności dół do eksploatacji/klas repozytorium? Takie klasy mogłyby być testowalne, mogły być wykorzystywane przez kontrolery i mogły uczynić kod kontrolera bardziej zwartym.

  2. Zbierz wspólność w pomocników działania.

+0

Czy masz linki do dowolnych przykładów? Gdyby obejmowały testowanie kodu, byłoby świetnie - Zend i TTD/automatyczne testy wciąż są dla mnie nowością. – HorusKol

+1

Cóż, oto część, w której rozpaczliwie macham rękoma, aby odciągnąć od braku szczegółów, wskazując na wyszukiwania w Google dla PHPUnit/TDD. Kilka konkretnych odniesień: (1) Uważam, że testy jednostkowe w ZF są dość pouczające. (2) Podoba mi się [kod źródłowy bloga Dasprids] (http://site.svn.dasprids.de/trunk/), gdzie można znaleźć przykłady wykorzystania usług do optymalizacji kodu kontrolera. (3) Repozytoria Doctrine2 są dobrymi przykładami przesyłania danych do klasy repozytoriów. (4) [Ten artykuł] (http://devzone.zend.com/1218/action-helpers-in-zend-framework/) z MWOP jest dobrym intro do działania pomocników. –

+0

Dziękuję za to - czas na lekturę – HorusKol

1

Skoro masz do tego kroku za każdym razem wniosek został wykonany, można zapisać, że otrzymują, analizowania i sprawdzania poprawności odebranego żądania w Zend_Controller_Plugin które trzeba wykonać co PreDispatch wszystkich kontrolerów.(Do-stanie tylko wtedy, gdy żądanie XML są standaryzowane) (Jeśli używasz XMLRPC, REST lub jakiś standardowy sposób budowania zapytań do Państwa dyspozycji, można czekamy te moduły wbudowane w ZF)

walidacji danych (specyficzne dla działania) może być wykonane za pomocą metody kontrolera (która następnie byłaby wywoływana przez działanie (akcje) jej wymagające) (jeśli parametry są specyficzne dla jednego lub wielu działań tego kontrolera) lub można to zrobić za pomocą wzorców Factory i Builder w przypadku, gdy masz dużo wspólnych params między kontrolerami/działań

// call to the factory 
$filteredRequest = My_Param_Factory::factory($controller, $action, $paramsArray) // call the right builder based on the action/controller combination 

// the actual Factory 

class My_Param_Factory 
{ 
    public static function factory($controller, $action, $params) 
    { 
     $builderClass = "My_Param_Builder_" . ucfirst($controller) . '_' . ucfirst($action); 

     $builder = new $builderClass($params); 

     return $builder->build(); 
    } 
} 

budowniczy będzie następnie zadzwonić Paramerty y walidacji zajęcia w oparciu o który potrzebuje specyficznych wypełniaczy (które mogłyby poprawić re-usability)

W kontrolerze, jeżeli co potrzebne params są ważne, przechodzą obróbkę we właściwym sposobie prawej modelu

$userModel->getUserInfo($id) // for example 

Który usunie wszystkie operacje przetwarzania danych z kontrolerów, które będą musiały jedynie sprawdzić, czy wejście jest w porządku, a następnie odpowiednio wysłać.

zapis wyników (błąd lub Succes) w zmiennej, które zostaną wysłane do widoku

przetwarzające dane (format i sterująca (wymienić < z & LT, jeżeli mają one być zawarte w odpowiedzi na przykład)), wyślij do pomocnika widoku, aby zbudować XML, a następnie wydrukuj (echo) dane w widoku (co będzie odpowiedzią dla twojego użytkownika).

public function getDetailsAction() 
{ 
    if ($areRequestParamsValid === true) { 
     // process data 
    } else { 
     // Build specific error message (or call action helper or controller method if error is general) 
    } 

    $this->view->data = $result 
}