Facebook LinkedIn SourceForge Twitter RSS LastFM
logologo

Réimplémenter la fonction file_put_contents() en PHP

Geoffray Warnants|26/08/2013|62 commentaires

J'en convient, l'ide de vouloir faire pareille chose peut paratre farfelue. Et pourtant ! Quelle ne ft pas ma surprise, lorsque lors d'une intervention sur une application dont on m'avait innocemment cach les caractristiques de l'hbergement, je me suis rapidement retrouv face l'erreur :

Fatal error: Call to undefined function: file_put_contents()

Et pareil constat avec sa consoeur :

Fatal error: Call to undefined function: file_get_contents()

Je crus d'abord une farce. Puis une restriction volontaire de l'hbergeur via la directive disable_functions, ne cessant nanmoins de me demander quelle obscure raison pouvait motiver cet acte de pur sadisme. Mais un rapide phpinfo() me rvla bien vite la triste ralit de la situation : j'tais l sur un antdiluvien serveur PHP 4.2, et ces fonctions aujourd'hui combien familires n'existaient tout simplement pas encore dans cette antique version du langage parue en 2002 !

Mme si les statistiques mondiales collectes par w3techs.com (aot 2013) permettent d'estimer qu'aujourd'hui moins de 3% des hbergements PHP fonctionnent encore en PHP 4 et que ce chiffre tend lentement vers 0 dans une longue agonie, voici malgr tout un patch permettant de rendre vie ce couple de fonctions bien utiles. Bien conscient du peu d'intrt qu'il pourra susciter, je le ddie spcialement au prochain Marty McFly du web qui se retrouvera tout comme moi propuls dans une lointaine poque.

if (!function_exists('file_put_contents')) {
    function file_put_contents($filename, $data, $flags=0, $context=null) {
        $fopen_args = array(
            $filename,
            (($flags&FILE_APPEND) == FILE_APPEND) ? 'a' : 'w',
            (($flags&FILE_USE_INCLUDE_PATH) == FILE_USE_INCLUDE_PATH)
        );
        if (is_resource($context)) {
            $fopen_args[] = $context;
        }
        if (($fd = call_user_func_array('fopen', $fopen_args)) !== false) {
            if (($flags&LOCK_EX) == LOCK_EX && !flock($fd, LOCK_EX)) {
                fclose($fd);
                return false;
            }
            for ($written=0, $l=strlen($data); $written < $l; $written += $nb) {
                if (($nb = fwrite($fd, substr($data, $written))) === false) {
                    if (($flags&LOCK_EX) == LOCK_EX) {
                        flock($fd, LOCK_UN);
                    }
                    fclose($fd);
                    return false;
                }
            }
            if (($flags&LOCK_EX) == LOCK_EX) {
                flock($fd, LOCK_UN);
            }
            fclose($fd);
            return $written;
        }
        return false;
    }
}

if (!function_exists('file_get_contents')) {
    function file_get_contents($filename, $use_include_path=false, $context=null, $offset=-1, $maxlen=-1) {
        $fopen_args = array($filename, 'r', $use_include_path);
        if (is_resource($context)) {
            $fopen_args[] = $context;
        }
        if (($fd = call_user_func_array('fopen', $fopen_args)) !== false) {
            if ($offset > 0) {
                fseek($fd, $offset);
            }
            $buffer = '';
            while (!feof($fd) && ($maxlen < 0 || ($r=$maxlen-strlen($buffer)) > 0)) {
                if (($data = fread($fd, ($maxlen < 0 || $r > 8192) ? 8192 : $r%8192)) === false) {
                    fclose($fd);
                    return false;
                }
                $buffer .= $data;
            }
            fclose($fd);
            return $buffer;
        }
        return false;
    }
}

Ragir cet article | Lire la suite >>>

Ma première contribution au manuel PHP

Geoffray Warnants|24/05/2011|36 commentaires

Au cours de mes recherches dans les profondeurs de la documentation PHP, il m'arrive assez frquemment de rencontrer des anomalies diverses comme des prototypes de fonction incomplets, de grossires fautes d'orthographe ou des phrases peine plus comprhensibles que celles gnres par un traducteur automatique. Autant de dtails dont l'importance peut paratre insignifiante mais qui contribuent dcrdibiliser le srieux du langage. Et pourtant, en ingrat dveloppeur que je suis, j'avoue n'avoir jamais pris la peine d'informer la communaut PHP de ces dfauts, sombrant de jours en jours en peu plus loin dans le pch.

Jusqu'au jour o ma bonne conscience se rveilla, probablement illumine par une intervention de notre bienveillant Dieu Rasmus, qui de tout l-haut ramne ses fidles dveloppeurs 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 problmes lis la documentation y sont bien entendu accepts, car tout bug est gal son prochain.

Je me suis donc depuis lors acharn signaler le moindre problme rencontr, et c'est ainsi plus d'une centaine de corrections qui ont t envoyes, approuves puis appliques notre manuel chri. Sans vouloir imiter Frdric Hardy et ses Nouvelles du front, voici l'inventaire des modifications apportes 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 releves 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 dmarche apparemment trop peu frquente, 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 intgrit humaine. Je ne peux donc qu'encourager chaque dveloppeur participer, ds qu'il en a l'opportunit, l'amlioration de ce formidable outil dont nous profitons tous gostement. Un simple acte citoyen suffit tmoigner de la reconnaissance envers les nombreux anonymes bnvoles dont les contributions nous furent, un jour ou l'autre, d'une aide plus que prcieuse.

Ragir cet article | Lire la suite >>>

Convertir des nombres en base 62 avec PHP

Geoffray Warnants|09/02/2011|77 commentaires

Dans la continuit du billet trs intressant de Vincent Battaglia consacr l'utilit du systme de numration hexatridcimal (soit en base 36) dans l'algorithme d'un raccourcisseur d'URL, j'ai voulu savoir si utiliser d'autres systmes de numration encore plus vastes, comme le base 62 voqu la fin de son article, pouvait se faire avec autant de facilit, ce qui permettrait de gnrer des URL encore plus courtes moindre effort.

Comme il l'a dj bien expliqu, le principal critre 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 systme alphanumrique minuscule tandis que la base 62 inclut aussi les caractres majuscules.

D'autre part, il va videmment de soi qu'au plus la base est leve (autrement dit au plus l'ventail de symboles possibles est vaste), au plus un nombre peut-tre reprsent de manire concise. Dans le cadre des raccourcisseurs d'URL, c'est donc aussi un critre primordial. Non seulement pour l'conomie de caractres que cela implique, mais aussi pour le nombre d'identifiants que le service pourra grer, et donc le nombre d'URL qu'il pourra prtendre offrir. Bien que la plupart des services actuels comme TinyURL, bit.ly ou goo.gl semblent s'tre limits 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 caractres accentus dans ses rsultats.

Lors de la mise en pratique, je fus tout d'abord surpris de dcouvrir 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 rcrire une fonction sensiblement identique permettant cette fois de jongler entre diffrentes bases arbitraires avec une totale libert.
Par la mme occasion, j'y ai ajout au travers d'un dernier paramtre optionnel la possibilit de spcifier le jeu de caractres prendre en compte lors de la conversion. J'y vois plusieurs cas d'utilisation concrets, comme par exemple :

  • Pouvoir djouer le caractre prdictif inhrent ce genre de conversion, en spcifiant un alphabet dont l'ordre aura t pralablement altr.
  • Pouvoir gnrer des valeurs "user-friendly", en omettant par exemple les caractres susceptibles de prter confusion lors de la lecture, tels o,O,0,1,l, etc...
  • Pouvoir dfinir son propre jeu de caractres, qui pourrait par exemple inclure des caractres spciaux.
/**
 * Convertit un nombre entre diffrentes 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 ncessaire
    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 ncessaire
    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 ide de l'allure que peuvent avoir les valeurs retournes par cette fonction, voici titre indicatif les rsultats d'une srie de conversions entre diffrentes bases. En ce qui concerne la base 163, j'ai considr le jeu de caractres 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Ç

Ragir cet article | Lire la suite >>>

Patch PHP5 pour le plugin FunctionList de Notepad++

Geoffray Warnants|11/12/2010|17 commentaires

S'il fallait citer un plugin sans lequel le dveloppement sous Notepad++ deviendrait une calamit, mon choix se porterait trs certainement sur l'indispensable Function List, un explorateur de code qui vient se greffer l'diteur et ainsi l'enrichir d'un outil par dfaut absent mais pourtant trs pris des dveloppeurs habitus aux IDE tels Eclipse ou NetBeans.

Bien que l'ide paraisse allchante, on dcouvre trs vite que dans un environnement PHP orient-objet, l'intrt de ce plugin se rvle finalement assez limit, surtout sachant avec quelle prcision les IDE actuels peuvent synthtiser la structure d'une classe. Function List se contente lui de lister les fonctions et mthodes de manire dpouille et surtout sans aucune prise en compte des caractristiques orientes-objet tels la porte des mthodes, leur niveau de visibilit, les variables membres, les constantes de classes, etc...

J'ai cependant dcouvert que la configuration par dfaut du plugin n'est pas du tout prvue pour exploiter pleinement les possibilits offertes. En effet, le plugin reconnait les diffrentes structures de n'importe quel langage grce une collection d'expressions rgulires parfaitement configurables. Celles qui concernent la syntaxe de PHP sont effectivement rduites leur strict minimum.

Je me suis alors attard rcrire une configuration plus adapte au dveloppement orient-objet en PHP. Inspir par l'explorateur de code d'Eclipse, je suis arriv au rsultat suivant :

Avant :
FunctionList
Aprs :
FunctionList pour PHP
L'Outline View d'Eclipse :
Eclipse Outline View

Voil donc un lifting rajeunissant qui ne dpaysera pas trop les utilisateurs d'Eclipse pour qui les icnes choisies sont dj familires. Pour les autres, ou titre de rappel, en voici la signification :

Classe (class)
Interface (interface)
Fichier inclu (include, include_once, require, require_once, use)
Constante (const, define)
Variable d'instance publique (public)
Variable de classe publique (public static)
Variable d'instance protge (protected)
Variable de classe protge (protected static)
Variable d'instance prive (private)
Variable de classe prive (private static)
Mthode d'instance publique (public function) ou fonction globale (function)
Mthode d'instance finale (final public function)
Mthode d'instance abstraite (abstract public function)
Mthode de classe publique (static public function)
Mthode d'instance protge (protected function)
Mthode d'instance abstraite protge (abstract protected function)
Mthode d'instance finale protge (final protected function)
Mthode de classe protge (static protected function)
Mthode d'instance prive (private function)
Mthode de classe prive (static private function)

Pour l'avoir adopte depuis quelques temps, je trouve cette configuration plus agrable, mme si elle provoque un effet dsagrable inhrent aux limitations actuelles du plugin (dans sa version 2.1) : Le tri ne s'applique plus sur l'ensemble de la liste mais sparment sur chaque groupe d'lments, ce qui est parfois dstabilisant. Si un courageux se sent d'attaque, le code du plugin est en open source (C++) ;-)

Dernire remarque pour ceux qui voudraient amliorer cette configuration, j'ai pu remarquer que le systme d'interprtation des expressions rgulires 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 rgulires. Ceci alourdi l'criture des rgles et restreint les possibilits.

Pour installer cette configuration, copiez les fichiers FunctionListRules.xml et php.bmp dans le rpertoire %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 rgles par dfaut pour tous les autres langages. Pour conserver vos ventuelles personnalisations, ne copiez que le noeud <Language name="PHP"> du fichier XML.

Tlchargement

Ragir cet article | Lire la suite >>>

Estimer la consommation mémoire des ressources GD

Geoffray Warnants|13/11/2010|5 commentaires

La librairie GD forme aujourd'hui avec PHP un couple apprci des dveloppeurs web. Elle offre en effet un accs d'une grande simplicit aux traitements d'images, ces oprations obscures qu'on sait truffes de formules mathmatiques et autres transformations matricielles, et qu'on devine par consquent probablement gourmandes en ressources matrielles. Mais sait-on exactement jusqu' quel point ? Pour ma part non, jusqu' aujourd'hui...

Le graphique ci-dessous reprsente la quantit de mmoire vive consomme par un script lors de la cration d'une ressource GD en fonction des dimensions de l'image traite (ou plutt de sa dfinition pour tre exact)

Mmoire alloue par GD selon la dfinition de l'image (GD bundled v2.0.34)

Comme on pouvait s'en douter, la mmoire 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 compltement la mmoire libre, celle-ci n'tant pas illimite. Il en rsultera alors un arrt brutal du script caus par une erreur fatale (Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes)). Un sombre scnario 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 mmoire en vigueur. J'insiste sur le caractre approximatif car j'ai pu constater qu' dfinition gale, la mmoire occupe par une ressource GD dpend aussi de ses proportions. Ainsi une image au format "portrait" (plus haute que large) risque de ncessiter d'avantage de mmoire que la mme au format "paysage". Heureusement cette diffrence ne concerne de manire significative que des images tires l'extrme.

Voici donc les dimensions maximales estimes 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 vrifications adaptes aux contraintes mmoire 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 entirement en mmoire, ce qui n'est pas le cas par exemple de ses homologues imagesx et imagesy qui risquent de provoquer la saturation mmoire avant mme d'avoir pu s'en prmunir...

Conclusion

La consommation mmoire des ressources GD n'est finalement pas si excessive. Les configurations standards permettent dj de manipuler des images importantes sans soucis. Evidemment, je ne vous apprends rien en rptant que tout fichier externe doit tre considr comme une source de donnes potentiellement risque, ce qui est d'autant plus vrai pour les images reues par exemple au travers d'un formulaire d'upload, et qu'elles doivent donc toujours faire l'objet d'un contrle minutieux avant d'tre manipules. Car une gestion correcte de la mmoire est avant tout du bon ressort du programmeur.

Ragir cet article | Lire la suite >>>

La comparaison de nombres à virgule flottante en PHP

Geoffray Warnants|30/07/2010|6 commentaires

Malgr les considrables volutions qu'a subi PHP, on ne peut nier qu'il est encore aujourd'hui trahi par de petites imperfections qui salissent son blason. Les dtracteurs s'en donnent coeur joie pour critiquer l'amateurisme du langage, les autres, certainement un peu fatalistes, prfrent dire qu'il a le mrite d'tre une source intarissable de surprises ;)

Dernire curiosit en date : La comparaison de 2 nombres dcimaux n'est pas fiable. Ainsi :

var_dump(0.2+0.3 == 0.5);   // affiche true
var_dump(0.2+0.4 == 0.6);   // affiche false

Le second rsultat est pour le moins inattendu ! Il se comprend nanmoins en regardant de plus prs la valeur relle de chacune des valeurs jusqu' leurs infimes dcimales.

printf('%.20f', 0.2);       // 0.20000000000000001110
printf('%.20f', 0.3);       // 0.29999999999999998890
printf('%.20f', 0.2+0.3);   // 0.50000000000000000000
printf('%.20f', 0.5);       // 0.50000000000000000000

printf('%.20f', 0.2);       // 0.20000000000000001110
printf('%.20f', 0.4);       // 0.40000000000000002220
printf('%.20f', 0.2+0.4);   // 0.60000000000000008882
printf('%.20f', 0.6);       // 0.59999999999999997780

"Honteux" crierons les dtracteurs, "normal" diront simplement les autres. En effet, un avertissement dans la doc PHP nous met en garde face ce comportement hasardeux bien connu et l'explique par le fait que la reprsentation interne de certains nombres dcimaux n'est pas possible sans une infime perte de prcision. Une remarque qui n'est pas sans raviver mes lointaines notions d'ASM et les prises de tte pour comprendre la reprsentation des dcimaux en binaire.

Bref, ce problme d'approximation est inhrent nos ordinateurs. On le rencontre d'ailleurs dans d'autres langages de bas niveau, notamment en C, langage avec lequel est crit PHP.

Que faut-il faire ?

Comme indiqu dans la doc, comparer des nombres dcimaux de manire classique est proscrire. Pour ce genre d'opration qui ncessite une prcision importante, PHP fourni un ensemble de fonctions spcifiques, dont bccomp qui permet de comparer des nombres de grande taille.

var_dump(bccomp(0.2+0.4, 0.6)==0); // affiche true

Une autre solution plus artisanale consiste caster les nombres en chanes de caractres avant d'effectuer la comparaison.

var_dump((string)(0.2+0.4)==(string)0.6); // affiche true

Cette solution semble tout aussi acceptable puisque lors de la conversion, PHP prend soin de retirer les ventuels zros non significatifs qui pourraient poser problme en cas de comparaison textuelle.

var_dump((string)(0.2+0.4)==(string)000.60000); // affiche true aussi

La suite n'est plus qu'une histoire de prfrence...

Ragir cet article | Lire la suite >>>

Améliorer l'autocomplétion PHP de Notepad++

Geoffray Warnants|24/06/2010|22 commentaires

Mme si le systme d'autocompltion de fonction propos par l'diteur Notepad++ n'est pas pleinement satisfaisant, il propose nanmoins une option que je trouve fort utile : l'autocompltion de paramtres. Elle permet d'afficher instantanment en infobulle le prototype complet de la fonction native qu'on est en train d'utiliser.

Notepad++ auto-complete

Pour l'activer, il faut se rendre dans le menu [Paramtrage]/[Prfrences]/[Sauvegarde/Auto-compltion] et cocher la case [Afficher paramtres pendant la saisie]

Malheureusement, en ce qui concerne PHP, la base de donnes utilise par l'diteur semble dater de Mathusalem. De nombreuses fonctions n'y sont pas rpertories, et pire encore pour tre induit en erreur, certains paramtres sont parfois manquants, les types et les valeurs de retour ne sont pas toujours corrects, la plupart des valeurs par dfaut ne sont pas mentionnes, de mme que les passages par rfrence.

Je me suis donc attard reconstruire une base de donnes aussi "up-to-date" que possible. Elle est tlchargeable ici (dernire mise jour le 16/02/2011) et doit tre extraite dans le rpertoire Notepad++/plugins/APIs/

Ragir cet article | Lire la suite >>>

Sortie de PhpMyAdmin 3.3.0

Geoffray Warnants|08/03/2010|3 commentaires

Ce n'est pas dans mes habitudes d'annoncer la disponibilit d'une nouvelle version d'un logiciel, mais comme celle-ci m'est assez particulire, je me permets d'insister sur la release frachement sortie de PhpMyAdmin version 3.3.0.0. Cette version est effectivement une peu spciale pour moi car c'est la premire intgrer officiellement ma maigre contribution au projet, savoir une nouvelle fonctionnalit dont j'avais dj parl prcdemment qui permet d'exporter les donnes d'une table MySQL sous forme d'un tableau PHP associatif.

J'ai donc eu l'honneur d'tre cit dans dans le changelog de la release, dont l'extrait concern figure ci-dessous. Une prcision qui n'a bien entendu d'autre utilit que la postrit ;)

+ patch #2805828 [export] PHP array export plugin,
  thanks to Geoffray Warnants

Ragir cet article | Lire la suite >>>

"Only variables should be passed by reference"

Geoffray Warnants|12/02/2010|16 commentaires

Dans un environnement de dveloppement normalement configur pour afficher les erreurs de type E_NOTICE mais aussi E_STRICT, on se retrouve parfois confront une curieuse alerte, provoque par une squence d'instructions qui semble pourtant anodine.

Par exemple, pour extraire l'extension d'un nom de fichier, j'cris souvent de faon machinale :

$extension = end(explode('.', $filename));

Ce qui provoque une alerte assez droutante :

Strict Standards: Only variables should be passed by reference

Pour comprendre ce message, il faut d'abord se rappeler que la fonction end() accepte en ralit une rfrence de tableau, ce qui est tout fait comprhensible puisqu'elle va le modifier en positionnant le pointeur interne sur son dernier lment.

Ensuite [1], il faut savoir que lorsqu'une fonction retourne une valeur, une variable temporaire est cre. Gnralement, on rcupre cette variable temporaire en l'assignant une autre variable dclare explicitement, mais dans notre cas, la variable temporaire retourne par la fonction explode() est directement transmise la fonction end() qui, bien que ce soit effectivement un Array, la refuse et provoque l'alerte.

Ce comportement tout fait lgitime ne date pas d'hier. Il a t introduit en PHP 5.0.5 afin d'viter des corruptions mmoires. On peut certainement mieux comprendre ce risque en se souvenant de la diffrence significative entre les pointeurs et les rfrences du C++.

Selon Rasmus Lerdorf, fondateur de PHP, il est recommand d'effectuer l'opration en deux tapes, en transitant par une variable dclare.

$split = explode('.', $filename);
$extension = end($split);

Il souligne aussi trs juste titre que dans ce cas bien prcis qui consiste en une opration fondamentale sur une chane de caractres, il est absolument injustifi de passer par un tableau pour rsoudre ce problme alors que des fonctions spcifiquement ddies aux chanes de caractres suffisent. J'ajouterai mme qu'elles seront aussi plus performantes !

$extension = substr(strrchr($filename, '.'), 1);

J'ai cependant dcouvert que la fonction current() ne provoque pas d'erreur avec une variable temporaire alors que son prototype signale aussi un passage par rfrence. On peut donc supposer, en sachant que current() ne modifie pas le pointeur interne du tableau, que le message d'erreur gnr par PHP n'est pas tout fait pertinent. Il devrait plutt mentionner "Only variables should be updated by reference". Rasmus, si tu pouvais confirmer... ;)

Sachant cela, les fanatiques qui voudraient tout prix s'en tirer avec une seule ligne de code peuvent toujours le faire, au prix d'une opration certainement assez coteuse :

$extension = current(array_reverse(explode('.', $filename)));

Ragir cet article | Lire la suite >>>

L'upcasting en PHP

Geoffray Warnants|11/02/2010|5 commentaires

Les problmes de casting (transtypage) sont souvent considrs comme le propre des langages typage fort tels que par exemple Java, C++ ou C#. L'upcasting (communment traduit en franais par "transtypage vers le haut" ou encore "surclassement") consiste transformer une classe drive en une classe dont elle hrite, lui faisant ainsi volontairement perdre de sa spcificit. C'est cette notion d'ascendance qui lui vaut le nom de casting vers le haut, par opposition au downcasting.

Bien que PHP ne puisse pas raliser cette transformation nativement, voyons comme faire pour y remdier en imaginant l'exemple suivant :

class Person
{
}
class Child extends Person
{
}

Ainsi, avec une hirarchie similaire crite en Java, convertir une instance de Child en un objet de type Person aurait pu se faire tout naturellement.

Child c = new Child();
Person m = c;

Ce n'est malheureusement pas pareil en PHP, puisque comme l'indique la documentation sur le type casting, seule la conversion vers les types natifs est possible. La tentative suivante se solderait donc par une erreur de syntaxe.

$c = new Child();
$m = (Person)$c;

Pour remdier ce problme, je m'inspire ici du concept de "constructeur de copie" qu'on retrouve en C++ et qui a pour but de raliser une copie d'un objet via le constructeur de la classe. Bien souvent, la copie consiste tout btement en une initialisation dite membre membre. On peut donc ajouter la classe mre un constructeur jouant ce rle :

class Person
{
    /**
     * Constructeur de copie
     */
    public function __construct(Person $c=null)
    {
        if ($c !== null) {
            foreach (get_object_vars($c) as $property => $value) {
                if (!is_object($value)) {
                    $this->$property = $value;
                } else {
                    $this->$property = clone $value;
                }
            }
        }
    }
}

On peut alors simuler l'upcasting :

$c = new Child();
$m = new Person($c);

Un tel besoin peut paratre droutant, puisque en ralit, une instance de Child est dj par hritage une instance de Person, laquelle des spcificits sont apportes. Nanmoins, dans certains cas, il peut s'avrer utile de retirer les atouts d'une instance drive pour n'en retrouver que les comportement et proprits hrites. Par exemple, une mthode qui accepte un objet en paramtre pourrait interdire la rception d'une classe drive dont les caractristiques seraient juges inopportunes.

class Person
{
    public function haveSex(Person $p)
    {
        if ($p instanceof Child) {
            throw new Exception('Tu devrais pas tre au lit toi ?');
        }
        // n'golo golo !
    }
}

Finalement, voici comment conclure par un malheureux inceste programmatique ;)

$serge = new Person();
$charlotte = new Child();
$serge->haveSex(new Person($charlotte));

Ragir cet article | Lire la suite >>>

Plugin phpMyAdmin : exporter des tables en PHP Array

Geoffray Warnants|10/06/2009|9 commentaires

Par soucis de performance, il m'arrive de temps en temps d'exporter le contenu d'une table MySQL pour en crer un tableau PHP associatif. Disposant ainsi des donnes en mmoire, ceci me permet, par exemple lors d'une requte de slection, d'pargner une jointure avec cette table. Bien entendu, ceci n'est rellement intressant que pour les tables de petite taille (table de devises, de pays, etc...)

Malgr la petite taille de ces tables, la procdure pour gnrer le code du tableau est gnralement fastidieuse moins de disposer d'un petit script "fait maison" sous la main.

J'ai donc dcid de publier un modeste outil utilisable sous la forme d'un plugin pour phpMyAdmin. Il ajoute la fonctionnalit dsire (cf. checkbox "PHP Array") la liste des formats disponibles pour l'export des bases de donnes.

exporter tables MySQL en tableau PHP

Pour l'installation, rien de plus simple. Il suffit de tlcharger le plugin ici et d'extraire le fichier PHP dans le rpertoire phpMyAdmin/libraries/export/.

Par contre, le script tant toujours en version beta, tout rapport de bug ou suggestion d'amlioration sera vivement apprci. Merci d'avance !

Ragir cet article | Lire la suite >>>

La compression de données par RLE en PHP

Geoffray Warnants|28/05/2009|2 commentaires

Intrigu par les algorithmes de compression de donnes, je me suis pench sur RLE qui est sans conteste l'un des moins complexe implmenter. Il s'applique aux donnes qui prsentent de longues rptitions d'un mme caractre. Le principe consiste ne conserver qu'un seul caractre de chaque plage auquel on prfixera le nombre rel de rptitions attendues. Par exemple :

AAAABBBBBBCDDDDDEEEEEEEEE (25 octets)
devient
4A6B1C5D9E (10 octets)
Dans ce cas bien choisi le niveau de compression est intressant, mais loin d'tre systmatiquement satisfaisant : l'algorithme s'avre en effet trs peu appropri aux donnes qui ne prsentent pas de rptitions suffisantes. Ainsi :
ABCDE (5 octets)
devient
1A1B1C1D1E (10 octets)

On voit clairement que RLE ne peut tre envisag que dans des domaines d'application bien prcis pour pouvoir en esprer un quelconque bnfice. Initialement invent pour compresser les documents scanns en noir et blanc (qui prsentent invitablement de longues plages d'octets "blancs"), on peut l'appliquer au cas typique des images bitmaps formes de grandes zones d'une couleur unie. Ces pixels contigus dont le code couleur est identique se traduiront dans le fichier par une suite conscutive d'octets identiques.

Piet Mondrian Pour illustrer ce principe, j'ai test la compression du fichier bitmap ci-contre. La compression RLE a permi de rduire le poids du fichier de 67.3 Ko 9.1 Ko, c'est qui est dj apprciable.
Ce qui laisse rveur, c'est d'imaginer l'inventivit mise en oeuvre par le format PNG, pour arriver avec ce mme fichier source un rsultat final d' peine 400 petits octets !

Ma classe PHP

/**
 * "Run-length encoding" algorithm compression class
 *
 * @author  Geoffray Warnants - http://www.geoffray.be/
 * @version 1.0
 */
class RLE
{
    /**
     * Compress a string using RLE
     *
     * @param   string  $str
     * @return  string
     */
    public static function encode($str)
    {
        $encoded = '';
        for ($i=0, $l=strlen($str), $cpt=0; $i<$l; $i++) {
            if ($i+1<$l && $str[$i]==$str[$i+1] && $cpt<255) {
                $cpt++;
            } else {
                $encoded .= chr($cpt).$str[$i];
                $cpt = 0;
            }
        }
        return $encoded;
    }
    
    /**
     * Uncompress a RLE string
     *
     * @param   string  $str
     * @return  string     
     */
    public static function decode($str)
    {
        $decoded = '';
        for ($i=0,$l = strlen($str); $i<$l; $i+=2) {
            if ($i+1<$l && ord($str[$i]) > 0) {
                $decoded .= str_repeat($str[$i+1], 1+ord($str[$i]));
            } else {
                $decoded .= $str[$i+1];
            }
        }
        return $decoded;
    }
}

Ragir cet article | Lire la suite >>>

PHP 5.2.9 améliore la méthode magique __call()

Geoffray Warnants|02/03/2009|1 commentaire

Avec quelques jours de retard, je viens de tester les amliorations mineures apportes par la toute fraiche release 5.2.9 de PHP, dernire ligne droite avant la trs attendue 5.3. Outre les corrections de plusieurs bugs, cette nouvelle version prsente une lgre amlioration du comportement de la mthode magique __call() vis vis des mthodes prives et protges. Ainsi, sous les versions antrieures 5.2.9, l'exemple suivant se soldait par une toute belle Fatal error: Call to private method Foo::bar() from context ''

<?php
class Foo {
    public function __call($method, $args) {
        if (method_exists($this, $method)) {
            call_user_func_array(array($this, $method), $args);
        }
    }
    private function bar() {
        echo 'Hello';
    }
}

$foo = new Foo();
$foo->bar();
?>

Bonne nouvelle : cet agaant comportement que tout un chacun a probablement dj rencontr fait dsormais partie du pass.

Ragir cet article | Lire la suite >>>

Le chiffrement en ROT13

Geoffray Warnants|29/01/2009|4 commentaires

A l'école primaire, il m'arrivait souvent de jouer les agents secrets en mettant mes camarades au défi de déchiffrer des messages codés. Un codage utilisé était par exemple de remplacer chacune des lettres par sa précédente dans l'alphabet, rendant ainsi le texte illisible mais facilement déchiffrable par qui en connaissait l'astuce.

Sans le savoir, je venais de mettre en pratique une méthode de chiffrement ancestrale appelée le Chiffre de César en l'honneur à Jules César, son inventeur, qui l'utilisait probablement en son temps pour transmettre ses mots doux en toute discrétion ;-). Un cas particulier de cet algorithme est le ROT13, qui décale chaque caractère alphabétique de 13 positions. Une clé qui n'a pas été choisie sans raison : Considérant notre alphabet de 26 lettres comme une suite circulaire (on revient au A après le Z), appliquer la translation 2 fois de suite permet de retrouver le texte original.

C'est d'ailleurs pourquoi la fonction PHP str_rot13() ne possède tout logiquement pas d'équivalence pour le décodage.

echo str_rot13(str_rot13('La boucle est bouclée'));
Je trouve cet exemple très pertinent pour illustrer que le fait d'appliquer successivement une même fonction d'encodage contribue parfois à affaiblir la robustesse d'un algorithme !

Ragir cet article | Lire la suite >>>

Récupérer les en-têtes HTTP en PHP avec la lib cURL

Geoffray Warnants|13/12/2008|10 commentaires

Voulant une fois pour toutes pouvoir grer correctement les appels des pages distantes via HTTP, j'ai dcid de me pencher sur la librairie cURL. Une des premires tapes que je souhaitais accomplir tait d'extraire les en-ttes HTTP afin d'obtenir une indication sur le droulement de la requte HTTP demande. Une premire approche permet de raliser ceci trs simplement grce l'option de transmission CURLOPT_HEADER :

<?php
$url = curl_init();
curl_setopt($url, CURLOPT_URL, 'http://www.google.com');
curl_setopt($url, CURLOPT_RETURNTRANSFER, true);
curl_setopt($url, CURLOPT_HEADER, true);

$page = curl_exec($url);
curl_close($url);
?>

L'inconvnient majeur de cette mthode est que les en-ttes HTTP et le contenu de la page demande se retrouvent concatns dans le rsultat de curl_exec(). Effectuer une dcomposition manuelle du rsultat obtenu pourrait fournir le rsultat attendu, mais c'est alors omettre une solution plus propre offerte par la librairie. Elle propose en effet un mcanisme de fonctions "callbacks" appeles lorsqu'un vnement particulier se produit. Dans le cas qui nous intresse ici, l'option CURLOPT_HEADERFUNCTION permet d'appeler une fonction chaque en-tte HTTP rencontr. Attention que cette fonction doit absolument retourner le nombre d'octets de l'en-tte reu en paramtre.

<?php
function read_header($url, $str) {
    echo 'Header : '.$str."\n";
    return strlen($str);
}

$url = curl_init();
curl_setopt($url, CURLOPT_URL, 'http://www.google.com');
curl_setopt($url, CURLOPT_RETURNTRANSFER, true);
curl_setopt($url, CURLOPT_HEADER, true);
curl_setopt($url, CURLOPT_HEADERFUNCTION, 'read_header');

$page = curl_exec($url);
curl_close($url);
?>

Sur base de ce principe, on peut alors se construire rapidement une petit classe, histoire d'encapsuler ces traitements dans un objet :

<?php
/**
 * A sample class to read HTTP headers
 * @author Geoffray Warnants - http://www.geoffray.be
 */
class HTTPReader {
    protected $_url = null;
    protected $_headers = array();
    protected $_body = '';
    
    public function __construct($url) {
        $this->_url = curl_init($url);   
        curl_setopt($this->_url, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->_url, CURLOPT_HEADER, true);
        curl_setopt($this->_url, CURLOPT_HEADERFUNCTION,
            array($this, 'readHeaders')
        );
    }
    public function __destruct() {
        curl_close($this->_url);
    }
    public function getHeaders() {
        $this->_body = curl_exec($this->_url);
        return $this->_headers;
    }
    public function getBody() {
        return $this->_body;
    }
    protected function readHeaders($url, $str) {
        if (strlen($str) > 0) {
            $this->_headers[] = $str;
        }
        return strlen($str);
    }
}
?>

Ragir cet article | Lire la suite >>>

Comment utiliser un objet PHP sans le déclarer ?

Geoffray Warnants|19/10/2008|5 commentaires

Pour illustrer ce que je voulais pouvoir faire en PHP, je me suis inspir du langage Java o on rencontre parfois ce genre d'appel :
// en Java
new Application(login, pwd).start();
La particularit est qu'on peut utiliser un objet sans avoir forcment besoin de le dclarer dans une variable. Ceci est souvent utilis dans le cas o cet objet n'est ncessaire qu' un unique endroit du programme. Malheureusement, PHP n'autorise pas cette syntaxe, ce qui nous contraint de scinder l'opration et de s'encombrer d'une variable intermdiaire qui nous est inutile.
// en PHP
$app = new Application($login, $pwd);
$app->start();
unset($app);

L'avantage de la premire solution, outre le fait apprci qu'elle offre une criture plus concise, rside dans la mise en vidence de l'inutilit de l'instance en dehors de cet unique appel, ce qui peut s'avrer d'une grande utilit pour une comprhension rapide du code par un tierce dveloppeur.

Pour pouvoir reproduire cette criture en PHP, j'ai cr la petite classe suivante (qui aurait trs bien pu n'tre qu'une simple fonction) :
class ClassLoader {
    public static function load($className, $arg=null) {
        $c = new ReflectionClass($className);
        return ($c->hasMethod('__construct') || $c->hasMethod($className)) ?
            $c->newInstanceArgs(array_splice(func_get_args(), 1)) :
            $c->newInstance();
    }
}
On peut alors crire :
ClassLoader::load('Application', $login, $pwd)->start();
Le principe devient intressant, mais la clart du code en a pris un sacr coup ! Pour simplifier, j'ai pens une nouveaut trs attendue de PHP 5.3 : la nouvelle mthode magique __callStatic, dont le comportement est identique la mthode __call bien connue, mais adapte aux mthodes statiques.
//  partir de PHP 5.3
class ClassLoader {
    public static function __callStatic($method, $args) {
        $c = new ReflectionClass($method);
        return ($c->hasMethod('__construct') || $c->hasMethod($method)) ?
            $c->newInstanceArgs($args) :
            $c->newInstance();
    }
}    
L'criture gagne ainsi nettement en simplicit :
ClassLoader::Application($login, $pwd)->start();
Il ne reste plus qu' finaliser la classe pour la rendre compatible PHP 5 et 6, ce qui ne pose pas de problme. Il sera juste laiss au dveloppeur le soin de raliser les appels adquats selon la version de PHP utilise, avec la seule petite restriction qu'en PHP 6, le chargement d'une ventuelle classe nomme "Load" devra invitablement se faire via l'criture PHP 5.
class ClassLoader {
    public static function load($className, $arg=null) {
        $c = new ReflectionClass($className);
        return ($c->hasMethod('__construct') || $c->hasMethod($className)) ?
            $c->newInstanceArgs(array_splice(func_get_args(), 1)) :
            $c->newInstance();
    }
    
    public static function __callStatic($className, $args) {
        return call_user_func_array(
            array(self, 'load'), array_merge(array($className),$args)
        );
    }
}

Ragir cet article | Lire la suite >>>

Formater les noms de répertoires en PHP

Geoffray Warnants|06/10/2008|3 commentaires

Lors de la manipulation de noms de répertoires dans des variables PHP, il m'est souvent arrivé d'être embêté par un slash final parfois manquant, parfois présent. Pour éviter ce désagrément, j'ai pris pour habitude d'adopter la convention qui impose qu'un nom de répertoire doit toujours être terminé par un slash. Ainsi, pour uniformiser mes variables dès leur initialisation, j'opère un rapide formatage désormais devenu un réflexe :
$path = rtrim($path, '/\\').'/';
Pour être pointilleux, on pourrait même pousser le vice jusqu'à utiliser la constante DIRECTORY_SEPARATOR afin de terminer la chaîne par le caractère slash ou backslash adéquat selon le système d'exploitation sur lequel on se trouve :
$path = rtrim($path, '/\\').DIRECTORY_SEPARATOR;
J'avoue me contenter généralement de la première solution, bien plus rapide à écrire et qui se révèle tout aussi portable puisque gérée par les principaux OS (Windows, *nix, Mac OS). Cette constante n'est pas pour autant totalement dénuée d'intérêt puisqu'elle peut par exemple trouver son utilité lorsqu'on souhaite traiter un chemin retourné par le système d'exploitation. Il peut donc s'avérer utile de faire :
$path = str_replace(DIRECTORY_SEPARATOR, '/', rtrim(getcwd(), '/\\')).'/';

Ragir cet article | Lire la suite >>>

Un effet pervers du transtypage avec in_array

Geoffray Warnants|27/05/2008|2 commentaires

Malgr son apparente simplicit, voici un bel exemple qui montre qu'une bonne connaissance des subtilits du langage PHP s'avre trs importante :
in_array(0, array('A','B','C'));   // Retourne TRUE !!
Cette instruction montre que pour PHP, le nombre 0 se trouve bien dans le tableau ne comportant que des chanes de caractres. Bien que cela puisse paratre surprenant, ce comportement est tout fait normal. Pour le comprendre, il faut se souvenir de la manire dont PHP ralise les comparaisons entre valeurs de types diffrents. PHP tant un language faiblement typ, pour pouvoir comparer ce qui est comparable, il doit parfois raliser implicitement des conversions de type (transtypage, ou casting) sur l'une des 2 oprandes. Ainsi, dans le cas d'une comparaison nombre/chane, il est important de savoir que c'est toujours la chane qui est implicitement caste en nombre.
// cette condition sera vrifie
echo (0 == 'A') ? 'TRUE' : 'FALSE';

// car elle est identique 
echo (0 == (int)'A') ? 'TRUE' : 'FALSE';
Et comme on sait que le casting d'une chane non numrique en un nombre retournera tout logiquement la valeur 0, voil notre condition vrifie !

Bien entendu, une comparaison stricte l'aide de l'oprateur "triple gal" nous aurait offert le rsultat initialement attendu puisqu'il ralise une comparaison aussi bien les valeurs que sur les types.

// cette condition ne sera pas vrifie
echo (0 === 'A') ? 'TRUE' : 'FALSE';
Il faut aussi savoir que la fonction in_array possde elle mme le moyen de raliser cette comparaison stricte car elle accepte un dernier paramtre optionnel qui permet d'activer ou non cette fonctionnalit. Non active par dfaut, il nous aurait fallu crire ds le dpart :
in_array(0, array('A','B','C'), true);   // Retourne FALSE
Pour conclure, outre le fait que profiter du confort offert par le faible typage de PHP ne se fait pas sans une extrme vigilance, j'ajouterai qu'avant d'accuser le langage d'un nouveau bug, il est peut-tre utile d'envisager une ventuelle dfaillance du programmeur ;)

Liens utiles

Ragir cet article | Lire la suite >>>

Sortie du Zend Framework 1.5

Geoffray Warnants|18/03/2008|1 commentaire

Zend Framework 1.5Vivement attendue, la release 1.5 du célèbre framework signé Zend Technologies débarque en version finale. Une sortie toute fraiche dont j'attendais impatiemment l'arrivée avant de me lancer dans l'apprentissage de ce framework, choisi après mûre réflexion parmi les nombreux autres prétendants très convaincants qui se bousculent au portillon. Voilà, maintenant plus d'excuses, va falloir s'y mettre !

Ragir cet article | Lire la suite >>>

Convertir un tableau PHP en Javascript

Geoffray Warnants|21/01/2008|2 commentaires

Cette petite fonction à l'allure peu digeste permet de transformer un tableau PHP en un tableau Javascript. Rien de bien formidable en soi, mais elle à tout de moins le mérite de gérer correctement les tableaux multidimensionnels de façon récursive, les valeurs NULL, booléennes ou non numériques. C'est pas du grand art, mais c'est simple et efficace.
public static function writeArray($aInput, $jsVarName, $eol=PHP_EOL)
{
    $js = $jsVarName.'=new Array();'.$eol;
    foreach ($aInput as $key => $value) {
        if (!is_numeric($key)) {
            $key = '"'.$key.'"';
        }
        if (is_array($value)) {
            $js .= self::writeArray($value, $jsVarName.'['.$key.']', $eol);
        } else {
            if (is_null($value)) {
                $value = 'null';
            } elseif (is_bool($value)) {
                $value = ($value) ? 'true' : 'false';
            } elseif (!is_numeric($value)) {
                $value = '"'.$value.'"';
            }
            $js .= $jsVarName.'['.$key.']='.$value.';'.$eol;
        }
    }
    return $js;
}

Ragir cet article | Lire la suite >>>

Simulateur de fautes de frappe

Geoffray Warnants|13/01/2008|3 commentaires

Travaillant sur un petit système de correction orthographique online, je me suis penché sur la réalisation d'une classe PHP qui permettrait d'obtenir toutes les combinaisons qu'il pourrait résulter d'une faute de frappe lors de l'encodage d'un mot au clavier. L'algorithme se base sur l'adjacence des touches du clavier (azerty et qwerty supportés) pour ne proposer que les fautes de frappe les plus probables dues à la maladresse humaine. Il est par exemple peu fréquent d'encoder un "s" alors qu'on vise un "k", ces deux touches étant complètement opposées.
<?php
$clumsy = new ClumsyTyper();
print_r($clumsy->getMistypedString('sexy''));
?>
Va afficher :
Array (
    [0] => zexy
    [1] => eexy
    [2] => qexy
    [3] => dexy
    [4] => wexy
    [5] => xexy
    [6] => szxy
    [7] => srxy
    [8] => ssxy
    [9] => sdxy
    [10] => sesy
    [11] => sedy
    [12] => sewy
    [13] => secy
    [14] => sext
    [15] => sexu
    [16] => sexg
    [17] => sexh
)
Bien évidemment, certains petits filous auront vite cerné l'utilité de cette classe dans d'autres domaines complètement machiavéliques tels que le typosquatting (ou URL hijacking), la génération massive de mots clés (Massive keyword list), et tout un tas de trucs avec des noms qui font très peur, à essayer avec prudence dans un but purement éducatif, bien entendu !

Ragir cet article | Lire la suite >>>

<<< Articles plus rcents | Articles plus anciens >>>

zend framework