The blog of 2017-07-15T20:50:06+00:00 http://www.guanzhui39.top Un environnement complet de tests avec Docker - jour 2 (tests d'IHM) 2015-10-19T00:00:00+00:00 http://www.guanzhui39.top/environnement-test-docker-regression-ihm-gemini <p>Ce billet est le second d’une <a href="/environnement-test-docker-behat/">série sur la création d’un environnement de test pour des projets web</a>. Aujourd’hui nous parlerons d’IHM, c’est-à-dire <strong>comment tester que l’interface (visuelle) de mon site n’est pas cassée</strong> sous tel ou tel navigateur.</p> <p>Je rappelle que l’objectif ce ces billets / outils est de pouvoir dire “Vous n’avez pas de raison valable de ne pas tester?:)</p> <p>## Introduction</p> <p>La question de <strong>tester une interface graphique est complexe</strong> pour plusieurs raisons :</p> <ul> <li><strong>manque de maturité</strong> des outils</li> <li>complexité de comparer l’incomparable (un site avec des publicités change à chaque rafraîchissement, ce n’est pas pour autant qu’il est cassé)</li> <li>complexité de gérer les changements de contenus (une page d’accueil avec des articles change tout le temps)</li> <li>gérer les <strong>faux-positifs</strong> (liés, par exemple, au lissage des polices qui peuvent être différents pour un même navigateur)</li> </ul> <p>### Mon retour sur quelques outils de test de régression</p> <p>Au fil du temps, j’ai testé pas mal d’outils de test de régression. Tous s’appuient sur des comparaisons de captures d’écrans (du site en entier, ou de blocs (divs, etc.) du site).</p> <p><strong>Navigateurs headless :</strong></p> <ul> <li><a href="https://github.com/Huddle/PhantomCSS">PhantomCss</a> : performant, mais souvent insuffisant car basé sur un navigateur virtuel. Le rendu, même sur des sites moyennement complexes, est souvent insatisfaisant et génère de nombreux faux-positifs)</li> <li><a href="http://garris.github.io/BackstopJS/">BackstopJs</a> : idem</li> </ul> <p><strong>Vrais navigateurs</strong> (qui plus est ces outils ont des connecteurs vers des solutions de virtualisation externe, de type <a href="https://saucelabs.com/">SauceLabs</a> ou <a href="https://www.browserstack.com/">BrowserStack</a>) :</p> <ul> <li><a href="https://github.com/bbc-news/wraith">Wraith</a> : développé par le BBC, efficace mais à l’usage la sélection de blocs est assez aléatoire (voire buggée). Cet outil génère une gallerie des screenshots très très pratique. Je n’ai jamais réussi à faire fonctionner <a href="https://github.com/andrewccadman/wraith-selenium">wraith-selenium</a> correctement.</li> <li><a href="https://github.com/facebookarchive/huxley">Huxley</a> : développé (mais abandonné) par Facebook, complexe à utiliser (on passe son temps à patcher le code source)</li> <li><a href="https://github.com/bem/gemini">Gemini</a> : simple et efficace, c’est lui qui a ma préférence aujourd’hui. Permet, comme Wraith, de générer un rapport HTML</li> </ul> <p><strong>Sass :</strong></p> <ul> <li><a href="https://ghostinspector.com/">ghostinspector.com/</a> : très joli, mais tout se fait au clickdrome avec une extension Chrome, je n’ai pas trouvé de moyen de le piloter automatiquement ou d’écrire ses propres scripts.</li> <li><a href="http://percy.io">percy.io</a> : j’aimerai bien tester, mais je n’ai pas d’invitation. Pour l’instant seul Firefox est supporté</li> </ul> <p>Pour les curieux, ceux que je n’ai fait que survoler :</p> <ul> <li><a href="https://github.com/xebia/VisualReview">VisualReview</a></li> <li><a href="https://github.com/gabrielrotbart/gatling">Gatling</a> (non, pas LE gatling qu’on connaît tous)</li> <li><a href="https://www.screenbeacon.com/">ScreenBeacon</a></li> <li><a href="http://shoov.io/">Shoov</a></li> <li><a href="https://github.com/webdriverio/webdrivercss">WebDriverCss</a></li> <li><a href="http://browsershots.org/">BrowserShots</a></li> <li>Bref, il existe pas mal d’outils différents, et ça bouge bien en ce moment.</li> </ul> <p>## Installation</p> <p>Nous allons donc utiliser Gemini. Voici un exemple de rapport HTML que l’on souhaite obtenir :</p> <p><img src="/images/2015-10-gemini.png" alt=" Tests de non régression responsive avec Gemini " /></p> <p>Si le site a subi des régression, <strong>les écarts seront mis en surbrillance comme suit</strong>:</p> <p><img src="/images/2015-10-gemini-fails.png" alt=" Tests de non régression responsive avec Gemini " /></p> <p>Là encore, j’ai préparé une image Docker pour vous:</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker pull qualiboo/testing-gemini </code></pre> </div> <p>On va s’appuyer sur le fichier <code class="highlighter-rouge">docker-compose.yml</code> que l’on a créé <a href="/environnement-test-docker-behat/">la dernière fois</a>.</p> <div class="highlighter-rouge"><pre class="highlight"><code>gemini: image: qualiboo/testing-gemini links: - hub - firefox - chrome volumes: - ./gemini:/var/work hub: image: qualiboo/testing-hub ports: - 4444:4444 firefox: image: qualiboo/testing-node-firefox ports: - 5900 links: - hub chrome: image: qualiboo/testing-node-chrome ports: - 5900 links: - hub </code></pre> </div> <p>Vous constatez que l?lt;strong>on utilise Selenium</strong>, avec des noeuds Chrome et Firefox.</p> <p>Vérifions l’installation. La commande suivante doit afficher la version de gemini:</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose run gemini version </code></pre> </div> <p>## Premiers tests</p> <p>Nous allons procéder en deux temps :</p> <ol> <li>faire les captures d’écrans qui serviront de référence pour les tests (<code class="highlighter-rouge">gemini capture</code>) ;</li> <li>comparer de nouvelles capture à ce référentiel pour voir s’il y a des régression (<code class="highlighter-rouge">gemini test</code>).</li> </ol> <p>Pour cela, créez un dossier <code class="highlighter-rouge">./gemini/suite</code> et créez-y le fichier <code class="highlighter-rouge">premier-test.js</code> avec le contenu suivant :</p> <div class="highlighter-rouge"><pre class="highlight"><code>var gemini = require('gemini'); gemini.suite('homepage', function(suite) { suite .setUrl('/') .setCaptureElements('#nav-menu'); .capture('menu'); }); </code></pre> </div> <p>Le code (JavaScript) est assez simple : on capture la <code class="highlighter-rouge">div</code> dont l’id est <code class="highlighter-rouge">#nav-menu</code> depuis la racine du site.</p> <p>Maintenant, créez le fichier <code class="highlighter-rouge">./gemini/.gemini.yml</code> qui va permettre de configurer Gemini :</p> <div class="highlighter-rouge"><pre class="highlight"><code>rootUrl: http://qualiboo.com gridUrl: http://hub:4444/wd/hub browsers: chrome: desiredCapabilities: browserName: chrome firefox: desiredCapabilities: browserName: firefox </code></pre> </div> <p>Nous indiquons ici à Gemini l’URL de base du site, les navigateurs souhaités, et comment se connecter à Selenium Grid..</p> <p><strong>Lancez la capture du référentiel</strong> (ça prend quelques secondes):</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose run gemini capture </code></pre> </div> <p>Vérifiez bien à chaque fois que vous faites une <code class="highlighter-rouge">capture</code> que les images générées (dossier <code class="highlighter-rouge">./gemini/gemini/screens</code>) correspondent à vos attentes. Elle serviront de référentiel pour les tests. Voilà, <strong>les tests peuvent être lancés</strong> :</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose run gemini test </code></pre> </div> <p>Après quelques secondes/minutes, vous voilà avec un “beau?rapport HTML dans <code class="highlighter-rouge">./gemini/gemini-report/index.html</code>. Il ne reste plus qu’?l’ouvrir dans votre navigateur préferé?lt;/p> <p>## Tester le responsive design</p> <p>Avant toute chose, il faut savoir que <strong>le monde de tests d’IHM n’est pas parfait</strong>. La version du ChromeDriver actuelle pour Linux (2.20) ne permet pas de capturer un bloc qui est plus grand que le viewport du navigateur. Si vous voulez capturer toute la page (le <code class="highlighter-rouge">body</code>), il faudra passer par Firefox.</p> <p>Il est simple avec Gemini de forcer certains tests à être exécutés uniquement dans Firefox ou Chrome. Dans le fichier <code class="highlighter-rouge">./gemini/.gemini.yml</code> ajoutez le contenu suivant:</p> <div class="highlighter-rouge"><pre class="highlight"><code>(...) sets: firefox: files: - suite/firefox-only browsers: - firefox chrome: files: - suite/chrome-only browsers: - chrome </code></pre> </div> <p>Tous les tests qui seront placés dans le dosser <code class="highlighter-rouge">./gemini/suite/firefox-only</code> seront désormais lancés uniquement par firefox (et même chose pour <code class="highlighter-rouge">./gemini/suite/chrome-only</code>).</p> <p>Comme nous souhaitons capturer la page entière (le <code class="highlighter-rouge">body</code>), plaçons notre fichier de test (<code class="highlighter-rouge">./gemini/suite/premier-test.yml</code>) dans le dossier <code class="highlighter-rouge">firefox-only</code></p> <p><strong>Nous allons tester notre site sous des résolutions différentes</strong> ; pour cela, modifiez le fichier <code class="highlighter-rouge">premier-test.yml</code>.</p> <p>Nous allons changer la résolution de l’écran avant chaque test, et cette fois nous capturons donc le <code class="highlighter-rouge">body</code> :</p> <div class="highlighter-rouge"><pre class="highlight"><code>var gemini = require('gemini'); gemini.suite('homepage', function(suite) { suite .setUrl('/') .setCaptureElements('body') .capture('homepage', function (actions) { actions.setWindowSize(1200, 1024).wait(1000); }) .capture('homepage tablet', function (actions) { actions.setWindowSize(768, 1024).wait(1000); }) .capture('homepage mobile', function (actions) { actions.setWindowSize(320, 568).wait(1000) }); }); </code></pre> </div> <p>N’oubliez pas de mettre à jour le référentiel de screenshots :</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose run gemini capture </code></pre> </div> <p>A vous désormais de lancer les tests à votre guise (par exemple à chaque Push sur votre dépôt, à l’aide de Jenkins).</p> <p>## Aller plus loin et faire abstraction du contenu dynamique</p> <p>Je l’ai évoqué, <strong>un problème récurrent des tests de régression d’IHM concerne le contenu dynamique</strong> (publicités, actualités?. Il faut trouver un moyen de faire abstraction de ce type de contenu.</p> <p>Le seul moyen simple que j’ai trouvé consiste à remplacer ce contenu par du “Lorem ipsum? Il faut alors, avant chaque test, exécuter un script JavaScript pour ce faire:</p> <div class="highlighter-rouge"><pre class="highlight"><code>suite .setUrl('/') .setCaptureElements('body') .before(function(actions, find) { actions.executeJS(function(window) { // replacing images $('img.my-class').attr('src', ''); // replacing some texts $('p.another-class, .title, (etc.)').html('Lorem ipsum dolor sit amet'); // advertising $('.pub').html('PUBLICITE'); // etc. }) .capture(...) </code></pre> </div> <p>Dès lors, toutes les images et contenus dynamiques sont remplacés. Il est assez simple de factoriser ce code dans un fichier à part pour le réutiliser partout.</p> <p>Le contenu du site a changé, donc, <strong>oui, nos tests sont alors incomplets et éloignés de la réalité</strong>. Mais si on ne le fait pas, le risque est que ces tests ne soient jamais pertinents car fondés uniquement sur de faux-positifs. <strong>La suppression des publicités est un compromis pour être pragmatique</strong>.</p> <p>## Conclusion</p> <p>Vous l’avez vu, <strong>vous avez là un moyen simple de tester l’IHM de votre site sous différentes résolutions</strong>, à l’aide de Docker. Si vous avez un feedback, n’hésitez pas à laisser un commentaire ; et si ce billet vous plaît, n’hésitez pas à le partager :)</p> Un environnement complet de tests avec Docker - jour 1 (Behat) 2015-10-12T00:00:00+00:00 http://www.guanzhui39.top/environnement-test-docker-behat <p>Ce billet est le premier d’une série sur la création d’un environnement de test pour des projets web. A la fin de cette série, vous disposerez d’un environnement Docker capable de lancer facilement :</p> <ul> <li>des tests fonctionnels avec Behat, Selenium Grid, Chrome, Firefox et PhantomJs</li> <li>des tests de non régression d’interface utilisateur</li> <li>des tests de performance</li> </ul> <p>Aujourd’hui nous allons démarrer avec les tests Behat, exécutés dans un navigateur grâce à Selenium Grid et Docker.</p> <h2 id="docker">Docker</h2> <p>Je ne vais pas refaire un billet sur ce que d’autres ont fait avec bien <a href="http://geoffrey.io/what-is-docker.html">plus de clarté</a> que je ne saurais faire. Sachez juste que Docker va vous permettre de “virtualiser?une partie de votre système, permettant donc d’installer plein d’outils sans pourrir votre machine, et sans risque de conflits de versions.</p> <p>Pour tous les billets, je suppose que Docker et Docker-Compose sont installés sur votre machine. Si ce n’est pas le cas, il est encore temps :</p> <ul> <li><a href="https://docs.docker.com/installation/mac/">Mac OS X</a></li> <li><a href="https://docs.docker.com/installation/ubuntulinux/">Ubuntu</a></li> <li><a href="https://docs.docker.com/installation/">Autres systèmes</a></li> </ul> <p>Et pour Docker-Compose :</p> <p>Linux:</p> <div class="highlighter-rouge"><pre class="highlight"><code>curl -L https://github.com/docker/compose/releases/download/1.4.2/docker-compose-`uname -s`-`uname -m` &gt; /usr/local/bin/docker-compose </code></pre> </div> <p>OSX:</p> <div class="highlighter-rouge"><pre class="highlight"><code>Déjà installé avec Docker (au passage, je ne comprendrais jamais pourquoi on n'a pas la même processus d'installation sur Max oO) </code></pre> </div> <p>Windows:</p> <div class="highlighter-rouge"><pre class="highlight"><code>curl -L https://github.com/docker/compose/releases/download/1.4.2/docker-compose-`uname -s`-`uname -m` &gt; /usr/local/bin/docker-compose </code></pre> </div> <p>## Behat</p> <p>En deux mots, Behat est un <a href="http://www.guanzhui39.top/php/behat-jour-1-comment-tester-son-produit-scrum/">outil de test</a>. Il permet de convertir des phrases (langues naturelles) en code source. <strong>Il est du coup possible d’associer ces phrases à des actions utilisateurs dans un navigateur</strong> (Chrome, Firefox? ou un émulateur de navigateur (on parle de “navigateur headless?.</p> <p>Pour vous simplifier la vie, j’ai préparé des images “prêtes à l’emploi? Commencez par télécharger l’image principale (c’est l’heure du premier café):</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker pull qualiboo/testing-behat </code></pre> </div> <p>Voilà, vous voilà avec un Behat prêt à l’emploi. Créez un dossier local <code class="highlighter-rouge">behat</code> (et un sous-dossier <code class="highlighter-rouge">build</code> pour les logs) pour stocker les tests Behat, puis demandons à Behat d’y créer l’arborescence de base, en montant un volume :</p> <div class="highlighter-rouge"><pre class="highlight"><code>mkdir -p behat/build docker run -v $(pwd)/behat:/var/work qualiboo/testing-behat --init </code></pre> </div> <p>Vous voilà presque prêt(e) à démarrer.</p> <p>Behat interagit avec des Fonctionnalités, décrites dans des fichiers <code class="highlighter-rouge">*.features</code>. <strong>Il est temps d’écrire notre premier test</strong> ; créez le fichier <code class="highlighter-rouge">./behat/premier-pas.feature</code>:</p> <div class="highlighter-rouge"><pre class="highlight"><code>Feature: My first feature Scenario: Visitor can follow link to get information on blogpost Given I am on "http://www.guanzhui39.top" When I follow "Un outil pour améliorer la qualité d'un projet web" Then I should see "qualiboo" And the url should match "/outil-mesure-qualite-projet-web/" </code></pre> </div> <p>Si vous lancez Behat tel quel, Behat ne saura pas quoi faire de ces phrases. Nous avons besoin d’importer Mink, une extension bien pratique qui permet d’utiliser des expressions liées à la navigation web. Ouvrez le fichier <code class="highlighter-rouge">./behat/bootstrap/FeatureContext.php</code>, et ajoutez-y le code suivant:</p> <div class="highlighter-rouge"><pre class="highlight"><code>public function __construct(array $parameters) { $this-&gt;useContext('mink', new \Behat\MinkExtension\Context\MinkContext($parameters)); } </code></pre> </div> <p>Nous venons de dire à Behat que nous souhaitons utiliser l’extension Mink.</p> <p>Lançons les tests:</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker run --rm -t -v $(pwd)/behat:/var/work qualiboo/testing-behat </code></pre> </div> <p><strong>Tout est vert</strong> ; les tests sont donc passés (si vous ne me faites pas confiance essayez de modifier le fichier feature ;) )</p> <p><strong>En quelques minutes, nous disposons donc d’un robot capable de vérifier rapidement et à l’infini que mon blog est en ligne</strong>, que je peux suivre un lien et que suivre ce lien me fait changer de page. Pas mal !</p> <p>N’oubliez pas que vous pouvez connaître la liste des expressions prêtes à l’emploi grâce à la commande suivante :</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker run --rm -t -v $(pwd)/behat:/var/work qualiboo/testing-behat -dl </code></pre> </div> <p>Pour aller plus loin sur la manière d’écrire des fonctionnalités ou sur les bonnes pratiques Behat, <strong>je vous invite à lire cet <a href="http://communiquez.lepine.pro/download/developpement-pilote-par-le-comportement-tome2.pdf">ebook</a> Open Source que j’ai écrit il y a un moment sur le sujet</strong>.</p> <p>## Un “vrai?navigateur</p> <p>Bon, on a nos tests, mais en réalité jusqu’ici nous utilisons un navigateur “virtuel? Ce navigateur est incapable d’interpréter du javascript. Il faut aller plus loin.</p> <p>Nous avons besoin maintenant de lancer nos tests dans un vrai navigateur. Nous pourrions utiliser le nôtre, mais n’oubliez pas que désormais on dispose de Docker, ce qui est un énorme avantage : <strong>grâce à Docker on peut fixer une version, avoir un navigateur qui n’est pas pollué par 20 extensions</strong> qui faussent les résultats des tests (AdBblock &amp; Co)?lt;/p> <p>Nous allons utiliser Selenium, qui permet de piloter des navigateurs de manière très efficace, plus spécifiquement Selenium Grid, qui permet en plus de gérer une grille de navigateurs (dans des versions et résolutions différentes).</p> <p>Pour simplifier les choses, là encore je vous ai préparé des images Docker. Nous allons utiliser Docker-Compose pour les télécharger et les relier entre-elles.</p> <p>Il vous suffit de créer un fichier <code class="highlighter-rouge">docker-compose.yml</code> pour les utiliser :</p> <div class="highlighter-rouge"><pre class="highlight"><code>behat: image: qualiboo/testing-behat volumes: - ./behat:/var/work links: - hub environment: website: http://www.guanzhui39.top hub: image: qualiboo/testing-hub ports: - 4444:4444 firefox: image: qualiboo/testing-node-firefox ports: - 5900 links: - hub environment: REMOTE_HOST_PARAM: "-maxSession 3 -browser browserName=firefox,maxInstances=3" chrome: image: qualiboo/testing-node-chrome ports: - 5900 links: - hub </code></pre> </div> <p><strong>C’est ce fichier que nous allons enrichir au fil des billets, pour obtenir un environnement de test complet</strong>.</p> <p>Grâce à ce fichier, Docker Compose va télécharger les images:</p> <ul> <li>“qualiboo/testing-behat? qui permet de lancer Behat</li> <li>“qualiboo/testing-hub? qui contient Selenium Grid</li> <li>“qualiboo/testing-node-firefox? qui contient Firefox</li> <li>“qualiboo/testing-node-chrome? qui contient Chrome</li> </ul> <p>Lançons Docker Compose (c’est l’heure du second café) :</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose up -d </code></pre> </div> <p>Et là, la magie opère. Commencez par tagger la fonctionnalité avec <code class="highlighter-rouge">@javascript</code> pour indiquer à Behat que nous voulons utiliser un vrai navigateur :</p> <div class="highlighter-rouge"><pre class="highlight"><code>@javascript Feature: My first feature </code></pre> </div> <p>Les images Docker que vous venez d’installer sont pré-configurées. Vous pouvez désormais choisir quel navigateur utiliser simplement en utilisant l’option <code class="highlighter-rouge">--profile=&lt;navigateur</code>.</p> <p>Par exemple:</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose run behat --profile=firefox </code></pre> </div> <p>Notez que vous avez quatre navigateur à votre disposition:</p> <ul> <li>Goutte (aucun paramètre)</li> <li>PhantomJs (<code class="highlighter-rouge">--profile=phantomjs</code>)</li> <li>Chrome (<code class="highlighter-rouge">--profile=chrome</code>)</li> <li>Firefox (<code class="highlighter-rouge">--profile=firefox</code>)</li> </ul> <p>Notez également que désormais Behat a été lancé par Docker Compose (commande <code class="highlighter-rouge">docker-compose</code>) et non simplement Docker.</p> <p>Dernière chose: vous pouvez superviser vos noeuds Selenium à l’adresse <a href="http://localhost:4444/grid/console">http://localhost:4444/grid/console</a>.</p> <p>Ah au fait : si vous voulez plus de noeuds Selenium (par exemple, disposer de plusieurs Firefox pour paralléliser les tests), n’hésitez pas à utiliser les capacités de scaling de docker. Par exemple, pour avoir 5 firefox:</p> <div class="highlighter-rouge"><pre class="highlight"><code>docker-compose scale firefox=5 </code></pre> </div> <p>Voilà, c’est tout pour ce premier billet. la prochaine fois, on parlera de tests de non régression visuelle.</p> <p>Si le sujet du test vous intéresse, ou si que vous voulez que j’ajoute d’autres types d’outils / tests à cette série de billets, n’hésitez pas à laisser un commentaire (ou même pour m’encourager ^^).</p> Un outil pour améliorer la qualité d'un projet web 2015-09-14T00:00:00+00:00 http://www.guanzhui39.top/outil-mesure-qualite-projet-web <p><strong>Le mot “qualité? quand on parle d’un logiciel, est très ambigu</strong> : parle-t-on de faible complexité du code source ? D’évolutivité, de performance, d’accessibilité ? Que dire à propos des tests et de l’intégration continue ? La gestion des anomalies (Issue tracker) fait-elle partie de la qualité ? En bref, que siginifie “qualité logicielle?? Et <strong>comment la mesurer ?</strong></p> <p>Les premières véritables tentatives de définitions datent des années 70, et de McCall et Boehm. Dès 1996, le standard <a href="https://en.wikipedia.org/wiki/ISO/IEC_9126">ISO 9126</a>, régulièrement amélioré, donne une liste de caractéristiques possibles, puis les standard <a href="https://fr.wikipedia.org/wiki/S%C3%A9rie_des_normes_ISO_9000">ISO 9000:2000</a> viennent affiner les concepts. Une grille de facteurs de la qualité logicielle est établie et mature.</p> <p>## Grille de facteurs de qualité</p> <p>Personnellement, la grille que j’utilise dans mes stratégies de test est celle proposée par le groupe <a href="http://www.istqb.org/">ISTQB</a> :</p> <ul> <li>** Exactitude**: Degré de conformité par rapport aux spécifications</li> <li>** Fiabilité**: Capacité d’un programme à accomplir sa fonction sans défaillance</li> <li>** Efficacité**: Capacité du programme à exploiter efficacement (de manière optimale) les ressources (CPU, mémoire? à disposition?lt;/li> <li>** Intégrité**: Capacité de protéger le système et les données qu’il manipule</li> <li>** Ergonomie**: Capacité du programme à être utilisé avec peu d’efforts</li> <li>** Maintenabilité**: Capacité à faciliter la localisation et la correction d’anomalies</li> <li>** Testabilité**: Capacité du programme à se prêter à une vérification (tests) de bon fonctionnement</li> <li>** Flexibilité**: Capacité d’évolution et d’adaption à de nouvelles fonctionnalités</li> <li>** Portabilité**: Facilité de changement d’environnements (OS, matériel?</li> <li>** Réutilisabilité**: Possibilité de réutilisation de composants dans d’autres projets</li> <li>** Interopérabilité**: Capacité du programme à être associé à un autre (échange de données, utilisation?</li> </ul> <p>Disposer d’une grille, c’est beau, c’est bien?Mais, et après ? On peut s’en servir pour élaborer une stratégie de test, mais ça reste très théorique. Comment mesurer tout ça ?</p> <p>Si pour la performance (au sens premier, à savoir l’exploitation efficace des ressources mises à disposition), un consensus peut facilement émerger (mesure du temps de réponse, mesure de la <a href="https://en.wikipedia.org/wiki/Asymptotic_computational_complexity">complexité asymptotique</a>), qu’en est-il des autres facteurs ? Pas facile?lt;/p> <p>## Mesurer les facteurs de qualité prioritaires</p> <p>C’est un sujet que j’ai vraiment beaucoup (beaucoup beaucoup) étudié, et à force de confrontations à la réalité des projets, je me suis rendu compte que pour les projets web, <strong>certains facteurs sortent du lot</strong>, car généralement considérés par les équipes techniques comme “plus importants?:</p> <ul> <li>la <strong>maintenabilité</strong></li> <li>l?lt;strong>évolutivité</strong></li> <li>la <strong>fiabilité</strong></li> </ul> <p>Il existe des métriques logicielles, statiques ou dynamiques, qui tentent de mesurer ces aspects :</p> <ul> <li><a href="https://en.wikipedia.org/wiki/Maintainability">Indice de maintenabilité</a>, l?lt;a href="https://en.wikipedia.org/wiki/Programming_complexity">absence de cohésion des méthodes</a> ou le <a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity">nombre de complexité cyclomatique</a> ?pour la maintenabilité et l’évolutivité ;</li> <li>le <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=210575">CRAP</a>, la couverture ou le taux de résistance aux mutations le taux de détection de bugs?pour la fiabilité ;</li> <li>le <a href="https://en.wikipedia.org/wiki/Software_package_metrics">couplage</a>, l?lt;a href="https://en.wikipedia.org/wiki/Software_package_metrics">abstraction</a>?pour l’évolutivité et la réutilisabilité.</li> </ul> <p>Malheureusement, <strong>les outils pour mesurer ces métriques sont rares</strong> ; c’est pour ça que j’ai développé <a href="http://phpmetrics.org">PhpMetrics</a>, ou encore <a href="https://github.com/Halleck45/MutaTesting">MutaTesting</a>, tous deux Open Source.</p> <p>Une fois que l’on sait collecter ponctuellement ces métriques, reste encore à rendre la collecte systématique ; pour ça on utilise souvent <a href="http://www.sonarqube.org/">Sonar</a> ou <a href="https://jenkins-ci.org/">Jenkins</a>. Or Sonar se focalise plus sur la dette technique (<a href="https://en.wikipedia.org/wiki/SQALE">SQALE</a>), et Jenkins n’est clairement pas fait pour ça (mais pour de l’intégration continue). <strong>Il faut une énergie immense dans tous les sens pour obtenir un résultat acceptable, à défaut de convenable</strong>.</p> <p>Autre problème : quand bien même on a mis en place ces outils, il reste à rendre les résultats lisibles et clairs, ce qui (croyez-moi) pour le coup n’est vraiment pas simple.</p> <p>Encore un autre problème : on ne se focalise que sur un code source ; or je l’ai dit, un projet c’est bien plus : on a souvent plusieurs dépôts, un tracker de bugs?Si vraiment on veut mesurer la maintenabilité d’un projet, <strong>on doit par exemple regarder l’évolution du nombre de tickets ouverts</strong>, l’évolution du taux de détection des bugs (DDP), . A ma connaissance, il n’existe que peu d’outils de mesure qui prennent en compte un projet dans sa globalité (je pense à VisualStudio ou Cast software), et aucun pour PHP.</p> <p>## Qualiboo.com, un outil de mesure en ligne</p> <p>C’est ce qui m’a amené à réflechir à la création d’un outil capable d’aider à mesurer un projet web dans son ensemble, en collectant un grand nombre de métriques variées. Bien sûr cet outil ne pourrait pas mesurer la “qualité?d’un projet web au sens propre, mais aiderait à fournir des indicateurs. Ce serait un assistant à la qualité, ou à défaut à la détection d’éléments susceptibles de mener à la “non-qualité?</p> <p>Il existe plusieurs très bons outils en ligne d’analyse de code pour PHP : <a href="https://insight.sensiolabs.com/">Sensio Insight</a>, <a href="https://scrutinizer-ci.com/">Scrutinizer</a>?Mais tous ont ces inconvénients que j’ai cités : ils ne se focalisent que sur le code source, et qui plus est sur un seul dépôt de code (pas très pratique quand on fait du microservice par exemple, avec plein de dépôts Git pour un seul projet).</p> <p><strong>C’est pour ces raisons que j’ai construit <a href="https://www.qualiboo.com">qualiboo.com</a></strong>. C’est une application de suivi de la qualité d’un projet web :</p> <ul> <li><strong>analyse du code</strong> JavaScript et PHP</li> <li><strong>aggrégation</strong> d’analyses sur plusieurs dépôts de code (Git, Gitlab et Github)</li> <li>analyse du <strong>tracker de bugs</strong> (Redmine et Github)</li> <li>suivi de l?lt;strong>intégration continue</strong> (Jenkins - bientôt Travis-ci)</li> <li>suivi des <strong>dépendances</strong> (licenses, vulnérabilités connues?</li> <li>suivi de l?lt;strong>activité</strong> Git</li> <li>rapports très graphiques en “mode TV?lt;/li> <li>et très vite bien plus :)</li> </ul> <p>Voici un aperçu de l’application:</p> <p><img src="/images/2015-09-qualiboo-overview.jpg" alt=" Aperçu de www.qualiboo.com " /></p> <p>C’est un projet à la fois gratuit et payant : gratuit pour les projets Open Source, payant pour les entreprises. Payant ? Si vous me connaissez, vous savez que ça n’est pas fréquent chez moi ; 100% de mes projets sont Open source, libres et gratuits. Oui, mais cette fois j’ai des coûts d’exploitation (serveurs) élevés qu’il me faut rentabiliser, et surtout j’aimerai vraiment améliorer sans cesse la qualité du service rendu par ce site, et donc pouvoir m’y investir pleinement, en prenant le temps qu’il faut pour y travailler efficacement, et pas seulement “vite fait?le matin ou le soir.</p> <p>Le projet est encore en jeune, mais <strong>j’apprécierai vraiment <a href="https://docs.google.com/forms/d/1fEO59O6z5UErPmZL1Fd_2X9WPfaf1zGj2jMSxIKatSo/viewform?usp=send_form">votre feedback</a></strong> : cet outil vous paraît-il utile ? Adapté ? Seriez-vous prêt à l’utiliser ? Que manque t-il à votre avis ?</p> <p>Et si vous pensez que qualiboo.com peut avoir un intérêt, n’hésitez pas à en parler autour de vous :) . Merci !</p> Open source, libre et gestion des medias 2015-02-25T00:00:00+00:00 http://www.guanzhui39.top/open-source-libre-medias <p>Composer, Bower, NPM?autant d’outils pour gérer les dépendances techniques de nos projets. C’est bien. Mais quid de la gestion des licenses ? Et que faire des médias (images, sons, vidéos) libres ou open source que nous utilisons ?</p> <p>Prenons le problème des médias. Il existe des outils (<a href="https://www.openhub.net">OpenHub</a> par exemple), mais rien de vraiment lié au quotidien du développeur. Jusqu’ici j’avais tendance à noter les images que j’utilise dans un fichier texte. Mais cette démarche est un peu brouillonne, et à long terme je m’y perd entre les images qui sont vraiment utilisées sur mon site et celles que j’ai téléchargées “pour tester?</p> <p>D’où l’idée de créer un outil pour me faciliter la gestion des médias libres dans un projet : <a href="https://github.com/Halleck45/OSS">OSS</a>. Les objectifs sont :</p> <ul> <li>d’inciter les développeurs à déclarer explicitement les médias libres qu’ils utilisent ;</li> <li>d’aider les développeurs à s’y retrouver dans leur gestion des licences ;</li> <li>de rationnaliser les licenses en utilisant le référentiel <a href="http://spdx.org/licenses/">SPDX</a>.</li> </ul> <p>Compatible avec pas mal de plate-formes grâce à <a href="https://github.com/mitchellh/gox">Gox</a>, vous pouvez <a href="https://bintray.com/halleck45/OSS/bin/view">télécharger les binaires pour votre OS ici</a>.</p> <p>Au premier usage, lancez simplement la commande <code class="highlighter-rouge">oss init</code>. Cette dernière va chercher le référentiel SPDX et va créer le fichier <code class="highlighter-rouge">.oss</code> à la racine de votre projet. C’est ce fichier qui va désormais servir d’annuaire de vos médias.</p> <p>Ensuite c’est assez simple ; les commandes sont proches de celles de Git :</p> <ul> <li><code class="highlighter-rouge">oss add &lt;licence&gt; &lt;fichier&gt;</code> : référencer un fichier</li> <li><code class="highlighter-rouge">oss rm &lt;fichier&gt;</code> : déréférencer un fichier</li> <li><code class="highlighter-rouge">oss status</code> : état du référentiel, liste l’ensemble des médias référencés</li> <li><code class="highlighter-rouge">oss show</code> <fichier> : informations sur un fichier</fichier></li> </ul> <p>Un fichier apparaît en rouge quand il n’est pas trouvé dans le projet.</p> <p><img src="/images/2015-02-oss.png" alt="Exemple de sortie de la commande oss status" /></p> <p>Un des objectifs étant d’aider les développeurs à s’y retrouver dans la gestion des licenses, l’outil vient avec les commandes suivantes :</p> <ul> <li><code class="highlighter-rouge">oss licenses</code> : liste les licenses du référentiel SPDX</li> <li><code class="highlighter-rouge">oss search &lt;licence&gt;</code> : recherche une licence</li> </ul> <p>Si la licence n’existe pas lors de l’ajout d’un média, l’outil suggerera une license phonétiquement proche. <strong>Il est impossible d’ajouter un média si sa licence ne fait pas partie du référentiel SPDX</strong>.</p> <p>## Le vrai problème</p> <p>J’aimerai un outil capable de répertorier l’ensemble des licenses des briques d’un projet. J’aimerai beaucoup ajouter à OSS une fonction “scan? qui découvrirait les licences des dépendances Bower, Composer, Npm, Gem?lt;/p> <p>Techniquement rien de bien compliqué ; le code est quasi prêt. Non, le vrai problème vient des développeurs. En effet, rares sont les outils de gestion de dépendances qui imposent / incitent à déclarer une licence valide. Les licenses sont souvent vides ou inexploitables.</p> <p>Et même si c’était le cas, un problème majeur vient des outils de gestion de dépendances eux-mêmes. Prenez Bower par exemple ; il est possible d’obtenir des informations sur un paquet grâce à l’API. Par exemple la requête HTTP <code class="highlighter-rouge">http://bower.herokuapp.com/packages/jquery</code> nous donnera :</p> <figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="nt">"name"</span><span class="p">:</span><span class="s2">"jquery"</span><span class="p">,</span><span class="nt">"url"</span><span class="p">:</span><span class="s2">"git://github.com/jquery/jquery.git"</span><span class="p">}</span></code></pre></figure> <p>Mais comme vous le voyez, aucune info sur la license. Il faut alors des rustines de rustines pour réussir à récupérer la bonne licence dans le fichier LICENSE du dépôt Git associé.</p> <p>Et ce n’est q’un exemple ! Bref, le vrai problème, c’est que <strong>les développeurs, pourtant fervents utilisateurs de l’Open Source, ne sont pas encore habitués à intéragir avec le logiciel libre</strong>.</p> <p>A titre d’exemple, il y a quelques jours je suis intervenu sur un projet bien entamé, qui utilise un composant NodeJs spécifique. Curieux, j’ai ouvert le fichier LICENSE du composant en question ; et là, surprise : le composant n’était pas forcément si libre de droits que ça. Lorsque j’ai fait part de ces informations à l’équipe technique, j’ai eu le droit comme réponse à :</p> <blockquote> <p>“Mais pourtant c’est Github, on peut récupérer le code source, donc c’est gratuit?lt;/p> </blockquote> <p>Non ! <strong>Tout ce qui est sur Github n’est pas gratuit</strong>. D’ailleurs par défaut, tout projet déposé sur Github est propriétaire, sauf avis contraire dans les sources. Mettre son projet sur Github c’est bien, mais n’oublions pas d’y associer une vraie licence, exploitable et claire.</p> <p>Il existe des <a href="http://spdx.org/licenses/">référentiels</a> assez complets et prêts à l’emploi ; il est temps de rendre nous outils compatibles avec monde du libre.</p> Semver, Git et tags : automatisation 2015-01-07T00:00:00+00:00 http://www.guanzhui39.top/semver-automatique-au-build <p>Pour résumer, le <a href="http://semver.org/lang/fr/">sémantique versionning</a> est une logique de fabrication des numéros de version d’un produit où l’on identifie une version v1.2.3 telle que décrite sur le site officiel :</p> <blockquote> <p>Étant donné un numéro de version MAJEUR.MINEUR.CORRECTIF, il faut incrémenter :</p> <p>le numéro de version MAJEUR quand il y a des changements rétro-incompatibles, le numéro de version MINEUR quand il y a des changements rétro-compatibles, le numéro de version de CORRECTIF quand il y a des corrections d’anomalies rétro-compatibles</p> </blockquote> <p><strong>La prise de décision d’incrémenter une version ne peut être que manuelle</strong> : il faut un humain pour savoir si ce qui a été modifié concerne un correctif ou une nouvelle fonctionnalité?lt;/p> <p><strong>Par contre, il peut-être utile de répercuter cette prise de décision automatiquement</strong>. Par exemple pour tagger un dépôt Git, ou encore pour changer un fichier de code source qui contiendrait la version actuelle en dur. C’est ce que j’ai fait pour <a href="http://www.phpmetrics.org">PhpMetrics</a>.</p> <p>À l’heure où j’écris ces lignes, la version actuelle de PhpMetrics est la v1.2.0, comme nous le montre la commande suivante :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">php phpmetrics.phar --version</code></pre></figure> <p>La version de l’application est stockée en dur dans le fichier de création du phar (<code class="highlighter-rouge">build.php</code>):</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$app</span> <span class="o">=</span> new Hal<span class="se">\.</span>..<span class="se">\P</span>hpMetricsApplication<span class="o">(</span><span class="s1">'...'</span>, <span class="s1">'1.2.1'</span><span class="o">)</span>;</code></pre></figure> <p>À chaque montée de version, je dois donc penser à modifier ce fichier, puis à créer le tag Git ; ce que j’oublie tout le temps de faire?Il me faut donc l’automatiser.</p> <p>Première étape : trouver un moyen simple de stocker mon numéro de version actuelle et de l’incrémenter. Ma première idée était de me tourner vers les tags Git en parsant le résultat d’un <code class="highlighter-rouge">git describe --tags</code>, mais j’ai trouvé plus simple : <a href="https://github.com/flazz/semver/">semver</a>, un outil qui fait ça très bien. Pour incrémenter une version je n’ai plus qu’?lancer, au choix :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">semver inc major semver inc minor semver inc patch</code></pre></figure> <p>Et je récupère la version actuelle avec la commande <code class="highlighter-rouge">semver tag</code>. (les infos sont stockées dans un fichier <code class="highlighter-rouge">.semver</code> à la racine du projet).</p> <p>Au lieu de mettre en dur la version dans mes sources, j’utilise donc un tag:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$app</span> <span class="o">=</span> new Hal<span class="se">\.</span>..<span class="se">\P</span>hpMetricsApplication<span class="o">(</span><span class="s1">'...'</span>, <span class="s1">'&lt;VERSION&gt;'</span><span class="o">)</span>;</code></pre></figure> <p>Il faut désormais remplacer ce tag par la vraie version récupérée avec <code class="highlighter-rouge">semver tag</code>. <code class="highlighter-rouge">sed</code> est mon meilleur allié :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sed -i <span class="s2">"s/&lt;VERSION&gt;/</span><span class="sb">`</span>semver tag<span class="sb">`</span><span class="s2">/g"</span> build.php</code></pre></figure> <p>Reste enfin à créer un tag git pour cette release :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">git tag -a <span class="k">$(</span>semver tag<span class="k">)</span> -m <span class="s2">"tagging </span><span class="k">$(</span>semver tag<span class="k">)</span><span class="s2">"</span></code></pre></figure> <p>Et voilà le travail ! Un petit Makefile et le tour est joué. Voici pour les plus curieux le Makefile de PhpMetrics:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">REPLACE</span><span class="o">=</span><span class="sb">`</span>semver tag<span class="sb">`</span> <span class="c"># Build phar</span> build: <span class="nb">test</span> @echo Copying sources @mkdir -p /tmp/phpmetrics-build @cp <span class="k">*</span> -R /tmp/phpmetrics-build @rm -Rf /tmp/phpmetrics-build/vendor /tmp/phpmetrics-build/composer.lock @echo Releasing phar @sed -i <span class="s2">"s/&lt;VERSION&gt;/</span><span class="sb">`</span>semver tag<span class="sb">`</span><span class="s2">/g"</span> /tmp/phpmetrics-build/build.php @echo Installing dependencies @cd /tmp/phpmetrics-build <span class="o">&amp;&amp;</span> composer.phar install --no-dev --optimize-autoloader --prefer-dist @echo Building phar @cd /tmp/phpmetrics-build <span class="o">&amp;&amp;</span> php build.php @cp /tmp/phpmetrics-build/build/phpmetrics.phar build/phpmetrics.phar @rm -Rf /tmp/phpmetrics-build @echo Testing phar ./vendor/bin/phpunit -c phpunit.xml.dist --group<span class="o">=</span>binary <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"Done"</span> <span class="c"># Run unit tests</span> <span class="nb">test</span>: ./vendor/bin/phpunit -c phpunit.xml.dist <span class="c"># Publish new release. Usage:</span> <span class="c"># make tag VERSION=(major|minor|patch)</span> <span class="c"># You need to install https://github.com/flazz/semver/ before</span> tag: @semver inc <span class="k">$(</span>VERSION<span class="k">)</span> @echo <span class="s2">"New release: </span><span class="sb">`</span>semver tag<span class="sb">`</span><span class="s2">"</span> <span class="c"># Tag git with last release</span> git_tag: git tag -a <span class="k">$(</span>semver tag<span class="k">)</span> -m <span class="s2">"tagging </span><span class="k">$(</span>semver tag<span class="k">)</span><span class="s2">"</span></code></pre></figure> <p>Lorsque je veux une nouvelle version, il ne me reste donc que deux lignes à taper pour construire mon phar avec la bonne version:</p> <div class="highlighter-rouge"><pre class="highlight"><code>make tag VERSION=minor make </code></pre> </div> <p>La commande <code class="highlighter-rouge">php phpmetrics.phar --version</code> indique désormais la bonne version.</p> <p>Je sais qu’il existe des plugins Jenkins pour ça, mais je souhaitais pouvoir exécuter le processus manuellement si besoin, directement dans mon terminal.</p> <p>je me doute qu’il existe des solutions plus pertinentes que celle que je décris ici ; vos retours sont donc les bienvenus.</p> Intégration continue : utiliser le fichier .travis.yml dans Jenkins avec Docker 2014-05-29T00:00:00+00:00 http://www.guanzhui39.top/travis-ci-jenkins-docker <p>J’utilise massivement <a href="https://travis-ci.org/">travis-ci</a> comme plate-forme d’intégration continue pour mes projets open source.</p> <p>Pour cela, il suffit d’ajouter un simple fichier <code class="highlighter-rouge">.travis.yml</code>. Voici par exemple celui que j’utilise pour <a href="https://github.com/Halleck45/PhpMetrics">PhpMetrics</a>:</p> <figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="s">language</span><span class="pi">:</span> <span class="s">php</span> <span class="s">php</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">5.3</span> <span class="pi">-</span> <span class="s">5.4</span> <span class="s">before_script</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">wget http://getcomposer.org/composer.phar</span> <span class="pi">-</span> <span class="s">php composer.phar install --dev --prefer-dist</span> <span class="s">script</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">./vendor/bin/phpunit -c phpunit.xml.dist</span></code></pre></figure> <p>Ce qui est génial avec Travis-ci, c’est de pouvoir lancer son build sur plusieurs environnements (ici PHP 5.3 et PHP 5.4).</p> <p>J’ai également une PIC locale , pour laquelle j’utilise <a href="http://jenkins-ci.org/">Jenkins</a>, et qui me permet de suivre l’ensemble de ses projets personnels. Et je dois avouer que ça m’agace de configuer deux fois mon build : et dans Jenkins, et dans Travis.</p> <p>Voici donc un moyen minimaliste de builder un projet dans Jenkins, dans des environnements isolés (grâce à Docker), en utilisant le même fichier <code class="highlighter-rouge">.travis.yml</code> qu’avec Travis-ci.</p> <h2 id="installation-de-docker-et-configuration-des-containers">Installation de Docker et configuration des containers</h2> <p>Je ne vais pas m’attarder sur Docker : de nombreux tutoriels existent sur le net, et la documentation est assez bien faite. Sachez juste que c’est ce qui va nous permettre de virtualiser nos environnements.</p> <p>Tout ce dont on a besoin pour l’installation (debian) tient sur ces quelques lignes :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash</span> <span class="c"># fichier install.sh</span> <span class="c"># Dépendances PIP (pour le Yaml)</span> apt-get update -y -q apt-get install python-pip -y pip install shyaml <span class="c"># Docker</span> apt-get install docker.io -y ln -sf /usr/bin/docker.io /usr/local/bin/docker <span class="c"># Containers</span> docker build -t debian/5.3 - &lt; Dockerfile53 docker build -t debian/5.4 - &lt; Dockerfile54 docker build -t debian/5.5 - &lt; Dockerfile55</code></pre></figure> <p>Les trois dernières lignes sont intéressantes. Ce sont elles qui vont créer nos environnements de test. Docker permet en effet de provisionner et configurer un container à l’aide d’un fichier de configuration (le fameux <code class="highlighter-rouge">Dockerfile</code>, mais ici qui s’appelle <code class="highlighter-rouge">Dockerfile53</code>, <code class="highlighter-rouge">Dockerfile54</code>, etc). Voici ceux que j’ai utilisé pour cet exemple, libre à vous de les enrichir :</p> <p>pour PHP 5.3 :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># fichier Dockerfile53</span> FROM debian:squeeze RUN apt-get update -y -q RUN apt-get install php5-common php5-cli php5-curl php5-xdebug wget -y --force-yes RUN apt-get clean <span class="o">&amp;&amp;</span> rm -rf /var/lib/apt/lists/<span class="k">*</span> /tmp/<span class="k">*</span> /var/tmp/</code></pre></figure> <p>pour PHP 5.4 :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># fichier Dockerfile54</span> FROM debian RUN <span class="nb">echo</span> <span class="s2">"deb http://packages.dotdeb.org wheezy all"</span> | tee -a /etc/apt/sources.list RUN <span class="nb">echo</span> <span class="s2">"deb-src http://packages.dotdeb.org wheezy all"</span> | tee -a /etc/apt/sources.list RUN apt-get update -y -q RUN apt-get install php5-cli php5-curl php5-apc php5-xdebug wget -y --force-yes RUN apt-get clean <span class="o">&amp;&amp;</span> rm -rf /var/lib/apt/lists/<span class="k">*</span> /tmp/<span class="k">*</span> /var/tmp/</code></pre></figure> <p>pour PHP 5.5 :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># fichier Dockerfile55</span> FROM debian RUN <span class="nb">echo</span> <span class="s2">"deb http://packages.dotdeb.org wheezy-php55 all"</span> | tee -a /etc/apt/sources.list RUN <span class="nb">echo</span> <span class="s2">"deb-src http://packages.dotdeb.org wheezy-php55 all"</span> | tee -a /etc/apt/sources.list RUN apt-get update -y -q RUN apt-get install php5-cli php5-curl php5-apc php5-xdebug wget -y --force-yes RUN apt-get clean <span class="o">&amp;&amp;</span> rm -rf /var/lib/apt/lists/<span class="k">*</span> /tmp/<span class="k">*</span> /var/tmp/</code></pre></figure> <p>Si vous lancez maintenant les commandes d’installation qui sont plus haut, vous vous retrouverez avec trois containers :</p> <ul> <li>debian/5.3</li> <li>debian/5.4</li> <li>debian/5.5</li> </ul> <p>Leurs noms sont très importants : ce sont eux qui vont nous permettre de faire le lien avec le fichier <code class="highlighter-rouge">.travis.yml</code>.</p> <h2 id="lancement-des-tests">Lancement des tests</h2> <p>L’objectif est de ne plus avoir à configurer quoique ce soit dans Jenkins. Nous allons donc créer un script qui va lire le fichier <code class="highlighter-rouge">.travis.yml</code>, repérer les commandes à exécuter (noeuds <code class="highlighter-rouge">before_script</code> et <code class="highlighter-rouge">script</code>), puis lancer ces commandes pour chaque environnement souhaité.</p> <p>Pour les plus pressés, voici ce script (et voici <a href="https://gist.github.com/Halleck45/be9eb3270cea0c9c28ab">le gist</a> si vous souhaitez l’améliorer).</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash</span> <span class="nv">EXIT_STATUS</span><span class="o">=</span>0 <span class="nb">echo</span> <span class="s2">"#!/bin/bash"</span> &gt; ./docker-travis-test.sh cat .travis.yml | shyaml get-values before_script &gt;&gt; ./docker-travis-test.sh cat .travis.yml | shyaml get-values script &gt;&gt; ./docker-travis-test.sh <span class="nv">PHP_VERSIONS</span><span class="o">=</span><span class="sb">`</span>cat .travis.yml | shyaml get-values php<span class="sb">`</span> <span class="k">for </span>PHP_VERSION <span class="k">in</span> <span class="nv">$PHP_VERSIONS</span> <span class="k">do </span><span class="nb">echo echo</span> <span class="s2">"Running tests for PHP </span><span class="nv">$PHP_VERSION</span><span class="s2">"</span> <span class="nb">echo</span> <span class="s2">"==================================="</span> <span class="nv">DOCKER_IMG</span><span class="o">=</span>debian/<span class="nv">$PHP_VERSION</span> <span class="nv">DOCKER_ID</span><span class="o">=</span><span class="k">$(</span>docker run -d -t <span class="nv">$DOCKER_IMG</span> /bin/bash<span class="k">)</span> docker run -v <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>:/project -w <span class="s2">"/project"</span> -t <span class="nv">$DOCKER_IMG</span> /bin/bash ./docker-travis-test.sh <span class="nv">CODE</span><span class="o">=</span><span class="nv">$?</span> <span class="k">case</span> <span class="s2">"</span><span class="nv">$CODE</span><span class="s2">"</span> <span class="k">in </span>0<span class="p">)</span> <span class="nv">RESULT</span><span class="o">=</span><span class="s2">"OK"</span> <span class="nv">OUTPUT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OUTPUT</span><span class="k">}</span><span class="s2">PHP </span><span class="k">${</span><span class="nv">PHP_VERSION</span><span class="k">}</span><span class="s2">: OK </span><span class="se">\n</span><span class="s2">"</span> <span class="p">;;</span> <span class="k">*</span><span class="p">)</span> <span class="nv">RESULT</span><span class="o">=</span><span class="s2">"ERROR (</span><span class="nv">$CODE</span><span class="s2">)"</span> <span class="nv">OUTPUT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OUTPUT</span><span class="k">}</span><span class="s2">PHP </span><span class="k">${</span><span class="nv">PHP_VERSION</span><span class="k">}</span><span class="s2">: Error (code </span><span class="k">${</span><span class="nv">CODE</span><span class="k">}</span><span class="s2">) </span><span class="se">\n</span><span class="s2">"</span> <span class="nv">EXIT_STATUS</span><span class="o">=</span>1 <span class="p">;;</span> <span class="k">esac</span> <span class="nv">DOCKER_IDS</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">DOCKER_IDS</span><span class="k">}</span><span class="s2"> </span><span class="k">${</span><span class="nv">DOCKER_ID</span><span class="k">}</span><span class="s2">"</span> <span class="k">done </span>docker restart <span class="nv">$DOCKER_ID</span> <span class="k">$(</span>docker <span class="nb">wait</span> <span class="k">${</span><span class="nv">DOCKER_IDS</span><span class="k">})</span> <span class="nb">echo echo</span> <span class="s2">"Results"</span> <span class="nb">echo</span> <span class="s2">"==================================="</span> <span class="nb">echo</span> -e <span class="nv">$OUTPUT</span>; <span class="nb">exit</span> <span class="nv">$EXIT_STATUS</span></code></pre></figure> <p>Passons aux explications :)</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">echo</span> <span class="s2">"#!/bin/bash"</span> &gt; ./docker-travis-test.sh cat .travis.yml | shyaml get-values before_script &gt;&gt; ./docker-travis-test.sh cat .travis.yml | shyaml get-values script &gt;&gt; ./docker-travis-test.sh</code></pre></figure> <p>Ces quelques lignes permettent de concaténer les contenus des noeuds <code class="highlighter-rouge">before_script</code> et <code class="highlighter-rouge">script</code> dans un fichier nommé arbitrairement <code class="highlighter-rouge">./docker-travis-test.sh</code>. Ce fichier sera donc executé pour lancer les tests. La lecture du fichier yaml s’effectue grâce à <a href="https://github.com/0k/shyaml">shyaml</a></p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">PHP_VERSIONS</span><span class="o">=</span><span class="sb">`</span>cat .travis.yml | shyaml get-values php<span class="sb">`</span> <span class="k">for </span>PHP_VERSION <span class="k">in</span> <span class="nv">$PHP_VERSIONS</span> <span class="k">do</span> ... <span class="k">done</span></code></pre></figure> <p>Ces lignes extraient du fichier <code class="highlighter-rouge">.travis.yml</code> la liste des versions de PHP sur lesquelles ont souhaite exécuter nos tests, puis itèrent sur chaque version ($PHP_VERSION vaut ici ?.3?puis ?.4?</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">DOCKER_IMG</span><span class="o">=</span>debian/<span class="nv">$PHP_VERSION</span></code></pre></figure> <p>Cette ligne est très importante : c’est elle qui fait le lien entre la version souhaitée et un container Docker. Rappelez-vous que l’on a nommé nos containers “debian/5.3? “debian/5.4?et “debian/5.5?</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">DOCKER_ID</span><span class="o">=</span><span class="k">$(</span>docker run -d -t <span class="nv">$DOCKER_IMG</span> /bin/bash<span class="k">)</span> docker run -v <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>:/project -w <span class="s2">"/project"</span> -t <span class="nv">$DOCKER_IMG</span> /bin/bash ./docker-travis-test.sh</code></pre></figure> <p>C’est là que tout se passe : on récupère l’ID de notre container (pour pouvoir travailler avec), puis on lance le script <code class="highlighter-rouge">./docker-travis-test.sh</code> que l’on a créé plus haut sur notre code.</p> <p>À noter :</p> <ul> <li><code class="highlighter-rouge">-w /project</code> définit l’espace de travail du container</li> <li><code class="highlighter-rouge">-v \~pwd\~:/project</code> permet de partager le dossier courant avec le container</li> </ul> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">CODE</span><span class="o">=</span><span class="nv">$?</span> <span class="k">case</span> <span class="s2">"</span><span class="nv">$CODE</span><span class="s2">"</span> <span class="k">in </span>0<span class="p">)</span> <span class="nv">RESULT</span><span class="o">=</span><span class="s2">"OK"</span> <span class="nv">OUTPUT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OUTPUT</span><span class="k">}</span><span class="s2">PHP </span><span class="k">${</span><span class="nv">PHP_VERSION</span><span class="k">}</span><span class="s2">: OK </span><span class="se">\n</span><span class="s2">"</span> <span class="p">;;</span> <span class="k">*</span><span class="p">)</span> <span class="nv">RESULT</span><span class="o">=</span><span class="s2">"ERROR (</span><span class="nv">$CODE</span><span class="s2">)"</span> <span class="nv">OUTPUT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OUTPUT</span><span class="k">}</span><span class="s2">PHP </span><span class="k">${</span><span class="nv">PHP_VERSION</span><span class="k">}</span><span class="s2">: Error (code </span><span class="k">${</span><span class="nv">CODE</span><span class="k">}</span><span class="s2">) </span><span class="se">\n</span><span class="s2">"</span> <span class="nv">EXIT_STATUS</span><span class="o">=</span>1 <span class="p">;;</span> <span class="k">esac</span> ... <span class="nb">echo echo</span> <span class="s2">"Results"</span> <span class="nb">echo</span> <span class="s2">"==================================="</span> <span class="nb">echo</span> -e <span class="nv">$OUTPUT</span>; <span class="nb">exit</span> <span class="nv">$EXIT_STATUS</span></code></pre></figure> <p>Ces lignes permettent de gérer les codes de retour d’erreur, et concatènent le résultat de chaque jeu de tests dans la variable <code class="highlighter-rouge">OUTPUT</code> afin de l’afficher à la fin du build.</p> <p>## Exemple dans Jenkins</p> <p>Voici donc à quoi ressemblent mes builds Jenkins désormais</p> <p><img src="/images/2014-05-jenkins-docker-travis-exemple.png" alt="Exemple de configuration Jenkins qui utilise Docker et le fichier .travis.yml" /></p> <p>La prochaine étape serait la parallélisation (le plus simple me paraît d’utiliser <a href="http://www.gnu.org/software/parallel/">parallel</a>), et bien sûr de rendre tout ça un peu plus propre (rollback des containers, nettoyage?. En attendant, à vous de jouer :)</p> Qualité logicielle et métriques de code - comment fixer les valeurs limites ? 2014-05-07T00:00:00+00:00 http://www.guanzhui39.top/industrialisation/bornes-pour-les-indicateurs-et-metriques <p>Plus j’avance dans la réalisation de <a href="https://github.com/Halleck45/PhpMetrics">PhpMetrics</a>, plus une question se pose : je dispose de métriques sur le code source, mais à partir de quel moment alerter l’utilisateur ? Quelle est la valeur idéale ? Quelles sont les valeurs minimales et maximales ?</p> <p>Je vous propose un petit retour d’expérience sur la manière dont j’ai tenté de fixer ces valeurs pour PhpMetrics. Ce qu’il faut savoir, c’est que j’ai procédé itérativement selon la manière suivante :</p> <ol> <li>utilisation de valeurs théoriques</li> <li>pondération de la théorie</li> <li>analyse démographique</li> <li>ajustement par l’expérience</li> </ol> <p>## Étape 1 : Utilisation de valeurs théoriques</p> <p>La première méthode est la plus simple : la plupart des métriques logicielles que j’ai intégrées sont très bien théorisées, et de des suggestions de bornes maximales et minimales existent assez souvent.</p> <p>Prenez LCOM4 par exemple. Cet indicateur (Lack of cohesion of methods) tend à révéler la cohésion d’une classe :</p> <div class="highlighter-rouge"><pre class="highlight"><code>&lt;?php class Example { private $a; public function m1() { $this-&gt;m2(); } public function m2() { $this-&gt;a = 1; } public function m3() { $this-&gt;a = 1; } public function m4() { $this-&gt;m5(); } public function m5() { echo 'ok'; } } </code></pre> </div> <p>Examinons les corrélations des méthodes de cette classe :</p> <ul> <li><code class="highlighter-rouge">m1()</code> appelle <code class="highlighter-rouge">m2()</code>. <code class="highlighter-rouge">m2()</code> partage avec <code class="highlighter-rouge">m3</code> un attribut commun <code class="highlighter-rouge">a</code></li> <li><code class="highlighter-rouge">m4()</code> appelle <code class="highlighter-rouge">m5()</code></li> </ul> <p>Il y a donc deux flux de code bien distincts dans cette classe. On dit alors que <code class="highlighter-rouge">LCOM4(Example) = 2</code>.</p> <p>Cet indicateur est à mettre en relation avec le <code class="highlighter-rouge">S</code> des principes SOLID (Single reponsability). Les méthodes d’une classe qui servent une besoin commun s’articulent ensemble : elles s’appellent les unes les autres ou partagent des attributs communs. Il est donc probable ici que notre classe possède au moins deux reponsabilités.</p> <p><strong>D’un point de vue purement théorique</strong>, la valeur idéale de LCOM4 d’une classe est donc de 1.</p> <p>## Étape 2 : Pondération de la théorie</p> <p>Mais il ne faut pas oublier que ces indicateurs, bien que volontairement agnostiques, ont tout de même été fortement orientés par les langages de programmation utilisés pour les théoriser / illustrer.</p> <p>Revenons à LCOM4, et à l’idéal théorique de <code class="highlighter-rouge">LCOM4(classe) = 1</code>.</p> <p>Cet idéal signifie que toutes les méthodes d’une classes fonctionnent ensemble (principe de cohésion). Or la plupart des classes PHP comportent un très grand nombre de getters/setters, souvent inutilisés à l’intérieur de la classe même. Vu par un robot, les <strong>getters/setters méthodes semblent des méthdoes orphelines</strong>, et augmentent donc artificiellement LCOM4.</p> <p>Cet aspect est propre aux langages qui ne comportent pas de sucre syntaxique pour les getters et setters. En C#, on pourrait simplement écrire</p> <div class="highlighter-rouge"><pre class="highlight"><code>public int X { get; set; } </code></pre> </div> <p>En PHP ce n’est <a href="https://wiki.php.net/rfc/propertygetsetsyntax">pas encore le cas</a>. il faut donc pondérer cet indicateur.</p> <p>Il en va de même pour la majorité des indicateurs : le code écrit en PHP est assez typique : typique par le langage même, mais aussi par l’orientation des projets qui l’utilisent, assez souvent plus orientés production (court terme) plutôt qu’évolution (long terme).</p> <p><em>Petite parenthèse utile : je ne cherche pas à polémiquer, juste à faire un constat. Ce constat pourrait tout à fait être identique pour d’autres langages de programmation. Oui, je sais qu’il y a d’excellents projets PHP, orientés très long terme. Mais je pense ici faire un constat d’ordre général selon <strong>mon</strong> expérience. Bref, j’essaye d’être <strong>pragmatique</strong>.</em></p> <h2 id="étape-3--analyse-démographique">Étape 3 : Analyse démographique</h2> <p>Il m’a donc fallu trouver un moyen de déterminer de nouvelles bornes, proches de la téhorie, mais plus adéquates à PHP.</p> <p>Le plus simple m’a semblé de faire une analyse démographique de projets PHP. Idéalement de projets représentatifs. Coup de chance, packagist fournit la liste des paquets les plus utilisés. Je me suis donc mis à les analyser.</p> <p><strong>[Spoiler]</strong> La première chose à comprendre est que cette analyse est biaisée :</p> <ul> <li>les projets sont open source. Ils sont fait par des volontaires, avec un processus souvent plus qualitatif (revue de code) que des projets d’entreprise</li> <li>les projets recensés sur packagist sont ceux qui sont déclarés comme paquet Composer. Ils sont donc, pour la plupart, très récents.</li> <li>les projets open source concernent généralement de l’outillage (composants, frameworks, librairies?. Ils ne sont pas représentatifs de projet fonctionnement complexes</li> </ul> <p><strong>[/Spoiler]</strong></p> <p>Je reviendrai sur ces aspects par la suite. En attendant, je pense que cette analyse, bien que biaisée, reste pertinente si on la prend pour ce qu’elle est, et rien de plus : une analyse de projets bien spécifiques, représentatifs d’une partie seulement des typologies de projets possibles.</p> <p>Le processus d’analyse est le suivant :</p> <ol> <li>Téléchargement des sources</li> <li>Exécution de l’analyse sur un échantillon représentatif (environ 5 000 classes)</li> <li>Aggrégat des résultats</li> <li>Elimination des extrêmes (par écart interquartile)</li> <li>Analyse des percentiles de chaque série</li> </ol> <p>Je me suis alors retrouvé avec des résultats de ce type :</p> <p><img src="/images/2014-05-phpmetrics-resultats-analyse-demographique.png" alt="Résultat de l'analyse démographique des métriques" /></p> <p>Par tâtonnements, il m’a paru judicieux de considérer comme :</p> <ul> <li>borne inférieure =&gt; le trentième percentile</li> <li>borne supérieure =&gt; le quatre vingtième percentile</li> <li>borne supérieure maximale =&gt; le quatre vingt dixième percentile</li> </ul> <p>Dans le cas du nombre de lignes de code, <strong>il en ressortirai qu’un fichier doit contenir idéalement entre 65 et 130 lignes de code, et au maximum 154.</strong> Ces chiffres ne semblent pas absurdes.</p> <p>Voici quelques graphiques qui illustrent ces résultats:</p> <p><img src="/images/2014-05-graph-lines-of-code.png" alt="Résultat de l'analyse démographique - Nombre de lignes de code " /></p> <p><img src="/images/2014-05-graph-cyclomatic-complexity.png" alt="Résultat de l'analyse démographique - Complexity cyclomatique " /></p> <p><img src="/images/2014-05-graph-maintenability-index.png" alt="Résultat de l'analyse démographique - Indice de maintenabilité " /></p> <p><img src="/images/2014-05-graph-lcom.png" alt="Résultat de l'analyse démographique - Lack of cohesion of methods " /></p> <h2 id="étape-4--ajustements-par-lexpérience">Étape 4 : Ajustements par l’expérience</h2> <p>Je l’ai dit : l’analyse démographique, bien qu’intéressante, est faussée : elle ne considère que certains types de projets (généralement des outils).</p> <p>Dans la vraie vie, la plupart des codes sources sont “en dehors de clous? <strong>Facile d’avoir une complexité cyclomatique faible quand on n’a pas de délais</strong>, qu’on ne gère pas de changements fonctionnels fréquents ou qu’on ne gère pas de règles métiers?lt;/p> <p>J’ai donc confronté ces indicateurs à mon expérience. J’ai en effet la chance de réaliser des audits chez de nombreux clients, ce qui me permet d’être confronté à des projets très variés et nombreux . J’ai donc systématiquement analysé le code des projets auxquels j’ai été confronté, et ce depuis que je suis développeur (ou presque).</p> <p>Attention, là encore l’analyse est biaisée : quand on fait appel à un consultant, c’est en général que le projet subit des avaries, souvent dûes à des problèmes techniques (code peu évolutif, architecture mal adaptée?. Le code de ces projets est donc généralement moins maintenable que sur des projets où tout se passe bien (même si ce n’est pas systématiquement le cas). On est donc dans la situation inverse des projets Open Source précédemment mentionnés.</p> <p>J’ai donc réajusté les indicateurs en fonction de ces analyses, et continue de les réajuster au fur-et-à-mesure.</p> <h2 id="conclusion">Conclusion</h2> <p>Enfin, un point important reste à soulever. C’est d’ailleurs cet aspect qui m’a fait hésiter à fournir des indicateurs visuels (rouge pour “critique? jaune pour “?examiner?et vert pour “probablement correct? : aucune borne, aucune limite n’est universelle.</p> <p>Lorsqu’on analyse un Controlleur, il est évident qu’on ne s’attend pas à avoir le même Vocabulaire que dans un Service. Une Entité n’aura pas le même Indice de maintenabilité qu’un Validateur?lt;/p> <p>La conclusion de ces analyses est donc assez triviale : c’est à l’humain de pondérer les chiffres. Un outil d’analyse ne fournit que des indicateurs, pas des vérités.</p> <p>Attention, il ne faut pas pour autant rejeter tout outil d’analyse statique (comme on le fait malheureusement trop souvent). Ces indicateurs sont pratiques et indispensables si on sait les interpréter et les prendre pour ce qu’ils sont : <strong>des indicateurs</strong>. Ce qui compte, ce n’est pas de savoir si telle classe ou telle fichier a une complexité trop élevée. Non, ce qui est important c’est de surveiller et de tenir compte de l?lt;strong>accumulation d’indicateurs et de leur orientation générale</strong>.</p> <p>Si une classe a un peu trop de lignes de code ce n’est pas grave ; si les classes d’un paquet ont une Complexité cyclomatique élevée, une LCOM élevée, un Indice de maintenabilité faible?là il faudra écouter les tendances des indicateurs, et se pencher sur le code en question pour comprendre ce qui peut causer ces remontées.</p> <quote>**L'analyse statique n'est pas une diseuse de vérité, mais un un outil d'alerte**</quote> Indice de maintenabilité d'un projet PHP et Jenkins 2014-02-06T00:00:00+00:00 http://www.guanzhui39.top/industrialisation/indice-de-maintenabilite-dun-projet-php-et-jenkins <p>Dans mon <a href="/php/la-maintenabilite-dun-projet-php-en-images" title="La maintenabilité d’un projet PHP en images">dernier billet</a> je vous avais présenté un outil sur lequel je travaille : <a href="https://github.com/Halleck45/PhpMetrics">PhpMetrics</a>. Cet outil permet de calculer différents indicateurs sur le code source, dont l'Indice de Maintenabilité, le Poids des commentaires, la Difficulté d'un code, etc.</p> <p>Je pense qu'avoir une vision régulière de ce type d'indicateurs est important pour assurer la qualité d'un projet, ou au moins détecter les écarts de qualité assez rapidement. Bien que ce ne soit pas vraiment le rôle de Jenkins d'assurer un tel suivi (mais plutôt d'un outil comme Sonar, par exemple), mais vu que Jenkins reste un outils assez répandu aujourd'hui, je vous propose de voir comment intégrer ces indicateurs de code dans Jenkins.</p> <a href="http://www.guanzhui39.top/images/2014-02-phpmetrics-chart.png"><img src="http://www.guanzhui39.top/images/2014-02-phpmetrics-chart.png" alt="Graphique PhpMetrics dans Jenkins" width="740" height="342" class="aligncenter size-full wp-image-804" /></a> <p>Si vous n'avez jamais utilisé Jenkins, je vous invite à lire les excellents billets de Pascal Martin sur l'<a href="http://blog.pascal-martin.fr/post/integration-continue-jenkins-installation-configuration">installation</a> et la <a href="http://blog.pascal-martin.fr/post/integration-continue-jenkins-projet-php">configuration</a> d'un premier projet PHP. Et si vous êtes un peu fainéant, vous pouvez aussi directement utiliser la procédure d'installation expliquée sur sur <a href="http://jenkins-php.org">jenkins-php.org</a>. </p> <p>Je pars du principe pour la suite que vous avez un job Jenkins fonctionnel.</p> <h2>Première étape: lancer l'analyse. </h2> <p>Différentes options s'offrent à vous. Si avoir Java sur votre machine d'intégration ne vous dérange pas, vous pouvez utiliser <a href="http://ant.apache.org/">Ant</a>. Voilà à quoi ressemblera votre fichier build.xml (par exemple):</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;</span> <span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"demo"</span> <span class="na">default=</span><span class="s">"phpmetrics"</span> <span class="na">basedir=</span><span class="s">"./"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- source to analyze --&gt;</span> <span class="nt">&lt;property</span> <span class="na">name=</span><span class="s">"src"</span> <span class="na">value=</span><span class="s">"${basedir}/src"</span><span class="nt">/&gt;</span> <span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"phpmetrics"</span><span class="nt">&gt;</span> <span class="nt">&lt;exec</span> <span class="na">command=</span><span class="s">"wget https://github.com/Halleck45/PhpMetrics/raw/master/build/metrics.phar"</span><span class="nt">/&gt;</span> <span class="nt">&lt;exec</span> <span class="na">command=</span><span class="s">"php metrics.phar --summary-xml=phpmetrics.xml --summary-html=phpmetrics.html ${src}"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/target&gt;</span> <span class="nt">&lt;/project&gt;</span></code></pre></figure> <p>Si vous pensez que, finalement, ce n'est peut être pas si pertinent d'avoir [troll]une énorme usine à gaz juste pour lancer deux commandes[/troll], vous pouvez simplement configurer le job pour qu'il exécute les commandes en question directement :</p> <a href="http://www.guanzhui39.top/images/2014-02-build-shell.png"><img src="http://www.guanzhui39.top/images/2014-02-build-shell.png" alt="Jenkins et PhpMetrics : confiuration du build - shell" width="699" height="237" class="aligncenter size-full wp-image-805" /></a> <h2>Deuxième étape: le graphique</h2> <p>Nous allons utiliser le plugin de Jenkins <a href="https://wiki.jenkins-ci.org/display/JENKINS/Plot+Plugin">Plot</a>, dont c'est justement la spécialité; Il suffit de configurer une étape Post-Build pour ajouter le résultat de la dernière analyse au graphique :</p> <a href="http://www.guanzhui39.top/images/2014-02-jenkins-phpmetrics-plot-build.png"><img src="http://www.guanzhui39.top/images/2014-02-jenkins-phpmetrics-plot-build.png" alt="Jenkins et PhpMetrics : configuration de plot" width="708" height="655" class="aligncenter size-full wp-image-806" /></a> <h3>Bonus: le rapport HTML</h3> <p>PhpMetrics fournit un <a href="http://halleck45.github.io/PhpMetrics/report/demo/v0.0.3/index.html">rapport Html</a> qui offre, je l'espère, un peu de recul sur un code source. N'hésitez pas à installer le plugin <a href="https://wiki.jenkins-ci.org/display/JENKINS/HTML+Publisher+Plugin">Html Publisher</a>, qui vous permettra d'ajouter un lien vers ce rapport HTML dans le menu de votre projet :</p> <a href="http://www.guanzhui39.top/images/2014-02-jenkins-phpmetrics-htmlreport.png"><img src="http://www.guanzhui39.top/images/2014-02-jenkins-phpmetrics-htmlreport.png" alt="Jenkins et PhpMetrics : HTML Publisher" width="871" height="178" class="aligncenter size-full wp-image-807" /></a> <h2>Le mot de la fin</h2> <p>Bien évidemment vous pouvez intégrer d'autres courbes: Volume du code, Difficulté... La démarche sera la même que celle présentée ci-dessus.</p> <p>Je vous rappelle que PhpMetrics est Open Source, et a besoin d'être éprouvé, tant sur les indicateurs fournis que sur la stabilité de l'outils. N'hésitez pas à [lien github issue]remonter les anomalies que vous rencontrerez, ou simplement à me dire si vous l'utilisez : ça me motivera encore plus pour l'enrichir et l'améliorer ;)</p> La maintenabilité d'un projet PHP en images 2013-12-18T00:00:00+00:00 http://www.guanzhui39.top/php/la-maintenabilite-dun-projet-php-en-images <p>Ça faisait longtemps que je cherchais un outil capable de me fournir un aperçu, très général et visuel, de la maintenabilité d'un projet. Ne trouvant finalement que peu de chose sur le net, je me suis décidé à en coder un rapidement. </p><p> <p>Pour illustrer le fonctionnement de cet outil, voici les rapport pour les sources de différents projets PHP :</p> </p><p> <p>Symfony2 Component :</p> </p><p> <a href="http://halleck45.github.io/PhpMetrics/report/symfony2-component/index.html"><img src="http://www.guanzhui39.top/images/2013-12-18-preview-symfony2-component.png" alt="Aperçu du rapport pour Symfony2 Component" /></a> </p><p> <p>Zend Framework 2 :</p> </p><p> <a href="http://halleck45.github.io/PhpMetrics/report/zendframework2/index.html"><img src="http://www.guanzhui39.top/images/2013-12-18-preview-zendframework2.png" alt="Aperçu du rapport pour Zend Framework 2" /></a> </p><p> <p>Drupal 7 :</p> </p><p> <a href="http://halleck45.github.io/PhpMetrics/report/drupal7/index.html"><img src="http://www.guanzhui39.top/images/2013-12-18-preview-drupal7.png" alt="Aperçu du rapport pour Drupal" /></a> </p><p> <p>CakePHP 2 :</p> </p><p> <a href="http://halleck45.github.io/PhpMetrics/report/cakephp2/index.html"><img src="http://www.guanzhui39.top/images/2013-12-18-preview-cakephp.png" alt="Aperçu du rapport pour CakePHP" /></a> </p><p> <p>WordPress 3 2 :</p> </p><p> <a href="http://halleck45.github.io/PhpMetrics/report/wordpress3/index.html"><img src="http://www.guanzhui39.top/images/2013-12-18-preview-wordpress3.png" alt="Aperçu du rapport pour WordPress" /></a> </p><p> <p>Chaque cercle représente un fichier. En schématisant, plus le cercle est gros, plus le code est complexe ; la couleur, elle, est représentative de l'indice de maintenabilité.</p> </p><p> <p>Pour les plus pressés, <a href="http://halleck45.github.io/PhpMetrics/">l'outil en question est disponible sur Github</a>.</p> </p><p> <p>Pour les autres, voici quelques explications.</p> </p><p> <p>Avant de commencer, il faut être conscient que <strong>la notion de maintenabilité applicative est bien plus complexe que ne sait le résoudre un tel outil</strong>. Elle dépend de facteurs humains (équipe en charge du projet), environnementaux (contexte de l'entreprise, contraintes de délais), du code source (clarté, simplicité), du choix des outils (présence d'un support, possibilité d'obtenir des formations), etc.</p> </p><p> <p>Cet outil ne prétend en aucun cas fournir des chiffres précis sur l'ensemble de ces critères. Non, l'objet de cet outil est de fournir des <strong>indicateurs</strong> à partir d'une analyse statique du code source.</p> </p><p> <p>Je n'ai rien inventé : les algorithmes utilisés pour calculer ce type d'indicateurs existent depuis la fin des années 70, et de nombreux IDE (<a href="http://blogs.msdn.com/b/zainnab/archive/2011/05/26/code-metrics-maintainability-index.aspx">Visual Studio par exemple</a>) intègrent nativement des modules de supervision du code basés sur ces mêmes algoritmes pour différents langages de programmation.</p> </p><p> <p>L'outil commence d'abord par convertir le code source en tokens, pour calculer le premier indicateur utilisé : les <a href="http://fr.wikipedia.org/wiki/M%C3%A9triques_d'Halstead">métriques d'Ahlstead</a>. Cet indicateur fourni des informations à partir d'un ratio entre le volume du code, la variété et le type de structures manipulées dans un code source. Il fournit :</p> </p> <ul> <li>la <strong>taille</strong> du programme (N) ; <li>le <strong>vocabulaire</strong> (n) : en gros, plus le code emploie de fonctions, variables, opérateurs... différents, plus le vocabulaire est riche ;</li> <li>le <strong>volume</strong> du programme (V) : ratio entre la taille et le vocabulaire ;</li> <li>le niveau de <strong>difficulté</strong> (D) : propension d'erreurs du programme ;</li> <li>l'<strong>effort</strong> d'implémentation fourni (E) ;</li> <li>le <strong>temps</strong> d'implémentation estimé en secondes (T) ;</li> <li>le nombre de <strong>bugs</strong> délivrés (B) : il s'agit d'une estimation du nombre de bugs dans le code source. Ce nombre est idéalement inférieur à 2.</li> </ul> <p> <p>La seconde étape consiste à calculer la <strong>complexité cyclomatique</strong> du code source. De ce côté, je n'ai pas cherché à réinventer la route : j'utilise l'excellent <a href="https://github.com/sebastianbergmann/phploc">phploc</a>.</p> </p><p> À partir de ces informations, nous pouvons calculer l'<a href="http://fr.wikipedia.org/wiki/Maintenabilit%C3%A9"><strong>Indice de maintenabilité</strong></a>. Cet indicateur fourni une note, de 0 à 171, où 0 représente un code très difficilement maintenable, et 171 un code maintenable. </p><p> <p>Le code source est disponible sur <a href="http://halleck45.github.io/PhpMetrics/">Github: Halleck45/PhpMetrics</a>. Cet outil reste à affiner ; n'hésitez donc surtout pas à proposer des améliorations ou à signaler des anomalies.</p> </p> eBooks open source sur le Développement piloté par le comportement 2013-10-04T00:00:00+00:00 http://www.guanzhui39.top/php/ressources-tutos-php/ebooks-open-source-sur-le-developpement-pilote-par-le-comportement <p> Ça faisait longtemps que je les avais rédigés (plus de 6 mois), je viens de les retrouver au fond de mon placard. Voici deux ebooks open source et gratuits sur le développement piloté par le comportement (Behavior Driven Development). </p><p> <p>Ces deux livres courts (50 pages) sont, plutôt que des documentations complètes, des invitations : il ne s'agit en aucun cas d'expliquer techniquement ce qu'est le Développement piloté par le comportement, mais bien de comprendre la philosophie qui anime cette démarche. Ils s'adressent bien spécifiquement à deux cibles : les fonctionnels (clients, product owner...) et les techniques (développeurs, testeurs...).</p> </p> <h2>Tome 1 : Communiquez avec vos développeurs</h2> <p> <a href="http://www.guanzhui39.top/images/2013-10-cover1-small.jpg"><img src="http://www.guanzhui39.top/images/2013-10-cover1-small.jpg" alt="Tome 1 : Communiquez avec les développeurs" width="250" height="333" class="size-full wp-image-772" /></a> Tome 1 : Communiquez avec les développeurs </p><p> <p>Ce tome, comme son titre l'indique, explique aux chefs de projets fonctionnels (et clients, product owners...) les avantages qu'ils auraient à utiliser la méthodologie du développement piloté par le comportement : économies de communication, gain de vélocité, efficacité et stabilité... Sans jamais aborder les aspects techniques de la démarche.</p> </p> <h2>Tome 2 : Développeurs, faites-vous plaisir !</h2> <p><a href="http://www.guanzhui39.top/images/2013-10-cover2-small.jpg"><img src="http://www.guanzhui39.top/images/2013-10-cover2-small.jpg" alt="Tome 2 : Développeurs, faites-vous plaisir" width="250" height="333" class="size-full wp-image-773" /></a> Tome 2 : Développeurs, faites-vous plaisir </p><p> <p>Ce tome, lui, s'adresse aux développeurs soucieux de la qualité de leurs projets, ou fatigués de faire des va-et-vients entre les demandes toujours changeantes de leurs clients. Il ne s'adresse pas spécifiquement aux développeurs PHP, même si les exemples utilisés utilisent <a href="http://behat.org/">Behat</a> comme outil de test.</p> </p> <h2>Open Source ?</h2> <p> <p>Ces deux livres, publiés avec l'application PHP <a href="https://github.com/javiereguiluz/easybook">easyBook</a>, sont open source. Les fichiers MarkDown utilisés sont accessibles sur <a href="https://github.com/Halleck45/livre-developpement-pilote-comportement">mon Github</a>. N'hésitez pas, si le coeur vous en dit, à y contribuer :-)</p> </p><p> <p>Mon idée première était de ne faire qu'un seul livre, à double destination (fonctionnels et techniques), puis de contacter un éditeur pour une publication classique. Plusieurs éléments m'ont convaincus du contraire :</p> </p> <ul> <li>Au départ le projet a été lancé en collaboration avec une startup nantaise bien connue. Différents changements d'organisation là-bas ont ralenti la finalisation du livre. Il est temps pour moi de le publier, le plus simple reste d'en faire des ebooks accessibles à tous.</li> <li>Plusieurs éditeurs m'ont pertinemment fait remarquer qu'un livre doit choisir une, et une seule seule, cible. Sinon les revendeurs ne sauront jamais comment communiquer pour vendre le livre.</li> <li>Je me sers déjà depuis plusieurs mois du tome 1 auprès de mes clients pour leur expliquer les avantages du Développement piloté par le comportement et leur expliquer comment formaliser leur besoin efficacement. Ça l'air plutôt efficace, j'ai donc envie que le maximum de personnes puissent faire de même en introduisant cette pratique dans leur société.</li> <li>Pourquoi pas ? :) . Je ne travaille qu'avec des produits open source, pourquoi un livre ne le serait-il pas ? Surtout qu'il a été rédigé en MarkDown language, ce qui facilite d'autant plus la contribution...</li> </ul> <p> <p>Je vous invite donc à visiter <strong><a href="http://communiquez.lepine.pro">communiquez.lepine.pro</a></strong> pour télécharger ces ebooks (PDF ou ePub). Les sources sont, elles, hébergés sur <a href="https://github.com/Halleck45/livre-developpement-pilote-comportement">Github</a>.</p> </p><p> <p>Je suis preneur de tout retour, toute remarque ou suggestion. Les livres ne sont pas figés, ils sont faits pour évoluer. Merci à vous !</p> </p> Mutation Testing en PHP : indicateurs de qualité des tests unitaires 2013-06-03T00:00:00+00:00 http://www.guanzhui39.top/php/mutation-testing-en-php-indicateurs-de-qualite-des-tests-unitaires <p> <p>Aujourd'hui, dans l'écosystème PHP, on ne se pose enfin plus la question de savoir ce qu'est un test unitaire. Les tests unitaires sont devenus une pratique courante, et il existe des frameworks de tests matures, comme PHPUnit ou atoum.</p> </p><p> <p>L'engouement pour la qualité logicielle a poussé la communauté à progresser et à proposer de nouvelles pratiques, de nouveaux outils... Cependant, il reste encore exceptionnel de contrôler la qualité des tests unitaires produits.</p> </p><p> <p>Attention, par contrôle de qualité des tests, je ne parle pas d'indicateurs de couverture de code. Non, car une couverture de code à 100 % est contre-productive, impossible et surtout totalement fausse. Et voici pourquoi.</p> </p> <h2>Couverture de nœuds et couverture de chemins</h2> <p> <p>Les outils de couverture actuelle ont un défaut majeur, inhérent à leur fonctionnement : ils indiquent uniquement si des portions de code ont été exécutées lors des tests unitaires.</p> </p><p> <p>Or, prenons un exemple simple :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="k">class</span> <span class="nc">Foo</span> <span class="p">{</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">bar</span><span class="p">(</span><span class="nv">$x</span><span class="p">,</span> <span class="nv">$y</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span><span class="p">(</span><span class="nv">$x</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="k">echo</span> <span class="s1">'A'</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">echo</span> <span class="s1">'B'</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span><span class="p">(</span><span class="nv">$y</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="k">echo</span> <span class="s1">'C'</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">echo</span> <span class="s1">'D'</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>et le test unitaire suivant :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">require_once</span> <span class="nx">__DIR__</span><span class="o">.</span><span class="s1">'/Foo.php'</span><span class="p">;</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="k">class</span> <span class="nc">FooTest</span> <span class="k">extends</span> <span class="nx">PHPUnit_Framework_TestCase</span> <span class="p">{</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">testFoo1</span><span class="p">()</span> <span class="p">{</span> <span class="nv">$foo</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Foo</span><span class="p">;</span> <span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">bar</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span> <span class="nv">$foo</span><span class="o">-&gt;</span><span class="na">bar</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p> <p>Le premier appel de bar() affiche 'AD', le second affiche 'BC'. Je suis donc passé partout dans mon code source, la couverture de code de ma suite de test est de 100 % :</p> </p><p> <a href="http://www.guanzhui39.top/images/2013-06-couverture-code-100.png" ><img src="http://www.guanzhui39.top/images/2013-06-couverture-code-100.png" alt="Couverture de code PHP à 100%" width="471" height="291" class="aligncenter size-full wp-image-755" /></a> </p><p> <p>Super ! 100 % ! J'ai couvert tout mon code ! En plus c'est vert, c'est donc que tout va bien.</p> </p><p> <p>Et bien non?ou plutôt, je suis passé à chaque nœud de mon code source, mais je n'ai pas couvert tous les chemins possibles de code. Car avec ces deux if(), le code peut faire :</p> </p><ul><li>A, C</li> <li>A, D</li> <li>B, C</li> <li>B, D</li></ul> <p> <p>Quatre chemins possibles donc, alors que la couverture de code est de 100 % en à peine deux tests. La couverture de code est donc un indicateur assez peu fiable, puisqu'elle nous trompe allègrement : dans mon exemple, très simple, je n'ai en réalité couvert que 50 % des chemins possibles ; et encore, uniquement pour cette portion de code. Et le code est très simple : imaginez un switch() avec des if() imbriqués, vous verrez qu'en réalité la différence entre la réalité et la couverture de code indiquée est exponentielle.</p> </p> <h2>Pire : les test unitaires qui ne testent rien</h2> <p> <p>Attendez, j'ai donc ici une couverture de code de 100 %, mais, en réalité, mes tests unitaires ne font strictement rien. Il n'y a même pas d'assertion !</p> </p><p> <p>Il arrive en effet très fréquemment que les tests unitaires ne servent à rien. Oui, même dans la vraie vie, même sur de gros projets. C'est encore plus vrai lorsqu'on commence à " sur-mocker " tout et n'importe quoi : un mock par-ci, un mock par-là... Il m'arrive de voir des tests unitaires où les assertions portent sur des mocks ; autant ne pas écrire de test unitaire.</p> </p> <h2>Quel indicateur de qualité alors pour les tests unitaires?</h2> <p> <p>Vous l'avez compris, il est difficile d'obtenir des indicateurs fiables de qualité pour des tests unitaires. Et encore, on ne cherche que des indicateurs...</p> </p><p> <p>C'est ici qu'intervient le Mutation Testing. Dès les années 70 (oui, c'est vieux), s'est posée la question de savoir comment résoudre cette question de la qualité de tests.</p> </p><p> <p>L'idée du Mutation Testing consiste à introduire des bugs dans le code source, puis à vérifier si les tests unitaires ont bien détecté ces bugs. Autrement dit, nous introduisons des mutations dans le code source (on parle de " mutants "), les tests unitaires sont sensés les détecter (on dit alors que le mutant en question est " tué ").</p> </p><p> <p>Si tous les bugs ont bien été détectés, c'est sans doute que les tests unitaires sont fiables. Si aucun bug n'est détecté, les tests unitaires ne servent à rien. Dire que tous les mutants ont été tués est donc un indicateur de la bonne qualité de tests unitaires.</p> </p><p> <p>Les avantages du Mutation Testing sont nombreux :</p> </p> <ul><li>c'est efficace</li> <li>c'est très simple et ne requiert pas de développement spécifique en plus des tests unitaires</li> <li>c'est assez précis.</li></ul> <p> <p>Par contre, le Mutation Testing a un certain nombre d'inconvénients :</p> </p> <ul><li>C'est très très long à s'exécuter (le volume de mutations possibles est exponentielle)</li> <li>Il arrive qu'il y ait des mutants impossible à tuer</li></ul> <p> <p>Maintenant que les bases sont posées, parlons outils. Il n'existait en PHP, à ma connaissance, qu'un seul outil pour le Mutation Testing : <a href="https://github.com/padraic/mutagenesis" target="_blank">Mutagenesis</a>. Malheureusement, cet outil, pourtant très intéressant, a, malgré ses avantages, trois inconvénients :</p> </p> <ul><li>il est sorti trop tôt, à une époque où tester son code restait encore une pratique anecdotique en PHP</li> <li>le dernier commit date de plus d'un an</li> <li>il nécessite d'installer une extension PHP assez lourde : RunKit</li></ul> <p> <p>Partant de ce constat, j'ai réfléchi à une solution pour proposer un outil plus moderne et moins lourd. J'ai donc démarré le développement de <a href="https://github.com/Halleck45/MutaTesting/" target="_blank">MutaTesting</a>. A titre personnel, je dois avouer que ça m'intéressait beaucoup de voir comment résoudre les difficultés inhérentes à cet outil sans devoir passer par l'installation de RunKit ou de xDebug.</p> </p> <h2>MutaTesting, un nouvel outil pour la qualité PHP</h2> <p> <a href="https://github.com/Halleck45/MutaTesting/" target="_blank">MutaTesting</a>, c'est quoi ? C'est un outil PHP qui crée des mutants à partir votre code source puis lance vos tests unitaires pour voir s'il est possible de tuer ces mutants. </p><p> <p>Mon idée première a été de faire un outil très simple : pas besoin d'extension PHP, pas besoin de configuration compliquée ; il suffit, en ligne de commande, d'indiquer trois choses :</p> </p> <ul><li>le framework de test utilisé</li> <li>le chemin du binaire à exécuter pour lancer les tests</li> <li>le dossier des tests unitaires</li> </ul> <p> <p>Par exemple, pour une suite de tests PHPUnit :</p> </p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">./bin/mutatesting phpunit phpunit.phar myTestFolder</code></pre></figure> <p> <p>ou pour atoum :</p> </p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">./bin/mutatesting atoum mageekguy.atoum.phar myTestFolder</code></pre></figure> <p> <p>C'est tout. A partir de là, MutaTesting va procéder à un certain nombre de processus :</p> </p> <ol> <li>les tests vont être lancés une première fois</li> <li>chaque suite de test va être isolée, puis relancée pour déterminer quelles sources PHP elle permet de tester</li> <li>le code source est converti en tokens, puis chaque token transformable est transformé en mutant</li> <li>chaque suite de test va être relancée sur chaque mutation de code</li> </ol> <p>Bien entendu, votre code source n'est jamais modifié. En réalité, l'outil joue avec un StreamWrapper spécifique pour le flux de fichier standard (file://) pour substituer la mutation à votre code originel.</p> <p>Voici quelques exemples de bugs qui peuvent être introduits :</p> <ul><li>remplacer un test d'égalité (" == ") par un test de non-égalite (" != ")</li> <li>remplacer " true " par " false "</li> <li>supprimer un bloc " else "</li> ... </ul> <p>Voici le résultat de la mutation pour les tests de MutaTesting même :</p> <p> <img src="http://www.guanzhui39.top/images/2013-06-mutation-result-console.png" alt="Résulat global de la mutation" width="394" height="258" class="aligncenter size-full wp-image-756" /> </p><p> <p>L'analyse du code source a donc permis de créer 46 mutants, donc 26 ont survécu. Le score des tests est donc de 43 %.</p> </p><p> <p>Les tests unitaires ont donc des anomalies. Relançons le même outil pour obtenir un compte rendu au format HTML, plus complet (option –format=html). Voici un aperçu de ce qui est obtenu :</p> </p><p> <a href="http://www.guanzhui39.top/images/2013-06-mutation-result-html.png"><img src="http://www.guanzhui39.top/images/2013-06-mutation-result-html.png" alt="Rapport détaillé HTML de la mutation" width="476" height="359" class="aligncenter size-full wp-image-757" /></a> </p><p> <p>Cette fois-ci on a plus de détails : les mutants sont détaillés et regroupés par fichier de source.</p> </p><p> <p>Ce qui est intéressant, c'est que les sources qui ressortent comme les moins bien testées sont justement celles que je n'ai pas développées par TDD. L'analyse de ce rapport va donc me permettre de me focaliser sur les tests unitaires qui concernent les sources sur lesquelles le plus de mutations a survécu.</p> </p> <h2>Conclusion</h2> <p> <p>Le Mutation Testing est donc finalement assez simple : on introduit des bugs dans un code source, puis on vérifie qu'ils sont bien détectés par les tests unitaires.</p> </p><p> <p>Ce qui est surtout intéressant c'est que cette pratique ne nécessite aucun développement en plus, mais simplement l'utilisation d'un outil, automatisé</p> </p><p> <p>Concernant MutaTesting même, que j'ai développé et qui est disponible sur Github, je pense que c'est un outil que j'espère simple à utiliser et à étendre. Bien que fonctionnel, il peut largement être amélioré. Les Pull Requests sont les bienvenues :-) .</p> </p><p> <p>De la même façons, vos retours sur les bienvenus : sur vos pratiques, vos attentes en terme d'outils...si l'envie vous prend de l'essayer, voire d'y contribuer.</p> </p><p> <p>PS: j'ai eu pas mal de remarques sur la lisibilité du thème de ce blog. J'ai changé de thème ; c'est mieux ? :p</p> </p> Mais... on peut faire "ça" en PHP ? Mais c'est horrible ! 2013-03-26T00:00:00+00:00 http://www.guanzhui39.top/php/mais-on-peut-faire-ca-en-php-mais-cest-horrible <p>Pour changer, je ne vais pas parler de ce qui est super avec PHP, mais plutôt de ce qui pue dans PHP. Et oui...</p> <p> Attention, qu'on ne me fasse pas dire ce que je n'ai pas dit : j'adore PHP ! C'est la techno que j'utilise tous les jours, c'est un beau langage, riche et puissant, mais aussi très souple, et il est facile de faire n'importe quoi avec. Les codes suivants sont des exemples de ce qu'on peut faire grâce (ou à cause) de cette souplesse. </p> <p> Ces techniques sont vraiment crades, mais peuvent parfois, en dernier recours, être utiles. Après, si vous voyez un code qui les utilise, posez-vous la question : est-ce PHP qui est crade, ou bien n'est-ce pas plutôt le développeur derrière qui ne s'est pas posé assez de questions ? </p> <h2>Redéfinir la portée d'un attribut</h2> <p>Commençons doucement. Un attribut privé est un attribut qui n'est accessible que par la classe qui le possède (par définition)... Ah bon ? Et si on essaye de changer sa portée ?</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Foo { private $bar = 5; public function bar() { return $this-&gt;bar; } } $foo = new Foo; $attribute = new ReflectionProperty($foo, 'bar'); $attribute-&gt;setAccessible(true); $attribute-&gt;setValue($foo, 'nouvelle valeur'); var_dump($foo-&gt;bar()); // string(15) "nouvelle valeur" </code></pre></figure> <p>Nom de zeus ! On vient de modifier la valeur d'un attribut privé !</p> <h2>Lire des attributs privés</h2> <p>On a pu changer la portée d'une variable, mais l'honneur est sauf : il n'est pas possible de lire un attribut privé, tant qu'il reste privé.</p> <p>Bon, il doit bien y avoir une solution, non ? Essayons avec un petit coup de serialize().</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Foo { private $bar = 5; } $foo = new Foo; $serialized = serialize($foo); preg_match('!bar";(.):(.*);!', $serialized, $matches); list(,$type,$value) = $matches; var_dump("bar vaut $value"); // bar vaut 5 </code></pre></figure> Facile ! Bien sûr, l'exemple est trivial, il existe des <a href="http://www.sitepoint.com/how-to-expose-phps-private-parts/" target="_blank">solutions plus complètes</a>. <h2>Hériter... directement de ses grand-parents</h2> <p>L'héritage c'est pratique ; un objet hérite de son parent, qui lui-même hérite d'un autre objet, le tout dans une harmonie parfaite.</p> <p>Mais attendez, saviez-vous qu'il est possible d'hériter de sa classe grand-mère, sans passer par sa classe Mère ? (par pitié, n'essayez pas de vous représenter ce que ça ferait dans la vraie vie)</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class GrandMother { public $bar; public function foo() { return $this-&gt;bar. ' in GrandMother'; } } class Mother extends GrandMother { public function foo() { return $this-&gt;bar. ' in Mother'; } } class Child extends Mother { public function foo() { return GrandMother::foo(); } } $child = new Child; $child-&gt;bar = 'abc'; var_dump($child-&gt;foo()); // string(18) "abc in GrandMother" </code></pre></figure> <p>Il suffit donc de ne pas utiliser le mot-clef "parent", mais directement le nom de la classe Grand-mère. Le contexte ($this) est bel et bien préservé. C'est un reliquat de PHP 4...</p> <h2>Ne pas donner de nom à une variable</h2> <p>Toute variable possède un nom : $i, $foo, $bar... Que se passe t-il si vous tenez de déclarer une variable sans lui donner de nom ?</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$ = 5; // PHP Parse error: syntax error, unexpected '=', expecting variable </code></pre></figure> <p>Arf... Mais attendez, ne peut-on pas utiliser l'évaluation dynamique des noms de variables pour créer une variable dont le nom serait une chaîne vide ? Essayons :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$foo = ''; ${$foo} = 5; print_r(get_defined_vars()); // ... // [foo] =&gt; // [] =&gt; 5 //) </code></pre></figure> <p> Et bien si, on peut. Constatez au passage que la variable est bien visible dans le get_defined_vars(). </p> <p> Du coup, on peut s'amuser à faire plein de trucs pas très utiles, comme nommer sa varaible $0 par exemple : </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">echo $0; // syntax error, unexpected '0' ... echo ${0}; // ok </code></pre></figure> <h2>Transtyper un objet</h2> <p>Un objet est d'une classe donnée. Chaque classe possède un contexte propre (des attributs) et son propre comportement.</p> <p>Du coup il ne devrait pas être possible de réutiliser un objet A pour en faire un objet B. Et pourtant ...</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Foo { public $a = 5; public $b = 6; } class Bar { public $a = 'abc'; public $b = 'def'; } $foo = new Foo; $bar = new Bar; $object = convert($foo, 'Bar'); print_r($object); //Bar Object //( // [a] =&gt; 5 // [b] =&gt; 6 //) </code></pre></figure> <p>Comment a t-on fait ? C'est assez simple, il suffit une fois de plus de sérializer notre objet, de modifier une valeur dans la chaîne obtenue, puis de créer un nouvel objet.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function convert($object, $class) { $serialized = serialize($object); $className = get_class($object); $len = strlen($className); $start = $len + strlen($len) + 6; $serializedInfos = 'O:' . strlen($class) . ':"' . $class . '":'; $serializedInfos .= substr($serialized, $start); return unserialize($serializedInfos); } </code></pre></figure> <p>Vous trouverez de <a href="http://www.php.net/manual/en/language.types.type-juggling.php#100460" target="_blank">nombreux exemples</a> sur le net.</p> <h2>"Ecouter" les changements d'une variable</h2> <p> Vous connaissez sûrement la fonction JavaScript <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/watch" target="_blank">watch()</a>, très pratique, qui permet d'écouter les changements qui peuvent survenir sur une variable. </p> <p> Vous me voyez venir : il est possible (à grands frais : lenteurs, charge mémoire, etc !) de faire la même chose en PHP. Je vous aurai prévenu : ne faites pas ça en prod ! </p> <p> L'idée consiste à utiliser une <a href="http://www.php.net/manual/fr/function.register-tick-function.php" target="_blank">fonction qui va être exécutée à chaque tick</a>, pour observer si la variable que l'on souhaite "écouter" à été modifiée ou non. Si c'est le cas, un simple appel à <a target="_blank" href="http://www.php.net/manual/fr/function.debug-backtrace.php">debug_baktrace()</a> nous permettra de savoir comment cette variable a été modifiée. </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$var = 'abc'; function tick() { global $var, $expectedVar; if (isset($var)) { if (isset($expectedVar) <span class="err">&amp;&amp;</span> $var !== $expectedVar) { // // La variable a été modifiée $context = debug_backtrace(); $where = (isset($context[1]['class']) ? $context[1]['class'] . '::' : '') . $context[1]['function'] . '()'; printf('la variable $var a été modifiée par %s (fichier %s, ligne %d), et vaut désormais "%s"' , $where, $context[1]['file'], $context[1]['line'], $var); } $expectedVar = $var; } } // // Enregistrons notre pseudo "écouteur" register_tick_function("tick"); declare(ticks = 1); </code></pre></figure> <p>Vérifions :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">// // L'heure du test function foo() { global $var; $var = 'def'; } foo(); // // Affiche : // // la variable $var a été modifiée par foo() // (fichier /home/data/www/jeff/misc/php-berk/watch-var.php, ligne 39), // et vaut désormais "def" </code></pre></figure> <h2>C'est tout ?</h2> <p>J'hésite à aller plus loin : on pourrait parler encore de ce qu'il est possible de faire avec les __PHP_Incomplete_Class, de la confusion possible à utiliser des fonctions comme <a target="_blank" href="http://php.net/class_alias">class_alias()</a> (<a target="_blank" href="http://blog.mageekbox.net/?post/2013/01/17/Modifier-un-espace-de-nom-sans-casser-la-r%C3%A9tro-compatibilit%C3%A9">quoique parfois utile</a>), de ce qu'il est affreusement possible de faire avec <a target="_blank" href="http://php.net/runkit">runkit</a>, dela possibilité de remplacer $_GET par une valeur de notre choix... Mais je crois que ça suffira là :) .</p> <p> Bref, vous l'aurez compris, si je suis vraiment convaincu que PHP est un super langage, je vois passer beaucoup de mauvais code, de mauvais développeurs et de mauvaises pratiques, qui me font quotidiennement prendre conscience qu'il est possible de faire vraiment n'importe quoi en développement. Lorsque l'on sort des sentiers battus, on doit avoir une bonne raison et comprendre pourquoi on le fait.</p><p>PHP est un beau langage, ce n'est pas parce qu'il est "facile" de débuter en PHP qu'il faut en faire n'importe quoi. Heureusement, on peut aussi faire de très belles choses avec ! Et c'est justement ça le job d'un développeur. </p> Behat - créer des tests solides et efficaces 2013-02-25T00:00:00+00:00 http://www.guanzhui39.top/php/ressources-tutos-php/behat-creer-des-tests-solides-et-efficaces <p> <p>Ca commence à faire déjà quelques temps que j'utilise Behat, et j'ai eu la chance de pouvoir l'utiliser sur différents projets, gros et petits. Je commence donc à avoir un peu plus de recul sur la chose, et à avoir accumulé pas mal de mauvaises pratiques.</p> </p> <p>Par manque de chance (ou pas ^^), il a fallu que je commence à utiliser Behat sur un projet assez important. C'est donc là que j'ai commencé à faire mes plus grosses erreurs. Je vous propose de voir quelles ont été ces erreurs, et comment éviter de les reproduire. </p> <h2>Utiliser Behat sans faire du Développement piloté par le comportement</h2> <p>C'est évident, mais il faut le dire : Behat n'est qu'un outil, ce qui compte vraiment c'est le Développement piloté par le comportement. Et oui ! Behat n'est ni un outil de test, ni un outil de spécification. Ce n'est "rien de plus" qu'un bonus par rapport à une démarche de travail.</p> <p> <p>Si on prend Behat, comme il m'est arrivé de le faire, comme simple outil de test, on fonce dans le mur : au bout de plusieurs mois on risque d'avoir substitué Behat à des outils de tests unitaires (PHPUnit, atoum). Behat permet (entre autres) de tester du besoin fonctionnel. Encore faut-il que ce besoin soit exprimé !</p> </p> <h2>Faire du refractoring de code plutôt que du refractoring de phrases</h2> <p>Allez, créons une méthode privée réutilisable dans notre Contexte de définition. Ah, et puis passons la publique le jour où il nous faudra l'utiliser dans un autre Contexte...</p> <p>Au final on se retrouve avec une application dans l'application. Quand c'est le cas, et ça m'est arrivé, c'est qu'on confond refractoring de code et refractoring de définitions.</p> <p>Les Contextes de définition servent à traduire des expressions en code source. C'est donc sur ces expressions qu'il faut se focaliser. L'idéal est de réussi à parvenir à découper chaque expression en unités atomiques fondamentales.</p> <p>Par exemple :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nf">Quand</span> que j'ajoute dans mon panier <span class="s">"Télévision Sony"</span> depuis le catalogue produits</code></pre></figure> <p>On peut découper cette expression en sous-expressions (étapes)</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nf">Etant donné</span> que je suis sur la page du catalogue produit <span class="nf">Quand</span> j'ajoute <span class="s">"Télévision Sony"</span> au panier</code></pre></figure> <p>Et au final on peut arriver à des expressions atomiques simples :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nf">Etant donné</span> que je suis sur <span class="s">"/catalogue/produits"</span> <span class="nf">Quand</span> je sélectionne <span class="s">"Télévision Sony"</span> <span class="nf">Et</span> je clique <span class="s">"Ajouter au panier"</span></code></pre></figure> <p>Vous voyez que là on fait du refractoring de phrases. L'avantage est que c'est hyper simple avec Behat :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="k">use</span> <span class="nx">Behat\Behat\Context\Step</span><span class="p">;</span> <span class="sd">/** * @Given /^que je consulte le catalogue produit$/ */</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">queJeConsulteLeCatalogueProduit</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">array</span><span class="p">(</span> <span class="k">new</span> <span class="nx">Step\Given</span><span class="p">(</span><span class="s1">'que je suis sur la page "/catalogue/produits"'</span><span class="p">)</span> <span class="p">);</span> <span class="p">}</span> <span class="o">//</span> <span class="nx">etc</span></code></pre></figure> <p>Il suffit d'utiliser des sous-appels d'étapes dans la définitions. </p><p>C'est la seule manière que j'ai trouvé pour se retrouver avec des Contextes de définition simples et surtout stables dans le temps. Sinon on passe notre temps à les réécrire dans leur ensemble. </p><p>Par contre ça oblige à avoir plein de définitions, d'où le point suivant :</p> <h2>Utiliser Behat sans couche d'isolation</h2> <p>Au départ, quand on utilise Behat, on a tendance à se créer 2 ou 3 contextes de définition, et puis c'est tout. C'est à mon avis une erreur. Aujourd'hui, je démarre chaque projet qui utilise Behat avec au moins ce découpage :</p> <ul> <li>Contextes de définition métiers</li> <li>Contexte de définition de vue (web)</li> <li>Couche d'isolation pour la persistance des données si besoin</li> </ul> <p> <p>Pourquoi ? Tout simplement parce que je me suis rendu compte d'une chose : il arrive souvent de devoir repasser sur des définitions ! Et oui. Et c'est long, laborieux et, disons-le, très très démotivant.</p> </p><p> <p>Il faut au moins regrouper les définitions qui concernent l'interface graphique dans un contexte spécifique. Par défaut, si vous utilisez Mink, c'est ce qui fait plus ou moins implicitement lorsque vous utilisez des définitions toute-prêtes de Mink.</p> </p><p> <p>Mais que se passera t-il quand le client vous annoncera qu'il sort une appli mobile ? Normalement, appli mobile ou web, aucune spécification ne devrait changer. Par contre, si vous avez mal organisés vos Contextes de définition, vous allez galérer.</p> </p><p> <p>Au contraire, si vous regroupez tout ce qui concerne l'UI dans un Contexte de vue, mettons par exemple "View\WebContext.php", il vous suffit simplement de gérer vos sous-contextes :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">public function __construct(array $parameters) { if ($parameters['view'] == 'mobile') { $this-&gt;useContext('view', new View\MobileContext($parameters)); } else { $this-&gt;useContext('view', new View\WebContext($parameters)); } }</code></pre></figure> <p>C'est aussi simple que ça : il vous suffit de regrouper tout ce qui concerne l'affichage dans un Contexte spécifique.</p> <p>Bien entendu, pour les très grosses applications on peut aller plus loin, et d'ailleurs ça devient très vite intéressant : créer un Contexte par page. Par exemple:</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$this-&gt;useContext('view.catalogue', new View\Web\CatalogueContext($parameters)); $this-&gt;useContext('view.panier', new View\Web\PanierContext($parameters));</code></pre></figure> <p>Ca peut paraître extrémiste, mais dans le cas où votre application est conséquente, c'est très utile. Il faut bien se dire que tout ce travail consiste à solidifier votre architecture de test. De la même manière qu'il faut découper une application en blocs de code (modules), il faut découper les Contextes de définition selon leur... contexte.</p> <p>Après, il faut prendre la juste mesure des choses, et trouver un découpage adapté :</p> <ul> <li>aux besoins</li> <li>à la taille du projet</li> <li>au temps disponible</li> <li>à la compétence de l'équipe</li> </ul> <p>Exactement comme on le ferait pour un applicatif.</p> <p>Voilà pour ces quelques retours que je souhaitais partager avec vous. Après, ils sont basés sur mon expérience personnelle, et je serai très curieux de savoir ce que vous en pensez, et surtout comment vous vous avez fait pour gérer la croissance de vos projets utilisant Behat / Cucumber / etc.. N'hésitez pas à partager votre expérience...</p> dependency.me, un service pour savoir si vos dépendances composer sont à jour 2013-01-30T00:00:00+00:00 http://www.guanzhui39.top/php/actus-php/dependency-me-un-service-pour-savoir-si-vos-dependances-composer-sont-a-jour <p>Ça faisait pas mal de temps que l'idée me trottait en tête : un service pour savoir si l'on utilise des dépendances composer à jour ou non.</p> <p>Tel que je le voyais, je trouvais optimal de pouvoir intégrer une image à notre readme github, exactement comme pour <a href="https://travis-ci.org/">Travis-Ci</a>. Bien entendu, j'ai cherché sur le net ce que je pouvais trouver, et j'ai été assez surpris de trouver pas mal d'outils pour Ruby, Python, NodeJs... mais rien pour les dépendances de PHP avec composer !</p> <p>Prenant mon courage à deux mains, je me suis donc décidé à coder (très rapidement) <a href="http://dependency.me">http://dependency.me</a>. Il vous suffit de vous connecter avec votre compte Github et de sélectionner les repositories pour lesquels vous souhaitez activer le service, vous aurez en temps (quasi) réel une idée de l'état de vos dépendances, branche par branche :</p> <p><a href="http://www.guanzhui39.top/images/2013-01-capture-composer-dependency-owner.jpg"><img src="http://www.guanzhui39.top/images/2013-01-capture-composer-dependency-owner.jpg" alt="Vue de l&#039;état des dépendances Composer par propriétaire" width="400" height="184" class="aligncenter size-full wp-image-690" /></a></p> <p>Bien entendu, il est tout à fait possible d'avoir le détail pour chaque branche :</p> <p><a href="http://www.guanzhui39.top/images/2013-01-capture-composer-dependency-branche.jpg"><img src="http://www.guanzhui39.top/images/2013-01-capture-composer-dependency-branche.jpg" alt="Vue de l&#039;état des dépendances Composer par branche" width="400" height="115" class="aligncenter size-full wp-image-691" /></a></p> <p>Le site vous donne pour chaque dépôt le Markdown à copier-coller dans votre readme.md (par exemple) pour afficher une image mise automatiquement à jour selon l'état de vos dépendances :</p> <ul> <li><a href="http://www.guanzhui39.top/images/2013-01-build-status-latest.png"><img src="http://www.guanzhui39.top/images/2013-01-build-status-latest.png" alt="build-status-latest" width="170" height="13" class="aligncenter size-full wp-image-693" /></a></li> <li><a href="http://www.guanzhui39.top/images/2013-01-build-status-outofdate.png"><img src="http://www.guanzhui39.top/images/2013-01-build-status-outofdate.png" alt="build-status-outofdate" width="170" height="13" class="aligncenter size-medium wp-image-694" /></a></li> <li><a href="http://www.guanzhui39.top/images/2013-01-build-status-recent.png"><img src="http://www.guanzhui39.top/images/2013-01-build-status-recent.png" alt="build-status-recent" width="170" height="13" class="aligncenter size-medium wp-image-695" /></a></li> <li><a href="http://www.guanzhui39.top/images/2013-01-build-status-unknown.png"><img src="http://www.guanzhui39.top/images/2013-01-build-status-unknown.png" alt="build-status-unknown" width="170" height="13" class="aligncenter size-medium wp-image-696" /></a></li> </ul> <p>Bien entendu, le site est Open Source, et est <a href="https://github.com/DependencyMe/dependency.me">hébergé sur Github</a>.</p> <p>Toutes les bonnes âmes souhaitant m'aider sont les bienvenues : qu'il s'agisse de signaler les bugs, me faire retours, et pourquoi pas directement à faire évoluer le code concerné. Je pense qu'on peut faire mieux : actuellement le service ne gère pas les dépôts privés par exemple... Et un coup de pouce pour l'anglais serait le bienvenue !</p> <p>Je suis également preneur de toute remarque, positive ou négative, sur ce service.</p> <p>Merci à vous !</> Doctrine n'est pas un ORM ? 2013-01-04T00:00:00+00:00 http://www.guanzhui39.top/php/doctrine-nest-pas-un-orm <p>Pour bien commencer l'année, je vous propose de troller un peu sur les ORM. Ce qui suit est mon point de vue et n'engage que moi :)</p> <p>Selon moi, Doctrine 2 représente un tournant majeur dans le monde de PHP, sans doute plus encore que Symfony2 ou Zend Framework 2. Mais j'ai le sentiment que Doctrine n'est pas utilisé à sa juste valeur...</p> <h2>Les ORM classiques</h2> <p>Prenons un peu de recul ; qu'est-ce qu'un ORM ? Traditionnellement, un ORM est une technique pour représenter des enregistrements (et leurs relations) sous forme d'objets. Généralement, on implémente pour cela principalement ces patterns :</p> <dl><dd><strong>Table Data Gateway</strong>:</dd><dt>Un objet est une passerelle vers une table</dt><dd><strong>Active Record</strong>:</dd><dt>Un objet représente un enregistrement et dispose du comportement nécessaire pour implémenter la logique métier de cet enregistrement</dt><dd><strong>Data Access Object</strong>:</dd><dt>Un objet est utilisé pour encapsuler tous les accès vers une source de donnée</dt></dl> <br /> <p>C'est ce qui se passe depuis des années, par exemple avec Zend Framework 1 :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="c1">// Table Data Gateway </span><span class="k">class</span> <span class="nc">TableUsers</span> <span class="k">extends</span> <span class="nx">Zend_Db_Table_Abstract</span> <span class="p">{</span> <span class="c1">// nom de la table et de la base </span> <span class="k">protected</span> <span class="nv">$_name</span> <span class="o">=</span> <span class="s1">'tbl_users'</span><span class="p">;</span> <span class="k">protected</span> <span class="nv">$_schema</span> <span class="o">=</span> <span class="s1">'database_name'</span><span class="p">;</span> <span class="p">}</span> <span class="nv">$table</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TableUsers</span><span class="p">;</span> <span class="c1">// Rowset : jeu de résultat Traversable </span><span class="nv">$users</span> <span class="o">=</span> <span class="nv">$table</span><span class="o">-&gt;</span><span class="na">find</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="cp">?&gt;</span></code></pre></figure>   <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="k">class</span> <span class="nc">User</span> <span class="k">extends</span> <span class="nx">Zend_Db_Table_Row_Abstract</span> <span class="p">{</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">resetPassword</span><span class="p">()</span> <span class="p">{</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">pwd</span> <span class="o">=</span> <span class="nb">md5</span><span class="p">(</span><span class="nb">uniqid</span><span class="p">());</span> <span class="p">}</span> <span class="p">}</span> <span class="nv">$user</span> <span class="o">=</span> <span class="nv">$table</span><span class="o">-&gt;</span><span class="na">fetchRow</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'id'</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">));</span> <span class="nv">$user</span><span class="o">-&gt;</span><span class="na">name</span> <span class="o">=</span> <span class="s1">'Jean-François'</span><span class="p">;</span> <span class="nv">$user</span><span class="o">-&gt;</span><span class="na">resetPassword</span><span class="p">();</span> <span class="nv">$user</span><span class="o">-&gt;</span><span class="na">save</span><span class="p">();</span> <span class="cp">?&gt;</span></code></pre></figure> <h2>Et les principes SOLID ?</h2> <p>Bon, vous allez me dire que des milliers de projets ont été faits de cette manière, et que ça fonctionne.</p> <p>Oui, ça fonctionne, mais ça tombe en marche : les coûts de maintenance sont nécessairement élevés, vu que <strong>le code ne respecte en rien les principes SOLID</strong>. Et oui, <strong>notre objet User a plusieurs responsabilités</strong> ! Il a la responsabilité de représenter une information, de gérer ses règles métiers, de s'enregistrer... Bref, c'est une God Class (un objet dieu), ce n'est pas de l'orienté objet... Et on fonce dans le mur.</p> <p>En réalité, <strong>un ORM ne peut pas partir du postulat qu'on va représenter des enregistrements par des objets. Non, il doit faire l'inverse !</strong></p> <p>Je m'explique : le principe de la programmation orientée objet est de manipuler des objets. Il existe différents types d'objet : des agrégats, des objets valeurs... Pour conserver un code propre et maintenable, il ne faut se focaliser que sur le comportement des objets que l'on souhaite utiliser). La manière dont ils sont stockés n'a aucune importance. Vraiment aucune ! Si si; j'insiste : aucune ! Après tout, qu'ils soient stockés sous forme de fichiers, d'une base relationnelle, d'un document... tout cela n'impacte pas le comportement de votre application. Non, ça n'aura un impact que sur la couche basse d'infrastructure de votre application, exclusivement.</p> <h2>L'apport du Domain Driven Design</h2> <p>Le Domain Driven Design est une approche qui tente de focaliser la conduite d'un projet informatique sur le besoin métier exclusivement. Très grossièrement, l'idée est de maximiser la communication entre les équipes techniques et les équipes fonctionnelles pour faciliter l'expression du besoin, puis de calquer le code source sur cette expression de besoin. De cette manière, le code source est en permanence le reflet exact du besoin tel qu'il a été exprimé par les fonctionnels.</p> <h3>Identité et entité</h3> <a href="/php/doctrine-nest-pas-un-orm/images/identity_crisis_cat_by_sebreg-d5fcofy-resized.jpg" rel="attachment wp-att-662"><img class="aligncenter size-full wp-image-662" alt="Crise d'identité - Domain Driven Design" src="http://www.guanzhui39.top/images/2013-01-identity_crisis_cat_by_sebreg-d5fcofy.resized.jpg" width="200" height="193" /></a> <p>Que nous dit le DDD (Domain Driven Design) ? <strong>Que chaque projet manipule des concepts, des noms communs, qui semblent plus importants que les autres</strong>. Ce pourra être par exemple les mots "acheteur", "panier" et "produit" dans le cas d'une boutique en ligne.</p> <p>Quelle est la spécificité de ces mots ? C'est qu'ils représente des choses différentiables les unes des autres de manière certaine : chaque produit est unique, chaque acheteur se distingue des autres acheteurs ; bref, chacune des ces choses est unique. On dira dans ce cas que chaque acheteur, chaque produit, <strong>ont une identité propre</strong>.</p> <p>Cette notion d'identité est fondamentale : une identité est unique, et est marquée par des informations d'état de l'objet à laquelle elle s'applique. Par exemple, mon identité pourrait être marquée par mon numéro de sécurité sociale.</p> <p>En informatique, on a longtemps oublié cette notion d'identité, en utilisant un "identifiant unique" (id) à la place d'une vraie information. Souvent, par exemple, on stocke des informations sur des comptes bancaires dans une table qui a une colonne "id" et une autre "account_number". </p> <p>C'est une erreur conceptuelle : la colonne "id" n'a aucune valeur fonctionnelle. Ce qui fait l'identité du compte, c'est son numéro de compte. C'est donc la colonne "account_number" qui doit être utilisée pour distinguer un compte d'un autre ! Je conçois qu'il est pratique d'ajouter une colonne "id" pour des raisons d'index et de performance dans une table, mais cet "id" ne doit pas être utilisé dans le code source, puisqu'il ne représente rien en terme de métier (fonctionnellement donc).</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="k">class</span> <span class="nc">Account</span> <span class="p">{</span> <span class="k">private</span> <span class="nv">$accountNumber</span> <span class="o">=</span> <span class="s1">'xxxxxxxxxx'</span><span class="p">;</span> <span class="p">}</span> <span class="cp">?&gt;</span></code></pre></figure> <p>Ces choses, qui ont une identité, qui sont absolument uniques, sont désignées par le terme d'entités, ou "entity" en anglais dans le monde du Domain Driven Design.</p> <p>Ah, comme dans Doctrine ? Oui, comme dans Doctrine... Les entités sont des objets qui ont une identité. Simplement, et uniquement.</p> <p>Dans le DDD, il existe des collections d'entités, que l'on appelle des Agrégats (Aggregate). Oui, toujours comme dans Doctrine :)</p> <h3>Les services</h3> <p>On a vu qu'il existait des concepts importants dans tout projet informatique, concepts représentés par des noms communs.</p> <strong>Il existe d'autres concepts très importants, qui sont eux représentés par des verbes</strong> : "acheter", "vendre", "ajouter dans le panier"... <p>Comment représenter techniquement ces concepts ? En créant, nous dit le DDD, des objets "Services" :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="k">class</span> <span class="nc">ServicePanier</span> <span class="p">{</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">ajouterProduit</span><span class="p">(</span><span class="nx">ProduitInterface</span> <span class="nv">$produit</span><span class="p">,</span> <span class="nx">PanierInterface</span> <span class="nv">$panier</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ... </span> <span class="p">}</span> <span class="p">}</span> <span class="cp">?&gt;</span></code></pre></figure> <p>Ces objets services sont justement les objets que votre application doit manipuler. Ils constituent le comportement global de votre application. C'est donc eux qui doivent être utilisés dans les Contrôleurs, et eux seuls.</p> <h3>Les dépôts / Repositories</h3> <p>Un projet informatique nécessite souvent de retrouver des informations. Ou plutôt, de retrouver ce qui est désigné fonctionnellement par des noms communs ("acheteur", "panier", "produit"...) et qui possède une identité.</p> <p>C'est le rôle des dépôts (Repository). Un Repository ne sert qu'à retrouver des informations. Et uniquement selon des critères fonctionnels ; on ne doit pas appeler un repository en passant des tableaux de paramètres... Non, il faut continuer de penser SOLID :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="k">class</span> <span class="nc">RepositoryProduit</span> <span class="p">{</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">listerProduitsDuPanier</span><span class="p">(</span><span class="nx">PanierInterface</span> <span class="nv">$panier</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ... </span> <span class="p">}</span> <span class="k">public</span> <span class="k">function</span> <span class="nf">recupererProduit</span><span class="p">(</span><span class="nv">$identite</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ... </span> <span class="p">}</span> <span class="p">}</span> <span class="cp">?&gt;</span></code></pre></figure> <p>Attendez, on a aussi le concept de Repository dans le DDD, comme dans Doctrine ?! Et oui ! Vous voyez pourquoi je fais tout ce détour par le DDD pour parler de Doctrine :-)</p> <h3>Et bien plus ...</h3> <p>Il y a encore plein de choses dans le DDD, et j'ai schématisé très grossièrement, mais vous voyez où je veux en venir : <strong>Doctrine n'est pas un ORM classique</strong> (et ne doit pas être utilisé comme tel), Doctrine est un super outil pour pratiquer l'approche du Domain Driven Design.</p> <strong>Surtout, la démarche de Doctrine n'est pas de mapper une base de données sur des objets. Non, c'est exactement l'inverse : on part des objets (et donc du besoin fonctionnel) pour ensuite les faire persister.</strong> <p>De nombreux concepts du DDD sont présents dans Doctrine. Du coup, pourquoi ne pas penser Domain Driven Design dans vos projets Doctrine ? Qu'il s'agisse de projets Symfony 2, Zend Framework 2 (avec le module Doctrine), ou autre... ?</p> <p>Et dans ce cas, si les entités sont des concepts fonctionnels importants (un panier, un produit, un acheteur...) qui ont une identité... <strong>par pitié ne créez pas d'entités pour vos relations</strong> dans Doctrine. Une table intermédiaire n'est pas une entité. D'ailleurs ça n'existe même pas : il n'existe que des relations entre les objets, tout le reste n'a aucun sens en terme de Programmation Orientée Objet.</p> <p>Et si vous voulez me répondre que créer des entités-relations dans Doctrine reste pratique vu que ça permet de gérer et contrôler plus facilement certaines relations, je vous répondrai que ce n'est pas à PHP de vérifier la cohérence d'une donnée, mais à un SGBD relationnel, et que le SGBD sera bien meilleur vu qu'il est conçu pour ça.</p> <p>Je crois que ça devrait être la résolution pour ce début d'année 2013 de tous les développeurs PHP : <strong>arrêtez de voir les ORM comme des solutions pour manipuler des bases de données.</strong></p> <p>Voyez plutôt des objets, qui ont des responsabilités, des comportements, parfois même une identité ; et considérez qu'il s'agit plutôt d'un accident quand on doit les persister.</p> <p>Après tout, si on faisait pas du web, le besoin fonctionnel resterait le même, mais on n'aurait pas besoin de persister nos "acheteurs", "produits" et "paniers". Il suffirait de "livrer", "commander"... Il n'y a que ça qui compte. Pas vrai ? :-)</p> Automatisation des tâches avec Phing 2012-11-16T00:00:00+00:00 http://www.guanzhui39.top/php/automatisation-des-taches-avec-phing <p>Je ne vais pas vous convaincre qu'un bon développeur est un développeur faignant, c'est à dire qui sait employer suffisamment d'énergie à un moment donner pour en gagner plein par la suite... Non, par contre, pour ceux qui ne l'utilisent pas encore, je vais vous montrer qu'on n'a pas besoin de se compliquer la vie lorsqu'on veut automatiser. On peut même s'organiser !</p> <p>Phing, c'est quoi ? Les mauvaises langues diront que c'est une version instable de Ant. C'est pas totalement faux, mais c'est loin d'être vrai. Phing, c'est un outil d'automatisation de tâches, mais spécialisé et conçu <u><strong>pour</strong></u> PHP, ce qui change vraiment tout, comme vous allez le voir. </p> <p>Ce qu'il faut noter également avant de commencer c'est que <a href="http://www.phing.info/docs/guide/stable/">la documentation est très riche</a>, même si elle n'est pas toujours 100% pratique.</p> <h2>Premiers pas avec Phing</h2> <p>L'installation de phing est simple :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;p&gt;</span>pear channel-discover pear.phing.info<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>pear install phing/phingdocs<span class="nt">&lt;/p&gt;</span></code></pre></figure> <p>Il suffit ensuite de créer un fichier build.xml, qui va décrire un certain nombre de tâches que l'on souhaite exécuter :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp">&lt;?xml version="1.0" ?&gt;</span> <span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"demo"</span> <span class="na">basedir=</span><span class="s">"."</span> <span class="na">default=</span><span class="s">"example"</span><span class="nt">&gt;</span> <span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"example"</span><span class="nt">&gt;</span> <span class="nt">&lt;echo</span> <span class="na">message=</span><span class="s">"yep, I will create a directory"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;mkdir</span> <span class="na">dir=</span><span class="s">"/my/path/"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/target&gt;</span> <span class="nt">&lt;/project&gt;</span></code></pre></figure> <p>Rien de bien compliqué : un projet est composé de n tâches (noeud target), identifiées par un nom (attribut name). Si j'ai besoin de créer le dossier "/my/path", il ne me reste plus qu'à exécuter :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">phing</code></pre></figure> <p>Pas besoin de paramètre : vous voyez l'attribut "default" du noeud "project" : c'est la tâche à lancer par défaut si rien n'est précisé, ce qui est le cas ici.</p> <h2>Un peu plus loin</h2> <p>Bon, ok, c'est rigolo... Mais on peut faire plein de choses : il n'y a aucune limite. Vous avez vu le noeud "mkdir". Il en existe des tas : "echo", mais aussi "phpunit", "ftpdeploy", "gitpull", "phpdepend"... Vous commencez à voir pourquoi utiliser Phing est intéressant : il gravite autour des utils PHP. Il existe enfin le noeud "exec", qui permet de lancer une commande classique. </p> <p>Un truc bien pratique aussi c'est l'utilisation de variable. Un variable c'est :</p> <ul> <li>un noeud "property" dans votre fichier xml</li> <li>une déclaration dans un fichier de propriété</li> <li>un paramètre que vous fournissez lorsque vous lancez phing, avec l'option -D</li> </ul> <p>Chaque variable est réutilisable, il suffit de l'écrire sous la forme ${maVariable}. Soit :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;property</span> <span class="na">name=</span><span class="s">"out"</span> <span class="na">value=</span><span class="s">"/my/path/"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"prepare"</span><span class="nt">&gt;</span> <span class="nt">&lt;mkdir</span> <span class="na">dir=</span><span class="s">"${out}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/target&gt;</span></code></pre></figure> <p>Je vais pouvoir écraser la valeur de ${out} en lançant phing :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">phing -Dout<span class="o">=</span>/another/path</code></pre></figure> <p>Je pourrai également importer un fichier de variables :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="c">&lt;!-- file phing.xml --&gt;</span> <span class="nt">&lt;property</span> <span class="na">file=</span><span class="s">"./my.properties"</span><span class="nt">/&gt;</span> <span class="c">&lt;!-- file my.properties --&gt;</span> out=/another/path var2=xxxx var3=xxxx</code></pre></figure> <p>Vous trouverez un <a href="https://gist.github.com/3794330">exemple d'utilisation basique de phing</a> pour générer un rapport minimal de vos sources : CodeSniffer pour vos conventions, PHPUnit pour vos tests, phpMessDetector pour le "smell code", rats pour la sécurité, CopyPasteDetector pour les copier-collers, CodeBrowser pour intégrer tout ça dans un navigateur... </p> <p>Vous voyez qu'en quelques lignes de xml on se retrouve à ne plus jamais avoir besoin de lancer ces outils à la main. En un mot : on automatise !</p> <p>Notez qu'il est possible de gérer la dépendance entre différentes tâches :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"t1"</span> <span class="na">depends=</span><span class="s">"t2,t3"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- (...) --&gt;</span> <span class="nt">&lt;/target&gt;</span></code></pre></figure> <p>Toute exécution de la tâche t1 lancera les tâches t2 et t3.</p> <h2>Gérer le plan de tâches</h2> <p>Ce qui est chouette avec Phing, c'est qu'on peut faire plein de choses. Et même gérer le déroulement de nos tâches... Comment ? Et bien par exemple avec des si et des alors. C'est possible !</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;if&gt;</span> <span class="nt">&lt;equals</span> <span class="na">arg1=</span><span class="s">"123456"</span> <span class="na">arg2=</span><span class="s">"${valueToCompare}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;then&gt;</span> <span class="nt">&lt;echo</span> <span class="na">message=</span><span class="s">"${valueToCompare}"</span> <span class="err">est</span> <span class="err">égal</span> <span class="err">à</span> <span class="err">123456"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/then&gt;</span> <span class="nt">&lt;else&gt;</span> <span class="nt">&lt;echo</span> <span class="na">message=</span><span class="s">"${valueToCompare}"</span> <span class="err">est</span> <span class="err">différent</span> <span class="err">de</span> <span class="err">123456"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/else&gt;</span> <span class="nt">&lt;/if&gt;</span></code></pre></figure> Pas mal non ? Bien sûr, on peut faire tout ce qui est classique : égal, différent, plus grand que, elseif... <p>Un autre moyen de gérer notre déroulement est de demander à phing d'interrompre le processus à moins que... à moins qu'une variable donnée soit vraie. Par exemple :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;fail</span> <span class="na">unless=</span><span class="s">"myVariable"</span> <span class="na">message=</span><span class="s">"On stoppe tout!"</span> <span class="nt">/&gt;</span></code></pre></figure> <h2>Structurer, découper et organiser les tâches Phing</h2> <p>Le risque de phing est de se retrouver à devoir copier-coller plein de tâches pour gérer les différents cas . Il existe un moyen simple d'éviter cela : chaque tâche est réutilisable par une autre (un peu à l'image d'une fonction), grâce au noeud "phingCall"</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"myTask1"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- (...) --&gt;</span> <span class="nt">&lt;/target&gt;</span> <span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"myTask2"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- we call myTask1 --&gt;</span> <span class="nt">&lt;phingCall</span> <span class="na">target=</span><span class="s">"myTask1"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/target&gt;</span></code></pre></figure> <p>Vous me direz : "Et les variables dans tout ça ? On peut les passer en paramètre ?". Bien sûr :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"myTask1"</span><span class="nt">&gt;</span> <span class="nt">&lt;echo</span> <span class="na">message=</span><span class="s">"ma variable vaut ${myVar}."</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/target&gt;</span> <span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"myTask2"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- we call myTask1 --&gt;</span> <span class="nt">&lt;phingCall</span> <span class="na">target=</span><span class="s">"myTask1"</span><span class="nt">&gt;</span> <span class="nt">&lt;property</span> <span class="na">name=</span><span class="s">"myVar"</span> <span class="na">value=</span><span class="s">"abcd"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/phingCall&gt;</span> <span class="nt">&lt;/target&gt;</span></code></pre></figure> <p>Pas mal non ?</p> <p>Pour organiser un peu mieux vos tâches, il est également possible de découper votre fichier xml en plusieurs sous fichiers, et de les importer (à la manière d'un require en PHP) :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;import</span> <span class="na">file=</span><span class="s">"./another-file1.xml"</span><span class="nt">/&gt;</span> <span class="nt">&lt;import</span> <span class="na">file=</span><span class="s">"./another-file2.xml"</span><span class="nt">/&gt;</span></code></pre></figure> <p>Les tâches ne sont pas exécutées, mais sont disponibles à l'utilisation lorsque l'on fait un phingCall. Comme un "require" donc...</p> <h2>Evaluer du PHP</h2> <p>Ce qui me plaît le plus avec Phing, c'est la possibilité d'évaluer du code PHP directement, sans se compliquer. Oui c'est possible, je vous ai dit que Phing était justement fait pour PHP.</p> <p>En plus c'est simple : l'évaluation de l'expression est directement mise dans la variable déclarée dans l'attribut "returnProperty" :</p> <figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;php</span> <span class="na">expression=</span><span class="s">"in_array('curl', get_loaded_extensions())"</span> <span class="na">returnProperty=</span><span class="s">"curlEnabled"</span><span class="nt">/&gt;</span> <span class="nt">&lt;fail</span> <span class="na">unless=</span><span class="s">"curlEnabled"</span> <span class="na">message=</span><span class="s">"You need cUrl to use it"</span> <span class="nt">/&gt;</span></code></pre></figure> <h2>Pour finir</h2> <p>Vous le voyez, Phing mérite d'être utilisé. Si vous voulez voir un projet plus complet (et pourquoi pas y contribuer, si si, j'insiste), je vous invite à regarder <a href="http://goo.gl/CerSr">PHPStarter</a>, qui est un simple automate d'installation de sites (téléchargement des sources du framework utilisé, création des virtual hosts...), et qui synthétise tout ce que je viens de présenter.</p> <p>Enfin, une petite astuce que m'a donnée mon collègue <a href="https://fr.twitter.com/gaspaio">@gaspaio</a>, et à laquelle je n'avais pas pensé : il suffit de définir comme tâche par défaut une tâche qui affiche de l'aide, et vous voici avec un outil encore plus structuré et simple d'utilisation. C'est ce qui se passe <a href="https://github.com/Halleck45/PhpStarter/blob/master/build.xml#L15-L50">par exemple ici</a>.</p> <p>Phing n'est pas parfait bien entendu, surtout que c'est finalement assez peu utilisé par rapport à son utilité, même si pas mal de monde l'utilise. D'ailleurs je suis curieux : qui l'utilise au quotidien ? De temps en temps ? Jamais ? Et parmi la dernière catégorie, qui compte l'utiliser maintenant ?</p> Industrialiser le Contrat dans un projet PHP - Slides 2012-10-31T00:00:00+00:00 http://www.guanzhui39.top/industrialisation/industrialiser-le-contrat-dans-un-projet-php-slides <p>Suite à l'invitation de l'antenne nantaise de l'AFUP pour un rendez-vous PHP (très sympa au passage!), j'ai eu envie de parler de la notion de Contrat dans un projet PHP, et surtout des outils pour s'assurer qu'un contrat, quel qu'il soit, soit respecté.</p> <p>En effet, mon opinion est que TOUT, absolument tout, est Comportement : une application est un comportement vis-à-vis d'une donnée entrante (requête HTTP) pour fournir une information (réponse HTTP) ; de la même façon une fonction est un comportement vis-à-vis d'une donnée entrante (paramètre) pour fournir une information (valeur de retour) ; etc.</p> <p>Or, qui dit "comportement", dit "contrat" pour s'assurer que le comportement souhaité est bel et bien appliqué. Je vous propose donc dans ces slides <a href="/php/ressources-tutos-php/communiquer-a-travers-internet">de retrouver</a> différents niveaux de contrat (code source, travail en équipe, besoin fonctionnel...), et à chaque fois différents outils pour s'assurer automatiquement qu'il sont respectés.</p> <p>Je suis curieux de vos retours sur ces notions de Comportement/Contrat, et aussi curieux de savoir quels outils vous avez l'habitude d'utiliser parmi ceux cités. N'hésitez pas à nous dire ça par commentaire :-)</p> <div style="margin-bottom:5px"> <strong> <a href="http://fr.slideshare.net/halleck45/industrialiser-le-contrat-dans-un-projet-php" title="Industrialiser le contrat dans un projet PHP" target="_blank">Industrialiser le contrat dans un projet PHP</a> </strong> from <strong><a href="http://fr.slideshare.net/halleck45" target="_blank">halleck45</a></strong> </div> Gérer des règles métiers complexes et/ou changeantes 2012-09-27T00:00:00+00:00 http://www.guanzhui39.top/php/gerer-des-regles-metiers-complexes-etou-changeantes <p>Désolé d'avance pour la longueur de ce billet ; comme ça fait longtemps que mon blog n'a pas été mis à jour, j'en profite pour faire un mini-tutoriel sur un sujet qui me tient à coeur : comment gérer les règles métier, autrement dit les Spécifications fonctionnelles, dans un projet php ?</p> <p> <p>Je pense qu'on a tous (enfin, on devrait :-) ) avoir en tête les principes <a href="http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29">SOLID</a>. Ces principes sont étroitement liées à la notion de Complexité Cyclomatique, qui elle, est moins connue.</p> </p> <p> <p>Derrière ce terme barbare, que vous connaissez bien si vous faites du test unitaire ou si vous utilisez des outils tels que <a href="http://pdepend.org/">PHPDepend</a>, se cache en réalité quelque chose de simple : si chaque bloc conditionnel de votre projet est un noeud, la complexité cyclomatique est la somme de l'ensemble des chemins empruntable dans votre projet.</p> </p> <p> <p>De cette manière, le code suivant :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">if</span> <span class="nx">c1</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="nx">a</span><span class="p">()</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">else</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="nx">b</span><span class="p">()</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">if</span> <span class="nx">c2</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="nx">c</span><span class="p">()</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">else</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="nx">d</span><span class="p">()</span></code></pre></figure> <p> <p>a une complexité cyclomatique plus élevée que</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">if</span> <span class="nx">c1</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="nx">a</span><span class="p">()</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">else</span><span class="o">&lt;/</span><span class="nx">p</span><span class="o">&gt;</span> <span class="nx">b</span><span class="p">()</span></code></pre></figure> <p> <p>Un programme maintenable est en général un programme dont la complexité cyclomatique est la plus faible possible. Et c'est justement l'enjeu de la POO de nous fournir un moyen de concevoir des applications de Complexité cyclomatique faible.</p> </p> <p> <p>Bon, ça c'est pour comprendre le principe. Après on me dit souvent : "<strong>oui, mais moi comment je fais pour gérer mes différents cas possibles si je dois limiter mes if() ?</strong>"</p> </p> <p> <p>Prenez cet exemple :</p> </p> <ul> <li>on a un panier de produits</li> <li>on ne peut ajouter que des produits en stock</li> <li>on ne peut ajouter que 20 produits maximum</li> <li>la règle ci-dessus ne s'applique pas en période de fêtes</li> <li><strong>les règles ci-dessus sont susceptibles de changer souvent</strong></li> </ul> <p> <p>Générallement on imbrique des if(), du coup on se retrouve avec un arbre applicatif assez large, c'est-à-dire de nombreux chemins possibles. A terme :</p> </p> <ul> <li>on a un risque de changement du code source très important (à chaque fois qu'on ajoute une règle métier)</li> <li>on gère mal l'ajout de nouvelles règles</li> <li>très vite l'algorithme devient imbuvable car trop complexe</li> </ul> <p> <p>C'est là qu'intervient la notion <strong>Specification</strong> (bon, je sais il est temps, l'intro était longue ;-) )</p> </p> <p> <p>Ce pattern répond à : "Comment gérer mes règles métier dans mon projet". Il est généralement associé au DDD (Domain Driven Design), mais on peut l'appliquer dans n'importe quel contexte qui s'y prête.</p> </p> <p> <p>L'idée est la suivante : chaque règle métier va être représentée par un objet (une Spécification), à qui l'on va demander si la règle est respectée :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$anyObject = new StdClass; $specification = new MySpecification; $isOk = $specification-&gt;isSatisfedBy($anyObject);</code></pre></figure> <p> <p>Là où ça devient puissant, c'est qu'on va pouvoir créer des Spécifications composites pour créer des règles métiers complexes à partir d'un ensemble de règles simples :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$anyObject = new StdClass; $specification = new MySpecification1() -&gt;and(new MySpecification2()) -&gt;and( new MySpecification3() -&gt;or(new MySpecification4()) ); ; $isOk = $specification-&gt;isSatisfedBy($anyObject);</code></pre></figure> <p> <p>Vous voyez les avantages : vous pouvez désormais appliquer n'importe quelle règle métier sans avoir à imbriquer plein de if() ; si vous souhaitez tester unitairement une règle, vous pouvez mocker les autres ; vos règles sont facilement évolutives...</p> </p> <p> <p>Bon concrètement comment ça se passe ? Il faut commencer par créer notre contrat pour le fonctionnement de nos Spécifications :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">interface SpecificationInterface { public function isSatisfiedBy($object); public function andSpec(SpecificationInterface $specification); public function orSpec(SpecificationInterface $specification); public function notSpec(SpecificationInterface $specification); }</code></pre></figure> <p> <p>Ensuite, pour permettre la création de Spécification composite il faut créer une classe abstraite générique pour nos spécifications.</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">abstract class Specification implements SpecificationInterface { public function andSpec(SpecificationInterface $specification) { return new AndSpecification($this, $specification); } public function orSpec(SpecificationInterface $specification) { return new OrSpecification($this, $specification); } public function notSpec(SpecificationInterface $specification) { return new NotSpecification($this); } }</code></pre></figure> <p> <p>Il ne nous reste plus qu'à déterminer le comportement de chacunes de nos structures de contrôle :</p> </p> <p>Pour le "et":</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class AndSpecification extends Specification implements SpecificationInterface { private $specification1; private $specification2; function __construct(SpecificationInterface $specification1, SpecificationInterface $specification2) { $this-&gt;specification1 = $specification1; $this-&gt;specification2 = $specification2; } public function isSatisfiedBy($object) { return $this-&gt;specification1-&gt;isSatisfiedBy($object) <span class="err">&amp;&amp;</span> $this-&gt;specification2-&gt;isSatisfiedBy($object); } }</code></pre></figure> <p> <p>Pour le "ou" :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class OrSpecification extends Specification implements SpecificationInterface { private $specification1; private $specification2; function __construct(SpecificationInterface $specification1, SpecificationInterface $specification2) { $this-&gt;specification1 = $specification1; $this-&gt;specification2 = $specification2; } public function isSatisfiedBy($object) { return $this-&gt;specification1-&gt;isSatisfiedBy($object) || $this-&gt;specification2-&gt;isSatisfiedBy($object); } }</code></pre></figure> <p> <p>Et enfin pour le "non" :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class NotSpecification extends Specification implements SpecificationInterface { private $specification; public function __construct($specification) { $this-&gt;specification = $specification; } public function isSatisfiedBy($object) { return !$this-&gt;specification-&gt;isSatisfiedBy($object); } }</code></pre></figure> <p> <p>Ca y est, on vient de se créer le minimum vital pour gérer nos règles métiers. Ca, c'est fait une bonne fois pour toute...</p> </p> <p> <p>Maintenant dans notre projet il suffit de faire hériter nos règles de la classe Specification. Par exemple :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class SpecLePannierPeutEtreRempli extends Specification { public function isSatisfiedBy($customer) { return(boolean) $x; // la condition de notre règle ici } } class SpecOnEstEnPeriodeDeFetes extends Specification { public function isSatisfiedBy($customer) { return (boolean) $x; // la condition de notre règle ici } } class SpecLeProduitEstEnStock extends Specification { public function isSatisfiedBy($customer) { return (boolean) $x; // la condition de notre règle ici } }</code></pre></figure> <p> <p>Et après il suffit simplement d'utiliser nos règles. Depuis PHP 5.4 on peut utiliser une interface fluide sur nos constructeurs, nous voici donc avec :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$specification = new SpecLeProduitEstEnStock() -&gt;and(SpecLePannierPeutEtreRempli()) -&gt;and( new SpecIlResteDeLaPlaceDansLePannier() -&gt;or(new SpecOnEstEnPeriodeDeFetes()) ); if($specification-&gt;isSatisfedBy(specification)) { $panier-&gt;ajouterProduit($produit); } else { throw new ArticleNePeutPasEtreMisDansLePannierException('...'); }</code></pre></figure> <p> <p>Pour synthétiser :</p> </p> <ul> <li>la Spécification permet de gérer des règles métiers</li> <li>elle permet de combiner n règles métiers dynamiquement</li> <li>elle facilite la gestion du changement fonctionnel</li> <li>elle facile la lisibilité des règles</li> <li>elle vous permet de mocker certaines parties des règles métier</li> <li>ce n'est pas un remède miracle, mais elle mérite d'être plus utilisé ^^</li> </ul> <p> <p>N'hésitez pas à laisser vos retours sur ce pattern Specification, je suis curieux de savoir s'il est utilisé massivement ou non ? Ou peut-être utilisez-vous déjà un framework de gestion de règles métier, comme ceux qu'il existe dans le monde du Java ?</p> </p> Behat - Une interface graphique pour rédiger / lister ses fonctionnalités 2012-07-31T00:00:00+00:00 http://www.guanzhui39.top/php/actus-php/behat-une-interface-graphique-pour-rediger-lister-ses-fonctionnalites <p>Encore du Behat, mais du neuf cette fois !</p> <p>Ca fait maintenant pas mal de temps que je planche sur une interface graphique pour utiliser Behat. Pourquoi ? Tout simplement parce que je trouve qu'un client / product owner n'a pas à plonger dans des fichiers textes imbuvables pour rédiger ses spécifications. C'est à mon avis le grand problème de Behat : seuls les développeurs savent l'utiliser.</p> <p>Pour rappel, <strong>Behat permet d'écrire des spécifications</strong> / tests de recette dans une syntaxe précise (Gherkin), puis de lancer une recette automatisée de ces spécifications, afin d'en ressortir la liste des fonctionnalités finies / en cours de développement / en échec.</p> <p>Comme je le disais, j'ai donc travaillé sur une <a href="http://halleck45.github.com/BehatWizardBundle/demo/behat/wizard/list.html" target="_blank">interface graphique</a> pour <strong>simplifier la vie des clients et des Products Owners</strong> qui souhaitent fournir des spécifications à leurs développeurs. Ca a été un travail intéressant pour pas mal de raisons.</p> <strong>La première de ces raisons est technique</strong> : je suis originaire du monde Zend Framework, cet outil a été pour moi l'occassion d'approfondir un peu mes connaissances de Symfony 2. <p>Une autre raison, sans doute la plus importante, concerne l'<strong>ergonomie de ce logiciel</strong>. C'est sans doute ce qui m'a pris le plus de temps : réussir à me plonger dans la tête d'un utilisateur non technique pour offrir une expérience utilisateur agréable à n'importe qui, même non développeur. Pas si simple, et peut-être pas si réussi, mais je suis tout de même assez satisfait, même si je me doute que cette appli va pas mal évoluer au fil du temps (j'attends vos retours là-dessus hein :-) ! ).</p> <p>Enfin, et c'est pas négligeable, <strong>c'est mon premier projet open-source !</strong> Certes j'ai déjà contribué par-ci par là à des projets open source, mais je n'avais jamais comme ça jusqu'à maintenant. Ce qui me pousse (et m'a poussé à consacrer pas mal de temps à ce projet) c'est l'envie de voir plus utilisé cet outil (Behat) que je trouve pertinent et adapté à la vie en entreprise, mais malheureusement trop difficile d'accès pour les décideurs. J'espère que cet outil sera un pas de plus pour l'appropriation du <acronym title="Behaviour Driven Development">BDD</acronym> par les entreprises.</p> <p>Quoi qu'il en soit, je rappelle je vous invite à <a href="http://halleck45.github.com/BehatWizardBundle/demo/behat/wizard/list.html" target="_blank">regarder la démo de ce produit</a> pour me dire ce que vous en pensez, et pourquoi pas à contribuer à son amélioration en forkant ce projet et en y ajoutant votre pierre. L'installation est, j'espère, on ne peut plus simple, vu que c'est un <a href="https://github.com/Halleck45/BehatWizardBundle#behatwizardbundle" target="_blank">simple Bundle pour Symfony 2</a>.</p> <p>Ce n'est pas la <a href="http://www.jubianchi.fr/2012/04/10/teasing-behat-viewer/" target="_blank">seule tentative</a>, et comme les autres ce n'est pas encore stable. Mais j'espère qu'on arrivera ensemble à faire quelque chose de bien pour proposer un outil digne de ce nom.</p> <p>J'attends vos retours et idées d'amélioration avec impatience ! :-p</p> Slides AFUP Orléans - Exploiter PHP 5 2012-07-09T00:00:00+00:00 http://www.guanzhui39.top/php/ressources-tutos-php/slides-afup-orleans-exploiter-php-5 <p>Comme promis à ceux qui étaient présents, voici mes slides de notre rendez-vous du 5 juillet à Orléans, durant lequel on a parlé "outils" et "SPL". </p> <p>Bien sûr, les slides ne sont pas exhaustifs, mais j'espère avoir réussi à convaincre que PHP était un outil riche, très riche même, et qui mérite largement qu'on aille explorer un peu plus loin la documentation pour éviter de réinventer la roue à chaque développement :-)</p> <p>Par rapport aux questions qu'il y a pu avoir, voici un lien pour obtenir des informations sur la <a href="https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md" target="_blank">PSR-0</a>.</p> <p>Merci à tous ceux qui sont venus, j'ai pris plaisir à faire cette petite présentation ;-)</p> <div style="width:425px" id="__ss_13568786"><strong style="display:block;margin:12px 0 4px"><a href="http://www.slideshare.net/halleck45/exploiter-php-5" title="Exploiter php 5">Exploiter php 5</a></strong><object id="__sse13568786" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=afupexploiterphp5-120707033002-phpapp01&stripped_title=exploiter-php-5&userName=halleck45" /><param name="allowFullScreen" value="true"/><param name="allowScriptAccess" value="always"/><param name="wmode" value="transparent"/><embed name="__sse13568786" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=afupexploiterphp5-120707033002-phpapp01&stripped_title=exploiter-php-5&userName=halleck45" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" wmode="transparent" width="425" height="355"></embed></object><div style="padding:5px 0 12px">View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/halleck45">halleck45</a>.</div></div> BDD : produit mal pensé, développement raté 2012-07-05T00:00:00+00:00 http://www.guanzhui39.top/php/bdd-produit-mal-pense-developpement-rate <p>Ce billet, non technique, peut surprendre, mais je pense qu'en tant que développeur il faut comprendre un minimum ce que doit être une spécification, surtout si on veut faire du Développement Piloté par le Comportement. Cela fait quelques temps que j'avais en tête de rédiger ce petit billet, alors allons-y :-)</p> <p>Rédiger des tests d'acceptation / tests de recette n'est certes pas mon métier, mais à force de l'expliquer, de le décrire, bref, à force de tenter de former des gens, je crois que je commencer à comprendre ce qui est si difficile dans ce travail de clarification.</p> <p>En un mot : la majorité des gens est déformée par son expérience professionnelle passée ; trop habitués aux SFG, aux SFD et autres specs bien verbeuses. Fournir une spécification claire et précise, alors que cela devrait être naturelle et facile, semble au contraire contre intuitif.</p> <p>Au risque de dire des choses évidentes, lorsqu'on fournit des spécifications à un développeur, ce n'est pas pour lui dire comment il doit faire son travail, mais pour lui décrire ce qu'il souhaite possible pour l'utilisateur. Une application, sans utilisateur pour la manipuler, est une coquille vide.</p> <p>Un produit, au contraire, place les utilisateurs (<strong>qui ont donc un rôle</strong>) au centre. Le produit est donc constitué par ce que l'utilisateur peur faire avec (son <strong>comportement</strong>).</p> <p>J'insiste, l'application n'a en aucun cas un comportement ; non, c'est la manière dont l'utilisateur interagit avec l'application qui constitue un comportement. On ne dit pas "cette application peut" mais "cette application permet"...</p> <p>Bref, tout ça pour dire quoi ? Et bien que si la personne qui rédige les spécifications n'a pas cette vision du produit, l'utilisation de tests automatisés (Behat) risque d'être contre productive.</p> <h2>Un peu de concret</h2> <p>Je vais prendre un exemple que j'ai pu voir récemment. Il s'agit de spécifier un import de données dans une application :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kd">Fonctionnalité</span><span class="p">:</span> pouvoir importer des événement Afin d'ajouter des événements dans l'application depuis un fichier d'import En tant que Batch Je dois pouvoir importer un événement <span class="err">Scénario </span><span class="p">:</span> <span class="nf">Etant donné</span> que je reçois un fichier d'import valide <span class="nf">Et</span> que je reçois un événement valide <span class="nf">En</span> tant que Batch <span class="err">Je</span> <span class="err">dois</span> <span class="err">pouvoir</span> <span class="err">importer</span> <span class="err">un</span> <span class="err">événement</span></code></pre></figure> <p>Au premier coup d'oeil, le développeur voit que quelque chose cloche dans cette fonctionnalité. En tant que Batch ?! Hum, bizarre, mais oui c'est vrai, c'est bien un batch qui va s'en charger. Je reçois cette spéc, elle me paraît logique, je vais donc l'implémenter.</p> <p>Oui, mais NON. Si on implémente cette fonctionnalité on fonce dans le mur ! Que se passera t-il ? Tout d'abord on est tributaire du moyen technique utilisé. On pourrait très bien envoyer l'info <a href="http://www.rfc1149.net/rfc1149.html" target="_blank">par pigeon voyageur</a> pour une raison x ou y, et dans ce cas la fonctionnalité deviendrait invalide.</p> <p>Autre souci : quel bénéfice l'utilisateur tire t-il ? Aucun. Non non, aucun. On se moque que l'application puisse importer de la donnée, ça c'est le moyen. Non, la vrai fonctionnalité c'est de pouvoir intégrer des nouvelles données dans l'application. Et là j'ai un bénéfice pour l'utilisateur. J'ai donc aussitôt proposé ceci :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kd">Fonctionnalité</span><span class="p">:</span> pouvoir intégrer mes nombreux événements dans l'application Afin d'ajouter rapidement plusieurs événements En tant qu'utilisateur qui a le droit d'ajouter par lots des événements Je dois pouvoir intégrer de nouveaux événements rapidement <span class="err">Scénario </span><span class="p">:</span> <span class="nf">Etant donné</span> que je fournis une liste d'événements <span class="nf">Et</span> que ces événements sont valides <span class="nf">Quand</span> je demande à intégrer cette liste d'événements <span class="nf">Alors</span> je dois pouvoir constater que ces événements ont bien été ajoutés</code></pre></figure> <p>Pas grand chose n'est changé, mais tout change. Certes, ce n'est toujours pas parfait, mais désormais, quelque soit le moyen technique utilisé (transfert par pigeons voyageurs, clef USB, FTP...), la spécification de la fonctionnalité restera valable.</p> <p>Plus encore, le développeur comprend tout de suite le souhait du client. Après c'est son rôle à lui, développeur (ou un ergonome, un architecte, mais surtout pas au fonctionnel) de déterminer comment satisfaire la demande du client. Et là, utiliser Behat a un sens.</p> <p>Un autre exemple : le taxi. On peut dire que ça consiste à demander de l'argent à un client afin de le transporter dans une voiture. Mais on peut aussi dire qu'il s'agit de transporter une ou plusieurs personnes d'un point A à un point B. Et là ça marche, car en tant que client, mon souhait c'est bel et bien de me rendre quelque part. Et là surtout, la spécification reste valable qu'on parle de taxi moto, de calèche...</p> <h2>Impact sur les dates de livraison du projet</h2> <p>Un autre avantage fondamental est le contrôle que cela offre au propriétaire sur la date de livraison. En découpant le produit en fonctionnalités fondamentales, claires et simples, il devient facile de mettre de côté certaines fonctionnalités pour gagner un peu de temps, quitte à les ajouter plus tard.</p> <p>Dans l'exemple du taxi, si je monte ma compagnie, je vais pouvoir dire ceci : "On du retard. Nous allons donc laisser de côté la fonctionnalité de paiement. Certes je vais perdre de l'argent un temps, mais au moins je serai visible. Nous ajouterons la fonctionnalité de paiement, qui n'est pas fondamentale pour l'instant, dans 15 jours."</p> <p>A mon avis, les développeurs doivent en être conscients, au risque sinon d'aboutir à un échec du projet. <strong>Pour faire de Développement Piloté par le Comportement, il FAUT un comportement</strong>, et donc un produit clair dont le coeur est l'utilisateur.</p> <p>Après, c'est mon ressenti, je serai curieux de connaître le vôtre sur cette question :-)</p> Les principales causes d'échec du BDD 2012-05-24T00:00:00+00:00 http://www.guanzhui39.top/php/les-principales-causes-dechec-du-bdd <p>Bonjour à tous ! Alors bien évidemment, il serait absurde de vouloir lister toute les erreurs possibles, et totalement illusoire de croire que j'en n'en fait plus ; mais je crois pouvoir donner quelques exemples de ce qu'il faut éviter à tout prix lorsque l'on fait du développement piloté par le comportement avec Behat.</p> <p>Petit rappel : Behat, c'est quoi ? En un mot, c'est un outil qui va vous permettre de pratiquer du BDD (Behavior Driven Development) en PHP. En d'autres mots, il va vous permettre de tester automatiquement si le développement d'un produit correspond aux spécifications qu'en a donné le client. Vous trouverez une <a title="Behat ?jour 1 : comment tester son produit SCRUM ?" href="/php/behat-jour-1-comment-tester-son-produit-scrum">description beaucoup plus complète ici</a>.</p> <p>Bref, c'est génial, c'est simple à utiliser... mais c'est extrêmement difficile à utiliser correctement. Et c'est catastrophique si c'est mal mal utilisé ! Pourquoi ?</p> <p>Behat a deux côtés : un côté fonctionnel (rédacteur), rédigé avec la syntaxe de Gherkin, un côté "développeur", en PHP.</p> <p>De ce que je vois, le plus souvent <span style="text-decoration: underline;">le fonctionnel</span> (au choix) :</p> <ul> <li><strong>ne dispose pas du temps nécessaire</strong> pour se consacrer à la rédaction des tests d'acceptation (fonctionnalité)</li> <li>n'a <strong>pas une vision assez clair de son produit</strong> pour pouvoir le découper fonctionnellement</li> <li><strong>confond interface</strong> (ergonomie, disposition...) <strong>et comportement</strong> de l'application</li> <li><strong>confond contrôle sur les données et test sur le comportement</strong> (trèèès souvent!)</li> <li>ou, plus rarement, confond socle technique et fonctionnalité</li> </ul> <p>De ce que je constate, <span style="text-decoration: underline;">le développeur</span> (au choix) :</p> <ul> <li>est obligé de se substituer au client dans la rédaction des tests, ce qui n'est <strong>pas son métier</strong> (pas simple donc)</li> <li><strong>n'arrive pas à s'abstraire du technique</strong></li> <li><strong>se focalise sur le cheminement</strong> (comment arriver là?) et l'emplacement dans l'application</li> <li>a tendance à <strong>écrire du code PHP</strong> plutôt que de réutiliser des étapes existantes</li> </ul> <p>Bon, le constant est sévère, mais je généralise bien sûr. Cependant il est très difficile d'échapper à ça.</p> <p>Je passe aux exemples, tirés d'un code vu ce matin même.</p> <h2>Décrire une fonctionnalité : pas si simple</h2> <p>Prenons ce bout de fonctionnalité que j'ai reçu comme spécification :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kd">Feature</span><span class="p">:</span> access to the task's page from a list of tasks In order to see a task As a logged in user I need to open a task <span class="kn">Background</span><span class="p">:</span> <span class="nf">Given</span> I am logged in user <span class="kn">Scenario Outline</span><span class="p">:</span> <span class="nf">When</span> I press <span class="s">"Find a task"</span> <span class="nf">And</span> I fill in <span class="s">"Task reference"</span> with <span class="s">"&lt;reference&gt;"</span> <span class="nf">And</span> I press <span class="s">"Search"</span> <span class="nf">Then</span> I should be on <span class="s">"index/task/id/&lt;id&gt;"</span> <span class="nn">Examples</span><span class="p">:</span> <span class="p">|</span> <span class="nv">reference  </span> <span class="p">|</span> <span class="nv">id </span> <span class="p">|</span> <span class="p">|</span> <span class="n">task1 </span> <span class="p">|</span> <span class="n">1  </span> <span class="p">|</span> <span class="p">|</span> <span class="n">task2</span> <span class="n"> </span> <span class="p">|</span> <span class="n">2  </span> <span class="p">|</span></code></pre></figure> <p>Bon, ça marche. Mais quand on y regarde plus près :</p> <ul> <li>on se consacre plus aux étapes permettant d'accéder aux conditions du scénario qu'au scénario lui-même</li> <li>si la structure de la page change, le test est obsolète</li> <li>on ne teste pas le comportement, mais la donnée. Si la donnée change, le test est obsolète</li> <li>si l'url change (rewriting, etc), le test est obsolète</li> </ul> <p>Bref, <span style="text-decoration: underline;">le test va rapidement devenir obsolète.</span></p> <p>Il est difficile dans ce cas de voir comment s'abstraire des données (liaison id et task). Après réflexion, on peut suggérer d'évoluer vers ceci :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kd">Feature</span><span class="p">:</span> access to the task's page from a list of tasks In order to see a task As a logged in user I need to open an task's page from a list <span class="kn">Background</span><span class="p">:</span> <span class="nf">Given</span> I am logged in user <span class="kn">Scenario Outline</span><span class="p">:</span> <span class="nf">Given</span> I see a list of tasks, including the task <span class="s">"&lt;reference&gt;"</span> <span class="nf">When</span> I follow <span class="s">"&lt;reference&gt;"</span> <span class="nf">Then</span> I should be on the Task's page <span class="nf">And</span> I should see the task <span class="s">"&lt;reference&gt;"</span> <span class="nn">Examples</span><span class="p">:</span> <span class="p">|</span> <span class="nv">reference </span> <span class="p">|</span> <span class="p">|</span> <span class="n">task1     </span> <span class="p">|</span> <span class="p">|</span> <span class="n">task2     </span> <span class="p">|</span></code></pre></figure> <p>On a donc opéré des modifications afin de rendre le test indépendant du jeu de données ou de l'interface de l'application.</p> <p>Ce que je dis souvent, c'est que, en théorie, un test de comportement est valide quelque soit le support :<strong> que l'on passe d'un site web à une application mobile, le changement de support ne change pas la fonctionnalité ou les scénarios !</strong> Ca ne change que leur implémentation.</p> <p>On pourra certainement trouver encore à redire, mais la fonctionnalité, telle qu'elle est décrite, est désormais valable quelque soit son implémentation technique. Seul son comportement est ici spécifié. Elle a donc une forte probabilité d'être viable et pertinente dans le temps.</p> <h2>Implémenter une définition de fonctionnalité : pas plus facile</h2> <p>On a vu un exemple de fonctionnalité à risque. Passons de l'autre côté et mettons-nous du point de vue du développeur. De la même façon, voici une implémentation possible :</p> <p>Note : en l’occurrence, la liste des tâches n'est possible dans l'application qu'après  avoir effectué une recherche.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @Given /^I see a list of tasks, including the task "([^"]*)"$/ */ public function iSeeAListOfTasksIncludingTheTask($reference) { $session = $this-&gt;getMainContext()-&gt;getSubcontext('mink')-&gt;getSession(); $page = $session-&gt;getPage(); $session-&gt;visit('/task'); // Find the task in the search engine $page-&gt;find('css', '.ipt-task-search')-&gt;setValue($reference); $page-&gt;find('css', '.button-search')-&gt;press(); if ($session-&gt;getCurrentUrl() != "/task/{$reference}") { throw new AssertException("We cannot find the task {$reference} with the search engine"); } }</code></pre></figure> <p>On constate différente choses :</p> <ul> <li>Que de code ! C'est long à écrire</li> <li>Que de code ! Et pas réutilisable en plus !</li> <li>Que de code ! Et qu'est-ce qui se passe si l'interface HTML change ?</li> <li>On ne comprend pas ce qui se passe au premier coup d'oeil</li> </ul> <p>L'implémentation fonctionne, mais est peu viable dans le temps, et surtout on a perdu du temps pour l'écrire (c'est fastidieux de devoir manipuler le navigateur à la main).</p> <p>Là où Behat est fort, c'est qu'il nous permet, en PHP, de faire comme si on écrivait des étapes de scénario "à la main". Ca fait gagner un temps monstre et permet de réutiliser les définitions existantes :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @Given /^I see a list of tasks, including the task "([^"]*)"$/ */ public function iSeeAListOfTasksIncludingTheTask($reference) { return array( new Given('I am on "/"') , new When(sprintf('I fill "Task reference" with "%s"', $reference)) , new When('I press "Search"') ); }</code></pre></figure> <p>Pour s'aider, le développeur peut s'appuyer (si tout se passe bien) sur la personne qui a rédigé le scénario, qui l'aidera à découper sa définition en différentes étapes.</p> <p>Attention, contrairement au scénario Gherkin, ce code peut être amené parfois à évoluer. Par  exemple, si on ajoute un scénario pour la recherche de tâche, avec cette étape :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nf">When</span> I search the task <span class="s">""</span></code></pre></figure> <p>On pourra dès lors écrire :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @Given /^I search the task "([^"]*)"$/ */ public function iSearchTheTask($reference) { return array( new Given('I am on "/"') , new When(sprintf('I fill "Task reference" with "%s"', $reference)) , new When('I press "Search"') ); } /** * @Given /^I see a list of tasks, including the task "([^"]*)"$/ */ public function iSeeAListOfTasksIncludingTheTask($reference) { return array( new When(sprintf('I search the task "%s"', $reference)) ); }</code></pre></figure> <p>Le développeur peut donc, sans architecture ou code complexe, mais simplement en utilisant les objets Etapes fournis par Behat, organiser ses définitions de façon à les rendre réutilisables.</p> <h2>Le mot de la fin</h2> <p>Bref, ça semble évident, mais quand on fait du BDD... et bien il faut se focaliser le le Comportement. Ce n'est pas facile, et contre intuitif pour beaucoup de monde. Toutefois, le rédacteur du test peut s'aider de la structure de la fonctionnalité (comment la décrire, qui y participe, quels en sont les bénéfices, puis quels cas d'utilisation je peux en donner).</p> <p>En d'autres mots, le "rédacteur" ne doit pas s'appuyer sur ce qu'il connaît de son application (emplacement, design...), mais sur la vision du produit (comment ça se passe ? Avec quel gain pour l'utilisateur ?). En clair :</p> <blockquote>Le rédacteur ne doit pas s'appuyer sur ce qu'il connaît de son application mais sur la vision du produit</blockquote> <p>Le développeur, lui, doit prendre l'habitude de ne pas se lancer tête baissée dans le code, au risque de consacrer trop de temps et d'énergie à l'utilisation de Behat. Certes il doit écrire ce code, mais il ne code plus pour interagir avec un autre code (comme lorsqu'il le fait pour un test unitaire par exemple), mais pour interagir avec un produit. En clair :</p> <blockquote>Le développeur n'interagit plus avec du code mais avec un produit</blockquote> <p>Ceci dit, félicitations d'avoir lu ce billet jusqu'au bout :-) .</p> <p>Je ne prétend pas avoir le recul suffisant, mais je crois que ces constats s'appliquent généralement. C'est le cas pour vous aussi ? Vous avez vu d'autres écueils courants ? Ou au contraire, pour vous tout a roulé tout de suite ?</p> Mémento Industrialisation PHP : Outils et bonnes pratiques 2012-05-22T00:00:00+00:00 http://www.guanzhui39.top/php/memento-industrialisation-php-outils-et-bonnes-pratiques <p style="text-align: center;"><a href="http://www.amazon.fr/M%C3%A9mento-outils-PHP-Eyrolles/dp/2212134800"><img class="aligncenter" title="Couverture du mémento PHP Industrialisation" src="http://ecx.images-amazon.com/images/I/411vzWHHC5L._SL500_AA300_.jpg" alt="Couverture du mémento PHP Industrialisation" width="300" height="300" /></a></p> <p> <p>Certains d'entre-vous sont déjà au courant, voire l'ont déjà entre les mains, mais je planche depuis déjà quelques temps sur un mémento dédié aux outils PHP et aux pratiques liées à l'industrialisation.</p> </p><p> <p>Mais comme l'a bien dit <a href="http://raphaelhertzog.fr/2012/04/17/le-memento-git-est-disponible/" target="_blank">Raphaël Hertzog il y a peu quand il parlait de son mémento Git</a>, écrire un mémento de quelques pages ce n'est pas si facile !</p> </p><p> <p>Au départ il y a une volonté : faire une petite synthèse des principaux outils utiles et pratiques quand on souhaite "industrialiser" certains aspects d'un développement afin d'en faciliter la qualité : tests unitaires, de comportement, outils d'audit de code, de versionning...</p> </p><p> <p>Ensuite... Ensuite, et bien je me suis rendu compte qu'il était extrêmement difficile de faire un travail de synthèse suffisant pour faire tenir toutes ces informations en quelques pages (13 pages exactement). Et même très difficile ! Il a fallu condenser, relire, supprimer, re-condenser l'information.</p> </p><p> <p>Au final, plus qu'un simple mémento "cheat-sheet", j'espère avoir réussi à présenter un tour d'horizon des outils et pratiques pour ceux et celles qui souhaiteraient aller plus loin dans leurs développements PHP, qu'il s'agisse de faciliter le travail en équipe, la maintenabilité ou la fiabilité.</p> </p><p> <p>L'idée n'était surtout pas de faire la liste des outils disponibles et de les comparer. Au contraire; j'ai essayé de créer une continuité entre les outils présentés, afin d'en avoir une vision d'ensemble, et surtout afin de tirer parti de chacune de leur spécificité. Et j'espère avoir réussi :-) !</p> </p><p> <p>Chaque outil est présenté avec sa description, sa procédure d'installation, ses usages, et généralement des petites astuces bonnes à avoir sous les yeux.</p> </p><p> <p>Ceux qui me connaissent savent que la qualité logicielle me tient beaucoup à cœur. J'espère que ce mémento saura vous aider à découvrir, ou redécouvrir, quelques uns des outils utiles à mettre en place et conserver cette qualité dans vos projets PHP.</p> </p><p> <p>Je vais faire mon commercial, mais <a href="http://www.amazon.fr/M%C3%A9mento-outils-PHP-Eyrolles/dp/2212134800" target="_blank">le mémento est en pré-vente sur Amazon au prix de 9,90 ?lt;/a>. Comme tous les mémentos Eyrolles, il est plastifié et théoriquement "indéchirable", vous pouvez l'amener partout. Attention, le mémento a la même couleur que le très bon <a href="http://www.eyrolles.com/Informatique/Livre/memento-php-et-sql-9782212117851" target="_blank">mémento PHP et SQL</a>. A ne pas confondre donc...</p> </p><p> <p>Pour finir, si vous avez l'occasion de l'avoir entre vos mains n'hésitez surtout pas à me faire un retour : ce qui va, ce qui manque, ce qui est en trop... J'attends avec impatience vos avis !</p> </p><p> <p>Au passage, encore merci à <a href="http://jolicode.com/equipe" target="_blank">Xavier Lacot</a> et à <a href="https://twitter.com/#!/fauveauarmel" target="_blank">Armel Fauveau</a>, qui m'ont laaaaargement aidé et relu ;-)</p> </p> Flux personnalisés et filtres en PHP (Streams) 2012-04-13T00:00:00+00:00 http://www.guanzhui39.top/php/flux-personnalises-et-filtres-en-php-streams <p>Lorsque vous faites un fopen(), ou toute autre fonction équivalente, PHP vous retourne une ressource, sous forme d'un flux. Il <a href="http://www.php.net/manual/en/wrappers.php" target="_blank">existe différent types de flux en PHP</a> :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="nt">&lt;p&gt;</span>fopen('file://...') // fichier<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>fopen('php://temp') // fichier temporaire<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>fopen('php://memory') // en mémoire<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>fopen('php://stdout') // sortie de la console<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>etc.<span class="nt">&lt;/p&gt;</span></code></pre></figure> <p>Bref, il y en a pas mal...</p> <p>Mais on oublie souvent qu'on peut aussi ajouter ses propres types de flux. Par exemple je vais créer un type de flux "twitter" pour lire mes tweets :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$fp = fopen("twitter://@halleck45", "r"); if ($fp) { while (!feof($fp)) { var_dump(fgets($fp, 140)); } }</code></pre></figure> <h2>Création d'un nouveau type de flux</h2> <p>C'est relativement facile : il suffit d'<a href="http://fr.php.net/manual/fr/function.stream-wrapper-register.php" target="_blank">ajouter un nouveau gestionnaire de flux</a>, c'est à dire une classe qui respecte le prototype <strong><a href="http://fr.php.net/manual/fr/class.streamwrapper.php" target="_blank">StreamWrapper</a></strong>.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">stream_wrapper_register("twitter", "TwitterStream");</code></pre></figure> <p>Plutôt que d'implémenter toutes les méthodes de ce prototype, concentrons nous sur le principal, et créons 4 méthodes :</p> <ul> <li><strong>stream_open(</strong>), qui va ouvrir notre flux</li> <li><strong>stream_close()</strong>, pour le fermer</li> <li><strong>stream_read($size)</strong> qui va être appelée à chaque lecture dans le flux</li> <li><strong>stream_eof()</strong>, pour indiquer qu'on arrive à la fin</li> </ul> <p>Pour simplifier l'exemple, le flux Twitter est en lecture seule. Pour la même raison, on va rapatrier tous les tweets d'un coup et les stocker dans notre objet dans un tableau.</p> <p>Pour démarrer, créons une petite fonction qui va aller chercher les tweets d'un utilisateur donné :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">ini_set('allow_url_fopen', 1); define('TWITTER_PWD', 'votre-mot-de-passe'); define('TWITTER_LOGIN', 'votre-login'); function example_stream_twitter_fetch($username) { $ch = curl_init(); $url = sprintf('http://api.twitter.com/1/statuses/user_timeline.json?screen_name=%s<span class="err">&amp;</span>include_entities=true<span class="err">&amp;</span>include_rts=true<span class="err">&amp;</span>count=20', $username); curl_setopt($ch, CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_USERPWD, TWITTER_LOGIN . ':' . TWITTER_PWD); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($ch); curl_close($ch); return $result ? json_decode($result) : null; }</code></pre></figure> <p>Maintenant le gestionnaire de flux :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class TwitterStream { protected $_json; protected $_offset = 0; public function stream_open($path, $mode, $options, <span class="err">&amp;</span>$opened_path) { // // Read only ! if ($mode != 'r') { trigger_error("Unsupported mode", E_USER_WARNING); return false; } // // Calling example_stream_twitter_fetch() in order to fetch data from twitter $result = false; if (preg_match('!@(\w*)!', $path, $match)) { $result = example_stream_twitter_fetch($match[1]); } if (!$result) { if (($options <span class="err">&amp;</span> STREAM_REPORT_ERRORS)) { trigger_error("Username not found", E_USER_WARNING); } return false; } $this-&gt;_json = $result; return (bool) $result; } public function stream_close() { $this-&gt;_json = null; return true; } public function stream_read($count) { return $this-&gt;_json[$this-&gt;_offset++]-&gt;text; } public function stream_eof() { return $this-&gt;_offset &gt;= sizeof($this-&gt;_json); } }</code></pre></figure> <p>Rien de bien compliqué. Il faut juste bien penser à lancer un warning en cas de problème...</p> <p>Et ça suffit :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">stream_wrapper_register("twitter", "TwitterStream"); $fp = fopen("twitter://@halleck45", "r", STREAM_REPORT_ERRORS); if ($fp) { while (!feof($fp)) { var_dump(fgets($fp, 140)); } }</code></pre></figure> <p>Pratique non ?</p> <p>Bon, bien sûr l'exemple est trivial, ne serait-ce parce que les tweets peuvent dépasser les 140 caractères à cause des liens ; mais je pense que vous aurez compris l'intérêt de la chose :-)</p> <h2>Appliquer des fitres sur des flux</h2> <p>Autre "truc pratique" assez peu utilisé mais vraiment utile : on peut appliquer des filtre sur des flux, même sur les flux "natifs".</p> <p>Par exemple, je veux convertir le texte que j'écris dans un fichier en l33t, en utilisant cette fonction :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function l33t($string) { return str_replace(array('l', 'e', 't'), array('1', '3', '7'), $string); }</code></pre></figure> <p>Nous allons enregistrer le filtre "l33t" et l'associer à la classe "l33t_filter' :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">stream_filter_register("l33t", "l33t_filter");</code></pre></figure> <p>Cette classe l33t_filter doit hériter de la classe native php_user_filter :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class l33t_filter extends php_user_filter { function filter($in, $out, <span class="err">&amp;</span>$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { // // leet -&gt; l33t $bucket-&gt;data = l33t($bucket-&gt;data); $consumed += $bucket-&gt;datalen; stream_bucket_append($out, $bucket); } // on retournerait PSFS_ERR_FATAL en cas d'erreur bloquante return PSFS_PASS_ON; } }</code></pre></figure> <p>On peut par exemple imaginer appliquer des filtres de cryptage, de contrôle...</p> <p>Alors, convaincu ? :-) Avez-vous déjà utilisé votre propres flux ou filtre de flux en PHP ?</p> Behat : liens et ressources utiles 2012-04-02T00:00:00+00:00 http://www.guanzhui39.top/php/behat-liens-et-ressources-utiles <p>Ce billet fait suite à :</p> <ul> <li><a title="Behat ?jour 1 : comment tester son produit SCRUM ?" href="/php/behat-jour-1-comment-tester-son-produit-scrum">Behat ?jour 1 : comment tester son produit SCRUM ?</a></li> <li><a title="Behat ?jour 2 : Installation et premiers tests" href="/php/behat-jour-2-installation-et-premiers-tests">Behat ?jour 2 : Installation et premiers tests</a></li> <li><a title="Behat ?jour 3 : Tester une application web avec Mink" href="/php/behat-jour-3-tester-une-application-web-avec-mink">Behat ?jour 3 : Tester une application web avec Mink</a></li> <li><a title="Behat ?jour 4 : API Mink, Sous-contextes et Hooks" href="/php/behat-jour-4-api-mink-sous-contextes-et-hooks">Behat ?jour 4 : API Mink, Sous-contextes et Hooks</a></li> </ul> <p>Pour ce dernier billet du tutoriel Behat, j'avais prévu de parler la mise en place de Behat au sein d'une plate-forme d'intégration continue (PIC). Je me suis rendu compte qu'il y avait déjà quelques bons tutos là dessus. Je vous propose donc plutôt une petite liste de liens que je vous invite à découvrir pour aller plus loin avec Behat.</p> <h2>Contextes prêts à l'usage</h2> <p>Le <a href="https://github.com/gabrielpillet/BehatCH" target="_blank">BehatCH</a>, développé par <a href="http://team-fusion.pmsipilot.com/" target="_blank">PMSIpilot</a> est ce que j'ai trouvé de plus complet pour apporter des bonnes bases à un projet. regardez surtout le <a href="https://github.com/gabrielpillet/BehatCH/blob/master/features/bootstrap/contexts/BrowserContext.php" target="_blank">BrowserContext</a> et le <a href="https://github.com/gabrielpillet/BehatCH/blob/master/features/bootstrap/contexts/TableContext.php" target="_blank">TableContext</a>, qui permettent d'écrire des choses comme :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="err">&lt;p&gt;</span><span class="nf">I</span> wait <span class="s">"5"</span> seconds until I see <span class="s">"element"</span><span class="nv">&lt;/p&gt;</span> <span class="err">&lt;p&gt;</span><span class="nf">I</span> should see <span class="s">"5"</span> elements&lt;/p&gt; <span class="err">&lt;p&gt;The</span> <span class="err">1st</span> <span class="err">column</span> <span class="err">of</span> <span class="err">the</span> <span class="err">1st</span> <span class="err">row</span> <span class="err">in</span> <span class="err">the</span> <span class="err">"table"</span> <span class="err">table</span> <span class="err">should</span> <span class="err">contain</span> <span class="err">"Lorem"&lt;/p&gt;</span></code></pre></figure> <p>Vous pouvez également utiliser les <a href="https://github.com/Behat/CommonContexts" target="_blank">Extra Contexts</a> mis à disposition par l'auteur de Behat, qui sont eux moins axés outils mais plus à mon sens comme des composants pour créer d'autres outils.</p> <h2>Blogs intéressants sur Behat</h2> <p>Pour les blogs francophones, je vous recommande les blog de <a href="http://team-fusion.pmsipilot.com/" target="_blank">PMSIpilot</a> et de <a href="http://knplabs.fr/blog" target="_blank">knpLabs</a> (bon, ok c'est plus souvent en anglais qu'en français :-) ...).</p> <p>Pour les anglohpones, vous pouvez jeter un oeil sur le blog de <a href="http://lestbddphp.wordpress.com" target="_blank">Shashikant Jagtap</a>, de l'<a href="http://everzet.com/" target="_blank">auteur même de Behat</a>, ou aussi sur <a href="http://www.craftitonline.com/category/bdd/" target="_blank">craftItOnline</a></p> <h2>Plate-forme d'intégration continue et Behat</h2> <p>Avant toute chose sachez que Behat permet très facilement de générer des rapports en XML, au format JUnit :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat -f junit --out ./report</code></pre></figure> <p>Voici un tutoriel pour installer <a href="http://lestbddphp.wordpress.com/2012/01/03/behat-with-sauce-labs-and-jenkins/" target="_blank">Behat sur Jenkins</a>, et en voici <a href="http://team-fusion.pmsipilot.com/679/creer-un-job-behat-dans-hudson/" target="_blank">un autre pour Hudson</a></p> <p>Bonne lecture ! :-) N'hésitez pas si vous avez des ressources à suggérer à les mettre en commentaire</p> Behat - jour 4 : API Mink, Sous-contextes et Hooks 2012-03-29T00:00:00+00:00 http://www.guanzhui39.top/non-classe/behat-jour-4-api-mink-sous-contextes-et-hooks <p>Voici l'avant dernier billet de la série sur la prise en main de Behat. Pour rappel, on a vu :</p> <ul> <li><a title="Behat ?jour 1 : comment tester son produit SCRUM ?" href="/php/behat-jour-1-comment-tester-son-produit-scrum">Behat ?jour 1 : comment tester son produit SCRUM ?</a></li> <li><a title="Behat ?jour 2 : Installation et premiers tests" href="/php/behat-jour-2-installation-et-premiers-tests">Behat ?jour 2 : Installation et premiers tests</a></li> <li><a title="Behat ?jour 3 : Tester une application web avec Mink" href="/php/behat-jour-3-tester-une-application-web-avec-mink">Behat ?jour 3 : Tester une application web avec Mink</a></li> </ul> <p>Allons un peu plus loin :-)</p> <h2>Sortir des sentiers battus : l'API de Mink</h2> <p>Assez rapidement on se retrouve à devoir gérer des cas particuliers, par exemple :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nf">Quand</span> je suis à découvert <span class="err">&lt;p&gt;</span><span class="nf">Alors</span> le bouton <span class="s">"retirer de l'argent"</span> doit être désactivé</code></pre></figure></p> <p>On sort des cas classiques de Mink. Comment faire ?</p> <p>La solution consiste à développer nous même ce comportement :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @Then /^le bouton "([^"]*)" doit être désactivé$/ */ public function leBoutonDoitEtreDesactive($button) { throw new PendingException(); }</code></pre></figure> <p>On commence par récupérer notre page à partir de l'objet de session:</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$page = $this-&gt;getSession()-&gt;getPage();</code></pre></figure> <p>Ensuite on va récupérer l'élément html concerné. <a href="http://mink.behat.org/#traverse-the-page-selectors" target="_blank">Il existe différente manière de faire cela</a>. Le plus simple dans notre cas consiste à passer par un des raccourcis de sélection de Mink : <strong>findButton</strong>(libellé | id | nom)...</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$element = $page-&gt;findButton($button);</code></pre></figure> <p>Si l'élément html n'est pas trouvé, on va lever une exception, sinon on va continuer en faisant une assertion simple : l'élément doit avoir l'attribut "disabled". Ce qui donne au final :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$page = $this-&gt;getSession()-&gt;getPage(); $element = $page-&gt;findButton($button); if (null === $element) { throw new Behat\Mink\Exception\ElementNotFoundException( $this-&gt;getSession(), 'element', 'css', $button ); } <span class="nt">&lt;p&gt;</span>assertEquals(true, $element-&gt;hasAttribute('disabled'));</code></pre></figure></p> <p>Au passage, remarquez qu'il s'agit d'une assertion classique de PHPUnit, mais en mode fonction. Pour cela on aura bien entendu ajouté au début de notre fichier :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">require_once 'PHPUnit/Autoload.php'; <span class="nt">&lt;p&gt;</span>require_once 'PHPUnit/Framework/Assert/Functions.php';</code></pre></figure></p> <p>Je vous laisse regarder la <a href="http://mink.behat.org/http://" target="_blank">documentation</a> ou la <a title="Cheat Sheet Behat" href="/php/ressources-tutos-php/cheat-sheet-behat" target="_blank">feuille d'astuce pour Mink</a> pour plus d'informations. Sachez juste qu'on peut faire pas mal de chose, comme exécuter du JavaScript <a href="/php/ressources-tutos-php/communiquer-a-travers-internet">par exemple</a> (avec $session->evaluateScript() ) ;-) ...</p> <h2>Organiser son code</h2> <p>Jusqu'ici on a systématiquement mis notre code dans le fichier FeatureContext.php. C'est pas l'idéal : on va très vite se retrouver avec un fichier énorme et imbuvable. Il nous suffit de découper notre contexte en sous-contextes. Tout se fait dans le constructeur du contexte principal :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class FeatureContext extends BehatContext { public function __construct(array $parameters) { $this-&gt;useContext('mink', new MinkContext($parameters)); $this-&gt;useContext('example1', new MyExample1Context($parameters)); }</code></pre></figure> <p>On a donc isolé le contexte de Mink pour en faire un sous-contexte, et on a plus ajouté le nôtre ('example1').</p> <p>L'utilisation des contextes est assez simple. Chaque sous-contexte a un nom (ici 'mink' et 'behat'), que l'on peut utiliser pour les récupérer :</p> <dl><dt><strong>getMainContext()</strong></dt><dd>Récupérer le contexte principal</dd><dt><strong>getSubContext('nom')</strong></dt><dd>Récupérer un sous contexte</dd><dt><strong>getSubcontexts()</strong></dt><dd>Récupérer la liste des tous les sous-contextes</dd></dl> <p>Par exemple :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$session = $this-&gt;getMainContext()-&gt;getSubContext('mink')-&gt;getSession();</code></pre></figure> <p>On va donc créer le fichier MyExample1Context.php :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class MyExample1Context extends BehatContext { /** * @Then /^le bouton "([^"]*)" doit être désactivé$/ */ public function leBoutonDoitEtreDesactive($button) { $session = $this-&gt;getMainContext()-&gt;getSubContext('mink')-&gt;getSession(); $page = $session-&gt;getPage(); $element = $page-&gt;findButton($button); if (null === $element) { throw new Behat\Mink\Exception\ElementNotFoundException( $this-&gt;getSession(), 'element', 'css', $button ); } assertEquals(true, $element-&gt;hasAttribute('disabled')); } }</code></pre></figure> <p>Et voilà, nous voici avec un code découpé et des fichiers plus spécialisés.</p> <h2>Les hooks de Behat</h2> <p>Comme pour les tests unitaires, il est possible d'exécuter du code à certaines phases du déroulement du tests. Il suffit d'utiliser des annotations :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @BeforeSuite */ <span class="nt">&lt;p&gt;</span>public static function prepare(SuiteEvent $event)<span class="nt">&lt;/p&gt;</span> { // (...) }</code></pre></figure> <p>les déclencheurs disponibles sont :</p> <ul> <li>BeforeSuite</li> <li>AfterSuite</li> <li>BeforeFeature</li> <li>AfterFeature</li> <li>BeforeScenario</li> <li>AfterScenario</li> <li>BeforeStep</li> <li>AfterStep</li> <li>AfterStep</li> </ul> <p>Un dessin valant mieux qu'un long discours, le plus simple est de<a href="http://docs.behat.org/guides/3.hooks.html#behat-event-system" target="_blank"> regarder ici</a>.</p> <h2>Conclusion</h2> <p>On voit qu'on a quand même peu de limites avec Mink et Behat. Bien plus, on peut même l'intégrer à une PIC (Jenkins, Hudson...). C'est d'ailleurs ce qu'on verra dans le prochain billet, qui sera le dernier de la série ^^</p> => Juste par curiosité : j'utilise exclusivement Sahi pour mes tests. Beaucoup de monde utilise Selenium ? C'est mieux ? Vous avez des avis ? Behat ?jour 3 : Tester une application web avec Mink 2012-03-19T00:00:00+00:00 http://www.guanzhui39.top/php/behat-jour-3-tester-une-application-web-avec-mink <p>On a vu précédemment <a href="/php/behat-jour-1-comment-tester-son-produit-scrum" title="Behat ?jour 1 : comment tester son produit SCRUM ?">ce qu'était Behat</a> et <a title="Behat ?jour 2 : Installation et premiers tests" href="/php/behat-jour-2-installation-et-premiers-tests">comment tester une application simple en php avec Behat</a>.</p> <p>Maintenant allons plus loin et voyons comment tester une application web (à la quelle on accède par un navigateur). Et oui c'est possible ! :-)</p> <h2>Behat et ... Mink</h2> <p>On l'a dit : Behat permet de tester un produit. Ceci dit, il est rare qu'un client vous demande une application en ligne de commande ; le plus souvent le produit en question va être constitué de pages web. Qu'à cela ne tienne, Behat est très fortement lié à un autre outil : <a href="http://mink.behat.org/" target="_blank">Mink</a>.</p> <p>Mink c'est quoi ? Et bien <strong>Mink va vous permettre</strong> :</p> <ul> <li>ou bien de <strong>simuler un navigateur</strong> pour interagir avec votre application (produit)</li> <li>ou bien de <strong>piloter un vrai navigateur</strong> (Firefox, Chrome...) pour interagir avec le produit</li> </ul> <p>Voyons comment le lancer en mode "simulation" dans un premier temps.</p> <h3>Application de test</h3> <p>Pour vous éviter de perdre du temps, je vous propose de télécharger une petite page web toute simple qui va correspondre à notre produit de test :</p> <ul> <li>une seule page pour consulter notre compte et ajouter/retirer des sous</li> <li>un formulaire de connexion (pas de mot de passe)</li> <li>les informations sont stockées en session</li> </ul> <p>Ce qui donne ça : <a href="http://www.guanzhui39.top/images/2012-03-screen-app-exemple-mink.jpg"><img class="alignnone size-medium wp-image-459" title="screen-app-exemple-mink" src="http://www.guanzhui39.top/images/2012-03-screen-app-exemple-mink.jpg" alt="" width="208" height="300" /></a></p> <p>Pour télécharger l'application exemple, ça se passe ici : <a href="http://www.guanzhui39.top/images/2012-03-behat-jour3-appli-web1.zip">behat-jour3-appli-web.zip</a> (15 Ko).</p> <h3>Notre produit web</h3> <p>N'oublions pas que le client (product owner) reste le maître de son produit. Il nous fournit donc un fichier de fonctionnalités pour décrire son produit.</p> <em>Remarque : la fonctionnalité est décrite en anglais ; bien que très actif, <strong>utiliser behat et mink en français reste aujourd'hui très aventureux à mon goût</strong>, ne serait-ce qu'à cause des nombreux apostrophes présents dans notre langue <a href="https://github.com/Behat/Behat/issues/106" target="_blank">qui posent des difficultés</a>.</em> <p>Fichier bank.feature :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="err">&lt;p&gt;Feature</span><span class="p">:</span> <span class="err">Manage</span> <span class="nf">a</span> bank account&lt;/p&gt; <span class="nf">In</span> order to manage my account <span class="err">As</span> <span class="nf">a</span> logged in user <span class="nf">I</span> need to be able to add or take mon ey on my account <span class="kn">Background</span><span class="p">:</span> <span class="nf">And</span> I am logged in as <span class="s">"jeanfrancois"</span> <span class="nf">And</span> I have <span class="s">"50"</span> euro <span class="nf">And</span> I am on <span class="s">"/"</span> <span class="kn">Scenario</span><span class="p">:</span> Check my bank account <span class="nf">Then</span> I should see <span class="s">"You have 50 euro on your account"</span> <span class="kn">Scenario Outline</span><span class="p">:</span> Add money <span class="nf">Given</span> I have <span class="s">"&lt;initialAmount&gt;"</span> euro <span class="nf">When</span> I select <span class="s">"&lt;operation&gt;"</span> from <span class="s">"Operation"</span> <span class="nf">And</span> I fill in <span class="s">"Amount"</span> with <span class="s">"&lt;amount&gt;"</span> <span class="nf">And</span> I press <span class="s">"Go"</span> <span class="nf">Then</span> I should see <span class="s">"You have &lt;finalAmount&gt; euro on your account"</span> <span class="nn">Examples</span><span class="p">:</span> <span class="p">|</span> <span class="nv">operation</span> <span class="p">|</span> <span class="nv">initialAmount</span> <span class="p">|</span> <span class="nv">amount</span> <span class="p">|</span> <span class="nv">finalAmount</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Add</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span> <span class="n">60</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Add</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="n">70</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Add</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">5</span> <span class="p">|</span> <span class="n">55</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Add</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">0</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Take</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span> <span class="n">40</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Take</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="n">30</span> <span class="p">|</span> <span class="p">|</span> <span class="n">Take</span> <span class="n">money</span> <span class="p">|</span> <span class="n">50</span> <span class="p">|</span> <span class="n">30</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="kn">Scenario</span><span class="p">:</span> Overdrafts are not allowed <span class="nf">Given</span> I have <span class="s">"50"</span> euro <span class="nf">When</span> I select <span class="s">"Take money"</span> from <span class="s">"Operation"</span> <span class="nf">And</span> I fill in <span class="s">"Amount"</span> with <span class="s">"60"</span> <span class="nf">And</span> I press <span class="s">"Go"</span> <span class="nf">Then</span> I should see <span class="s">"You have 50 euro on your account"</span> <span class="nf">And</span> I should see <span class="s">"Overdrafts are not allowed"</span></code></pre></figure> <p>Nous voilà prêts :-) Notez que les expressions utilisées (I fill in "xxx" with "xx", etc) ne sont pas anodines, bien au contraire. On va le voir par la suite.</p> <h2>Installer Mink</h2> <p>Rien de plus simple :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo pear install behat/mink-beta</code></pre></figure> <p>Maintenant on va "dire" à Behat que l'on souhaite utiliser Mink pour nos tests. Ouvrez le fichier bootstrap/FeatureContext.php, et remplacez</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class FeatureContext extends BehatContext {</code></pre></figure> <p>par</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">require_once 'mink/autoload.php'; class FeatureContext extends Behat\Mink\Behat\Context\MinkContext </code></pre></figure> <p>C'est là que la magie s'opère : Mink dispose déjà de nombreuses expressions disponibles pour tester notre produit web ! On va pouvoir le confirmer en faisant un :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat -dl --lang<span class="o">=</span>en</code></pre></figure> <a href="http://www.guanzhui39.top/images/2012-03-behat-mink-syntaxe.jpg"><img class="aligncenter size-medium wp-image-464" title="Résultat de la commande behat -dl après avoir installé Mink" src="http://www.guanzhui39.top/images/2012-03-behat-mink-syntaxe.jpg" alt="Résultat de la commande behat -dl après avoir installé Mink" width="300" height="278" /></a> <p>Il ne nous reste plus qu'à préparer un petit fichier de configuration, nommé <strong>feature/behat.yml</strong>, qui sera automatiquement lu par behat :</p> <figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="s">&lt;p&gt;default:&lt;/p&gt;</span> <span class="s">context</span><span class="pi">:</span> <span class="s">parameters</span><span class="pi">:</span> <span class="s">base_url</span><span class="pi">:</span> <span class="s">http://localhost/mettez/ici/l/adresse/a/tester</span></code></pre></figure> <p>Ici nous indiquons simpelment à Mink la racine de base de notre application, pour éviter de la répéter dans chacun de nos tests.</p> <h3>Tester le produit : Mink crée du sens</h3> <p>Il est temps de se lancer et d'exécuter en ligne de commande :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat</code></pre></figure> <p>Miracle ^^ : la majorité des expressions utilisées par notre product owner possède déjà un sens. En tant que développeur, il n'y a que deux expressions pour lesquelles je dois donner du sens :</p> <a href="http://www.guanzhui39.top/images/2012-03-mink-resultat-commande1.jpg"><img class="aligncenter size-medium wp-image-466" title="Mink : résultat de la commande 1 : il reste des étapes à définir" src="http://www.guanzhui39.top/images/2012-03-mink-resultat-commande1.jpg" alt="Mink : résultat de la commande 1 : il reste des étapes à définir" width="300" height="187" /></a>. <p>Quand le product owner spécifie 'I should see "content"', Mink va tout seul faire le lien avec l'assertion "le contenu de la page doit comporter le texte "content".</p> <p>Mais bien mieux encore : les champs de mes formulaires ne sont pas reconnus par leur nom ou leur identifiant (name ou id). Non non, <strong>le product owner n'en n'a rien à faire de la structure interne d'une page web ; ce qu'il veut c'est un produit qui à l'écran correspond à ses attentes</strong>. <strong>Les éléments de formulaires sont reconnus par leur libellé</strong> (label).</p> <blockquote>le product owner n'en a rien à faire de la structure interne d'une page web ; ce qu'il veut c'est un produit qui, à l'écran, correspond à ses attentes</blockquote> <p>Bien sûr, Mink est permissif et on peut tout même cibler un lien, bouton, élément de formulaire... par son identifiant ou son nom... peu importe :-)</p> <p>Vous trouverez la <a href="/php/ressources-tutos-php/cheat-sheet-behat" title="Cheat Sheet Behat">liste des expressions de base de Mink ici</a>.</p> <h2>Donner son sens aux expressions non définies</h2> <p>Il nous reste donc deux expressions qui n'ont actuellement pas de signification : "Given I am logged in as ..." et "I have x euro". A nous, développeurs, de les définir dans features/bootstrap/FeatureContext.php.</p> <p>Attention, il y a là un <strong>énorme piège</strong>. Je l'ai vu à chaque fois, le premier réflexe d'un développeur va être de créer un lien entre le code source de l'application et le code disponible dans le fichier FeatureContext.php. Par exemple en créer une instance d'une Zend_Application comme on le ferait pour des tests unitaires d'une application Zend... C'est la dernière chose à faire !</p> <p>Non, si le product owner veut un produit web, si Mink nous permet de tester un produit web, continuons de tester un produit web uniquement, et laissons le code de côté :-p .</p> <p>Le product owner nous dit qu'il est un utilisateur connecté ("I am logged in as..."). Connectons l'utilisateur au sein de l'application :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @Given /^I am logged in as "([^"]*)"$/ */ public function iAmLoggedInAs($username) { return array( new Step\Given('I go to "login.php"') ,new Step\When("I fill in \"My name\" with \"$username\"") ,new Step\When('I press "Login"') ); }</code></pre></figure> <p>Pensez à ajouter au début du fichier :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">use Behat\Behat\Context\Step;</code></pre></figure> <p>Comme vous le voyez, on réutilise les étapes utilisables directement dans nos scénarios. Dans 99% des cas cela suffit. <strong>Ce sont les mêmes étapes que le product owner pourrait utiliser dans un fichier de fonctionnalité classique.</strong></p> <p>Ce qui reviendrait à écrire :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nf">Given</span> I go to <span class="s">"login.php"</span> <span class="nf">When</span> I fill in <span class="s">"My name"</span> with <span class="s">"jeanfrancois"</span> <span class="nf">When</span> I press <span class="s">"Login"</span></code></pre></figure> <p>On fait la même chose pour donner du sens à 'I have "50" euro' :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * @Given /^I have "([^"]*)" euro$/ */ public function iHaveEuro($balance) { return array( new Step\Given('I go to "/"') , new Step\When("I fill in \"New balance\" with \"$balance\"") , new Step\When('I press "Reset"') ); }</code></pre></figure> <p>Ca y est, on est bons :</p> <a href="http://www.guanzhui39.top/images/2012-03-mink-resultat-commande2.jpg"><img class="aligncenter size-medium wp-image-470" title="Mink : le produit est conforme" src="http://www.guanzhui39.top/images/2012-03-mink-resultat-commande2.jpg" alt="Mink : le produit est conforme" width="234" height="300" /></a> <p>Le système de test est fiable car il n'est pas assujetti à des modifications de notre code, mais seulement aux modifications de notre produit. Si l'application venait à ne plus correspondre aux souhaits du product owner, celui-ci en serait immédiatement averti.</p> <h2>Un vrai navigateur, avec du vrai javascript</h2> <p>C'est beau, mais pas très réaliste : quid des applications riches ? Que faire si on a du Javascript et de l'ajax de tous les côtés ? </p> <p>Et bien on va continuer d'utiliser Mink, mais cette fois-ci on va piloter un vrai navigateur. Mink permet de piloter n'importe quel navigateur en utilisant le driver de notre choix : <a href="http://sourceforge.net/projects/sahi/files/" target="_blank">Sahi</a> ou <a href="http://seleniumhq.org/download/" target="_blank">Selenium</a> (1 ou 2). </p> <p>Ayant eu de nombreuses déconvenues avec Selenium par le passé, et n'ayant pas encore eu de souci majeur à ce jour avec Sahi, j'ai une <strong>nette préférence pour Sahi</strong>. Installons le ensemble :</p> <p>L'instalaltion est simple. Téléchargez Sahi depuis http://sourceforge.net/projects/sahi/files/ et exécutez le :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">wget http://sourceforge.net/projects/sahi/files/latest/download?source<span class="o">=</span>files &lt;p&gt;java -jar sahi_v35_20110719.jar&lt;/p&gt;</code></pre></figure> Cliquez sur suivant, suivant... c'est pas moi qui vais vous apprendre un installer quelque chose :-D. <p>Lancez ensuite Sahi (placez vous bien dans le dossier spécifié pour éviter les ennuis) : <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>dossier/installation/bin/&lt;/p&gt; ./sahi.sh &amp;</code></pre></figure> <h3>Lier des fonctionnalités à un vrai navigateur</h3> <p>Pour utiliser un vrai navigateur à la place de l'émulateur, il suffit d'utiliser le tag @javascript devant le scénario qui le nécessite:</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"> <span class="nt">@javascript</span> <span class="kn">Scenario</span><span class="p">:</span> <span class="err">...</span></code></pre></figure> <p>ou devant la fonctionnalité toute entière :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="nt">@javascript</span> <span class="err">&lt;p&gt;Feature</span><span class="p">:</span> <span class="err">...&lt;/p&gt;</span></code></pre></figure> <p>Ensuite on lance behat comme d'habitude :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat</code></pre></figure> <p>Vous devriez à ce stade voir se lancer votre navigateur et voir les pages changer toutes seules : les champs se remplissent, ça clique... Bref, ça marche !</p> <p>Alors bien sûr vous allez me dire : c'est lent ! Et alors ? Ca reste toujours infiniment plus rapide que de tester tout ça à la main, et surtout c'est fiable : si une ligne est rouge, le produit n'est pas livrable ; mais si tout est vert, alors c'est que vous êtes dans les clous et que vous avez bien fait votre boulot. De plus le gain de temps de test manuel et d'aller et retours avec le product owner est considérable. Là c'est vraiment le client qui est le maître de son produit.</p> <h3>Configurer le navigateur</h3> <p>Il nous reste deux ou trois petites choses à voir. En effet, figurez vous que c'est firefox qui est lancé pour les tests, mais que moi je préfererai que ce soit chrome. Pas de problème, modifions le fichier behat.yml :</p> <figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="s">default</span><span class="pi">:</span> <span class="s">context</span><span class="pi">:</span> <span class="s">parameters</span><span class="pi">:</span> <span class="s">base_url</span><span class="pi">:</span> <span class="s">base_url</span><span class="pi">:</span> <span class="s">http://localhost/mettez/ici/l/adresse/a/tester</span> <span class="s">browser</span><span class="pi">:</span> <span class="s">chrome</span></code></pre></figure> <p>Il y a pas mal d'options, je vous laisse consulter la <a href="/php/ressources-tutos-php/cheat-sheet-behat" title="Cheat Sheet Behat" target="_blank">petite cheat sheet</a> si besoin ;-)</p> <p>La prochaine fois on verra comment allez plus loin dans le contrôle du navigateur, en exploitant l'API de Mink, et comment s'organiser pour travailler à plusieurs en créant des sous-contextes personnalisés plutôt que de ranger tout notre code dans un seul fichier. </p> <p>Mais en attendant, n'hésitez pas à tester Behat et Mink et surtout à dire ce qu'il en est pour vous :-)</p> Behat ?jour 2 : Installation et premiers tests 2012-03-11T00:00:00+00:00 http://www.guanzhui39.top/php/behat-jour-2-installation-et-premiers-tests <p>Maintenant qu'on a vu <a title="Behat ?jour 1 : comment tester son produit SCRUM ?" href="/php/behat-jour-1-comment-tester-son-produit-scrum">à quoi sert Behat</a>, il est temps de passer à la pratique...</p> <em>Vous trouverez une archive contenant toutes les sources de ce billet en bas de cette page.</em> <h2>Installer Behat</h2> <p>Le plus simple à mon goût : passer par pear :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">pear channel-discover pear.symfony.com <span class="nt">&lt;p&gt;</span>pear channel-discover pear.behat.org<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>pear install behat/behat</code></pre></figure></p> <p>Un petit test pour vérifier que tout s'est bien passé :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat --version</code></pre></figure> <h2>Démarrer un projet</h2> <p>Voici la structure initiale de mon exemple :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">mkdir -p  application/library tests/product</code></pre></figure> <a href="http://www.guanzhui39.top/images/2012-03-day2-behat-arbo.jpg"><img class="alignnone size-full wp-image-417" title="Arborescence du projet Behat" src="http://www.guanzhui39.top/images/2012-03-day2-behat-arbo.jpg" alt="Arborescence du projet Behat" width="152" height="105" /></a> <p>Placez-vous dans le dossier tests/product, puis tapez :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat --init</code></pre></figure> <p>Cela a pour effet de créer les dossiers nécessaires à votre projet :</p> <ul> <li><strong>features</strong> : les fichiers de fonctionnalité *.feature</li> <li><strong>features/bootstrap</strong> : les classes nécessaires au fonctionnement des fonctionnalités (contextes)</li> <li><strong>features/bootstrap/FeatureContext.php</strong> : le contexte principal</li> </ul> <h2>Première fonctionnalité</h2> <p>Il est temps de démarrer. Notre client (product owner) souhaite une application dans laquelle on puisse gérer un compte bancaire : on peut consulter son solde, ajouter des sous, en retirer... et on n'a pas le droit d'être découvert.</p> <p>Le fichier de fonctionnalité livré par notre client ressemble à ceci >feature/banque.feature< :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="c"># language: fr</span> <span class="err">&lt;p&gt;Fonctionnalité</span><span class="p">:</span> <span class="err">posséder</span> <span class="err">un</span> <span class="err">compte</span> <span class="err">bancaire&lt;/p&gt;</span> <span class="err">Afin</span> <span class="err">de</span> <span class="err">gérer</span> <span class="err">les</span> <span class="err">comptes</span> <span class="err">bancaires</span> <span class="err">des</span> <span class="err">utilisateurs</span> <span class="nf">En</span> tant que client <span class="err">Je</span> <span class="err">dois</span> <span class="err">être</span> <span class="err">capable</span> <span class="err">d'effectuer</span> <span class="err">des</span> <span class="err">opérations</span> <span class="err">basique</span> <span class="err">sur</span> <span class="err">mon</span> <span class="err">compte</span> <span class="kn">Scénario</span><span class="p">:</span> Avoir un compte bancaire valide <span class="nf">Etant donné</span> que je suis un nouveau client <span class="nf">Alors</span> je dois avoir <span class="s">"0"</span> euros sur mon compte <span class="kn">Scénario</span><span class="p">:</span> Retirer de l'argent sur mon compte <span class="nf">Etant donné</span> que je suis un client <span class="nf">Et</span> que je possède <span class="s">"50"</span> euros sur mon compte <span class="nf">Quand</span> je retire <span class="s">"10"</span> euros <span class="nf">Alors</span> je dois avoir <span class="s">"40"</span> euros sur mon compte <span class="kn">Plan du Scénario</span><span class="p">:</span> Ajouter de l'argent sur mon compte <span class="nf">Etant donné</span> que je suis un client <span class="nf">Et</span> que je possède <span class="s">"&lt;soldeInitial&gt;"</span> euros sur mon compte <span class="nf">Quand</span> je dépose <span class="s">"&lt;montant&gt;"</span> euros <span class="nf">Alors</span> je dois avoir <span class="s">"&lt;soldeFinal&gt;"</span> euros sur mon compte <span class="nn">Exemples</span><span class="p">:</span> <span class="p">|</span> <span class="nv">soldeInitial</span> <span class="p">|</span> <span class="nv">montant</span> <span class="p">|</span> <span class="nv">soldeFinal</span> <span class="p">|</span> <span class="p">|</span> <span class="n">0</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span> <span class="p">|</span> <span class="n">15</span> <span class="p">|</span> <span class="n">5</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="p">|</span> <span class="n">35</span> <span class="p">|</span> <span class="n">5</span> <span class="p">|</span> <span class="n">40</span> <span class="p">|</span> <span class="kn">Scénario</span><span class="p">:</span> Interdire les découverts <span class="nf">Etant donné</span> que je suis un client <span class="nf">Quand</span> j'essaye de retirer plus d argent que je n en ai sur mon compte <span class="nf">Alors</span> j'ai un message d erreur <span class="s">"Vous ne pouvez pas être à découvert"</span></code></pre></figure> <p>Le product owner sait ce qu'il veut, il nous a même donné des exemples pour mieux nous orienter dans notre développement.</p> <p>Notez au passage le commentaire "# language: fr" au début du fichier. Il indique que notre fonctionnalité est décrite en français.</p> <h2>Lancer les tests</h2> <p>Il va être temps de lancer behat. Placez-vous dans le dossier tests/product/feature, puis tapez simplement :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">behat --lang<span class="o">=</span>fr</code></pre></figure> (<em>N'oubliez pas de préciser la langue, vu qu'on travaille pour l'instant en français</em>). On va dérouler ensemble le résultat de cette commande : <h3>Rappel de la fonctionnalité</h3> <p>On commence par un rappel de la fonctionnalité et des scénarios testés.</p> <a href="http://www.guanzhui39.top/images/2012-03-day2-behat-scenario.jpg"><img class="aligncenter size-medium wp-image-426" title="Fonctionnalité Behat" src="http://www.guanzhui39.top/images/2012-03-day2-behat-scenario.jpg" alt="Fonctionnalité Behat" width="251" height="300" /></a> <p>S'il y avait eu des erreurs, le texte aurait été écrit en <span style="color: #ff0000;">rouge</span>, et si tout avait été ok,<span style="color: #008000;"> il serait vert</span>. Là le texte est<span style="color: #ff9900;"> orange</span> : il n'a pas encore de signification par rapport à notre produit.</p> <h3>Bilan du test</h3> <p>On a ensuite des informations sur les tests :</p> <a href="http://www.guanzhui39.top/images/2012-03-day2-behat-result1.jpg"><img class="aligncenter size-full wp-image-431" title="Behat - résultats : en attente" src="http://www.guanzhui39.top/images/2012-03-day2-behat-result1.jpg" alt="Behat - résultats : en attente" width="242" height="58" /></a> <h2>Donner du sens aux fonctionnalités</h2> <p>Toutes nos étapes sont en attente de définition. En effet, on n'a rien qui fait le lien entre les scénarios (les souhaits du product owner) et notre produit (le code source). C'est là que Behat va être à mon sens magique : il nous fournit le code PHP nécessaire pour créer ce lien, avec pour chaque phrase :</p> <ul> <li>l'<strong>annotation</strong> qui permet de <strong>faire le lien</strong> (<em>@Given /^que je suis un nouveau client$/</em>)</li> <li>la <strong>méthode</strong> a insérer pour <strong>donner du sens</strong> à cette phrase (<em>public function queJeSuisUnNouveauClient()</em>)</li> <li>les<strong> valeurs de cas</strong> du scénario (encadrés par des guillemets), qui sont fournis en <strong>paramètres</strong> de la méthode</li> </ul> <p>Voici donc la dernière étape du traitement du résultat de la commande :</p> <a href="http://www.guanzhui39.top/images/2012-03-day2-behat-result3.jpg"><img class="aligncenter size-medium wp-image-435" title="Behat : donner du sens aux fonctionnalités" src="http://www.guanzhui39.top/images/2012-03-day2-behat-result3.jpg" alt="Behat : donner du sens aux fonctionnalités" width="300" height="103" /></a> <p>Behat nous fourni le code a copier-coller vers la classe qui gère notre contexte principal, à savoir la classe <strong>FeatureContext</strong> contenue dans /features/bootstrap/FeatureContext.php. Allons -y : copiez le code dans le fichier <FeatureContext.php>.</p> <p>Vous pouvez constater que chaque méthode lance une exception de type <strong>PendingException</strong>. Cela signifie qu'il va falloir modifier ces méthodes pour les relier à notre application.</p> <p>Pour vous simplifier la vie, je vous propose de télécharger directement le code nécessaire au bon fonctionnement de ces tests :<a href="http://www.guanzhui39.top/images/2012-03-Account.php_.zip"> Account.php</a>, à placer dans application/library. Sinon libre à vous d'écrire le code qui correspondra à l'application ; en soit, peu importe le code, ce qui nous intéresse c'est le produit ;-)</p> <p>Nous allons maintenant convertir le texte (langue naturelle) en code (source) :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="c">&lt;!--?php use Behat\Behat\Context\ClosuredContextInterface, Behat\Behat\Context\TranslatedContextInterface, Behat\Behat\Context\BehatContext, Behat\Behat\Exception\PendingException; use Behat\Gherkin\Node\PyStringNode, Behat\Gherkin\Node\TableNode; require_once 'PHPUnit/Autoload.php'; require_once 'PHPUnit/Framework/Assert/Functions.php'; require_once __DIR__ . '/../../../../application/library/Account.php'; use \MyApp\Account as Account; /** * Features context. */ class FeatureContext extends BehatContext { /** * Testes account * * @var \MyApp\Account */ private $_account; /** * Contains the last exception * * @var \Exception */ private $_lastException; /** * @Given /^que je suis un nouveau client$/ */ public function queJeSuisUnNouveauClient() { $this---&gt;</span>_account = new Account; } /** * @Then /^je dois avoir "([^"]*)" euros sur mon compte$/ */ public function jeDoisAvoirEurosSurMonCompte($balance) { assertEquals($balance, $this-&gt;_account-&gt;getBalance()); } /** * @Given /^que je suis un client$/ */ public function queJeSuisUnClient() { if(is_null($this-&gt;_account)) { $this-&gt;_account = new Account; } } /** * @Given /^que je possède "([^"]*)" euros sur mon compte$/ */ public function queJePossedeEurosSurMonCompte($balance) { $this-&gt;_account-&gt;setBalance($balance); } /** * @Given /^je retire "([^"]*)" euros$/ */ public function jeRetireEuros($amount) { $this-&gt;_account-&gt;takeMoney($amount); } /** * @Given /^je dépose "([^"]*)" euros$/ */ public function jeDeposeEuros($amount) { $this-&gt;_account-&gt;addMoney($amount); } /** * @Given /^j\'essaye de retirer plus d argent que je n en ai sur mon compte$/ */ public function jEssayeDeRetirerPlusDArgentQueJeNEnAiSurMonCompte() { try { $this-&gt;_account-&gt;setBalance(50); $this-&gt;_account-&gt;takeMoney(100); } catch (\Exception $e) { $this-&gt;_lastException = $e; } } /** * @Given /^j\'ai un message d erreur "([^"]*)"$/ */ public function jAiUnMessageDErreur($message) { assertEquals($message, $this-&gt;_lastException-&gt;getMessage()); } }</code></pre></figure> <p>Vous constaterez que :</p> <ul> <li>on utilise PHPUnit en mode "fonction" pour nos assertions</li> <li>chaque méthode correspond à une phase d'un scénario</li> <li>on fait ce qu'on veut à l'intérieur de notre contexte ^^</li> <li>c'est simple : il ne faut même pas 3 minutes pour écrire ce code</li> </ul> <h2>L'heure de vérité</h2> <p>Le suspense est à son comble : notre code correspond t--il au souhait du client quant à son produit ?</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">behat --lang=fr</code></pre></figure> <a href="http://www.guanzhui39.top/images/2012-03-behat-product-valid.jpg"><img class="aligncenter size-medium wp-image-444" title="Behat : le produit est conforme" src="http://www.guanzhui39.top/images/2012-03-behat-product-valid.jpg" alt="Behat : le produit est conforme" width="300" height="285" /></a> <h2>Conclusion</h2> <p>Si besoin voici une archive contenant l'ensemble de ce projet : <a href="http://www.guanzhui39.top/images/2012-03-behat-decouverte-jour2.zip">Découverte de behat - jour 2.zip</a>.</p> <p>Notre client (product owner) peut désormais s'assurer à tout moment que son produit est valide.Bien plus, il va pouvoir ajuster son produit au fur et à mesure des sprints en vous décrivant clairement son besoin : fini les specs de 500 pages, c'est le product owner qui est maître de son produit ! Et cela grâce à Behat.</p> <p>La prochaine fois on verra comment mieux organiser son code : sous-contextes, configuration... et surtout comment tester une application web, grâce à Mink. mais en attendant, toute remarque / commentaire est le bienvenu ;-)</p> <p>Je vous rappelle qu'une <a title="Cheat Sheet Behat" href="/php/ressources-tutos-php/cheat-sheet-behat">cheat sheet pour behat</a> est disponible.</p> Cheat Sheet Behat 2012-03-06T00:00:00+00:00 http://www.guanzhui39.top/php/ressources-tutos-php/cheat-sheet-behat <p>Je vous propose une petite feuille d'astuces (cheat sheet) pour Behat et Mink. N'hésitez pas pas à vous en servir et vous en resservir :-)</p> <p>PDF : <strong><a href="http://www.guanzhui39.top/images/2012-04-behat-cheat-sheet1.pdf">Cheat Sheet Behat and Mink</a></strong></p> <strong>Edit</strong> : The English version : PDF (en) : <a href='http://www.guanzhui39.top/images/2012-03-behat-cheat-sheet-en.pdf'>English Behat Cheat Sheet</a> <p>Image :</p> <table> <tbody> <tr> <td align="center"><a href="http://www.guanzhui39.top/images/2012-04-behat-cheat-sheet.jpg"><img class="alignnone size-medium wp-image-408" title="Cheat Sheet Behat Mink - page 1" src="http://www.guanzhui39.top/images/2012-04-behat-cheat-sheet.jpg" alt="Cheat Sheet Behat Mink - page 1" width="212" height="300" /></a> <p>Page 1</td></p> <td align="center"><a href="http://www.guanzhui39.top/images/2012-04-behat-cheat-sheet-mink.jpg"><img class="alignnone size-medium wp-image-409" title="Cheat Sheet Behat Mink - page 2" src="http://www.guanzhui39.top/images/2012-04-behat-cheat-sheet-mink.jpg" alt="Cheat Sheet Behat Mink - page 2" width="212" height="300" /></a> <p>Page 2</td></p> </tr> </tbody> </table> <p>Au programme :</p> <p>Page 1 :</p> <ul> <li>Utilisation de la ligne de commande</li> <li>Behat en 2 mots</li> <li>Les fichiers de fonctionnalités et les scénarios</li> <li>Insérer des exemples dans les scénarios</li> <li>Naviguer avec Mink</li> </ul> <p>Page 2 :</p> <ul> <li>Infos sur la Session</li> <li>Infos sur les éléments</li> <li>Infos sur les noeuds HTML</li> <li>Configuration par défaut (behat.yml)</li> <li>Drivers disponibles pour Mink</li> </ul> <p>Pour découvrir Behat, je vous invite à consulter le billet <a title="Behat ?jour 1 : comment tester son produit SCRUM ?" href="/php/behat-jour-1-comment-tester-son-produit-scrum">Jour 1 - Comprendre et utiliser Behat pour tester un produit Scrum ?</a></p> <p>Tout commentaire est le bienvenu (ce qui manque, n'est pas clair, etc...). Merci de vos retours !</p> Behat - jour 1 : comment tester son produit SCRUM ? 2012-03-02T00:00:00+00:00 http://www.guanzhui39.top/php/behat-jour-1-comment-tester-son-produit-scrum <p>Ce billet démarre une série consacrée à cet outil que je trouve génial : <a title="Behat - site officiel" href="http://behat.org/" target="_blank">Behat</a>. Désolé pour la longueur, mais le sujet est suffisamment intéressant à mon goût pour être creusé comme il faut. :-)</p> <p>Pour ce premier jour avec Behat, on va se placer du côté du client uniquement. la partie développeur viendra dans le prochain billet. Nous allons donc découvrir Behat pas à pas.</p> <p>Behat, c'est quoi ? Pour le comprendre, il faut d'abord faire un tour du côté des méthodes agiles, et particulièrement du côté de SCRUM</p> <h2>Scrum - la notion de produit et de test d'acceptation</h2> <em>J'abstrais volontairement une partie des aspects de Scrum pour me consacrer uniquement aux points déterminants pour comprendre les enjeux de Behat. Vous trouverez plus de détails sur Scrum <a href="http://fr.wikipedia.org/wiki/Scrum_%28m%C3%A9thode%29">ici</a>).</em> <p>Scrum, c'est avant tout un état d'esprit, mais c'est aussi un exemple d'organisation. Imaginez un peintre à qui l'on demande de peindre Mona Lisa. Si on fait l'analogie avec le développement, la manière classique consiste à peindre le tableau de haut en bas, comme le ferait une imprimante. Le tableau ne pourra être livré qu'une fois la peinture entièrement achevée, de bas en haut.</p> <em>(les images proviennent de <a href="http://www.agileproductdesign.com/blog/dont_know_what_i_want.html">Jeff Patton</a>)</em> <span style="color: #ff0000;"><strong><a href="http://www.guanzhui39.top/images/2012-03-incrementing.jpg"><img class="aligncenter size-medium wp-image-366" title="Le développement incrémental" src="http://www.guanzhui39.top/images/2012-03-incrementing.jpg" alt="Le développement incrémental - mona lisa" width="300" height="117" /></a></strong></span> <p>Au contraire, en Scrum, on va commencer par esquisser les traits de Mona Lisa, puis on va demander au client si ça lui convient. Puis on va affiner le trait et le dessin. On redemande au client si ça lui convient. Enfin on va pouvoir ajouter des touches de couleurs, etc. C'est ce qu'on appelle un <strong>développement itératif</strong>. A chaque itération ("<strong>release</strong>"), le client valide le produit fourni et décide ou non de continuer. S'il s'arrête, le produit est simplement moins riche qu'escompté, mais il est livrable et <strong>utilisable par le client</strong>.</p> <span style="color: #ff0000;"><strong><a href="http://www.guanzhui39.top/images/2012-03-iterating.jpg"><img class="aligncenter size-medium wp-image-367" title="Le développement itératif" src="http://www.guanzhui39.top/images/2012-03-iterating.jpg" alt="Le développement itératif - mona lisa" width="300" height="121" /></a></strong></span> <p>En Scrum, le client est donc le maître du produit qu'on lui fourni : il a la main dessus et doit participer activement a sa description fonctionnelle. On dit alors qu'il est le propriétaire du produit (<strong>Product Owner</strong>).</p> <p>Pour décrire, justement, son produit, le client fourni généralement des spécifications fonctionnelles détaillées (SFD - qui, au passage, ne sont en général jamais lues car trop longues). Cette fois le client va au contraire décrire des <strong>cas d'utilisation</strong> de son produit. Pour savoir si le produit livré correspond à son besoin, il lui suffira de s'assurer que les cas d'utilisation sont respectés. Ces cas d'utilisation, garants du produit, sont regroupés dans tests d'acceptations du produit.</p> <p>A chaque itération (release), <strong>le produit livré est donc comparé au produit souhaité</strong> au moyen de ces <strong>tests d'acceptation</strong>. C'est la garantie :</p> <ul> <li>de satisfaire la demande initiale du client</li> <li>d'éviter les quiproquos ("mais moi je voulais pas ça")</li> <li>d'éviter les spécifications trop volumineuses</li> <li>d'éviter l'ajout de nouvelles fonctionnalités par le client au fur et à mesure du développement (mais il pourra ajouter des fonctionnalités lors d'une prochaine release)</li> </ul> <h2>Behat dans tout ça ?</h2> <p>Behat, c'est l'outil PHP qui va permettre de<strong> faire le lien entre ces tests d'acceptation</strong>, écrits par le Product Owner,<strong> et le produit</strong> que moi, développeur, je lui livre. Bien plus,<strong> il permet de lancer ces tests d'acceptation de manière automatisée</strong> sur le produit et d'en fournir un rapport lisible par n'importe quel client. A chaque release, en un coup d'oeil, le client voit si ce qu'on lui a livré correspond à son besoin.</p> <p>Attention ! <strong>Behat ne se substitue pas aux tests unitaires</strong>. Non, au contraire. Ce qu'il faut se dire c'est que<span style="text-decoration: underline;"> le client n'en a rien à faire de savoir  que le code est de bonne qualité</span> ; ce qu'il veut c'est un produit fini, fonctionnel, conforme à ses souhaits initiaux. Le test unitaire, lui, va nous permettre de nous assurer de l'intégrité du code pour des fonctionnalités sensibles ou complexes. Il ne teste pas le comportement du produit (<span style="text-decoration: underline;">mais le test unitaire reste cependant indispensable</span>)</p> <p>Le développeur a en effet souvent tendance à détourner ses tests unitaires de leur fonction initiale (tester du code), pour tester des scénarios (par exemple qu'un Controlleur a le comportement attendu). Ce n'est pas aux outils de tests unitaires de faire cela. Heureusement, Behat est là pour ça ;-) !</p> <h2>Comment ça marche ?</h2> <p>Bon, c'est bien, mais plus concrètement ça marche comment ?</p> <p>Et bien le Product Owner va rédiger ses tests d'acceptation en langue naturelle. Chaque fonctionnalité va être écrite dans un <strong><fichier nom_de_la_fonctionnalite.feature></strong>.</p> <p>Il commencer par une description de sa <strong>fonctionnalité</strong> :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kd">Fonctionnalité</span><span class="p">:</span> Avoir un compte bancaire Afin d'offrir aux utilisateurs la possibilité d'avoir un compte bancaire Etant donné que je suis inscrit <span class="err">Je</span> <span class="err">dois</span> <span class="err">être</span> <span class="err">capable</span> <span class="err">d'ajouter</span> <span class="err">ou</span> <span class="err">de</span> <span class="err">retirer</span> <span class="err">de</span> <span class="err">l'argent</span> <span class="err">sur</span> <span class="err">mon</span> <span class="err">compte</span></code></pre></figure> <p>Cette description permet de comprendre la fonctionnalité de manière générale. Ensuite il va fournir des cas d'utilisation qui vont décrire cette fonctionnalité. On parle alors de "<strong>Scénario</strong>". Chaque scénario est défini par :</p> <ul> <li>un contexte (étant donné que)</li> <li>des événements déclencheurs (quand)</li> <li>un résultat attendu (alors)</li> </ul> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kn">Scénario</span><span class="p">:</span> <span class="nf">Etant donné</span> que je suis un utilisateur connecté <span class="nf">Et</span> que j'ai un compte bancaire <span class="nf">Et</span> que le solde de mon compte est de <span class="s">"10"</span> euros <span class="nf">Quand</span> j'ajoute <span class="s">"5"</span> euros sur mon compte <span class="nf">Alors</span> mon solde doit être de <span class="s">"15"</span> euros</code></pre></figure> <p>Il va pouvoir combiner les scénarios :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kn">Scénario</span><span class="p">:</span> <span class="nf">Etant donné</span> que j'ai un compte bancaire <span class="nf">Et</span> que le solde de mon compte est de <span class="s">"10"</span> euros <span class="nf">Quand</span> j'ajoute <span class="s">"5"</span> euros sur mon compte <span class="nf">Alors</span> mon solde doit être de <span class="s">"15"</span> euros <span class="err">&lt;p&gt;Scénario</span><span class="p">:</span><span class="err">&lt;/p&gt;</span> <span class="nf">Etant donné</span> que j'ai un compte bancaire <span class="nf">Et</span> que le solde de mon compte est de <span class="s">"10"</span> euros <span class="nf">Quand</span> je retire <span class="s">"50"</span> euros sur mon compte <span class="nf">Alors</span> je dois avoir le message d'erreur <span class="s">"vous n'avez pas le droit d'être à découvert"</span> <span class="nf">Et</span> mon solde doit être de <span class="s">"10"</span> euros</code></pre></figure> <p>Quand je lancerai Behat, si mon application fonctionne correctement, ce scénario va être valide et donc coloré en vert à l'écran.Sinon il sera rouge.</p> <p>Mieux encore, le Product Owner va pouvoir insérer des exemples. Behat va automatiquement lancer ces tests pour chaque exemple fourni :</p> <figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="err">Scénario Outline</span><span class="p">:</span> <span class="nf">Etant donné</span> que j'ai un compte bancaire <span class="nf">Et</span> que le solde de mon compte est de <span class="s">""</span> euros <span class="nf">Quand</span> j'ajoute <span class="s">""</span> euros sur mon compte <span class="nf">Alors</span> mon solde doit être de <span class="s">""</span> euros <span class="nn">Examples</span><span class="p">:</span> <span class="p">|</span> <span class="nv">soldeInitial</span> <span class="p">|</span> <span class="nv">montant</span> <span class="p">|</span> <span class="nv">soldeFinal</span> <span class="p">|</span> <span class="p">|</span> <span class="n">5</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span> <span class="n">15</span> <span class="p">|</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="n">40</span> <span class="p">|</span> <span class="p">|</span> <span class="n">20</span> <span class="p">|</span> <span class="n">7</span> <span class="p">|</span> <span class="n">27</span> <span class="p">|</span> <span class="p">|</span> <span class="n">0</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span> <span class="n">10</span> <span class="p">|</span></code></pre></figure> <p>Pas mal non ? Je vais avoir un rapport visuel (une page web par exemple) de l'état de mon produit par rapport à ces tests :</p> <a href="http://www.guanzhui39.top/images/2012-03-behat-report1.jpg"><img class="aligncenter size-medium wp-image-375" title="Exemple de rapport de test d'acceptation avec Behat" src="http://www.guanzhui39.top/images/2012-03-behat-report1.jpg" alt="Exemple de rapport de test d'acceptation avec Behat" width="300" height="263" /></a> <p>Maintenant vous avez compris le principe du test d'acceptation. Vous vous êtes mis dans la peau d'un Product Owner qui rédige ses tests d'acceptation. Si vous êtes Product Owner, vous n'avez pas besoin d'aller plus loin. Si vous êtes développeur ou Scrum Master, on verra ensemble le versant technique de Behat dans le prochain Billet ;-)</p> <p>Edit: le jour 2 est prêt : <a href="/php/behat-jour-2-installation-et-premiers-tests" title="Behat ?jour 2 : Installation et premiers tests">Behat ?jour 2 : Installation et premiers tests</a></p> Atelier PHP sur Orléans - Tester son code / produit 2012-02-29T00:00:00+00:00 http://www.guanzhui39.top/php/actus-php/atelier-php-sur-orleans-tester-son-code-produit <p>Comme chaque premier jeudi du mois, demain se tiendra un atelier afup à <strong>Orléans</strong>.</p> <p>Au programme :</p> <ul> <li>le <strong>développement piloté par les test</strong>s (TDD)</li> <li>tester son code avec <a href="http://www.phpunit.de/" target="_blank"><strong>phpUnit</strong></a></li> <li>tester son code avec <a href="https://github.com/mageekguy/atoum" target="_blank"><strong>Atoum</strong></a></li> <li>tester son produit avec <a href="http://behat.org/" target="_blank"><strong>Behat</strong></a></li> </ul> <a href="http://afup.org/pages/site/?route=actualites/518/tour-dhorizon-des-pratiques-et-outils-de-tests-en-php-a-orleans">Les infos nécessaires sont ici</a> <p>Je n'ai pas prévu de slides : uniquement du code ;-) . Nous échangerons ensuite sur le thème du test en général et sur les outils présentés...</p> <p>Tout le monde est le bienvenu : membres et non-membres de l'AFUP, développeur confirmé ou non.</p> <p>A demain !</p> Utilisez un moteur javascript en PHP et faites exploser vos perfs 2012-02-25T00:00:00+00:00 http://www.guanzhui39.top/php/utilisez-un-moteur-javascript-en-php-et-faites-exploser-vos-perfs <p>Bon, je l'admets, ce titre est un brin accrocheur et trolleur :-) . Ceci dit, je viens de tomber sur Twitter sur un package PECL que je ne connaissais pas et qui à mon avis ouvre des perspectives très intéressantes, notamment pour améliorer les performances de certaines fonctionnalités chronophage de PHP.</p> <p>Sur certains de mes tests (<strong>voir plus bas</strong>), le résultat est juste... 300 fois plus performant !</p> <p>Ce package, <a title="V8JS" href="http://pecl.php.net/package/v8js" target="_blank">V8JS</a>, permet d'utiliser dans PHP le célèbre <a title="V8 javascript Engine" href="http://code.google.com/intl/fr/apis/v8/intro.html" target="_blank">V8 JavaScript Engine</a>. Ce moteur, développé par Google et très performant, est le moteur javaScript de Chrome.</p> <h2>Installation du V8 JavaScript Engine</h2> <p>On va commencer par installer le moteur en lui-même :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo apt-get install libv8-dev &lt;p&gt;sudo apt-get install libv8-3.1.8.22</code></pre></figure></p> <h2>Installation du package pear</h2> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo pecl install v8js-beta <span class="nv">php_ini</span><span class="o">=</span>/chemin/vers/le/php.ini</code></pre></figure> <p>Puis modifier le php.ini pour inclure le module v8js.so</p> <figure class="highlight"><pre><code class="language-ini" data-lang="ini"><span class="nn">[v8js]</span> <span class="err">&lt;p&gt;</span><span class="py">extension</span><span class="p">=</span><span class="s">/chemin/du/module/v8js.so</span></code></pre></figure></p> <p>Vous pouvez déplacer le module v8js.so dans le dossier des modules de PHP, ce qui permet d'avoir plus simplement :</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">extension</span><span class="o">=</span><span class="nx">v8js</span><span class="p">.</span><span class="nx">so</span></code></pre></figure></p> <p>Un petit phpinfo() pour vérifier :</p> <p style="text-align: center;"><a href="http://www.guanzhui39.top/images/2012-02-moteur-javascript-php.jpg"><img class="aligncenter wp-image-317" title="Moteur Javascript V8 en PHP" src="http://www.guanzhui39.top/images/2012-02-moteur-javascript-php.jpg" alt="phpinfo du moteur Javascript V8 en PHP" width="497" height="192" /></a></p> <h2>Utiliser V8JS</h2> <p>les choses sérieuses commencent ! Examinons la <a href="http://www.php.net/manual/en/book.v8js.php" target="_blank">documentatio</a>n. Plutôt simple : on a une classe <a href="http://www.php.net/manual/en/class.v8js.php">V8Js</a>, et une méthode <a href="http://www.php.net/manual/en/v8js.executestring.php">executeString()</a> pour appeler le moteur :</p> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">$v8</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">V8Js</span><span class="p">();</span> <span class="nx">$JS</span> <span class="o">=</span> <span class="o">&lt;&lt;&lt;</span><span class="nx">EOT</span> <span class="kd">function</span> <span class="nx">car</span><span class="p">(){</span> <span class="p">}</span> <span class="kd">var</span> <span class="nx">myCar</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">car</span><span class="p">();</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">myCar</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="s1">'green'</span><span class="p">;</span><span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span><span class="nx">print</span><span class="p">(</span><span class="nx">myCar</span><span class="p">.</span><span class="nx">color</span><span class="p">);</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">EOT</span><span class="p">;</span><span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="k">try</span> <span class="p">{</span><span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span> <span class="nx">$v8</span><span class="o">-&gt;</span><span class="nx">executeString</span><span class="p">(</span><span class="nx">$JS</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">V8JsException</span> <span class="nx">$e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">var_dump</span><span class="p">(</span><span class="nx">$e</span><span class="p">);</span> <span class="p">}</span></code></pre></figure> <p>On a donc utilisé le moteur javascript pour :</p> <ul> <li>déclarer une classe</li> <li>instancer un objet</li> <li>ajouter un attribut à cette instance</li> </ul> <h2>Les performances</h2> <p>C'est là qu'on tombe dans des choses intéressantes.</p> <p>Parenthèse :</p> <em>Attention, comme tout bench, ces mesures sont à remettre dans leur cadre. ici je teste un type de comportement, dans un contexte donné, sur ma machine. De plus c'est orienté, j'ai délibérément choisi quelque chose pour lequel JavaScript excelle.</em> <p>Bref, en voyant ça, je me suis dit : tiens, et si je testais un peu les performances ? J'ai alors cherché un comportement pour lequel JavaScript est particulièrement performant, et qui existe également en PHP. J'ai tout de suite pensé aux fonctions array_reduce / reduce. Voici deux codes qui me semblent comparables.</p> <p>Voici le premier, en PHP pur :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span> <span class="c1">// bench </span><span class="nv">$start</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="c1">// // Test </span><span class="nv">$myArray</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span> <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">100000</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nb">array_push</span><span class="p">(</span><span class="nv">$myArray</span><span class="p">,</span> <span class="nv">$i</span><span class="p">);</span> <span class="p">}</span> <span class="k">function</span> <span class="nf">rsum</span><span class="p">(</span><span class="nv">$v</span><span class="p">,</span> <span class="nv">$w</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$v</span> <span class="o">+=</span> <span class="nv">$w</span><span class="p">;</span> <span class="k">return</span> <span class="nv">$v</span><span class="p">;</span> <span class="p">}</span> <span class="nv">$r</span> <span class="o">=</span> <span class="nb">array_reduce</span><span class="p">(</span><span class="nv">$myArray</span><span class="p">,</span> <span class="s2">"rsum"</span><span class="p">);</span> <span class="c1">// // Bench </span><span class="nv">$duration</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="o">-</span> <span class="nv">$start</span><span class="p">;</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nb">var_dump</span><span class="p">(</span><span class="nv">$duration</span><span class="p">);</span></code></pre></figure></p> <p>Dans l'autre, on reporte les calculs sur le moteur JavaScript :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">// // bench $start = microtime(true); $js = <span class="err">&lt;&lt;</span><span class="nt">&lt;EOT</span> <span class="err">&lt;</span><span class="na">p</span><span class="nt">&gt;</span>var i;<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;</span>var myArray = [];<span class="nt">&lt;/p&gt;</span> for (i = 0; i <span class="nt">&lt; 100000</span><span class="err">;</span> <span class="na">i</span><span class="err">++)</span> <span class="err">{</span> <span class="na">myArray</span><span class="err">.</span><span class="na">push</span><span class="err">(</span><span class="na">i</span><span class="err">);</span> <span class="err">}</span> <span class="na">function</span> <span class="na">rsum</span><span class="err">(</span><span class="na">v</span><span class="err">,</span> <span class="na">w</span><span class="err">)</span> <span class="err">{</span> <span class="na">v</span> <span class="err">+=</span> <span class="na">w</span><span class="err">;</span> <span class="na">return</span> <span class="na">v</span><span class="err">;</span> <span class="err">}</span> <span class="na">var</span> <span class="na">r =</span><span class="err"> </span><span class="s">myArray.reduce(rsum);</span> <span class="na">print</span><span class="err">(</span><span class="na">r</span><span class="err">);</span> <span class="err">&lt;</span><span class="na">p</span><span class="nt">&gt;</span>EOT;<span class="nt">&lt;/p&gt;</span> // // Test $v8 = new V8Js(); <span class="nt">&lt;p&gt;</span>try {<span class="nt">&lt;/p&gt;</span> $v8-&gt;executeString($js); } catch (V8JsException $e) { var_dump($e); }</code></pre></figure> <h2>les résultats</h2> <p>Voici ce que j'obtiens :</p> <ul> <li><strong>en pur PHP : 2.5989</strong></li> <li><strong>en reportant sur le moteur javascript : 0.0085</strong></li> </ul> <p>Bref, comme je le disais plus haut : un sacré écart !</p> <h2>Conclusion sur le moteur V8 en PHP</h2> <p>je n'ai aucun recul sur cette utilisation du moteur V8 au sein de PHP. Le package est d'ailleurs en bétâ. Cependant, je pense que c'est une piste sérieuse si on cherche à reporter certains traitements (particulièrement des calculs) vers JavaScript...</p> Le point sur les limites du typage de PHP 2012-02-20T00:00:00+00:00 http://www.guanzhui39.top/php/le-point-sur-les-limites-du-typage-de-php <h2>Les limites</h2> <p>PHP a ceci de particulier qu'il est est un <strong>langage de typage faible</strong> (le type des variables peut changer en cours de route), mais qu'il <strong>autorise un typage fort partiel des paramètres de fonctions</strong> pour ce qui concerne les objets et les tableaux. On pourra ainsi écrire :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function test(array $argument) { (...) }</code></pre></figure> &nbsp; <figure class="highlight"><pre><code class="language-php" data-lang="php">function test(monObjet $argument) { (...) }</code></pre></figure> <p>mais par contre il n'est <strong>pas possible de typer les paramètres de méthodes pour les types scalaires</strong> (entier, chaînes de caractères...)</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function test(string $argument) { // qui a dit que PHP ne savait pas être drôle ? // Argument 1 passed to test() must be an instance of string, string given }</code></pre></figure> <p>Pourtant, ça semble bien souvent manquer : combien de fois vois t-on des tests de type dans un code ?</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function test($argument) { if(!is_string($argument)) { throw new InvalidArgumentException(<span class="ni">&amp;amp;</span>quot;eh ! on voulait une chaîne !<span class="ni">&amp;amp;</span>quot;); } }</code></pre></figure> <p>Bref, on se retrouve avec du code pas vraiment utile (qui n'est ni métier ni applicatif), qui, bien souvent, nuit à la lecture et à la bonne compréhension des sources.</p> <h2>Les <strike>solutions</strike> pistes</h2> <p>il existe cependant des solutions, ou au moins des tentatives de solutions, pour pallier à ces inconvénients.</p> <h3>Patch</h3> <p>La première provient d'un modification du langage lui-même, sous forme d'un patch ou d'une extension. <a href="http://ilia.ws/archives/207-Type-Hinting-Conclusion.html">Ilia Alshanetsky</a> propose depuis longtemps un patch pour PHP 5.3. Ce patch, complet, modifie le comportement du parseur, mais complète également Reflection.</p> <p>Malheureusement, l'utilisation de ce patch nécessite de devoir patcher systématiquement PHP, avec d'autant plus de risques que le patch n'est pas officiel. Mais surtout ce patch n'est pas mis à jour, et n'est plus compatible avec les versions récentes de PHP 5.3 (ne parlons même pas de <a href="/php/ca-vous-dit-dinstaller-lalpha-de-php-5-4" title="Ca vous dit d’installer l’alpha de PHP 5.4 ?">PHP 5.4</a> :-) )</p> <h3>La Standard Php Libary</h3> <p>Bon, c'est un idée, mais il y en a eu d'autres. Marcus Börger et David Coallier ont développé l'<a href="http://pecl.php.net/package/SPL_Types">extension PECL SPL_Types</a> pour insérer ce comportement dans la <a href="http://php.net/manual/en/book.spl-types.php">SPL</a>.</p> <p>Ici, pas vraiment de typage fort scalaire, mais une surcouche à utiliser :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function test(SplInt $integer){ // (...) } $value = new SplInt(5); <span class="nt">&lt;p&gt;</span>test($value);</code></pre></figure></p> Ça semble marcher, mais personnellement je trouve ca peu élégant et pas vraiment optimal : quid de la compatibilité avec des librairies externes qui n'utiliseront pas les typages Spl ? Et bon, on ne type pas les scalaires ; dans notre exemple ce qui suit ne fonctionnera pas, ce qui montre en soi que cette solution n'est qu'une rustine : <figure class="highlight"><pre><code class="language-php" data-lang="php">assert(5 instanceof SplInt); // faux <span class="nt">&lt;p&gt;</span>assert(is_int( new SplInt(5) )); // faux</code></pre></figure></p> <h3>La rustine de la mort qui tue</h3> <p>Très vite, de nombreux développeurs ont eu l'idée de passer par un gestionnaire d'erreur personnalisé pour résoudre ce problème. Personnellement, je l'avais mis en place sur un framework dans une ancienne boite. À l'époque j'avais trouvé ça tout seul et ça m'avait bien amusé. Après j'avais fait des benchs et je l'avais vite retiré ;-)</p> <p>L'idée est d'intercepter les erreurs, d'analyser le message de l'erreur et de voir s'il correspond pas à un problème de type. Si c'est le cas on fait nous même le contrôle "a la main", sinon on renvoie vers le gestionnaire d'erreur par défaut.</p> <p>Voici une version basique :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">set_error_handler(function($errno, $errstr, $errfile, $errline) { if(preg_match('!must be an instance of (\w*), (\w*) given!', $errstr, $matches)) { $matches[2] = str_replace('double','float', $matches[2]); return strtolower($matches[1]) == strtolower($matches[2]); } });</code></pre></figure>En 6 petites lignes de code, magique : on peut désormais écrire ceci : <figure class="highlight"><pre><code class="language-php" data-lang="php">function example1(integer $v) { echo 'Cool, integer was given !'.PHP_EOL; } function example2(string $v) { echo 'Cool, string was given !'. PHP_EOL; } function example3(float $v) { echo 'Cool, float was given !'; } function example4(boolean $v) { echo 'Cool, boolean was given !'; } function example5(object $v) { echo 'Cool, object was given !'; } function example6(resource $v) { echo 'Cool, resource was given !'; }</code></pre></figure> <p>N'oubliez pas que <strong>c'est catastrophique côté perf </strong>:-)</p> <h3>Conclusion</h3> <p>Bref, on se retrouve avec des rustines et pas vraiment des solutions exploitables en production. Pourtant, si je comprend que le typage fort puisse gêner, <strong>je trouve que le typage des paramètres et des retours de de fonction (comme en Java par exemple) manque beaucoup</strong>, et pourrait apporter du grain dans la professionnalisation du langage. </p> <p>D'ailleurs, si on observe bien, on retrouve tout de même de <a href="https://wiki.php.net/rfc/typechecking">nombreuses RFC à ce sujet</a>, et le sujet à été débattu à de nombreuses reprises, et par de nombreuses "figures" de PHP (<a href="https://wiki.php.net/rfc/typecheckingparseronly">Derick Rethans</a>, <a href="http://sebastian-bergmann.de/archives/900-Scalar-Type-Hints-in-PHP-5.3.99.html">Sebastian Bergmann</a>...), on a même un <a href="http://schlueters.de/blog/archives/139-Scalar-type-hints-in-PHP-trunk.html">moment que c'était bon</a>. Aux dernières nouvelles (mais je dois avouer que je ne sais pas trop ce qu'il en est ) il me semble que l'introduction du typage fort des arguments des fonctions reste très largement discutée, voire contestée, et qu'alors même on l'espérait pour php 5.4, elle n'est prévue dans aucune des futures versions sur les rails.</p> <strong>Quel est votre avis sur la question ?</strong> Le typage fort des paramètres et des retours de fonctions vous manque t-il au quotidien ? Ou bien au contraire trouvez-vous que c'est justement la force de PHP que d'offrir une grande souplesse aux développeurs et de s'en passer ? <strong>Etes-vous déçus de ne pas avoir cette nouveauté dans PHP 5.4 ?</strong> Slides de l'atelier php sur les Traits pour l'AFUP 2012-02-03T00:00:00+00:00 http://www.guanzhui39.top/php/slides-de-latelier-php-sur-les-traits-pour-lafup <p>Pour ceux qui étaient là hier pour notre atelier php à Orléans sur la programmation orientée objet, php 5.4 et les traits, voici les slides que j'ai utilisés :</p> <div id="__ss_11402129" style="width: 425px;"><strong style="display: block; margin: 12px 0 4px;"><a title="Programmation Orientée Objet et les Traits en PHP 5.4" href="http://www.slideshare.net/halleck45/programmation-oriente-objet-et-les-traits-en-php-54" target="_blank">Programmation Orientée Objet et les Traits en PHP 5.4</a></strong> <iframe src="http://www.slideshare.net/slideshow/embed_code/11402129" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="425" height="355"></iframe></div> <div style="padding: 5px 0 12px;">View more <a href="http://www.slideshare.net/" target="_blank">presentations</a> from <a href="http://www.slideshare.net/halleck45" target="_blank">halleck45</a></div> <div style="padding: 5px 0 12px;">Au passage, merci à tous d'êtres venus ! Prochain rendez-vous le 1er mars (premier jeudi du mois) :-)</div> PHP à Orléans : ça bouge ! 2012-01-20T00:00:00+00:00 http://www.guanzhui39.top/php/php-a-orleans-ca-bouge <p>Et bien pour un premier apéro php sur Orléans, on peut dire que ça s'est bien passé ! Une bonne dizaine de personnes, motivées, sympas.... finalement à Orléans on semble bien décidés à montrer qu'on existe :-)</p> <p>Pour synthétiser la soirée, qui s'est tenu sur la place du Martroi, au coeur d'Orléans, et bien des bonnes discussions de geeks, des projets... Ainsi a t-il été décidé de se retrouver une fois par mois pour des ateliers / formations sur PHP et sur des technologies tierces.</p> <p>Parmi les présents, on retrouve en force SUPINFO, mais aussi la CCI, et différentes web agency orléanaises. On espère être plus nombreux pour le prochain rendez-vous ; aussi si vous êtes de la région, n'hésitez pas à surveiller le site d'apéro php ou à m'envoyer un petit message si vous êtes intéressés. Y'aurait un développeur photographe dans l'équipe ? :-p</p> <p>En attendant le prochain rendez-vous, voici quelques photos de la soirée. Bon, désolé pour la qualité de l'image, on ne peut pas dire que mon téléphone soit le meilleur pour ce qui est de prendre des photos...</p> <a href="http://www.guanzhui39.top/images/2012-01-IMG_20120119_194548.jpg"><img class="size-medium wp-image-267 aligncenter" title="Apéro php Orléans 19 janvier 2012" src="http://www.guanzhui39.top/images/2012-01-IMG_20120119_194548.jpg" alt="Apéro php Orléans 19 janvier 2012 - photo 1" width="300" height="225" /></a> <a href="http://www.guanzhui39.top/images/2012-01-IMG_20120119_194555.jpg"><img class="size-medium wp-image-268 aligncenter" title="Apéro php Orléans 19 janvier 2012" src="http://www.guanzhui39.top/images/2012-01-IMG_20120119_194555.jpg" alt="Apéro php Orléans 19 janvier 2012 - photo 2" width="300" height="225" /></a> Comment tester un Trait avec phpUnit ? 2011-09-13T00:00:00+00:00 http://www.guanzhui39.top/php/comment-tester-un-trait-avec-phpunit <p>Il peut être intéressant dès aujourd'hui de tester un code avec des <a title="PHP 5.4 : les Traits (Horizontal Reuses)" href="/php/php-5-4-les-traits-horizontal-reuses" target="_blank">Traits</a>, avec PHP 5.4 (alpha) et phpUnit</p> <p>Lors de mes premiers tests, je me suis vite rendu compte d'un problème : pour pouvoir tester un trait, c'est à dire un comportement, il faut une classe qui implémente ce comportement :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait MyBehavior { public function getAny() { return 'ok'; } } class Example { use MyBehavior; }</code></pre></figure> <p>Et le test :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class BehaviorTest extends PHPUnit_Framework_TestCase { public function testMyBehavior() { $behavior = new Example; $this-&gt;assertEquals('ok', $behavior-&gt;getAny()); } }</code></pre></figure> <p>Or ici on voit bien l'erreur : on ne test pas unitairement le comportement du Trait, mais son implémentation dans une classe qui est sujette à modifications.</p> <p>C'est ici qu'intervient phpUnit 3.6, qui introduit la méthode <strong>getObjectForTrait()</strong>. Grâce à cette méthode, il est possible de tester directement notre comportement.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait MyBehavior { public function getAny() { return 'ok'; } }</code></pre></figure> <p>et le test :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class BehaviorTest extends PHPUnit_Framework_TestCase { public function testMyBehavior() { $behavior = $this-&gt;getObjectForTrait('MyBehavior'); $this-&gt;assertEquals('ok', $behavior-&gt;getAny()); } }</code></pre></figure> <p>Pratique non ?</p> Couverture de code de PHP : un avis sur la polémique de PHP 5.3.7 ? 2011-09-09T00:00:00+00:00 http://www.guanzhui39.top/php/couverture-de-code-de-php-un-avis-sur-la-polemique-de-php-5-3-7 <p>Si vous êtes intéressé par la couverture de code de PHP* (la manière dont les fonctions natives du langage sont testées par des Tests unitaires), il est très intéressant de consulter <a href="http://gcov.php.net/" target="_blank">http://gcov.php.net/</a> .</p> <p>Ce site contient en temps réel les compte-rendus de la couverture de la version en cours de PHP (PHP 5.3) et la la prochaine version (PHP 5.4)</p> <p>Vous y trouverez :</p> <ul> <li>la couverture de code</li> <li>les compte-rendus de compilation</li> <li>la liste des fonctions en erreur / warning</li> <li>la liste des fonctions non testées</li> </ul> <p>Ce site va peut être mettre fin aux polémiques sur le workflow des versions de PHP. En effet, on se souvient tous de l'<a href="http://www.php.net/archive/2011.php#id2011-08-22-1" target="_blank">affaire récente de PHP 5.3.7</a> qui a révélé de grosses lacunes dans le langage (à savoir que les versions stables de PHP contiennent en réalité de nombreuses fonctions dont les tests unitaires échouent).</p> <p>J'ai bien envie de tirer alors un petit bilan, qui n'engage que moi, mais sur lequel j'aimerai avoir votre avis. Je pense que depuis PHP 5.3, PHP s'est largement professionnalisé, grâce à d'une part un nouveau modèle Orienté Objet plus riche, d'autre part l'appropriation par les développeurs des outils de PIC (Plate-forme d'intégration continue) et de phpUnit en particulier.</p> <p>Cette professionnalisation a amené de nouveaux types de développeurs PHP, avec de nouvelles exigences, auxquelles PHP tente de répondre aujourd'hui.</p> <p>Je pense alors que, oui on peut critiquer le processus actuel de mise en production de PHP, car avec PHP 5.3.7 PHP a été la "risée" du web (j'éxagère exprès, mais disons que ça ne l'aide pas à effacer l'image très négative des développeurs PHP, qui sont souvent considérés comme des "sous-développeurs", même encore aujourd'hui).</p> <p>Mais c'est sur la bonne voie. PHP se professionnalise, se réorganise (processus de votes, passage de subversion à git...), se réoriente. Le langage répond de mieux en mieux à nos exigences de qualité (il suffit de voir le nombre de type d'<a href="http://www.php.net/~helly/php/ext/spl/classException.html">exceptions dans la SPL</a> par exemple, ou encore l'apparition des Traits dans PHP 5.4). Bref, je trouve dommage de jeter la pierre, comme certains l'ont fait, à un langage qui est dans une phase de transition, et, je l'espère, de transition positive.</p> <p>Et vous ? Pensez-vous également que cette phase de transition soit positive ? Il serait aussi intéressant de savoir ce qu'en pensent des développeurs d'autres langages, pour connaître des avis extérieurs...</p> <p>____________</p> * note pour  ceux qui ne sont pas familiarisés avec les tests unitaires, il est peut-être temps de vous y mettre :-) . L'outil le plus utilisé est <a href="http://www.phpunit.de/manual/3.5/en/automating-tests.html" target="_blank">PhpUnit</a>, mais un "nouveau" venu qui a l'air bien conçu mais que j'ai pas encore eu l'occasion de tester est <a href="http://www.slideshare.net/impossiblium/atoum-le-framework-de-tests-unitaires-pour-php-53-simple-moderne-et-intuitif" target="_blank">Atoum</a>, de <a href="http://blog.mageekbox.net/" target="_blank">Frédéric Hardy</a>. <p>Pour générer un compte rendu de la couverture de code avec phpUnit, cette simple commande suffit :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">phpunit  --coverage-html /chemin/du/rapport/ /chemin/des/tests/unitaires</code></pre></figure> PHP Solutions d'Aout est disponible 2011-08-02T00:00:00+00:00 http://www.guanzhui39.top/php/php-solutions-daout-est-disponible <p>Bon, et bien comme un peu de pub n'a jamais fait de mal, et surtout que j'ai écrit comme je vous le disais un petit article dans ce numéro, je vous invite à découvrir le dernier numéro de PHP Solution :</p> <ul> <li>POO en PHP et nouveautés apportées par PHP 5.4.</li> <li>Drupal et Magento deux géants pour une plateforme e-commerce / m-commerce complète.</li> <li>Facebook : Créer vos applications au bout des doigts.</li> <li>Générer des classeurs pour tableur avec PHPExcel.</li> <li>Les solutions pour booster WordPress.</li> <li>PC ASUS G73S.</li> </ul> <p>Bref, si le coeur vous en dit ça se passe ici : <a href="http://phpsolmag.org/drupal-et-magento-creez-votre-plateforme-e-commerce/" target="_blank">PHP Solutions Aout 2011</a></p> <p>L'article "Repenser l'approche orientée objet" est une <strong>introduction</strong> à une suite d'article sur la professionnalisation de PHP et sur l'arrivée prochaine de PHP 5.4. En quelques pages je n'ai pas la prétention de "repenser" l'objet, mais simplement démarrer en présentant mon point de vue sur : d'un côté qu'utiliser une classe ne veut pas dire qu'on pense Objet ; d'autre part que pour moi une application se définit comme un maillage de "Comportements", d'où l'intérêt de PHP 5.4 et des <a title="PHP 5.4 : les Traits (Horizontal Reuses)" href="/php/php-5-4-les-traits-horizontal-reuses">Traits</a> selon moi.</p> <p>Après, si vous avez des suggestions, des retours et des critiques, n'hésitez pas à laisser un petit commentaire, et surtout faites-nous partager votre vision de la Programmation Orientée Objet, et de la manière dont le modèle Objet de PHP oriente vos développements :-)</p> Redonner son sens à l'héritage grâce aux Traits 2011-08-01T00:00:00+00:00 http://www.guanzhui39.top/php/redonner-son-sens-a-lheritage-grace-aux-traits <p>Suite à la rédaction d'un petit article pour PHP Solutions sur l'approche Orientée Objet, je me suis mis à faire quelques tests pour voir jusqu'à quel point les Traits de PHP 5.4 pourront changer notre manière de programmer.</p> <p>Et bien ça fait plus que changer, les Traits sont à mon avis une <strong>véritable révolution dans l'approche et la conceptualisation d'un code</strong>, quel qu'il soit.</p> <p>En effet, <strong>un Trait permet de réutiliser un Comportement</strong>. Or sur un gros projet, même en organisant bien notre code, on est souvent amené à recoder plusieurs fois le même comportement. Par exemple, pour créer un Singleton on a deux choix :</p> <ol> <li>ou bien on écrit tout le code nécessaire dans notre classe</li> <li>ou bien on fait hériter notre classe d'une classe mère Singleton</li> </ol> <p>Dans le premier cas, on a inévitablement du <strong>code redondant</strong> dans notre application.</p> <p>Dans le second, on perd le principe de l'héritage : l'héritage ne sert pas ici à spécialiser un comportement métier, mais à disposer d'outils pour un fonctionnement interne. On perd alors totalement ce principe de l'Orienté Objet qui consiste<strong> réduire le couplage entre les modules métiers et les modules internes</strong> ; L'héritage en PHP n'étant pas multiple, <strong>on perd le seul héritage métier/fonctionnel que l'on aurait pu avoir pour notre classe</strong>.</p> <p>Un solution consisterait à augmenter le niveau d'héritage et de créer une hiérarchie de classes (c'est ce qui est souvent fait, vu que l'héritage multiple n'existe pas en PHP). Mais dans ce cas :</p> <ul> <li>on a perdu la logique objet : l'héritage n'a pas servi à spécialiser</li> <li>le code n'est plus maintenable</li> <li>l'architecture n'est plus maintenable (on est pris dans un cercle vicieux, celui de rajouter toujours un niveau au dessus jusqu'à créer une classe géante à la racine qui fait tout)</li> </ul> <p>Bref, à mon avis sur ce genre de code on est souvent dans une impasse. C'est là qu'interviennent les Traits. Plutôt que de consacrer l'héritage à un comportement interne, laissons l'héritage pour le comportement métier et utilisons les traits pour ce qui est interne.</p> <p>Attention, je ne dis pas que les Traits ne peuvent pas être utilisés pour du comportement métier (bien au contraire), ne me faites pas dire ce que je n'ai pas dit :-)</p> <p>En voici un exemple simple. Cet exemple n'est pas idéal, dans la mesure où il s'agit plus d'un détournement des Traits que d'une utilisation plus intelligente, mais je pense qu'il est clair :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Singleton { /** * Constructor * */ protected function __construct() {} /** * Get singleton instance * * @return static */ public static function getInstance() { static $instance = null; if (is_null($instance)) { $instance = new static; } return $instance; } /** * Prevents cloning * * @throws Exception */ public function __clone() { throw new \Exception('Cloning of this object isn\'t authorized'); } /** * Prevents deserialization * * @throws Exception */ public function __wakeup() { throw new \Exception("Cannot deserialize instance of Singleton pattern in" . get_called_class()); } }</code></pre></figure> <p>Il ne reste plus qu'à l'utiliser :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Example extends MaClasseMetier { use \Singleton; } $oExample = Example::getInstance(); var_dump($oExample === Example::getInstance()); // true $oExample = new Example; // Fatal error: Call to protected Example::__construct() from invalid context</code></pre></figure> <p>Ce détournement nous permet d'utiliser le Comportement "Singleton" pour toutes les classes qui en ont besoin, avec un simple "use Singleton". Bon, il ne faut pas forcément le faire, et je sens les remarques qui vont arriver sur le Singleton et la testabilité, mais je pense que c'est un exemple clair des horizons que nous ouvrent les Traits :-p</p> Compiler et installer PHP 2011-07-04T00:00:00+00:00 http://www.guanzhui39.top/php/compiler-et-installer-php <p>Comme promis, voici le pas-à-pas d'une installation personnalisée de PHP (en l'occurrence PHP 5.4 alpha1).</p> <!--more--> <p>Avant toute chose, notez qu'en production il ne faut PAS utiliser une version alpha. Pour installer une version stable, ce code suffira :</p> <em><strong>Note</strong> : toutes les commandes ci-dessous sont à utiliser pour Ubuntu</em> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo apt-get install php5</code></pre></figure> <p>Commencez par télécharger la release : <a title="Lien vers PHP 5.4 alpha" href="http://qa.php.net/" target="_blank">http://qa.php.net/</a></p> <p>Ensuite, il suffit d'extraire le contenu de l'archive dans un dossier temporaire à part :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">wget http://downloads.php.net/stas/php-5.4.0alpha1.tar.gz tar xvzf php-5.4.0alpha1.tar.gz</code></pre></figure> <h2>Uniquement en mode console ( cli )</h2> <p>Ouvrez un terminal, puis entrez :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">./configure --enable-pcntl --enable-shmop --enable-cli --without-apache --disable-cgi --enable-posix</code></pre></figure> (c'est en l'occurrence ce dont moi j'avais besoin : ligne de commande uniquement + pcntl ; à vous de voir ce qu'il vous faut). <p>Si tout s'est bien passé, il ne reste plus qu'à préparer l'installation :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">make</code></pre></figure> <p>C'est sans doute le moment de lancer les tests de PHP. Ces tests unitaires vous permettront de vous assurer que tout s'est bien passé :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">make <span class="nb">test</span></code></pre></figure> <p>par contre, ne vous inquiétez pas, c'est plutôt long.</p> <p>Si tout est ok, il est temps de lancer l'installation de version de PHP :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo make install</code></pre></figure> <p>Pour vérifier votre version de PHP (cli), c'est simple :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">php -v // PHP 5.4.0alpha1 <span class="o">(</span>cli<span class="o">)</span> <span class="o">(</span>built: Jun 29 2011 13:24:55<span class="o">)</span> Copyright <span class="o">(</span>c<span class="o">)</span> 1997-2011 The PHP Group Zend Engine v2.4.0, Copyright <span class="o">(</span>c<span class="o">)</span> 1998-2011 Zend Technologies</code></pre></figure> <p>Je ne connais pas la procédure sous Windows, et je ne suis même pas sûr que ce soit possible (peut-être avec Visual Studio ?), mais si quelqu'un veut bien l'expliquer ...</p> <p>Edit: apparemment c'est <a href="http://www.artfulsoftware.com/php_mysql_win.html">possible sous Windows</a>. Des retours ?</p> <h2>Mode web (Apache)</h2> <p>Bon, lancer un script php c'est souvent pas suffisant, c'est mieux quand un site web tourne avec :-p</p> <p>Dans ce cas, on va légèrement changer la configuration, pour créer en même temps le module php pour apache (le with-apxs2):</p> Petite parenthèse: <p>Si vous n'avez pas apxs2, utilisez commande suivante :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo apt-get install apache2-threaded-dev un which apxs2 devrait vous retourner le chemin complet</code></pre></figure> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">./configure --with-mysql<span class="o">=</span>shared --with-pdo-mysql --with-apxs2<span class="o">=</span>/usr/bin/apxs2 --enable-xml --with-zlib copier le libphp5.xo make</code></pre></figure> <p>Il faut copier le module qui a été généré par le make (dans libs/libphp5.so) dans le répertoire des modules apache :</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo cp libs/libphp5.so /usr/lib/apache2/modules/libphp5.so</code></pre></figure> <p>et bien sûr, redémarrer Apache</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">sudo /etc/init.d/apache2 restart</code></pre></figure> <p>le phpinfo() vous affiche désormais :</p> <p style="text-align: center;"><a href="http://www.guanzhui39.top/images/2011-07-apercu-phpinfo-apres-compilation2.png"><img class="aligncenter size-full wp-image-172" title="Aperçu phpinfo() après compilation" src="http://www.guanzhui39.top/images/2011-07-apercu-phpinfo-apres-compilation2.png" alt="Aperçu phpinfo() après compilation" width="503" height="413" /></a></p> <p style="text-align: center;"><a href="http://www.guanzhui39.top/images/2011-07-apercu-phpinfo-apres-compilation.png"> </a></p> <p>PS : ce n'est pas forcément la meilleure configuration, l'intérêt de compiler est justement de prendre ce dont vous avez besoin; A vous de voir quels extensions vous sont utile ; n'hésitez pas à faire un tour dans votre phpinfo() avant pour le savoir :-)</p> <p>PS : en même temps que j'ai publié ce billet, notre cher Rasmus Lerdorf a publié une <a href="http://codepad.org/SXfRlJ0w" target="_blank">config pour tester PHP 5.4</a>.</p> Ca vous dit d'installer l'alpha de PHP 5.4 ? 2011-07-01T00:00:00+00:00 http://www.guanzhui39.top/php/ca-vous-dit-dinstaller-lalpha-de-php-5-4 <p>Ca y est, depuis le 28 juin une alpha de PHP 5.4 est officiellement disponible. Ca vous dit de la tester ?</p> <!--more--> <p>Personnellement (et il n'est jamais trop tard pour changer d'idée :-) ), je pense qu'un développeur doit varier ses environnements de travail, ne serait-ce que pour être prêt pour n'importe quelle migration. </p> <p>Bien sûr, il ne me viendrait pas à l'idée de travailler sur une alpha au boulot ou de l'installer sur mon serveur de production : je ne suis pas suicidaire. Par contre, avoir, par exemple, une machine avec PHP 5.4 dès aujourd'hui, me permettra d'être prêt le jour où le besoin s'en fera sentir professionnellement ; cela me permettra d'apprendre à utiliser les apports de cette version, mais aussi d'en connaître les pièges et les subtilités.</p> <p>De même, varier les environnements peut être avantageux : si pour une raison x ou y vous devez changer de type serveur (Linux à Windows par exemple) pour une application, vous saurez anticiper un minimum les contraintes et limiter les dégâts (même si là j'ai pris un exemple extrême :-p )</p> <p>Mais vous, comment fonctionnez-vous généralement quand une version majeure est annoncée ? Vous migrez rapidement (pour vos développements persos) pour pouvoir prendre en main les nouveautés rapidement ? Ou bien préférez-vous attendre que ce soit bien stable avant d'y toucher, de peur que certains comportements changent en cours de route ?</p> <p>Je pense que ça peut être intéressant de voir les habitudes des développeurs PHP sur ce point, alors n'hésitez pas à décrire les vôtres ici. A vos votes et vos commentaires :-p !</p> [poll id="2"] Apéro PHP à Blois : Joyeux anniversaire PHP ! 2011-07-01T00:00:00+00:00 http://www.guanzhui39.top/php/apero-php-a-blois-joyeux-anniversaire-php <p>Pour l'<a href=".org/pages/site/?route=actualites/459/trinquons-partout-en-france-pour-lanniversaire-de-php53" title="Anniversaire de PHP 5.3" target="_blank">anniversaire de PHP 5.3</a>, <a href="http://www.viadeo-static.com/fr/profile/sophie.beaupuis" title="Sophie Beaupuis" target="_blank">Sophie</a> a fort gentiment organisé une rencontre sur Blois.</p> <p>Etaient principalement présents l'équipe de la société <a href="http://www.flavea.fr" title="Flavea et l'Apéro PHP 2011" target="_blank">Flavea</a> et celle de la Maison de Valérie.</p> <p>Le petit éléphant bleu était bien là pour son anniversaire :-)</p> <p>Très bonne soirée ! On remet ça pour la sortie de PHP 5.4 ?</p> <p>Vous étiez-où vous ? Apparemment à Lille ça a été pas mal non plus non ?</p> [gallery] Tour d'horizon des Callbacks en PHP 2011-06-17T00:00:00+00:00 http://www.guanzhui39.top/php/tour-dhorizon-des-callbacks-en-php <p>Je suis toujours étonné de voir de nombreux développeurs PHP, pourtant habitués à travailler avec des callbacks en JavaScript, connaître si peu voire pas du tout les callbacks PHP.</p> <p>C'est pourquoi je vous propose aujourd'hui un <strong>petit rappel des callbacks en PHP</strong> :<!--more--></p> <p>Un callback, c'est quoi ? En un mot, <strong>Un callback est une référence vers un code exécutable</strong>.</p> <p>L'utilisation de callbacks est très fréquente dans certains langages. Un exemple simple en JavaScript :</p> [javascript] <a onclick="doAnything()">example</a> [/javascript] <p>La fonction doAnything() est un callback, qui sera exécuté à chaque clic sur le lien.</p> <h2>Création d'un callback en PHP</h2> <p>En PHP, les callbacks peuvent être définis de nombreuses manières :</p> <h3>Fonction anonyme</h3> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback = function() { echo "un callback dans une fonction anonyme"; };</code></pre></figure> <h3>Fonction existante</h3> <p>Les callbacks peuvent référer une fonction existante. Dans ce cas, on utilise le nom de la fonction comme callback :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback = 'maFonction';</code></pre></figure> <h3>Méthode de classe existante</h3> <p>De la même façon, on peut référer une méthode de classe, avec cette fois quelques subtilités :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback = array('NomDeLaClass','maMethode');</code></pre></figure> <p>Equivaut à :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$obj = new NomDeLaClass; $callback = array($obj,'maMethode');</code></pre></figure> <p>Ou encore (méthode statique) :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback = array('NomDeLaClass::maMethodeStatique');</code></pre></figure> <h3>Cas particuliers</h3> <p>Pour accéder au parent de la classe actuelle, utilisez le mot clef "parent" de cette façon :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback = array('parent::maMethode');</code></pre></figure> <p>et cela de la même façon pour self et static :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback = array('self::maMethode'); $callback = array('static::maMethode');</code></pre></figure> <h2>Appeler/Exécuter un callback</h2> <p>le plus simple est sans doute d'utiliser la fonction <a title="Documentaiton de call_user_func()" href="http://php.net/manual/fr/function.call-user-func.php" target="_blank"><strong>call_user_func</strong></a> :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">call_user_func($callback);</code></pre></figure> <p>le moins élégant (c'est le moins qu'on puisse dire :-)) est d'utiliser la syntaxe suivante :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback();</code></pre></figure> <h2>Passage de paramètres et références</h2> <p>Nous allons nous attarder maintenant sur les paramètres à passer au callback. C'est simple :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$callback($param1, $param2); // ou call_user_func($callback, $param1, $param2);</code></pre></figure> <p>la fonction call_user_func a une soeur, <strong>call_user_func_array</strong>, qui permet de passer les paramètres sous forme de tableau :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">call_user_func_array($callback, array($param1, $param2));</code></pre></figure> <p>Jusque là, c'est simple. <strong>Il faut juste faire attention au passages de paramètres par référence</strong>. En effet, la fonction call_user_func() ne passe pas les paramètres par référence :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function test(<span class="err">&amp;</span>$val) { } call_user_func('test', $value); // Warning: Parameter 1 to test() // expected to be a reference, value given</code></pre></figure> <p>Il faut ruser et passer par call_user_func_array() en forçant la référence :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">function test(<span class="err">&amp;</span>$a) { } call_user_func_array('test',array(<span class="ni">&amp;$value));</span> // Cette fois c'est bon</code></pre></figure> <h2>Performances</h2> <p>Les appels de fonctions de type callback sont plus lents que des appels de fonction directs. Voici un bench sur 1 million d'itérations :</p> <a href="http://www.guanzhui39.top/images/2011-06-bench-callbacks-php-5-3.jpg"><img title="Bench Callbacks en PHP 5.3" src="http://www.guanzhui39.top/images/2011-06-bench-callbacks-php-5-3.jpg" alt="Bench Callbacks en PHP 5.3" width="400" height="231" /></a> <p>Certes il y a une différence, mais dans application standard <strong>cette différence est généralement négligeable</strong>.</p> <h2>Le mot de la fin</h2> <p>Vous me direz que PHP n'est pas à la base un langage où l'utilisation massive des callbacks est pertinente. Mais bien utilisé, les callbacks peuvent significativement alléger le code, et en voici un exemple trivial :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$array=array('pomme','peche','poire','abricot'); array_walk($array, function(<span class="err">&amp;</span>$item) { $item = strtoupper($item); }); print_r($array); //Array //( // [0] =&gt; POMME // [1] =&gt; PECHE // [2] =&gt; POIRE // [3] =&gt; ABRICOT //)</code></pre></figure> <p>Et vous, vous avez l'habitude d'utiliser des callbacks, ou bien vous réservez ça pour d'autres langages ?</p> Le Design Pattern Flyweight (Poids mouche) 2011-06-08T00:00:00+00:00 http://www.guanzhui39.top/architecture/le-design-pattern-flyweight-poids-mouche <p> <p>Aujourd'hui je vous propose de parler <strong>Design Pattern</strong>, et plus particulièrement d'un pattern intéressant à mettre en place quand on cherche à alléger (en mémoire) une application : le <strong>pattern Flyweight (poid mouche)</strong>.</p> </p> <h2>Problème à résoudre</h2> <p> <p>l'application doit manipuler de très nombreuses Entités (des produits pour une boutique par exemple), et chaque Entité est représentée par un Objet. L'instanciation de tous ces objets est très gourmande en mémoire :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$oDataMapper = new DataMapper_Product(); $tRowset = $oDataMapper-&gt;fetchAll($where); foreach($tRowset as $oProduct) { // one instance of Product $oProduct is created in each loop echo $oProduct-&gt;getName(); }</code></pre></figure> <!--more--> <p> <p>On voit le problème : pour chaque ligne récupérée, un objet est instancié, donc est créé, chargé en mémoire... c'est lent et coûteux.</p> </p> <h2>La solution : Flyweight</h2> <p> <p>Il suffit de n'instancier qu'une seule fois l'objet en question, et de l'hydrater au fur et à mesure du besoin :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$oDataMapper = new DataMapper_Product(); $tRowset = $oDataMapper-&gt;fetchAll($where); // Only one instance of Product is used $oProduct = new Product; foreach($tRowset as $tInfosAboutProduct) { $oProduct-&gt;hydrate($tInfosAboutProduct); echo $oProduct-&gt;getName(); }</code></pre></figure> <p> <p>Ce qui dans notre cas pourrait donner par exemple cette classe :</p> </p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Product extends Dao { protected $_name; protected $_price; (... getter and setters ...) public function hydrate(array $tData) { $this -&gt;setName($tData['name']) -&gt;setPrice($tData['price']); return $this; } }</code></pre></figure> <p> <p>Les performances sont dans ce cas meilleures, et l'<strong>utilisation mémoire est plus réduite</strong>.</p> </p> <h2>Quand utiliser le pattern Flyweight ?</h2> <p> <p>Ce type de pattern n'est pas forcément adapté dans toutes les situations, on l'utilise :</p> </p> <ul> <li>quand on doit manipuler de très nombreux petits objets</li> <li>quand le coût (mémoire/vitesse) de cette manipulation est élevé</li> </ul> <p> <strong>Et vous, avez-vous déjà utilisé ce pattern ? Quel est votre retour d'expérience ?</strong> </p> Objet : accéder au grand parent en PHP 2011-06-06T00:00:00+00:00 http://www.guanzhui39.top/php/objet-acceder-au-grand-parent-en-php <p>S'il est courant de surcharger une méthode parente en PHP, il est plus rare d'accéder directement à la classe "grand -mère" sans passer par la mère. Pourtant... c'est possible, et voici comment :-)<!--more--></p> <p>En effet, un héritage de PHP 4 permet préserver le contexte d'exécution de la classe fille quand on appelle une classe parente en utilisant son nom plutôt que l'opérateur "parent" (qui n'existait pas avant). Un exemple tout simple :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class GrandMother { protected $_myVar = '5'; public function doSomething() { echo "GrandMother, value is " . $this-&gt;_myVar; } } class Mother extends GrandMother { public function doSomething() { echo 'Mother, value is '; // Never called } }</code></pre></figure> <p>La classe fille n'utilise pas "parent", mais directement le nom de la classe grand-mère :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Child extends Mother { public function doSomething() { $this-&gt;_myVar = '20'; return GrandMother::doSomething(); } }</code></pre></figure> <p>Il ne reste plus qu'à tester :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$oObject = new Child; $oObject-&gt;doSomething(); // affiche "GrandMother, value is 20"</code></pre></figure> <p>A utiliser avec précaution et modération ;-)</p> PHP 5.4 : les Traits (Horizontal Reuses) 2011-06-04T00:00:00+00:00 http://www.guanzhui39.top/php/php-5-4-les-traits-horizontal-reuses <p>PHP 5.4 offre son lot de nouveautés, dont les <strong>Traits</strong>. Un trait permet d'injecter dans une classe des méthodes d'une ou plusieurs autres "classes" (des traits):</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Color { public function getColor() { return 'blue'; } } class Vehicle {} class Car extends Vehicle { use Color; } $myCar = new Car; echo $myCar-&gt;getColor(); // blue</code></pre></figure> <p>On voit ici l'intérêt du trait : <strong>reporter sur une entité un aspect "fonctionnel" d'une classe</strong>. On peut imaginer une classe Car presque vide mais fonctionnelle :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Color { public function getColor() { (...) } public function changeColor() { (...) } } trait Selling { use Price; // un trait peut utiliser un autre trait public function buy() { (...) } } class Car { use Color, Selling; }</code></pre></figure> <strong>Attention toutefois</strong> : les Traits rompent avec les aspects classique de la programmation par Interface (de l'orienté objet donc) ou de la programmation par Contrat : <p>la classe Car <strong>n'implémente PAS une Interface</strong> Color ou Selling, elles ne fait que <strong>reporter, utiliser, <span style="text-decoration: underline;">horizontalement</span></strong>.</p> <p>Ce point est très important et dangereux, car en PHP un principe de base est la verticalité des classes et le respect du principe de substitution de Liskov : un objet qui travaille avec un autre objet Y doit pouvoir continuer à fonctionner de la même façon lorsqu'on substitue à cet objet Y un autre objet Z de même Interface.</p> <h2>Un Trait peut s'assurer de la présence d'une méthode dans la classe</h2> <p>Il est possible de s'assurer dans un Trait de la présence de certaines méthodes dans la classe qui l'utilise :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Selling { (...) abstract public function getCarBrand(); } class Car { use Color, Selling; protected $_brand; public function getCarBrand() { return 'Ma marque est '.$this-&gt;_brand; } }</code></pre></figure> <h2>Portée des méthodes</h2> <p>Il est possible de préciser certaines informations lors de la déclaration du trait dans une classe :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Selling { (...) public function getConstructorPrice() {}; } class Car { use Selling { getConstructorPrice as private // la méthode getCarBrand est rendue privée } } $myCar = new Car; $myCar-&gt;getConstructorPrice(); // Error</code></pre></figure> <h2>Priorité des Traits</h2> <p>Que faire si une classe utilisent des traits qui contiennent les mêmes méthodes ? Tout est prévu :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Selling { public function exampleDuplicateMethod() {} } trait Color { public function exampleDuplicateMethod() {} } class Car { use Selling, Color { Color::exampleDuplicateMethod insteadof Selling; } }</code></pre></figure> <h2>Cas pratique : le singleton</h2> <p>Il est temps de voir un cas pratique :-) Il est pratique en PHP de créer une classe abstraite Singleton, que l'on serait tentée d'hériter à chaque fois que l'on a besoin d'un singleton. Malheureusement, PHP ne permet pas l'héritage multiple, et on se prive alors peut-être d'un héritage fonctionnel plus intéressant pour notre classe. L'utilisation des Traits résouts partiellement ce problème :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">trait Singleton { public static function getInstance() { (...) } } class Example extends MotherExample { use Singleton; } $obj = Example::getInstance();</code></pre></figure> <p>Pratique non ? (n'oubliez pas qu'un singleton c'est plus que ça, là il ne s'agit que d'un exemple)</p> <h2>Et PHP 5.3 dans tout ça ?</h2> <p>Les Traits n'existent pas en PHP 5.3, pourtant cette version du language risque d'être longtemps utilisée. Il existe une astuce simple pour simuler ces traits en PHP 5.x : la relation de Composition et l'utilisation massive des méthodes magiques :</p> <p>Nous allons commencer par créer notre classe Child1, en lui donnant un tableau (nommé $_tTraits) qui contiendra la liste des classes dont on souhaite qu'elle puisse les utiliser comme des Traits.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Trait1{ public function method1() { echo 'Cette fonction est dans le Trait 1'; } } class Trait2 { public $attribute2 = 'demo'; public function method2() { echo 'Cette fonction est dans le Trait 2'; } } class Child extends Parent1 { private $_tTraits = array('Trait1', 'Trait2'); }</code></pre></figure> <p>Maintenant, nous allons automatiquement instancier ces classes lors de la construction de l'objet. Ces instances seront stockées, nous nous en serviront pour reporter les actions effectuées sur la classe vers les classes Traits.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Child extends Parent1 { private $_tTraits = array('Trait1', 'Trait2'); private $_tTraitsInstances = array(); // ce tableau contient toutes les instances créées par le constructeur /** * Constructeur * création des instances de chaque classe Trait */ public function __construct() { // ::::: build instance for each Trait class ::::: foreach($this-&gt;_tTraits as $className) { $this-&gt;_tTraitsInstances[] = new $className; } } }</code></pre></figure> <p>Ensuite, nous allons reporter chaque appel de méthode, si elle n'existe pas dans Child1, vers l'un de ses Traits, si cette méthode existe.</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * Méthode magique __call() * On va reporter chaque appel sur une des instances des classes mères * @param string $funcName * @param array $tArgs * @return mixed */ public function __call($funcName, $tArgs) { foreach($this-&gt;_tTraitsInstances as <span class="err">&amp;</span>$object) { if(method_exists($object, $funcName)) { return call_user_func_array(array($object, $funcName), $tArgs); } } throw new Exception("The $funcName method doesn't exist"); }</code></pre></figure> <p>Désormais, tout appel de méthode de Child1 est reporté, si elle n'existe pas dans Child1 même, vers une de ses classes Traits. Il est donc possible de surcharger une méthode de ces classes Traits simplement en la déclarant dans Child1.</p> <p>Enfin, nous allons reporter toutes les lectures d'attributes (accesseurs) vers les attributs des instances des classes Traits:</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">/** * Méthode magique __get() * On va reporter chaque lecture d'attribut (accesseur) sur une des instances des classes mères * @param string $varName * @return mixed */ public function __get($varName) { foreach($this-&gt;_tTraitsInstances as <span class="err">&amp;</span>$object) { $tDefinedVars = get_defined_vars($object); if(property_exists($object, $funcName)) return $object-&gt;{$varName}; } throw new Exception("The $varName attribute doesn't exist"); }</code></pre></figure> <p>Pour au final avoir:</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">class Child extends Parent1 { private $_tTraits = array('Trait1', 'Trait2'); private $_tTraitsInstances = array(); /** * Constructeur * création des instances de chaque classe Trait */ public function __construct() { // ::::: build instance for each Trait class ::::: foreach($this-&gt;_tTraits as $className) { $this-&gt;_tTraitsInstances[] = new $className; } } /** * Méthode magique __call() * On va reporter chaque appel sur une des instances des classes mères * @param string $funcName * @param array $tArgs * @return mixed */ public function __call($funcName, $tArgs) { foreach($this-&gt;_tTraitsInstances as <span class="err">&amp;</span>$object) { if(method_exists($object, $funcName)) { return call_user_func_array(array($object, $funcName), $tArgs); } } throw new Exception("The $funcName method doesn't exist"); } /** * Méthode magique __get() * On va reporter chaque lecture d'attribut (accesseur) sur une des instances des classes mères * @param string $varName * @return mixed */ public function __get($varName) { foreach($this-&gt;_tTraitsInstances as <span class="err">&amp;</span>$object) { $tDefinedVars = get_defined_vars($object); if(property_exists($object, $funcName)) return $object-&gt;{$varName}; } throw new Exception("The $varName attribute doesn't exist"); } }</code></pre></figure> <p>Nous avons tout ce qu'il nous faut pour pouvoir utiliser notre classe :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$oObject = new Child; // appel d'une méthode d'un Trait $oObject-&gt;method2(); // affiche "Cette fonction est dans le Trait 1" // lecture d'une variable echo $oObject-&gt;attribute2; // affiche "demo"</code></pre></figure> <p>Vous voilà prêt à utiliser les Traits en PHP :-) Pour plus d'info, vous pouvez consulter la <a href="https://wiki.php.net/rfc/horizontalreuse">RFC de PHP sur les Traits</a>.</p> plus rapide et facile qu'un array_merge : l'opérateur + 2011-03-04T00:00:00+00:00 http://www.guanzhui39.top/php/astuces-php-union-de-deux-tableaux-plus-pratique-et-rapide-quun-array_merge <p>Aujourd'hui, j'ai envie de partager avec vous une astuce PHP peu connue, mais qui est bien pratique : l'union (+) de deux tableaux</p> <p>Utiliser la fonction array_merge est un vrai casse-tête quand il s'agit  de fusionner des tableaux en préservant les clefs.</p> <p>Or il existe un opérateur bien pratique : <strong>l'opérateur +</strong>, qui fusionne deux tableaux :<!--more--></p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$t1 = array('a','b','d'); $t2 = array(3 =&gt; 'f',4 =&gt; 'g'); $t3 = $t1 + $t2; print_r($t3); <span class="nt">&lt;p&gt;</span>Array<span class="nt">&lt;/p&gt;</span> ( [0] =&gt; a [1] =&gt; b [2] =&gt; d [3] =&gt; f [4] =&gt; g )</code></pre></figure> <p>Pratique non ?</p> <p>Et en plus, utiliser l'opérateur + est légèrement (à peine) plus rapide qu'un array_merge classique (sur 10 000 occurrences de fusion de deux petits tableaux en PHP 5.3, sous Ubuntu, j'obtiens un très léger écart de 0.05 secondes ^^)</p> <p>Attention, le comportement en cas de clefs communes est différent entre array_merge et + :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$array1 = array( 'brand' =&gt; 'peugeot', 'car' =&gt; '206', 'color' =&gt; 'red' ); $array2 = array( 'brand' =&gt; 'renault', 'car' =&gt; 'scenic', 'color' =&gt; 'blue' );</code></pre></figure> <p>Avec array_merge : la dernière valeur rencontrée écrase la précédente :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$array4 = array_merge($array1, $array2); print_r($array4); Array ( [brand] =&gt; renault [car] =&gt; scenic [color] =&gt; blue )</code></pre></figure> <p>Avec l'opérateur +, la première valeur est conservée :</p> <figure class="highlight"><pre><code class="language-php" data-lang="php">$array3 = $array1 + $array2; print_r($array3); Array ( [brand] =&gt; peugeot [car] =&gt; 206 [color] =&gt; red )</code></pre></figure> <em>Merci à gege2061 de m'avoir fait remarquer une absence dans ce billet, désormais corrigée ;-)</em> Performance PHP : l'héritage 2011-02-25T00:00:00+00:00 http://www.guanzhui39.top/php/performance-php-lheritage <p>Quand on parle d'orienté objet, une crainte récurrente est celle de la performance.Or le moteur PHP Objet est très bien optimisé :</p> (les benchs suivants portent sur des moyennes de tests executés 10 000 fois minimum, sous Linux en PHP 5.3.3 standard, et sont exprimés en secondes) <h2>Héritage</h2> <p>Contrairement aux idées reçus, instancier une classe qui en hérite de plusieurs autres n'est pas plus lent qu'instancier une classe mère :</p> <h3>Appel de classes statiques (secondes):</h3> <!-- body, div, table, thead, tbody, tfoot, tr, th, td, p { font-family: "Arial"; font-size: x-small; } --> <table style="height: 56px;" border="0" cellspacing="0" width="312" frame="VOID" rules="NONE"><colgroup><col width="95"></col><col width="95"></col><col width="108"></col></colgroup> <tbody> <tr> <td width="95" height="17" align="CENTER" bgcolor="#333333"><strong><span style="color: #ffffff;">Occurrences</span></strong></td> <td width="95" align="CENTER" bgcolor="#333333"><strong><span style="color: #ffffff;">4 héritages</span></strong></td> <td width="108" align="CENTER" bgcolor="#333333"><strong><span style="color: #ffffff;">aucun héritage</span></strong></td> </tr> <tr> <td height="17" align="RIGHT" bgcolor="#333333"><strong><span style="color: #ffffff;">10000</span></strong></td> <td align="RIGHT">0,7106118202</td> <td align="RIGHT">0,7099120617</td> </tr> <tr> <td height="17" align="RIGHT" bgcolor="#333333"><strong><span style="color: #ffffff;">1000</span></strong></td> <td align="RIGHT">0,0733890533</td> <td align="RIGHT">0,0721051693</td> </tr> </tbody> </table> <h3> <a href="http://www.guanzhui39.top/images/2011-02-php-5-3-performance-heritage1.gif"><img class="size-medium wp-image-23" title="php-5-3-performance-heritage1" src="http://www.guanzhui39.top/images/2011-02-php-5-3-performance-heritage1gif" alt="test de Performance PHP 5.3 : héritage et appels statiques" width="300" height="232" /></a></h3> <h3>Appel de classes instanciées (secondes):</h3> <!-- body, div, table, thead, tbody, tfoot, tr, th, td, p { font-family: "Arial"; font-size: x-small; } --> <table border="0" cellspacing="0" frame="VOID" rules="NONE"><colgroup><col width="95"></col><col width="95"></col><col width="108"></col></colgroup> <tbody> <tr> <td width="95" height="17" align="CENTER" bgcolor="#333333"><strong><span style="color: #ffffff;">Occurrences</span></strong></td> <td width="95" align="LEFT" bgcolor="#333333"><strong><span style="color: #ffffff;">4 héritages</span></strong></td> <td width="108" align="LEFT" bgcolor="#333333"><strong><span style="color: #ffffff;">aucun héritage</span></strong></td> </tr> <tr> <td height="17" align="RIGHT" bgcolor="#333333"><strong><span style="color: #ffffff;">10000</span></strong></td> <td align="RIGHT">0,7323720455</td> <td align="RIGHT">0,7246758938</td> </tr> <tr> <td height="17" align="RIGHT" bgcolor="#333333"><strong><span style="color: #ffffff;">1000</span></strong></td> <td align="RIGHT">0,0770690441</td> <td align="RIGHT">0,0738449097</td> </tr> </tbody> </table> <a href="http://www.guanzhui39.top/images/2011-02-php-5-3-performance-heritage-instances.gif"><img class="size-full wp-image-24" title="php-5-3-performance-heritage-instances" src="http://www.guanzhui39.top/images/2011-02-php-5-3-performance-heritage-instances.gif" alt="test de Performance PHP 5.3 : héritage et appels sur des instances" width="296" height="292" /></a> <p>La différence est négligeable, même sur une grosse application.</p> <p>En conclusion, l'utilisation massive de l'héritage ne nuit pas à la performance d'une application...</p> Forum AFUP : Slides disponibles 2010-11-17T00:00:00+00:00 http://www.guanzhui39.top/php/forum-afup-slides-disponibles <p>Tous les slides des conférences AFUP 2010 sont disponibles. Ca se passe ici : <a href="http://www.afup.org/pages/forumphp2010/resumes.php">Slides Forum AFUP 2010</a></p> <p style="text-align: center;"><img class="aligncenter" title="Forum AFUP" src="http://www.afup.org/templates/forumphp2010/images/logo_afup.png" alt="" width="150" height="71" /></p> Forum AFUP 2010 2010-11-14T00:00:00+00:00 http://www.guanzhui39.top/php/forum-afup-2010 <p>Le forum AFUP c'est quoi ? C'est LE lieu de rencontre des professionnels PHP à ne pas manquer. Et cette année, on fêtait les 15 ans de PHP !</p> <p>On a eu le droit à des grands noms, comme Rasmus Lerdof, Zeev Suraski (respectivement créateur de PHP et de Zend quand même ! Si si, pour la petite anecdote si beaucoup de fonctions PHP en C sont préfixées par zend_ c'est pour <strong>ZE</strong>ev Surasky et a<strong>ND</strong>y Gutmans, l'autre fondateur de Zend).</p> <p>Et aussi d'autres noms moins connus mais qu'il est bon de connaître et suivre en France : Julien Pauli, Pascal Martin, Eric Daspet...</p> <p>Bref, un forum intéressant, je pense essayer d'y retourner l'année prochaine ! En attendant je vous prépare une petite synthèse des conférences que j'ai préferées...</p> Forum AFUP 2010 : Plein PHAR 2010-11-14T00:00:00+00:00 http://www.guanzhui39.top/php/forum-afup-2010-plein-phar <p>Première conférence intéressante : <strong>Plein Phar, de Fréderic Hardy</strong>.</p> <p>Je vous livre mes notes en vrac, en espérant que ce soit lisible :<!--more--></p> <h2>Introduction</h2> <p>Phar = JAR en Java (archive qui regroupe et éventuellement compresse plusieurs fichiers en un)</p> <ul> <li>Concaténer plusieurs fichiers en 1</li> <li>Compression (zLib, Tar?</li> <li>Sécurité : openSSL possible, signatures</li> <li>Executable ou non</li> </ul> <p>Peut être inclus très facilement en PHP :</p> <p>require "phar://...";</p> <h2>Structure</h2> <p>Un fichier Pahr contient:</p> <ul> <li>Un fichier de démarrage (<strong>Stub</strong>) en PHP <ul> <li>Environnement</li> <li>Autoload</li> <li>Configuration</li> </ul> </li> <li>Un <strong>manifeste </strong>(structure)</li> <li>les <strong>fichiers</strong></li> <li>[Éventuellement une <strong>Signature</strong>]</li> </ul> <h2>Métadonnées</h2> <p>Utilisation possible de metadonnées (un peu comme les annotations). On peut y mettre n'importe quoi : Auteur, release, version?lt;/p> <p>Je pense que ça peut être très pertinent de les utiliser pour gérer les versions : si avec un SVN c'est assez simple, le SVN ne fonctionne que dans un seul sens (on connaît la version en amont). Pour un serveur qui déploie souvent, sur des serveurs clients dont il n'est pas maître, prévoir une surcouche des versions par le Phar permet de mettre en place des systèmes de mise à jour automatique... Bref, une piste à creuser.</p> <h2>Compression</h2> <p>3 extensions :</p> -    Phar -    PharTar -    PharZip <p>Il est possible de créer ses propres extensions, mais il vaut mieux utiliser ces 3 principales</p> <h2>Point de montage</h2> <p>On peut monter dans le phar des fichiers externes (mount)</p> <h2>Test unitaires</h2> <p>A la création, le stub n’est pas testé. Une fois le phar créé, les erreurs sont difficiles à gérer et opaques.</p> <p>On peut utiliser des tests unitaires pour :</p> <ul> <li> S’assurer que les données sont complètes (utilisation de la signature)</li> <li>S’assurer des injections de dépendance</li> <li>En utilisant des mocks (objets répliquant virtuels d’objets, qui permettent de modifier son comportement. Ex : créer un mock d’une connexion mysql)</li> </ul> <h2>Performance</h2> <p>Varie selon l’environnement.</p> <ul> <li>Compressé : chute des perfs de 10/15%. C’est résolu par l’utilisation de phar.cache_list. (mais on a évidemment de l'autre côté des gains de Ram)</li> <li>Standard : baisse de 2/3%</li> </ul> <p>Phar est compatible avec APC (et ça c'est une bonne chose ^^)</p> <h2>Obfuscation</h2> <p>Un phar n’est pas obfuscé ! Il ne faut pas compter dessus pour protéger le code (même s'il n'existe aucune solution ultime, Phar n'en est pas du tout une)</p> <h2>Déploiement et SVN</h2> <p>Une bonne idée : il suffit d’utiliser Hudson pour regénerer le Phar à chaque commit</p> <h2>Avec le Zend Framework</h2> <p>On peut imaginer mettre tous les fichiers du ZF dans un Phar, puis mettre le controller frontal dans le Stub.</p> <h2>Ressources</h2> <p>Un bon tutoriel (et au passage un bon blog) : <a href="http://blog.pascal-martin.fr/post/php-5.3-phar-php-archive">http://blog.pascal-martin.fr/post/php-5.3-phar-php-archive</a></p> <p>Voici mes notes en vrac. Cette conférence m'a donnée pas mal d'idée, je pense que je vais m'en servir dès lundi. Prochaine étape la prod' ?</p> Un blog est né (encore!) 2010-11-14T00:00:00+00:00 http://www.guanzhui39.top/non-classe/bonjour-tout-le-monde <p>Aujourd'hui, ouverture de mon blog. Développeur PHP à plein temps et fan de SEO à mes heures, je compte vous proposer une synthèse de ce que je pense pertinent de connaître, aussi bien en veille qu'en contenu de fond.</p> <p>J'espère que cela vous plaira ! ^^</p> 850Ϸ