subversion + fichier .ini + include_path = gestion dynamiques des dépendances (librairies,...)
Votre application utilise certainement le tout dernier framework dernier cri, ou encore LA librairie ultime pour faire du PDF ou de l'ODT en PHP.. Ou encore, votre application dépend d'une autre application que vous avez réalisé ou interne à votre entreprise, elle a besoin de certaines classes par exemple.
Voici une méthode (il en existe certainement plein d'autres !) vous permettant de gérer vos dépendances pour être sûr de travailler toujours sur la bonne version d'une librairie, et pas sur une version modifiée en local sur votre poste et qui serait différente de celle sur votre serveur de production !
Subversion : la base de tout
Subversion (ou CVS, même si je conseille de l'abandonner au profit de SVN) est un outil de gestion de source/version qui permet de centraliser une copie du code source de votre application sur un serveur central.
Cet outil permet entre autre de gérer la synchronisation des développement entre plusieurs développeurs d'une équipe, et de garder la version officielle du code source de votre application, ainsi qu'éventuellement toutes les releases officielles que vous auriez sorties.
Au delà de "maintenir" le code source de votre application, SVN, de son petit nom, peut aussi maintenir le code source de plusieurs applications, chacune ayant une adresse :
(exemple d'une installation de subversion en mode DAV/Svn avec Apache)
Mon application 1 => http://mon-serveur-svn/mon-application-1/trunk Mon application 2 => http://mon-serveur-svn/mon-application-2/trunk
Imaginons que l'application 1 ait besoin de certaines classes de l'application 2 pour fonctionner, il est nécessaire lorsque l'on récupère le code source de l'application 1 de récupérer aussi le code source de l'application 2.
Ces opérations peuvent être réalisées grâce à un "checkout" via subversion (i.e. "récupération en local du code source de l'application en restant lié au serveur central), ou bien une mise à jour via un "update" (i.e. "je synchronise la copie locale du code source que j'ai sur mon ordinateur avec celle du serveur en récupérant toutes les différences sur mon ordinateur local")
En règle général, ces opérations sont manuelles, on utilise souvent un client SVN pour cela, comme par exemple TortoiseSVN ou bien Eclipse/Subclipe/Subversive (entre autres).
Dépot de dépendances local
Quand je développe en local, j'ai donc besoin :
- du code source de mon application (en cours de développement : trunk)
- du code source de toutes les dépendances dont mon application à besoin (versions précises)
Si j'ai récupéré en local les différentes dépendances de mon application, il faut maintenant que je paramètre mon application pour qu'elle trouve les fichiers en fonction de leur emplacement.
Typiquement les répertoires qui contiennent les classes de l'application 2 dont j'ai besoin dans l'application 1 doivent être disponible dans l'include path de mon application :
- Exemple dans le fichier contexts/@common.php (inclus par tous les fichiers de mon application) :
<?php set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/../dependencies/mon-application-2/'); // ...
Imaginons que mon-application-2 contiennent entre autres des classes mais aussi des fichiers d'autres types, en règle général on va plutôt isoler dans un répertoire les classes php, par exemple library/, nous aurons donc :
- Exemple dans le fichier contexts/@common.php (inclus par tous les fichiers de mon application) :
<?php set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/../dependencies/mon-application-2/library'); // ...
Vous n'êtes pas développeur de l'application 2, donc elle peut être en cours de développement. Vous devez être sûr que la version que vous utilisez est compatible avec vos développements dans votre application (les signatures de méthodes doivent être les bonnes, les fonctionnalités doivent être celles que vous attendez...). Pour garantir ces points, il est nécessaire que vous récupériez une VERSION précise de la dépendance que vous savez compatible avec le mode de fonctionnement que vous attendez. Vous allez donc récupérer un "tag" subversion de la dépendance 2 :
Mon application 2 dans la version 0.1.1 (compatible avec mes développements) : http://mon-serveur-svn/mon-application-2/tags/0.1.1
En local, vous allez mettre le code source de cette version dans un répertoire qui porte le numéro de version pour que ce soit plus explicite :
- Exemple dans le fichier contexts/@common.php (inclus par tous les fichiers de mon application) :
<?php set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/../dependencies/mon-application-2/0.1.1/library'); // ...
Vous avez maintenant dans votre répertoire dependencies/ l'ensemble de vos dépendances qui proviennent de tags subversion (i.e. de versions précises).
Détection de dépendances et modification de l'include path automatisé
Au cours du temps vous serez amenez à utiliser des versions successives de vos dépendances, au rythme de leur développement.
Vous passerez peut être à la version 0.1.2 de mon-application-2 (votre dépendance) puis 0.1.5 puis 0.2 puis ... puis 1.0.
N'oubliez pas alors de modifier votre fichier contexts/@common.php (ou équivalent) qui spécifie l'include_path :
<?php set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/../dependencies/mon-application-2/1.0.0/library'); // ...
Un moyen plus pratique, surtout quand il y a plusieurs dépendances, est d'utiliser un fichier de configuration que vous chargez et qui vous permet de modifier l'include path.
Ce fichier peut être au format php, ini, xml ... au choix, voici un exemple en utilisant un fichier ini (utilisable par phing) :
- Fichier config/dependencies.ini :
;; liste des dépendances dependencies.list = "ma2" ;; dépendance mon-application-2 dependencies.ma2.name = "mon-application-2" dependencies.ma2.version = "1.0.0" dependencies.ma2.base = "library"
Vous pouvez maintenant ajouter la lecture et l'analyse de ce fichier dans votre "bootstrap" :
- Fichier contexts/@common.php (inclus par tous les scripts php) :
<?php
$paths = array();
$depsInfo = parse_ini_file(dirname(__FILE__).'/../config/dependencies.ini');
foreach(explode(',',$depsInfo['dependencies.list']) as $depName) {
$depKeyPrefix = 'dependencies.'.$depName.'.';
$paths[] = dirname(__FILE__).'/../dependencies/'.$depsInfo[$depKeyPrefix.'name'].'/'.$depsInfo[$depKeyPrefix.'base'];
}
set_include_path(get_include_path().PATH_SEPARATOR.implode(PATH_SEPARATOR,$paths));
// ...
Grâce à ce fichier config/dependencies.ini et à ces quelques lignes de code PHP, vous n'avez plus besoin de vous préoccuper de votre include_path, il sera automatiquement paramétré à l'exécution de votre application. Si vous souhaiter changer la version d'une dépendance, commencer par faire un checkout dans le répertoire dependencies/<nom-dependence>/<version>/ puis modifier le fichier config/dependencies.ini pour y paramétrer le numéro de version ou ajouter la dépendance (utilisez la "," pour spécifier plusieurs dépendances dans la liste).
Subversion : maintenir le code source des dépendances
Au delà de 'maintenir' le code source de VOS applications (i.e. de vos développements), le serveur Subversion (même local dans votre entreprise) peut aussi maintenir une copie du code source des dépendances externes (i.e. open source, publique...) que pourraient avoir vos applications.
Par exemple, je développe souvent avec Zend Framework (entre autres), et je peux donc stocker le code source de ces librairies chez moi, sur mon serveur subversion par exemple dans les urls suivantes :
Zend Framework 1.8.3 => http://mon-serveur-svn/ZendFramework/tags/1.8.3 Zend Framework 1.9.0 => http://mon-serveur-svn/ZendFramework/tags/1.9.0 ...
Il me suffit alors de faire un checkout de l'url http://mon-serveur-svn/ZendFramework/tags/1.8.3 et de modifier mon fichier de configuration des dépendances :
- Fichier config/dependencies.ini :
;; liste des dépendances dependencies.list = "ma2,zf" ;; dépendance mon-application-2 dependencies.ma2.name = "mon-application-2" dependencies.ma2.version = "1.0.0" dependencies.ma2.base = "library" ;; dépendance zend-framework dependencies.zf.name = "zend-framework" dependencies.zf.version = "1.8.3" dependencies.zf.base = "library"
Dépendances de type ressources
Certaines dépendances concernent des fichiers images, css, javascript, ... comme par exemple les frameworks javascript tels que Dojo, ExtJS, JQuery...
Il est tout a fait possible de gérer ces dépendances via ce même canal en altérant un peu le principe.
L'idée est de stocker les dépendances en local au même endroit que précédemment, cependant il ne faut pas forcément ajouter le répertoire à l'include path.
Par contre il faut faire en sorte que les fichier ressources (images, css, js...) soit accessible dans le document root (htdocs/public/html) du serveur web :
- Fichier config/dependencies.ini :
;; liste des dépendances dependencies.list = "ma2,zf,ej" ;; dépendance mon-application-2 dependencies.ma2.name = "mon-application-2" dependencies.ma2.version = "1.0.0" dependencies.ma2.base = "library" ;; dépendance zend-framework dependencies.zf.name = "zend-framework" dependencies.zf.version = "1.8.3" dependencies.zf.base = "library" ;; dépendance ext-js dependencies.ej.name = "ext-js" dependencies.ej.version = "1.0.0" dependencies.ej.public = yes dependencies.ej.path = no dependencies.ej.htdocs = ""
On subversionne ensuite la dépendance ext-js dans svn :
ExtJS 1.0.0 => http://mon-serveur-svn/ExtJS/tags/1.0.0
- Fichier contexts/@common.php (inclus par tous les scripts php) :
<?php
$paths = array();
$depsInfo = parse_ini_file(dirname(__FILE__).'/../config/dependencies.ini');
foreach(explode(',',$depsInfo['dependencies.list']) as $depName) {
$depKeyPrefix = 'dependencies.'.$depName.'.';
if (!isset($depsInfo[$depKeyPrefix.'path'])) {
$depsInfo[$depKeyPrefix.'path'] = 'yes';
}
if ('yes' === $depsInfo[$depKeyPrefix.'path']) {
$paths[] = dirname(__FILE__).'/../dependencies/'.$depsInfo[$depKeyPrefix.'name'].'/'.$depsInfo[$depKeyPrefix.'base'];
}
}
set_include_path(get_include_path().PATH_SEPARATOR.implode(PATH_SEPARATOR,$paths));
// ...
- Fichier .htaccess ou fichier de configuration apache (pour le virtual host par exemple) :
# ... Alias /extjs <chemin-vers-la-racine-de-votre-application>/dependencies/ext-js/1.0.0 # ...
Conclusion
Cette technique vous permet de gérer un include_path dynamique et notamment :
- utiliser plusieurs versions différentes de la même dépendance pour des applications différentes sur le même serveur / poste
- maîtriser le paramétrage de vos dépendances
- éviter de mettre l'url subversion de votre projet les différentes dépendances comme si il s'agissait de code source que vous aviez développé (cela alourdi considérablement le dev)
- ...
Une amélioration possible, testée, est l'automatisation avec phing du checkout/update des dépendances, ainsi vous laissez à phing le soin de récupérer les dépendances en fonction du fichier dependencies.ini, notamment juste avant d'exécuter les tests unitaires ou de faire un déploiement.
Et vous quels sont vos pratiques pour gérer vos dépendances ?
Commentaires
> Subversion (ou CVS, même si je conseille de l'abandonner au profit de CVS)
Je suppose que tu voulais parler de Subversion et non de CVS la seconde fois. ;)
petite question sur un détail constaté :
"Exemple dans le fichier contexts/@common.php (inclus par tous les fichiers de mon application) :"
à quoi sert ce @ dans le nom de fichier ?
Classement ? comportement magique ?