Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 17-12-2010 21:58:23

Sylv1
Nouveau membre
Date d'inscription: 31-10-2007
Messages: 5

[Résolu] Paginator, DbTable et grosse base de données

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 :

Code:

// 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 :

Code:

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 :

Code:

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

 

#2 17-12-2010 22:40:04

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Résolu] Paginator, DbTable et grosse base de données

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 :

Code:

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 smile

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.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#3 18-12-2010 01:34:08

Sylv1
Nouveau membre
Date d'inscription: 31-10-2007
Messages: 5

Re: [Résolu] Paginator, DbTable et grosse base de données

Bonsoir Benjamin et merci pour cette réponse !

Je me suis inspiré de tes conseils, dans ma DbTable :

Code:

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 :

Code:

// 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 :

Code:

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 big_smile . Merci d'avance !

Bonne nuit!

Hors ligne

 

#4 18-12-2010 11:29:11

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Résolu] Paginator, DbTable et grosse base de données

Hello.

Non, ce n'est pas bon smile

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 smile
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 :

Code:

# 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 :

Code:

# 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 :

Code:

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


A+ benjamin.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#5 18-12-2010 15:17:46

Sylv1
Nouveau membre
Date d'inscription: 31-10-2007
Messages: 5

Re: [Résolu] Paginator, DbTable et grosse base de données

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 smile ). Voici ma DbTable :

Code:

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 :

Code:

// 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

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages