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.
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?
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 );
//...
}
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;
}
}
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;
}
}
?>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With