我正在开发一个Zend Framework(1.7)项目,其结构基于快速启动应用程序的结构 - 前端控制器,动作控制器,视图&使用Zend_Db_Table访问数据库的模型。我的一个主要模型依赖于一些昂贵的连接来提升其主要列表,所以我正在研究使用Zend_Paginator来减少从数据库带回的行数。我的问题是Zend_Paginator只配备了4个适配器,但这些适配器似乎都不适合我。
将分页器传递到模型中感觉就像它也会违反MVC分离。问题是我错误地构建了我的模型,我是在保持MVC分离的教条,还是我错过了将所有活动部件粘在一起的干净,优雅的方法?
答案 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 ...
}
现在,您可以将此类用作任何“模型”的基类。