Pagination in a Rich Domain Model
I use rich domain model in my app. The basic ideas were taken there. For example I have User and Comment entities. They are defined as following:
<?php
class Model_User extends Model_Abstract {
public function getComments() {
/**
* @var Model_Mapper_Db_Comment
*/
$mapper = $this->getMapper();
$commentsBlob = $mapper->getUserComments($this->getId());
return new Model_Collection_Comments($commentsBlob);
}
}
class Model_Mapper_Db_Comment extends Model_Mapper_Db_Abstract {
const TABLE_NAME = 'comments';
protected $_mapperTableName = self::TABLE_NAME;
public function getUserComments($user_id) {
$commentsBlob = $this->_getTable()->fetchAllByUserId((int)$user_id);
return $commentsBlob->toArray();
}
}
class Model_Comment extends Model_Abstract {
}
?>
Mapper's getUserComments function simply returns something like:
return $this->getTable->fetchAllByUserId($user_id)
which is array. fetchAllByUserId accepts $count and $offset params, but I don't know to pass them from my Controller to this function through model without rewriti开发者_运维技巧ng all the model code.
So the question is how can I organize pagination through model data (getComments). Is there a "beatiful" method to get comments from 5 to 10, not all, as getComments returns by default.
If you only care about paginating the results the user sees, and aren't concerned about improving your performance, you can probably avoid building pagination into your model infrastructure.
Assuming you've got some kind of Model_Collection_Abstract class which all your collection classes descend from, you could likely hack pagination into that.
So then you'd have code that looks something like:
<?PHP
//$comments is a subclass of Model_Collection_Abstract, which implements the paging stuff
$comments = $user->getComments();
$comments->setStart(10);
$comments->setPageLength(10);
$numPages = $comments->numPages(); //can be derived from the pagelength and the collection's internal record store.
$currentPage = $comments->currentPage(); //can be derived from start and page length
foreach($comments as $comment){
//this code runs up to ten times, starting at the tenth element in the collection.
}
The downside here is that you're always grabbing all the comments, even if you only want to see ten of them. But this might be an acceptable solution for you.
If you only want N records pulled from the database (for N=number to display), then of course you'll need to implement some way to pass start/limit, or equivalent, parameters, all the way down through your model.
EDIT: The other answer about looking at Zend_Paginator is worth reading too. If your collection class implements Iterator
, it's quite possible that you can plug it into Zend_Paginator and save some serious headaches. I've not done this (yet), but it's worth having a look!
Zend_Paginator
may be the simple solution you are looking for. It can take any array()
or instance of an Iterator
(which a Zend_Db_Table_Rowset
is)
$paginator = Zend_Paginator::factory($model->getComments());
$paginator->setItemCountPerPage(5);
$paginator->setCurrentPageNumber($this->getRequest()->getParam('page',1));
$this->view->comments = $paginator;
In the view:
<?php foreach($this->comments as $comment): ?>
Render your HTML for the comment
<?php endforeach; ?>
<?php echo $this->paginationControl($this->comments, 'Sliding', '_pagination.phtml'); ?>
And a (very) simple paginationControl()
partial (taken from this blog post):
<?php if ($this->pageCount): ?>
<div class="paginationControl">
<?php if (isset($this->previous)): ?>
<a href="<?= $this->url(array(’page’ => $this->previous)); ?>">< Previous</a> |
<?php else: ?>
<span class="disabled">< Previous</span> |
<?php endif; ?>
<?php foreach ($this->pagesInRange as $page): ?>
<?php if ($page != $this->current): ?>
<a href="<?= $this->url(array(’page’ => $page)); ?>"><?= $page; ?></a> |
<?php else: ?>
<?= $page; ?> |
<?php endif; ?>
<?php endforeach; ?>
<?php if (isset($this->next)): ?>
<a href="<?= $this->url(array(’page’ => $this->next)); ?>">Next ></a>
<?php else: ?>
<span class="disabled">Next ></span>
<?php endif; ?>
</div>
<?php endif; ?>
More examples of Zend_Paginator are available via a google search.
Thinking about this too right now. Have you found a neat solution yet? My thoughts so far are 1) avoid using anything like Zend_Paginator and just do everything on your own. i.e you get params from the environment (request object or some config file etc) like itemCountPerPage, currentPageNumbe, you pass those into your service layer method (or in your case it's just mapper) like
$comments = $this->getCommentsMapper()->getPage($itemCountPerPage, $currentPage);
then you request total item amount from your mapper (it's fully up to you if you should do this in a separate request or not) like
$totalComments = $this->getCommentsMapper()->getTotal();
So now you have all data to make a "pagination control view". Speaking "Zendy" you pass all these variables to the view and draw the pagination control by yourself.
2) Develop an adapter for Zend_Paginator that would LIE about its state. I.e. instead of having all items it will just have some part of them that you need, but you will manually set "total count" value and others. I don't really like this approach myself.
3) Add some special domain object called "Page" that would be returned from mappers or service layers. Such object would encapsulate all those variables we need to build a pagination control presentation. With this we would be able to do the following:
` $commentsService = $this->getCommentsService(); $commentsService->setItemCountPerPage(10); // let say 10 is taken from some config.ini
$commentsPage = $this->getCommentsService()->getPage(12); $this->view->commentsPage = $commentsPage;
`
Somewhere in the view we can get these ` $commentsPage->getItems(); // collection of comments
$commentsPage->getCurrentPageNumber();
$commentsPage->getTotalItemsCount();
`
which is enough to build a pagination control.
This it all.
I'm currently choosing between first and third approaches, probably will go with third.
Here is my solution:
class Model_Mapper_Db_Comment extends Model_Mapper_Db_Abstract {
public function getUserCommentsPaginator($user_id) {
$select = $this->_getTable()->select()->where('user_id = ?', (int)$user_id);
$paginator = Zend_Paginator::factory($select, 'DbSelect');
return $paginator;
}
}
class Model_User extends Model_Abstract implements Zend_Auth_Adapter_Interface {
public function getCommentsPaginator() {
$paginator = $this->getMapper(null, 'Comment')->getUserCommentsPaginator($this->id);
$paginator->setFilter(new App_Filter_Array_Collection('Model_Collection_Comments'));
return $paginator;
}
}
Model requests Zend_Paginate object with prepared query based on user_id provided from Model. Then Model added filter to Zend_Paginate object to make it compatible with other Model methods (return Model_Collection classes, not arrays). All other pagination parameters are set in controller (page, the number of items per page, etc.).
Thet's how I had separated storage, business and control logic in my application.
精彩评论