268 Chapitre 6. Architecture du site : le pattern MVC Nous allons reprendre la classe générique IhmBD déjà présentée partiellement dans le chapitre 3, consacré à la programmation objet (voir page 167) et l’étendre dans l’optique d’un Modèle MVC aux aspects propres à la recherche et à la mise à jour de la base. Elle s’appellera maintenant TableBD. Le tableau 6.2 donne la liste des méthodes génériques assurant ces fonctions (ce tableau est complémentaire de celui déjà donné page 170). Tableau 6.2 — Les méthodes d’interaction avec la base de la classe TableBD . Méthode Description nouveau (donn´ees ) Création d’un nouvel objet. chercherParCle (cl´e ) Recherche d’une ligne par clé primaire. controle () Contrôle les valeurs avant mise à jour. insertion (donn´ees ) Insertion d’une ligne. maj (donn´ees ) Mise à jour d’une ligne. Conversion des données de la base vers une instance de TableBD La classe (ou plus exatement les objets instance de la classe) vont nous servir à interagir avec une table particulière de la base de données. Le but est de pouvoir manipuler les données grâce aux méthodes de la classe, en recherche comme en insertion. La première étape consiste à récupérer le schéma de la table pour connaître la liste des attributs et leurs propriétés (type, ou contraintes de clés et autres). Il faut aussi être en mesure de stocker une ligne de la table, avant une insertion ou après une recherche. Pour cela nous utilisons deux tableaux, pour le schéma, et pour les données. protected $schema = array () ; // Le schéma de la table protected $donnees = array(); // Les données d’une ligne La déclaration protected assure que ces tableaux ne sont pas accessibles par une application interagissant avec une instance de la classe. En revanche ils peuvent être modifiés ou redéfinis par des instances d’une sous-classe de TableBD. Comme nous le verrons, TableBD fournit des méthodes génériques qui peuvent être spécialisées par des sous-classes. Pour obtenir le schéma d’une table nous avons deux solutions : soit l’indiquer explicitement, en PHP, pour chaque table, soit le récupérer automatiquement en interrogeant le serveur de données. Notre classe BD dispose déjà d’une méthode, schemaTable(), qui récupère le schéma d’une table sous forme de tableau (voir page 132). Nous allons l’utiliser. Cette méthode prend en entrée un nom de table et retourne un tableau comportant une entrée par attribut. Voici par exemple ce que l’on obtient pour la table Internaute. Array ( [ email ] => Array ( [longueur] => 40 [type] => string [clePrimaire] => 1 [ notNull ] => 1 ) 6.4 Structure d’une application MVC : le modèle 269 [nom] => Array ( [longueur] => 30 [type] => string [ clePrimaire] => 0 [ notNull ] => 1 ) [prenom] => Array ( [longueur] => 30 [type] => string [ clePrimaire] => 0[ notNull ] => 1 ) [ mot_de_passe ] => Array ( [longueur] => 32] [type] => string [clePrimaire] => 0 [ notNull ] => 1 ) [ annee_naissance ] => Array ( [ longueur ] => 11 [ type ] => int [ clePrimaire ] => 0 [ notNull ]=>0 ) [ region ] => Array ( [longueur] => 30 [type] => string [ clePrimaire] => 0 [ notNull ] => 0 ) ) Ces informations nous suffiront pour construire la classe générique. Notez en particulier que l’on connaît le ou les attributs qui constituent la clé primaire (ici, l’e-mail). Cette information est indispensable pour chercher des données par clé ou effectuer des mises à jour. Le constructeur de TableBD recherche donc le schéma de la table-cible et initialise le tableau donnees avec des chaînes vides. function __construct ($nom_table , $bd , $script="moi") { // Initialisation des variables privées $this−>bd = $bd ; $this−>nom_table = $nom_table ; // Lecture du schéma de la table , et lancer d’exception si problème $this−>schema = $bd−>schemaTable($nom_table); // On initialise le tableau des données foreach ($this−>schema as $nom => $options) { $this−>donnees [$nom] = "" ; } } Le tableau des données est un simple tableau associatif, dont les clés sont les noms des attributs, et les valeurs de ceux-ci. Après l’appel au constructeur, ce tableau des données est vide. On peut l’initialiser avec la méthode nouveau(), qui prend en 270 Chapitre 6. Architecture du site : le pattern MVC entrée un tableau de valeurs. On extrait de ce tableau les valeurs des attributs connus dans le schéma, et on ignore les autres. Comme l’indiquent les commentaires dans le code ci-dessous, il manque de nombreux contrôles, mais l’essentiel de la fonction d’initialisation est là. /∗∗ ∗ Méthode créant un nouvel objet à partir d ’un tableau ∗ / public function nouveau ($ligne) { // On parcourt le schéma. Si , pour un attribut donné , // une valeur existe dans le tableau: on l ’affecte foreach ($this−>schema as $nom => $options) { // Il manque beaucoup de contrôl es . Et s i $ligne [ $nom] // était un tableau ? if (isSet($ligne[$nom])) $this−>donnees[$nom] = $ligne [$nom]; } } Une autre manière d’initialiser le tableau des données est de rechercher dans la base, en fonction d’une valeur de clé primaire. C’est ce que fait la méthode chercherParCle(). public function chercherParCle ($cle) { // Commençons par chercher la ligne dans la table $clauseWhere = $this−>accesCle ($params , "SQL") ; // Création et exécution de la requête SQL $ r e quete = "SELECT ∗ FROM $ t h i s −>nom_table WHERE $clauseWhere "; $resultat = $this−>bd−>execRequete( $requete) ; $ligne = $this−>bd−>ligneSuivante( $resultat ); // Si on n’a pas trouvé , c ’ est que la clé n’ existe pas: // on lève une exception if (! is_array($ligne)) { throw new Exception ("TableBD :: chercherParCle . La ligne n ’ existe pas."); } // Il ne reste plus qu’à créer l ’objet avec les données du tableau $this−>nouveau($ligne ) ; return $ligne ; } La méthode reçoit les valeurs de la clé dans un tableau, constitue une clause WHERE (avec une méthode accesCle() que je vous laisse consulter dans le code 6.4 Structure d’une application MVC : le modèle 271 lui-même), et initialise enfin le tableau des données avec la méthode nouveau(), vue précédemment. Une exception est levée si la clé n’est pas trouvée dans la base. Finalement, comment accéder individuellement aux valeurs des attributs pour une ligne donnée ? On pourrait renvoyer le tableau donnees, mais ce ne serait pas très pratique à manipuler, et romprait le principe d’encapsulation qui préconise de ne pas dévoiler la structure interne d’un objet. On pourrait créer des accesseurs nommés getNom (),oùnom est le nom de l’attribut dont on veut récupérer la valeur. C’est propre, mais la création un par un de ces accesseurs est fastidieuse. PHP fournit un moyen très pratique de résoudre le problème avec des méthodes dites magiques. Elles permettent de coder une seule fois les accesseurs get et set,et prennent simplement en entrée le nom de l’attribut visé. Voici ces méthodes pour notre classe générique. /∗∗ ∗ Méthode "magique" get : renvoie un élément du tableau ∗ de données ∗ / public function __get ($nom) { / / On v é r i f i e que l e nom e s t b i en un nom d ’ a t t r i b u t du schéma if (! in_array($nom, array_keys ($this−>schema))) { throw new Exception ("$nom n’ est pas un attribut de la table $this−>nom_table") ; } // On renvoie la valeur du tableau return $this−>donnees [$nom]; } /∗∗ ∗ Méthode "magique" set : affecte une valeur à un élément ∗ du tableau de données ∗ / public function __set ($nom, $valeur) { / / On v é r i f i e que l e nom e s t b i en un nom d ’ a t t r i b u t du schéma if (! in_array($nom, array_keys ($this−>schema))) { throw new Exception ("$nom n’ est pas un attribut de la table $this−>nom_table") ; } // On affecte la valeur au tableau (des contrôles seraient // bienvenus ) $this−>donnees[$nom] = $valeur ; } 272 Chapitre 6. Architecture du site : le pattern MVC La méthode __get(nom ) est appelée chaque fois que l’on utilise la syntaxe $o->nom pour lire une propriété qui n’existe pas explicitement dans la classe. Dans notre cas, cette méthode va simplement chercher l’entrée nom dans le tableau de données. La méthode __set(nom, valeur ) est appelée quand on utilise la même syntaxe pour réaliser une affectation. Ces méthodes magiques masquent la structure interne (que l’on peut donc modifier de manière transparente) en évitant de reproduire le même code pour tous les accesseurs nécessaires. Il existe également une méthode magique __call(nom, params ) qui intercepte tous les appels à une méthode qui n’existe pas. Contrôles, insertion et mises à jour Maintenant que nous savons comment manipuler les valeurs d’un objet associé à une ligne de la table, il reste à effectuer les contrôles et les mises à jour. La méthode controle() vérifie les types de données et la longueur des données à insérer. La contrainte de généricité interdit d’aller bien plus loin. protected function controle () { // On commence par v é r i f i e r les types de données foreach ($this−>schema as $nom => $options) { // Contrôle selon le type de l ’ attribut if ($options [ ’type ’] == " string") { // C’est une chaîne de caractères. Vérifions sa taille if ( strlen ($this−>donnees[$nom]) > $options [ ’longueur ’ ]) { $this−>erreurs [] = "La valeur pour $nom est trop longue "; return false ; } } else if ($options [ ’type ’ ] == " int") { // Il faut que ce soit un entier if (! is_int($this−>donnees [$nom]) ) { $this−>e r r e u r s [ ] = "$nom d o it ê t r e un e n tie r " ; return false ; } } return true ; } } Les méthodes d’insertion et de mise à jour fonctionnent toutes deux sur le même principe. On construit dynamiquement la requête SQL (INSERT ou UPDATE), puis on l’exécute. L’exemple de l’insertion est donné ci-dessous. Bien entendu on exploite le schéma de la table pour connaître le nom des attributs, et on trouve les valeurs dans le tableau de données. public function insertion () { // Initialisations $noms = $valeurs = $virgule = "" ; . un de ces accesseurs est fastidieuse. PHP fournit un moyen très pratique de résoudre le problème avec des méthodes dites magiques. Elles permettent de coder une seule fois les accesseurs get et. ligne. Conversion des données de la base vers une instance de TableBD La classe (ou plus exatement les objets instance de la classe) vont nous servir à interagir avec une table particulière de la base de données et set ,et prennent simplement en entrée le nom de l’attribut visé. Voici ces méthodes pour notre classe générique. /∗∗ ∗ Méthode "magique" get : renvoie un élément du tableau ∗ de données ∗