Ce blog — désormais archivé — est en lecture seule. Pour continuer à lire mes tribulations, rendez-vous sur le blog d'Akei, ma société.

Prendre un Café

L'espace d'expression de Nicolas Perriault

Aller au contenu | Aller au menu | Aller à la recherche

Keyword - tips

Fil des billets

mardi 15 septembre 2009

Optimize your Doctrine Workflow with Specialized Queries

I’m currently working on a big Symfony project, with a lot of Doctrine models and complex queries to write. I found a way to organize all of them in an object-oriented and cleaner way than using the traditionnal addNamedQuery() and createNamedQuery() methods workflow[1].

The idea is to create dedicated query classes for a given model ; this way, you can provide useful methods to build the business-related parts of your query.

As usual, the theory is more understandable with a concrete example. Let’s consider this simple Doctrine model[2] :

Disclaimer: The provided examples have been written in a hurry, so mistakes might have been not detected by my attentive proof-reading ;)

 yaml
BlogAuthor:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true  
    name:
      type: string(255)
  relations:
    Post:
      type: one
      class: BlogPost
      local: id
      foreign: author_id

BlogPost:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    author_id:
      type: integer(4)
      notnull: true
    title:
      type: string(255)
    content:
      type: string(65535)
  relations:
    Author:
      type: one
      class: BlogAuthor
      local: author_id
      foreign: id
    Comments:
      type: many
      class: BlogComment
      local: id
      foreign: post_id

BlogComment:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    post_id:
      type: integer(4)
      notnull: true
    author:
      type: string(255)
    content:
      type: string(5000)
  relations:
    Post:
      type: one
      class: BlogPost
      local: post_id
      foreign: id

Now let’s imagine a Query class dedicated to query the BlogPost table:

 php
<?php 
class BlogPostQuery extends Doctrine_Query
{
  static public function create($conn = null, $class = null)
  {
    return parent::create($conn, 'BlogPostQuery')
      ->from('BlogPost p');
  }
  
  public function addPosts($fields = 'p.*')
  {
    return $this->addSelect('p.*');
  }
  
  public function addComments($fields = 'c.*')
  {
    return $this
      ->addSelect($fields)
      ->leftJoin('p.Comments c')
      ->addGroupBy('c.id');
  }
  
  public function addAuthors($fields = 'a.*')
  {
    return $this
      ->addSelect($fields)
      ->leftJoin('p.Author a')
      ->addGroupBy('a.id');
  }
  
  public function addCommentsCount($alias = 'nb_comments')
  {
    return $this
      ->addSelect(sprintf('COUNT(c.id) as %s', $alias))
      ->addGroupBy('c.id');
  }
  
  public function filterByAuthorName($authorName)
  {
    return $this
      ->andWhere('a.name = ?', $authorName);
  }
}

So how can we use this query object? Here are some sample uses:

 php
// Retrieve all posts
$posts = BlogPostQuery::create()
  ->addPosts()
  ->fetchArray();

// Retrieve all posts with comments
$posts = BlogPostQuery::create()
  ->addPosts()
  ->addComments()
  ->fetchArray();

// Retrieve all posts with comments and their count per post
$posts = BlogPostQuery::create()
  ->addPosts()
  ->addComments()
  ->addCommentsCount('yataa')
  ->fetchArray();

// Retrieve all post with chuck as its author and related comments
$posts = BlogPostQuery::create()
  ->addAuthors()
  ->addPosts()
  ->addComments()
  ->filterByAuthorName('chuck')
  ->fetchArray();

// and so on...

Of course, this example of use is not really relevant as our model is really simple, but when you’re dealing with dozens of internationalized objects, it can help cleaning your model classes, controllers and improving the organization of your work.

Update and important precisions

Some people are having negative feedback regarding this technique, claiming it will encourage people using the custom query object directly in the controllers; that’s absolutely not the case as the queries are to be used only within the model layer, for example in the BlogPostTable class:

 php
<?php
class BlogPostTable extends Doctrine_Table
{
  static public function getPostsWithCommentsByAuthor($authorName)
  {
    return BlogPostQuery::create()
      ->addPosts()
      ->addComments()
      ->filterByAuthorName($authorName)
      ->fetchArray()
    ;
  }
}

And in a controller:

 php
class blogActions extends sfActions
{
  public function executeListByAuthor(sfWebRequest $request)
  {
    $this->posts = BlogPostTable::getPostsWithCommentsByAuthor($request->getParameter('author'));
  }
}

Notes

[1] … or raw queries written directly within controllers, but you may know that this is really bad ;)

[2] I’m using Doctrine 1.2 beta (bundled with upcoming symfony 1.3) in the provided example.

mardi 24 juin 2008

ESC_RAWquerie

Le saviez-vous ? Dans symfony, il est possible de désactiver l'échappement des contenus en passant la constante ESC_RAW en dernier paramètre d'une fonction ou méthode de classe depuis une template :

 php
<?php echo $article->getTitle(ESC_RAW) ?>

Plutôt donc que :

 php
<?php echo $article->getRawValue()->getTitle() ?>

Ou l'infâme :

 php
<?php echo $sf_data->getRaw('article')->getTitle() ?>

Voila, c'est tout, c'était surtout l'occasion de faire en titre le pire jeu de mot de ce blog, et de pousser le billet d'humeur sur la RATP un peu plus bas sur la home.

lundi 18 février 2008

Rendre les extensions Firefox disponibles pour la version 3 beta

Clairement, Firefox 3 beta 3 est une énorme tuerie. Le programme, à charge d'extensions comparable, est beaucoup plus véloce et moins gourmand en mémoire que son ainé Firefox 2.

J'utilise depuis quelques temps la version 3 beta comme navigateur principal avec bonheur, à une exception près : l'absence cruelle de disponibilité d'extensions incontournables pour tout développeur web qui se respecte, comme la Web Developer Toolbar par exemple.

Pour contourner le problème et forcer l'installation de ces extensions officiellement déclarées non compatibles avec Firefox 3, voici une petite astuce (via) qui désactivera la vérification de compatibilité à l'installation de ces dernières :

  • Ouvrir le panneau de configuration en tapant about:config dans la barre d'adresse du navigateur
  • Créer une nouvelle entrée booléenne nommée extensions.checkCompatibility et positionner sa valeur à false
  • C'est tout, vous pouvez installer ce que bon vous semble.

Attention : La désactivation la vérification de compatibilité des extensions peut engendrer une instabilité de ces dernières et du navigateur. À manier avec la plus grande précaution.

samedi 19 mai 2007

Petit pense-bête des raccourcis Mac OS X

Je travaille depuis peu et à temps partiel sous Mac OS X et je suis relativement destabilisé par la profusion de raccourcis clavier propres au système d'exploitation. Voici donc un petit pense-bête des raccourcis que j'ai mis un peu de temps à découvrir et à assimiler [1] :

Gestion des fenêtres

  • Naviguer entre les applications : <pomme> + <Tab>
  • Masquer les fenêtres de l'application active : <pomme> + H
  • Masquer toutes les autres applications : <pomme> + <alt> + H
  • Naviguer parmis les fenêtres de l'application active : <pomme> + < ou <pomme> + >
  • Minimiser une fenêtre dans le dock : <pomme> + M

Émulation de touches et caractères virtuels

  • Émuler la touche <delete> : <fn> + <backspace>
  • Émuler les touches <Page Up> et <Page Down> : <fn> + <Haut> et <fn> + <Bas>
  • Émuler les touches <Home> et <End> : <pomme> + <Gauche> et <pomme> + <Droite>

Raccourcis pratiques du Finder

  • Vider la corbeille depuis le finder : <pomme> + <shift> + <backspace>
  • Ouvrir le répertoire home : <pomme> + <shift> + H
  • Ouvrir le répertoire des applications : <pomme> + <shift> + A
  • Ouvrir un emplacement sur le système de fichier : <pomme> + <shift> + G

Caractères spéciaux

  • Pipe (|) : <alt> + <shift> + L
  • Accolades ({}) : <alt> + ( et <alt> + )
  • Crochets ([]) : <alt> + <shift> + ( et <alt> + <shift> + )
  • Backslash (\) : <shift> + <alt> + :
  • Euro (€) : <alt> + $
  • Tilde (~) : <alt> + N
  • E dans l'O (œ) : <alt> + O
  • C cédille majuscule (Ç) : <alt> + ç
  • U accent grave majuscule (Ù) : <alt> + u

Captures d'écran

  • Pour une capture de tout l'écran : <pomme> + <shift> + " (guillemet double)
  • Pour une capture zonnée de l'écran : <pomme> + <shift> + ' (guillemet simple)
  • Pour capturer une fenêtre : <pomme> + <shift> + ' (guillemet simple) + <Espace> (sélectionner ensuite la fenêtre à capturer)

Navigation au sein de formulaires

Dans Safari comme dans Firefox, il faut activer manuellement la navigation au sein des formulaires au moyen de la touche <Tab> :

  • Dans Safari, cochez Préférences > Avancées > La touche Tab permet de naviguer parmis les objets des pages web
  • Dans Firefox, cochez Préférences > Avancé > Toujours utiliser les touches de navigation pour se déplacer à l'intérieur d'une page

Pour activer la navigation au clavier pour l'UI du système d'exploitation, il faut se rendre dans Préférences Système > Clavier & Souris > Raccourcis clavier et selectionner l'option Dans les fenêtres et zones de dialogues, la touche Tabulation activera : tous les réglages.

D'autres ressources plus complètes sur le sujet

Notes

[1] Ces raccourcis sont valables pour le Macbook. Je ne sais pas s'ils fonctionnent sur les autres modèles Apple...

dimanche 4 mars 2007

Rhythmbox tips

(Excusez cette soudaine profusion de billets, je solde)

Je suis fan de Rhythmbox, le gestionnaire de fichiers musicaux fourni avec Gnome dans Ubuntu. C'est simple, ça fonctionne. Voici quelques astuces destinées à une utilisation optimale du programme.

Applet de contrôle dans un tableau de bord Gnome

Music Applet permet de contrôler votre lecteur musical préféré depuis un tableau de bord Gnome :

Music Applet

Pour l'installer :

$ sudo apt-get install rhythmbox-applet

Puis ajouter un nouvel applet au tableau de bord et choisissez Music Applet dans la section Multimedia. Notez qu'il est également capable de piloter Banshee ou Muine.

Notification du titre écouté dans Gajim

Pierpaolo Follia a réalisé un plugin pour Rhythmbox permettant de changer votre message de statut Gajim en fonction du titre que vous écoutez. Pour l'installer, c 'est très simple :

$ mkdir ~/.gnome2/rhythmbox ~/.gnome2/rhythmbox/plugins
$ cd ~/.gnome2/rhythmbox/plugins
$ wget -c http://madchicken.altervista.org/tech/download/gajim-status.tar.gz
$ tar xvzf gajim-status.tar.gz && rm gajim-status.tar.gz

Relancez Rhythmbox, et configurez le plugin via le menu Edition > Greffons > Gajim status changer :

Capture-Gajim_Status_Changer.png

Suggestions Last.fm avec le plugin LastRhythm

Je suis également un très grand fan de last.fm, particulièrement des fonctionnalités de suggestions par similarité artistique. Aussi, quand je suis tombé complètement par hasard sur l'existence d'un plugin LastRhythm pour Rhythmbox, je me suis empressé de l'installer pour le tester.

Verdict, ça marche fort bien (sous Edgy du moins.)

La procédure d'installation est très simple :

$ sudo apt-get install subversion python-elementtree
$ svn co https://svn.usrportage.de/lastrhythm/trunk/ /usr/lib/rhythmbox/plugins/lastrhythm

Redémarrez rhythmbox, activez le plugin greffon et appréciez :)

jeudi 21 décembre 2006

Back(space) to the future

[Via Ubuntonista]

On était quelques-uns à regretter, depuis la sortie de Firefox2 sous Ubuntu, la disparition de la fonctionnalité permettant de revenir en arrière dans son historique de navigation au moyen de la touche [Backspace]. Et bien réjouissons-nous, une solution a été trouvée rétablissant l'odre naturel et immuable des choses en ce bas monde \o/ :

  • Tapez about:config dans la barre d'adresse de Firefox 2,
  • Cherchez browser.backspace_action et remplacez la valeur par 0 (zéro)

C'est tout. Content.

dimanche 22 octobre 2006

Sauvegarde facile avec Hubackup

[via Jorge Castro]

Sauvegarder son répertoire /home régulièrement peut vous prémunir contre l'aliénation mentale en cas de crash disque. Pour vous aider à backuper votre système simplement, Dieu a inventé hubackup :

$ sudo apt-get install hubackup
$ hubackup

Hubackup

Vous n'avez plus qu'à insérer un CD/DVD vierge, et à le graver.

Simple, efficace, sans bavure.

Edit : Preuve (s'il en est besoin) de l'utilité de faire des backups régulièrement :-/

jeudi 21 septembre 2006

Dotclear2 et les gravatars

Voici une astuce pour gérer les gravatars dans les commentaires de votre blog Dotclear2. Dans le répertoire de votre thème courant, créez un fichier _public.php et ajoutez-y ces quelques lignes de code [1] :

<?php
$core->tpl->addValue('gravatar', array('gravatar', 'tplGravatar'));
class gravatar {
  public static function tplGravatar($attr)
  {
    return '<?php echo md5(strtolower($_ctx->comments->getEmail(false))); ?>';
  }
}
?>

Maintenant, dans votre fichier de template pour les billets (généralement, il s'agit de post.html), vous pouvez appeller votre image de gravatar de cette façon entre les balises <tpl:Comments> et </tpl:Comments> :

<img src="http://www.gravatar.com/avatar.php?gravatar_id={{tpl:gravatar}}"
     class="gravatar_img" alt="Gravatar Image" />

Voila, c'est codé en 5 minutes et vous aurez constaté que je ne génère pas directement l'url complète du gravatar et encore moins la syntaxe HTML de la balise image depuis la classe PHP, c'est tout simplement car :

  1. J'en ai la flemme,
  2. Je préfère gérer ça dans le template.

Hope it helps anyway :)

Edit du 22 septembre 2006

Bon, voila finalement une version un poil plus customizable. Le code qui suit annule et remplace le précedent dans votre fichier _public.php :

<?php
$core->tpl->addValue('gravatar', array('gravatar', 'tplGravatar'));

class gravatar {

  const
    URLBASE = 'http://www.gravatar.com/avatar.php?gravatar_id=%s&amp;default=%s&amp;size=%d',
    HTMLTAG = '<img src="%s" class="%s" alt="%s" />',
    DEFAULT_SIZE = '40',
    DEFAULT_CLASS = 'gravatar_img',
    DEFAULT_ALT = 'Gravatar de %s';

  public static function tplGravatar($attr)
  {
    $md5mail = '\'.md5(strtolower($_ctx->comments->getEmail(false))).\'';
    $size    = array_key_exists('size',   $attr) ? $attr['size']   : self::DEFAULT_SIZE;
    $class   = array_key_exists('class',  $attr) ? $attr['class']  : self::DEFAULT_CLASS;
    $alttxt  = array_key_exists('alt',    $attr) ? $attr['alt']    : self::DEFAULT_ALT;
    $altimg  = array_key_exists('altimg', $attr) ? $attr['altimg'] : '';
    $gurl    = sprintf(self::URLBASE,
                       $md5mail, urlencode($altimg), $size);
    $gtag    = sprintf(self::HTMLTAG,
                       $gurl, $class, eregi("%s", $alttxt) ?
                                      sprintf($alttxt, '\'.$_ctx->comments->comment_author.\'') : $alttxt);
    return '<?php echo \'' . $gtag . '\'; ?>';
  }

}
?>

Du côté de votre template post.html et toujours entre vos deux balises <tpl:Comments> et </tpl:Comments>, voila comment ça se passe :

{{tpl:gravatar class="gravatar_img" size="80" altimg="http://www.example.host/default_gravatar.png" alt="Gravatar de %s"}}

Je crois que les attributs sont assez parlants, mais en voici le détail au cas où :

  • class : le nom de la classe CSS à appliquer à l'image
  • size : la taille en pixels
  • alt : Le contenu du texte alternatif à l'image (acepte la syntaxe de sprintf)
  • altimg : L'url de l'image par defaut en l'absence de gravatar (sera url encodée automatiquement)

Enjoy.

Finalement, voila le plugin :-)

Bon, je suis incorrigible mais l'occasion était trop belle pour commencer à mettre le nez dans le nouveau système de plugins de Dotclear2 ; voici donc le plugin Gravatars [2] :

Bien évidemment, pour une toute première version, il doit exister quelques bugs et limitations :

  • Pas de traduction française
  • Pas d'aide contextuelle
  • Pas de configuration par blog (à venir)
  • Utilisation d'un archaïque fichier INI (pas compris comment mettre les settings en base)

Au chapitre des fonctionnalités :

  • Possibilité de définir les paramètres des gravatars de façon globale...
  • ou individuellement au niveau du template d'appel (voir plus haut)

Edit du 25 août 2007 : Grâce à Goulven, vous avez droit à la version patchée compatible Dotclear2b7 du plugin :

Notes

[1] Prochaine étape : gérer la coloration syntaxique du code dans Dotclear2.

[2] Packagé grâce au plugin Pack it! d'Elaboration.be

- page 1 de 6