58 Chapitre 2. Techniques de base $connexion = mysql_pconnect ($pServeur , $pNom, $pMotPasse) ; if (! $connexion) { echo "Désolé , connexion au serveur $pServeur impossible\n" ; exit ; } // Connexion à la base if (! mysql_select_db ($pBase , $connexion)) { echo "Désolé , accès à la base $pBase impossible\n"; echo "<b>Message de MySQL : </b> " . mysql_error ($connexion); exit ; } // On renvoie la variable de connexion return $connexion ; } // Fin de la fonction ?> La première ligne de la fonction est sa signature (ou prototype). Elle définit les paramètres que la fonction accepte. L’interpréteur vérifie, au moment de l’appel à une fonction, que le nombre de paramètres transmis correspond à celui de la signature. L’apport essentiel de Connexion() par rapport à mysql_pconnect() est de tester le cas de l’échec de l’accès au serveur de MySQL et de prendre les mesures en conséquence. Les deux avantages de l’utilisation des fonctions donnés ci-dessus apparaissent dès cette simple implantation : 1. délégation : le script qui se connecte à MySQL a certainement des choses plus importantes à faire que de tester ce genre d’erreur ; 2. partage : c’est le bénéfice le plus apparent ici. On n’aura plus jamais à se soucier de l’échec de l’accès au serveur. De plus, la politique appliquée en cas d’échec est définie en un seul endroit. Ici on a choisi de quitter le script, mais le jour où l’on décide de créer un fichier dans tmp avec toutes les erreurs rencontrées, la modification affecte seulement la fonction Connexion(). REMARQUE – Pour l’instant les messages d’erreur sont affichés à l’écran. Sur un site en production c’est une très mauvaise pratique, pour des raisons d’image et de sécurité. La bonne méthode (quoique légèrement hypocrite) consiste à afficher un message courtois disant que le site est en maintenance, et à envoyer un message à l’administrateur pour lui signaler le problème. Exécution de requêtes Selon le même principe, il est possible de définir des fonctions pour exécuter une requête avec MySQL. Le fichier ExecRequete.php contient trois fonctions: la première pour exécuter une requête, la seconde et la troisième pour récupérer une ligne du résultat respectivement sous forme d’objet (un groupe $o, dont chaque valeur v est accessible par $o->v) ou de tableau associatif (un tableau $t dont chaque valeur v est accessible par $t[’v’]). 2.1 Programmation avec fonctions 59 Exemple 2.2 exemples/ExecRequete.php : Fonctions exécutant une requête <?php // Exécution d’une requête avec MySQL function ExecRequete ( $requete , $connexion) { $resultat = mysql_query ($requete , $connexion); if ($resultat) return $resultat ; else { echo "<b>Erreur dans l ’exécution de la requête ’$requete ’. </b><br/>" ; echo "<b>Message de MySQL : </b> " . mysql_error ($connexion) ; exit ; } } // Fin de la fonction ExecRequete // Recherche de l ’objet suivant function ObjetSuivant ( $resultat ) { return mysql_fetch_object ($resultat ); } // Recherche de la ligne suivante (retourne un tableau) function LigneSuivante ( $resultat ) { return mysql_fetch_assoc ($resultat); } ?> Le regroupement de fonctions concourant à un même objectif – ici l’exécution d’une requête, puis l’exploitation du résultat – est une pratique classique et mène à la notion, elle aussi classique, de module. Un module est un ensemble de fonctionnalités qui correspond à une partie cohérente et bien identifiée d’une application plus large, placées en général dans un même fichier. 2.1.2 Utilisation des fonctions Les définitions de fonctions doivent être placées dans des fichiers séparés, lesquels sont inclus avec l’instruction require() ou require_once() au début de chaque script qui fait appel à elles. Voici l’exemple 1.6, page 37, qui donnait un premier exemple d’accès à la base MySQL à partir d’un script PHP, maintenant réécrit avec quelques-unes des fonctions précédentes. Exemple 2.3 exemples/ExMyPHP4.php : L’exemple 1.6, avec des fonctions <?xml version=" 1.0 " encoding=" iso −8959−1"?> <!DOCTYPE htm l PUBLIC " −/ /W3C / / DTD XHTML 1 . 0 S t r i c t / / EN " "http ://www.w3. org/TR/xhtml1/DTD/xhtml1−strict .dtd"> 60 Chapitre 2. Techniques de base <html xmlns="http ://www.w3. org/1999/xhtml" xml: lang=" fr " > <head> <title >Connexion à MySQL</ title > < link rel=’stylesheet ’ href="films . css" type="text/ css"/> </head> <body> <h1>Interrogation de la table FilmSimple </h1> <?php require_once("Connect.php") ; require_once("Connexion.php") ; require_once("ExecRequete.php") ; $connexion = Connexion(NOM, PASSE, BASE, SERVEUR) ; $ r e s u l t a t = ExecRequete ( "SELECT ∗ FROM FilmSimple " , $connexion) ; while ($film = ObjetSuivant( $resultat )) echo "<b>$film−>titre </b>, paru en $film−>annee , réal isé " ."par $film−>prenom_realisateur $film−>nom_realisateur.<br/>\n" ; ?> </body> </html> On peut apprécier l’économie réalisée dans la taille du code et la lisibilité qui en résulte. Entre autres avantages, il faut noter qu’il n’y a plus dans ce script aucune référence à MySQL. Le jour où l’on choisit d’utiliser – par exemple – PostgreSQL, les modifications ne touchent que les fonctions d’accès à la base et restent transparentes pour les autres scripts. La portabilité d’un code MySQL/PHP sur plusieurs SGBD sera développée dans le chapitre 3. 2.1.3 À propos de require et include Il existe deux instructions pour inclure du code dans un fichier, require (et sa variante require_once)etinclude. La différence est subtile : • require(fichier ) se contente d’inclure le code de fichier dans le script courant, et tout se passe ensuite comme si l’instruction require avait été définitivement remplacée par le contenu de fichier ; • include(fichier ), en revanche, correspond à une inclusion répétitive de fichier, chaque fois que l’instruction est rencontrée. En général, c’est une mauvaise pratique que de placer des instructions dans un fichier pour l’exécuter avec require ou include. Le danger vient du fait que les variables manipulées dans les fichiers inclus viennent se confondre avec celles du script principal, avec des résultats imprévisibles et surtout difficilement détectables. 2.1 Programmation avec fonctions 61 L’utilisation de fonctions est bien préférable car les variables des fonctions sont locales, et l’interaction avec le script se limite aux arguments de la fonction, faci- lement identifiables. En conclusion, il est fortement recommandé d’utiliser seulement require,etde ne placer dans les fichiers inclus que des définitions de fonctions ou de constantes. On est sûr alors que le script ne contient ni variables ni instructions cachées. La fonction include() devrait être réservée aux cas où il faut déterminer, à l’exécution,lefichier à inclure. Un exemple possible est un site multi-langues dans lequel on crée un fichier pour chaque langue gérée. Il est possible d’utiliser require récursivement. Voici par exemple le fichier UtilBD.php que nous utiliserons par la suite pour inclure en une seule fois les décla- rations de constantes et de fonctions pour l’accès à MySQL. Exemple 2.4 exemples/UtilBD.php : Un fichier global d’inclusion des constantes et fonctions <?php // Fonctions et déclarations pour l ’accès à MySQL require_once ("Connect.php"); require_once ("Connexion.php") ; require_once ("ExecRequete .php") ; ?> La variante require_once assure qu’un fichier n’est pas inclus deux fois dans un script (ce qui peut arriver lors d’inclusion transitives, un fichier qui en inclut un autre qui en inclut un troisième ). 2.1.4 Passage par valeur et passage par référence Une fonction prend en entrée des paramètres et renvoie une valeur qui peut alors être stockée dans une variable du script appelant, ou transmise comme paramètre à une autre fonction. Les paramètres sont passés par valeur en PHP. En d’autres termes, le programme appelant et la fonction disposent chacun d’un espace de stockage pour les valeurs de paramètres, et l’appel de la fonction déclenche la copie des valeurs depuis l’espace de stockage du programme appelant vers l’espace de stockage de la fonction. REMARQUE – Attention, les objets sont passés par référence depuis la version 5 de PHP. La conséquence essentielle est qu’une fonction ne peut pas modifier les variables du programme appelant puisqu’elle n’a pas accès à l’espace de stockage de ce der- nier. Une fonction effectue une ou plusieurs opérations, renvoie éventuellement le résultat, mais ne modifie pas ses paramètres. Il s’agit d’une caractéristique importante (« pas d’effet de bord ») pour la lisibilité et la robustesse d’un programme. Elle permet en effet d’estimer avec certitude, en regardant un script constitué d’appels de fonctions, quel est l’effet de chacune. Ce n’est malheureusement plus vrai dès que l’on recours à des pratiques comme l’utilisation de variables globales et le passage par référence. N’utilisez pas de variables globales si vous voulez garder un code sain. Quant au passage des paramètres par référence, il est possible en PHP. La notion de référence 62 Chapitre 2. Techniques de base (identique à celle du C++) correspond à la possibité de désigner un même contenu par plusieurs variables. Soit par exemple le fragment suivant $a=3; $b = &$a; Les variables $a et $b référencent alors le même contenu, dont la valeur est pour l’instant 3. Toute modification du contenu par l’intermédiaire de $a sera visible de $b et réciproquement. Par exemple : $a=5; echo $b; // Affiche la valeur 5 Il faut souligner, pour ceux qui sont familiers avec le langage C, que les références ne sont pas des pointeurs puisque, contrairement à ces derniers, une référence est un symbole désignant un contenu pré-existant (celui d’une autre variable). Pour la question qui nous intéresse ici, elles peuvent cependant servir, comme les pointeurs C, à permettre le partage entre un contenu manipulé par un script et ce même contenu manipulé par une fonction. En passant en effet à une fonction la référence r àunevariablev du script appelant, toute modification effectuée par la fonction sur r impactera v. Il est tentant d’utiliser le passage par référence dans (au moins) les deux cas suivants : 1. pour des fonctions qui doivent renvoyer plusieurs valeurs ; 2. quand les paramètres à échanger sont volumineux et qu’on craint un impact négatif sur les performances. En ce qui concerne le premier point, on peut remplacer le passage par référence par le renvoi de valeurs complexes, tableaux ou objets. Voici un exemple comparant les deux approches. Le premier est une fonction qui prend des références sur les variables du script principal et leur affecte le jour, le mois et l’année courante. Les variables ceJour, ceMois et cetteAnnee sont des références, et permettent donc d’accéder au même contenu que les variables du script appelant la fonction. Notez que le passage par référence est obtenu dans la déclaration de la fonction en préfixant par & le nom des paramètres. Exemple 2.5 exemples/References.php : Fonction avec passage par référence. <?php // Exemple de fonction renvoyant plusieurs valeurs grâce à un // passage par références function aujourdhui_ref (&$ceJour , &$ceMois , &$cetteAnnee ) { // On calcule le jour , le mois et l ’année courante $ceJour = date(’d’); $ceMois = date (’m’); . le cas de l’échec de l’accès au serveur de MySQL et de prendre les mesures en conséquence. Les deux avantages de l’utilisation des fonctions donnés ci-dessus apparaissent dès cette simple implantation. // Fin de la fonction ExecRequete // Recherche de l ’objet suivant function ObjetSuivant ( $resultat ) { return mysql_ fetch_object ($resultat ); } // Recherche de la ligne suivante (retourne. d’effet de bord ») pour la lisibilité et la robustesse d’un programme. Elle permet en effet d’estimer avec certitude, en regardant un script constitué d’appels de fonctions, quel est l’effet de