Articles avec le tag ‘Doctrine’

Symfony Doctrine NestedSet getTree + useResultCache

Jeudi 17 juin 2010

Pour un besoin d’un site Internet développé sous Symfony, j’ai décidé d’utiliser le behavior NestedSet pour gérer des catégories d’un catalogue produit sous la forme d’un arbre.

Un des inconvénients des arbres avec Doctrine NestedSet, c’est le nombre important de requêtes exécutées pour récupérer la totalité de l’arbre. Je vais vous montrer comment j’ai mis en place le cache Doctrine (useResultCache) avec le behavior NestedSet.

Tout d’abord, dans config/ProjectConfiguration.class.php, vérifiez que vous avez bien activé le cache Doctrine:

public function configureDoctrine(Doctrine_Manager $manager)
{
  /* Initialisation du cache Doctrine APC */
  $cacheDriver = new Doctrine_Cache_Apc();
  $manager->setAttribute(Doctrine::ATTR_QUERY_CACHE, $cacheDriver);
  $manager->setAttribute(Doctrine::ATTR_QUERY_CACHE_LIFESPAN, sfConfig::get('app_cache_lifetime')); //mettre cette variable dans app.yml
  $manager->setAttribute(Doctrine::ATTR_RESULT_CACHE, $cacheDriver);
  $manager->setAttribute(Doctrine::ATTR_RESULT_CACHE_LIFESPAN, sfConfig::get('app_cache_lifetime'));
}

Ensuite au niveau de votre action ou component, pour récupérer la totalité de votre arbre avec le cache d’activé :

$this->categories = Doctrine::getTable('category')->getTree();
$q = Doctrine::getTable('category')->createQuery()->useResultCache();
$this->categories->setBaseQuery($q);
$this->categories = $this->categories->fetchTree();

Et enfin, au niveau du template, voici le code pour afficher correctement votre arbre sous la forme de menu par exemple :

<h2>Catégories</h2>
<ul>
  <?php foreach ($categories as $node) : ?>

    <?php if($node['level'] == 0) continue; ?> //ici on affiche pas la catégorie ROOT
    <?php if($node['level'] == '1'): ?>
    <li>
      <span><a href="#"><?php echo $node['label'] ?></a></span>
      <?php if($node->getNode()->hasChildren()): ?>
        <ul id="child_<?php echo $node['id']; ?>">
        <?php foreach($node->getNode()->getChildren() as $child): ?>
          <li><a href="<?php echo url_for('category', $child); ?>"><?php echo $child->getLabel(); ?></a></li>
        <?php endforeach; ?>
        </ul>
      <?php endif; ?>
    </li>
    <?php endif;?>

  <?php endforeach; ?>
</ul>

Voilà, votre arbre est maintenant mis en cache!

To be continued…

Symfony unsetAllExcept + i18n = unsetAllExceptI18n

Mardi 15 septembre 2009

Suite au commentaire de eddie sur l’article « Symfony – Formulaire en plusieurs étapes » voici un exemple d’un formulaire multilangue en plusieurs étapes.

symfony-logo

Petit rappel du contexte: symfony 1.2 + Doctrine

Dans le fichier schema.yml

Projet1Exemple:
  tableName: projet1_exemple
  actAs:
    I18n:
      fields: [titre, description, mots]
  columns:
    id:
      type: integer(4)
      primary: true
      unsigned: true
      notnull: true
      autoincrement: true
    fichier:
      type: string(255)
      unique: true
      notnull: true
    type:
      type: enum
      values: ['image', 'video', 'audio', 'document']
      notnull: true
      default: image
    titre:
      type: string(255)
      description:
      type: string(4000)
    mots:
      type: string(4000)

Nous avons donc deux champs communs à toutes les langues : fichier et type. Et trois champs multilangues : titre, description mots.

L’idée consisterait à mettre dans une première étape uniquement les champs : fichier (commun), titre (multilangue) et description (multilangue).

Dans le fichier actions.class.php

public function executeIndex(sfWebRequest $request) {
  $this->form = new Projet1ExempleForm()
}

Dans le fichier /lib/form/doctrine/BaseFormDoctrine.class.php, ajouter la fonction suivante :

public function unsetAllExceptI18n($keepFields = array(), $cultures = array(), $keepFieldsI18n = array()) {
  $keepFields[] = $cultures;
  foreach ($this as $key => $value)
    $baseFields[] = $key;
  $temp = array_diff($baseFields, $keepFields);
  foreach ($temp as $unsetField)
    unset($this[$unsetField]);
  $embeds = $this->getEmbeddedForms();
  foreach ($cultures as $culture)
    $formsI18n[$culture] = $embeds[$culture];
  foreach ($formsI18n as $culture => $formI18n)
  {
    foreach ($formI18n as $key => $value)
      $baseFieldsI18n[] = $key;
    $temp = array_diff($baseFieldsI18n, $keepFieldsI18n);
    foreach ($temp as $unsetField)
      unset($formI18n[$unsetField]);
    unset($this[$culture]);
    $this->embedForm($culture, $formI18n);
  }
}

Explication des paramètres :

  • -  »$keepFieds » : un tableau des champs ‘commun’ que vous souhaitez « garder ».
  • -  »$cultures » : un tableau des différentes cultures que vous voulez utiliser.
  • -  »$keepFieldsI18n » : un tableau des champs ‘multilangues’ que vous souhaitez « garder ».

Et enfin dans le fichier /lib/form/doctrine/Projet1ExempleForm.class.php

class Projet1ExempleForm extends BaseProjet1ExempleForm {
  public function configure() {
    //Introduire les formulaires multilangues dans un premier temps
    $this->embedI18n(array('fr'));
    $this->embedI18n(array('en'));
    //Garder uniquement les champs dont vous avez besoin
    $this->unsetAllExceptI18n(array('fichier'), array('fr', 'en'), array('titre', 'description'));
  }
}

Et enfin dans votre template

//Soit tout simplement
<?php echo $form;?>

//ou
<?php echo $form['fichier']->renderLabel()?>
<?php echo $form['fichier']->render()?>
<?php echo $form['fr']['description']->renderLabel()?>
<?php echo $form['fr']['description']->render()?>
//...

Voici un aperçu du résultat : Capture_d_ecran_2009-09-15_a_19.25.01

Vous pouvez ensuite continuer de la même manière pour les prochaines étapes de votre formulaire.

N’hésitez pas à me solliciter pour plus d’explications.

To be continued…

Symfony – Formulaire en plusieurs étapes

Vendredi 28 août 2009

Comment mettre en place un formulaire en plusieurs étapes avec Symfony 1.2 et Doctrine? Je vais tenter de vous expliquer cela par un exemple concret.

symfony-logo

Nous allons réaliser une inscription de membre en plusieurs étapes:

  • - Etape 1 : Identification  »(pseudo, mail, password) »
  • - Etape 2 : Informations complémentaires  »(nom, prenom, adresse, cp, ville) »

Dans le fichier /config/doctrine/schema.yml :

membre:
  columns:
    id: { type: integer(4), primary: true, notnull: true, autoincrement: true }
    pseudo: { type: string(255) }
    mail: { type: string(255) }
    password: { type: string(255) }
    nom: { type: string(255) }
    prenom: { type: string(255) }
    adresse: { type: string(255) }
    cp: { type: string(255) }
    ville: { type: string(255) }

Lancer ensuite la commande :

symfony doctrine:build-all

Editer le fichier /lib/form/doctrine/BaseFormDoctrine.class.php et ajouter cette fonction :

public function unsetAllExcept ($keepFields = array()) {
  foreach ($this as $key => $value)
    $baseFields[] = $key;
    $temp = array_diff($baseFields, $keepFields);
    foreach ($temp as $unsetField)
      unset($this[$unsetField]);
}

Nous allons maintenant créer deux formulaires ‘membre’ pour les deux étapes du formulaire.

Créer le fichier /lib/form/doctrine/membreEtape1Form.class.php

class membreEtape1Form extends BasemembreForm {
  public function configure () {
    $this->unsetAllExcept(array( 'pseudo', 'mail', 'password' ) );
  }
}

Créer maintenant le fichier /lib/form/doctrine/membreEtape2Form.class.php

class membreEtape2Form extends BasemembreForm {
  public function configure ()   {
    $this->unsetAllExcept(array( 'nom', 'prenom', 'adresse', 'cp', 'ville' ) );
  }
}

Lancer ensuite la commande pour vider le cache :

symfony cc

Vous pouvez maintenant initialiser vos formulaires dans actions.class.php

public function executeInscriptionEtape1(sfWebRequest $request) {
  $this->form = new membreEtape1Form();
}
public function executeInscriptionEtape2(sfWebRequest $request) {
  $this->form = new membreEtape2Form();
}

Vous pouvez dès à présent vous inspirer de cette base pour vos propres besoins.

N’hésitez pas à me contacter par commentaire pour plus d’explications.

To be continued…