php model-view-controller zend-framework zend-paginator

php - Zend_Paginator borrando líneas MVC



model-view-controller zend-framework (7)

Bueno, no puedo darle una respuesta a sus inquietudes sobre el uso de DbSelect, pero encontré este fragmento de código (en los comentarios del blog de ibuildings) relacionado con la cuestión de reducir el número de filas extraídas. Puede ser útil para algunos lectores.

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

Estoy trabajando en un proyecto de Zend Framework (1.7) con una estructura basada en la estructura de la aplicación de inicio rápido: controlador frontal, controladores de acción, vistas y modelos que usan Zend_Db_Table para acceder a la base de datos. Uno de mis modelos principales depende de algunas uniones costosas para obtener su listado principal, así que estoy buscando usar Zend_Paginator para reducir el número de filas traídas de la base de datos. Mi problema es que Zend_Paginator solo viene con 4 adaptadores, ninguno de los cuales realmente parece ser adecuado para mí.

  • Array : Crear la matriz para alimentar a ZP implicaría obtener todos los registros, que es lo que intento evitar
  • Iterador : Un iterador tonto presentaría los mismos problemas que una matriz y uno inteligente se sentiría como un ajuste pobre para el Modelo.
  • DbSelect : Conseguir un objeto DbSelect hasta el controlador incómodamente uniría el controlador al funcionamiento interno de mi DB (sin mencionar la producción de filas de resultados sin procesar en lugar de objetos encapsulados)
  • DbTableSelect : igual que DbSelect
  • Adaptador nulo : pasa todos los detalles hacia adelante y hacia atrás manualmente.

Pasar el paginador al modelo parece que también violaría la separación de MVC. ¿El problema es que he construido incorrectamente mi modelo, que estoy siendo dogmático sobre el mantenimiento de la separación de MVC o me falta una forma limpia y elegante de unir todas las partes móviles?


Una consideración importante a tener en cuenta al trabajar con MVC es que el modelo es para toda la lógica de dominio, mientras que el controlador es para la lógica comercial. Una regla general es que los modelos no deberían tener conocimiento de la interfaz (controladores o vistas), pero no necesitan ser simples accesadores de DB. Para ser tan portátil como sea posible, tampoco deberían saber nada sobre el formato o las propiedades de visualización (a menos que sea parte de la lógica del dominio).

De hecho, toda la lógica que manipula la lógica del dominio debe estar en el modelo y no en los controladores. El controlador debe pasar información de la interfaz, transformándola según sea necesario, al modelo y seleccionar qué vista mostrar / actualizar. Si no tiene nada que ver con la interfaz, podría representarse mejor en un modelo que en un controlador, ya que permitiría una mayor reutilización del código si decide cambiar el emparejamiento entre el controlador y la vista más tarde.

Idealmente, su modelo debería proporcionar una interfaz para acceder a la información que necesita. Cómo se implementa eso detrás de esa interfaz no es una preocupación de MVC, siempre y cuando el modelo no tenga conocimiento de la parte de VC de MVC. Si eso significa pasar alrededor de un objeto de paginador, eso no es una violación directa de los principios de MVC, aunque si el paginador tiene algo que ver con la renderización (lo siento, no conozco a Zend), podría ser mejor pasar una interfaz de ello (que le faltan los métodos de renderizado), haga que el modelo lo manipule / complete, y luego vuelva a pasarlo. De esta forma, no se generará un código de representación desde el modelo, y podría reemplazar la implementación del paginador si decidiera convertir su aplicación en una aplicación de consola más tarde (o agregue una interfaz de API de algún tipo).


Puede proporcionar una interfaz en sus modelos que acepte los parámetros $current_page y $per_page y devuelva el conjunto de datos de la página actual, así como un objeto del paginador.

De esta forma, todo su código de paginación estará dentro del modelo y podrá usar los adaptadores Db sin sentir que ha roto el concepto.

Además, el controlador realmente no debería configurar el buscapersonas de todos modos, ya que tiene razón en que está vinculado a los datos (y los modelos son para datos, no solo para conexiones de bases de datos).

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


Si usa el adaptador DbSelect, simplemente puede pasar el resultado y esto ayuda a mantener la separación. Entonces en tu controlador:

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

En la vista puede acceder a los datos a través del paginador

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

Este es un ejemplo simplificado (también configuré el estilo de desplazamiento predeterminado y el parcial de vista predeterminado en el programa de arranque), pero creo que configurarlo en el controlador no está nada mal porque el Controlador coloca los datos recuperados del modelo en la vista. de todos modos y esta implementación hace uso del conjunto de resultados NO del modelo.


Ahora hay un método setFilter para Zend_Paginator que le permite cargar los datos del objeto fila a cualquier objeto modelo que desee:

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


También puede implementar Zend_Paginator_Adapter_Interface directamente o extender Zend_Paginator_Adapter_DbSelect en cualquier modelo que necesite soportar paginación.

De esta forma, el modelo no sabe nada directamente sobre View, Controller o incluso Zend_Paginator, pero puede usarse directamente con Zend_Paginator donde sea que tenga más sentido.

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

Con algo como esto, puede instanciar directamente un buscapersonas usando una instancia de esta clase donde sea que tenga más sentido:

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

Ahora, puede usar esta clase como la clase base para cualquier "Modelo" que sea un conjunto.


Realmente necesitaba una solución en la que pudiera usar un método de clase Zend_Db_Table como recurso para mi adaptador de paginador, en lugar de un conjunto o un objeto Zend_Db_Select.

Este tipo de modelado avanzado no es compatible con los adaptadores estándar para Zend_Paginator. Seguí adelante y arreglé esto para todos los que están desesperados por una respuesta, como yo.

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

Así es como funciona en su controlador:

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

Los únicos requisitos para que este adaptador funcione es enumerar los siguientes parámetros en la lista de argumentos de los métodos del modelo (en cualquier orden [el adaptador detectará la firma del método a través de la reflexión):

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

El paginador llamará a su método con los valores apropiados para los argumentos $ limit, $ offset y $ count. ¡Eso es!

Ejemplo:

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