Zend_Paginator模糊MVC行

时间:2008-12-05 02:55:58

标签: php model-view-controller zend-framework zend-paginator

我正在开发一个Zend Framework(1.7)项目,其结构基于快速启动应用程序的结构 - 前端控制器,动作控制器,视图&使用Zend_Db_Table访问数据库的模型。我的一个主要模型依赖于一些昂贵的连接来提升其主要列表,所以我正在研究使用Zend_Paginator来减少从数据库带回的行数。我的问题是Zend_Paginator只配备了4个适配器,但这些适配器似乎都不适合我。

  • 数组:构建数组以提供给ZP将涉及获取我正在努力避免的所有记录
  • 迭代器:一个愚蠢的迭代器会出现与数组相同的问题,而一个聪明的迭代器会觉得它不适合模型
  • DbSelect :获取DbSelect对象直到控制器会让控制器与我的数据库的内部工作方式紧密相关(更不用说生成原始结果行而不是封装对象)。
  • DbTableSelect :与DbSelect相同
  • 空适配器:手动来回传递所有详细信息。

将分页器传递到模型中感觉就像它也会违反MVC分离。问题是我错误地构建了我的模型,我是在保持MVC分离的教条,还是我错过了将所有活动部件粘在一起的干净,优雅的方法?

7 个答案:

答案 0 :(得分:2)

您可以在模型上提供接受$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 :(得分:2)

现在有一个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 :(得分:1)

真的 需要一个解决方案,我可以使用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

paginator将使用$ 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;    

            }

        }

    ?>

答案 3 :(得分:0)

好吧,我无法回答您对使用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;

答案 4 :(得分:0)

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

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

理想情况下,您的模型应提供用于访问所需信息的界面。如果该模型仍然不知道MVC的VC部分,那么在该接口后面实现的方式不是MVC的关注点。如果这意味着传递一个paginator对象,这不是直接违反MVC原则,但如果paginator与渲染本身有任何关系(对不起,我不知道Zend),最好通过一个接口它(缺少渲染方法),让模型操纵/填充,然后将其传回。这样您就不会从模型中生成渲染代码,如果您决定稍后将应用程序设置为控制台应用程序(或添加某种类型的API接口),则可以替换分页器实现。

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

您也可以直接实现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 ...
}

现在,您可以将此类用作任何“模型”的基类。