Alléger le nombre de requêtes les éléments d'un module Symfony : Différence entre versions

De e-glop
m (Alléger le nombre de requêtes dans une liste d'un module Symfony déplacé vers Alléger le nombre de requêtes les éléments d'un module Symfony)
Ligne 3 : Ligne 3 :
 
Un module généré par '''doctrine:generate-admin''' dans Symfony génère un nombre de requêtes important et ralentit sensiblement le fonctionnement de votre application. Vous cherchez donc à optimiser la/les requête/s générées automatiquement, quitte à ''mettre les mains dans le code''.
 
Un module généré par '''doctrine:generate-admin''' dans Symfony génère un nombre de requêtes important et ralentit sensiblement le fonctionnement de votre application. Vous cherchez donc à optimiser la/les requête/s générées automatiquement, quitte à ''mettre les mains dans le code''.
  
Ici l'exemple portera sur une liste de ''prescriptions'' auquelles sont associées un ''bénéficiaire'' et un ''individu''. Cet individu est lui-même rattaché à un organisme ''prescripteur''. Le nombre de requêtes générées sans intervention est à la base de 58 pour une pagination de 20 enregistrements.
+
Ici l'exemple portera sur une liste de ''prescriptions'' auquelles sont associées un ''bénéficiaire'' et un ''individu''. Cet individu est lui-même rattaché à un organisme ''prescripteur''. Le nombre de requêtes générées sans intervention pour leur listing est au départ de 58 pour une pagination de 20 enregistrements. Pour la fiche prescription en édition, de 88.
  
 
Nous souhaitons afficher, pour résumer, les informations relatives à la prescription, ainsi que celles directement liées au bénéficiaire et au prescripteur (en passant par la table Individu qui est celle rattachée à notre table ''Prescription'' originelle).
 
Nous souhaitons afficher, pour résumer, les informations relatives à la prescription, ainsi que celles directement liées au bénéficiaire et au prescripteur (en passant par la table Individu qui est celle rattachée à notre table ''Prescription'' originelle).
  
== Solution ==
+
== Solution pour les listes ''generate-admin'' ==
  
 
=== Créer une requête personnalisée optimisée ===
 
=== Créer une requête personnalisée optimisée ===
Ligne 36 : Ligne 36 :
 
   (...)
 
   (...)
  
=== Conclusion ===
+
== Solution pour la fiche ''prescription'' ==
  
Pour vérifier l'efficacité de notre solution, rechargeons la liste des prescriptions : nous sommes passé de 58 requêtes à 7 requêtes, et à un temps d'exécution proportionnel... CQFD.
+
=== Optimiser la classe ''Prescription'' ===
  
== Webographie ==
+
Nous allons préparer la classe PrescriptionTable pour lui donner la capacité à aller chercher (totalement ou non) automatiquement ses relations. Afin de pouvoir utiliser ces fonctionnalités, il faudra ensuite passer par la méthode '''Doctrine::getTable('Prescription')->getRecord()'''.
 +
 
 +
==== automatisé ====
 +
 
 +
'''lib/model/doctrine/PrescriptionTable.class.php''' (ne charge que les relations directes)
 +
 
 +
  (...)
 +
  public function construct()
 +
  {
 +
    $this->query = $this->createQuery('p');
 +
    foreach($this->getRelations() as $key => $info)
 +
      $this->query->leftJoin('p'.$key.' '.substr($key, 0, 3));
 +
  }
 +
 
 +
  public function getRecords($criterias = null,$order_by = null)
 +
  {
 +
    if ( is_array($criterias) )
 +
    foreach ( $criterias as $criteria => $value )
 +
      $this->query->andWhere($criteria.' = ?',$value);
 +
   
 +
    if ( $order_by )
 +
      $this->query->orderBy($order_by);
 +
     
 +
    return $this->query->execute();
 +
  }
 +
  (...)
 +
 
 +
==== manuel ====
 +
 
 +
'''lib/model/doctrine/PrescriptionTable.class.php''' (charge toutes les relations souhaitées)
 +
 
 +
  (...)
 +
  public function construct()
 +
  {
 +
    $this->query = $this->createQuery('p')
 +
      ->leftJoin('p.Beneficiaire b')
 +
      ->leftJoin('p.Individu i')
 +
      ->leftJoin('i.Prescripteur p2')
 +
      ->leftJoin('p.PrestationsEntreprise pe')
 +
      ->leftJoin('pe.Individu pei')
 +
      ->leftJoin('pei.Entreprise e')
 +
      ->leftJoin('p.Objectifs o')
 +
      ->leftJoin('p.PrescriptionApres3mois pa3m')
 +
      ->leftJoin('p.PrescriptionFormation pf')
 +
      ->leftJoin('p.PrescriptionEmploi pem')
 +
      ->orderBy('p.id DESC');
 +
  }
 +
 
 +
  public function getRecords($criterias = null,$order_by = null)
 +
  {
 +
    if ( is_array($criterias) )
 +
    foreach ( $criterias as $criteria => $value )
 +
      $this->query->andWhere($criteria.' = ?',$value);
 +
   
 +
    if ( $order_by )
 +
      $this->query->orderBy($order_by);
 +
     
 +
    return $this->query->execute();
 +
  }
 +
  (...)
 +
 
 +
=== Passer par l'appel PrescriptionTable::getRecords() pour obtenir l'objet ===
 +
 
 +
Dans les classes ayant pour des prescriptions en relation, sur-charger la fonction '''->getPrescriptions()''' (''Prescriptions'' étant défini dans le modèle de données) :
 +
 
 +
'''lib/model/doctrine/Individu.class.php'''
 +
 
 +
  (...)
 +
  public function getPrescriptions()
 +
  {
 +
    return Doctrine::getTable('Prescription')->getRecords(
 +
      array('individu_id' => $this->id),
 +
      'id DESC'
 +
    );
 +
  }
 +
  (...)
 +
 
 +
=== Dans les templates / partials ===
 +
 
 +
L'appel à la collection ''Prescriptions'' issu de l'objet courant ('''$object''') passera automatiquement par la fonction que vous avez défini... qui optimisera alors naturellement les objets ''Prescription'' contenus.
 +
 
 +
== Conclusion ==
 +
 
 +
Pour vérifier l'efficacité de notre solution, rechargeons la liste des prescriptions : nous sommes passé de 58 à 7 requêtes, et à un temps d'exécution proportionnel... Rechargeons maintenant une fiche prescription : nous sommes passé de 88 à 8 requêtes. CQFD.
 +
 
 +
== Améliorations possibles ==
 +
 
 +
Il faudrait maintenant automatiser la recherche des relations (manuelles ou non) lors de la création d'un objet, sans passer par la méthode '''MyclassTable::getRecords()'''. Si vous avez l'astuce, merci de [http://www.libre-informatique.fr/sw/04-Contact me contacter].
 +
 
 +
== Webographie et remerciements ==
  
 
* [http://www.symfony-project.org/jobeet/1_2/Doctrine/fr/12#chapter_12_sub_table_method Practical Symfony, Jour 12 : L'Admin Generator]
 
* [http://www.symfony-project.org/jobeet/1_2/Doctrine/fr/12#chapter_12_sub_table_method Practical Symfony, Jour 12 : L'Admin Generator]
 +
* Garfield-fr sur #symfony-fr @ freenode et son exemple (si les liens sont encore fonctionnels) :
 +
** [http://pastie.textmate.org/private/hsklo4ig3xdysdores2w schema.yml]
 +
** [http://pastie.textmate.org/private/xupcmzyjepspctxbuo3p4a ArticleTable.class]
 +
** [http://pastie.textmate.org/private/jxoevv56s83bgkn5uuzsow actions.class.php]
 +
** [http://pastie.textmate.org/private/qigdbvx8cdx9kdauuzafxw un partial pour exemple]

Version du 20 mai 2010 à 06:17

Présentation de la problématique

Un module généré par doctrine:generate-admin dans Symfony génère un nombre de requêtes important et ralentit sensiblement le fonctionnement de votre application. Vous cherchez donc à optimiser la/les requête/s générées automatiquement, quitte à mettre les mains dans le code.

Ici l'exemple portera sur une liste de prescriptions auquelles sont associées un bénéficiaire et un individu. Cet individu est lui-même rattaché à un organisme prescripteur. Le nombre de requêtes générées sans intervention pour leur listing est au départ de 58 pour une pagination de 20 enregistrements. Pour la fiche prescription en édition, de 88.

Nous souhaitons afficher, pour résumer, les informations relatives à la prescription, ainsi que celles directement liées au bénéficiaire et au prescripteur (en passant par la table Individu qui est celle rattachée à notre table Prescription originelle).

Solution pour les listes generate-admin

Créer une requête personnalisée optimisée

Nous allons créer une requête permettant de retrouver l'ensemble des champs nécessaires à l'affichage de la liste, dans la classe PrescriptionTable du modèle. Ainsi dans le fichier lib/model/doctrine/PrescriptionTable.class.php :

 (...)
 public function retrieveInformationsForListing(Doctrine_Query $q)
 {
   $rootAlias = $q->getRootAlias();
   $q->leftJoin($rootAlias . '.Individu i')
     ->leftJoin('i.Prescripteur') 
     ->leftJoin($rootAlias . '.Beneficiaire b');
   return $q;
 }
 (...)

Déclaration de cette méthode dans le generator.yml

Il est maintenant nécessaire de déclarer l'utilisation de cette méthode au moment de la construction de la liste. Pour se faire, ajouter la ligne suivante dans le fichier apps/backend/modules/prescription/config/generator.yml :

 (...)
 config:
     list:
       title: Liste des prescriptions
       display: [=Beneficiaire, prescripteur]
       table_method: retrieveInformationsForListing
 (...)

Solution pour la fiche prescription

Optimiser la classe Prescription

Nous allons préparer la classe PrescriptionTable pour lui donner la capacité à aller chercher (totalement ou non) automatiquement ses relations. Afin de pouvoir utiliser ces fonctionnalités, il faudra ensuite passer par la méthode Doctrine::getTable('Prescription')->getRecord().

automatisé

lib/model/doctrine/PrescriptionTable.class.php (ne charge que les relations directes)

 (...)
 public function construct()
 {
   $this->query = $this->createQuery('p');
   foreach($this->getRelations() as $key => $info)
     $this->query->leftJoin('p'.$key.' '.substr($key, 0, 3));
 }
 
 public function getRecords($criterias = null,$order_by = null)
 {
   if ( is_array($criterias) )
   foreach ( $criterias as $criteria => $value )
     $this->query->andWhere($criteria.' = ?',$value);
   
   if ( $order_by )
     $this->query->orderBy($order_by);
     
   return $this->query->execute();
 }
 (...)

manuel

lib/model/doctrine/PrescriptionTable.class.php (charge toutes les relations souhaitées)

 (...)
 public function construct()
 {
   $this->query = $this->createQuery('p')
     ->leftJoin('p.Beneficiaire b')
     ->leftJoin('p.Individu i')
     ->leftJoin('i.Prescripteur p2')
     ->leftJoin('p.PrestationsEntreprise pe')
     ->leftJoin('pe.Individu pei')
     ->leftJoin('pei.Entreprise e')
     ->leftJoin('p.Objectifs o')
     ->leftJoin('p.PrescriptionApres3mois pa3m')
     ->leftJoin('p.PrescriptionFormation pf')
     ->leftJoin('p.PrescriptionEmploi pem')
     ->orderBy('p.id DESC');
 }
 
 public function getRecords($criterias = null,$order_by = null)
 {
   if ( is_array($criterias) )
   foreach ( $criterias as $criteria => $value )
     $this->query->andWhere($criteria.' = ?',$value);
   
   if ( $order_by )
     $this->query->orderBy($order_by);
     
   return $this->query->execute();
 }
 (...)

Passer par l'appel PrescriptionTable::getRecords() pour obtenir l'objet

Dans les classes ayant pour des prescriptions en relation, sur-charger la fonction ->getPrescriptions() (Prescriptions étant défini dans le modèle de données) :

lib/model/doctrine/Individu.class.php

 (...)
 public function getPrescriptions()
 {
   return Doctrine::getTable('Prescription')->getRecords(
     array('individu_id' => $this->id),
     'id DESC'
   );
 }
 (...)

Dans les templates / partials

L'appel à la collection Prescriptions issu de l'objet courant ($object) passera automatiquement par la fonction que vous avez défini... qui optimisera alors naturellement les objets Prescription contenus.

Conclusion

Pour vérifier l'efficacité de notre solution, rechargeons la liste des prescriptions : nous sommes passé de 58 à 7 requêtes, et à un temps d'exécution proportionnel... Rechargeons maintenant une fiche prescription : nous sommes passé de 88 à 8 requêtes. CQFD.

Améliorations possibles

Il faudrait maintenant automatiser la recherche des relations (manuelles ou non) lors de la création d'un objet, sans passer par la méthode MyclassTable::getRecords(). Si vous avez l'astuce, merci de me contacter.

Webographie et remerciements