Posts

Profiler son application avec Xdebug sous wamp / easyphp

20 juin 2009 17 h 36 min

Lorsqu’on veut optimiser son application, la première chose à faire c’est d’identifier les parties de code trop gourmandes en temps ou en ressources. La plupart des developpeurs utilisent des logs pour trouver les points de contention. Ils ajoutent dans leur code, à intervalles réguliers, des appels à leur classe de log perso (dont ils sont très fier…) je le sais parce que je l’ai aussi fait ;-) Le désavantage c’est que ça fait beaucoup de lignes à taper et que le code devient vite illisible pollué par de multiples appels à la classe log. Sans compter que lorsque vous voulez logger une application déjà faite, il y en a pour des heures…

Mais il y a plus simple avec l’utilisation transparente d’un profiler.

J’ai choisi Xdebug, une extension PECL de PHP qui trace le fonctionnement du moteur PHP en générant des logs normalisés lisibles par un programme tiers (KCacheGrind, WinCacheGrind, WebGrind…). L’avantage c’est qu’il n’y a rien à insérer dans le code et la solution se met en place en quelques minutes. En outre, elle permet de ne pas alourdir le fonctionnement de l’application, même en production, grâce à son déclenchement limité à une page et un utilisateur (mode trigger). En ajoutant simplement dans l’url web ?XDEBUG_PROFILE, on ne logge que les processus de la page en question, on évite ainsi de générer 3Go de log par jour sur de grosses applications en loggant l’ensemble de l’activité.
Nous allons voir plus spécifiquement l’utilisation de Xdebug en développement local sous Windows (Wamp ou EasyPHP). Je vous laisse rechercher sur le web son installation sous Linux, mais rassurez-vous, c’est encore plus simple !

Nous commencerons par la fin: le lecteur de log. Je vous propose WebGrind un projet opensource simple et super pratique. C’est une petite application php téléchargeable sur http://code.google.com/p/webgrind/
Dans votre répertoire local /www créez un répertoire /xdebug dans lequel vous allez coller les fichiers de Webgrind après décompression. Ajouter y un repertoire /cache qui nous servira à archiver les logs issus de Xdebug. En racine (/www/xdebug) vous devriez maintenant trouver un fichier config.php. Modifiez la ligne suivante (debut du fichier) en y ajoutant le répertoire de log (”/cache”) comme ceci :

  1. static $storageDir = "/cache";

Nous allons maintenant télécharger la bonne version de Xdebug. Celle-ci dépend de votre version de PHP. Wamp ou EasyPHP vous fournissent cette information, dans mon cas j’ai PHP 5.2.6. Allez sur http://xdebug.org/download.php et choisissez la dernière version stable de xdebug ( actuellement version 2.0.3) pour PHP 5.2 VC6. Collez ensuite cette DLL dans le répertoire extensions de php (”/ext”). Sur mon PC, il se trouve sur C:\wamp\bin\php\php5.2.6\ext\php_xdebug-2.0.3-5.2.5.dll. En réalité vous n’avez pas besoin de coller cette librairie strictement dans ce répertoire mais c’est plus propre ainsi.

Nous allons ensuite modifier notre php.ini pour lancer cette extension au démarrage de PHP. Editez php.ini et ajoutez les lignes suivantes en fin de script  en prennant bien soin de modifier les chemins d’accès et le nom de la DLL en fonction de votre propre système:

  1. [XDebug]
  2. zend_extension_ts="C:\wamp\bin\php\php5.2.6\ext\php_xdebug-2.0.3-5.2.5.dll"
  3. xdebug.remote_enable=1
  4. xdebug.remote_host=127.0.0.1
  5. xdebug.remote_port=9000
  6. xdebug.remote_handler=dbgp
  7. xdebug.remote_mode=req
  8. xdebug.show_local_vars=1
  9. xdebug.profiler_enable_trigger=1
  10. xdebug.profiler_output_dir="C:\wamp\www\xdebug\"
  11. xdebug.profiler_output_name="cachegrind.out.%s"

Il ne vous reste plus qu’à redemarrer votre Wamp ou votre EasyPHP, Xdebug est maintenant opérationnel. Pour le vérifier, lancez un phpinfo(), vous devriez voir apparaitre la nouvelle extension Xdebug.

Une fois ceci fait, lancez une de vos applications et, sur la page qui vous semble mal optimisée, ajoutez dans la barre d’url du navigateur ?XDEBUG_PROFILE en paramètre. Ceci va déclencher la génération d’un log pour cette page ainsi que pour tous les processus appellés pour la construction de cette page image4 Ouvrez ensuite l’url http://localhost/xdebug, vous devriez tomber sur l’interface de WebGrind. Séléctionnez simplement “microseconds” à la place de “percent” et cliquez sur le bouton “update”. WebGrind ira chercher automatiquement le dernier log enregistré. Il se présente sous la forme d’un tableau :
image3 Traduisez : la fonction php imagefilltoborder de la ligne 69 est appellée une fois et consomme 5934 microsecondes.

Et là, il vous faut 2 secondes pour comprendre qu’un fopen est plus long qu’un file_get_contents, que votre fonction check_it_all est appelée 564 fois alors qu’elle ne sert pas dans ce contexte ou encore qu’un explode tue la performance… maintenant c’est à vous de jouer ! Sachez enfin qu’il y d’autres utilisations de Xdebug (autres que le mode trigger décrit ci-dessus) et que le meilleur lecteur de log est probablement KCacheGrind sous Linux car il permet de visualiser les appels aux fonctions sous forme graphique.

Logique floue et soundex français

27 mai 2009 0 h 11 min

Tous les CRM proposent un module de recherche dans la base de données, mais bien souvent cette recherche ne trouve que ce qu’un processus logique peut trouver. Ainsi, si l’on cherche un client dont le nom est “FAURE” on trouvera l’enregistrement associé. Si l’on cherche “FORT” par contre, on n’obtiendra aucun résultat. Les utilisateur s’y sont habitués (”un ordi c’est pas bien malin !”), les informaticiens expliquent pourquoi (”c’est pas logique !”) et les chercheurs proposent des algorithmes très complexes et très lents.

Il y pourtant plus simple pour peu qu’on prenne le problème sous un autre angle.

Quel que soit le calcul mathématique (égalité, intersection…) “FAURE” est toujour différent de “FORT”. Avec seulement deux lettres en commun, il n’y a qu’un humain pour faire le lien. Pour nous sortir de cette impasse, nous allons utiliser une alternative théorique que l’on appelle logique floue. C’est une méthode qui intègre la logique “humaine” dans la logique mathématique. Elle se résumerait ainsi: “FAURE” est presque égal à “FORT”. L’objectif n’est pas d’obtenir un résultat parfaitement exact mais un “flou” qui tombe le plus souvent dans le vrai. Cette méthode est très légère en termes de resources consommées car elle ne cherche pas l’absolue perfection, elle se nourrit plutôt d’approximations statistiques.

Si l’on considère qu’un humain voit une ressemblance parceque la prononciation de ces deux mots est identique, c’est donc la prononciation que l’on va numériser. Les languages de programmation nous fournissent une fonction qui évalue la sonnorité d’un mot, en php il s’agit de soundex(). Cette fonction nous renvoie 4 caractères qui représentent approximativement l’empreinte sonnore du mot, mais manque de chance elle est clairement adaptée à la langue anglaise. Ainsi “FAURE” sera codé “F600″ et “FORT” “F630″. Pas d’égalité, donc pas de reconnaissance !

Je vous propose un algorithme adapté au Français que j’ai numérisé à grand renfort de REGEX, le tout dans une classe optimisée de moins de 70 lignes de PHP. Et cette fois-ci l’empreinte de “FAURE” est strictement identique à celle de “FORT” ;-)
La méthode proposée pour obtenir les 4 caractères de l’empreinte à partir du nom $msg est la suivante :

  1. // 1- Suprimer les blancs à droite et à gauche du nom
  2. // 2- Convertir le nom en majuscule
  3. $msg = strtoupper(trim($msg));
  4.  
  5. // 3- Remplacer les lettres accentuées et le c cédille par leur équivalent non accentué
  6. $msg = ereg_replace($cle,$valeur,$msg);
  7. // en remplaçant successivement $cle et $valeur ci-dessus par les clés et les valeurs du tableau suivant :
  8. "Ä|ä|À|à|Â|â" => "A",
  9. "Ë|ë|É|é|È|è|Ê|ê" => "E",
  10. "Ï|ï|Î|î" => "I",
  11. "Ö|ö|Ô|ô" => "O",
  12. "Ü|ü|Ù|ù|Û|û" => "U",
  13. "Ÿ|ÿ" => "Y",
  14. "Ç|ç" => "C",
  15.  
  16. // 4- Supprimer les blancs et les tirets
  17. " |-" => "",
  18.  
  19. // 5- Remplacer les groupes de lettres suivantes par leur correspondance
  20. "GUI" => "KI",
  21. "GUE" => "KE",
  22. "GA" => "KA",
  23. "GO" => "KO",
  24. "GU" => "K",
  25. "CA" => "KA",
  26. "CO" => "KO",
  27. "CU" => "KU",
  28. "Q" => "K",
  29. "CC" => "K",
  30. "CK" => "K",
  31.  
  32. // 6- Remplacer toutes les voyelles sauf le Y par A
  33. "E|I|O|U" => "A",
  34.  
  35. // 7- Remplacer les préfixes suivants par leur correspondance
  36. "^ASA" => "AZA",
  37. "^KN" => "NN",
  38. "^PF" => "FF",
  39. "^PH" => "FF",
  40. "^SCH" => "SSS",
  41.  
  42. // 8- Supprimer les H sauf s’ils sont précédés par C ou S
  43. "([^C|S])H" => "\\1",
  44.  
  45. // 9- Supprimer les Y sauf s’il est précédé d’un A
  46. "([^A])Y" => "\\1",
  47.  
  48. // 10- Supprimer les lettres A, T, D et S en fin de mot
  49. "[A|T|D|S]$" => "",
  50.  
  51. // 11- Supprimer tous les A sauf le A au début du mot s’il y en a un
  52. $msg = ((substr($msg,0,1)=="A") ? "A":"").ereg_replace("A","",$msg);
  53.  
  54. // 12- Remplacer toutes les sous chaînes de lettre répétitives (SS, TT) par une seule lettre (S, T)
  55. private function unique($msg)
  56.   {
  57.   $box = $tmp = "";
  58.   for($i=0;$i<strlen($msg);$i++)
  59.     {
  60.     if($box!=$msg[$i]) $tmp.= $msg[$i];
  61.     $box = $msg[$i];
  62.     }
  63.   return $tmp;
  64.   }
  65.  
  66. // 13- Conserver les 4 premiers caractères du mot et si besoin le compléter avec des blancs pour obtenir 4 caractères
  67. return substr($msg."    ",0,4);

Voilà, ainsi fait il ne vous restera plus qu’à ajouter un champs “soundex” à la table des clients de la base de donnée de votre CRM. Les empreintes seront précalculées à partir du nom et ajoutées dans ce champs pour chaque enregistrement de façon à obtenir des recherches non seulement performantes mais aussi ultra rapides (pas de LIKE mais un simple = sur 4 caractères). Votre commercial, grand utilisateur de CRM, va enfin trouver Mr FORT dans sa base lorsqu’il aura cru entendre Mr FAURE ou même Mme PHORE se présenter lors d’une conversation téléphonique de moindre qualité…

Vous trouverez un exemple avec un fichier de près de 1000 prénoms français (pour évaluation de l’algorithme en situation difficile) ainsi que la classe “sonnex” qui génère les empreintes dans le zip ci-dessous:

Télécharger soundex.zip

Algorithme: Suis-je dans le polygone ?

21 mai 2009 0 h 27 min

S’il existe un domaine qui correspond parfaitement à ma philosophie de simplicité, c’est bien celui de l’algorithmie mathématique. Un algorithme c’est l’art de simplifier des calculs extrêmement complexes. Contrairement à ce que l’on peut croire, il s’agit d’une science très ancienne qui n’est pas née avec les ordinateurs, mais à une époque antique où l’on écrivait ses formules sur un lit de sable avec un simple bâton. Etonnant, non ? Ainsi les grand noms de l’algorithmie sont Archimède, Pythagore, Euclide, Averroès… et dire qu’il a fallu attendre le 17ème siècle (René Descartes, le Discours de la méthode, 1637) pour commencer à entrevoir toute l’utilité d’une pratique sans laquelle le serveur de mon blog ne fonctionnerait même pas !

Je vous propose un petit algorithme simple et son application concrète pour vous donner envie d’aller plus loin dans ce domaine.

poly1Le but du jeu est de déterminer un calcul mathématique permettant de savoir si un point se trouve à l’intérieur ou à l’extérieur d’un polygone à n côtés. Ca parrait pas très concret comme ça, mais comment faire autrement pour savoir si le bar des familles se trouve dans le quartier délimité par la rue de l’église, l’avenue du Général de Gaulle et la plage avec un placement des coordonnées dans Googlemaps ?
Avant tout calcul, l’algorithmie se définit par un cheminement logique qu’il faut expliquer. Considérons le schéma ci-contre, on a 4 points A,B,C et D formant un polygone à 4 côtés. La croix au milieu correpond au point à tester (Fig. 1). Euclide (-300 av.JC) nous dit que si la somme des angles formés en reliant successivement les 4 points au point à tester est égale à 360°, le point à tester est dans le polygone (Fig. 2). Si la somme est égale à 0°, il est dehors (Fig 3).
Traduisons ça en PHP. La première chose qu’il faut savoir faire c’est calculer l’angle d’une droite. Pour ceux qui ne sont pas allés en cours de math depuis longtemps, je vous le rappelle : l’angle est égal à l’arctangente de (dy,dx) dans lequel dx est la différence d’ascisse entre les deux points formant la droite et dy la différence d’ordonnée. Ainsi une droite allant du point A(0,1) au point B(2,3) aura un angle de arctan(2,2) soit 45°. Les notation d’angles se font ici en radian: 360° correspond à 2pi, 180° à pi et 90° à 1/2pi. Notons que la faible précision des calculs nous interdit de tester la stricte égalité avec 2pi ou avec 0, parce que ça ne tombe que rarement pile dessus (exemple : 1.11E-14 est presque égal à zéro), on testera donc si la somme des angles est supérieure ou inférieure à pi. Par ailleurs, lorsqu’un angle dépasse les 180°, nous lui affectons une valeur inférieure négative, ainsi 270° deviendra -90°. Ceci devrait permettre aux fonctions de PHP de s’en sortir.

  1. public function inside($polygon)
  2.   {
  3.   $angle = 0;
  4.   $n = count($polygon);
  5.  
  6.   for ($i=0;$i<$n;$i++)
  7.     {
  8.     $theta1 = atan2($polygon[$i]->y - $this->y, $polygon[$i]->x - $this->x);
  9.     $theta2 = atan2($polygon[($i+1)%$n]->y - $this->y, $polygon[($i+1)%$n]->x - $this->x);
  10.     $dtheta = $theta2 - $theta1;                               
  11.  
  12.     while($dtheta > pi()) $dtheta -= 2*pi();
  13.     while($dtheta < -pi()) $dtheta += 2*pi();
  14.  
  15.     $angle += $dtheta;
  16.     }
  17.  
  18.   return (abs($angle) > pi()) ? true: false;
  19.   }

Je vous propose de tester ça visuellement avec un petit script qui affiche 20 tirages au sort par page. De quoi voir que même avec des polygones complètement étranges, ça fonctionne toujours ;-) ouvrir dans une nouvelle fenêtre ou téléchargez le :

Télécharger insidepoly.zip

Créez des graphiques avec Google charts

20 mai 2009 14 h 45 min

Pour visualiser d’un coup d’oeil des données brutes, rien ne vaut un bon graphique. Camemberts, barres ou autres lignes permettent d’analyser rapidement une baisse des ventes ou une part de marché naissante. En cherchant sur la toile, on trouve les APIs Google Charts qui permettent de “grapher” des données, elles s’utilisent en requêtant une url avec des parametres en get : http://chart.apis.google.com/chart?chco=…&chd=… en retour on obtient une image PNG. Leur utilisation s’avère complexe à cause du format totalement exotique des données à entrer.

Mais il y a plus simple, surtout quand on utilise la classe que je vous propose ci-dessous.

camembertCommençons par le classique camembert. Le but de mon traitement de données est de fournir une simple fonction qui permet de passer un tableau de données et de récuperrer l’url de l’image. La classe va donc trouver les bons paramètres et y concaterner les valeurs correctement formatées. Pour illustrer ces exemples, j’ai pris les données que j’avais sous la main, en l’occurence la moyenne du cours du Dollar vs Euro pour chaque mois de 2003 à 2009, ces données sont regroupées dans un tableau $tab qui a la forme suivante :

    “200301″ => “0.941605″, “200302″ => “0.927264″, “200303″ => “0.926369″, “200304″ => “0.920756″, …

Avant tout, nous allons devoir regrouper ces données par années, pour obtenir un graphique lisible (le tableau a près de 100 lignes). Je vous laisse voir la fonction groupby() dans les sources téléchargeables en fin de page: en gros, on lui donne une chaine de caractères représentant les “tranches” (2003, 2004, 2005, 2006, 2007, 2008, 2009) et elle regroupe les valeurs en les additionnant. Le camembert représente des pourcentages, aussi nous allons devoir connaitre la somme de toutes les valeurs et déterminer le pourcentage que représente chaque ligne par rapport à l’ensemble du tableau. Voilà, en termes de traitement, nous avons maintenant toutes les données voulues : un tableau de la somme des valeurs de chaque année et des valeurs en pourcentage du total.

    “2003″ => “17″, “2004″ => “16″, “2005″ => “16″, “2006″ => “15″, …

Il ne nous reste plus qu’à formater correctement l’url d’appel vers les API de google. Les labels (indexs) sont séparrés par des pipe (”|”) et concaténés dans la variable “chl”, les données sont séparrées par des virgules (”,”) et concaténés dans la variable “chd”.

  1. public function camembert($tab,$groupby="")
  2.   {
  3.   if($groupby!="") $tab = $this->groupby($tab,$groupby);
  4.  
  5.   $chl = implode("|",array_keys($tab));
  6.   $chd = "";
  7.   $tot = array_sum($tab);
  8.   while(list($k,$v) = each($tab))
  9.     {
  10.     $chd .= ",".intval(($v/$tot) * 100);
  11.     }
  12.   return $this->out("cht=p3&amp;chs=300×100&amp;chl=".$chl."&amp;chd=t:". substr($chd,1));
  13.   }

La fonction out() nous donne en retour l’url exacte avec les paramètres de couleur en plus. On obtient : http://chart.apis.google.com/chart?chco=0000FF&chf=bg,s,FFFFFF&cht=p3&chs= 300×100&chl=2003|2004|2005|2006|2007|2008|2009&chd=t:17,16,16,15,14,13,6

Au total on n’aura simplement qu’à appeller la fonction echo $chart->camembert($tab,$slices);

bar1Attaquons nous maintenant aux graphiques à barres. On va utiliser la même fonction de regroupement des données par années mais cette fois-ci on va juste les additionner sans calculer leur pourcentage par rapport au total. Par contre, on va coder ces données et c’est sur ce point où google a été fortement mal inspiré, bien que ça parte d’une bonne intention… en effet l’idée est de limiter la taille de l’url parcequ’en get, la limite est souvent de 4096 caractères. Il existe deux types de codages dans l’API google charts. Le premier code une valeur entre 0 et 61, l’autre les valeurs de 0 à 4096. Cette dernière ne fonctionnant pas avec les graphiques à barre, on se retrouve bien embêté avec des valeurs dont le maximum possible est 61 ! La solution que j’ai mis en place est un échelonnage du graphique. Disons que le maximum est 61, on va traduire nos valeurs en base 61. Connaissant la valeur maximum comprise dans notre tableau, une simple règle par 3 nous donnera la valeur “google” codée. Si on a une valeur max de 1000 dans notre tableau, on codera 61; si on veut coder 500 on codera 30 (arrondi de 30.5). Le graphique sera quant à lui plafonné à 61 grâce au parametre “chds=0,61″.

  1. public function barre($tab,$groupby="")
  2.   {
  3.   if($groupby!="") $tab = $this->groupby($tab,$groupby);
  4.  
  5.   $chl = implode("|",array_keys($tab));
  6.   $chd = "";
  7.   $tmp = array_values($tab);
  8.   rsort($tmp);
  9.   $max = $tmp[0];
  10.   while(list($k,$v) = each($tab))
  11.     {
  12.     $v = intval(($v/$max) * 61);
  13.     $chd .= $this->encode_s($v);
  14.     }
  15.   $params = "cht=bvg&amp;chs=".(100+30 * count($tmp))."x100&amp;chds=0,61&amp;chxt=x,y&amp;chxl=0:|".$chl."|1:|0|".$max. "&amp;chd=s:".$chd;
  16.  
  17.   return $this->out($params);
  18.   }

Pour info, la méthode “encode_s” fonctionne comme suit: 0 est codé en A, 1 = B, 2 = C, …. , Z = 25, a = 26, b = 27, … , z = 51, 0 = 52, 1 = 53, … , 9 = 61. Par ailleurs, la taille des barres étant fixe, on aura besoin d’alonger l’image PNG en fonction du nombre de barres affichées. Enfin, la fonction “out” nous donnera en retour l’adresse web suivante : http://chart.apis.google.com/chart?chco=0000FF&chf=bg,s,FFFFFF&cht=bvg&chs= 310×100&chds=0,61&chxt=x,y&chxl=0:|2003|2004|2005|2006|2007|2008|2009|1:|0|10.625385 &chd=s:9332yvV

Au total on n’aura simplement qu’à appeller la fonction echo $chart->barre($tab,$slices);
 
ligne1Passons maintenant aux graphiques à lignes. Ceux-ci nous permettent d’afficher toutes les données, de sorte que la résolution du graphique soit plus interessante. On aura donc une centaine de données à envoyer et seulement les années comme index (labels). La même technique d’échelonnage du graphique sera utilisée à cause du codage spécial en base 61. Ci-dessous, la fonction “showonly()” va renvoyer les labels : chaine vide sauf si l’année change, vous pourrez voir son fonctionnement dans les sources téléchargeables en bas de page.

  1. public function ligne($tab,$groupby="")
  2.   {
  3.   $chl = ($groupby!="") ? $this->showonly($tab,$groupby): implode("|",array_keys($tab));
  4.   $chd = "";
  5.   $tmp = array_values($tab);
  6.   rsort($tmp);
  7.   $max = $tmp[0];
  8.   while(list($k,$v) = each($tab))
  9.     {
  10.     $v = intval(($v/$max) * 61);
  11.     $chd .= $this->encode_s($v);
  12.     }
  13.   $params = "cht=lc&amp;chs=300×100&amp;chds=0,61&amp;chxt=x,y&amp;chxl=0:|".$chl."|1:||". $max."&amp;chd=s:".$chd;
  14.  
  15.   return $this->out($params);
  16.   }

En retour, on obtient http://chart.apis.google.com/chart?chco=0000FF&chf=bg,s,FFFFFF &cht=lc&chs=300×100&chds=0,61&chxt=x,y&chxl=0:|2003||||||||||||2004||||||||||||2005|||||||||||| 2006||||||||||||2007||||||||||||2008||||||||||||2009||||||1:||0.941605&chd=s:988743565330zz02 11010zxwxxxyz11001221210yzzyyzyxxxwvvwvvutsssrppppprtwyvwyxxw

Voilà ainsi fait, les API google chart sont utilisables simplement :

  1. <?php
  2. include "chart.class.php";
  3.  
  4. $slices = "2003|2004|2005|2006|2007|2008|2009";
  5. $tab = json_decode(file_get_contents("ratesUSD.txt"),true);     
  6.  
  7. $chart = new Chart("0000FF","FFFFFF");
  8. echo $chart->camembert($tab,$slices);
  9. echo $chart->barre($tab,$slices);
  10. echo $chart->ligne($tab,$slices);
  11. ?>

Pour être complet, sachez que ces API exposent encore d’autres graphiques comme vous le montre l’image ci-dessous, jettez un coup d’oeil sur http://code.google.com/intl/fr/apis/chart/, on peut toujours en avoir besoin…

image5

Vous trouverez toutes les sources et la classe chart.class.php dans le zip ci-dessous.

Télécharger googlechart.zip

Géolocalisation d’une IP via 3 API publiques

10 mai 2009 23 h 22 min

Dans l’article précédent (ici), vous aurez peut-être noté que la carte googlemaps pointait étrangement très près de chez vous. Ce n’était pas un hasard…
Je vous propose un script qui permet de géolocaliser la plupart des IP en France. Il n’a pas une valeur exhaustive mais il vous montrera les principes utiles. Tout comme un site de localisation des IPs, le principe de base est une base de donnée bien fournie. Mais plutôt que de la construire vous même, je vous propose plus simple avec un script d’une centaine de lignes qui va interroger 3 grosses base de données mise à disposition du public : celle de ripe.net, celle de frimousse.org et celle de maps.google.com. Au passage nous verrons comment interroger des bases via des API de 3 façons différentes.

RIPE est le Centre de coordination des Réseaux IP Européens. Il dispose d’une base contenant toutes les IP d’europe et fourni quelques informations sur celles-ci. Une API est mise à notre disposition, elle s’interroge via une connexion sur le port 43. La réponse est sous la forme suivante:

    inetnum: 90.20.40.0 - 90.20.40.255
    netname: IP2000-ADSL-BAS
    descr: BSORL157 Orléans Bloc 1
    country: FR
    admin-c: WITR1-RIPE
    tech-c: WITR1-RIPE …

c’est du texte à la ligne et chaque ligne sera concatenée dans la variable $source. Quelques entrées spécifiques nous interessent plus que d’autres, notamment la ligne qui contient “country:” nourira la variable $pays, et les lignes contenant “descr: ” et “address: ” qui seront concatenées dans $texte.

  1. <?php
  2. $server = "whois.ripe.net";
  3. $source = "";
  4. $pays = "";
  5. $entries = array("descr: ","address: ");
  6. $texte = "";
  7.  
  8. if($fp = fsockopen($server, 43, $errno, $errstr, 60))
  9.   {
  10.   fputs($fp,"$ip\r\n");
  11.   while(!feof($fp))
  12.     {
  13.     $line = fgets($fp,128);
  14.     $source .= $line;
  15.     if(($pays=="")&&(stristr($line,"country:")))
  16.          $pays = substr(trim(eregi_replace("country:","",$line)),0,2);
  17.     foreach($this->entries AS $entry) if(stristr($line,$entry))
  18.          $texte .= trim(substr($line,strpos($line,":")+1))."\n";
  19.     }
  20.   fclose($fp);
  21.   }
  22. ?>

La requete sur le port 43 s’obtient en ouvrant une socket PHP (avec “fsockopen”) et en y injectant l’IP demandée (avec “fputs”). La réponse qui arrive de manière synchrone est lue ligne par ligne (avec “fgets”).

Maintenant que nous avons une extration du log brut de RIPE, nous avons de quoi savoir si cette IP appartient à FREE ($texte contient “proxad”) ou a ORANGE ($texte contient “france telecom”). Ce sont ces deux groupes de télécom qui vont pouvoir être geolocalisés pleinement. Ils représentent tout de même la majorité des connexions en France.
Pour ORANGE, la ligne qui nous interesse est celle-ci: BSORL157 Orléans Bloc 1, on y voit clairement le nom de la ville, que nous allons mettre dans la variable $ville grâce à un REGEX (”^bs([a-z]{3}[0-9]{3}) (.*) bloc”). Depuis que Orange a refait ses déclarations à RIPE, on trouve toujours cette info à la première ligne de l’extraction $texte (première occurence de “desc:” dans la réponse RIPE).

  1. if(stristr($texte,"france telecom"))
  2.   {
  3.   $lignestexte = explode("\n",$texte);
  4.   $line = $lignestexte[0]; //première ligne
  5.   if(eregi("^bs([a-z]{3}[0-9]{3}) (.*) bloc",$line,$reg))
  6.     {
  7.     $ville = $reg[2];
  8.     }
  9.   }

Pour FREE, c’est un peu plus complexe, il nous faut trouver le nom du N.R.A. (Noeud de Raccordement Réseau: le boitier télécom ou le centre télécom auquel vous êtes raccordés près de chez vous). Dans la plupart des cas, le nom du N.R.A. figure sur le nom d’hôte de l’IP. Une adresse IP se transforme en nom avec l’instruction gethostbyaddr(). On obtient une chaine de la forme: did75-6-62-74-182-49.fbx.proxad.net, ce sont les 3 premières lettres et les deux chiffres suivant que nous allons retenir (did75). Dans certains cas particulier (FREE permettant de changer le nom de son IP), on trouvera le N.R.A. sur le log de RIPE à la troisième ligne de l’extraction $texte.

  1. if(stristr($texte,"proxad"))
  2. {
  3. $line = gethostbyaddr($ip);
  4. if(!eregi("^([a-z]{3}[0-9]{2})(.*)",$line))
  5.   {
  6.   $lignestexte = explode("\n",$texte);
  7.   $line = $lignestexte[2]; // troisième ligne
  8.   }
  9. if(eregi("^([a-z]{3}[0-9]{2})(.*)",$line,$reg))
  10.   {
  11.   $nra = $reg[1];
  12.   }
  13. }

Avec ce nom de N.R.A. nous allons interroger notre deuxième base de données. Il s’agit de l’API de frimousse.org, site regroupant les abonnés de FREE et leur donnant des infos sur le dégroupage de free. Cette API s’interroge en XML RPC en envoyant le N.R.A. et le nom de la fonction demandée (”getExchangeInfo”) par POST. La réponse est sous la forme suivante :

    <?xml version="1.0" encoding="iso-8859-1"?>
     <methodResponse>
      <params>
       <param>
        <value>
         <struct>
          <member>
           <name>zone</name>
           <value>
            <int>5</int>
           </value>
          </member> …

On reconnait là un format XML RPC, l’ancêtre de SOAP. Sachez que de nombreux WebServices utilisent ce format, il est conçu pour permettre à des structures de données complexes d’être transmises, exécutées et renvoyées très facilement.

  1. <?php
  2. $nra = "did75";
  3.  
  4. $url = "http://www.frimousse.org/outils/xmlrpc";
  5. $params = xmlrpc_encode_request("getExchangeInfo", array($nra));
  6. $request = stream_context_create(array("http" => array(
  7.         "method" => "POST",
  8.         "header" => "Content-Type: text/xml",
  9.         "content" => $params)));
  10. $file = file_get_contents($url, false, $request);
  11. $reponse = xmlrpc_decode($file);
  12.  
  13. $ville = $reponse["commune"];
  14. $departement = $reponse["departement"];
  15. ?>

Malgré le côté extrêmement verbeux du XML RPC, en PHP ça reste simple grâce à deux instructions “xmlrpc_encode_request” et “xmlrpc_decode”. Cette dernière transforme la réponse XML en un simple tableau contenant chaque valeur. Ainsi on retrouvera facilement la commune et le département.

Pour les autres fournisseurs d’accès, on obtient une information inégale. Certains passent toutes leurs connexions par les mêmes IPs, et dans ce cas RIPE ne donne que l’adresse du siège social (AOL, Numéricâble, Neuf….), d’autres encore donnent pratiquement l’adresse exacte. C’est quitte ou double. Pour toutes les IP n’appartenant pas à FREE ou à ORANGE, nous allons chercher dans chaque ligne de l’extract de la réponse RIPE quelque chose qui s’apparente à un code postal (5chiffre et nom de ville) avec le REGEX suivant :

  1. foreach(explode("\n",$texte) AS $line)
  2.   {     
  3.   if(eregi("([0-9]{4,5}) ([a-z ]*)",$line,$reg))
  4.     {
  5.     $ville = $reg[2];
  6.     $departement = substr($reg[1],0,2);
  7.     break;
  8.     }
  9.   }

Voilà. A ce stade nous avons un nom de ville pour la plupart des IP françaises et un numéro de département pour certaines. Ce nom est incomplet ou mal formaté, aussi nous allons utiliser l’API de googlemaps pour le formater correctement, en récuperrant au passage le nom de la région et du département.
On interroge cette API en requetant une url et en passant les paramètres en GET, on obtient une réponse sous la forme:

    { “name”: “Boulogne Billancourt 92″,
      ”Status”: {
        ”code”: 200,
        ”request”: “geocode”
      },…

Il s’agit là d’une structure JSON très proche de la structure de sérialisation d’un objet. Comme expliqué dans l’article précédent, il vous faut un compte google et générer la clef d’API qui vous est propre pour utiliser l’API. Pour ce faire, rendez vous sur http://code.google.com/intl/fr/apis/maps/signup.html. Une fois que vous aurez cette clef, vous pourrez l’ajouter dans le code ci-dessous à la place de la mention “00000votreclef00000″. Cette clef est valable pour un nom de domaine donné. Notez que pour le developement, elle n’est pas nécéssaire.

  1. $url = "http://maps.google.com/maps/geo?q=".
  2.     urlencode($ville." ".$departement).
  3.     "&output=json&oe=utf8&sensor=false&key=00000votreclef00000";
  4.  
  5. $obj = $pos->Placemark[0]->AddressDetails->Country->AdministrativeArea;
  6. $region = utf8_decode($obj->AdministrativeAreaName);
  7. $dept = utf8_decode($obj->SubAdministrativeArea->SubAdministrativeAreaName);
  8. $ville = utf8_decode($obj->SubAdministrativeArea->Locality->LocalityName);

Nous passons les arguments ($ville et $département) directement dans l’url. L’url est lue grâce à la fonction file_get_contents et PHP décode le JSON très simplement en le transformant en un objet $pos contenant tous les éléments de la réponse. Enfin, le JSON est encodé en UTF8, aussi nous avons besoin de le décoder (”utf8_decode”) pour lire en clair les accents français.

Vous trouverez un exemple ainsi que toutes les sources et notamment la classe whois.class.php regroupant l’ensemble de ces codes dans le zip ci-dessous:

Télécharger whois.zip

Intégrer une carte Googlemaps

9 mai 2009 14 h 28 min

Si vous n’avez pas envie de passer 2 heures dans la doc des API de google pour reproduire le moteur de recherche googlemaps, je vous propose 3 minutes de lecture et un robot tout fait.
Il s’agit d’un script PHP qui recherche une localité et l’affiche avec googlemaps. On passe la phrase à chercher simplement par un GET (googlemaps.php?x=paris 12e 75), ainsi la carte peut parfaitement s’instancier dans une iframe, n’importe où sur votre site.
Démonstration :

Nous allons d’abord voir l’implémentation de la carte google en html/javascript. Avant tout, vous devez savoir que l’API googlemaps est une application autorisée, il vous faut un compte google et générer la clef d’API qui vous est propre. Pour ce faire, rendez vous sur http://code.google.com/intl/fr/apis/maps/signup.html. Une fois que vous aurez cette clef, vous pourrez l’ajouter dans le code ci-dessous à la place de la mention “00000votreclef00000″. Cette clef est valable pour un nom de domaine donné. Notez que pour le developement, elle n’est pas nécéssaire.

  1. <html>
  2.   <head>
  3.   <script src="http://maps.google.com/maps?file=api&v=2&key=00000votreclef00000" type="text/javascript"></script>
  4.   <script type="text/javascript">
  5.     function load() {
  6.       if(GBrowserIsCompatible()) {
  7.         var map = new GMap2(document.getElementById("map"));
  8.         var point = new GLatLng(0, 0);
  9.         map.setCenter(point, 14);
  10.         map.setMapType(G_HYBRID_MAP);
  11.         map.openInfoWindowHtml(point, "info1<br>info2<br>");
  12.         map.addControl(new GLargeMapControl());
  13.         map.addControl(new GMapTypeControl());         
  14.         }
  15.      }
  16.   </script>
  17.   <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  18.   </head>
  19.   <body onload="load()" onunload="GUnload()">
  20.     <div id="map" style="width:600px;height:400px"></div>
  21.   </body>
  22. </html>

Pas de commentaires sur l’implémentation, un rapide copier/coller vous donnera entière satisfaction ;-) Comme vous pouvez le voir, outre votre DIV “map”, tout se passe dans la fonction “load” du header. Deux lignes en particulier vont être modifiées par le code php :

  1. var point = new GLatLng(0, 0);

C’est ici que l’on met les coordonnées géographiques du lieu à pointer. Nous verrons plus loin comment obtenir ces coordonnées, en attendant ce code pointe sur une latitude 0° et une longitude 0°.

  1. map.openInfoWindowHtml(point, "info1<br>info2<br>");

C’est ici que l’on va ajouter le texte de l’info bulle. Ce champ accepte du code html, aussi vous pourrez y mettre des liens, des images et plus encore.

Passons maintenant au code PHP, c’est lui qui va transformer une recherche textuelle en coordonnées. Pour ce faire, nous allons utiliser l’API de géolocalisation de google. Il nous suffit d’interroger une url en passant quelques paramètres (dont votre clef d’API) et nous obtenons la réponse au format csv, xml ou json. J’ai choisi JSON parce que c’est mon préféré… alors quand j’ai le choix…

  1. <?php
  2. $msg = "paris 12e 75";
  3. $url = "http://maps.google.com/maps/geo?q=".urlencode($msg).
  4. "&output=json&oe=utf8&sensor=false&key=00000votreclef00000";
  5.  
  6. $x = $pos->Placemark[0]->Point->coordinates[1];
  7. $y = $pos->Placemark[0]->Point->coordinates[0];
  8. $info = $pos->Placemark[0]->AddressDetails->Country->AdministrativeArea ->SubAdministrativeArea->Locality->LocalityName;
  9. ?>

La lecture de l’url distante et le décodage du JSON sont extrèmement simple avec PHP. Dans un précedent article (ici) j’avais parlé de “file_get_contents” pour lire des fichiers, cette commande fonctionne aussi bien sur des fichiers distants.

On se retrouve ainsi avec un objet $pos qui contient toutes les informations de la réponse de l’API. On séléctionne celles qui nous interessent ($x, $y, $info) et on remplace dynamiquement les valeurs de la page d’intégration vue plus haut : la latitude, la longitude et le texte de l’info bulle.

Vous trouverez toutes les sources prêtes à fonctionner dans le zip téléchargeable ci-dessous. Avec ce script vous pourrez visualiser n’importe quelle adresse incomplète… google trouve toujours !

Télécharger googlemaps.zip

L’ajax le plus simple au monde

3 mai 2009 15 h 36 min

Comment ajouter des blocs de texte, des images ou déclencher des scripts php dans une page sans avoir à la recharger ? Réponse: AJAX. Pour l’implémenter, on peut utiliser des frameworks spécialisés, des bibliothèques javascript de 1000 lignes… le choix est vaste, et l’implémentation peut s’averer longue.

Si vous n’avez que 5 minutes pour ce job, je vous propose l’ajax le plus simple au monde. Il fait 10 lignes et s’insère parfaitement dans tous les projets possibles.

Je n’ai gardé que le strict nécéssaire, et pourtant il reste compatible avec tous les navigateurs et est largement suffisant dans la plupart des cas. Pour preuve de compatibilité et de facilité d’intégration, je l’ai ajouté dans cette page: cliquez ici pour ajouter un bloc de texte à ce paragraphe et hop !

Le javascript “ajax.js” :

  1. function ajax(div,url)
  2.   {
  3.   document.getElementById(div).innerHTML = "loading…";
  4.   var req = null;
  5.   req=(window.XMLHttpRequest) ?
  6.         new XMLHttpRequest():
  7.         new ActiveXObject(Msxml2.XMLHTTP);
  8.   req.onreadystatechange = function() {
  9.         if((req.readyState == 4)&&(req.status == 200))
  10.                 document.getElementById(div).innerHTML = req.responseText;
  11.         }
  12.   var data = null;
  13.   req.open("POST", url, true);
  14.   req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  15.   req.send(data);
  16.   }

Lorsque la fonction est appelée, elle va instancier un objet XmlHttpRequest (ou éventuellement un activeX pour les vieux Internet Explorer) qui va se charger du boulot. Tout comme un mini-navigateur web indépendant, cet objet va requêter un fichier du serveur (ici test.php), puis attendre la réponse. L’évenement “onreadystatechange” de l’objet se déclenche lorsque la réponse arrive. Dans les fait, cet évenement est appellé plusieurs fois avant que la réponse n’arrive réellement, aussi nous avons besoin de tester un code “4″ (page bien chargée) puis un code http status “200″ (ok). Une fois ces deux conditions réunies, le contenu de la réponse de la page requêtée est disponible dans la variable “responseText” de l’objet. Le javascript le pousse dans le div via la commande innerHTML. Rien de plus simple.

L’appel dans la page web :

  1. <html>
  2.   <head>
  3.     <script src="ajax.js" type="text/javascript"></script>
  4.   </head>
  5.   <body>
  6.     <div id="container">
  7.       <a href="javascript:ajax(’container’,'testajax.php?x=2′)">cliquez ici</a>
  8.     </div>
  9.   </body>
  10. </html>

Vous trouverez toutes les sources et un exemple de son intégration dans le zip téléchargeable ci-dessous. Avec cet ajax, vous pourrez interroger n’importe quel script php en lui passant des paramètres, c’est une bonne introduction aux techniques de l’ajax.
Pour la suite, à vous de jouer… Ajax ne se limite pas à l’utilisation de XmlHttpRequest !

Télécharger ajax.zip

Création de zip à la volée en PHP

1 mai 2009 15 h 47 min

Bon nombre de processus métier nécéssitent, après la génération de fichiers, de les zipper de façon à fournir un seul fichier téléchargeable. Il y a mille manières d’y arriver… de l’utilisation en ligne de commande d’un programme tiers, à l’utilisation de frameworks complexes.

Je vous propose ce qui me semble le plus simple dans le domaine.

Une seule classe php de 100 lignes crée en suivant pas à pas la documentation officielle de PK consultable ici : http://www.pkware.com/appnote.txt. Cette classe permet d’intégrer la génération à la volée directement dans votre code php. Chaque fichier est ajouté à l’archive par la méthode add() puis on génère le contenu du fichier zip à télécharger avec la methode dump(). C’est tout.

  1. <?php
  2. include "zip.class.php";
  3. $zip = new zip();
  4.  
  5. $zip->add(file_get_contents("montexte.txt"),"montexte.txt");
  6. $zip->add(file_get_contents("monimage.jpg"),"monimage.jpg");
  7.  
  8. header("Content-Type: application/octet-stream");
  9. header("Content-Disposition: attachment; filename=monzip.zip");
  10. echo $zip->dump();
  11. ?>

Dans cet exemple, on remarque que le contenu du fichier zip est envoyé au navigateur avec un header “attachment” (pièce jointe). Ceci a pour effet de proposer le téléchargement sans affichage de page. Ainsi vous il suffira de mettre un lien internet sur votre page vers le fichier php pour qu’en cliquant dessus, sans changer de page, votre navigateur vous télécharge le fichier.
Vous trouverez un exemple ainsi que toutes les sources et notamment la classe zip.class.php dans le zip ci-dessous:

Télécharger zip.zip

Filtre d’affinage d’image en PHP

25 avril 2009 23 h 15 min

sharpenIl y a quelques années je travaillais pour un site de vente privées, chaque semaine il fallait rentrer une nouvelle gamme de produits soit près de 200 photos à traiter … Honnêtement, je pense avoir fait les 3 première photos avec photoshop puis j’ai confié ce boulot à un bot.
Or lorsqu’on redimensionne une image avec PHP on obtient souvent un résultat flou, l’image semble de moins bonne qualité.
Il ne faut pas perdre de vue qu’en termes de communication, de belles photos sous entendent de beaux produits et de beaux produits sont compatibles avec l’image que le client se fait de lui… bref par un effet narcissique latent de masse, les ventes augmentent! Dans un marché concurrentiel ça peut faire toute la différence.
Pour compenser cet effet de flou, le travail à la main avec un logiciel d’imagerie semble nécessaire : on augmente le nombre de couleurs de l’image avant de redimensionner, de recadrer, puis on passe un filtre photoshop d’affinage (sharpen). C’est à ce prix que l’on crée de jolies images web qui malgré leur taille montrent les produits sous leur meilleur jour… par contre avec 200 images, une semaine après, le boulot n’est toujours pas fini.

Mais il y a plus simple et surtout plus rapide. Grâce à la fonction “imageconvolution”, vous allez pouvoir automatiser la génération des images small et medium à partir des originaux sans perte de qualité.

  1. <?php
  2. $i = "image0.jpg";
  3.  
  4. //lecture de l’image $i
  5.  
  6. //affinage
  7. $matrice = array(array(-1,-1,-1),array(-1,16,-1),array(-1,-1,-1));
  8. $diviseur = 8;
  9. $offset = 0;
  10. imageconvolution($img, $matrice, $diviseur, $offset);
  11.  
  12. //affichage     
  13. header("Content-type: image/jpeg");
  14. imagejpeg($img,"",100);
  15. ?>

Pour comprendre comment agit cette fonction, il faut d’abord se figurer une image comme une série de pixels. Chaque pixel possède 3 coordonnées: la quantité de rouge (R), la quantité de vert (V) enfin celle de bleu (B). Ceci définit une matrice à 3 dimentions. Pour appliquer un filtre sur notre image, nous allons multiplier chaque matrice “pixel” par une matrice “filtre”. Les valeurs de la matrice “filtre” définissent l’effet désiré.
Prennons un exemple simple: le filtre noir et blanc. Chaque pixel doit perdre sa couleur en conservant sa luminosité. On obtient cet effet en faisant la moyenne des valeurs RVB et en appliquant cette moyenne à chaque canal, ainsi un pixel rouge (210,0,0) deviendra gris (70,70,70). Bon là c’est simple parceque c’est le produit d’une matrice 3×1. Pour l’affinage il faut une matrice 3×3, celle-ci exactement :

  1. $matrice = array(array(-1,-1,-1),array(-1,16,-1),array(-1,-1,-1));

Par la suite vous pourrez jouer avec ces valeurs, vous découvrirez différents filtres photo : image surexposée, flou gaussien…etc. Sachez tout de même que de nombreux autres filtres sont disponibles avec la fonction PHP5 “imagefilter“.

Il nous reste à redimensionner l’image originelle (celle d’un appareil numérique) en miniature pour l’affichage web. Les vignettes ont une taille unique quel que soit le format de la photo (paysage ou portrait), parce qu’elles s’affichent dans la même page produit dynamique. Il nous faut donc un redimensionnement intelligent avec recadrage. Dans l’exemple ci dessous j’ai choisi le format 7/6 en 280×240 pixels.

  1. <?php
  2. $i = "image1.jpg";
  3. $xl = 280;
  4. $yl = 240;
  5.                
  6. //taille src
  7. $size = getimagesize($i);
  8. $rapport = $size[0]/$size[1];
  9.                
  10. if ($rapport > (7/6))
  11.         { //format paysage par rapport au 7/6eme
  12.         $xl_src = $size[1]*(7/6);
  13.         $yl_src = $size[1];
  14.         $x_src = ($size[0]-$xl_src)/2;
  15.         $y_src = 0;                    
  16.                        
  17.         } else { //format portrait par rapport au 7/6eme
  18.         $xl_src = $size[0];
  19.         $yl_src = $size[0]*(6/7);
  20.         $x_src = 0;
  21.         $y_src = ($size[1]-$yl_src)/2;                 
  22.         }
  23.                        
  24. //crea images src et dst
  25. $image = imagecreatetruecolor($xl,$yl);
  26. $image_src = imagecreatefromjpeg($i);
  27.                
  28. //redim et recadrage
  29. imagecopyresampled($image,$image_src,0,0,$x_src,$y_src,$xl,$yl,$xl_src,$yl_src);
  30. imagedestroy($image_src);
  31.                
  32. //affinage
  33. $matrice = array(array(-1,-1,-1),array(-1,16,-1),array(-1,-1,-1));
  34. $diviseur = 8;
  35. $offset = 0;
  36. imageconvolution($image, $matrice, $diviseur, $offset);
  37.                
  38. //enregistre   
  39. imagejpeg($image,"small_".$i,100);
  40.  
  41. //affiche
  42. header("Content-type: image/jpeg");
  43. imagejpeg($image,"",100);
  44. imagedestroy($image);
  45. ?>

Vous trouverez le programme complet (redimension, recadrage et affinage) et des exemples d’utilsation dans le zip téléchargeable ci-dessous. Pour mettre ne oeuvre ce script vous devez activer l’extension GD de PHP. La fonction magique imageconvolution n’est pas disponible sur tous les serveurs (notamment pour les serveur partagés), c’est pourquoi vous trouverez aussi dans ce zip la fonction réecrite en php, c’est celle que j’utilisais lorsque imageconvolution n’existait pas encore.

Télécharger sharpen.zip

Générer des images avec une police TrueType

24 avril 2009 23 h 32 min

truetypeLa plupart des clients soucieux de leur image sur internet veulent que l’on respecte scrupuleusement leur charte graphique. Bien souvent celle-ci possède des titres de catégories ou de produits avec une police TrueType spéciale, qui évidement n’est pas supportée par tous les navigateurs. Ceci force à une création fastidieuse d’images sous photoshop. Par expérience, un site de vente classique compte facilement 300 de ces images. C’est beaucoup de travail, mais pire que ça, c’est un travail répétitif. Tout bon développeur vous dira qu’un travail répétitif c’est bon pour des robots ;-)
Alors si vous n’arrivez pas à faire comprendre à votre client que les textes dans les images ne sont pas indexés par les moteurs de recherche, je vous propose un robot qui fera ces images à votre place.

Il y a encore peu de temps, la librairie graphique GD de PHP ne fournissait que 4 polices basiques. Pour travailler avec une police TrueType il fallait la transformer en police GD puis l’importer dans le code… il y a aujourd’hui plus simple grâce à la fonction imageTTFText.

  1. <?php
  2. $msg = "message test";
  3. $police = "truetype.ttf";
  4. $img = imagecreatetruecolor(220, 20);
  5.  
  6. $blanc = imagecolorallocate($img, 255,255,255);
  7. $gris = imagecolorallocate($img, 128, 128, 128);
  8. $noir = imagecolorallocate($img, 54, 54, 54);
  9. imagefill($img, 0, 0, $blanc);
  10.  
  11. imageTTFText($img,14,0,0,15,$noir,$police,$msg);
  12.  
  13. header("Content-type: image/png");
  14. imagepng($img);
  15. imagedestroy($img);
  16. ?>

Voyons comment se compose ce code. La première inquiétude du client est la qualité graphique. Beaucoup ont du mal à croire qu’un robot donne une qualité équivalente au travail humain sous photoshop. Il faut savoir que la qualité d’image dépend de la résolution et du nombre de couleurs par points. La résolution étant basse sur écran, nous allons jouer sur le nombre de couleurs. Ceci permettra un meilleur lissage des courbes afin d’éviter les effet d’ “escalier”.

  1. $img = imagecreatetruecolor(220, 20);

Nous voilà donc en 16 millions de couleurs. Après avoir défini les couleurs utilisables et rempli le fond de l’image en blanc, nous allons écrire le texte $msg avec la police $police. Cette dernière variable est le chemin vers le fichier TTF. On trouve bon nombre de polices dans le répertoire d’un windows : C:\WINDOWS\Fonts\ ou encore sur http://www.dafont.com par exemple.

  1. imageTTFText($img,14,0,0,15,$noir,$police,$msg);

A partir de ce bout de code, vous pourrez ajouter la gestion du retour à la ligne et l’enregistrement de l’image sur le disque de façon à éviter de faire appel à GD en permanence. La génération de toutes les images d’un site prend entre 5 et 10 secondes. Je vous propose un code complet “toute option” avec une police fournie dans le zip ci-dessous. Pour mettre ne oeuvre ce script vous devez activer l’extension GD de PHP. Il ne vous restera plus qu’à relier le code à la base de donnée du site. Avec une requête SQL vous devriez recuperrer tous les textes… comme ça vous n’aurez même pas à les taper !

Télécharger truetype.zip