128 Chapitre 3. Programmation objet qui raffinent la description des objets de la super-classe est un aspect inséparable de la spécialisation. Surcharge Enfin la surcharge est le mécanisme qui consiste à enrichir, voire à remplacer com- plètement, un comportement défini au niveau de la super-classe par un autre, adapté aux caractéristiques de la classe spécialisée. REMARQUE – Attention, la documentation PHP utilise le terme « surcharge » (overloading) dans un sens différent de celui consacré en programmation objet. La notion de surcharge présentée ici est conforme avec celle classique, rencontrée en C++ ou en Java. Dans notre exemple la méthode copier() de la sous-classe R´epertoire doit être implantée différemment de la méthode codée au niveau de la classe Fichier, car, outre la copie du fichier-répertoire lui-même, on doit également copier l’en- semble des fichiers contenus dans le répertoire. Ce comportement de la méthode copier() est tout à fait spécifique à ce type de fichier et nécessite toute la « sur- charge » – la redéfinition – de la méthode héritée. Voici, très simplifié, l’essentiel des instructions que l’on pourrait trouver dans cette surcharge : class Répertoire { // Propriétés private $liste_fichiers ; // Une méthode public copier ($destination) { // On commence par copier le répertoire lui−même // en appelant la méthode de la super−classe parent :: copier($destination); // Puis on copie tous les fichiers contenus foreach ($this−>liste_fichier as $fichier) $fichier−>copier($destination); } Cette méthode se décompose clairement en deux parties : l’une consistant à effectuer une copie standard, telle qu’elle est définie au niveau de la classe parent, l’autre répercutant la demande de copie sur l’ensemble des fichiers contenus dans le répertoire et référencés par la propriété ajoutéee liste_fichiers. Pour appliquer la copie standard, on doit appeler le code défini au niveau de la classe parente. On utilise pour cela la construction parent::copier. Cette pratique est d’usage dans tous les cas, fréquents, où la surcharge consiste à enrichir le compor- tement défini au niveau de la classe générique, ce qui implique de conserver ce com- portement tout en lui ajoutant de nouvelles instructions. Ces nouvelles instructions consistent ici à parcourir l’ensemble des fichiers contenus en leur appliquant à leur tour la méthode copier(). 3.1 Tour d’horizon de la programmation objet 129 foreach ($this−>liste_fichiers as $fichier) $fichier−>copier($destination); À chaque étape de la boucle, la variable $fichier référence un des fichiers contenus dans le répertoire, et on demande à cet objet de se copier vers la destination. Il s’agit d’un excellent exemple du processus d’abstraction consistant à voir selon les circonstances ces fichiers comme des objets « génériques » (instance de la classe Fichier) ou comme des objets spécialisées, instances des sous-classes de Fichier. Il faut imaginer ici, pour se limiter à notre exemple, que les fichiers contenus dans un répertoire peuvent être soit des fichiers texte, soit eux-mêmes des répertoires contenant d’autres fichiers. En les considérant uniformément, dans la boucle, comme des instances de Fichier dotés d’une méthode de copie, on s’évite le souci d’avoir à distinguer les différents types d’actions à effectuer en fonction du type précis de fichier manipulé, et on laisse à l’objet lui-même le soin de déterminer la méthode à appliquer. Ce type de programmation peut sembler subtil quand on y est confronté les premières fois, mais il s’acquiert d’autant plus vite qu’on est convaincu du gain apporté par un raisonnement en termes génériques sans avoir à se soucier à chaque instant des détails d’implantation. La simplicité du code obtenu une fois qu’on a résolu le problème de la conception et de la modélisation d’une application objet vient largement compenser l’effort initial à fournir. 3.1.5 Spécialisation et classes abstraites : la classe BD Voyons un exemple complet qui nous permettra également d’introduire un dernier concept. La suite du chapitre consistera à approfondir, par la conception et l’implan- tation de plusieurs classes, tout ce qui est résumé ici. Notre exemple consiste ici à définir, en recourant à la spécialisation objet, un ensemble de classes définissant de manière uniforme les accès à une base de données relationnelle, quelle qu’elle soit. Nous allons prendre comme cibles MySQL, Post- greSQL et ORACLE, avec comme objectif la possibilité de définir des applications qui utilisent indifféremment l’un ou l’autre système, et ce de manière totalement transparente. Le site décrit dans la seconde partie de l’ouvrage s’appuie sur ces classes pour rendre le code compatible avec tout système relationnel. REMARQUE – Le code proposé ici fonctionne correctement, mais il est surtout conçu comme une illustration des concepts orientés-objet. Comme signalé précédemment, l’interface PDO de PHP offre une solution normalisée et plus complète. Reportez-vous page 238 pour une introduction à PDO. En termes de spécialisation, il n’y a aucune raison de dire qu’une classe définissant les interactions avec PostgreSQL hérite de celle accédant à MySQL, ou l’inverse. La bonne question à se poser est toujours « un objet instance de la classe spécialisée est-il aussi un objet de la classe générique ? ». La réponse est clairement non puisqu’un objet accédant à MySQL n’est pas un objet accédant à PostgreSQL et vice-versa. En revanche tous deux sont des exemples d’un concept commun, celui d’objet accédant 130 Chapitre 3. Programmation objet à une base de données. Ce concept commun n’a pas d’instanciation : il n’existe pas d’objet qui fournisse ce comportement indépendamment d’un choix concret d’une base de données spécifique. Quand on a besoin de définir un comportement commun à un ensemble d’objets sans que ce comportement puisse être directement instancié, on utilise la notion de classe abstraite qui permet de factoriser la description des méthodes fournies par tous les objets instances des classes spécialisées, à charge pour ces classes de définir l’implantation appropriée de chacune de ces méthodes. Dans notre cas, il s’agit de définir toutes les méthodes (au sens précis de : noms, liste des paramètres en entrée et en sortie, rôle de la méthode) communes à tous les objets, quel que soit le SGBD auquel ils permettent d’accéder. Il nous faut au minimum : 1. une méthode de connexion ; 2. une méthode d’exécution des requêtes ; 3. une ou plusieurs méthodes pour récupérer le résultat ; 4. une méthode pour traiter les apostrophes ou autres caractères gênants dans les chaînes à insérer dans les requêtes (la technique d’échappement pouvant varier d’un SGBD à un autre) ; 5. une gestion des erreurs. Au moment de la définition de ces méthodes, il faut s’assurer qu’elles corres- pondent à des fonctionnalités qui peuvent être fournies par tous les SGBD. Il faut également réfléchir soigneusement aux paramètres d’entrée et de sortie nécessaires à chaque méthode (on désigne souvent par signature cette spécification des para- mètres). En d’autres termes, on doit définir de manière générique, c’est-à-dire sans se soucier des détails d’implantation, l’interface d’accès à un SGBD en évitant de se laisser influencer, à ce stade, par les particularités de l’un d’entre eux. Voici la classe abstraite BD. Exemple 3.3 exemples/BD.php : La classe abstraite BD <?php // Classe abstraite définissant une interface générique d’accès // à une base de données . Version simplifiée : une déf i n ition // plus complète est donnée avec le site Films abstract class BD { // −−−− Partie privée : les propriétés protected $connexion , $nom_base; // Constructeur de la classe function __construct ($login , $mot_de_passe , $base , $serveur) { // On conserve le nom de la base $this−>nom_base = $base ; 3.1 Tour d’horizon de la programmation objet 131 // Connexion au serveur par appel à une méthode privée $this−>connexion = $this−>connect($login , $mot_de_passe , $base , $serveur); // Lancé d’exception en cas d’erreur if ($this−>connexion == 0) throw new Exception (" Erreur de connexion au SGBD") ; // Fin du constructeur } // Méthodes privées abstract protected function connect ($login , $mot_de_passe , $base , $serveur) ; abstract protected function exec ($requete); // Méthodes publiques // Méthode d ’ exécution d ’une requête public function execRequete ($requete) { if (! $resultat = $this−>exec ($requete)) throw new Exception ("Problème dans l ’exécution de la requête : $requete.<br/> " .$this−>messageSGBD() ) ; return $resultat ; } // Méthodes abstraites // Accès à la ligne suivante , sous forme d ’ objet abstract public function objetSuivant ($resultat); // Accès à la ligne suivante , sous forme de tableau associatif abstract public function ligneSuivante ($resultat); // Accès à la ligne suivante , sous forme de tableau indicé abstract public function tableauSuivant ($resultat); // Echappement des apostrophes et autres préparations à // l ’insertion abstract public function prepareChaine($chaine) ; // Retour du message d’ erreur abst ract public function messageSGBD () ; // Fin de la classe } La définition d’une classe abstraite est préfixée par le mot-clé abstract,demême que toutes les méthodes de la classe pour lesquelles seule la signature est donnée. Toute classe PHP comprenant au moins une méthode abstraite doit elle-même être déclarée comme abstraite. 132 Chapitre 3. Programmation objet REMARQUE – La notion de classe abstraite existe depuis PHP 5, de même que celle d’interface, c oncept assez proche mais encore un peu plus générique, que nous ne présentons pas ici. Dans la classe BD toutes les méthodes sont abstraites, à l’exception du construc- teur et de la méthode execRequete() qui exécute une requête. Ces deux méthodes montrent comment répartir les tâches entre classe abstraite et classe dérivée : 1. tout ce qui est commun à l’ensemble des SGBD doit être factorisé au niveau de la classe abstraite : ici il s’agit de la réaction à adopter si une connexion ou l’exécution d’une requête échoue (on a choisi en l’occurrence de lever une exception) ; 2. tout ce qui est spécifique à un système particulier doit être dévolu à une méthode abstraite qui devra être implantée au niveau de chaque classe dérivée (ici on a donc deux méthodes abstraites connect() et exec() destinées à fournir respectivement le code de connexion et d’exécution de requêtes propres à chaque système). La mention protected introduite ici est une variante de private. Elle signifie que la méthode ou la propriété est invisible de tout script appelant (comme si elle était privée) mais accessible en revanche à toute sous-classe qui peut donc la surcharger. Toute propriété ou méthode déclarée private n’est accessible que dans la classe qui la définit. La méthode exec() par exemple doit être déclarée protected pour pouvoir être redéfinie au niveau des sous-classes de BD. Il est impossible d’instancier un objet d’une classe abstraite. Celle-c n’est d’une certaine manière qu’une spécification contraignant l’implantation des classes déri- vées. Pour être instanciables, ces classes dérivées doivent impérativement fournir une implantation de toutes les méthodes abstraites. Voici la classe BDMySQL (à comparer avec la classe MySQL, page 121). Exemple 3.4 exemples/BDMySQL.php : La classe dérivée BDMySQL <?php // Sous−classe de la classe abstraite BD, implantant l ’accès à / / MySQL require_once("BD.php"); class BDMySQL extends BD { // Pas de propriétés: elles sont héritées de la classe BD // Pas de constructeur : lui aussi est hérité // Méthode connect : connexion à MySQL protected function connect ($login , $mot_de_passe , $base , $serveur) { // Connexion au serveur MySQL . une méthode abstraite qui devra être implantée au niveau de chaque classe dérivée (ici on a donc deux méthodes abstraites connect() et exec() destinées à fournir respectivement le code de connexion et. s’agit de définir toutes les méthodes (au sens précis de : noms, liste des paramètres en entrée et en sortie, rôle de la méthode) communes à tous les objets, quel que soit le SGBD auquel ils permettent. répertoire, et on demande à cet objet de se copier vers la destination. Il s’agit d’un excellent exemple du processus d’abstraction consistant à voir selon les circonstances ces fichiers comme des objets