5. Aller plus loin...

Utiliser un gestionnaire de version

Des outils de gestion de code existent depuis des décennies. Ils permettent de retrouver rapidement le code écrit par un développeur, de le comparer, de le retenir ou de l'éliminer au moment de la création d'une version.

Ces outils sont capables de gérer des branches différentes, qui permettent de faire évoluer le logiciel dans différentes directions ou par différentes équipes, puis de fusionner les branches quand c'est nécessaire.

Aujourd'hui, deux types d'outils existent : ceux qui sont bâtis sur une architecture centralisée, comme Subversion1. Ils permettent de garantir l'unicité du code, mais imposent un stockage dans un lieu unique.

D'autres, de conception plus récente, comme GIT2, fonctionnent de manière décentralisée : le code est enregistré localement, les dépôts peuvent se synchroniser entre eux. En général, un dépôt de référence est créé dans un site central (une forge logicielle). L'avantage, c'est que GIT peut être utilisé pour des petits projets, même sans forge centrale.

Les forges logicielles

Une forge logicielle regroupe un ensemble d'outils pour gérer le développement d'applications :

- un gestionnaire de code (cf. ci-dessus) ;

- un wiki, pour rédiger de la doc, des documents divers, etc.

- un gestionnaire de tickets, pour gérer les remontées de bugs ou les demandes d'évolutions ;

- des listes de discussion ;

- des outils de téléchargement, dépôt...

Il existe des forges mondiales, dont la plus connue est Sourceforge3, mais les entreprises créent également leurs propres forges. Il existe de nombreux logiciels openSource qui permettent de créer rapidement des forges où tous les produits sont intégrés. Parmi ces logiciels, on peut citer Redmine4, FusionForge5 ou Tuleap6, parmi d'autres.

L'internationalisation de l'application

Pour quoi faire ?

Outre la possibilité de gérer plusieurs langues dans l'application, pour répondre aux besoins évidents de partage ou de travail à plusieurs, il est souvent souhaitable de laisser la possibilité d'adapter certains libellés. C'est souvent le cas pour les informations concernant le login.

Il existe plusieurs techniques pour gérer les traductions. La plus simple, c'est d'utiliser un fichier de correspondance, ou fichier de langue.

Les fichiers de langue

En général, les fichiers de langue sont stockés dans un dossier locales. Leur gestion va s'appuyer sur deux mécanismes. D'une part, un code permet de savoir quelle langue utilisée. Ce code peut être statique (défini dans un fichier de paramètres) : c'est le cas le plus courant. Il peut également être dynamique, en prenant en compte la langue du navigateur. La troisième possibilité, c'est de stocker dans le profil de l'utilisateur (cookie, base de données...) la langue qu'il souhaite utiliser.

La gestion des libellés est plus classique. On distingue deux approches principales (mais il en existe d'autres), l'une consistant à créer un tableau contenant tous les libellés, l'autre utilisant un fichier xml. Dans tous les cas de figure, il est fortement conseillé de regrouper les libellés par module, pour faciliter la maintenance. Voici un exemple d'un fichier de langue :

fichier locales/fr_FR.php
<?php
$LANG= array();
$LANG["menu"][0] = "Gestion";
$LANG["menu"][1] = "Opérations de gestion";
$LANG["menu"][2] = "Liste des comptes";
$LANG["menu"][3] = "Liste des logins - identification via la base de données";
$LANG["menu"][4] = "Administration";
$LANG["menu"][5] = "Administration de l'application";
$LANG["menu"][6] = "Déconnexion";
$LANG["menu"][7] = "Déconnexion de l'application";
$LANG["menu"][8] = "A propos";
$LANG["menu"][9] = "A propos de prototypePHP";
$LANG["menu"][10] = "Aide";
$LANG["menu"][11] = "Quelques conseils...";
$LANG["menu"][12] = "Gestion des droits";
$LANG["menu"][13] = "Gestion des droits d'accès aux modules de l'application";
$LANG["menu"][20] = "Aide";
$LANG["message"][0] = "Bienvenue";
$LANG["message"][1] = "prototypephp - titre de l'application";
$LANG["message"][2] = "Module administration";
$LANG["message"][3] = "Module gestion";
$LANG["message"][4] = "Suppression effectuée";
$LANG["message"][5] = "Enregistrement effectué";
$LANG["message"][6] = "Vidage effectué";
$LANG["message"][7] = "Vous êtes maintenant déconnecté";
$LANG["message"][8] = "Vous n'êtes pas connecté";
$LANG["message"][9] = "Vous n'avez pas le droit d'accéder à cette fonction";
$LANG["message"][10] = "Identification réussie !";
$LANG["message"][11] = "Identification refusée";
$LANG["message"][12] = "Problème lors de la mise en fichier...";
$LANG["message"][13] = "Problème lors de la suppression";
$LANG["message"][14] = "Vous ne pouvez pas accéder directement à cette page";
$LANG["message"][15] = "oui";
$LANG["message"][16] = "non";
$LANG["message"][17] = "Attention :";
$LANG["message"][18] = "Confirmez-vous l'opération ?";
$LANG["message"][19] = "Valider";
$LANG["message"][20] = "Supprimer";
$LANG["message"][21] = "Rechercher";
$LANG["message"][22] = "Echec de connexion à la base de données";
$LANG["login"][0] = "Login";
$LANG["login"][1] = "Mot de passe";
$LANG["login"][2] = "Veuillez utiliser votre login du domaine pour vous identifier";
$LANG["login"][5] = "Nouveau login";
$LANG["login"][6] = "login";
$LANG["login"][7] = "Nom - prénom";
$LANG["login"][8] = "Mél";
$LANG["login"][9] = "Nom";
$LANG["login"][10] = "Prénom";
$LANG["login"][11] = "Date";
$LANG["login"][12] = "Répétez le mot de passe";
?>

Il suffit ensuite d'utiliser ce fichier dans l'application, en chargeant d'une part le fichier contenant le tableau des libellés, puis en l'utilisant dans l'application.

$language = "fr_FR";
include_once('locales/'.$language.".php");

L'intégration des libellés avec SMARTY

Pour utiliser ce fichier de langue dans Smarty, on va d'abord l'assigner :

$smarty->assign("LANG",$LANG);

puis, dans les templates smarty :

<form method="POST" action="index.php">
<table class="tablesaisie">
<tr>
<input type="hidden" name="module" value={$module}>
<td>{$LANG.login.0} :</td><td> <input name="login" maxlength="32"></td>
</tr>
<tr><td>
{$LANG.login.1} :</td><td><input name="password" type="password" maxlength="32"></td>
</tr>
<tr>
<td><input type="submit"></td><td> <input type="reset"></td>
</tr>
</table>

En ligne 5, on va retrouver l'affichage du libellé « login », et en ligne _, le libellé « mot de passe », sur cet écran de saisie du login/mot de passe.

Les cas particuliers

Cette gestion s'applique bien à des libellés courts, ceux d'un formulaire ou les entêtes de tableaux. Par contre, c'est beaucoup moins adapté à une documentation ou à des pages contenant beaucoup de texte.

On va alors utiliser un autre mécanisme, toujours basé sur notre variable $language.

On veut créer une documentation, qui pourra être disponible dans plusieurs langues.

Dans le dossier doc, on crée un dossier par fichier de langue, par exemple fr_FR, ou en_US...

la page index.php, qui va permettre d'accéder à cette documentation, va simplement inclure le chemin correspondant au dossier de langue. Voici un exemple d'intégration dans smarty :

<?php
$handle = @fopen("doc/".$language."/".$t_module["param1"], "r");
$doc = "";
if ($handle) {
while (!feof($handle)) {
$buffer = fgets($handle, 4096);
$doc .= $buffer;
}
fclose($handle);
$smarty->assign("doc",$doc);
$smarty->assign("corps","documentation/index.html");
}
?>

En ligne 2, $t_module[« param1 »] contient le nom de la page html à afficher. On charge ainsi la page html doc/fr_FR/essai.html (par exemple).

Les lignes 4 à 9 permettent de lire le contenu du fichier. La ligne 10 assigne le contenu de la documentation à la variable $doc de smarty, et la ligne 11 indique à l'application qu'elle doit charger le template documentation/index.html. Ce template va contenir au minimum la ligne suivante :

{$doc}

qui affichera le contenu du fichier html précédemment chargé.

Les pièges à éviter...

Il faut penser son application dès le départ : si c'est plus fastidieux lors de la création des écrans de saisie, ce n'en est que plus facile en terme de maintenance.

Il faut également penser au javascript, qui doit, lui aussi, être internationalisé (selon le même principe que pour la gestion de la documentation), faute de quoi on aura des boites de dialogue qui risquent d'être assez peu compréhensibles...

La documentation des classes et des fonctions

Le plus souvent, on n'imprime pas la documentation technique de l'application (les classes et leurs méthodes, les paramètres...). Par contre, dès lors que l'on cherche une information, il est utile de pouvoir s'y retrouver rapidement.

Dans cette optique, Sun Microsystems a développé un outil permettant de générer une documentation d'API en format HTML depuis les commentaires présents dans un code source en Java. Javadoc est le standard industriel pour la documentation des classes Java. La plupart des IDEs génèrent automatiquement le javadoc HTML.

Les tags Javadoc7

Les développeurs utilisent certains styles de commentaire et des tags Javadoc quand ils documentent un code source. Un bloc de commentaire java commençant par /** deviendra un bloc de commentaire Javadoc qui sera inclus dans la documentation du code source. Un tag Javadoc commence par un @. Quelques tags sont donnés dans le tableau suivant :

Exemple

Un exemple d'utilisation de Javadoc pour documenter une méthode :

/**

* Valide un mouvement de jeu d'Echecs.

* @param leDepuisFile File de la pièce à déplacer

* @param leDepuisRangée Rangée de la pièce à déplacer

* @param leVersFile File de la case de destination

* @param leVersRangée Rangée de la case de destination

* @return vrai(true) si le mouvement d'échec est valide ou faux(false) si invalide

*/

boolean estUnDéplacementValide(int leDepuisFile, int leDepuisRangée, int leVersFile, int leVersRangée)

{

...

}

Un exemple d'utilisation de Javadoc pour documenter une classe :

/**
* Classe de gestion d'étudiant
* @author John Doe
* @version 2.6
*/
public class Etudiant
{
...
}

En PHP...

Le PHP a conservé ces règles de rédaction de la documentation, et utilise les mêmes TAG de base.

Par défaut, Eclipse prend en charge la gestion de la documentation des classes et méthodes ; il suffit de taper /** avant une classe ou une méthode pour que les paramètres de base soient pré-rédigés.

Il existe de nombreux outils qui permettent la génération de la documentation PHP, dont les plus connus sont phpdocumentor8 et doxygen9 (dont un plugin eclipse existe10).. Ce dernier reprend les tags Javadoc, tandis que phpdocumentor propose plus de tags :

Les tags de PHPDOCUMENTOR11

Tout développeur doit impérativement documenter toutes les classes, méthodes et fonctions qu'il rédige, et décrire également, selon le même mécanisme, toute page php générée.

Les balises à utiliser, à minima, sont les suivantes : @author, @param, @return, et @copyright pour les fichiers php.

Cette documentation ne se substitue pas à la documentation d'implémentation ou au manuel utilisateur... Elle peut également être complétée par une documentation générale décrivant le fonctionnement global de l'application.

Gérer les différentes versions de l'application

Avec des applications en mode « client lourd », comme les traitements de textes, une nouvelle version se traduit par l'exécution d'un programme de mise à jour. Avec les applications web, la mise à jour s'effectue sur le serveur directement.

En général, les versions successives des applications sont stockées dans un dossier, dont le nom contient le numéro de version : smarty-2.6.26, par exemple. Sur un serveur web, plutôt que d'accéder à une application en décrivant le chemin complet (par exemple, http://monserveur.masociete.com/monappli-1.0.1/), il est fréquent de mettre en place une redirection et d'accéder à l'application par un lien direct : http://monappli.masociete.com.

Cette redirection s'effectue en deux étapes :

    • d'une part, une entrée DNS est créée (un nom de site web est déposé),
    • d'autre part, un alias est créé sur le serveur web.

Avec Apache, le fichier contenant l'alias a souvent la forme suivante :

    1. <VirtualHost *:80>
    2. ServerName monappli.masociete.com
    3. DocumentRoot /var/www/html/monappli-1.0.1
    4. </VirtualHost>

Lors de la mise en production de la nouvelle version, il est alors possible de modifier le fichier d'alias pour le faire pointer vers le dossier qui la contient, par exemple monappli-1.1.0 : il suffit simplement de changer la directive DocumentRoot.

Cela permet ainsi de disposer de plusieurs versions sur le serveur, mais seule celle qui est en production est accessible par les internautes.

Dans un certain nombre de cas, les administrateurs systèmes préfèrent stocker l'application dans une arborescence différente de celle de base du serveur, par exemple /var/lib plutôt que /var/www. Cela limite les risques d'accès indésirables.

L'autre approche, sous Linux, consiste à créer un lien vers le dossier contenant la version :

ln -s /var/www/monappli-1.0.1 /var/www/monappli

L'accès à l'application sera réalisé en pointant vers monappli. Lors du changement de version, il suffit de supprimer le lien, puis de le recréer vers la nouvelle version.

Les avancées du HTML et du JAVASCRIPT

Le langage HTML a été créé pour afficher simplement des informations, et sa grande force était de promouvoir la notion de lien hypertexte, à la base du « surf ».

Néanmoins, ces possibilités étaient, au départ, très limitées, les interface de saisie ou d'affichage étant très frustres.

Heureusement, HTML a été complété par de nouvelles fonctionnalités, comme la gestion des blocs ou les feuilles de style CSS (Cascading Style Sheets, feuilles de style en cascade, en français), et un moteur d'exécution de code, qui fonctionne sur le navigateur, a été rapidement implémenté, le Javascript.

Grâce à ces innovations, les interfaces web ont gagné en qualité, et sont même capable de rivaliser avec nombre d'applications développées en client lourd : il existe aujourd'hui des applications de gestion de courrier électronique qui gèrent le clic droit de la souris, le glisser-déplacer... comme le font les applications traditionnelles comme Thunderbird, par exemple.

Quel que soit l'outil mis en place du côté du navigateur, il ne faut pas perdre de vue que le programmeur, de par la conception même des applications web, n'a pas la possibilité de maîtriser complètement l'information qui est transmise. Ainsi, si des contrôles de cohérence des données peuvent être réalisés en Javascript sur le navigateur, ils devront impérativement être rejoués sur le serveur pour s'assurer que l'utilisateur n'a pas contourné les tests réalisés.

Regrouper les pages action en une seule

Prenons l'affichage de la liste des bénéficiaires d'un dossier. L'application devra permettre les opérations suivantes :

    • afficher la liste ;
    • afficher le détail d'un bénéficiaire ;
    • passer en mode modification avec l'affichage d'un formulaire ;
    • enregistrer les modifications ;
    • supprimer une fiche.

Rien que pour gérer cet aspect là du programme, une première approche nécessite la création de cinq pages PHP :

    • BeneficiaireListe.php
    • BeneficiaireDetail.php
    • BeneficiaireModif.php
    • BeneficiaireEcrire.php
    • BeneficiaireSupprimer.php

Nous allons créer simplement une seule page, beneficiaire.php, qui permettra de tout gérer.

Auparavant, nous allons définir trois fonctions (dataRead, dataWrite et dataDelete) qui nous seront utiles :

function dataRead($dataClass, $id, $smartyPage, $idParent=null) {
global $smarty;
if ($id > 0) {
$data=$dataClass->lire($id);
/*
* Gestion des valeurs par defaut
*/
} else {
$data=$dataClass->getDefaultValue($idParent);
}
/*
* Affectation des donnees a smarty
*/
$smarty->assign("data",$data);
$smarty->assign("corps",$smartyPage);
return $data;
}
function dataWrite ($dataClass, $data) {
global $message, $LANG, $module_coderetour;
$id = $dataClass->write($data) ;
if (strlen($message)>0) $message.='<br>';
if ($id > 0) {
$message .= $LANG["message"][5];
$module_coderetour = 1;
} else {
/*
* Mise en forme du message d'erreur
*/
$message.=formatErrorData($dataClass->getErrorData());
$message.=$LANG["message"][12];
$module_coderetour = -1;
}
return ($id);
}
function dataDelete($dataClass, $id) {
global $message, $LANG, $module_coderetour;
if (is_numeric($id) && $id > 0) {
if (strlen($message)>0) $message.='<br>';
$ret=$dataClass->supprimer($id);
if ($ret>0) {
$message= $LANG["message"][4];
$module_coderetour=2;
}else{
/*
* Mise en forme du message d'erreur
*/
$message=formatErrorData($dataClass->getErrorData());
$message.=$LANG["message"][13];
$module_coderetour=-1;
}
}
return($ret);
}

Ces trois fonctions travaillent avec ObjetBDD, qui contient notamment les fonctions suivantes :

- lire : permet de lire un enregistrement ;

- write : permet d'écrire un enregistrement ;

- supprimer permet de supprimer un enregistrement ;

- getErrorData : retourne les messages d'erreur, le cas échéant.

Ces fonctions permettent de préparer également les affichage réalisés avec le moteur de templates Smarty. Les données à afficher sont stockées dans le tableau $data, la page à afficher est déclarée dans $corps, et sera traitée grâce à la balise Smarty {include file=$corps}.

Notre page beneficiaire.php va pouvoir ressembler à ceci :

<?php
include_once 'modules/example/beneficiaire.class.php';
$dataClass = new Beneficiaire($bdd,$ObjetBDDParam);
$id = $_REQUEST["idBeneficiaire"];
switch ($t_module["param"]) {
case "list":
/*
* Display the list of all records of the table
*/
$smarty->assign("data", $dataClass->getListe());
$smarty->assign("corps", "beneficiaire/beneficiiaireList.tpl");
break;
case "display":
/*
* Display the detail of the record
*/
$data = $dataClass->lire($id);
$smarty->assign("data", $data);
$smarty->assign("corps", "beneficiaire/beneficiaireDisplay.tpl");
break;
case "change":
/*
* open the form to modify the record
* If is a new record, generate a new record with default value :
* $_REQUEST["idParent"] contains the identifiant of the parent record
*/
dataRead($dataClass, $id, "beneficiaire/beneficiaireChange.tpl", $_REQUEST["idParent"]);
break;
case "write":
/*
* write record in database
*/
dataWrite($dataClass, $_REQUEST);
break;
case "delete":
/*
* delete record
*/
dataDelete($dataClass, $id);
break;
}
?>

Ces quelques lignes permettent de traiter les événements suivants (décrits dans le tableau $t_module["param"] :

- list : affiche la liste des enregistrements (on peut le compléter avec un module de recherche, le cas échéant) ;

- display : affiche le détail d'une fiche ;

- change : affiche le formulaire de modification ;

- write : écrit les données en base de données ;

- delete : supprime un enregistrement.

Les fonctions retournent un code qui permettra d'enchaîner vers un autre module, en fonction du résultat du traitement.

Chaque module peut disposer de droits différents, ici gérés ailleurs dans le contrôleur du framework utilisé.

Générer des menus avec les feuilles de styles

Il y a quelques années, gérer les menus dans une page HTML était complexe. De nombreux scripts écrits en Javascript permettaient de les générer, avec fenêtres popups et effets variés.

Aujourd'hui, la solution la plus simple (une fois qu'elle est mise en place) consiste à s'appuyer sur les feuilles de styles CSS.

Le CSS (Cascading Style Sheet, feuilles de style en cascade) est un langage qui permet de décrire l'affichage de documents HTML ou XML, par l'intermédiaire de styles définissant comment l'information doit être présentée. Nous allons voir comment gérer un menu en s'appuyant sur les mécanismes que le CSS propose.

Notre menu va être réalisé en préparant une liste non ordonnée (balise html <ul>). Nous allons prendre comme exemple le menu général de l'application prototypePhp (html://prototypephp.sourceforge.net). Voici à quoi ressemble le menu une fois formaté :

Ce menu a comme caractéristique de proposer quatre grande rubriques principales, à savoir Gestion, Administration, Déconnexion, Aide. Deux sous-menus sont disponibles sous les rubriques Administration et Aide.

Ces menus sont constitués de listes HTML imbriquées. Ces listes (listes non ordonnées) sont créées avec les balises suivantes :

    • <ul>, qui délimite la liste
    • <li>, qui délimite chaque item de la liste.

Les sous-menus sont réalisés en intégrant une nouvelle liste dans une balise <li> :

  <link rel="stylesheet" href="CSS/proto.css" type="text/css">
<div class="menu">
<ul>
<li id="gestion"><a href="index.php?module=gestion" title="Opérations de gestion">Gestion</a></li>
<li id="administration"><a href="index.php?module=administration" title="Administration de l'application">Administration</a>
<ul>
<li id="loginliste"><a href="index.php?module=loginliste" title="Liste des logins - identification via la base de données">Liste des comptes</a></li>
<li id="gestiondroits"><a href="index.php?module=gestiondroits" title="Gestion des droits d'accès aux modules de l'application">Gestion des droits</a></li>
</ul>
</li>
<li id="identdisconnect"><a href="index.php?module=identdisconnect" title="Déconnexion de l'application">Déconnexion</a></li>
<li id="docindex"><a href="index.php?module=docindex" title="Utiliser PrototypePHP">Aide</a>
<ul><li id="docinstallation"><a href="index.php?module=docinstallation" title="Installer PrototypePHP">Installation</a></li>
<li id="docdroits"><a href="index.php?module=docdroits" title="Gérer les droits dans l'application">Gestion des droits</a></li>
<li id="docmodulelecture"><a href="index.php?module=docmodulelecture" title="Créer un module pour lire ou écrire un enregistrement">Module de lecture-écriture</a></li>
<li id="docparammodule"><a href="index.php?module=docparammodule" title="Configurer le module dans le fichier xml">Paramétrage du module</a></li>
<li id="docclasseaccess"><a href="index.php?module=docclasseaccess" title="Créer la classe permettant d'accéder à une table">Classe d'accès aux données</a></li>
<li id="docgestiondoc"><a href="index.php?module=docgestiondoc" title="Gérer les pages de la documentation">Gestion de la documentation</a></li>
<li id="docinternationalisation"><a href="index.php?module=docinternationalisation" title="Gestion des différentes langues, internationalisation de l'application">Langues</a></li><li id="docdescriptionorganisation"><a href="index.php?module=docdescriptionorganisation" title="Organisation de PrototypePHP">Structure</a></li>
<li id="apropos"><a href="index.php?module=apropos" title="A propos de prototypePHP">A propos</a></li>
</ul>
</li>
</ul>
</div>

Et voici ce qui serait affiché, si aucune feuille de style n'était définie :

Tout l'enjeu est maintenant d'afficher notre liste pour la transformer en menu dynamique.

Nous l'avons vu, notre menu commence par une balise <div>, qui définit la classe menu. Cette classe va être déclarée dans notre feuille de style proto.css :

div.menu {
border: #999999 1px solid;
background: #ccffcc;
font-size: 12px;
padding:0.5em;
margin-left: 15%;
margin-right: 15%;
}
div.menu:after {
content: "";
clear: both;
display: block;
}

Les arguments indiqués permettent de définir une bordure (border), la couleur de fond (background), la taille de la police (font-size), l'espace intérieur entre la marge et le texte (padding), et les marges gauches et droites (margin-left, margin-right), le menu n'occupant, dans cet exemple, que 70 % de la largeur de l'écran du navigateur.

La balise after permet de définir que l'affichage de ce qui suit la balise <div> sera réalisée dans un bloc, et les hauteurs recalculées.

La structure de notre menu ressemble à ceci, une fois enlevé le texte affiché :

<ul>
<li>
<ul>
<li> </li>
<li> </li>
</ul>
</li>
<li> </li>
</ul>

Le menu principal doit être horizontal, les sous-menus seront verticaux. Nous allons utiliser les capacités d'enchaînement du CSS pour décrire le comportement de chaque balise. Nous allons combiner deux mécanismes, l'enchaînement parent>enfant, et les sélecteurs descendants. Une balise enfant est définie par le symbole >, les sélecteurs descendants étant simplement séparés par un espace du sélecteur précédent.

Voici l'ensemble des éléments de la feuille de style utilisés pour afficher notre menu :

div.menu > ul {
list-style-type: none;
padding: 0;
cursor: pointer;
margin: 0 0 0 1em;
}
div.menu > ul span {
margin: 0;
padding: 0 1em 2px 1em;
}
div.menu > ul > li {
margin-top: -2px;
float: left;
}
div.menu > ul li:hover span {
background: #FFFFFF;
color: #ccffcc;
}
div.menu > ul li ul {
border: 1px solid;
border-color: #666;
margin: -1px 0 0 2px;
padding: 1px;
width: 10em;
height: auto;
background: #FFFFFF;
color: #ffffff;
list-style-type: none;
position: absolute;
display: none;
}
div.menu > ul li:hover ul {
display: block;
}
div.menu > ul li ul li {
margin: 0;
padding-left: 1px;
background: #ccffcc;
border-left: solid 1em #CCC;
color: #ffffff;
}
div.menu a:link, div.menu a:visited, div.menu a:active {
color: #000000;
display: block;
margin: 0;
padding: 2px 1em 2px 1em;
}
div.menu a:hover {
background: #FFFFFF;
color: #666666;
}

Le code permettant d'extraire les informations nécessaires pour gérer le menu

/**
* Fonction lisant l'arborescence sur 2 niveaux du fichier xml
* Lecture depuis la racine du fichier xml, des noeuds de niveau 1
* et des attributs associes
*
* @param string $racine
* @return array
*/
function lireGlobal() {
$root = $this->dom->getElementsByTagName($this->dom->documentElement->tagName);
$root = $root->item(0);
$noeuds = $root->childNodes;
$g_module = array();
foreach ($noeuds as $noeud){
// Exclusion du modele
if ($noeud->hasAttributes()&&$noeud->tagName<>"model") {
foreach ($noeud->attributes as $attname=>$noeud_attribute) {
$g_module[$noeud->tagName][$attname] = $noeud->getAttribute($attname);
}
}
}
return $g_module;
}
/**
* Fonction preparant un tableau multi-niveaux, contenant tous les items
* necessaires pour generer un menu a partir du fichier xml.
* Gere 2 niveaux de menu
* Le tableau recupere doit ensuite etre trie (via ksort), et les droits
* verifies (menuloginrequis et menudroits)
*
* @return array
*/
function genererMenu() {
$gmenu = array();
foreach ($this->g_module as $key => $value ) {
if (isset($value["menulevel"])) {
// Recuperation des informations sur le menu
$menu = array();
// print_r($value);
$menu["menuvalue"] = $value["menuvalue"];
$menu["module"] = $key;
if (isset($value["menudroits"]))
$menu["menudroits"] = $value["menudroits"];
if (isset($value["menuloginrequis"]))
$menu["menuloginrequis"] = $value["menuloginrequis"];
$menu["menutitle"] = $value["menutitle"];
// Recherche si on est en menu principal ou secondaire
if ($value["menulevel"]==0) {
foreach ($menu as $key1 => $value1) {
$gmenu[$value["menuorder"]][$key1]=$value1;
}
} else {
$gmenu[$value["menuparent"]][$value["menuorder"]]=$menu;
}
}
}
return $gmenu;
}

La première fonction, de la ligne 9 à la ligne 24, va transformer le fichier xml en tableau à deux niveaux. La seconde fonction, à partir de la ligne 35, va récupérer les informations concernant les menus dans le premier tableau généré, et les organiser pour décrire les menus et les sous-menus. Il ne restera alors qu'à générer le menu lui-même.

Le code générant la liste des menus

<?php
/**
* Preparation automatique du menu a partir du fichier xml
*/
$menuarray = $navigation->genererMenu();
//print_r($menuarray);
// Tri du tableau selon les cles
ksort($menuarray);
$menu = "<ul>";
foreach ($menuarray as $key => $value){
$ok = true;
// Verification des droits
if ($value["menuloginrequis"]==1 && !isset($_SESSION["login"])) $ok = false;
if (strlen($value["menudroits"])>1&& !$phpgacl->acl_check($GACL_aco,$value["menudroits"],$GACL_aro,$_SESSION["login"]))
$ok = false;
// Preparation menu niveau 0
if ($ok) {
$menu .= '<li id="'.$value["module"].'"><a href="index.php?module='.$value["module"].'" title="'
.$LANG["menu"][$value["menutitle"]].'">'.$LANG["menu"][$value["menuvalue"]].'</a>';
$flag=0;
// Gestion sous-menu
ksort($value);
foreach($value as $key1 => $value1){
if (is_array($value1)) {
$ok1 = true;
// Verification des droits
if ($value1["menuloginrequis"]==1 && !isset($_SESSION["login"])) $ok1 = false;
if (strlen($value1["menudroits"])>1&& !$phpgacl->acl_check($GACL_aco,$value1["menudroits"],$GACL_aro,$_SESSION["login"]))
$ok1 = false;
if ($ok1) {
// Preparation du sous-menu
if ($flag==0) {
$menu .="<ul>";
$flag = 1;
}
$menu .= '<li id="'.$value1["module"].'"><a href="index.php?module='.$value1["module"].'" title="'
.$LANG["menu"][$value1["menutitle"]].'">'.$LANG["menu"][$value1["menuvalue"]].'</a></li>';
}
}
}
if ($flag == 1) $menu .="</ul>";
$menu .="</li>";
}
}
$menu .="</ul>";
?>

Pour chaque niveau de menu, on trie le tableau (lignes 8 et 23), puis on vérifie les droits (lignes 26 à 30 et 37 à 39). A noter que ce script inclut l'internationalisation des libellés (variables $LANG).

Il ne restera plus alors qu'à afficher la variable $menu, qui contient le code HTML d'affichage du menu...

L'intégration dans la page HTML

Dans la page générale de l'application, on insère maintenant les lignes qui vont permettre de générer le menu, puis d'assigner la variable $menu à SMARTY :

/**

* Gestion du menu

*/

include ("menu/menu.inc.php");

$smarty->assign("menu",$menu);

Et, dans la page SMARTY :

<div class="menu">{$menu}</div>

Imprimer des documents depuis l'application

Les applications informatiques stockent les informations sous forme numérique. Il y a toutefois des moments où il devient nécessaire d'imprimer un courrier, un contrat, une facture... L'impression depuis un navigateur n'est en général pas de grande qualité, la mise en forme ne pouvant être vraiment respectée.

Lors de la réalisation d'impressions formatées, deux cas se présentent. Soit le document n'a pas vocation à être modifié, soit il doit être retravaillé dans un éditeur de texte. Par exemple, un contrat ou un courrier peuvent être préparés depuis l'application, celle-ci fournissant les informations techniques issues de la base de données. Ils seront ensuite complétés manuellement par l'utilisateur pour les adapter à un contexte particulier.

Dans le premier cas, l'impression est dans la plupart des situations réalisée en recourant au format PDF. Dans le second cas, l'application génère un fichier dans un format de traitement de texte, qui sera ensuite modifié manuellement.

Pour générer des fichiers PDF, deux classes sont actuellement largement répandues : fpdf12 et tcfpdf13. Pour générer des fichiers ODF, on pourra recourir à odtphp14 pour générer des fichiers ODT (traitement de texte), qui permet notamment de remplir des tableaux, ou OpenOffice_generation_document15 pour créer des fichiers ODS (feuille de calcul).

Quant à l'insertion de graphiques, si de nombreuses classes existent, très peu sont réellement opensource et libres de droits. Une des plus fréquentes est pChart16.

Les services Web

Dans une entreprise, il est rare qu'une application fonctionne de manière totalement autonome. Il est fréquent que la base d'identification des utilisateurs soit utilisée tant pour l'ouverture des sessions Windows que pour l'accès aux différents logiciels, et que les droits d'accès soient gérés de façon unique.

Certains processus peuvent également être mutualisés entre différents logiciels. Cela pourrait être le cas, par exemple, de la vérification de la validité d'un relevé d'identité bancaire ou d'une adresse postale : plutôt que de confier ce contrôle à chaque application, c'est une application unique au sein de l'entreprise qui serait chargée de cette tâche. La maintenance en est facilitée : toute erreur est corrigée pour l'ensemble des applications, toute évolution est immédiatement disponible.

Plusieurs techniques différentes sont utilisées pour faire dialoguer les serveurs et les applications entre eux. Parmi celles-ci, on retrouve CORBA, SOAP, et, plus récemment (bien qu'il s'agisse de la technologie originelle du web), REST.

La norme CORBA (Common Object Request Architecture) a été créée en 1992. Largement implémentée, elle a été utilisée pour faire dialoguer entre elles différentes applications, ou pour créer des applications à partir de de différentes briques bâties selon ce principe. Elle est surtout utilisée sur les gros systèmes.

SOAP (au départ, acronyme de Simple Object Access Protocol – protocole simple d'accès à des objets), est devenu un standard du W3C en 2003, dans sa version 1.2. Ce protocole est basé sur des échanges réalisés sur le web, tant en HTML qu'en SMTP, les données transitant dans un format XML normalisé. Le protocole prévoit également la mise en œuvre d'annuaires de services.

PHP5 intègre aujourd'hui des fonctions natives de gestion des échanges, grâce à l'extension SOAP, qui propose différentes classes natives pour gérer les échanges.

Comme les échanges de données s'effectuent en XML, il est parfois reproché à SOAP d'être un peu trop « verbeux », surtout si le volume de données à échanger est important.

REST (Representational State Transfert) n'est pas une norme à proprement parlé, mais plutôt un concept architectural. Il part du principe que toute information est une ressource (une image, une base de données, mais également le détail d'une fiche...). Il s'agit non seulement d'utiliser le protocole HTTP pour accéder à une ressource, mais également pour en créer une nouvelle, la modifier ou la supprimer. De nombreuses sociétés proposent des services web utilisables selon ce protocole : c'est le cas par exemple de Yahoo, qui fournit un service de géo-localisation pour les Etats-Unis17.

REST est basé sur le principe que « tout est ressource », et que toute information peut être obtenue par l'intermédiaire d'une requête HTTP.

Depuis un navigateur, l'appel à une ressource, par exemple une image, va être réalisé par l'intermédiaire d'une commande HTTP GET. Il en est de même pour récupérer le détail d'une fiche ou une liste de personnes.

S'il doit réaliser une mise à jour ou une création, le programmeur va très certainement envoyer son formulaire avec la commande POST vers le serveur.

En fait, HTTP gère d'autres commandes. Quatre sont utilisées généralement avec REST :

    • GET : lit une information (une fiche, une liste...) ;
    • POST : crée une nouvelle ressource (une nouvelle fiche, par exemple) ;
    • PUT : met à jour une ressource (modification d'une fiche) ;
    • DELETE : supprime une ressource (suppression d'une fiche).

Il est ainsi possible d'échanger des messages entre deux applications en utilisant ce mécanisme. En reprenant l'exemple précédent, pour obtenir notre racine carrée, il suffirait d'envoyer un message HTTP qui pointerait vers une page dédiée à cet effet, par exemple getRacineCarree.php, grâce à la classe PHP HttpRequest().

Cette page getRacineCarree.php devrait alors analyser la requête PHP, et renvoyer ensuite le résultat du traitement, comme le ferait n'importe quel service web.

Cette technologie est de plus en plus utilisée, en raison notamment de sa simplicité, même si le codage est un peu plus complexe (des exemples de classes tant pour le côté serveur que le côté client foisonnent sur le web).

Quelques conseils d'ergonomie18

Une application web, ce n'est pas un site... Un site web doit être « joli » et accrocheur, pour conserver le visiteur. Une application web n'a besoin que d'être esthétique. Par contre, elle doit être efficace, pour permettre aux utilisateurs de travailler rapidement. Elle doit également être intuitive, même s'il est possible de former les utilisateurs.

D'une manière générale, il vaut mieux conserver l'ergonomie générale du web et éviter de copier l'interface Windows. Cela peut se traduire ainsi :

    • une page web est toujours déconnectée, peut être abandonnée, ou on peut y accéder par l'intermédiaire d'un raccourci. Cela implique que les informations qui sont saisies doivent être conservées, quel que soit le contexte, et notamment lorsque la saisie impose de nombreuses pages successives. De même, il est impossible de prévoir à l'avance par où va passer l'utilisateur pour accéder à une information ;
    • l'ergonomie générale doit être de la forme : critères de sélection, liste de résultats, accès au détail, modification, suppression, ou rajout d'un élément ;
    • privilégiez le mode « chemin de fer » quand c'est possible, c'est à dire une présentation qui serait de la forme :

Critères > liste > détail fiche > modification

    • ce qui permet à l'utilisateur de savoir où il se trouve, et de revenir facilement à une situation antérieure. On retrouve d'ailleurs là les mécanismes présentés dans les diagrammes de séquence dans ce document.
    • Utilisez de préférence les formulaires en mode « GET », pour que la fonctionnalité RETOUR du navigateur puisse fonctionner. Ne réservez le mode « POST » que pour les enregistrements de fiches, et dès lors que le retour arrière peut avoir des répercussions gênantes dans l'application. De toute façon, dans ce cas de figure, prévoyez un mécanisme qui interdit le fait de relancer la même opération (l'appui sur la touche F5, par exemple, pourrait dupliquer une information en base de données) ;
    • ne multipliez pas les fenêtres : au pire, utilisez des fenêtres popup pour sélectionner des informations particulières concernant la fiche principale. Seule exception : les pages d'aide, qui ont tout intérêt à être séparées de l'application ;
    • en mode saisie, pensez que l'utilisateur va manipuler son clavier. Vérifiez l'ordre de saisie des champs (au besoin, vous pouvez les renuméroter avec l'attribut « tabindex »).
    • Définissez le champ qui recevra le focus (attribut « autofocus »).
    • Complétez les listes déroulantes avec des saisies au clavier, plus rapides... Ainsi, plutôt que de chercher dans une liste déroulante une commune, autant pouvoir la saisir au clavier, et l'associer ensuite à la fiche correspondante dans la base de données. Il est également possible de créer une zone de texte accessible en saisie, qui alimentera automatiquement une liste déroulante (script javascript associé à une interrogation Ajax) ;
    • Une page, une information...
    • utilisez tout l'écran : ne mettez pas de marge à gauche ou à droite. Si l'utilisateur dispose d'un grand écran, il ne pourra pas l'utiliser, ce qui serait dommage... Et s'il a un petit écran, ce que vous allez lui proposer pourrait même devenir illisible ;
    • si le scrolling vertical est un grand classique, l’ascenseur horizontal est à bannir ;
    • Préférez les liens aux boutons : leur symbolique peut échapper à l'utilisateur occasionnel. Vous pouvez également combiner les deux ;
    • Pour les saisies de dates, utilisez une zone de texte couplée à un calendrier. Ne découpez pas la date en trois zones, c'est une catastrophe d'un point de vue ergonomique, même si certains outils, comme Smarty, le propose ;
    • Pour les saisies multiples (plusieurs lignes rattachées à un dossier), soit vous réalisez la saisie dans une page dédiée, soit vous utilisez un formulaire situé sous la liste, à condition qu'il n'y ait pas trop d'informations à saisir ;
    • Évitez d'utiliser trop de Javascript : conservez simplement le code utile, évitez les fioritures ;
    • Conservez les liens hypertextes soulignés, avec le code couleur par défaut bleu/violet (ou noir/gris) : l'utilisateur ne sera pas dérouté, et pourra identifier immédiatement les liens dans la page. De plus, s'il traite une liste de dossiers, il verra au premier coup d'œil ceux qu'il aura déjà traités, la couleur changeant dès qu'un lien a été activé ;
    • ne touchez pas aux tailles de caractères (sauf cas particulier, comme le pied de page, par exemple). L'utilisateur doit pouvoir augmenter ou diminuer la taille d'affichage, en fonction de son poste de travail, de sa vue, etc ;
    • si vous utilisez des onglets, considérez que les onglets correspondent à une page web indépendante. Ne gérez pas les onglets dans une seule page, qui contiendrait tout le contenu du dossier (code DHTML). Chaque onglet doit pouvoir être enregistré de façon indépendante. L'utilisateur doit également savoir qu'il doit enregistrer chaque onglet, avant de passer au suivant (bouton « enregistrer » propre à l'onglet) ;
    • Si vous le pouvez, utilisez un graphiste web pour vous aider à créer votre modèle d'application : les bons développeurs ne sont pas forcément des bons graphistes...

On pourrait continuer longtemps mais, pour résumer : faites simple, évitez les fioritures ou les « astuces », mettez vous à la place de l'utilisateur. Pensez également que tout ce qui est complexe à écrire est complexe à maintenir.

1http://subversion.apache.org/

2http://git-scm.com/

3http://sourceforge.net

4http://www.redmine.org/

5http://fusionforge.org/

6http://www.tuleap.com/

7Source : http://fr.wikipedia.org/wiki/Javadoc

8http://www.phpdoc.org/

9http://www.stack.nl/~dimitri/doxygen/

10Pour installer le plugin : http://www.irisa.fr/bunraku/OpenMASK/EclipseDev/ch02s06.html

11http://en.wikipedia.org/wiki/PHPDoc – traduction de l'auteur

12 http://www.fpdf.org/

13http://www.tecnick.com/public/code/cp_dpage.php?aiocp_dp=tcpdf

14http://www.odtphp.com)

15http://membres.lycos.fr/tafelmak/,

16http://pchart.sourceforge.net/)

17http://developer.yahoo.com/maps/rest/V1/geocode.html

18La plupart des informations sont issues du livre blanc : Conception d'applications web : efficacité et utilisabilité, édité par SMILE (http://www.smile.fr/Livres-blancs/Culture-du-web/Conceptions-d-applications-web)