代码之家  ›  专栏  ›  技术社区  ›  Sean McSomething

Zend_Paginator模糊MVC线

  •  6
  • Sean McSomething  · 技术社区  · 16 年前

    我正在开发一个Zend框架(1.7)项目,该项目的结构松散地基于QuickStart应用程序的结构—前端控制器、操作控制器、视图和模型,它们使用Zend_DB_表访问数据库。我的一个主要模型依赖于一些昂贵的连接来调出它的主列表,因此我正在研究使用Zend_Paginator来减少从数据库返回的行数。我的问题是Zend_Paginator只配备了4个适配器,这些适配器都不适合我。

    • 数组 :构建要馈送给ZP的数组需要获取所有记录,这正是我试图避免的。
    • 迭代器 :一个哑迭代器将呈现与一个数组相同的问题,而一个智能迭代器似乎不适合该模型。
    • 二进制选择 :将一个dbselect对象放到控制器上会使控制器与我的数据库的内部工作联系在一起(更不用说生成原始结果行,而不是封装对象)。
    • 数据库表选择 :与dbselect相同
    • 空适配器 :手动来回传递所有详细信息。

    将分页器传递到模型中也会违反MVC分离。问题是我的模型构建不正确,是我对保持MVC分离的武断,还是我缺少一种干净、优雅的方式来将所有的运动部件粘在一起?

    7 回复  |  直到 16 年前
        1
  •  2
  •   dcousineau    16 年前

    您可以在模型上提供接受 $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 );
        //...
    }
    
        2
  •  2
  •   Troy    14 年前

    Zend_Paginator现在有一个setfilter方法,允许您将数据从Row对象加载到所需的任何模型对象:

    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;
        }
    }
    
        3
  •  1
  •   axiom82 Joeytje50    10 年前

    真的? 需要一个解决方案,在该解决方案中,我可以使用zend_-db_-table类方法作为分页器适配器的资源,而不是数组或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;    
    
                }
    
            }
    
        ?>
    
        4
  •  0
  •   rg88    16 年前

    嗯,我不能回答您使用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;
    
        5
  •  0
  •   Sydius    16 年前

    使用MVC时需要考虑的一个重要因素是,模型适用于所有域逻辑,而控制器适用于业务逻辑。一般的经验法则是模型不应该知道接口(控制器或视图),但它们不需要是简单的db访问器。为了尽可能地可移植,他们也不应该知道格式化或显示属性(除非这是域逻辑的一部分)。

    事实上,所有操作域逻辑的逻辑都应该在模型中,而不是在控制器中。控制器应该从接口传递信息,根据需要将其转换为模型,并选择要显示/更新的视图。如果它与接口没有任何关系,那么它最好在模型中表示,而不是在控制器中表示,因为如果您决定稍后交换控制器/视图对,它将允许更多的代码重用。

    理想情况下,您的模型应该提供一个访问所需信息的接口。只要模型不知道MVC的VC部分,那么在该接口后面如何实现这一点就不是MVC关心的问题。如果这意味着传递一个paginator对象,这不是对MVC原则的直接违反,尽管如果paginator与渲染本身有任何关系(抱歉,我不知道Zend),最好是传入它的一个接口(即缺少渲染方法),让模型操纵/填充它,然后再将其传递出去。这样,您就没有从模型生成呈现代码,如果您决定稍后将应用程序作为控制台应用程序(或添加某种类型的API接口),则可以替换分页器实现。

        6
  •  0
  •   Akeem    16 年前

    如果使用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);
    

    在视图中,您可以通过分页器访问数据。

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

    这是一个简化的例子(我也在引导程序中设置了默认的滚动样式和部分默认视图),但是我认为在控制器中设置它并不坏,因为从模型中检索到的数据无论如何都由控制器放置到视图中,并且这个实现使用的是结果集而不是模型。

        7
  •  0
  •   Elmo    14 年前

    您还可以直接实现Zend_Paginator_适配器_接口,或者在任何需要支持分页的模型中扩展Zend_Paginator_适配器_DBselect。

    这样,模型就不会直接了解任何关于视图、控制器甚至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 ...
    }
    

    现在,您可以使用这个类作为集合中任何“模型”的基类。