Zend_Paginator 模糊 MVC 线条

发布于 2024-07-10 00:16:28 字数 672 浏览 9 评论 0原文

我正在开发一个 Zend Framework (1.7) 项目,其结构松散地基于快速启动应用程序的结构 - 前端控制器、操作控制器、视图和视图。 使用 Zend_Db_Table 访问数据库的模型。 我的一个主要模型依赖于一些昂贵的联接来拉出其主要列表,因此我正在考虑使用 Zend_Paginator 来减少从数据库带回的行数。 我的问题是 Zend_Paginator 仅附带 4 个适配器,其中没有一个真正适合我。

  • Array:构建数组以提供给 ZP 将涉及获取所有记录,这是我试图避免的
  • Iterator:一个愚蠢的迭代器会带来与数组和聪明的人感觉它不太适合模型
  • DbSelect :将 DbSelect 对象获取到控制器会将控制器与我的数据库的内部工作联系起来(更不用说生成原始结果行而不是封装的对象)
  • DbTableSelect :与 DbSelect 相同
  • Null Adapter :手动来回传递所有详细信息。

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

I'm working on a Zend Framework (1.7) project with a structure loosely based on the structure of the quickstart application - front controller, action controllers, views & models that use Zend_Db_Table to get to the database. One of my main models relies on some expensive joins to pull up its primary listing, so I'm looking into using Zend_Paginator to reduce the number of rows brought back from the database. My problem is that Zend_Paginator only comes with 4 adaptors, none of which really seem to be a good fit for me.

  • Array : Building the array to feed to ZP would involve fetching all the records which is what I'm trying to avoid
  • Iterator : A dumb iterator would present the same problems as an array and a smart one feels like it would be a poor fit for the Model
  • DbSelect : Getting a DbSelect object up to the Controller would uncomfortably tie the Controller to the inner workings of my DB (not to mention producing raw result rows rather than encapsulated objects)
  • DbTableSelect : same as DbSelect
  • Null Adapter : pass all the details back and forth manually.

Passing the paginator into the model feels like it, too, would violate the MVC separation. Is the problem that I've built my model incorrectly, that I'm being to dogmatic about maintaining MVC separation or am I missing a clean, elegant way of sticking all the moving parts together?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

肥爪爪 2024-07-17 00:16:28

您可以在模型上提供一个接口,该接口接受 $current_page$per_page 参数并返回当前页面的数据集以及分页器对象。

这样,您的所有分页代码都包含在模型中,并且您可以自由使用数据库适配器,而不会觉得自己破坏了概念。

另外,控制器实际上不应该设置寻呼机,因为您将其与数据绑定在一起是正确的(并且模型用于数据,而不仅仅是数据库连接)。

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

You can provide an interface on your models that accepts $current_page and $per_page parameters and returns the current page's data set as well as a paginator object.

This way all your pagination code is contained within the model and you are free to use the Db adapters without feeling like you've broken the concept.

Plus, the controller really shouldn't be setting up the pager anyways since you are correct in it being tied to the data (and models are for data, not just database connections).

class Model
{
    //...
    /**
     * @return array Array in the form: array( 'paginator' => obj, 'resultset' => obj )
     */
    public function getAll( $where = array(), $current_page = null, $per_page = null );
    //...
}
拍不死你 2024-07-17 00:16:28

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

There is now a setFilter method for Zend_Paginator that allows you to load the data from the row object to any model object you want:

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;
    }
}
忘东忘西忘不掉你 2024-07-17 00:16:28

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

            }

        }

    ?>

I really needed a solution in which I could use a Zend_Db_Table class method as the resource for my paginator adapter, instead of an array or a Zend_Db_Select object.

This sort of advanced modeling is not compatible with the standard adapters for Zend_Paginator. I went ahead and fixed this for everyone who is desperate for an answer, as I was.

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

    }

?>

This is how it works in your controller:

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

        }

    }

?>

The only requirements for this adapter to work is to list the following parameters in your model methods arguments list (in any order [the adapter will detect the method signature through reflection):

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

The paginator will call your method with the appropriate values for the $limit, $offset, and $count arguments. That's it!

Example:

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

            }

        }

    ?>
烟沫凡尘 2024-07-17 00:16:28

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

Well, I can't give you an answer to your concerns with using DbSelect but I did come across this bit of code (in the comments of the ibuildings blog) relating to the issue of reducing the number of rows pulled. Might be useful to some readers.

$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;
浮萍、无处依 2024-07-17 00:16:28

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

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

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

An important consideration to take into account when working with MVC is that the model is for all domain logic, whereas the controller is for the business logic. A general rule of thumb is that the model(s) should have no knowledge of the interface (controllers or views), but they need not be simple DB accessors. To be as portable as possible, they should know nothing of formatting or display properties either (unless that is part of the domain logic).

In fact, all logic that manipulates domain logic should be in the model and not in the controllers. The controller should pass information from the interface, transforming it as needed, to the model, and select which view to display/update. If it doesn't have anything to do with the interface, it might better be represented in a model than in a controller, as it would allow for more code reuse if you decide to swap out the controller/view pairings later.

Ideally, your model should provide an interface for accessing the information you need. How that is implemented behind that interface is not a concern of MVC, so long as the model remains unaware of the VC portion of MVC. If that means passing around a paginator object, that is not a direct violation of the MVC principles, though if the paginator has anything to do with rendering itself (sorry, I don't know Zend), it might be best to pass an interface of it in (that is missing the rendering methods), have the model manipulate/fill it in, then pass it back out. That way you don't have rendering code being generated from the model, and you could replace the paginator implementation if you decided to make your application a console app later (or add an API interface of some sort).

那伤。 2024-07-17 00:16:28

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

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

If you use the DbSelect adapter you can simply pass in the resultset and this goes a long way in maintaining some separation. So in your controller:

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

In the view you can access the data through the paginator

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

This is a simplified example (I also setup up default scrolling style and default view partial in the bootstrap), but I thnk setting it up in the controller isn't bad because the data retrieved from the model is placed into the view by the Controller anyway and this implementation makes use of the resultset NOT the model.

酷炫老祖宗 2024-07-17 00:16:28

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

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

You could also implement the Zend_Paginator_Adapter_Interface directly or extend Zend_Paginator_Adapter_DbSelect in any model that needs to support pagination.

That way, the model doesn't directly know anything about View, Controller or even the Zend_Paginator, but can be directly used with the Zend_Paginator wherever it makes the most sense.

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

With something like this, you can directly instantiate a pager using an instance of this class wherever it makes the most sense:

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

Now, you can use this class as the base class for any "Model" that is a set.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文