Est-ce capital(es)?

Je viens de « commettre » un nouveau projet personnel, histoire de me familiariser avec certaines technologies et de tester comment leaflet peut s’intégrer avec backbone (pas trop mal…): http://capitales.saadtazi.com.

Il y aurait encore « de l’amour à donner » au projet (côté visuel notamment). essayez-le et n’hésitez pas à me faire part de vos questions/remarques.

Le projet est dans un repo privé bitbucket, encore une autre affaire que je voulais essayer, repo privé… Github, je t’aime… Mais bitbucket est acceptable et « fait la job ». Je peux rendre le code public si vous êtes intéressé.

Les données (pays, capitales, positions géographiques) proviennent de freebase.

voir la page « à propos » pour connaitre plus en détail les technologies utilisées.

Heu… Y a quelqu’un?

Ce petit billet rapide afin de vous donner quelques nouvelles de ma nouvelle vie… de développeur bien sûr.

J’ai quitté le monde du PHP il y a maintenant 3 ans. Je voulais me confronter à de nouveaux « stacks ».

J’ai travaillé pendant plus de 6 mois sur un projet purement Javascript (frontend), sur un « one page app ». J’ai approfondi les joies et les peines des callbacks, et j’ai été épargné sur ce projet par les problèmes de compatibilité de navigateurs. L’utilisation d’un framework du coté du browser (en l’occurrence JMVC…) a été une révélation.

Puis je suis passé sur un projet Python (avec le framework flask et canjs du coté frontend, experience bien appréciée! Là encore, j’ai principalement travaillé sur le frontend.

Puis depuis plus d’une année, javascript « all the way » pour une plateforme comprenant:

* une extension de navigateur (« browser extension ») pour Chrome, Firefox et IE (aïe, j’ai mal…),
* un site et des api en node.js (express)
* des tests d’integration en node.js avec selenium et wd.js

Il y a aussi un gros backend HBase/Spark/Flume, mais je ne touche pas trop à ce composant là…

Afin de m’aider dans le choix de package npm, j’ai monté une petite application bien simple. C’est fonctionnel, mais je vais probablement ajouté quelques fonctionnalités… N’hésitez pas à partager vos commentaires! Le code source est disponible sur github.

gRaphael, axes et graduations – tips

Je cherchais à personnaliser les textes de l’axe des x d’un graphique de type « linechart » fait en gRaphael.

En effet, il n’est pas possible de personnaliser les textes affichées sur les différents axes. Or je voulais pouvoir avoir un graphique pour lequel j’avais des données journalières, mais pouvoir afficher des graduation toutes les semaines.

N’ayant rien trouvé sur les tubes, j’ai donc décidé de creuser un peu dans le code, et voici ce que j’ai trouvé. Ceci est donc plus une note personnelle… En espérant que cela pourra aussi servir à quelqu’un.

Bon, je commence par la fin: voici un jsFiddle illustrant le résultat. Vous voyez comment les graduations de l’axe des y est pas beau? Les nombres affichés ne sont pas très « ronds »… J’ai ici corriger le tir pour l’axe des x.

J’ai utilisé une fonction utilitaire de graphael, Raphael.g.axis. J’ai noté que il y avait un paramètre label! Et en utilisant le debugger, la version non-minifiée de graphael, et en regardant comment g.line.js utilise cette foncction , j’ai pu comprendre son fonctionnement et voici ce qu’il faut faire pour avoir un axe des x avec des textes sous forme de texte:

var r = Raphael('testGraph', 320, 220);

// Creates a simple line chart at 10, 10
// width 300, height 200
var x = 10
,    y = 10
,    xlen = 300
,    ylen = 200
,    gutter = 20
, xdata = [0,2,4,6,8,10]
;
var chrt = r.linechart(x,y,xlen,ylen,xdata,[30,10,20,15,35,30], { gutter: gutter, nostroke: false, axis: "0 0 0 1", symbol: "circle", smooth: true });
// default gutter: 10
              //x, y, length, from, to, steps, orientation, labels, type, dashsize, paper
Raphael.g.axis(
    x + gutter, // 10 + gutter
    y + ylen - gutter, //y pos
    xlen - 2*gutter,
    null, null, // used to pass the initial value and last value (number) if no labels are provided
    xdata.length - 1, // number of steps
    0,
    ['0', '-2-', '-4-', '-6-', '-8-', '-10-'], // the labels
    r // you need to provide the Raphael object
);

Donc vous pouvez facilement controler le nombre de graduations et les textes affichés sur les axes. Pas pire, non?

Bon, si vous voulez aller plus loin (changer la police de caractères par exemple), il va falloir que vous fassiez votre propre fonction axis…

Bonus

documentation graphael

Si vous cherchez un peu de documentation sur graphael, voici quelques liens:

Documentation (manque de)

(en date du 11 mars 2012) Le code d’exemple sur la page d’accueil n’est pas bon:

// il y a ça:
// r.g.piechart(320, 240, 100, [55, 20, 13, 32, 5, 1, 2]);
// mais ça devrait être ça
r.piechart(320, 240, 100, [55, 20, 13, 32, 5, 1, 2]);

Node, functions asynchones et async

Ces derniers temps, je fais beaucoup de javascript… vraiment beaucoup…. et j’aime ça!

Il y a un « shift » de mentalité à faire lorsqu’on n’est pas habitué à utiliser des callbacks tous les jours… Mais en en faisant plus, on se rend compte de la puissance de la chose. Et j’aime ça! Mes vieux réflexes sont parfois encore là mais « je me soigne », et ça va de mieux en mieux.

J’ai eu un problème dernièrement, où il fallait que plusieurs callbacks de fonctions soient executés avant de passer à la suite.

Veuillez noter dans les exemples qui suivent, je vais utiliser la fonction setTimeout pour illustrer le code, mais vous pouvez remplacer setTimeout par toute autre function qui accepte un callback comme paramètre. En utilisant setTimeout, on ne se rend pas bien compte qu’on recherche à recevoir un résultat (en paramètre du callback), mais bon…

Une première approche, qui fonctionne, est la suivante:

setTimeout(function(
    // with "real functions with callbacks, 
    // the "result" is passed as params of the callback function
    ) {
    console.log('in function 1 callback');
    // ok, now run function2
        setTimeout(function() {
        console.log('in function 2 callback');
        // ok, I'm done
        // now, do what you have to do...
        // ...
    }, 100);
}, 200);

console.log('the end!');

Le résultat:

the end!
in function 1 callback
in function 2 callback

Cela fonctionne, mais on voit vite le problème: si on a une 3ème fonction à exécuter, on rajoute un autre niveau… De plus en plus difficile à lire, pas super élégant (à mon goût)… Donc plus d’erreurs en cas de modification future.
En plus, une telle approche exécute les differentes fonctions en série, les unes après les autres.

Donc, après une rapide recherche, j’ai découvert la librairie async de caolan (que je ne connaissais pas, mais qui est probablement connu de tout développeur nodejs (?)), initialement développé pour nodejs mais également disponible dans le browser. Cette librairie semble très populaire. Elle est notamment dans le top 6 des packages npm dont dépendent le plus d’autres packages npm.

async contient de nombreuses fonctions qui permettent de resoudre le genre de problèmes décrits plus haut.
Voici un exemple d’utilisation de aync.parallel:

// this example uses nodejs module loading system
var async = require('async');

async.parallel([
    function(callback){
    	// any function with callback
        setTimeout(function(){
        	console.log('in function 1 callback');
        	// when you have what you want, call "callback"
        	// to notify async that the function 1 is done
        	// note: we are in the setTimeout callback function,
        	// that is the beauty!!
            callback(null, 'function 1 result');
        }, 200);
    },
    function(callback){
        setTimeout(function(){
            console.log('in function 2 callback');
            callback(null, 'function 2 result');
        }, 100);
    },
],
// optional callback
function(err, results){
	// we are here only if function 1 and 2 called callback
	// or if one callback param 1 (err) is not null
    console.log('test 1 - in final callback - err:', err, ' - results: ', results);
});

console.log('the end!');

Le résultat (exécuté en ligne de commande « node test-async.js »):

the end!
in function 2 callback
in function 1 callback
test 1 - in final callback - err: undefined  - results:  [ 'function 1 result', 'function 2 result' ]

C’est certain que le code est plus long (mais j’ai aussi mis plus de commentaires…), mais on a des avantages:

  • les functions sont exécutées en parallèle. Veuillez noter qu’il y a de nombreuses autres stratégies de « flow » (en série, waterfall, …) disponible avec async. Une de plus avancée je trouve (mais je n’ai pas encore trouvé une utilité) est auto
  • on a aussi accès à des fonctions permettant d’appliquer la même fonction à des collections (comme async.forEach(), async.map() …)

Le README du projet sur github est complet et décrit très bien toutes les fonctions offertes par la librarie.

Voici un 2ème exemple de callback qui illustre une génération d’erreur:

// test #2 - error
var async = require('async');

async.parallel({
    func3: function(callback){
    	// any function with callback
        setTimeout(function(){
            console.log('in function 3 callback');
            callback('error returned by function 3');
        }, 200);
    },
    func3: function(callback){
        setTimeout(function(){
            console.log('in function 4 callback');
            callback(null, 'function 4 result');
        }, 100);
    },
},
// optional callback
function(err, results){
	// we are here only if func3 and 4 called callback
	// or if one callback first param (err) is not null
	// not that results may contain some values (if some functions have ended)
        // only only one error, 0 to functionCount results
        // note: because we used the parallel map notation, result is a map
    console.log('test 2 - in final callback - err:', err, ' - results: ', results);
});

console.log('the end!');

Le résultat:

the end!
in function 4 callback
in function 3 callback
test 2 - in final callback - err: error returned by function 3  - results:  { func2: 'function 4 result', func1: undefined }

Voilà, j’apprends, doucement… Et, au cas où vous ne l’auriez pas compris… j’aime ça!!

Du nouveau sur geoquity

Je vous ai déjà parlé de mon projet personnel geoquity. Je viens d’ajouter un petit « mashup » qui utilise Google Maps API, et tout un tas d’API geolocalisé:

J’ai aussi utilisé pour récupérer certaines statistiques sur Google Analytics la librairie ga:pi, que j’affiche sur cette page en utilisant le bundle SaadTaziGChartBundle.

Toujours du Symfony2 (parfait), du backbone (parfait aussi).

Je sais, la carte est trop chargée, trop d’icones… C’est un peu « brut » encore…

Des questions, remarques, commentaires? N’hésitez pas à poster un commentaire.

Projet: SaadTaziOEmbedBundle

J’ai rendu disponible sur github le bundle SaadTaziOEmbedBundle pour Symfony2 qui permet de récupérer des données oembed, pour aller chercher des informations sur des images, vidéos… lorsque celles-ci sont diffusées par un fournisseur (provider) qui met à disposition les données oembed, comme youtube.com, vimeo.com, flickr.com (la longue liste des fournisseurs est disponible ici)…

Une utilisation possible: aller chercher le thumbnail de vidéos youtube ou vimeo (sans avoir à utiliser les différentes apis des différents fournisseurs…)

Pour en savoir plus sur oembed, lisez la spécification ici.

J’ai en fait créé une librairie simple-oembed qui est indépendante de Symfony2 (php 5.3+ et curl nécessaires). Le bundle ne fait qu’exposer le service oembed grâce au DIC de Symfony2. Vous pouvez bien entendu utiliser cette librairie sans utiliser le bundle.

La documentation est dans le README github. Si vous avez des questions/bugs/commentaires, n’hésitez pas (via ce blog ou github).

En espérant que cela serve à quelqu’un…

Projet: Geoquity

Je viens de lancer un nouveau projet personnel, geoquity, qui utilise différentes méthodes de géolocalisation, et les compare (bientôt).
Ce projet est dans la lignée d’un autre projet que j’avais lancé il y a 2 ans et demi http://geo.fruitsoftware.com/. Mais chaque utilisateur est invité à soumettre les données récupérées par les différentes méthodes de géolocalisations, afin de pouvoir comparer les résultats (js geolocation api est super performant pour moi, et pour vous?…).
Toutes les statistiques collectées restent anonymes bien sûr!

Ça a été pour moi l’occasion d’essayer Symfony 2 et de tester plus en détail backbone. Voici un résumé de mon expérience:

Symfony

J’ai beaucoup aimé l’expérience. Le framework nous force à être organisé, et de plus facilement faire des bundles réutilisables.

Assetic a été très facile d’utilisation: j’ai mis en place les filtres lesscss et yui compressors très rapidement, mais j’ai perdu un peu de temps sur le serveur de production, car j’avais oublié d’exécuter:

php  app/console assetic:dump --env=prod

Au niveau de Doctrine (ORM), là aussi, assez simple et facile à mettre en place.

Par contre, du côté des formulaires, j’ai eu un peu plus de difficulté pour sauvegarder des entités avec une relation one-to-many… Mais une fois fini, on se rend compte que c’était « évident », et que ‘ »il suffisait de le savoir »…

Une note par rapport aux namespaces et à l’autoloading: Attention au nom des fichiers et des classes PHP: ils doivent être identiques, et la casse doit être la même. Disons que si j’avais été plus vigilant, j’aurais sauvé une couple de minutes… J’aime mon Mac Book Pro, mais disons que si le filesystem était « case-sensitive », la vie serait plus simple…

Backbone

Là encore, assez simple d’utilisation, et ce framework nous force à séparer les choses. Mais je crois que je verrai la puissance si j’utilisais un api RESTful et les collections.

Aussi, je trouve que la séparation Model et View est moins évidente en Javscript (pour moi en tout cas). Je suis pas mal certain que je ferai les choses différemment maintenant que j’en sais un peu plus sur backbone…

Autres affaires

html5 boilerplate

(vraiment rien à dire, dans le bon sens du terme…)

the semantic grid

The semantic grid est un beau projet, qui permet d’utiliser la puissance de lesscss pour réaliser des grids très flexibles sans avoir à utiliser des classes css obscures. Ça rend le html très propre.

Ce qu’il me reste à faire (un jour…)

Passage à Webfaction

Oui, je l’avoue, je suis encore sur du shared hosting… Mais je viens de passer à du shared hosting amélioré…

Cela faisait quelques temps que je voulais migrer vers un nouveau « hosting », pour pouvoir facilement expérimenter des nouvelles technologies, sans devoir me poser la question: « est-ce que mon hosting supporte ça? »

Mon soucis: je ne voulais pas faire le saut à un VPS:

  • plus cher généralement,
  • plus de soucis à installer,
  • il faut constamment mettre à jour, mais pas forcément tout…

Je me suis donc pris du hosting chez webfaction.com, et voici mon retour sur l’expérience « à date ».

Création du compte

Cela leur a pris 50 minutes pour me donner accès à mon serveur (je pense qu’on est 2 dessus… pour l’instant…). Pas pire…

Tickets

Cela leur a pris 11 minutes pour répondre et donner une solution qui fonctionnent. Bon, je l’avoue, c’est probablement une question qu’ils doivent se faire poser souvent: version php 5.3 pour une symlink app…

Version php

Bon, ils supportent mieux php 5.2 que php 5.3, mais ils supportent les 2.

En créant une app php (qui créée un répertoire dans webapps), on peut choisir quelle version de php 5.2(.11) ou php 5.3(.5). Mais les choses se corsent avec les app « symbolic links » (voir plus bas).

Update: 

  1. Les symbolic links peuvent être soient 5.2 soient 5.3, donc pas besoin modification du .htaccess.
  2. Mmm, pas de apc ou autre accélérateurs php, à moins d’installer une version apache et php custom (« coûte » de l’application memory)

Structures

Comment ça fonctionne: dans une interface d’admin web, on créée un domaine ou sous-domaine, une application (php, ruby, python…) et on créée finalement un site web qui lie un sous-domaine à une ou plusieurs apps (donc « http://monsite.com/ » peut rouler du php, « htpp://monsite.com/testnode » peut rouler nodejs…). Rien de neuf sous le soleil, mais facile et intuitif.

Mais comme il faut bien quelques irritants… Il faut créer une webapp dans leur panneau de contrôle pour pouvoir faire quelque chose. Donc comme la majorité des apps que j’ai sont en Symfony 1 ou 2 (mais le problème se poserait aussi pour cakePHP et Zend framework je pense), il ne faut qu’exposer un répertoire web/ (qui contient le front controller), et tout le reste du code se trouve au même niveau que web/ (app(s)/ ou lib/ ou src/…

En cherchant un peu, je trouve qu’il est possible de faire de « symbolic link » app: parfait! … mais il faut créer quand même une app (que j’ai appelé web_apps) pour déposer tout le code,  que je ne vais jamais linker à un site web pour pouvoir déposer mes apps qui ont besoin d’un répertoire web/.

Seul hic: les apps « symbolic link » ne sont possible (pour l’instant, d’après le support) qu’avec php 5.2… Le trick est d’ajouter la directive suivante dans le .htaccess (j’aime pas trop mais ça fonctionne…):

AddHandler php53-cgi .php

Pas top mais ça ne semble pas ralentir le site.

Ceci n’est plus nécessaire: 2 mode symbolic links maintenant.

Installation de node

Alors là, trop facile. 15 minutes et c’était fait… Une petite recherche sur google et … J’ai utilisé une méthode très similaire à celle décrite par Rob Flaherty sur son blog, sauf que:

  • j’ai mis les sources de node dans mon app web_apps pour la compilation
  • ce n’est qu’après que :
    • j’ai créée l’app custom (qui roule sur un port),
    • associer l’app à un site
    • et que je me suis loggé ssh créer le fichier app.js et pour l’exécuter

Avantage: les sources node ne pollue pas mon app node…

Configuration Google email (google apps)

Depuis l’interface d’admin my.webfaction.com, très facile: il suffit de créer les MX records…

Conclusion

Pour l’instant, je suis très satisfait.

J’espère juste qu’ils vont améliorer le support de php 5.3, notamment pour les « symbolic link apps ».

Autre point positif: beaucoup de documentation, même si l’ancien forum pollue parfois google…

GChart Bundle – un bundle Symfony pour utiliser Google Chart

Je viens de rendre disponible sur github.com GChartBundle, un bundle Symfony (Symfony2 donc…) qui « wrappe » le Google Chart Tool et quelques charts (mais surtout le QrCode) de Google Image Chart API.

Ce que j’ai appris en faisant ce bundle:

La puissance du DIC

La puissance du Dependency Injection Container… ou comment permettre à l’utilisateur de redéfinir des paramètres du Bundle, passer des services aux services que l’on créée…

La puissance des Twig Extensions

… ou comment définir ses propres fonctions Twig, qui elles-même peuvent appeler « render » sur un template.

J’ai passé un peu de temps à trouver comment faire, mais l’idée est d’utiliser la méthode initRunTime() pour garder une référence d’un Twig_Environment:

    //in Twig/TwigExtension.php
    public function initRuntime(\Twig_Environment $environment)
    {
        $this->environment = $environment;
    }

J’aurais pu aussi de la même manière utiliser un template php (en passant 2 resources dans le DIC par exemple…), mais un manque de motivation m’a gagné (je n’utilise pas les templates php de toute façon).

Cette partie (=le « rendering » d’un template dans l’extension Twig) pourrait être plus « propre aussi… Dans une version ultérieure peut-être?

Malheureusement, je n’ai pas de démo live. Mais voici tout de même un démo statique (copier coller du code html généré localement) de ce que génère l’action Demo.

En espérant que ça aide quelqu’un…

Symfony et l’AdminBundle – ça fonctionne!

« Ça ne marche pas, ça fonctionne » – JF

Excellente nouvelle, l’AdminBundle a été modifié pour fonctionner avec la version beta1 de Symfony.

Grâce à la documentation mise en ligne par Thomas Rabaix , j’ai assez facilement réussi à obtenir ça:

(oui, les datas ne sont pas très réaliste, car loadées rapidement avec DoctrineFixturesBundle)

Mon seul « soucis » a été de comprendre la configuration à insérer dans config.yml, ne sachant pas trop les préfixes « sonata » qu’il fallait garder. Donc voici ce que j’ai mis dans mon fichier:

services:
  sonata.blog.admin.post:
    class: SaadTazi\BlogBundle\Admin\PostAdmin
    tags:
      - { name: sonata.admin, manager_type: orm, group: saadtazi_blog, label: post }
    arguments: [null, SaadTazi\BlogBundle\Entity\Post, BlogBundle:PostAdmin]
  sonata.blog.admin.author:
    class: SaadTazi\BlogBundle\Admin\AuthorAdmin
    tags:
      - { name: sonata.admin, manager_type: orm, group: saadtazi_blog, label: author }
    arguments: [null, SaadTazi\BlogBundle\Entity\Author, BlogBundle:AuthorAdmin]

(oui, je sais, j’ai fait un mini système de blog, pas très original, et il existe déjà un NewsBundle… mais c’est assez simple comme objectif…)

À noter

  • La documentation: j’ai déjà donné le lien de la documentation, mais la revoici, juste au cas où vous n’avez pas compris que c’est ce qui m’a permis d’y arriver… http://rabaix.net/AdminBundle/html/index.html
  • La possibilité d’ajouter un enregistrement relié par une foreign key directement depuis (depuis une modal jQuery). Un exemple:

  • Par rapport à l’admin generator de symfony 1 (une ligne de commande et on avait un module), là il y a un plus de chose à faire pour le développeur: une classe Controller (super simple), une classe Admin, de la configuration dans config.yml (ou xml ou …)
  • Reste à trouver comment customiser les menus (xliff i18n?)…
  • Il m’a fallu appliquer ce patch afin de supprimer un problème de « invalid CSRF token » à chaque fois que je voulais sauvegarder quelque chose (pas encore accepté…). Donc ce n’est probablement pas super stable encore… Mais je pense que c’est une fonctionnalité essentielle pour l’adoption de Symfony par la communauté…

Merci, Merci, Merci, Merci!!!

Ça ressemble étrangement à django, vous ne trouvez pas 😉 ? Bravo en tout cas en gens autours de sonata-project.