Au cours de mes recherches dans les profondeurs de la documentation PHP, il m'arrive assez fréquemment de rencontrer des anomalies diverses comme des prototypes de fonction incomplets, de grossières fautes d'orthographe ou des phrases à peine plus compréhensibles que celles générées par un traducteur automatique. Autant de détails dont l'importance peut paraître insignifiante mais qui contribuent à décrédibiliser le sérieux du langage. Et pourtant, en ingrat développeur que je suis, j'avoue n'avoir jamais pris la peine d'informer la communauté PHP de ces défauts, sombrant de jours en jours en peu plus loin dans le péché.
Jusqu'au jour où ma bonne conscience se réveilla, probablement illuminée par une intervention de notre bienveillant Dieu Rasmus, qui de tout là-haut ramène ses fidèles développeurs dans le droit chemin. Au cours de cette divine apparition, il me pardonna puis me rappela l'existence de son outil de rapport de bugs et insista sur le fait que les problèmes liés à la documentation y sont bien entendu acceptés, car tout bug est égal à son prochain.
Je me suis donc depuis lors acharné à signaler le moindre problème rencontré, et c'est ainsi plus d'une centaine de corrections qui ont été envoyées, approuvées puis appliquées à notre manuel chéri. Sans vouloir imiter Frédéric Hardy et ses Nouvelles du front, voici l'inventaire des modifications apportées aux prototypes de nombreuses fonctions :
#54747, #54748, #54749, #54750, #54751, #54752, #54753, #54754, #54755, #54756, #54757, #54761, #54762, #54763, #54768, #54770, #54771, #54772, #54773, #54779, #54780, #54781, #54782, #54783, #54784, #54785, #54786, #54787, #54788, #54789, #54790, #54793, #54794, #54795, #54796, #54797, #54800
Celles-ci, plus anecdotiques, concernent essentiellement des corrections orthographiques dans la documentation francophone et des erreurs relevées dans les scripts d'exemple.
#54806, #54807, #54808, #54809, #54810, #54811, #54812, #54813, #54814, #54815, #54816, #54817, #54818, #54819, #54820, #54822, #54823, #54825, #54826, #54827, #54828, #54829, #54830, #54833, #54834, #54835, #54836, #54837, #54838, #54839, #54840, #54841, #54845, #54846, #54847, #54848, #54849, #54850, #54853, #54854, #54855, #54856, #54857, #54858, #54868, #54869, #54872, #54873, #54874, #54875, #54876, #54877, #54878, #54879, #54880, #54881, #54882, #54883, #54884, #54885, #54886, #54887, #54889, #54890, #54891
Une démarche apparemment trop peu fréquente, puisqu'elle a suscité la curiosité de plusieurs membres éminents de la Documentation Team comme Peter Cowburn, Richard Quadling ou Pierrick Charron, visiblement soucieux de mon intégrité humaine. Je ne peux donc qu'encourager chaque développeur à participer, dès qu'il en a l'opportunité, à l'amélioration de ce formidable outil dont nous profitons tous égoïstement. Un simple acte citoyen suffit à témoigner de la reconnaissance envers les nombreux anonymes bénévoles dont les contributions nous furent, un jour ou l'autre, d'une aide plus que précieuse.
Que ce soit lors de mes développements ou dans la vie de tous les jours, il m'arrive fréquemment de vouloir trouver les coordonnées GPS d'un lieu bien précis, en général à partir de son adresse postale, mais aussi parfois en préférant le localiser sur une carte s'il s'agit par exemple d'un endroit isolé au beau milieu de la savane.
Vu que ni Google Maps, Bing ou Mappy ne nous autorisent à faire ça simplement, j'ai décidé de créer une modeste application de géocodage répondant à mes besoins.
Basée sur l'API Javascript V3 de Google Maps, elle permet d'obtenir la latitude et la longitude d'une adresse postale où d'un marqueur pouvant être déplacé sur la carte. L'opération inverse (reverse geocoding) est également possible, dévoilant donc l'adresse d'un lieu désigné par ses coordonnées GPS.
J'invite ceux qui seraient intéressés par la manière d'intégrer ces services Google à jetter un oeil au code qui se résume en quelques lignes de Javascript d'une étonnante simplicité, et ce grâce à leur API formidablement bien pensée. Pour tout complément d'information, la documentation est tout aussi complète.
Demo
Dans la continuité du billet très intéressant de Vincent Battaglia consacré à l'utilité du système de numération hexatridécimal (soit en base 36) dans l'algorithme d'un raccourcisseur d'URL, j'ai voulu savoir si utiliser d'autres systèmes de numération encore plus vastes, comme le base 62 évoqué à la fin de son article, pouvait se faire avec autant de facilité, ce qui permettrait de générer des URL encore plus courtes à moindre effort.
Comme il l'a déjà bien expliqué, le principal critère qui influencera le choix entre l'une de ces deux bases est certainement le souci de la sensibilité à la casse. En effet, la base 36 se concentre sur le système alphanumérique minuscule tandis que la base 62 inclut aussi les caractères majuscules.
D'autre part, il va évidemment de soi qu'au plus la base est élevée (autrement dit au plus l'éventail de symboles possibles est vaste), au plus un nombre peut-être représenté de manière concise. Dans le cadre des raccourcisseurs d'URL, c'est donc aussi un critère primordial. Non seulement pour l'économie de caractères que cela implique, mais aussi pour le nombre d'identifiants que le service pourra gérer, et donc le nombre d'URL qu'il pourra prétendre offrir. Bien que la plupart des services actuels comme TinyURL, bit.ly ou goo.gl semblent s'être limités à une base 62, d'autres sont plus aventureux, comme par exemple le service (belge, disons-le !) http://ui.tl qui affirme être le plus concis du marché par l'utilisation d'une base 163, prenant ainsi le risque d'inclure des caractères accentués dans ses résultats.
Lors de la mise en pratique, je fus tout d'abord surpris de découvrir que la fonction base_convert de PHP ne supporte pas la conversion d'un nombre en base 62. L'occasion étant trop tentante, j'en ai profité pour réécrire une fonction sensiblement identique permettant cette fois de jongler entre différentes bases arbitraires avec une totale liberté.
Par la même occasion, j'y ai ajouté au travers d'un dernier paramètre optionnel la possibilité de spécifier le jeu de caractères à prendre en compte lors de la conversion. J'y vois plusieurs cas d'utilisation concrets, comme par exemple :
- Pouvoir déjouer le caractère prédictif inhérent à ce genre de conversion, en spécifiant un alphabet dont l'ordre aura été préalablement altéré.
- Pouvoir générer des valeurs "user-friendly", en omettant par exemple les caractères susceptibles de prêter à confusion lors de la lecture, tels o,O,0,1,l, etc...
- Pouvoir définir son propre jeu de caractères, qui pourrait par exemple inclure des caractères spéciaux.
/**
* Convertit un nombre entre différentes bases.
*
* @param string $number Le nombre à convertir
* @param int $frombase La base du nombre
* @param int $tobase La base dans laquelle on doit le convertir
* @param string $map Eventuellement, l'alphabet à utiliser
* @return string|false Le nombre converti ou FALSE en cas d'erreur
* @author Geoffray Warnants
*/
function base_to($number, $frombase, $tobase, $map=false)
{
if ($frombase<2 || ($tobase==0 && ($tobase=strlen($map))<2) || $tobase<2) {
return false;
}
if (!$map) {
$map = implode('',array_merge(range(0,9),range('a','z'),range('A','Z')));
}
// conversion en base 10 si nécessaire
if ($frombase != 10) {
$number = ($frombase <= 16) ? strtolower($number) : (string)$number;
$map_base = substr($map,0,$frombase);
$decimal = 0;
for ($i=0, $n=strlen($number); $i<$n; $i++) {
$decimal += strpos($map_base,$number[$i]) * pow($frombase,($n-$i-1));
}
} else {
$decimal = $number;
}
// conversion en $tobase si nécessaire
if ($tobase != 10) {
$map_base = substr($map,0,$tobase);
$tobase = strlen($map_base);
$result = '';
while ($decimal >= $tobase) {
$result = $map_base[$decimal%$tobase].$result;
$decimal /= $tobase;
}
return $map_base[$decimal].$result;
}
return $decimal;
}
Pour se faire une idée de l'allure que peuvent avoir les valeurs retournées par cette fonction, voici à titre indicatif les résultats d'une série de conversions entre différentes bases. En ce qui concerne la base 163, j'ai considéré le jeu de caractères revendiqué par le service http://ui.tl.
| base 10 | base 16 | base 36 | base 62 | base 163 |
|---|---|---|---|---|
| 100 | 64 | 2s | 1C | Æ |
| 1000 | 3e8 | rs | g8 | 'Ú |
| 10000 | 2710 | 7ps | 2Bi | e% |
| 100000 | 186a0 | 255s | q0U | %}I |
| 1000000 | f4240 | lfls | 4c92 | OIÌ |
| 10000000 | 989680 | 5yc1s | FXsk | $6Ô@ |
| 100000000 | 5f5e100 | 1njchs | 6LAze | @gÉ1 |
| 1000000000 | 3b9aca00 | gjdgxs | 15FTGg | #5Û#. |
| 10000000000 | 2540be400 | 4ldqpdk | aUKYOs | 6^/5Ç |
S'il fallait citer un plugin sans lequel le développement sous Notepad++ deviendrait une calamité, mon choix se porterait très certainement sur l'indispensable Function List, un explorateur de code qui vient se greffer à l'éditeur et ainsi l'enrichir d'un outil par défaut absent mais pourtant très prisé des développeurs habitués aux IDE tels Eclipse ou NetBeans.
Bien que l'idée paraisse alléchante, on découvre très vite que dans un environnement PHP orienté-objet, l'intérêt de ce plugin se révèle finalement assez limité, surtout sachant avec quelle précision les IDE actuels peuvent synthétiser la structure d'une classe. Function List se contente lui de lister les fonctions et méthodes de manière dépouillée et surtout sans aucune prise en compte des caractéristiques orientées-objet tels la portée des méthodes, leur niveau de visibilité, les variables membres, les constantes de classes, etc...
J'ai cependant découvert que la configuration par défaut du plugin n'est pas du tout prévue pour exploiter pleinement les possibilités offertes. En effet, le plugin reconnait les différentes structures de n'importe quel langage grâce à une collection d'expressions régulières parfaitement configurables. Celles qui concernent la syntaxe de PHP sont effectivement réduites à leur strict minimum.
Je me suis alors attardé à réécrire une configuration plus adaptée au développement orienté-objet en PHP. Inspiré par l'explorateur de code d'Eclipse, je suis arrivé au résultat suivant :
Voilà donc un lifting rajeunissant qui ne dépaysera pas trop les utilisateurs d'Eclipse pour qui les icônes choisies sont déjà familières. Pour les autres, ou à titre de rappel, en voici la signification :
Pour l'avoir adoptée depuis quelques temps, je trouve cette configuration plus agréable, même si elle provoque un effet désagréable inhérent aux limitations actuelles du plugin (dans sa version 2.1) : Le tri ne s'applique plus sur l'ensemble de la liste mais séparément sur chaque groupe d'éléments, ce qui est parfois déstabilisant. Si un courageux se sent d'attaque, le code du plugin est en open source (C++) ;-)
Dernière remarque pour ceux qui voudraient améliorer cette configuration, j'ai pu remarquer que le système d'interprétation des expressions régulières se comporte bizarrement. Il semblerait que ce soit une limitation de Scintilla (le composant sur lequel le plugin est basé) qui ne permette malheureusement pas de tirer profit de la pleine puissance des expressions régulières. Ceci alourdi l'écriture des règles et restreint les possibilités.
Pour installer cette configuration, copiez les fichiers FunctionListRules.xml et php.bmp dans le répertoire %APPDATA%/Notepad++/plugins/config ou %INSTALL_DIR%/plugins/Config selon que vous ayez choisi ou non d'utiliser %%APPDATA% lors de l'installation de l'éditeur. Attention que ceci va restaurer les règles par défaut pour tous les autres langages. Pour conserver vos éventuelles personnalisations, ne copiez que le noeud <Language name="PHP"> du fichier XML.
Téléchargement
La librairie GD forme aujourd'hui avec PHP un couple apprécié des développeurs web. Elle offre en effet un accès d'une grande simplicité aux traitements d'images, ces opérations obscures qu'on sait truffées de formules mathématiques et autres transformations matricielles, et qu'on devine par conséquent probablement gourmandes en ressources matérielles. Mais sait-on exactement jusqu'à quel point ? Pour ma part non, jusqu'à aujourd'hui...
Le graphique ci-dessous représente la quantité de mémoire vive consommée par un script lors de la création d'une ressource GD en fonction des dimensions de l'image traitée (ou plutôt de sa définition pour être exact)
Comme on pouvait s'en douter, la mémoire requise est proportionnelle au nombre de couleurs ainsi qu'aux dimensions de l'image. On peut donc envisager qu'un fichier suffisamment imposant puisse saturer complètement la mémoire libre, celle-ci n'étant pas illimitée. Il en résultera alors un arrêt brutal du script causé par une erreur fatale (Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes)). Un sombre scénario qui, pour pouvoir être évité, doit d'abord être anticipé.
L'objectif de cette étude est donc d'estimer les dimensions maximales approximatives acceptables selon la configuration mémoire en vigueur. J'insiste sur le caractère approximatif car j'ai pu constater qu'à définition égale, la mémoire occupée par une ressource GD dépend aussi de ses proportions. Ainsi une image au format "portrait" (plus haute que large) risque de nécessiter d'avantage de mémoire que la même au format "paysage". Heureusement cette différence ne concerne de manière significative que des images étirées à l'extrême.
Voici donc les dimensions maximales estimées pour des images au format 4/3 :
| memory_limit | imagecreatetruecolor | imagecreate |
|---|---|---|
| 64M | 13 Mpx (~4162x3123) | 32 Mpx (~6532x4899) |
| 128M | 26 Mpx (~5889x4415) | 65 Mpx (~9308x6983) |
| 256M | 53 Mpx (~8406x6305) | 128 Mpx (~13064x9798) |
| 512M | 106 Mpx (~11887x8917) | 262 Mpx (~18689x14019) |
| 1G | 201 Mpx (~16372x12277) | 514 Mpx (~26179x19634) |
Ces valeurs permettent alors d'instaurer des vérifications adaptées aux contraintes mémoire en vigueur. Par exemple :
<?php
ini_set('memory_limit', '64M');
$size = getimagesize($filename);
if ($size===false || $size[0] > 4000 || $size[1] > 3000) {
throw new Exception('Image too large');
}
$rc = imagecreatetruecolor($filename);
Notons ici que le choix de la fonction getimagesize n'est pas anodin : elle permet d'obtenir les dimensions du fichier sans le charger entièrement en mémoire, ce qui n'est pas le cas par exemple de ses homologues imagesx et imagesy qui risquent de provoquer la saturation mémoire avant même d'avoir pu s'en prémunir...
Conclusion
La consommation mémoire des ressources GD n'est finalement pas si excessive. Les configurations standards permettent déjà de manipuler des images importantes sans soucis. Evidemment, je ne vous apprends rien en répétant que tout fichier externe doit être considéré comme une source de données potentiellement à risque, ce qui est d'autant plus vrai pour les images reçues par exemple au travers d'un formulaire d'upload, et qu'elles doivent donc toujours faire l'objet d'un contrôle minutieux avant d'être manipulées. Car une gestion correcte de la mémoire est avant tout du bon ressort du programmeur.
<<< Articles plus récents | Articles plus anciens >>>
