Chapitre 43. Développement de NOALYSS
43.1. Intro
NOALYSS a déjà plus de 22 ans , il est né bien avant la naissance des frameworks pour PHP, il était déjà fort avancé quand sont apparus symphony, Laravel, DoctorORM…
En 2002, il était développé avec vi ou emacs, sous CVS pour la gestion des versions,en PHP3 [1] parce qu’il n’y avait pas d’outils professionnels pour travailler avec PHP , même pas un débuggueur, ou des suites de tests.
Au début, les programmeurs développaient la totalité d’un produit , quand le projet grossissait , ils regroupaient des fonctions dans des librairies.
Plus tard, ils vont non seulement réutiliser les fonctions qu’ils ont déjà développées mais ils adoptent aussi une méthode de développement, pour le nommage des fonctions, les conventions sur le code,… afin d’avoir un code homogène.
Et ces librairies, scripts , conventions finissent par devenir un cadre de travail (framework) qui permet d’avoir un code homogène et plus facilement abordable pour les nouveaux venus sur le projet.
Aujourd’hui, les développements en PHP sont supposés utiliser les normes PSR0/4, ces normes et frameworks sont arrivées trop tard pour nous, cela imposerait une énorme réécriture de NOALYSS et aucun gain réel si on le faisait.
Le code est réécrit au fur et à mesure des corrections et de l’évolution du programme.
Cela explique que l’organisation des fichiers et la façon de travailler avec NOALYSS , ne ressemble pas aux frameworks existants, mais on essaie d’uniformiser et de moderniser le code au fur et à mesure.
43.2. Organisation des fichiers
- html
-
ce qui est appelable directement (modèle mvc)
- include
-
fichier inclu par html (*.inc.php)
- include/ajax
-
traitement pour les appels AJAX
- include/template
-
template , écran & code html
- include/lib
-
les classes librairies
- include/class
-
classes métiers
- include/database
-
mapping table, vues et object [ORM]
- include/export
-
fichier pour confection de documents qu’on envoie (zip , csv, pdf …)
- include/ext
-
plugin(alias extensions)
- include/sql
-
répertoire avec les scripts SQL pour les mises à niveau du programme
43.3. Cycle de développement : organisation des branches Git
Il existe toujours 2 branches :
- stable
-
branche stable que tout le monde peut utiliser en production,
- unstable
-
branche contenant tous les développements en cours, elle est réservée aux développeurs. Cette branche n’est pas à 100% testée et est fortement déconseillée en production.
|
|
les changements pour la base de données ne sont pas encore dans noalyss/include/sql mais dans noalyss/sql/upgrade.sql, il faut
exécuter ce script afin de pouvoir utiliser la branche unstable.
|
43.3.1. Correction et patch
En bref, faire une branche à partir de stable, corriger le bug et publier le correctif.
Procédure
Afin de soumettre un patch , vous devez d’abord "cloner" la base de données depuis https://gitlab.com/noalyss
git clone https://gitlab.com/noalyss/noalyss
Ensuite vous créer votre propre branche à partir de unstable ou stable.
-
unstablesi c’est un développement pour la prochaine version, -
stablesi c’est une correction de bug (pour cette branche).
|
|
Les changements pour la base de données, vont toujours dans noalyss/sql/upgrade.sql pour les dossiers et dans noalyss/sql/ac-upgrade.sql pour account_repository
|
# Anonymement
git git@gitlab.com:noalyss/noalyss.git
git switch stable # ou unstable
git checkout -b mon_dev1
Corriger dans mon_dev1 , faites commit
puis simplement, générer les fichiers patchs que vous enverrez par email
git format-patch stable # OU unstable
Des fichiers patch seront générés, envoyez-les nous soit sur la mailing list (https://lists.nongnu.org/mailman/listinfo/noalyss-generale), soit sur le forum (https://forum.noalyss.eu) soit par email à dev-team@noalyss.eu
Ces patchs seront testés et intégrés dans la branche stable et unstable , éventuellement dans la branche "pre-release",
s’il y en a une.
43.3.2. Nouvelle version
Quand la branche unstable est suffisamment mature et testée pour devenir la prochaine version, on créera à partir d’elle une branche appelée pre-release-xx où xx est le prochain numéro de version.
Tous les correctifs sur stable seront aussi intégrés sur cette branche en plus d'`unstable`.
Quand la nouvelle version sera publiée, la branche sera intégrée dans stable , re-testée et publiée.
La branche pre-release sera alors effacée.
|
|
Les corrections et développement sur cette branche seront aussi portés vers unstable
|
43.4. Convention
43.4.1. Nommage
Les fichiers dans
include
sont destinés à être inclus et exécuter depuis le 1er script (route)
<action>.inc.php
include/ajax
Appelé depuis une procédure javascript , le nom de fichier est
ajax_<action>
43.4.2. include/database
mapping table, vues et object
<table>_SQL.class.php
43.4.3. include/template
template , écran & code html
<nom_class>-<nom-fonction>.php
43.4.4. Outil
Tout doit être documenté avec Doxygen https://www.doxygen.nl/
Tous les objets SQL doivent aussi être documentés https://www.postgresql.org/docs/current/sql-comment.html
La documentation doit être au format asciidoc , ou markdown dans le répertoire doc https://asciidoctor.org/
Utilisation de phpunit ou autre système (fichiers de test avec assert() ) pour faire des tests unitaires https://phpunit.de/index.html
43.5. Déboggage
Afin de débugger, il suffit dans le fichier noalyss/include/config.inc.php d’ajouter
define ("DEBUGNOALYSS",2);
Le niveau de déboggage est :
- 0
-
production, aucune erreur n’est affichée,
- 1
-
les erreurs et les avertissements sont affichés,
- 2
-
affichage comme le niveau 1 , mais en plus des informations sur la taille de l’écran (bande de couleur en haut), pour aider la mise au point des pages responsives , affichage des fichiers inclus, d’objets,…
Pour le niveau 2 , on utilisera la classe \Noalyss\Dbg
si on donne une valeur différente de 0 à DEBUGNOALYSS , en javascript on aura la variable
document.debug_noalyss=true, et donc on peut faire. Cependant , il vaut mieux être économe en plaçant des info de déboggage.
Il est recommandé de les supprimer après usage sauf si elles sont utiles pour aider des développements ultérieurs comme par exemple
pour indiquer quel est le fichier ou la fonction appelée.
if ( document.debug_noalyss) console.debug ('test')
/* ou */
document.debug_noalyss && console.debug ('test')
Affiche le nom du fichier inclus (à garder si possible)
\Noalyss\Dbg::echo_file(__FILE__);
Si DEBUGNOALYSS est supérieur à 1 , affiche le contenu de row dans un élément cacheable
if ( DEBUGNOALYSS>1) echo \Noalyss\Dbg::hidden_info("\$row", $row);
Si DEBUGNOALYSS est supérieur à 1, affiche l’utilisateur courant
\Noalyss\Dbg::echo_var(1,sprintf("current user is [%s]",$cn->get_value("select current_setting('noalyss.user_login')")));
\Noalyss\Dbg contient les différentes fonctions pour afficher, il existe aussi
la fonction tracedebug qui écrira dans un fichier (voir noalyss/lib/ac_common.php).
|
|
l’utilisateur connecté, est sauvé dans la mémoire de la base de données et peut de ce fait être utilisé dans des triggers, voir postgresql : current_setting |
43.6. Principe : développement en MVC
Le développement en MVC est un développement qui suit les concepts Modèle Vue Contrôle. Il en existe 2 versions. Ici, je ne parlerai que de la première.
En pratique, je crée 3 répertoires de base /html et /include et /include/template, le premier contient les « contrôles » le second les «vues» et le troisième les « modèles ».
43.6.1. Contrôle
Un contrôle est une page php, qui en fonction de l’action demandé va inclure la page «modèle» qu’il faut.
Dans noalyss, cette page est :
do.php pour toutes les demandes de pages
export.php pour toutes les impressions (PDF, CSV, …)
ajax_misc.php pour l’ajax.[2]
Exemple : la page contrôle contient
<?php
if ( !isset ($_REQUEST['do'])) exit();
$do=$_REQUEST['do'];
if ( $do == 'auteur' ) {
require_once('include/auteur.inc.php');
}
?>
L’explication est assez simple, si la page n’est pas appelée avec une variable do, donnée par un POST ou GET alors, le traitement s’arrête. Sinon en fonction de la valeur de la variable, il va inclure le fichier vue. Par habitude, mes fichiers vues ont toujours l’extension .inc.php.
Dans noalyss, le fichier do.php va chercher la vue à intégrer dans la base de données, ce qui améliore la sécurité mais aussi une grande souplesse pour les menus. Facile d’en ajouter, d’en retirer, de les renommer…
|
|
Puisque le fichier à inclure est dans la base de données, si votre profil ne l’inclus pas, alors vous avez pas du tout accès à ce menu, cela s’étend aux impressions, il est possible d’empêcher les impressions. Les menus et profils se configurent dans [C1MENU] |
43.6.2. Vue
Grâce à l’exemple vu dans Contrôle, vous avez compris ce qu’est le concept vue: c’est la présentation de votre action. Donc pour continuer l’exemple, dans le document include/auteur.inc.php, on aurait
require_once ('include/class_auteur.php');
if ( ! isset($_REQUEST['sdo']) ) exit();
$sdo=$_REQUEST['sdo'];
if ( $sdo == 'add' ) {
$auteur=new Auteur();
$auteur->display_form();
}
?>
Ici, comme dans contrôle, on vérifie qu’une action est bien demandée, si c’est la cas, on appelle un objet et on lui demande d’afficher quelque chose. Cet objet est ce qui est dans le concept MVC, un modèle. En fait, on dirait une espèce de sous-contrôle
43.6.3. Modèle
Eh oui, le dernier est donc le modèle. Ici ce sont deux classes, l’une va se charger de sauver, afficher, effacer les données et gère donc les relations avec la base de données et l’autre l’objet métier.
Le nom de ce fichier doit être
<nom de la classe>-<fonction>.php
Ce n’est pas toujours le cas, et cette réécriture se fait au fur et à mesure.
Il est vraiment fastidieux de construire une chaîne de
caractère contenant tout le code html, surtout quand on emploie aussi du
javascript et des tableaux. La solution que j’ai trouvé, je l’ai trouvé
en m’inspirant de l’idée de smarty. En fait, je crée un sous-répertoire
dans include appelé template, et dedans je mets le code HTML de ce
qui doit être affiché
<?php
class Auteur {
function display_form() {
ob_start();
include 'template/auteur-display_form.php';
$r=ob_get_contents();
$ob_clean();
return $r;
}
}
Ici on inclus le fichier, comme il est en PHP, il sera interprété par PHP, il contient surtout du code HTML et des balises PHP, on peut aussi manipuler la chaîne retournée avec les fonctions de chaînes comme str_replace, strcmp,... Ce qui donne à cette méthode encore plus de souplesse.
Il est important de respecter la règle suivante: un minimum d’HTML dans le PHP et un minimum de PHP dans l’HTML. Il faudra alors utiliser beaucoup de variables avant d’utiliser les templates, il faudra aussi résister à la tentation de mettre de l’html dans la variable, ce qui n’est pas toujours possible.
|
|
les variables sont définies en-dehors du fichier template. Les IDE avertissent que ces variables ne sont définies. il suffit d’ajouter un block commentaire pour supprimer cet avertissement |
<?php
/**
* @var $auteur string contient le nom de l'auteur
*/
|
|
toutes les variables doivent être commentées, sauf les variables en une seule lettre (utilisée principalement pour les boucles). |
43.7. La documentation
J’utilise Doxygen https://www.doxygen.nl/ afin de générer le code ce sont les TAGS brief, param… Cela me permet de générer une documentation; cette documentation me permet de développer plus vite et de mieux vérifier la qualité du code.
Voir le résultat ici https://wiki.noalyss.eu/doc/classes.html
Il est recommandé de toujours documenter les variables , les fonctions, les fichiers et les classes.
Le fichier qui va router les demandes (en général index.php ) doit contenir l’explication pour le développement [3]
<?php
// variable: $tot float total for the operation
$tot=0;
Quand une variable est héritée d’un autre fichier, à la suite d’un include ou require, afin que NetBeans (ou PhpStorm) n’indique pas une erreur.
<?php
/**
* @var $tot float total for the operation
*/
$tot=$tot+1;
43.8. Fichiers importants
Les fichiers à inclure sont dans la table menu_ref, cette table est accèdée à travers la table profile_menu qui décrit l’interface de l’utilisateur, ce qui automatiquement empêche un utilisateur d’accèder à un menu auquel il n’a pas accès.
Le répertoire include contient les fichiers à inclure, les noms de fichiers suivent la logique suivant :
- noalyss\include/constant.php
-
contient les variables globales, les constantes, l’autoloader.
- noalyss\include/config.inc.php
-
contient le paramétrage de NOALYSS, surtout l’accès aux bases de données.
- noalyss\include\ajax
-
Action ajax, le nom de fichier commence par ajax, contient le contenu de la variable op, c’est cette variable qui va indiquer le fichier à include.[4]
- fichier à inclure
-
le nom de fichier termine par inc.php,
- fichier ORM
-
correspondant à une table NOM_TABLE dans la base de données:class_NOM_TABLE_sql.class.php,
- fichier d’object
-
le nom se termine par .class.php
- noalyss/log
-
répertoire des logs, voir fonction record_log
- noalyss\include\template
-
ici vous avez les modèles, ce sont des fichiers contenant surtout du code HTML.
En résumé :
Les contrôleurs sont do.php, export.php, ajax_misc.php.
Pour les extensions (plugins) : do.php , ajax.php et extension.raw.php
Les modèles sont dans include/template et les vues sont dans le répertore include
43.9. La partie base de données
43.9.1. Les mises à jour
Depuis 2005, dans NOALYSS, il y a une table version qui contient les informations sur les patch de base
de données qui ont été appliqués. Quand le fichier do.php est exécuté,
il vérifie si DBVERSION contenu dans constant.php correspond à
la version actuelle.
<?php
/* Ficher do.php */
if (DBVERSION < dossier::get_version($cn))
{
echo '<h2 class="error" style="font-size:12px">' .
_("Attention: la version de base de donnée est supérieure à la version du programme, vous devriez
mettre à jour") . '</h2>';
}
if (DBVERSION > dossier::get_version($cn))
{
echo '<h2 class="error" style="font-size:12px">' . _("Votre base de données n'est pas à jour") . ' ';
$a = _("cliquez ici pour appliquer le patch");
$base = dirname($_SERVER['REQUEST_URI']) . '/admin/setup.php';
echo '<a hreF="' . $base . '">' . $a . '</a></h2>';
}
Dans le cas où DBVERSION n’est pas la valeur contenue dans la base de
donnée, do.php vous propose d’appliquer le patch sur vos bases de
données. Voici la fonction qui applique les patch SQL pour la base de
données. Ces fichiers sont dans noalyss/include/sql/patch.
<?php
/* fichier setup.php */
//----------------------------------------------------------------------
// Upgrade the folders
//----------------------------------------------------------------------
for ($e=0;$e < $MaxDossier;$e++) {
$db_row=Database::fetch_array($Resdossier,$e);
echo "<h3>Patching ".$db_row['dos_name'].'</h3>';
$name=$cn->format_name($db_row['dos_id'],'dos');
if ( $cn->exist_database($name)> 0 )
{
$db=new Database($db_row['dos_id'],'dos');
$db->apply_patch($db_row['dos_name']);
Dossier::synchro_admin($db_row['dos_id']);
} else
{
echo_warning(_("Dossier inexistant")." $name");
}
}
La fonction qui applique le patch est Database::apply_patch voir
class Database
Tous les patchs pour la base de données se trouve dans
/noalyss/include/sql/patch/ et sont numérotés dans l’ordre d’exécution.
Chaque patch commence par
begin;
et termine par
insert into version (val,v_description) values (..version.,'.description..');
commit;
Ainsi, si le script échoue, la mise à jour s’arrête et annule toute ce
qui a été fait dans ce script, la base de données
reste ainsi dans un état cohérent. Une fois l’erreur corrigée dans votre
base de données, ce script ainsi que ceux qui suivent seront appliqués
quand vous appelerez à nouveau noalyss/admin/setup.php (ou noalyss/html/install.php).
Le nombre dans la table version est le nombre du script moins un, exemple : le script upgrade199.sql, correspond à la 200 dans la table version
/* upgrade199.sql */
insert into version (val,v_description) values (200,'Widget and improve menu');
43.9.2. Accèder aux données : ORM
Pour accèder aux tables ou aux vues, pour les SQL complexes , nous créons une classe qui va hériter de Table_Data_SQL qui est dérivée de Data_SQL
et qui va nous permettre de faire la même chose en très peu de ligne de code.
Cette classe fournit entre-autres les fonctions suivantes qui peuvent être surchargée
- insert
-
pour insérer une ligne dans la base de données
- delete
-
pour insérer une ligne dans la base de données
- update
-
pour mettre à jour une ligne dans la base de données
- verify
-
à surcharger, vérifie que les données sont conformes
- setp
-
setter paramètre : nom de la colonne, valeur retourne l’objet
- getp
-
getter paramètre : nom de la colonne , retourne la valeur de cette colonne
- toString
-
affiche le contenu de l’objet
- from_array
-
transforme un tableau en un objet
- seek
-
recherche dans la table sur base d’une condition
- next
-
retourne l’objet suivant (après seek)
- check
-
contrôle des valeurs
Voici tout le code à taper par table, exemple pour la table stock_change.
Le fichier sera nommé table_sql.class.php , il sera dans un répertoire /database/ et la classe se nommera table_SQL
<?php
class Stock_Change_SQL extends Noalyss_SQL
{
// Le contructeur obligatoire
function __construct($p_id = -1)
{
// Façon dont les dates sont utilisées
$this->date_format="DD.MM.YYYY";
// Nom de la table
$this->table = "public.stock_change";
// nom de la clef primaire
$this->primary_key = "c_id";
// Structure de la table, à gauche le nom logique utilisable
// avec les getters/setters (setp/getp) et à droite le nom de la
// colonne
$this->name = array(
"id" => "c_id",
"c_comment" => "c_comment",
"c_date" => "c_date",
"tech_date"=>"tech_date",
"tech_user"=>"tech_user",
"r_id"=>"r_id"
);
// Type de données
$this->type = array(
"c_id" => "numeric",
"c_comment" => "text",
"c_date" => "date",
"tech_date"=>"date",
"tech_user"=>"text",
"r_id"=>"numeric"
);
// Les colonnes qui ne peuvent pas être changée ni par insert ni par
// update parce leurs valeurs sont données automatiquement
// exemple : la clef primaire qui est un numéro de séquence
//automatiquement donné
$this->default = array(
"c_id" => "auto",
"tech_date" => "auto"
);
global $cn;
parent::__construct($cn, $p_id);
}
}
43.9.3. Les tests unitaires
NOALYSS doit avoir des tests scriptés afin de ne pas devoir manuellement rejouer tous les scénarios.
Pour cela, il existe 3 méthodes utilisées.
La plus ancienne, ajouter une fonction function static test_me() dans les class . Cette fonction est appelée via le test.php
Ensuite, les scénarios : grâce à la variable LOGINPUT qui permet d’enregistrer toutes les requêtes (html, ajax ou export), dans un fichier dans le répertoire temporaire [5]
Et finalement, la plus récente avec PHPUNIT.
test-me
Les fonctions info et test_me qui sont très utiles lors des phases de déboggages. Je crée une simple page test-classe.php et j’appelle la classe; dans la fonction test_me (de la classe) je mets tout ce que je souhaite tester.
exemple :
<?php
static function test_me()
{
$cn=new Database(dossier::id());
$obj=new Periode($cn);
$obj->set_jrn(1);
$obj->display_form_periode();
}
|
|
ces tests sont ajoutés à NOALYSS uniquement pour tester les nouvelles fonctionnalités ou pour les corrections de bugs. |
Scenario
Vous pouvez enregistrer ce que vous soumettez, il faut dans
config.inc.php, ajouter
define ('LOGINPUT',true);
Ensuite il faut créer le fichier dans noalyss/html/authorized_debug , le contenu
n’a pas d’importance, ce fichier peut être vide.
Ensuite, il faut aller dans votre dossier de test et faire une action dans NOALYS
(une vente, un achat…), vous devez ouvrir le fichier test.php avec comme
paramètre le dossier (exemple
http://localhost/noalyss/html/test.php?gDossier=14) ce que vous venez
de faire a été sauvé dans le répertoire défini par $_ENV['TMP'] [6] avec un nom ressemblant à scenario-<nombre>.php,
export-<nombre>.php ou plugin-export.php.
Vous devez d’abord copier ce fichier dans le répertoire noalyss/scenario/LOCAL , de préférence avec un nom plus parlant.
Si vous pointez votre browser sur noalyss/html/test.`php (après avoir créé le fichier authorized_debug) en cliquant sur le lien avec le nom de fichier vous pourrez rejouer l’action.
|
|
Vous pouvez améliorer la description en changeant l’annotation //@description: <CODE>
|
L’objectif étant de pouvoir tester et de rejouer facilement les actions que vous avez faites. Cela permet de débogguer plus facilement en particulier les parties ajax. Il est possible de l’utiliser pour les sorties ajax, mises au point de librarie, scénario,…
Cela permet de tester les fichiers à include (.inc.php) , les exports , les réponses ajax, les exports en CSV ou PDF, appeler les fonctions test_me dans les classes [7],…
Dans votre code, vous pouvez ajouter des portions de code ainsi
if (defined ('TEST_UNIT')) {
// exécuté uniquement si appelé pour les tests unitaires
}
-
HTML : vérification pour les fichiers inclus
-
LIB : concerne les libraries
-
XML : réponse de script ajax, réponse souvent en XML
-
LOCAL : vos propres scripts, pour vos développements, ne seront pas dans GIT
|
|
Tout les sous-répertoires dans scénario, seront ajoutés. Pour faire vos propres tests
il suffit de créer un répertoire, d’y placer ses fichiers et ils seront proposés dans la page test.php.
|
PHPUNIT
Il faut tout d’abord un fichier bootstrap qui contient le path correct
pour php ainsi que les variables comme $g_user ou $cn, l’inclusion de
noalyss/include/config.inc.php et de noalyss/include/constant.php