Le blog

Chaîne d'intégration continue pour les applications Symfony

Publié le 12 mars 2015

Chez Les-Tilleuls.coop, la plupart de nos applications sont basées sur les mêmes procédures :

  • Le langage PHP avec les frameworks Symfony et Doctrine pour le développement back
  • CoffeeScript, Javascript, AngularJS ou encore Twitter Bootstrap pour le développement front (Grunt comme système de construction, Yeoman pour le "scaffolding", Karma et Jasmine pour les tests).
  • Des dépôts de Git privés (hébergés sur Github)
  • Des librairies privées et des bundles partagés entre les applications et exposés comme des packages Composer
  • Des tests unitaires PHPSpec
  • Doctrine Data Fixtures (avec Alice et Faker)
  • Des scenarii Behat
  • De la PHPdoc exhaustive
  • Des scripts Capifony pour le déploiement

Nous avons construit un système d'intégration continue permettant la réalisation d'applications Symfony de haute qualité. Dans ce tutoriel, vous allez découvrir son fonctionnement et comment le mettre en place étape par étape. Si vous n'en utilisiez pas auparavant, cela va augmenter drastiquement la qualité de vos projets Symfony mais également les performances de vos développeurs.

Notre système de chaîne d'intégration continue repose essentiellement sur Jenkins. A chaque commit il :

  • met à jour notre package Composer
  • exécute tous les tests et scenarios, y compris les tests "web acceptance"
  • vérifie la qualité du code : sécurité, code mort, duplication de code, l'analyse de la complexité cyclomatique, structuration et le respect des bonnes pratiques du framework Symfony
  • génération d'une documentation de l'API
  • déploiement continu de la nouvelle version de l'application

Les feature branches dans lesquelles les nouvelles fonctionnalités sont développées sont relues puis mergées dans master par un lead dev via des Pull Requests GitHub lorsque tous les tests et vérifications sont au vert.

Le tutoriel suivant peut être suivi sur Debian (Wheezy) et Ubuntu.

#
Installer Jenkins

L'équipe Jenkins maintient un dépôt Debian fournissant des packages binaires mis à jour. Installez-le sur votre serveur accueillant la chaîne d'intégration continue :

#
Sécuriser l'installation

Si tout c'est bien passé, l'interface utilisateur de Jenkins est désormais disponible sur le port 8080 de votre serveur d'intégration continue.

Dès lors, toute personne connaissant l'URL de votre installation Jenkins peut en prendre le contrôle. Il est judicieux de sécuriser l'accès à la chaîne d'intégration continue.

  • Allez dans "Manage Jenkins" puis dans "Setup Security"
  • Allez dans "Enable Security"
  • Dans la section "Security Realm", sélectionnez comme système d'utilisateur “Jenkins’ own user database” (si vous disposez d'un annuaire LDAP ou d'un autre système de gestion centralisé des utilisateurs pour votre organisation, vous pouvez également l'utiliser)
  • N'oubliez pas de cocher "Allow users to sign up", c'est obligatoire si vous souhaitez être en mesure de vous connecter après l'activation de la sécurité
  • Dans la partie "Autorization", choississez "Project-based Matrix Authorisation Strategy", laissez les cases "Anonymous" non remplies et créez un un admin ayant tous les droits
  • Activez la protection CSRF en cochant "Prevent Cross Site Request Forgery exploits"
  • Sauvegardez et retournez à l'accueil
  • Cliquez sur "Sign Up" et créez un compte avec le même nom d'utilisateur à qui vous aviez donné les droits d'administration

Jenkins est maintenant sécurisé. Retournez dans les paramètres de sécurité pour désactiver le formulaire d'inscription.

#
Connecter Jenkins aux dépôts Github

Jenkins doit être en mesure de récupérer les codes sources à partir de vos dépôts Github privés. Le meilleur moyen que nous ayons trouvé est de créer un Github Machine User capable de se connecter aux dépôts privés. Vous pouvez ajouter des utilisateurs ayant uniquement la possibilité de lecture de vos dépôts de Github en cliquant dans "Settings" puis "Collaborators". Pour des raisons de sécurité, rappelez-vous de donner uniquement la permission de lecture à l'utilisateur de la machine.

Maintenant, nous allons créer des clés SSH privées et publiques pour le compte UNIX dont se sert Jenkins (sur Debian, il est nommé jenkins):

La dernière étape consiste à ajouter la clé publique au compte Github de l'utilisateur de la machine. A partir de la page d'accueil de Github, connectez-vous en tant qu'utilisateur de la machine, allez dans "Setting" puis dans "SSH Keys". Ajoutez ensuite une nouvelle clé avec ce contenu :

Jenkins est maintenant capable de se connecter à nos dépôts Github.

#
Installer PHP

Le langage PHP est requis pour mener à bien nos tests et les autres outils tels que Composer et Satis. Installez-le.

Remarquerez que nous avons également installé APC pour accélérer l'exécution des scripts.

#
Installer Satis

Satis est le dépôt Composer le plus populaire et le seul qui soit Open Source.  Cet outil crée un index Composer statique. Plus tard, nous ajouterons une tâche dans Jenkins pour reconstruire le dépôt à chaque commit. Cela nous permettra d'avoir toujours une mise à jour des dépôts du package Composer. Pour cela, nous devons installer Satis et l'utilisateur de Jenkins doit être capable de s'en servir. 

Satis doit être installé à partir de Composer. Passez à l'utilisateur Jenkins, installez Composer, puis Satis.

Ensuite, créez un fichier de configuration pour Satis appelé packages.json :

Ce fichier de configuration permettra d'avoir deux packages Composer privés (repo1 et repo2). Grâce aux clés SSH configurées ultérieurement, l'utilisateur de jenkins pourra se connecter à nos dépôts Github privés. Bien sûr, ces dépôts doivent avoir à leur racine un fichier composer.json

Toutes les options de configuration de Satis se trouvent sur le site de Composer.

Dans un premier temps, générez le package manuellement :

La prochaine étape est d'exposer nos packages vers HTTP. Pour cela, nous utiliserons nginx. Revenez à la racine Ctrl + d et tapez :

Ensuite, modifiez ce contenu /etc/nginx/sites-enabled/default par celui ci :

N'oubliez pas de remplacer <my-ip> par la liste des adresses IP autorisées à avoir accès aux dépôts. Cela vous empêchera la mauvaise surprise de découvrir que vos dépôts privés sont exposés à tout l'internet.

Redémarrez nginx :

Vous devriez être en mesure de parcourir les dépôts avec votre navigateur Web favori.

#
Autoriser Git et support de GitHub

Le support de Git et de GitHub existent sous forme de plugins Jenkins. Allez dans "Manage Jenkins" puis "Manage Plugins" et installez les plugins nommés "Git Plugin" et "GitHub Plugin". Leur appellation est explicite.

Ensuite, nous allons configurer un Webhook GitHub afin de déclencher un build à chaque fois qu'un commit est poussé, dans n'importe quelle branche.

Allez dans vos dépôts GitHub, cliquez sur "Settings" puis "Webhooks & Services". Cliquez sur la boîte de sélection "Add service" et choisissez "Jenkins (GitHub plugin)".

Dans la boîte, entrez le point de terminaison de votre Webhook Jenkins. Il devrait s'agir de quelque chose comme http://ci.les-tilleuls.coop:8080/github-webhook/ (remplacez juste la partie domaine de l'URL). Cliquez sur "Add service".

Le Webhook est en place ! Jenkins sera notifié à chaque fois que nous poussons du code dans notre dépôt GitHub. Répétez cette étape à chaque fois que vous souhaitez qu'un dépôt Github déclenche des builds.

#
Installer PHP CS Fixer

PHP CS Fixer est un excellent outil libre développé par SensioLabs. Sa version 1.0 est sortie il y a peu. Il trouve et corrige automatiquement les violations des normes de codage PSR-0, PSR-1, PSR-2 et Symfony. Avec son option --dry-run, il peut être utilisé dans notre chaîne d'intégration continue pour vérifier que le code produit est propre.

PHP CS Fixer est conditionné en tant qu'archive PHAR. Installons-le :

#
Installer phpDocumentator

phpDocumentator permet de générer une belle documentation HTML basée sur les DocBlocks de vos classes et vos méthodes. Comme pour PHP CS Fixer, nous allons installer sa version PHAR, mais avant de la télécharger, nous devons installer des packages Debian supplémentaires :

# apt-get install graphviz php5-intl # phpDocumentator dependencies # su jenkins $ cd $ wget http://phpdoc.org/phpDocumentor.phar

#
Test d'acceptation avec Behat et PhantomJS

Aux Tilleuls, nous sommes des fans inconditionnels de Behat et de Mink. Nous les utilisons pour gérer nos scénarios utilisateurs et pour exécuter des scénarios de test web automatisés.Les contrôleurs Symfony sont testés avec l'extension Symfony2. Mais de nos jours, nous créons de plus en plus d'applications web monopages habituellement composées d'une API JSON / REST sur un socle Symfony 2et d'un client AngularJS.

Il nous semble judicieux de tester les interactions entre les clients AngularJS et les API Symfony REST dans le système de chaîne d'intégration continue. Behat et Mink vont nous y aider.

Nous utilisons habituellement Mink Selenium2 pour piloter PhantomJS, un browser "headless" très puissant basé sur Webkit. Contrairement à l'extension Symfony2, PhantomJS a besoin d'accéder à l'application via une URL publique. Nous avons besoin d'un contrôleur frontal exposant l'environnement de test. Ecrivons-le. Nous allons également configurer le serveur web intégré fourni par Symfony pour éviter de configurer une solution plus lourde comme nginx + PHP FPM.

La première étape est de créer un nouveau contrôleur frontal pour l'environnement de test (Symfony dispose par défaut de contrôleurs frontaux pour les environnements prod et dev mais pas pour l'environnement de test).

Créez un nouveau contrôleur frontal appelé app_test.php dans le répertoire web/ de votre application. Il devrait contenir quelque chose comme ceci :

&lt;?php// Adapted from https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.phpif (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) {
    return false;
}$_SERVER = array_merge($_SERVER, $_ENV);
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app_test.php';require 'app_test.php';

La prochaine étape consiste à installer PhantomJS. Le package PhantomJS n'est pas disponible dans Debian stable. Nous allons nous replier sur la version binaire fournie sur le site officiel :

# su jenkins
$ cd
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2
$ tar xvjpf phantomjs-1.9.8-linux-x86_64.tar.bz2</pre>
<p style="text-align: justify;">Les fichier <em>behat.yml&nbsp;</em>de votre projet doit être changé pour spécifier l'URL de base et l'hôte&nbsp;Webdriver de Selenium 2 à utiliser. Voici un fichier d'exemple :</p>
<div id="crayon-54b8cf0286437076090674" class="print-yes notranslate" style="text-align: justify;" data-settings=" minimize scroll-mouseover">
<div class="crayon-plain-wrap">
<pre class="lang:default decode:true">default:
    extensions:
        BehatMinkExtension:
            base_url: "http://127.0.0.1:8000"
            selenium2:
                wd_host: "http://127.0.0.1:8787/wd/hub"

Vous êtes fin prêts à créer des scénarios pour tester toute l'application. Ils seront exécutés à chaque commit ! Pour tester la cohérence dans les navigateurs et les plateformes (appareils mobiles, systèmes exotiques...), vous pouvez jeter un oeil à SauceLabs. Cette plateforme SaaS est bien intégrée à Behat et peut être une bonne alternative à PhantomJS, bien que plus onéreuse.

#
Vérifier la qualité du code avec SensioLabs Insight

En 2014, SensioLabs a publié une plateforme SaaS exécutant des analyses de qualité du code pour les projets PHP et, plus spécifiquement, pour les applications Symfony. Ce service s'appelle Insight. Il est facile d'utilisation, sympathique à utiliser (il dispose d'un système de gamification via des "médailles") et détecte de nombreuses mauvaises pratiques dans les bundles et applications Symfony. La première étape consiste à créer un compte et à enregistrer votre projet sur Insight. Configurez vos identifiants, spécifiez le dépôt Git à analyser et exécutez une première analyse manuellement. Vous trouverez sans doute, dans votre application, des problèmes qui doivent être résolus ! SensioLabs Insight fournit un outil permettant d'exécuter une nouvelle analyse dans un système de chaîne d'intégration continue. Comme les autres outils que nous avons déjà installés, la commande insight est disponible en PHAR. Installez-la en tant qu'utilisateur

jenkins
# su jenkins
$ cd
$ wget http://get.insight.sensiolabs.com/insight.phar

Trouvez l'uuid du projet que vous souhaitez analyser avec la commande suivante :

$ php insight.phar projects

La première fois que vous utiliserez

insight.phar

, vous devrez entrer votre nom d'utilisateur et le jeton de l'interface API. Vous pouvez les retrouver sur la page de votre compte SensioLabs Insight (onglet "API/SDK"). Vos identifiants seront sauvegardés par

insight.phar

Nous sommes maintenant en mesure d'intégrer SensioLabs Insight dans notre script Jenkins. Notez l'uuid de votre projet quelque part, nous en aurons besoin plus tard. Insight peut générer un rapport de violations au format PMD. Vous l'avez deviné, Jenkins possède un plugin PMD capable d'afficher joliment ce type de résultat. Allez dans "Manage Jenkins", "Plugin Manager", cliquez sur les onglets "Available" et cherchez "PMD plugin". Installez-le. SensioLabs Insight est gratuit pour les projets dont le code source est public mais il requière un abonnement pour analyser les projets privés. Selon nous, il s'agit d'un bon investissement mais nous ne pouvons pas vous jeter la pierre si vous préférez utiliser des logiciels open source. Sebastian Bergmann (le créateur de phpunit) tient à jour une documentation de qualité expliquant comment mettre en place des outils open source de contrôle de la qualité comme PHPMD, phploc, PHP_Depend et phpcpd. N'hésitez pas à compléter votre installation avec jenkins-php.org. Note : au moment où nous écrivons ces lignes, l'output PMD et l'option fail-condition de la commande insight rencontrent des problèmes. Nous leur avons soumis des corrections et qui devraient être intégrées rapidement au fichier PHAR.

#
Le Déploiement Continu avec Capifony

Il est toujours bon d'avoir un serveur de test exécutant la dernière version du projet. Parfois, nous allons plus loin et nous laissons Jenkins pousser du code stable en production grâce aux tags Git (cela ne sera pas détaillé ici). Quoi qu'il en soit, nous utilisons un outil que vous connaissez probablement déjà : capifony. C'est un logiciel de déploiement spécialisé pour les projets Symfony construits sur Capistrano. Il s'agit de la manière simple de déployer votre application. Il se charge de copier le code source sur le serveur via Git, de configurer les bons droits d'accès aux répertoires, d'installer les dépendances des projets, d'exécuter des scripts de migration de la base de données, de redémarrer des services comme PHP FPM et de bien d'autres choses encore. Si vous ne l'utilisez pas déjà, essayez-le, vous allez l'adopter. Comme la plupart des applications Ruby, capifony est disponible en tant que gem. Pour l'installer sur Debian, rien de plus simple :

# apt-get install rubygems
# gem install capifony
#
Créer le projet Jenkins

Retournez au tableau de bord Jenkins et cliquez sur le bouton "create new jobs". Sur l'écran suivant, donnez un nom à votre projet et choisissez "Freestyle project".

Sur l'écran suivant :

  • remplissez le champs "GitHub project" avec l'URL de base de votre dépôt GitHub

Dans la section "Source Code Management" :

  • sélectionnez "Git" comme "Source Code Management"
  • dans "Repository URL", entrez l'URL clone SSH de votre dépôt Git (elle doit ressembler à git@github.com:coopTilleuls/myrepo.git)
  • cliquez sur le bouton "Add" en-dessous de "Credentials"
  • pour "SSH Username with private key", choisissez "Kind"
  • choisissez "From a file on Jenkins master" pour "Private Key" et cliquez sur "Add" (cela permet d'utiliser les clefs SSH que nous avons créées et ajoutées à GitHub à l'étape précédente)
  • effacez le contenu de "Branches to build" (pour construire toutes les branches du dépôt)
  • sélectionnez "githubweb" comme "Repository browser" et entrez l'URL de la page d'accueil de votre dépôt une fois encore

Dans "Build triggers" :

  • cochez "Build when a change is pushed to GitHub"

Dans "Build" :

  • ajoutez une nouvelle étape de build "Execute shell" et utilisez le script suivant comme modèle pour répondre à vos propres besoins :
ghRepo=myOrg/myRepo
ghAccessToken=MyGitHubAccessToken
insightProject=my-sensiolabs-insight-project-uuidecho '{"state":"pending", "target_url": "'$BUILD_URL'", "description": "Build in progress..."}' | curl --header "Content-Type: application/json" -d @- https://api.github.com/repos/$ghRepo/statuses/$GIT_COMMIT?access_token=$ghAccessToken# Rebuild Satis index
~/satis/bin/satis --no-interaction build ~/packages.json ~/packages# Cleanup parameters.yml file from previous builds, we will let Symfony generate a new file with the default settings
if [ -f "app/config/parameters.yml" ]; then
  rm app/config/parameters.yml
fi# Cleanup cache from previous builds
if [ -d "app/cache" ]; then
  rm -Rf app/cache/*
fi# Create a build directory if necessary
mkdir -p build# To collect commands statutes
status=0# Analyze with SL Insight
php ~/insight.phar analyze $insightProject
# Check the result if the result is a platinum medal (all is good)
# Fail conditions are customizable, see http://blog.insight.sensiolabs.com/2014/02/12/jenkins-integration.html
php ~/insight.phar analysis $insightProject --format="pmd" --fail-condition="analysis.getGrade() != 'platinum'" &gt; build/pmd.xml || status=$((status+$?))# Check CS
php ~/php-cs-fixer.phar fix --no-interaction --dry-run --diff -vvv src/ || status=$((status+$?))# Generate PHPDoc
php ~/phpDocumentor.phar -d src -t build/doc || status=$((status+$?))# Install dependencies through Composer
php ~/composer.phar install --no-interaction --dev || status=$((status+$?))# Create the database (I mostly use a SQLite database stored in the cache/ directory of the app)
php app/console -e=test doctrine:schema:create || status=$((status+$?))# Load fixtures
php app/console -e=test doctrine:fixtures:load || status=$((status+$?))# Run unit tests through phpspec and store the result in the JUnit format, we will use it later
bin/phpspec run --format junit &gt; build/unit.xml || status=$((status+$?))# If you use phpunit instead of phpspec, comment the previous and uncomment the following line
#./bin/phpunit --log-junit build/unit.xml || status=$((status+$?))# Run PHP built-in web server
# Use the dev router because no test router is provided by default
php app/console server:run -e=test --router=app/config/router_test.php &amp;# Store the PID of the process in a variable
serverPID=$!# Run PhantomJS in WebDriver mode (Selenium2)
~/phantomjs-1.9.8-linux-x86_64/bin/phantomjs --webdriver=8787 &amp;# Store the PID again
phantomPID=$!# Wait for HTTP server and PhantomJS initialization
sleep 30# Run Behat scenarios
# The result is send to the console because the jUnit formatter is not ready yet for Behat 3.0 (https://github.com/Behat/Behat/issues/358)
bin/behat || status=$((status+$?))# Stop the HTTP server and PhantomJS
kill -9 $serverPID
kill -9 $phantomPIDif [ $status -eq 0 ]; then
    # Update the commit status on success
    echo '{"state":"success", "target_url": "'$BUILD_URL'", "description": "The build succeeded!"}' | curl --header "Content-Type: application/json" -d @- https://api.github.com/repos/$ghRepo/statuses/$GIT_COMMIT?access_token=$ghAccessToken    # Continuous deployment
    cap deploy
else
    # Update commit status on failure
    echo '{"state":"failure", "target_url": "'$BUILD_URL'", "description": "The build failed."}' | curl --header "Content-Type: application/json" -d @- https://api.github.com/repos/$ghRepo/statuses/$GIT_COMMIT?access_token=$ghAccessToken
fi# Return build status
exit $status

Fondamentalement, le script exécute les différents outils que nous avons installés un peu plus tôt et ne s'arrête pas lorsqu'une erreur survient. Au lieu de cela, il collecte le statut de retour des commandes, et le renvoi à la fin du script si le build a fonctionné ou non.

Cela permet de toujours exécuter toutes les vérifications, même si l'une d'entre elles échoue.  Le déploiement (s'il est activé) ne se produit que si le build est OK. Le serveur web intégré et PhantomJS sont lancés en arrière-plan pour leur permettre de fonctionner simultanément avec Behat. Ils s'arrêtent une fois les scénarios Behat terminés.

N'oubliez pas de personnaliser la valeur des variables au début du script.

Pourquoi pas un fichier XML ? Parce que nous utilisons parfois d'autres serveurs de builds comme Travis CI et Bamboo. Le fait d'utiliser un simple script shell permet de remplacer facilement Jenkins par un autre serveur. Les scripts shell peuvent également être versionnés directement dans le dépôt Git.

Dans "Post-build Actions" :

  • Ajoutez l'étape "Set build status on GitHub commit"
  • Ajoutez “Publish JUnit test result report” et spécifiez build/unit.xml pour “Test report XMLs”
  • Ajoutez “Publish PMD analysis results” et spécifiez build/pmd.xml pour “PMD results”
  • Ajoutez “Publish Javadoc” et définissez build/doc comme “Javadoc directory”
  • Ajoutez d'autres actions qui vous intéressent (comme envoyer des mails)
#
Résoudre les problèmes de limites d'utilisation de l'API GitHub

Si vous commencez à obtenir des erreurs comme "Could not fetch https://api.github.com/ (…), enter your GitHub credentials to go over the API rate limit" dans la sortie de la console, c'est parce que vous dépassez les limites d'utilisation de l'API GitHub pour les utilisateurs anonymes. Ouvrez juste un shell, passez à l'utilisateur Jenkins, téléchargez quelque chose avec Composer et entrez les identifiants utilisateur de votre ordinateur lorsque cela vous est demandé :

# su jenkins
$ php composer.phar create-project dunglas/php-schema.org-model
Username: my-machine-user
Password: my-password

Etant donné que Composer stocke un OAuth dans .composer/auth.json, tous les prochains appels à GitHub fonctionneront, y compris ceux effectués par Jenkins.

#
Résoudre les problèmes de mails

Pour permettre à Jenkins d'envoyer des mails (par exemple, lorsqu'un build échoue), vous devez configurer un serveur SMTP. La façon la plus simple est d'installer un serveur local sur votre serveur d'intégration continue :

# apt-get install postfix

Les options par défaut devraient être correctes.

Pour personnaliser les paramètres des mails, allez à "Manage Jenkins" depuis la page d'accueil de Jenkins puis à "Configure System". Les paramètres SMTP sont sous la section "E-mail notification" et l'adresse utilisée par Jenkins pour envoyer des mails est "Jenkins Location".

#
Mettre à jour automatiquement les outils installés localement 

Tous les outils utilisés par notre serveur d'intégration sont régulièrement mis à jour afin de corriger des bugs et ajouter de nouvelles fonctionnalités. Debian et Jenkins peuvent être mis à jour avec apt. Les mises à jour des plugins Jenkins sont à installer directement depuis l'interface utilisateur de Jenkins. Cependant, les logiciels que nous avons installés localement doivent être mis à jour "à la main". Nous allons exécuter périodiquement un petit script shell afin de mettre à jour ces outils.

Créez le script update-tools.sh suivant en tant qu'utilisateur jenkins dans son répertoire home ( ~jenkins) :

#!/bin/sh
php composer.phar self-update
php php-cs-fixer.phar self-update
# The self-update option of phpDocumentator is broken at time of writing
wget -q http://phpdoc.org/phpDocumentor.phar -O phpDocumentor.phar
cd satis/
php ../composer.phar update --no-interaction

N'oubliez pas de le rendre exécutable en exécutant chmod +x update-tools.sh. Pour obtenir les mises à jour chaque nuit, exécutez crontab -e et ajoutez la ligne suivante :

00 00 * * * ~/update-tools.sh

Votre système de chaîne d'intégration continue est désormais prêt. Votre suite de tests complète est exécutée à chaque mise à jour du code et des outils d'analyse qualité puissants sont systématiquement lancés !

En interne, nous disposons également de contrôles de qualité et des tests spécifiques pour nos applications frontend (JS). Si certains sont intéressés, nous écrirons peut-être un autre article détaillant cet autre pan de notre infrastructure.

Vous disposez probablement de vos outils de suivi qualité préférés et de vos propres bonnes pratiques pratiques pour les projets Symfony. N'hésitez pas à nous en faire part dans les commentaires !

Retrouvez la version anglaise de cet article !

Cécile Hamerel

Cécile Hamerel

Events & marketing manager

Mots-clésAPI, Behat, Capifony, Chaîne d'Intégration Continue, Github, Jenkins, PhantomJS, PHP, Satis, SensioLabs, Symfony, Tutoriel

Le blog

Pour aller plus loin