2008-12-05 13 views
6

Pracuję nad projektem Zend Framework (1.7) ze strukturą luźno opartą na strukturze aplikacji szybkiego startu - kontroler frontowy, kontrolery akcji, widoki & modele, które korzystają z Zend_Db_Table, aby dostać się do bazy danych. Jeden z moich głównych modeli opiera się na drogich sprzężeniach, aby podciągnąć swój główny listing, więc szukam możliwości użycia Zend_Paginator, aby zmniejszyć liczbę wierszy przywiezionych z bazy danych. Mój problem polega na tym, że Zend_Paginator ma tylko 4 adaptery, z których żaden nie wydaje się być dla mnie odpowiedni.Zend_Paginator rozmycie linii MVC

  • Array: Budowanie tablicę karmić do ZP wiązałoby ściągam wszystkie rekordy, które jest to, co staram się unikać
  • Iterator: głupie iterator przedstawi te same problemy jako tablicę a sprytny czuje się, jakby był źle dopasowany do Modelu: Dosunięcie obiektu DbSelect do kontrolera spowodowałoby niewygodne powiązanie kontrolera z wewnętrznymi funkcjami mojego DB (nie wspominając o tworzeniu nieprzetworzonych wierszy wynikowych). zamiast zamkniętych obiektów)
  • DbTableSelect: jak DbSelect
  • Null Adapter: przekazać wszystkie szczegóły iz powrotem ręcznie.

Przekazywanie paginatora do modelu również sprawia wrażenie, jakby naruszało separację MVC. Czy problem polega na tym, że mój model został nieprawidłowo skonstruowany, że mam do czynienia z dogmatycznym utrzymaniem separacji MVC, czy też brakuje mi czystego, eleganckiego sposobu łączenia wszystkich ruchomych części?

Odpowiedz

2

może dostarczyć interfejs na modelach, które akceptuje $current_page i $per_page parametry i zwraca zestaw danych bieżącej strony, jak również jako obiekt paginator.

W ten sposób cały kod paginacji jest zawarty w modelu i możesz swobodnie korzystać z adapterów Db bez poczucia, że ​​złamałeś tę koncepcję.

Co więcej, kontroler naprawdę nie powinien konfigurować pagera w każdym razie, ponieważ masz rację, ponieważ jest powiązany z danymi (a modele są dla danych, a nie tylko połączeń z bazą danych).

class Model 
{ 
    //... 
    /** 
    * @return array Array in the form: array('paginator' => obj, 'resultset' => obj) 
    */ 
    public function getAll($where = array(), $current_page = null, $per_page = null); 
    //... 
} 
0

Cóż, nie mogę udzielić odpowiedzi na pytania dotyczące korzystania z DbSelect, ale natknąłem się na ten fragment kodu (w komentarzach na blogu ibuildings), odnosząc się do problemu zmniejszenia liczby wyciągniętych wierszy. Może być przydatny dla niektórych czytelników.

$select = $db->from('users')->order('name');  
$paginator = new Zend_Paginator(new Zend_Paginator_Adapter_DbSelect($select)); 
$paginator->setCurrentPageNumber($this->_getParam('page', 1)); 
$paginator->setItemCountPerPage(25); 
$this->view->paginator = $paginator; 
0

Ważnym czynnikiem do wzięcia pod uwagę przy pracy z MVC jest to, że model jest dla wszystkich logiki domeny, podczas gdy regulator jest dla logiki biznesowej. Ogólna zasada jest taka, że ​​model (y) nie powinny mieć żadnej wiedzy na temat interfejsu (kontrolerów lub widoków), ale nie muszą to być proste akcesoria DB. Aby być możliwie jak najbardziej przenośnym, nie powinni również wiedzieć nic o formatowaniu ani właściwościach wyświetlania (chyba że jest to część logiki domeny).

W rzeczywistości cała logika, która manipuluje logiką domeny, powinna znajdować się w modelu, a nie w kontrolerach. Kontroler powinien przekazywać informacje z interfejsu, przekształcając je w razie potrzeby do modelu i wybrać widok do wyświetlenia/aktualizacji.Jeśli nie ma to nic wspólnego z interfejsem, może być lepiej reprezentowane w modelu niż w kontrolerze, ponieważ pozwoliłoby na ponowne użycie kodu, jeśli zdecydujesz się na zamianę kontrolera/podglądu parowania później.

W idealnym przypadku model powinien zapewniać interfejs dostępu do potrzebnych informacji. Sposób implementacji tego interfejsu nie jest problemem MVC, o ile model pozostaje nieświadomy części VC MVC. Jeśli oznacza to omijanie obiektu paginatora, nie jest to bezpośrednie naruszenie zasad MVC, ale jeśli paginator ma cokolwiek wspólnego z renderowaniem samego siebie (przepraszam, nie znam Zend), może najlepiej przekazać interfejs (w którym brakuje metod renderowania), niech model manipuluje/wypełnia go, a następnie przekazuje go z powrotem. W ten sposób nie generujesz kodu renderującego z modelu i możesz zastąpić implementację paginatora, jeśli zdecydujesz się na późniejszą aplikację na konsolę (lub dodasz jakiś interfejs API).

0

Jeśli używasz adaptera DbSelect, możesz po prostu przekazać zestaw wyników, co znacznie utrudnia zachowanie separacji. Więc w kontrolerze:

$items = new Items();//setup model as usual in controller 
$this->view->paginator = Zend_Paginator::factory($items->getAll()); //initialize the pagination in the view NB getAll is just a custom function to encapsulate my query in the model that returns a Zend_Db_Table_Rowset 
$this->view->paginator->setCurrentPageNumber($page); //$page is just the page number that could be passed in as a param in the request 
$this->view->paginator->setView($this->view); 

W widoku można uzyskać dostęp do danych za pośrednictwem paginator

<?php foreach($this->paginator as $item):?> 
<?=$item->someProperty?> 
<?php endforeach;?> 

To jest uproszczony przykład (ja również konfiguracja się domyślny styl przewijanie i domyślny widok częściowy w bootstrap), ale ustawienie go w kontrolerze nie jest złe, ponieważ dane pobierane z modelu są umieszczane w widoku przez kontroler, a ta implementacja korzysta z zestawu wyników NIE modelu.

+1

Ale nie to wydawać marnotrawstwem? Wywołanie getAll() - kosztowne połączenie, które może zwracać tysiące lub miliony rekordów - a następnie zignorować wszystkie z wyjątkiem 20, które mogą być potrzebne dla jednej strony wyświetlanych danych? –

2

Istnieje obecnie metoda setFilter dla Zend_Paginator który pozwala na załadowanie danych z obiektu wiersza do każdego modelu obiektu, który chcesz:

class Model_UserDataMapper { 
    public function getUsers($select, $page) { 
     $pager = Zend_Paginator::factory($select); 
     $pager->setItemCountPerPage(10) 
        >setCurrentPageNumber($page) 
        ->setFilter(new Zend_Filter_Callback(array($this,'getUserObjects'))); 
    } 

    public function getUserObjects($rows) { 
     $users = array(); 

     foreach($rows as $row) { 
      $user = new Model_User($row->toArray()); 

      $users[] = $user; 
     } 

     return $users; 
    } 
} 
0

Można również wdrożyć Zend_Paginator_Adapter_Interface bezpośrednio lub przedłużenia Zend_Paginator_Adapter_DbSelect w każdym modelu to musi obsługiwać paginację.

W ten sposób model nie ma bezpośredniego pojęcia o widoku, kontrolerze czy nawet Zend_Paginator, ale może być bezpośrednio użyty z Zend_Paginatorem, gdzie tylko ma to największy sens.

class ModelSet extends Zend_Paginator_Adapter_DbSelect 
{ 
    public function __construct(...) 
    { 
     // Create a new Zend_Db_Select ($select) representing the desired 
     // data set using incoming criteria 
     parent::__construct($select); 
    } 
    ... 
} 

Z czymś takim można bezpośrednio instancję pager używając instancję tej klasy gdziekolwiek to największy sens:

$modelSet = new ModelSet(...); 
... 
$pager = new Zend_Paginator($modelSet); 
$pager->setItemCountPerPage(...); 
$pager->setCurrentPageNumber(...); 
... 
// The first time the record set is actually retrieved will be at the beginning 
// of the first traversal 
foreach ($pager as $record) 
{ 
    // ... do stuff with the record ... 
} 

Teraz można użyć tej klasy jako klasy bazowej dla dowolny "Model", który jest zbiorem.

1

I naprawdę Potrzebowaliśmy rozwiązania, w których mogę użyć Zend_Db_Table metody klasy jako źródło mojego adaptera paginator zamiast tablicy lub obiektu Zend_Db_Select.

Ten rodzaj zaawansowanego modelowania nie jest zgodny ze standardowymi adapterami dla Zend_Paginator. Poszedłem naprzód i naprawiłem to dla każdego, kto desperacko szuka odpowiedzi, tak jak ja.

<?php 

    /* /zend/Paginator/Adapter/DbTableMethod.php */  
    class Zend_Paginator_Adapter_DbTableMethod implements Zend_Paginator_Adapter_Interface { 

     protected $_class; 
     protected $_method; 
     protected $_parameters; 
     protected $_rowCount = null; 

     public function __construct($class, $method, array $parameters = array()){ 

     $reflectionClass = new ReflectionClass($class); 
     $reflectionMethod = $reflectionClass->getMethod($method); 
     $reflectionParameters = $reflectionMethod->getParameters(); 

     $_parameters = array(); 

     foreach ($reflectionParameters as $reflectionParameter){ 

      $_parameters[$reflectionParameter->name] = ($reflectionParameter->isDefaultValueAvailable()) ? $reflectionParameter->getDefaultValue() : null; 

     }  

     foreach ($parameters as $parameterName => $parameterValue){ 

      if (array_key_exists($parameterName, $_parameters)) $_parameters[$parameterName] = $parameterValue; 

     } 

     $this->_class = $class; 
     $this->_method = $method; 
     $this->_parameters = $_parameters; 

     } 

     public function count(){ 

      if (is_null($this->_rowCount)){ 

       $parameters = $this->_parameters; 
       $parameters['count'] = true; 

       $this->_rowCount = call_user_func_array(array($this->_class, $this->_method), $parameters); 

      }  

      return $this->_rowCount; 

     } 

     public function getItems($offset, $itemCountPerPage){ 

      $parameters = $this->_parameters; 
      $parameters['limit'] = $itemCountPerPage; 
      $parameters['offset'] = $offset; 

      $items = call_user_func_array(array($this->_class, $this->_method), $parameters); 

      return $items; 
     } 

    } 

?> 

Jak to działa w kontrolerze:

<?php 

    class StoreController extends Zend_Controller_Action { 

     public function storeCustomersAction(){ 

      $model = new Default_Model_Store(); 
      $method = 'getStoreCustomers'; 
      $parameters = array('storeId' => 1); 

      $paginator = new Zend_Paginator(new Site_Paginator_Adapter_DbTableMethod($model, $method, $parameters)); 
      $paginator->setCurrentPageNumber($this->_request->getParam('page', 1)); 
      $paginator->setItemCountPerPage(20); 

      $this->view->paginator = $paginator; 

     } 

    } 

?> 

Jedyne wymagania dla tego adaptera do pracy jest notować następujące parametry w liście metody argumenty modelu (w dowolnej kolejności [adaptera wykryje podpis metody poprzez odbicie):

$ limit = 0, $ offset = 0, $ count = false

Paginator wywoła metodę z odpowiednimi wartościami dla argumentów $ limit, $ offset i $ count. To jest to!

Przykład:

 <?php 

     class Default_Model_Store extends Zend_Db_Table { 

     public function getStoreCustomers($storeId, $includeCustomerOrders = false, $limit = 0, $offset = 0, $count = false){ 

if ($count) /* return SELECT COUNT(*) ... */ 

       /* ... run primary query, get result */ 
       $select = $this->_db->select(...)->limit($limit, $offset); 


       $rows = $this->_db->fetchAll($select); 

       if ($includeCustomerOrders){ 

        foreach ($rows as &$row){ 

         $customerId = $row['id']; 
         $row['orders'] = $this->getCustomerOrders($customerId); 

        } 

       } 

       return $rows;  

      } 

     } 

    ?>