Zend_Paginator размытие линий MVC - PullRequest
6 голосов
/ 05 декабря 2008

Я работаю над проектом Zend Framework (1.7) со структурой, слабо основанной на структуре приложения быстрого запуска - фронт-контроллер, контроллеры действий, представления и модели, которые используют Zend_Db_Table для доступа к базе данных. Одна из моих основных моделей опирается на несколько дорогих объединений, чтобы вытащить свой основной листинг, поэтому я изучаю использование Zend_Paginator для уменьшения количества строк, возвращаемых из базы данных. Моя проблема в том, что Zend_Paginator поставляется только с 4 адаптерами, но ни один из них мне не подходит.

  • Массив : Построение массива для подачи в ZP потребовало бы выборки всех записей, чего я стараюсь избегать
  • Итератор : тупой итератор будет представлять те же проблемы, что и массив, а умный - плохое соответствие для модели
  • DbSelect : получение объекта DbSelect до Контроллера может привести к неудобным связям Контроллера с внутренней работой моей БД (не говоря уже о создании необработанных строк результата, а не инкапсулированных объектов)
  • DbTableSelect : аналогично DbSelect
  • Нулевой адаптер : передать все детали назад и вперед вручную.

Передача paginator в модель кажется, что это также нарушило бы разделение MVC. Является ли проблема в том, что я построил свою модель неправильно, что я догадываюсь о поддержании разделения MVC или мне не хватает чистого, элегантного способа склеивания всех движущихся частей вместе?

Ответы [ 7 ]

2 голосов
/ 23 февраля 2010

Теперь для Zend_Paginator существует метод setFilter, который позволяет загружать данные из объекта строки в любой нужный объект модели:

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;
    }
}
2 голосов
/ 05 декабря 2008

Вы можете предоставить интерфейс на ваших моделях, который принимает параметры $current_page и $per_page и возвращает набор данных текущей страницы, а также объект-пагинатор.

Таким образом, весь ваш код нумерации страниц содержится в модели, и вы можете свободно использовать адаптеры Db, не чувствуя, что вы нарушили концепцию.

Кроме того, контроллер действительно не должен настраивать пейджер в любом случае, так как вы правы в том, что он привязан к данным (а модели предназначены для данных, а не только для соединений с базой данных).

class Model
{
    //...
    /**
     * @return array Array in the form: array( 'paginator' => obj, 'resultset' => obj )
     */
    public function getAll( $where = array(), $current_page = null, $per_page = null );
    //...
}
1 голос
/ 30 декабря 2013

I действительно требовалось решение, в котором я мог бы использовать метод класса Zend_Db_Table в качестве ресурса для моего адаптера paginator вместо массива или объекта Zend_Db_Select.

Этот вид расширенного моделирования не совместим со стандартными адаптерами для Zend_Paginator. Я пошел дальше и исправил это для всех, кто отчаянно нуждается в ответе, как и я.

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

    }

?>

Вот как это работает в вашем контроллере:

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

        }

    }

?>

Единственное требование для работы этого адаптера - перечислить следующие параметры в списке аргументов методов модели (в любом порядке [адаптер обнаружит сигнатуру метода через отражение):

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

Пагинатор вызовет ваш метод с соответствующими значениями для аргументов $ limit, $ offset и $ count. Вот и все!

Пример: * * один тысяча двадцать-одна

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

            }

        }

    ?>
0 голосов
/ 24 февраля 2010

Вы также можете напрямую реализовать Zend_Paginator_Adapter_Interface или расширить Zend_Paginator_Adapter_DbSelect в любой модели, которая должна поддерживать нумерацию страниц.

Таким образом, модель напрямую ничего не знает о View, Controller или даже Zend_Paginator, но может напрямую использоваться с Zend_Paginator везде, где это имеет смысл.

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

С помощью чего-то подобного вы можете напрямую создать экземпляр пейджера, используя экземпляр этого класса везде, где это имеет смысл:

$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 ...
}

Теперь вы можете использовать этот класс в качестве базового класса для любой "Модели", которая является набором.

0 голосов
/ 07 декабря 2008

Если вы используете адаптер DbSelect, вы можете просто передать набор результатов, и это будет иметь большое значение для поддержания некоторого разделения. Итак, в вашем контроллере:

$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);

В представлении вы можете получить доступ к данным через paginator

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

Это упрощенный пример (я также настраиваю стиль прокрутки по умолчанию и частичное представление по умолчанию в загрузчике), но я настраиваю его в контроллере неплохо, потому что данные, полученные из модели, помещаются в представление Контроллер в любом случае, и эта реализация использует набор результатов, а не модель.

0 голосов
/ 05 декабря 2008

При работе с MVC важно учитывать, что модель предназначена для всей доменной логики, а контроллер - для бизнес-логики. Общее практическое правило заключается в том, что модели не должны знать интерфейс (контроллеры или представления), но они не должны быть простыми средствами доступа к БД. Чтобы быть максимально переносимыми, им также не нужно ничего знать о свойствах форматирования или отображения (если это не является частью логики домена).

На самом деле вся логика, которая управляет доменной логикой, должна быть в модели, а не в контроллерах. Контроллер должен передавать информацию из интерфейса, преобразовывая ее по мере необходимости, в модель и выбирать, какой вид отображать / обновлять. Если он не имеет ничего общего с интерфейсом, он может быть лучше представлен в модели, чем в контроллере, так как это позволит увеличить повторное использование кода, если вы решите поменять пары контроллер / представление позже.

В идеале ваша модель должна обеспечивать интерфейс для доступа к необходимой информации. То, как это реализовано за этим интерфейсом, не является проблемой MVC, пока модель не знает о части VC MVC. Если это означает передачу объекта paginator, это не является прямым нарушением принципов MVC, хотя если paginator как-то связан с рендерингом (извините, я не знаю, Zend), возможно, лучше передать интерфейс в нем (в котором отсутствуют методы рендеринга), пусть модель манипулирует / заполняет его, а затем передает его обратно. Таким образом, у вас не будет генерируемого кода рендеринга из модели, и вы можете заменить реализацию paginator, если позже решили сделать свое приложение консольным приложением (или добавить некоторый интерфейс API).

0 голосов
/ 05 декабря 2008

Ну, я не могу дать вам ответ на ваши вопросы по поводу использования DbSelect, но я наткнулся на этот фрагмент кода (в комментариях в блоге ibuildings), касающийся проблемы сокращения числа извлекаемых строк. Может быть полезным для некоторых читателей.

$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;
...