mageekblog - Mot-clé - autoloadLe blog personnel de Frédéric Hardy. Au menu, PHP, agilité, FreeBSD, cuisine et photographies.2021-12-02T08:20:54+01:00Frédéric Hardyurn:md5:26874ca5b8cd4cac8d08b0e68e64f63aDotclearUne petite énigme ?urn:md5:20cfbdff55306c0eca2bec6bcf6680402013-03-07T13:30:00+01:002013-03-07T15:08:42+01:00mageekguyPHPatoumautoloadphpwindowsénigme<p>Ça vous dis d'essayer de résoudre une énigme en <a href="http://www.php.net">PHP</a> sur laquelle je me casse les dents depuis deux jours maintenant ?</p>
<p>Il s’agit d’une énigme complexe et à tiroir qui aurait pu être posée par une sorcière vaudou habitant au fin fond du bayou, une de ces énigmes qui semblent simples, mais qui sont pourtant incroyablement complexes à résoudre, du moins tant que l’on n’a pas trouvé la pourtant si incroyablement évidente solution. </p> <p>Il s’agit d’un bug dans <a href="http://www.atoum.org/atoum">atoum</a> qui ne se produit que sous Windows et uniquement avec le code contenu dans la branche « autoloader ».</p>
<p>Il y a quelques jours, j’ai cherché une solution pour rendre insensible à la casse le mécanisme d’auto-chargement de classe d’<a href="http://www.atoum.org/atoum">atoum</a> (l’objectif recherché ainsi que les raisons historiques du fonctionnement actuel sont hors de propos).</p>
<p>Pour y parvenir, j’ai donc fait en sorte que la méthode <a href="https://github.com/atoum/atoum/blob/autoloader/classes/autoloader.php#L70"><code>atoum\autoloader::addDirectory()</code></a> parcourt chaque répertoire qu’elle reçoit en argument pour construire un tableau de correspondance entre un nom de classe et un chemin d’accès sur le système de fichier, tableau qui est ensuite utilisé pour localiser et charger à l’aide de <code>require</code> le fichier contenant la classe requise par le fonctionnement du programme.</p>
<p>Évidemment, ce parcourt a un impact sur les performances, et dans le cas d’<a href="http://www.atoum.org/atoum">atoum</a>, il est très important, car chaque méthode de test est exécutée par défaut dans un processus PHP différent.</p>
<p>En conséquence, avec une implémentation trop naïve de ce mécanisme, les répertoires concernés sont donc parcourus pour chaque méthode de test, ce qui est coûteux et surtout complètement inutile.</p>
<p>J’ai donc modifié le moteur qui exécute chaque méthode de test de façon à ce qu’il transmette le tableau évoqué précédemment au processus PHP concerné pour supprimer ce trop coûteux et parfaitement inutile parcours de répertoire.</p>
<p>Pour cela, j’ai tout simplement passé en argument à la fonction <code>var_export()</code> l’objet responsable de l’autochargement de classe dans atoum et j’ai <a href="https://github.com/atoum/atoum/blob/autoloader/classes/test/engines/concurrent.php#L141">envoyé le résultat</a> au processus PHP via un <a href="http://en.wikipedia.org/wiki/Pipeline_(Unix)">pipeline</a> ouvert à l’aide de la fonction <a href="http://www.php.net/proc_open"><code>proc_open()</code></a>.</p>
<p>Sous UNIX, tout a alors fonctionné sans le moindre problème et j’ai obtenu des performances tout à fait similaires à celles obtenues avec l’ancienne version du mécanisme d’autochargement de classe.</p>
<p>Par contre, les tests effectués par thehawk (que je remercie grandement encore une fois pour sa patience), l’un des contributeurs à atoum utilisant Windows, ont révélé un problème.</p>
<p>En effet, il obtient systématiquement le message d’erreur suivant lorsqu’il exécute la commande <code>php ./bin/atoum --test-it</code> permettant d’exécuter l’intégralité des tests unitaires de atoum :</p>
<blockquote><pre><code>ERROR: PHP Parse error: syntax error, unexpected ''O:26:"mageekguy\atoum\autoloa' (T_ENCAPSED_AND_WHITESPACE), expecting ')' in - on line 1</code></pre></blockquote>
<p>Évidemment, nous avons fait quelques vérifications et je peux déjà vous dire que :</p>
<ul>
<li>Le code transmis au processus PHP est bien un code PHP syntaxiquement valide, car l’utilisation de la commande <code>php -l</code> l’a confirmé.</li>
<li>Le code transmis au processus PHP est parfaitement exécutable et génère le résultat escompté sous Windows comme sous UNIX lorsqu’il est mis dans un fichier et exécuté en ligne de commande à l’aide de <code>php path/to/file.php</code>.
</li>
<li>Le code généré sous Windows et envoyé au processus PHP s’exécute sans le moindre problème sous UNIX, si ce n’est qu’évidemment les chemins d’accès aux classes sont invalides et que cela provoque une erreur lors de l’inclusion des fichiers.</li>
<li>Le responsable de l’erreur de syntaxe semble se cacher dans le contenu de la propriété <code>classes</code> de la classe <code>atoum\autoloader</code>.
</li>
</ul>
<p>Pour obtenir ces informations, j’ai notamment remplacé l’appel à la fonction <code>var_export()</code> par un appel à la fonction <code>serialize()</code>.</p>
<p>J’ai de plus implémenté l’interface <code>serializable</code> au niveau de la classe <code>atoum\autoloader</code> afin de pouvoir <a href="https://github.com/atoum/atoum/blob/autoloader/classes/autoloader.php#L32">contrôler finement</a> ce qu’il se passe lorsqu’une de ses instances est passée en argument à <code>serialize()</code>.</p>
<p>Pour autant, je n’ai aucune idée de l’origine du problème qui semble être lié à l’utilisation de <code>proc_open()</code> puisque l’erreur de syntaxe ne se manifeste que dans ce contexte.</p>
<p>Je subodore que les <code>\</code> utilisés dans les chemins de fichier sous Windows sont responsable, mais je n’ai pas réussi à en avoir la preuve.</p>
<p>Et j'ai encore plus étrange, si jamais cela ne vous suffit pas, puisque l'ajout d'un <code>print_r($this->classes)</code> dans le code de la méthode <code>atoum\autoloader::serialize()</code> fait <a href="http://pastebin.com/uBw0c0h1">disparaître l'erreur de syntaxe</a>.</p>
<p>Donc, si vous êtes amateur de prise de tête, que vous êtes sous Windows et que vous plonger dans les rouages de atoum ne vous effraye pas le moins du monde, je vous invite à <a href="https://github.com/atoum/atoum">forker le dépôt d’atoum</a>, à vous positionner sur <a href="https://github.com/atoum/atoum/tree/autoloader">la branche « autoloader »</a> et à tenter de résoudre cette énigme.</p>
<p>Et si d’aventure vous trouvez la solution et surtout l’explication, je vous invite à la partager ici en commentaire ou bien en tout autre lieu que vous jugerez adéquat.</p>
<p>Et si vous voulez des informations supplémentaires, je vous propose de me les demander soit en commentaire, soit plus simplement sur le canal <abbr title="Internet Relay Chat">IRC</abbr> officiel d’atoum, à savoir <code>##atoum</code> sur le réseau Freenode.</p>
<p>Et j’ai failli oublier le plus important : <privatejoke>pour ceux qui ne saurait pas encore, <a href="http://www.atoum.org/atoum">atoum</a> est un framework de test unitaire, simple, moderne et intuitif !</privatejoke></p>http://blog.mageekbox.net/?post/2013/03/07/Une-petite-%C3%A9nigme#comment-formhttp://blog.mageekbox.net/?feed/atom/comments/405Sparklineurn:md5:64731e93a842aa0c08d84ce1a013338b2010-05-19T08:00:00+02:002010-05-19T09:21:46+02:00mageekguysparklineautoloadGDPHPPHP 5.3PHP Standards Working Groupsparkline<p>Dans le cadre de <a href="http://blog.mageekbox.net/?post/2010/04/22/L-%C3%A9lectro-enc%C3%A9phalogramme-de-PHP-existe-%21">svneeg</a>, j'ai développé <a href="http://blog.mageekbox.net/?post/2010/04/26/Svneeg-%3A-la-gen%C3%A8se-de-la-classe-sparkline">un ensemble de classes</a> en <a href="http://www.php.net">PHP</a> permettant de générer des <a href="http://en.wikipedia.org/wiki/Sparkline">sparklines</a>.</p>
<p>Au fil du temps, ces classes sont devenues un projet dans le projet, au point qu'elles représentent à présent la majorité du code de <a href="http://blog.mageekbox.net/?post/2010/05/17/../?post/2010/04/22/L-%C3%A9lectro-enc%C3%A9phalogramme-de-PHP-existe-%21">svneeg</a>.</p>
<p>J'ai donc décidé de les rendre totalement indépendantes de ce dernier, afin qu'elles puissent être utilisées dans un tout autre contexte.</p> <p>Séparer le code de génération des <a href="http://en.wikipedia.org/wiki/Sparkline">sparklines</a> n'a pas été trop difficile, mon code étant naturellement fortement découplé et <a href="http://blog.mageekbox.net/?post/2010/05/17/../?post/2010/04/22/L-%C3%A9lectro-enc%C3%A9phalogramme-de-PHP-existe-%21">svneeg</a> très simple.</p>
<p>Mon problème a plutôt été de trouver une manière d'architecturer le code afin de permettre une intégration rapide et facile dans un projet.</p>
<p>Évidement, l'utilisation des <a href="http://fr2.php.net/namespace">espaces de noms</a> s'est imposé.</p>
<p>Leur utilisation permet en effet de s'affranchir des éventuels problèmes de collision de nom au niveau des classes ou des constantes.</p>
<p>Cerise sur le gâteau, cela évite d'avoir des noms de classe du style <code>maClasseRienQuaMoiQuiVaBienPourFaireLeCafe</code>.</p>
<p>Cependant, il faut trouver une arborescence qui ait sémantiquement du sens tout en permettant d'identifier l'origine du code et une utilisation efficace de <a href="http://fr2.php.net/autoload">l'auto-chargement de classes</a> (sic).</p>
<p>Je me suis donc basé sur <a href="http://groups.google.com/group/php-standards/web/psr-0-final-proposal">les préconisations</a> du <a class="ln" href="http://groups.google.com/group/php-standards">PHP Standards Working Group</a> sur le sujet et j'ai commencé par adopter la convention suivante :</p>
<ol><li>Afin de satisfaire mon esprit narcissique, le vendeur, au sens du <a class="ln" href="http://groups.google.com/group/php-standards">PHP Standards Working Group</a>, est <code>mageekguy</code>.</li>
<li>L'ensemble du code concernant les <a href="http://en.wikipedia.org/wiki/Sparkline">sparklines</a> se trouve dans l'espace de nom <code>mageekguy\sparkline</code>.</li>
</ol>
<p>Petite précision, il est inutile de me dire en commentaire de ce billet que <code>\</code>, c'est de la merde comme séparateur pour les espaces de nommage, personnellement, je ne les voit même plus.</p>
<p>Bref, les trois premiers points de la recommandation du <a class="ln" href="http://groups.google.com/group/php-standards">PHP Standards Working Group</a> sont respectés.</p>
<p>Il ne me restait plus qu'à prendre en compte les suivants, mais j'ai alors découvert un problème.</p>
<p>Je voulais pouvoir écrire en PHP ceci :</p>
<blockquote><pre><code><?php $sparkline = new \mageekguy\sparkline(); ?><br /></code></pre>
</blockquote>
<p>Or, suivant les préconisations du <abbr title="PHP Standards Working Group">PHPSWG</abbr>, le fichier correspondant à la la classe <code>\mageekguy\sparkline</code> aurait du se trouver dans <code>/path/to/my/projetcts/classes/mageekguy/sparkline.php</code>, ce qui ne me va pas du tout.</p>
<p>Je souhaite en effet que l'intégralité du code concernant les <a href="http://en.wikipedia.org/wiki/Sparkline">sparklines</a> soit, en toute logique, dans un répertoire nommé <code>sparkline</code>.</p>
<p>J'ai donc décidé de passer outre aux recommandations du <abbr title="PHP Standards Working Group">PHPSWG</abbr>, et j'ai défini ma méthode d'<a href="http://fr2.php.net/autoload">auto-chargement de classes</a> (re-sic) de façon à ce qu'elle prenne en compte cette contrainte.</p>
<p>Au final, pour utiliser <a href="https://svn.mageekbox.net/repositories/sparkline/trunk/">sparkline</a> dans vos projets, il vous suffit :</p>
<ol><li>D'avoir à votre disposition une version de <a href="http://www.php.net">PHP</a> supérieure ou égale à la 5.3 avec le support de <a href="http://fr.wikipedia.org/wiki/GD_%28biblioth%C3%A8que%29"><code>gd</code></a>.</li>
<li>De vous mettre dans le répertoire de votre projet.</li>
<li>De faire un export du dépôt subversion de <a href="https://svn.mageekbox.net/repositories/sparkline/trunk/">sparkline</a> :
<blockquote><pre><code># svn export https://svn.mageekbox.net/repositories/sparkline/trunk mageekguy/sparkline</code></pre></blockquote>
</li>
</ol>
<p>En terme de code <a href="http://www.php.net">PHP</a>, il vous suffira d'écrire un code similaire à celui ci-dessous dans un fichier nommé par exemple <code>index.php</code> situé à la racine du répertoire de votre projet :</p>
<blockquote><pre><code><?php<br /><br />use mageekguy\sparkline;<br />use mageekguy\sparkline\writers;<br />use mageekguy\sparkline\layer\linechart;<br /><br />require(__DIR__ . '/mageekguy/sparkline/autoloader.php');<br /><br />$data = array(1, 5, 6, -9, 10, -4, 8, 10, 11, 34, 3, 1, -7, -23);<br /><br />$sparkline = new sparkline(100, 20, $data);<br />$sparkline<br /> ->setPadding(4)<br /> ->addLayer(new linechart\curves\line(3, 40, 50, 255))<br /> ->addWriter(new writers\png())<br /> ->write()<br />;<br /><br />?><br /></code></pre></blockquote>
<p>Si tout fonctionne correctement, après éxécution de ce code via votre navigateur, vous devriez obtenir ceci à l'écran :</p>
<p><img title="sparkline, mai 2010" style="margin: 0 auto; display: block;" alt="" src="http://blog.mageekbox.net/public/sparkline.png" /></p>
<p>Il est maintenant temps de détailler un peu le fonctionnement de <a href="https://svn.mageekbox.net/repositories/sparkline/trunk/">sparkline</a>.</p>
Le constructeur de <code>mageekguy\sparkline</code> prend trois arguments :<ol><li>La longueur de la <a href="http://en.wikipedia.org/wiki/Sparkline">sparkline</a>.</li>
<li>La hauteur de la <a href="http://en.wikipedia.org/wiki/Sparkline">sparkline</a>.</li>
<li>Les données que doit représenter la <a href="http://en.wikipedia.org/wiki/Sparkline">sparkline</a>.</li>
</ol>
<p>Le tableau <code>$data</code> contenant les données n'a pas besoin d'être associatif, chaque valeur correspondant à une itération sur les abscisses.</p>
<p>La méthode <code>mageekguy\sparkline::setPadding()</code> permet de définir l'écart entre la courbe et le bord de l'image.</p>
<p><a href="https://svn.mageekbox.net/repositories/sparkline/trunk/">Sparkline</a> fonctionne suivant le principe des calques, chaque calque correspondant à un composant de l'image finale et étant ajouté à l'aide de la méthode <code>mageekguy\sparkline::addLayer()</code>.</p>
<p>Vous pouvez donc définir un calque pour définir le fond de l'image, la grille de fond, le texte de fond, la forme de la courbe, l'axe des ordonnées, etc.</p>
<p>Attention, l'ordre des calques a son importance, puisqu'ils sont dessinés dans l'ordre dans lequel ils sont définis.</p>
<p>En conséquence, si vous créez un calque pour avoir un fond coloré après avoir défini le calque correspondant à la courbe, cette dernière ne sera pas visible sur l'image finale.</p>
<p>L'intégralité des calques disponibles actuellement est visible dans le répertoire <code>mageekguy/sparkline\layer</code> et ils sont classés en fonction de leur type.</p>
<p>Enfin, Il faut définir le ou les formats de sortie de l'image finale, à l'aide de la méthode <code>mageekguy\sparkline::addWriter()</code>.</p>
<p>Seul le format <a href="http://fr.wikipedia.org/wiki/Portable_Network_Graphics"><code>png</code></a> est actuellement disponible, vu que c'est celui qui s'adapte le mieux au style graphique des sparklines.</p>
<p>Une fois tout cela défini, la génération de l'image finale est déclenchée par un appel à la méthode <code>mageekguy\sparkline::write()</code>.</p>
<p>Pour l'instant, il n'y a pas encore de documentation, mais je pense que le code est suffisament limpique pour que des personnes motivées s'y retrouve rapidement, et je suis disponible en cas de problèmes via l'adresse sparkline[AT]mageekbox dot net.</p>http://blog.mageekbox.net/?post/2010/05/17/Sparkline#comment-formhttp://blog.mageekbox.net/?feed/atom/comments/125