Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
Pages: 1
Bonjour tout le monde,
J'ai mis en place Paginator avec succès dans certaines parties de mon application, mais il y a un problème avec une table qui contient +- 70.000 enregistrements. J'obtiens soit une erreur Allow memory... ou une exécution de script > à 30 secondes.
Voici le code de mon controller :
// Liste des mails $mails = new Application_Model_DbTable_Mails(); // Pagination $paginator = Zend_Paginator::factory($mails->fetchAll()); $paginator->setItemCountPerPage(50); $paginator->setCurrentPageNumber($this->getRequest()->getParam('page')); $this->view->mails = $paginator;
Je redéfinis ma méthode fetchAll dans ma DbTable :
public function fetchAll() { $rows = parent::fetchAll( $this->select() ->setIntegrityCheck(false) ->from(array('m' => 'mails')) ->join(array('r' => 'recipients'), 'm.mail_id = r.mail_id') ->where("r.recipient_type = 'from'") ); return $rows; }
Et dans ma vue :
echo $this->paginationControl($this->mails, 'Sliding', 'paginator.phtml');
J'ai essayé de modifier ma requête afin qu'elle prenne en compte les LIMIT et les offsets, les résultats sont corrects mais étant donné qu'elle ne retourne donc pas tous les enregistrements, je perd la pagination.
Y a-t-il moyen de paginer un très grand nombre de données avec le Zend Framework ?
Merci d'avance !
Dernière modification par Sylv1 (18-12-2010 16:06:08)
Hors ligne
Salut,
D'après ton code tu pagines tes données après les avoir toutes remontées.
Il faut passer un select à Zend_Paginator et non pas les données, il se chargera lui même de compléter et d'exécuter la requête comme il se doit.
Tu dois ajouter une méthode spécifique dans ta collection (table).
Par exemple :
public function getDataPaginator($pageSize = 20, $page = 1) { $select = $this->select() ->setIntegrityCheck(false) ->from(array('m' => 'mails')) ->join(array('r' => 'recipients'), 'm.mail_id = r.mail_id') ->where("r.recipient_type = 'from'") ); $paginator = Zend_Paginator::factory($select); $paginator->setItemCountPerPage((int)$pageSize); $paginator->setCurrentPageNumber((int)$page); return $paginator; }
Zend_Paginator implémente l'interface IteratorAggregate, ce qui te permet de passer l'objet paginator dans une boucle foreach par ex. A chaque itération, Zend_Paginator va exécuter une requête pour remonter la liste des items de la page en cours.
Après si on pousse plus loin, je dirais que retourner un objet paginator depuis une collection n'est pas une bonne pratique, mais c'est la méthode ZF
Si c'était pour moi, je créerais moi même les requêtes (ou tu peux utiliser Zend_Paginator) pour retourner une collection de données pour la page en cours + un nombre total d'items et je construirais le paginateur côté front, dans le contrôleur, avec l'adapter "null".
Comme ça je remonte bien une collection d'objets métiers comme il se doit. La pagination est du ressort du frontend. Attention, c'est juste une remarque hein, rien d'obligatoire.
A+ benjamin.
Hors ligne
Bonsoir Benjamin et merci pour cette réponse !
Je me suis inspiré de tes conseils, dans ma DbTable :
public function fetchAll($page = 1, $rowcount = 50) { $rows = parent::fetchAll( $this->select() ->setIntegrityCheck(false) ->from(array('m' => 'mails')) ->join(array('r' => 'recipients'), 'm.mail_id = r.mail_id') ->where("r.recipient_type = 'from'") ->limitPage($page, $rowcount) ); return $rows; } public function countMails() { $select = $this->select(); $select->from($this->_name,'COUNT(1) AS num'); return $select; }
Et mon controller :
// Liste des mails $mails = new Application_Model_DbTable_Mails(); $this->view->mails = $mails->fetchAll($this->getRequest()->getParam('page'), 50); // Pagination $paginator = Zend_Paginator::factory($mails->countMails()); $paginator->setItemCountPerPage(50); $paginator->setCurrentPageNumber($this->getRequest()->getParam('page')); $this->view->paginator = $paginator;
Dans ma vue, j'utilise paginator pour la pagination, et emails pour le tableau des emails. Visuellement, c'est tip-top. Par contre j'ai ces erreurs :
Notice: Object of class Zend_Db_Table_Select could not be converted to int in [...] \library\Zend\Db\Select.php on line 637 Notice: Object of class Zend_Db_Table_Select could not be converted to int in [...] \library\Zend\Db\Select.php on line 640
Deux petites questions :
- D'où pourraient venir ces erreurs ? J'ai essayé de modifier la requête dans la table, j'ai la même erreur ;
- Est-ce que cette méthode est fiable ? J'arrive à des résultats assez rapide, mais en comparant avec phpMyAdmin, les premières pages correspondent mais j'ai des décalages sur les suivantes
Est-ce que ma solution correspond à tes idées ? Du coup ça fait 3 questions . Merci d'avance !
Bonne nuit!
Hors ligne
Hello.
Non, ce n'est pas bon
Dans la factory de Zend_Paginator tu passes soit un objet DbSelect qui correspond à la requête originale, soit un chiffre pour utiliser l'adapter NULL.
Là c'est pas bon parce que tu passes un objet DbSelect qui n'a rien à voir avec les données que tu affiches, donc que va faire Zend_Paginator ? Il va considérer que ta requête originale c'est celle retournée par countMails(), il va la compléter pour ajouter les limit et va en exécuter automatiquement une 2ème pour calculer le nombre total d'enregistrements correspondant à cette requête.
De plus la requête qui compte le nombre d'enregistrements total doit être paramétrée comme la requête qui retourne les enregistrements, sinon tu auras un décalage ça c'est certain.
Je pense que je t'ai un peu mélangé les pinceaux avec ma remarque
Comme je te l'ai dit tu peux très bien retourner directement un objet Paginator prêt à l'emploi depuis ta DbTable si une séparation stricte des couches n'est pas obligatoire dans ton application.
Sinon, voilà à quoi je pensais, 2 possibilités :
# DbTable public function fetchAll($page = 1, $rowCount = 50, &$totalItemCount) { // $select = ....; // avec limit // $countSelect = ....; // un COUNT avec mêmes jointures, mêmes conditions sans limit $rows = $this->fetchAll($select); $totalItemCount = $this->fetchRow($countSelect)->num; return $rows; }
2ème solution, tu peux utiliser Zend_Paginator pour t'aider à générer la requête count :
# DbTable public function fetchAll($page = 1, $rowCount = 50, &$totalItemCount) { // $select = ....; // sans limit $paginator = Zend_Paginator::factory($select); $paginator->setItemCountPerPage($rowCount); ->setCurrentPageNumber($page); $rows = $paginator->getCurrentItems(); $totalItemCount = $paginator->getTotalItemCount(); return $rows; }
Tu invoques la méthode comme ceci :
$page = $this->_getParam('page', 1); $totalItemCount = 0; $emails = $mailCollection->fetchAll($page, 50, $totalItemCount); $paginator = Zend_Paginator::factory($totalItemCount); $paginator->setItemCountPerPage(50); ->setCurrentPageNumber($page); $this->view->emails = $emails; $this->view->paginator = $paginator;
Attention, certains seraient prêts à me sauter à la gorge là :p
La pagination est un élément difficile à représenter dans la séparation des responsabilités de chaque couche. Il y a les requêtes d'un côté dans la couche modèle et l'affichage de l'autre dans la couche présentation (vue). Et dans les deux tu as finalement besoin du paginateur. Dans la première pour t'aider à construire les requêtes automatiquement, dans l'autre pour t'aider à générer la numérotation des pages
A+ benjamin.
Hors ligne
Salut Benjamin,
Merci pour ces informations.
J'ai un peu adapté ton idée (je n'arrive pas à me faire au passage de variables par référence ). Voici ma DbTable :
public function fetchAll($page = 1, $rowcount = 50) { $rows = parent::fetchAll( $this->select() ->setIntegrityCheck(false) ->from(array('m' => 'mails')) ->join(array('r' => 'recipients'), 'm.mail_id = r.mail_id') ->where("r.recipient_type = 'from'") ->limitPage($page, $rowcount) ); return $rows; } public function countMails() { return $this->fetchRow($this->select() ->setIntegrityCheck(false) ->from(array('m' => 'mails'), 'COUNT(1) AS num') ->join(array('r' => 'recipients'), 'm.mail_id = r.mail_id') ->where("r.recipient_type = 'from'") )->num; }
Et le Controller :
// Liste des mails $mails = new Application_Model_DbTable_Mails(); $this->view->mails = $mails->fetchAll($this->getRequest()->getParam('page'), 50); // Pagination $paginator = Zend_Paginator::factory((int)$mails->countMails()); $paginator->setItemCountPerPage(50) ->setCurrentPageNumber($this->getRequest()->getParam('page')); $this->view->paginator = $paginator;
C'est vrai qu'il y a une petite redondance, et une exploitation incomplète du Paginator par rapport à ta deuxième solution. Cependant, par rapport à tes exemples, je remarque une plus grande rapidité.
Au final, ça marche impec, 1-2 secondes de génération par page et aucun décalage sur les 61.998 enregistrements et 1240 pages de résultats ! De plus, aucun warning lié au troisième paramètre et le transtypage évite un warning ou une exception selon les exemples.
Merci pour tes conseils !
Hors ligne
Pages: 1