6

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

  • 数组:构建数组以提供给 ZP 将涉及获取所有记录,这是我试图避免的
  • 迭代器:愚蠢的迭代器会出现与数组相同的问题,而聪明的迭代器会觉得它不适合模型
  • DbSelect:将 DbSelect 对象添加到 Controller 会使 Controller 与我的 DB 的内部工作方式联系起来很不自在(更不用说生成原始结果行而不是封装对象了)
  • DbTableSelect:与 DbSelect 相同
  • 空适配器:手动来回传递所有细节。

将分页器传递给模型感觉也违反了 MVC 分离。问题是我错误地构建了我的模型,是我在维护 MVC 分离方面过于教条,还是我错过了一种将所有移动部件粘在一起的干净、优雅的方式?

4

7 回答 7

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 );
    //...
}
于 2008-12-05T18:12:41.747 回答
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;
    }
}
于 2010-02-23T19:55:45.987 回答
1

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

            }

        }

    ?>
于 2013-12-30T05:06:28.810 回答
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;
于 2008-12-05T04:23:32.250 回答
0

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

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

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

于 2008-12-05T19:01:58.263 回答
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;?>

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

于 2008-12-06T22:14:14.443 回答
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 ...
}

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

于 2010-02-24T06:08:20.043 回答