Exercice 4 — analyse et interprétation du code PHP, dans l’esprit des sessions 2022–2025
Le site VéloCité permet à un abonné de consulter l’historique de ses trajets d’un mois. La page historique.html (figure 1) envoie ses données au script historique.php (figure 2), incomplet, qui interroge la base de données « velocite » dont on donne des extraits (figure 3). La fonction JavaScript verifier(), non reproduite, refuse l’envoi du formulaire si le téléphone saisi ne comporte pas exactement 8 chiffres.
Figure 1 — code HTML de historique.html :
1 <form id="f" name="f" action="historique.php" method="post" onsubmit="return verifier()">
2 <label>N° d'abonné :</label>
3 <input type="number" id="num" name="abonne"><br>
4 <label>Téléphone :</label>
5 <input type="text" id="telAb" name="tel"><br>
6 <label>Mois :</label>
7 <select id="mois" name="mois">
8 <option value="">Choisir un mois</option>
9 <option value="2026-01">Janvier 2026</option>
10 <option value="2026-02">Février 2026</option>
11 <option value="2026-03">Mars 2026</option>
12 </select><br>
13 <label>Dernier total affiché :</label> <span id="total">0.000</span> D<br>
14 <input type="submit" value="Afficher"> <input type="reset" value="Annuler">
15 </form>Figure 2 — code incomplet de historique.php :
1 <?php
2 require("connexion.php");
3 //Tâche N° 1 : Établir une connexion à la base de données.
4 $con = ……①……($host, $user, $pass, $base) or die("Connexion impossible");
5 $num = $_POST["abonne"];
6 //Tâche N° 2 : Supprimer les espaces au début et à la fin du téléphone saisi.
7 $tel = ……②……;
8 $mois = $_POST["mois"];
9 //Tâche N° 3 : Retourner la longueur de la chaîne "tel".
10 if (……③…… != 8)
11 die("Le téléphone doit comporter 8 chiffres.");
12 $req1 = "select nomAbonne, codeFormule from Abonne
13 where numAbonne = $num and telAbonne = '$tel'";
14 //Tâche N° 4 : Exécuter la requête "req1".
15 $res1 = ……④…… or die("Requête erronée : " . mysqli_error($con));
16 //Tâche N° 5 : Retourner le nombre d'enregistrements retournés par "req1".
17 if (……⑤…… == 0)
18 echo "Abonné inconnu ou téléphone erroné.";
19 else
20 {
21 //Tâche N° 6 : Retourner l'enregistrement sous forme d'un tableau indicé.
22 $ab = ……⑥……;
23 echo "<h2>Trajets de $ab[0] (formule $ab[1]) - $mois</h2>";
24 $req2 = "select dateTrajet, dureeMin, montant from Trajet
25 where numAbonne = $num and dateTrajet like '$mois%'
26 order by dateTrajet";
27 $res2 = mysqli_query($con, $req2) or die("Requête erronée");
28 $total = 0;
29 echo "<table border=1><tr><th>Date</th><th>Durée</th><th>Montant</th></tr>";
30 //Tâche N° 7 : Retourner un enregistrement accessible par les noms des champs.
31 while ($t = ……⑦……)
32 {
33 echo "<tr><td>" . $t["dateTrajet"] . "</td><td>" . $t["dureeMin"];
34 echo " min</td><td>" . $t["montant"] . "</td></tr>";
35 $total = $total + $t["montant"];
36 }
37 echo "</table><p>Total du mois : $total D</p>";
38 }
39 //Tâche N° 8 : Se déconnecter de la base de données.
40 ……⑧……;
41 ?>N.B. : le fichier connexion.php définit les variables $host, $user, $pass et $base. Les fonctions mysqli_… admettent leurs équivalents mysql_… (mysql_connect / mysql_select_db, mysql_query, mysql_num_rows, mysql_fetch_row, mysql_fetch_array, mysql_close). Comme dans les sujets, une requête qui échoue fait retourner false à mysqli_query.
Figure 3 — extraits des tables (la table Trajet ci-dessous contient tous les trajets de 2026 des abonnés 1001 et 1004) :
Abonne
| numAbonne | nomAbonne | telAbonne | codeFormule |
|---|---|---|---|
| 1001 | Amel Karray | 20111222 | F2 |
| 1002 | Hedi Ellouze | 22333444 | F3 |
| 1004 | Karim Jmal | 98777888 | F2 |
Trajet
| idTrajet | dateTrajet | dureeMin | montant | idVelo | numAbonne |
|---|---|---|---|---|---|
| 8 | 2026-01-09 | 15 | 0.600 | 106 | 1001 |
| 9 | 2026-01-21 | 55 | 5.500 | 105 | 1004 |
| 11 | 2026-03-15 | 41 | 2.050 | 102 | 1004 |
| 12 | 2026-03-16 | 60 | 7.200 | 104 | 1001 |
| 13 | 2026-03-10 | 38 | 1.520 | 106 | 1003 |
| 14 | 2026-02-27 | 70 | 3.500 | 101 | 1004 |
| 15 | 2026-03-21 | 30 | 3.000 | 105 | 1004 |
| 16 | 2026-03-28 | 25 | 1.250 | 102 | 1004 |
1. Pour chaque ligne de tâche manquante, compléter le tableau en écrivant la fonction correspondante à partir de la liste suivante : mysqli_num_rows($res1) | count($tel) | mysqli_connect | trim($_POST["tel"]) | mysqli_affected_rows($con) | mysqli_fetch_row($res1) | mysqli_query($req1) | mysqli_close($con) | strlen($tel) | mysqli_fetch_rows($res1) | mysqli_query($con, $req1) | mysqli_fetch_array($res2).
| Tâche | Fonction |
|---|---|
| ① (ligne 4) | |
| ② (ligne 7) | |
| ③ (ligne 10) | |
| ④ (ligne 15) | |
| ⑤ (ligne 17) | |
| ⑥ (ligne 22) | |
| ⑦ (ligne 31) | |
| ⑧ (ligne 40) |
2. L’abonné saisit le numéro 1004, le téléphone 98777888, choisit « Mars 2026 », puis clique sur « Afficher ». On considère le script complété.
a. Compléter le tableau suivant.
| Variable du script | Ligne du champ dans historique.html | Valeur reçue |
|---|---|---|
$num (ligne 5) | | | ||
$tel (ligne 7) | | | ||
$mois (ligne 8) | | |
b. Pourquoi la ligne 5 lit-elle $_POST["abonne"] et non $_POST["num"] ?
c. Le script peut-il récupérer le « Dernier total affiché » (ligne 13 de historique.html) par $_POST["total"] ? Justifier.
d. Si l’attribut method de la ligne 1 valait "get", quelles modifications faudrait-il apporter aux lignes 5, 7 et 8 du script ? Quelle différence l’abonné verrait-il dans la barre d’adresse de son navigateur ?
3. L’administrateur du site utilise le script tarifs.php suivant (figure 4) pour augmenter le tarif à la minute des vélos électriques.
Figure 4 — code incomplet de tarifs.php :
1 <?php
2 require("connexion.php");
3 $con = mysqli_connect($host, $user, $pass, $base) or die("Connexion impossible");
4 $hausse = $_POST["hausse"];
5 $req = "update Modele set tarifMinute = tarifMinute + $hausse
6 where typeModele = 'Electrique'";
7 mysqli_query($con, $req) or die("Mise à jour impossible");
8 echo ……………………… . " modèle(s) modifié(s)";
9 mysqli_close($con);
10 ?>Extrait de la table Modele
| codeModele | libModele | typeModele | tarifMinute |
|---|---|---|---|
| M1 | Urbain 3V | Classique | 0.040 |
| M2 | Urbain 7V | Classique | 0.050 |
| M5 | E-City | Electrique | 0.100 |
| M6 | E-Trek | Electrique | 0.120 |
a. Parmi mysqli_num_rows($res), mysqli_affected_rows($con) et count($req), quelle expression faut-il écrire à la ligne 8 pour afficher le nombre de modèles dont le tarif a été modifié, sachant que $con désigne la connexion ?
b. L’administrateur envoie la valeur 0.010 dans le champ hausse. Donner le message affiché et les nouveaux tarifs des modèles concernés.
c. Pourquoi la fonction mysqli_num_rows ne convient-elle pas ici ?
4. Pour chacune des questions suivantes, mettre une croix dans la case correspondant à la réponse correcte (script historique.php complété).
- Si le fichier « connexion.php » appelé à la ligne 2 est inexistant : ☐ une erreur est générée et le script s’arrête ☐ une erreur est générée et le script continue à s’exécuter ☐ le script se connecte avec des paramètres par défaut
- En cas d’erreur de syntaxe dans la requête des lignes 12 et 13, le script : ☐ affiche « Connexion impossible » et s’arrête ☐ affiche « Requête erronée : » suivi du message d’erreur renvoyé par MySQL, et s’arrête ☐ affiche « Abonné inconnu ou téléphone erroné. »
- Si on remplace, à la ligne 22,
mysqli_fetch_row($res1)parmysqli_fetch_array($res1): ☐ une erreur de syntaxe est générée ☐ le comportement du script ne change pas ☐ le nom de l’abonné n’est plus affiché - Si on remplace, à la ligne 31,
mysqli_fetch_array($res2)parmysqli_fetch_row($res2): ☐ le comportement du script ne change pas ☐ le tableau affiche des lignes aux cases vides (clés inexistantes) et le total vaut 0 ☐ la boucle while ne s’exécute plus - La fonction
mysqli_error(ligne 15) retourne : ☐ une chaîne contenant le message d’erreur de la dernière requête ☐ un tableau contenant la liste des erreurs ☐ le numéro de la ligne erronée du script
5. Donner ce qu’affiche le script complété dans chacun des cas suivants :
a. l’exemple de la question 2 ;
b. numéro 1004, téléphone 98777880, mois « Mars 2026 » ;
c. numéro 1001, téléphone 20111222, mois « Février 2026 ».
6. La fonction verifier() refuse déjà un téléphone qui ne comporte pas 8 chiffres. Expliquer pourquoi le script refait ce contrôle aux lignes 9 à 11, avant de construire la requête.
Voir la correction commentéeAprès avoir posé votre démarche
1. Idée : lire le commentaire de chaque tâche, reconnaître la famille (connexion, chaîne, requête, résultat), puis vérifier les paramètres : mysqli_query prend la connexion et la requête, strlen s’applique à une chaîne, count à un tableau.
| Tâche | Fonction |
|---|---|
| ① | mysqli_connect |
| ② | trim($_POST["tel"]) |
| ③ | strlen($tel) |
| ④ | mysqli_query($con, $req1) |
| ⑤ | mysqli_num_rows($res1) |
| ⑥ | mysqli_fetch_row($res1) |
| ⑦ | mysqli_fetch_array($res2) |
| ⑧ | mysqli_close($con) |
Les quatre autres propositions sont écartées : mysqli_fetch_rows n’existe pas ; count($tel) compte les éléments d’un tableau, pas les caractères d’une chaîne ; mysqli_query($req1) oublie la connexion $con ; mysqli_affected_rows($con) compte les lignes touchées par INSERT, UPDATE ou DELETE, pas celles d’un SELECT.
En syntaxe mysql_…, également acceptée, la connexion s’écrit mysql_connect($host, $user, $pass) suivie de mysql_select_db($base), puis mysql_query($req1), mysql_num_rows($res1), mysql_fetch_row($res1), mysql_fetch_array($res2) et mysql_close().
2.a Idée : une clé de $_POST est la valeur de l’attribut name d’un champ ; une liste déroulante envoie la value de l’option choisie.
| Variable du script | Ligne du champ | Valeur reçue |
|---|---|---|
$num | 3 (name="abonne") | "1004" | ||
$tel | 5 (name="tel") | "98777888" | ||
$mois | 7 (<select name="mois">) | "2026-03" (la value de l’option, pas le texte « Mars 2026 ») |
2.b Le navigateur envoie chaque champ sous le nom donné par son attribut name : le champ de la ligne 3 a name="abonne". L’attribut id="num" ne sert qu’au JavaScript (getElementById) et au CSS : la clé $_POST["abonne"] existe, $_POST["num"] n’existe pas.
2.c Non. Un <span> n’est pas un champ de formulaire : seuls les input, select et textarea qui ont un name sont envoyés. $_POST["total"] n’existe pas (avertissement « clé indéfinie », valeur vide). Pour transmettre ce total, il faudrait un champ, par exemple <input type="hidden" name="total">, que le script JavaScript remplirait ; le script PHP pourrait alors lire $_POST["total"].
2.d Il faudrait lire $_GET["abonne"] (ligne 5), trim($_GET["tel"]) (ligne 7) et $_GET["mois"] (ligne 8), au lieu de $_POST[…]. Avec GET, les données sont ajoutées à l’adresse : la barre d’adresse afficherait historique.php?abonne=1004&tel=98777888&mois=2026-03, téléphone compris ; avec POST, elles voyagent dans le corps de la requête HTTP.
3.a Idée : UPDATE ne renvoie pas de lignes ; ce qui compte, ce sont les lignes touchées, que donne la connexion $con. Ligne 8 : echo mysqli_affected_rows($con) . " modèle(s) modifié(s)";
3.b Seuls les modèles de type Electrique sont modifiés : M5 (E-City) passe de 0.100 à 0.110 D/min et M6 (E-Trek) passe de 0.120 à 0.130 D/min. Le script affiche « 2 modèle(s) modifié(s) » ; les modèles Classique gardent leur tarif.
3.c mysqli_num_rows compte les lignes d’un résultat de SELECT. Pour un UPDATE, mysqli_query ne renvoie que true ou false (il n’y a pas de jeu de résultats, d’ailleurs le script ne garde aucune variable $res) : le nombre de lignes modifiées se lit sur la connexion, par mysqli_affected_rows($con).
4. Idée : require arrête le script si le fichier manque ; or die(...) affiche son message puis arrête ; fetch_row ne donne que des indices, fetch_array des indices et des noms.
- 1. Une erreur est générée et le script s’arrête : require provoque une erreur fatale (include aurait seulement produit un avertissement).
- 2. « Requête erronée : » suivi du message d’erreur renvoyé par MySQL, et arrêt : mysqli_query retourne false, donc la partie
or die(...)de la ligne 15 s’exécute. - 3. Le comportement ne change pas : mysqli_fetch_array retourne un tableau accessible par indices et par noms,
$ab[0]et$ab[1]existent toujours. - 4. Le tableau affiche des lignes aux cases vides et le total vaut 0 : mysqli_fetch_row ne crée que les clés 0, 1, 2 ;
$t["dateTrajet"],$t["dureeMin"]et$t["montant"]n’existent pas (valeur vide, avec avertissement : la colonne Durée n’affiche plus que « min »), et$totalreste à 0 (0 + une valeur vide donne 0). La boucle, elle, s’exécute autant de fois. - 5. Une chaîne contenant le message d’erreur de la dernière requête (le code numérique serait donné par mysqli_errno).
5.a Idée : dérouler les echo dans l’ordre, en appliquant chaque requête aux extraits de tables. Le téléphone a 8 caractères ; la requête des lignes 12–13 trouve l’abonné 1004 (1 ligne). La requête des lignes 24–26 retient ses trajets dont la date commence par « 2026-03 », triés par date : seuls les trajets 11, 15, 16 vérifient les deux conditions. Le navigateur affiche :
Trajets de Karim Jmal (formule F2) - 2026-03
| Date | Durée | Montant |
|---|---|---|
| 2026-03-15 | 41 min | 2.050 |
| 2026-03-21 | 30 min | 3.000 |
| 2026-03-28 | 25 min | 1.250 |
Total du mois : 6.3 D
$total est un nombre : 2.050 + 3.000 + 1.250 = 6.3, affiché 6.3 (PHP n’écrit pas les zéros inutiles), alors que chaque $t["montant"] est affiché tel que MySQL le renvoie, avec trois décimales.
5.b Aucun abonné n’a le numéro 1004 et le téléphone 98777880 : mysqli_num_rows retourne 0. Le script affiche seulement « Abonné inconnu ou téléphone erroné. », puis ferme la connexion (ligne 40).
5.c L’abonné 1001 existe (1 ligne), mais il n’a aucun trajet en février 2026 : la boucle while ne s’exécute pas. Le script affiche le titre « Trajets de Amel Karray (formule F2) - 2026-02 », la ligne d’en-tête du tableau (Date, Durée, Montant) sans aucune ligne de trajet, puis « Total du mois : 0 D ».
6. Idée : tout ce qui s’exécute dans le navigateur peut être contourné ; le serveur ne doit jamais faire confiance aux données reçues. verifier() s’exécute sur le poste de l’utilisateur : il suffit de désactiver JavaScript, de modifier la page ou d’envoyer directement une requête à historique.php pour que le contrôle n’ait pas lieu. Le script refait donc le test côté serveur, avant de placer le téléphone dans la requête, pour ne jamais interroger la base avec une donnée non contrôlée. Le contrôle JavaScript reste utile : il évite un aller-retour inutile vers le serveur.