2017 © Pedro PelĂĄez
 

library dbrm

Database Relational mapping, Access library.

image

jemdev/dbrm

Database Relational mapping, Access library.

  • Friday, March 23, 2018
  • by Cyrano
  • Repository
  • 1 Watchers
  • 0 Stars
  • 24 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 1 Forks
  • 0 Open issues
  • 3 Versions
  • 4 % Grown

The README.md

jemdev\dbrm : DataBase Relational Mapping


Installation

Avec Composer, ajouter ce qui suit dans la partie require de votre composer.json:, (*1)

{
  "jemdev/dbrm": "dev-master"
}

Présentation et principe de fonctionnement.

Ce package permet un accÚs aux données d'une base de données relationnelle. L'idée fondatrice part du principe qu'on peut faire des lectures sur des tables multiples mais que l'écriture ne peut se faire que sur une seule table à la fois. Par conséquent, il devenait envisageable de créer des objets dynamiques pour chacune des tables sur lesquelles on souhaitait effectuer des opérations en écriture., (*2)

Des mĂ©thodes relativement simples permettent d'exĂ©cuter des requĂȘtes prĂ©parĂ©es pour la collecte de donnĂ©es. Pour l'Ă©criture, d'autres mĂ©thodes permettent de crĂ©er une instance pour initialiser une ligne d'une table donnĂ©e et d'affecter les valeurs souhaitĂ©es aux diffĂ©rentes colonnes de la table pour cette ligne. Selon que l'identifiant de la ligne est fourni ou non, l'Ă©criture sera une crĂ©ation ou une modification, voire une suppression., (*3)

Pour pouvoir créer ces instances dynamiques, un systÚme permet d'établir une sorte de cartographie du schéma de données, détaillant la liste des tables, des tables relationnelles et des vues qui sont présentes. Sur la base de ces informations, une instance pour une table donnée définit les propriétés en lisant la liste des colonnes, leur types et d'autres informations pratiques., (*4)

Lors de la connexion, si le fichier de configuration n'existe pas, il est automatiquement créé. Par la suite, si on modifie la structure du schĂ©ma, mĂȘme si ce n'est que pour ajouter, modifier ou retirer une colonne dans une table, une mĂ©thode permet de rĂ©gĂ©nĂ©rer ce fichier de configuration. Il m'est apparu comme trĂšs peu pratique de devoir crĂ©er une classe pour chacune des tables, ces modifications de structure induisant la rĂ©-Ă©criture partielle de certaines de ces classes Ă  chaque fois. Ces classes sont donc gĂ©rĂ©es dynamiquement et sont, en rĂ©alitĂ©, des classes virtuelles., (*5)

Récupérer un objet de connexion

Configurer la connexion

Il est impĂ©ratif de crĂ©er un fichier contenant les paramĂštres de connexion au SGBDR. Ce fichier doit ĂȘtre nommĂ© dbCnxConf.php et ĂȘtre formatĂ© de la maniĂšre suivante :, (*6)

<?php
/**
 * Fichier de configuration des paramÚtres de connexion à la base de données.
 * Ce fichier est généré automatiquement lors de la phase initiale d'installation.
 */
$db_app_server  = 'localhost';          // Adresse du serveur de base de données
$db_app_schema  = 'testjem';            // Schéma à cartographier (base de données de l'application)
$db_app_user    = 'testjem';            // Utilisateur de l'application pouvant se connecter au SGBDR
$db_app_mdp     = 'testjem';            // Mot-de-passe de l'utilisateur de l'application
$db_app_type    = 'pgsql';              // Type de SGBDR, MySQL, PostGreSQL, Oracle, etc..
$db_app_port    = '5432';               // Port sur lequel on peut se connecter au serveur
$db_meta_schema = 'INFORMATION_SCHEMA'; // SchĂ©ma oĂč pourront ĂȘtre collectĂ©es les informations sur le schĂ©ma de travail
/**
 * Création des constantes globales de l'application
 * NE PAS MODIFIER LES LIGNES SUIVANTES
 */
defined("DB_ROOT_SERVER")       || define("DB_ROOT_SERVER",         $db_app_server);
defined("DB_ROOT_USER")         || define("DB_ROOT_USER",           $db_app_user);
defined("DB_ROOT_MDP")          || define("DB_ROOT_MDP",            $db_app_mdp);
defined("DB_ROOT_SCHEMA")       || define("DB_ROOT_SCHEMA",         $db_meta_schema);
defined("DB_ROOT_TYPEDB")       || define("DB_ROOT_TYPEDB",         $db_app_type);
defined("DB_ROOT_DBPORT")       || define("DB_ROOT_DBPORT",         $db_app_port);
defined("DB_APP_SCHEMA")        || define("DB_APP_SCHEMA",          $db_app_schema);
defined("DB_APP_SERVER")        || define("DB_APP_SERVER",          $db_app_server);
defined("DB_APP_USER")          || define("DB_APP_USER",            $db_app_user);
defined("DB_APP_PASSWORD")      || define("DB_APP_PASSWORD",        $db_app_mdp);

Les types de SGBDR supportés

À ce jour, ce n'est utilisable qu'avec MySQL et PostGreSQL. Je n'ai pas testĂ© avec les forks de MySQL autres que MariaDb (percona et autres) mais dans la mesure oĂč ils sont compatibles, ça ne devrait pas prĂ©senter de blocage. La valeur Ă  utiliser pour la variable $db_app_type :, (*7)

  • MySQL : mysql (fonctionne avec ce type pour MariaDB)
  • PostGreSQL : pgsql

Ce fichier devra ĂȘtre placĂ© dans le rĂ©pertoire oĂč sont situĂ©s vos Ă©ventuels autres fichiers de configuration selon l'architecture de votre application. Il conviendra par la suite de pouvoir fournir en temps voulu le chemin absolu vers ce fichier. DĂšs le dĂ©part, s'il n'existe pas, un autre fichier de configuration sera gĂ©nĂ©rĂ© automatiquement et sera indispensable au bon fonctionnement du package. Ce fichier sera gĂ©nĂ©rĂ© en deux version, le premier nommĂ© dbConf.php pourra ĂȘtre assez facilement lu par n'importe quel dĂ©veloppeur, le second qui sera privilĂ©giĂ© pour l'utilisation par l'application sera nommĂ© dbConf_compact.php et correspondra strictement au mĂȘme contenu mais compactĂ© et ramenĂ© sur une seule ligne. Ce fichier dĂ©crit en dĂ©tail l'ensemble de la structure de donnĂ©es, tables, tables relationnelles et vues, colonnes, clĂ©s et autres informations dĂ©taillĂ©es. Il sera utilisĂ© par les objets destinĂ©s Ă  toutes les opĂ©rations en Ă©criture, insertion, mises Ă  jour ou suppression., (*8)

Méthodes globales accessibles

Deux méthodes de base seront indispensables :, (*9)

  • setRequete($sql, $aParams = array(), $cache = true) DĂ©finit la requĂȘte Ă  exĂ©cuter, en option on peut indiquer des paramĂštres dans un tableau associatif oĂč chaque index est le nom d'une variable sql associĂ©e Ă  la valeur qui doit y ĂȘtre affectĂ©e, et un troisiĂšme paramĂštre permet d'activer la mise en cache du rĂ©sultat, cache qui est dĂ©sactivĂ© par dĂ©faut;
  • execute() Cette mĂ©thode permet d'exĂ©cuter directement une mĂ©thode dĂ©finie avec setRequete(). On peut alors envoyer une requĂȘte, un appel de procĂ©dure stockĂ©e ou une fonction utilisateur voire mĂȘme une requĂȘte en Ă©criture bien que cette derniĂšre option ne soit pas recommandĂ©e (Voir plus loin l'Ă©criture de donnĂ©es)

Deux autres méthodes nous intéressent principalement ici et ne seront utilisées que lorsqu'on devra enregistrer des création ou modifications de données :, (*10)

  • startTransaction() DĂ©marre une transaction si les tables utilisent un moteur transactionnel. Toutes les requĂȘtes suivantes seront alors incluses dans une transaction jusqu'Ă  ce qu'on appelle la mĂ©thode finishTransaction();
  • finishTransaction($bOk) Termine une transaction : le paramĂštre attendu est un booleen, TRUE exĂ©cutera un COMMIT, FALSE exĂ©cutera un ROLLBACK;

Une autre méthode pourra se révéler pratique lors de la phase de développement de votre application :, (*11)

  • getErreurs() Retourne la liste des erreurs rencontrĂ©es sous la forme d'un tableau

Lecture de données

Il n'y a pas de gĂ©nĂ©rateur de requĂȘtes, Ă  tout le moins pour l'instant. On devra Ă©crire soi-mĂȘme les requĂȘtes en lecture qui devront ĂȘtre exĂ©cutĂ©es pour la collecte de donnĂ©es., (*12)

Chaque requĂȘte peut ĂȘtre paramĂ©trĂ©e, et sera exĂ©cutĂ©e avec PDO : elle retournera une donnĂ©e unique, une ligne de donnĂ©es ou bien un tableau de donnĂ©es voire mĂȘme un objet. On s'appuiera sur une instance de la classe jemdev\dbrm\vue qu'on dĂ©finira au prĂ©alable., (*13)

Exemple : par convention, l'instance de connexion sera la variable « $oVue » et aura été définie en amont (entendez le mot de « vue » au sens SQL du terme)., (*14)

<?php
/* DĂ©finition de la requĂȘte */
$sql = "SELECT utl_id, utl_nom, utl_prenom, utl_dateinscription ".
       "FROM t_utilisateur_utl ".
       "WHERE utl_dateinscription > :p_utl_dateinscription ".
       "ORDER BY utl_nom, utl_prenom";
/* Initialisation de paramĂštre(s) */
$params = array(':p_utl_dateinscription' => '2015-10-15');
/* Initialisation de la requĂȘte */
$oVue->setRequete($sql, $params);
/* Récupération des données */
$infosUtilisateur = $oVue->fetchAssoc();

Note:

Le nom de l'objet $oVue dans cet exemple n'est pas anodin, il faut entendre le mot vue au sens SQL du terme. Une vue dans une base de donnĂ©es est une synthĂšse d'une requĂȘte d'interrogation de la base. On peut la voir comme une table virtuelle, dĂ©finie par une requĂȘte., (*15)

Les méthodes accessibles

Le nom des méthodes est inspiré de celles qu'on emploie avec l'extension MySQL. Le retours sont bien entendu similaires dans leur forme., (*16)

  • fetchAssoc() Retourne un tableau associatif de rĂ©sultats oĂč les index sont les noms des colonnes ou alias dĂ©terminĂ©s dans la requĂȘte;
  • fetchArray() Retourne un tableau oĂč chaque colonne est prĂ©sentĂ©e avec deux index, l'un numĂ©rique, l'autre associatif avec le nom de la colonne;
  • fetchObject() Retourne un objet oĂč chaque colonne est une propriĂ©tĂ©;
  • fetchLine($out = 'array') Retourne une seule ligne de donnĂ©es. On peut prĂ©ciser en paramĂštre quelle forme doit prendre le rĂ©sultat en passant une des constantes suivantes :
    • vue::JEMDB_FETCH_OBJECT = 'object' : indiquera un retour sous forme d'un objet;
    • vue::JEMDB_FETCH_ARRAY = 'array' : indiquera un retour sous forme d'un tableau avec double index numĂ©rique et associatif;
    • vue::JEMDB_FETCH_ASSOC = 'assoc' : indiquera un retour sous forme d'un tableau associatif;
    • vue::JEMDB_FETCH_NUM = 'num' : indiquera un retour sous forme d'un tableau indexĂ© numĂ©riquement;
  • fetchOne() Retourne une donnĂ©e unique;

Écriture de donnĂ©es

Les méthodes accessibles

Sur une instance donnée, vous disposez des méthodes suivantes :, (*17)

  • init($aPk = null) : Initialise l'instance de la ligne. En arriĂšre plan, l'objet sera construit dynamiquement avec comme propriĂ©tĂ©s les colonnes de la table visĂ©e;
  • sauvegarder() : Enregistre les modifications apportĂ©es aux propriĂ©tĂ©s par une requĂȘte INSERT ou UPDATE selon qu'on a dĂ©terminĂ© ou non la clĂ© primaire;
  • supprimer() : Supprime la ligne de la table par une requĂȘte DELETE. Dans le cas oĂč un moteur transactionnel serait utilisĂ© et que des clauses d'intĂ©gritĂ© rĂ©fĂ©rentielles auraient Ă©tĂ© dĂ©finies (CONSTRAINT), la mĂ©thode pourra retourner une erreur si des donnĂ©es faisant rĂ©fĂ©rences Ă  la ligne devant ĂȘtre supprimĂ©e existent encore dans les tables liĂ©es;
  • startTransaction() : DĂ©marre une transaction SQL;
  • finishTransaction($bOk) : Termine une transaction SQL, le paramĂštre attendu est un boolĂ©en indiquant si on doit effectuer un COMMIT ou un ROLLBACK;

Mise en pratique

On écrit des données, comme mentionné en introduction, que sur une seule table à la fois. Pour ce faire, on crée un objet représentant une ligne de ladite table. Voici d'abord un exemple schématique : Les tables sur lesquelles s'appuient notre exemple auront la forme suivante :, (*18)

  1. Table t_interlocuteur_int
+---------------------+-------------------------+------+-----+---------+----------------+
| Field               | Type                    | Null | Key | Default | Extra          |
+---------------------+-------------------------+------+-----+---------+----------------+
| int_id              | int(10) unsigned        | NO   | PRI | NULL    | auto_increment |
| adr_id              | int(10) unsigned        | YES  | MUL | NULL    |                |
| int_nom             | varchar(255)            | NO   |     | NULL    |                |
| int_prenom          | varchar(255)            | NO   |     | NULL    |                |
| int_dateinscription | date                    | YES  |     | NULL    |                |
+---------------------+-------------------------+------+-----+---------+----------------+
  1. Table t_adresse_adr
+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| adr_id            | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| adr_numero        | varchar(16)      | YES  |     | NULL    |                |
| adr_libelle_1     | varchar(128)     | NO   |     | NULL    |                |
| adr_libelle_2     | varchar(128)     | YES  |     | NULL    |                |
| adr_codepostal    | varchar(16)      | NO   |     | NULL    |                |
| adr_commune       | varchar(128)     | NO   |     | NULL    |                |
+-------------------+------------------+------+-----+---------+----------------+

Note importante : vous ne pourrez pas dĂ©finir vous-mĂȘme la valeur de la clĂ© primaire en insĂ©rant une nouvelle ligne de donnĂ©es. Cette valeur sera gĂ©nĂ©rĂ©e automatiquement par le SGBD si cette colonne est bien en auto-increment (MySQL et certains SGBDR. Pour d'autres comme Oracle qui ne disposent pas de cette fonctionnalitĂ©, il sera avisĂ© de mettre en place pour chaque table une sĂ©quence et un trigger before insert pour gĂ©nĂ©rer la clĂ© primaire automatiquement). Lorsqu'on initialise une ligne de donnĂ©es, on peut en option indiquer la valeur de cette clĂ© primaire si on la connaĂźt : le cas Ă©chĂ©ant, les colonnes seront alors alimentĂ©es avec les valeurs correspondant Ă  cette ligne, sinon, nous aurons une ligne vide prĂȘte Ă  complĂ©ter. À prĂ©sent, Ă©crivons une ligne de donnĂ©es :, (*19)

<?php
/* On crée l'instance de la ligne à partir du nom de la table cible */
$oAdresse = $oDbrm->getLigneInstance('t_adresse_adr');
/*
 * On détermine si l'on dispose ou non de la clé primaire de la ligne
 * et on stocke ça dans un tableau associatif
 */
$aPk = (!empty($adr_id)) ? array('adr_id' => $adr_id) ? null;
/* On initialise l'instance */
$oAdresse->init($aPk);
/*
 * DÚs cet instant, notre objet présente chaque colonne de la
 * table t_interlocuteur_int comme des propriétés qu'on peut modifier
 */
$oAdresse->adr_numero       = $adr_numero;
$oAdresse->adr_libelle_1    = $adr_libelle_1;
// ... etc...

/* On peut maintenant sauvegarder ces informations */
$enreg = $oAdresse->sauvegarder();
/*
 * Terminé, les écritures pour cette ligne sont terminées.
 * On peut récupérer la valeur de la clé primaire si nécessaire et s'il
 * s'agissait d'une création. Cette clé primaire est automatiquement gérée
 * et initialisée dans l'instance.
 * S'il y a eu une erreur, la méthode sauvegarder retournera l'erreur, sinon
 * elle retournera TRUE
 */
if(true == $enreg)
{
    /*
     * Ici, si par exemple vous avez d'autres données à enregistrer, données qui
     * dépendent la la réussite de ce premier enregistrement, vous continuez
     * sur l'enregistrement suivant, exemple :
     */
    $adr_id         = $oAdresse->adr_id;
    $oInterlocuteur = $oDbrm->getLigneInstance('t_interlocuteur_int');
    /*
     * On détermine si l'on dispose ou non de la clé primaire de la ligne
     * et on stocke ça dans un tableau associatif
     */
    $aPk = (!empty($int_id)) ? array('int_id' => $int_id) : null;
    /* On initialise l'instance */
    $oInterlocuteur->init($aPk);
    /*
     * DÚs cet instant, notre objet présente chaque colonne de la
     * table t_interlocuteur_int comme des propriétés qu'on peut modifier
     */
    $oInterlocuteur->adr_id     = $adr_id;      // Ici, on alimente la clé étrangÚre définie en enregistrant l'adresse.
    $oInterlocuteur->int_nom    = $int_nom;
    $oInterlocuteur->int_prenom = $int_prenom;
    if(!is_null($int_dateinscription))
    {
        $oInterlocuteur->int_dateinscription = $int_dateinscription;
    }
    /* On peut maintenant sauvegarder ces informations */
    $enreg = $oInterlocuteur->sauvegarder();

    // etc... suite selon les besoins.
}
else
{
    // Ici, le code permettant la gestion de l'erreur selon vos propres maniĂšres de faire.
}

Les erreurs possibles

Lors de l'insertion des donnĂ©es, une vĂ©rification est automatiquement effectuĂ©e. Si une colonne requiert obligatoirement une valeur et qu'aucune n'est envoyĂ©e, une eexception sera levĂ©e. Le type de donnĂ©e sera Ă©galement vĂ©rifiĂ© et selon le cas, une exception pourra ĂȘtre levĂ©e si une donnĂ©e n'est pas conforme au modĂšle de donnĂ©es dĂ©fini., (*20)

En résumé : l'instance d'une table

Lorsque vous crĂ©ez une instance pour une table donnĂ©e, chaque colonne de cette table devient une propriĂ©tĂ© de cette instance. Ainsi, la colonne int_nom est une propriĂ©tĂ© de l'objet $oInterlocuteur et peut donc ĂȘtre invoquĂ©e comme une propriĂ©tĂ© publique de l'objet. Si vous essayez d'affecter une valeur sur une colonne qui n'existe pas dans la table considĂ©rĂ©e, une exception sera levĂ©e. De mĂȘme que vous ne pourrez pas affecter une valeur arbitraire sur une clĂ© primaire. Cependant, il existe un cas oĂč vous pourrez dĂ©finir vous-mĂȘme la valeur d'une clĂ© primaire lorsqu'il s'agit d'une clĂ© composite sur une table relationnelle. Ainsi, si au lieu d'un lien direct entre interlocuteur et adresse dans notre exemple nous avions eu une table relationnelle entre les deux, par exemple r_int_has_adr_iha, nous aurions les colonnes int_id et adr_id composant la clĂ© primaire de cette table relationnelle. Dans un tel cas, nous modifierons le code prĂ©cĂ©dent dans la maniĂšre suivante:, (*21)

<?php
/* Définition des valeurs de bases des identifiants */
$int_id = null;
$adr_id = null;

/* On crée l'instance de la ligne à partir du nom de la table cible */
$oAdresse = $oDbrm->getLigneInstance('t_adresse_adr');
/*
 * On détermine si l'on dispose ou non de la clé primaire de la ligne
 * et on stocke ça dans un tableau associatif
 */
$aPk = (!empty($adr_id)) ? array('adr_id' => $adr_id) ? null;
/* On initialise l'instance */
$oAdresse->init($aPk);
/*
 * DÚs cet instant, notre objet présente chaque colonne de la
 * table t_interlocuteur_int comme des propriétés qu'on peut modifier
 */
$oAdresse->adr_numero       = $adr_numero;
$oAdresse->adr_libelle_1    = $adr_libelle_1;
// ... etc...

/* On peut maintenant sauvegarder ces informations */
$enreg = $oAdresse->sauvegarder();
/*
 * Terminé, les écritures pour cette ligne sont terminées.
 * On peut récupérer la valeur de la clé primaire si nécessaire et s'il
 * s'agissait d'une création. Cette clé primaire est automatiquement gérée
 * et initialisée dans l'instance.
 * S'il y a eu une erreur, la méthode sauvegarder retournera l'erreur, sinon
 * elle retournera TRUE
 */
if(true == $enreg)
{
    $adr_id         = $oAdresse->adr_id;
    // etc... suite selon les besoins.
}
else
{
    // Ici, le code permettant la gestion de l'erreur selon vos propres maniĂšres de faire.
}
$oInterlocuteur = $oDbrm->getLigneInstance('t_interlocuteur_int');
/*
 * On détermine si l'on dispose ou non de la clé primaire de la ligne
 * et on stocke ça dans un tableau associatif
 */
$aPk = (!empty($int_id)) ? array('int_id' => $int_id) : null;
/* On initialise l'instance */
$oInterlocuteur->init($aPk);
/*
 * DÚs cet instant, notre objet présente chaque colonne de la
 * table t_interlocuteur_int comme des propriétés qu'on peut modifier
 */
$oInterlocuteur->adr_id     = $adr_id;      // Ici, on alimente la clé étrangÚre définie en enregistrant l'adresse.
$oInterlocuteur->int_nom    = $int_nom;
$oInterlocuteur->int_prenom = $int_prenom;
if(!is_null($int_dateinscription))
{
    $oInterlocuteur->int_dateinscription = $int_dateinscription;
}
/* On peut maintenant sauvegarder ces informations */
$enreg = $oInterlocuteur->sauvegarder();
if(true == $enreg)
{
    $int_id         = $oInterlocuteur->int_id;
    // etc... suite selon les besoins.
}
else
{
    // Ici, le code permettant la gestion de l'erreur selon vos propres maniĂšres de faire.
}
if(!is_null($int_id) && !is_null($adr_id))
{
    /* Maintenant, on peut alimenter la tablea relationnelle */
    $oAdresseInt = $oDbrm->getLigneInstance('r_int_has_adr_iha');
    /* On définit les éléments de la clé primaire composite */
    $aPk = array(
        'int_id' => $int_id,
        'adr_id' => $adr_id
    );
    /* On initialise l'instance de l'objet */
    $oAdresseInt->init($aPk);
    /* On peut sauvegarder */
    $enreg = $oAdresseInt->sauvegarder();
    if(true !== $enreg)
    {
        // Ici, le code permettant la gestion de l'erreur selon vos propres maniĂšres de faire.
    }
}

Et là, pas de blocage lors de l'affectation des valeurs pour la clé primaire., (*22)

En pratique

Une utilisation pratique vous amĂšnera sans doute Ă  rĂ©partir les requĂȘtes en Ă©criture sur diffĂ©rentes tables dans diffĂ©rentes fonctions/mĂ©thodes appelĂ©es Ă  partir d'un endroit unique, ce qui vous permettra d'utiliser au besoin le mode transactionnel. En dĂ©marrant la transaction au dĂ©part, vous exĂ©cutez chaque enregistrement, et si une des mĂ©thodes retourne FALSE Ă  cause d'une erreur, vous pourrez interrompre la succession des enregistrements et terminer la transaction par un ROLLBACK, Ă©vitant ainsi de pourrir vos tables avec des donnĂ©es orphelines ou incohĂ©rentes., (*23)

Ce qu'on ne peut pas faire (pour l'instant)

Actuellement, il reste quelques éléments en TODO-LIST et en particulier, lors de l'écriture de données, la possibilité d'affecter non pas une valeur mais un appel de fonction SQL. Supposons par exemple que vous vouliez utiliser une fonction de chiffrement intégrée de MySQL pour affecter une valeur. Il n'est pour l'instant pas possible de faire ceci :, (*24)

$instanceLigne->nom_colonne = "AES_ENCRYPT('valeur', 'Clé de chiffrement')";

Comment contourner le problĂšme.

Pour une utilisation quotidienne, ce n'est pas un réel problÚme, ce type de cas particulier étant relativement rare. Si cependant vous devez pouvoir effectuer une telle opération, vous avez deux options., (*25)

  • La premiĂšre consiste Ă  envoyer une valeur en clair et ajouter un trigger sur la table avec un BEFORE INSERT qui exĂ©cutera alors la fonction SQL Ă  appliquer sur la valeur pour l'affecter Ă  la colonne. Mais cette mĂ©thode pourra ĂȘtre bloquĂ©e sur un serveur mutualisĂ© oĂč les fonctions utilisateurs, procĂ©dures stockĂ©es et triggers sont dĂ©sactivĂ©s par dĂ©faut;
  • La seconde consiste alors Ă  Ă©crire vous-mĂȘme la requĂȘte en Ă©criture INSERT ou UPDATE et Ă  la faire exĂ©cuter avec la mĂ©thode execute() de la maniĂšre suivante :
<?php
/* On a d'abord besoin d'une instance de jemdev\dbrm\vue */
$oVue = $oDb->getDbrmInstance();
/* On dĂ©finit la requĂȘte SQL d'insertion */
$sql  = "INSERT INTO matable (col_login, col_motdepasse)".  
        "VALUES('Toto', AES_ENCRYPT('valeur', 'Clé de chiffrement'))";  
$oVue->setRequete($sql);  
/* ExĂ©cution de la requĂȘte. */
$enreg = $oVue->execute();

La suite du code ne change pas., (*26)

Une instance = une ligne

Il n'a pas Ă©tĂ© prĂ©vu pour l'instant de pouvoir effectuer une mise Ă  jour ou encore une suppression de lignes multiples dans la mesure oĂč une mise Ă  jour s'effectuera uniquement en fonction de la valeur d'une clĂ© primaire. Pratiquant l'utilisation au quotidien de ce package depuis dĂ©jĂ  de nombreuses annĂ©es et ce sur une application de gestion, je n'ai en rĂ©alitĂ© jamais eu besoin d'implĂ©menter cette possibilitĂ©. Et pour les rares fois oĂč ça doit se produire, je peux contourner ce manque en collectant la liste des clĂ© primaires Ă  prendre en compte dans une mise Ă  jour et chaque ligne sera traitĂ©e individuellement dans une boucle., (*27)


Temps d'exĂ©cution des requĂȘtes

Ce petit systĂšme s'appuie sur la mĂ©thode native error_log de PHP pour la journalisation de requĂȘtes lentes. Au fil du dĂ©veloppement d'une application, il peut ĂȘtre utile d'identifier des goulets d'Ă©tranglement qui ralentissent l'application. On peut alors simplement activer un systĂšme de mesure qui effectuera un chronomĂ©trage systĂ©matique de toutes les requĂȘtes. On paramĂštre le systĂšme en lui indiquant :, (*28)

  • Le type de journalisation : « php », « fichier » ou « courriel »;
  • La durĂ©e minimale en secondes Ă  partir de laquelle on journalise une requĂȘte;
  • En option le chemin absolu vers un fichier journal oĂč seront enregistrĂ©es les informations si le mode « fichier » a Ă©tĂ© dĂ©fini;
  • En option une adresse de courriel oĂč adresser les messages d'avertissement si le mode « courriel » a Ă©tĂ© dĂ©fini.

La mise en oeuvre est trĂšs simple, exemple :, (*29)

$mode = 'fichier';
$maxtime = 1;
$fichier = 'app/tmp/logs/journaldb.log';
$this->_oDb->activerModeDebug($mode, $maxtime, $fichier);

C'est tout : une fois ceci lancĂ©, il suffit alors de naviguer normalement dans l'application, spĂ©cialement dans les parties qui affichent des ralentissements apparents, puis de vĂ©rifier le fichier journal pour y trouver Ă©ventuellement des requĂȘtes qui devraient alors ĂȘtre optimisĂ©es pour une accĂ©lĂ©ration., (*30)

Pour le mode « fichier », si le fichier n'existe pas, il sera créé.

Une gestion de cache dynamique

Un problĂšme d'accĂšs Ă  la configuration du serveur MySQL sur lequel je travaillais m'interdisait de paramĂ©trer le cache intĂ©grĂ© et mĂȘme tout simplement de l'activer par dĂ©faut. Souhaitant pouvoir disposer d'un systĂšme de gestion de cache de requĂȘtes, j'ai ajoutĂ© des classes permettant de gĂ©rer cet aspect., (*31)

Globalement, chaque requĂȘte en lecture peut, si le cache est activĂ©, stocker le rĂ©sultat en cache sur fichier voire mĂȘme sur MemCache si cette extension est activĂ©e. Toute Ă©criture sur une des table va rĂ©gĂ©nĂ©rer le cache pour les requĂȘtes oĂč est impliquĂ©e la table en question. La durĂ©e de vie du cache est donc fonction de l'exĂ©cution de nouvelles Ă©critures et non d'une durĂ©e de vie prĂ©-dĂ©finie. Si le rĂ©sultat d'une requĂȘte est valide pendant trois minutes et qu'une Ă©criture intervient, le cache est renouvelĂ©, si ce mĂȘme rĂ©sultat est toujours valide aprĂšs trois semaines, il est parfaitement inutile de le rĂ©gĂ©nĂ©rer., (*32)

Certaines mĂ©thodes permettent de rĂ©gĂ©nĂ©rer manuellement le cache pour certaines tables. Par exemple, si lors d'une Ă©criture sur une table un trigger va dĂ©clencher l'exĂ©cution d'une procĂ©dure stockĂ©e crĂ©ant des Ă©critures sur d'autres tables, il sera important de rĂ©gĂ©nĂ©rer le cache sur ces autres tables. Il n'est pas possible de dĂ©tecter ces Ă©critures en PHP dans la mesure oĂč c'est le SGBDR qui gĂšre ça directement. De mĂȘme si des tĂąches CRON dĂ©clenchent des Ă©critures sans passer par PHP, il n'est pas possible d'intercepter cette information pour mettre Ă  jour le cache correspondant, il conviendra donc d'Ă©crire un code PHP qui effectuera ce nettoyage, code qui devra ĂȘtre exĂ©cutĂ© dans une tĂąche Ă  ajouter au CronTab., (*33)

Par défaut, le cache n'est pas activé, et si vous avez la possibilité de gérer le cache intégré de votre SGBDR, ce sera alors une solution préférable et plus performante., (*34)

Intégration avec Symfony

Pour ceux dĂ©sirant travailler avec Symfony et accĂ©der aux donnĂ©es d'une base sans recourir Ă  Doctrine, cette solution prĂ©sente une alternative viable, quoique peut-ĂȘtre plus exigeante techniquement. Ici, pas question de pouvoir modifier la structure de la base de donnĂ©es avec cette librairie : ça relĂšve du DBA et il est hors de question qu'un dĂ©veloppeur touche Ă  cette structure. Cependant, le systĂšme mĂȘme de jemdev\dbrm permet les Ă©volution de la structure de donnĂ©es et un fichier de configuration doit pouvoir ĂȘtre gĂ©nĂ©rĂ©. Le systĂšme de ligne de commandes dans Symfony se rĂ©vĂšle pour celĂ  fort pratique. Nous allons donc voir ici comment gĂ©nĂ©rer ce fichier d'une seule ligne de commande., (*35)

Note : ce qui suit n'est qu'une suggestion qui fonctionne mais que vous pouvez vouloir implémenter autrement., (*36)

Les fichiers de configuration

Dans le répertoire /config de l'application Symfony, nous allons créer un sous-répertoire dans /config/packages que nous nommerons /dbrm., (*37)

Dans ce nouveau répertoire, nous allons créer deux fichiers de base, appConf.php et dbConf.php, et nous inscrirons des éléments de base pour rendre le systÚme de configuration opérationnel., (*38)

Le fichier appConf.php

<?php
$mode = (defined('APP_ENV')) ? APP_ENV : 'dev';
defined("MODE")                 || define("MODE",                   $mode);
defined("DS")                   || define("DS",                     DIRECTORY_SEPARATOR);
defined("REP_ROOT")             || define("REP_ROOT",               dirname(dirname(dirname(__DIR__))). DS);
/**
 * ParamÚtres de connexion root pour pouvoir récupérer si nécessaire
 * les informations de base de données et construire le fichier dbConf.php
 */
$db_server_type = 'mysql';
switch (MODE)
{
    case 'dev':
        $db_app_server = 'localhost';
        $db_app_port   = '3306';
        $db_app_schema = 'monshema';
        $db_app_user   = 'monutilisateur';
        $db_app_pwd    = 'abc123';
        $db_bin_path   = '/var/lib/mysql/bin';
        $db_root_schema = "INFORMATION_SCHEMA";
        break;
    case 'test':
        $db_app_server = 'localhost';
        $db_app_port   = '3306';
        $db_app_schema = 'monshematest';
        $db_app_user   = 'monutilisateurtest';
        $db_app_pwd    = 'abc123test';
        $db_bin_path   = '/var/lib/mysql/bin';
        $db_root_schema = "INFORMATION_SCHEMA";
        break;
    case 'prd':
    case 'prod':
        $db_app_server = 'localhost';
        $db_app_port   = '3306';
        $db_app_schema = 'monshema';
        $db_app_user   = 'monutilisateur';
        $db_app_pwd    = 'abc123';
        $db_bin_path   = '/var/lib/mysql/bin';
        $db_root_schema = "INFORMATION_SCHEMA";
        break;
}
defined("DB_ROOT_SERVER")       || define("DB_ROOT_SERVER",         $db_app_server);
defined("DB_ROOT_USER")         || define("DB_ROOT_USER",           $db_app_user);
defined("DB_ROOT_MDP")          || define("DB_ROOT_MDP",            $db_app_pwd);
defined("DB_ROOT_SCHEMA")       || define("DB_ROOT_SCHEMA",         $db_root_schema);
defined("DB_ROOT_TYPEDB")       || define("DB_ROOT_TYPEDB",         $db_server_type);
defined("DB_ROOT_DBPORT")       || define("DB_ROOT_DBPORT",         $db_app_port);
defined("DB_APP_SCHEMA")        || define("DB_APP_SCHEMA",          $db_app_schema);
defined("DB_APP_SERVER")        || define("DB_APP_SERVER",          $db_app_server);
defined("DB_APP_USER")          || define("DB_APP_USER",            $db_app_user);
defined("DB_APP_PASSWORD")      || define("DB_APP_PASSWORD",        $db_app_pwd);
defined("DB_BIN_PATH")          || define("DB_BIN_PATH",            $db_bin_path);

C'est pour l'instant tout ce dont nous avons besoin dans ce fichier., (*39)

Le fichier dbConf.php

Ce fichier est normalement généré par une des librairies du package, mais en fournissant le squelette de base, nous allons simplifier les choses., (*40)

<?php
/**
 * @package     jemdev
 *
 * Ce code est fourni tel quel sans garantie.
 * Vous avez la liberté de l'utiliser et d'y apporter les modifications
 * que vous souhaitez. Vous devrez néanmoins respecter les termes
 * de la licence CeCILL dont le fichier est joint Ă  cette librairie.
 * {@see http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html}
 *
 * Date de génération du fichier : 12/05/2023 11:05:36
 */

/**
 * Définition des constantes sur les types de données
 */
defined('TYPE_INTEGER') || define('TYPE_INTEGER', 'INT');
defined('TYPE_VARCHAR') || define('TYPE_VARCHAR', 'VARCHAR');
defined('TYPE_ENUM') || define('TYPE_ENUM', 'ENUM');
defined('TYPE_FLOAT') || define('TYPE_FLOAT', 'FLOAT');
defined('TYPE_TINYINT') || define('TYPE_TINYINT', 'TINYINT');
defined('TYPE_DATE') || define('TYPE_DATE', 'DATE');
defined('TYPE_MEDIUMINT') || define('TYPE_MEDIUMINT', 'MEDIUMINT');
defined('TYPE_BLOB') || define('TYPE_BLOB', 'BLOB');
/**
 * Description détaillée des schémas
 */
$dbConf = array(
    0 => array(
        'schema' => array(
            'name'   => DB_APP_SCHEMA,
            'SGBD'   => DB_ROOT_TYPEDB,
            'server' => DB_APP_SERVER,
            'port'   => DB_ROOT_DBPORT,
            'user'   => DB_APP_USER,
            'mdp'    => DB_APP_PASSWORD,
            'pilote' => DB_ROOT_TYPEDB
        ),
        'tables' => array(),
        'relations' => array(),
        'vues' => array()
    )
);

Ne modifiez rien dans cette base. Et par la suite, ne modifiez jamais manuellement ce fichier., (*41)

Mise en place de la commande

Dans notre classe, nous avons défini un nom pour la commande, « app:dbrmconf »., (*42)

Ouvrons maintenant une console et, aprĂšs nous ĂȘtre placĂ©s dans le rĂ©pertoire de l'application, crĂ©ons la commande :, (*43)

$ php bin/console make:command app:dbrmconf

Si tout s'est correctement déroulé, vous verrez alors s'afficher ce qui suit :, (*44)

 created: src/Command/DbrmconfCommand.php


  Success! 


 Next: open your new command class and customize it!
 Find the documentation at https://symfony.com/doc/current/console.html

Une classe a donc Ă©tĂ© créée dans le rĂ©pertoire /src/Command, nous allons y apporter quelques modifications. Éditons cette classe., (*45)

Configuration de la classe de commande

Voici le code nécessaire :, (*46)

<?php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use jemdev\dbrm\init\genereconf;
use jemdev\dbrm\vue;

/**
 * Commande personnalisée.
 *
 * Ce fichier va permettre de déclencher la (re)génération du fichier de configuration
 * de la base de données telle que requise par la librairie jemdev\dbrm
 *
 * En lançant simplement en console la commande suivante :
 * php bin/console app:dbrmconf
 *
 * @author JEM-Developpement Ltd
 */
class DbrmconfCommand extends Command
{
    protected static $defaultName = 'app:dbrmconf';
    protected static $defaultDescription = 'Génération du fichier de configuration de la base de données';
    protected static $dirconf;

    protected function configure(): void
    {
        $this->setHelp('Cette commande permet de générer le fichier de configuration de la base de données pour le package jemdev\dbrm');
        self::$dirconf = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR ."config". DIRECTORY_SEPARATOR ."packages". DIRECTORY_SEPARATOR ."dbrm". DIRECTORY_SEPARATOR;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        include(self::$dirconf ."appConf.php");
        $resetDbConf = $this->resetDbConf(DB_ROOT_USER, DB_ROOT_MDP);
        $msg = (true == $resetDbConf)
            ? "Terminé, le fichier a été correctement généré"
            : "Terminé avec des erreurs !";
        $io->success($msg);

        return Command::SUCCESS;
    }

    public function resetDbConf($dbuser,$dbmdp)
    {
        include(self::$dirconf ."appConf.php");
        // Création du fichier de configuration de la base.
        $cible      = REP_ROOT . DIRECTORY_SEPARATOR ."config". DIRECTORY_SEPARATOR ."packages". DIRECTORY_SEPARATOR ."dbrm". DIRECTORY_SEPARATOR ."dbConf.php";
        $oDbInit    = new genereconf(DB_APP_SCHEMA,DB_ROOT_USER,DB_ROOT_MDP,$dbuser,$dbmdp,DB_ROOT_TYPEDB,DB_ROOT_SERVER,DB_ROOT_DBPORT);
        $aConfIS    = array(
            'schema' => array(
                'name'   => DB_ROOT_SCHEMA,
                'SGBD'   => DB_ROOT_TYPEDB,
                'server' => DB_ROOT_SERVER,
                'port'   => DB_ROOT_DBPORT,
                'user'   => DB_ROOT_USER,
                'mdp'    => DB_ROOT_MDP,
                'pilote' => DB_ROOT_TYPEDB
            ),
            'tables'     => array(),
            'relations'  => array(),
            'vues'       => array()
        );
        $oVue       = vue::getInstance($aConfIS);
        $bGenConf   = $oDbInit->genererConf($oVue, $cible);
        return $bGenConf;
    }
}

La mĂ©thode DbrmconfCommand::execute va lancer l'exĂ©cution de la mĂ©thode DbrmconfCommand::resetDbConf que nous avons ajoutĂ©e, et le fichier de configuration vu plus haut dbConf.php va ĂȘtre rĂ©gĂ©nĂ©rĂ© selon les informations collectĂ©es dans la base de donnĂ©es., (*47)

Vérifions l'existence de cette nouvelle commande en demandant la liste des commandes existantes mais en limitant au namespace app :, (*48)

$ php bin/console list app

Le résultat normal devrait comporter ceci :, (*49)

. . . .

Available commands for the "app" namespace:
  app:dbrmconf  Génération du fichier de configuration de la base de données

Et nous voyons bien en bas notre nouvelle commande assortie du message informant de la nature de cette commande tel que nous l'avons défini dans notre nouvelle classe., (*50)

Essayons ça :, (*51)

$ php bin/console app:dbrmconf

Si tout s'est correctement déroulé, vous devriez voir ceci :, (*52)

[OK] Terminé, le fichier a été correctement généré 

Tel que nous l'avons également défini dans notre classe de commande, dans la méthode execute., (*53)

Attention, jemdev\dbrm n'est PAS Doctrine

Ce package ne remplacera jamais Doctrine, ce n'est pas un ORM. Il permet de collecter des donnĂ©es, de les modifier ou de les supprimer. Mais il ne permet pas de modifier la structure des donnĂ©es, ni de gĂ©nĂ©rer des formulaires prĂ©-remplis et autres fonctionnalitĂ©s exotiques du mĂȘme genre. Il n'impose pas non plus de faire usage d'une sorte de langage bĂątard entre le PHP et le SQL : si vous n'avez pas une maĂźtrise de base du langage SQL, alors gardez Doctrine., (*54)


Conclusion

Ce package se veut simple d'utilisation de façon à ne pas perdre le développeur dans les complications de l'implémentation, et ce sans avoir à se préoccuper du type de serveur de base de données utilisé, que ce soit MySQL/MariaDb, ou PostGreSQL., (*55)

À venir

Il reste Ă  dĂ©velopper le code qui permettra d'utiliser des SGBDR autres que MySQL ou PostGreSQL, codes qui pour l'instant n'existent pas. Il s'agit de pouvoir construire le tableau de configuration d'un schĂ©ma de donnĂ©es. MySQL et PostGreSQL implĂ©mentent INFORMATION_SCHEMA, ce qui facilite grandement ce travail, mais tous les SGBDR ne l'implĂ©mentent pas, comme par exemple Oracle. Il existe cependant d'autres maniĂšre de collecter ces informations pour aboutir au mĂȘme rĂ©sultat., (*56)

Par la suite, le fonctionnement s'appuyant sur PDO, l'intégration de jemdev\dbrm pourra se faire dans n'importe quel projet., (*57)

Les projets Ă  plus long terme

L'idĂ©e d'un gĂ©nĂ©rateur de requĂȘtes automatisĂ© flotte dans l'air depuis pas mal de temps mais requiert un niveau de connaissances en mathĂ©matiques que je n'ai malheureusement pas. Il est question de s'appuyer sur la thĂ©orie des graphes pour dĂ©terminer quelles jointures devront ĂȘtre Ă©tablies pour n'avoir Ă  dĂ©finir que les colonnes de telle ou telle table est attendue pour que le moteur construise automatiquement le chemin appropriĂ©. Le fichier de configuration permet d'ores et dĂ©jĂ  de crĂ©er une matrice (le code n'est pas intĂ©grĂ© dans le package mais est dĂ©jĂ  prĂȘt et opĂ©rationnel), il reste Ă  dĂ©finir l'algorithme appropriĂ© de façon Ă  construire des requĂȘtes respectant les standards les plus exigeants., (*58)

Toute contribution en la matiĂšre sera bienvenue., (*59)

The Versions

23/03 2018

dev-master

9999999-dev http://jem-web.info/

Database Relational mapping, Access library.

  Sources   Download

CECILL-2.0 CeCILL-2.1 CeCILL-3.0

The Requires

 

by Jean Molliné

database library validation data mapping namespace

02/11 2015

2.0.001

2.0.001.0 http://jem-web.info/

Database Relational mapping, Access library.

  Sources   Download

CECILL-2.0

The Requires

 

by Jean Molliné

database library validation data mapping namespace

30/10 2015

2.0

2.0.0.0 http://jem-web.info/

Database Relational mapping, Access library.

  Sources   Download

CECILL-2.0

The Requires

 

by Jean Molliné

database library validation data mapping namespace