PHP — sessions et espace membre
Comment un site peut vous « reconnaître » d'une page à l'autre, alors que le protocole qui le porte n'a aucune mémoire.
Résumé flash
HTTP est sans mémoire : chaque requête ignore les précédentes. La
session résout cela — le serveur garde les données, le navigateur ne
transporte qu'un jeton. Quatre gestes suffisent :
session_start() en toute première ligne, écrire dans
$_SESSION, lire, puis session_destroy(). Un espace membre, c'est
ça plus un garde en tête de chaque page privée.
🔑
#sans-état #session_start #$_SESSION
#cookie #$_POST #$_GET #header-Location
#exit #session_destroy #htmlspecialchars
#garde
01HTTP ne se souvient de rien
Le protocole HTTP est sans état (stateless) : chaque requête est traitée indépendamment des autres. Le serveur qui vous répond à la page 2 n'a aucun souvenir de la page 1 — même si vous venez de vous y connecter.
Imaginez un guichetier qui oublie tout dès que vous vous éloignez d'un pas. Vous revenez : il vous redemande qui vous êtes. Impossible de tenir un panier d'achat, encore moins une connexion.
La parade du vestiaire : il vous remet un jeton numéroté et range vos affaires derrière lui. Vous ne transportez que le numéro ; lui garde le contenu. C'est exactement le mécanisme d'une session.
C'est précisément pour cela qu'une session est bien plus sûre qu'un cookie qui contiendrait
directement le nom de l'utilisateur : un cookie est modifiable par le visiteur, le contenu de
$_SESSION ne l'est pas.
02Le cycle d'une session
<?php
session_start(); // 1. ouvrir — TOUTE PREMIÈRE LIGNE
$_SESSION["user"] = "Paul"; // 2. écrire
echo $_SESSION["user"]; // 3. lire → Paul
session_destroy(); // 4. détruire
?>| Étape | Instruction | Quand |
|---|---|---|
| Ouvrir | session_start() |
en tête de chaque page qui utilise la session |
| Écrire | $_SESSION["clé"] = valeur; | à la connexion |
| Lire | $_SESSION["clé"] | partout ensuite |
| Détruire | session_destroy() | à la déconnexion |
session_start() avant le moindre caractèrePas seulement avant un echo : avant tout ce qui part vers le
navigateur. Une ligne vide ou une simple espace avant le <?php
suffit à tout casser :
Warning: session_start(): Cannot send session cookie -
headers already sent by (output started at login.php:1)Pourquoi ? La session repose sur un cookie, et un cookie voyage dans les en-têtes HTTP, qui partent avant le contenu. Une fois le contenu commencé, les en-têtes sont déjà envoyés : trop tard.
La même règle vaut pour header("Location: …") — pour exactement la même
raison.
session_start() sur chaque page, sans exceptionC'est l'oubli n° 1 de tout espace membre : on démarre la session sur
login.php, on la lit sur
accueil.php… mais on a oublié session_start() sur cette
deuxième page. Résultat : $_SESSION y est vide, et
l'utilisateur semble déconnecté sans raison.
La session existe bien côté serveur ; c'est la page qui n'a pas demandé à y accéder.
03GET contre POST
L'exercice qui apprend le plus consiste à faire la même chose avec les deux méthodes, puis de comparer. Le résultat est le même, la façon dont il voyage ne l'est pas.
<form action="traitement.php" method="post">
<input type="text" name="nom">
<input type="text" name="prenom">
<input type="submit">
</form>$nom = htmlspecialchars($_POST["nom"]);
$prenom = htmlspecialchars($_POST["prenom"]);
echo "Bonjour $nom $prenom";En GET, il suffit de changer method="get" et
$_POST en $_GET. Mais l'URL devient :
traitement.php?nom=Dupont&prenom=Marc| GET | POST | |
|---|---|---|
| Données | dans l'URL | dans le corps |
| Visibles | oui | non |
| Dans l'historique / les favoris | oui | non |
| Peut être partagée par lien | oui — c'est son intérêt | non |
| Taille | limitée | très large |
| Pour | rechercher, filtrer, paginer | envoyer, modifier, connecter |
POST met les données hors de l'URL : elles n'apparaissent ni dans l'historique, ni dans les favoris, ni dans les journaux du serveur. C'est déjà beaucoup. Mais elles circulent en clair comme le reste.
Ce qui chiffre, c'est HTTPS. Un mot de passe en POST sur du HTTP simple se lit dans Wireshark aussi facilement qu'un mot de passe Telnet — voir le chapitre Dépannage.
04Valider côté serveur
Le deuxième exercice : un email non vide, un mot de passe d'au moins 6 caractères, et des messages d'erreur.
<?php
$erreurs = [];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!isset($_POST["email"]) || trim($_POST["email"]) === "") {
$erreurs[] = "L'email est obligatoire.";
}
if (!isset($_POST["password"]) || strlen($_POST["password"]) < 6) {
$erreurs[] = "Le mot de passe doit faire au moins 6 caractères.";
}
if (count($erreurs) === 0) {
echo "<p class='success'>Connexion réussie</p>";
} else {
foreach ($erreurs as $e) {
echo "<p class='error'>" . htmlspecialchars($e) . "</p>";
}
}
}
?>$_SERVER["REQUEST_METHOD"] : le bon garde-fouSans ce test, le script se déclenche au premier affichage de la page — avant que l'utilisateur ait rien saisi — et affiche toutes les erreurs d'entrée de jeu.
Ce test répond à la question : « suis-je là parce qu'on vient de m'envoyer un formulaire, ou parce qu'on affiche simplement la page ? »
strlen() et non empty() pour un mot de passeLa longueur se mesure avec strlen(). Et surtout : on n'applique
jamais trim() à un mot de passe — une espace peut en faire
légitimement partie, et la retirer changerait silencieusement ce que l'utilisateur a
tapé.
Pour le nom et l'email en revanche, trim() est indispensable : sans lui, un
champ ne contenant qu'une espace passe pour rempli.
05Le compteur de visites
Première vraie utilisation de la mémoire : compter les passages sur la page.
<?php
session_start();
if (!isset($_SESSION["visites"])) {
$_SESSION["visites"] = 1; // première visite
} else {
$_SESSION["visites"]++; // les suivantes
}
echo "Vous avez visité cette page " . $_SESSION["visites"] . " fois.";
?>isset() n'est pas optionnelSans lui, la première visite incrémenterait une variable qui n'existe pas encore. PHP 8 émet alors un Warning: Undefined array key, et le compteur démarre à 1 par accident plutôt que par décision.
La forme condensée, courante en PHP moderne :
$_SESSION["visites"] = ($_SESSION["visites"] ?? 0) + 1;?? signifie « ou, si ça n'existe pas, alors… ». Une ligne au lieu de cinq.
06L'authentification
Identifiants attendus : admin / 1234, vérifiés sans base de
données.
<?php
session_start();
$erreur = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$login = trim($_POST["login"] ?? "");
$mdp = $_POST["password"] ?? "";
if (strtolower($login) === "admin" && $mdp === "1234") {
$_SESSION["user"] = $login; // on mémorise
header("Location: accueil.php"); // on redirige
exit(); // ← ET ON S'ARRÊTE
} else {
$erreur = "Identifiants incorrects";
}
}
?>exit() après header("Location: …")header() se contente d'ajouter un en-tête à la réponse : il
n'interrompt pas le script. Sans exit(), tout le code qui suit
s'exécute quand même — et peut afficher ou modifier des choses avant que le
navigateur ne suive la redirection.
Sur une page protégée, l'oubli est une faille : le contenu privé est envoyé au visiteur non connecté, même si son navigateur change ensuite de page. Il lui suffit de regarder la réponse pour tout lire.
header("Location: …"); est toujours suivi de
exit();strtolower() sur l'identifiant, jamais sur le mot de passeAccepter « Admin », « ADMIN » ou « admin » est un confort légitime : un identifiant n'est pas censé être sensible à la casse.
Un mot de passe, si. Le passer en minuscules diviserait massivement le nombre de combinaisons possibles — exactement ce qu'on cherche à éviter (voir les calculs de force brute du chapitre cybersécurité).
Un identifiant et un mot de passe écrits en dur dans le code conviennent à un exercice, pas à un vrai site. En production :
- les utilisateurs sont en base de données ;
- le mot de passe n'est jamais stocké en clair mais
haché —
password_hash()pour l'enregistrer,password_verify()pour le contrôler ; - même haché, il n'est jamais mis dans
$_SESSION: on n'y garde que l'identifiant.
07Protéger une page privée
Une page réservée doit vérifier, avant tout affichage, que l'utilisateur est bien connecté. On appelle cela un garde.
<?php
session_start();
if (!isset($_SESSION["user"])) { // le garde
header("Location: login.php");
exit();
}
// À partir d'ici, on est certain que l'utilisateur est connecté.
include "header.php";
echo "<h2>Bonjour " . htmlspecialchars($_SESSION["user"]) . " !</h2>";
?>C'est l'erreur la plus instructive du chapitre, et elle est vicieuse. Placer le garde sur
login.php lui-même donne :
<?php
session_start();
if (!isset($_SESSION["user"])) {
header("Location: login.php"); // ← on redirige vers SOI-MÊME
exit();
}Un visiteur non connecté arrive sur login.php, n'est pas connecté, donc est
redirigé vers login.php, où il n'est toujours pas connecté… Le navigateur
s'arrête au bout de quelques dizaines de tours :
ERR_TOO_MANY_REDIRECTS
La page ne cesse de rediriger vers elle-même.La règle : le garde va sur les pages privées. La page de connexion doit rester accessible justement parce qu'on n'est pas connecté.
08La déconnexion
<?php
session_start();
$_SESSION = []; // 1. on vide le tableau
session_destroy(); // 2. on détruit la session côté serveur
header("Location: login.php");
exit();
?>session_destroy() seul ne vide pas $_SESSIONC'est une subtilité que le polycopié ne mentionne pas. session_destroy()
supprime la session stockée sur le serveur, mais le tableau $_SESSION
reste rempli jusqu'à la fin du script en cours.
Tant qu'on redirige immédiatement, cela ne se voit pas. Mais si la page continue et lit
$_SESSION["user"], elle trouvera encore l'utilisateur — et affichera du contenu
privé à quelqu'un qui vient de se déconnecter. D'où le $_SESSION = []; d'abord,
par sécurité.
L'organisation des fichiers
/projet
├── login.php formulaire de connexion — PAS de garde
├── traitement.php vérification des identifiants
├── accueil.php page privée — GARDE
├── logout.php déconnexion
├── header.php en-tête commun
└── footer.php pied de page communUne faute de frappe dans un nom de fichier se paie cher : traintement.php au
lieu de traitement.php, et l'include échoue. Le pire, c'est que
PHP se contente d'un warning et continue — la page s'affiche, mais
incomplète, sans message clair.
C'est exactement le cas où require vaut mieux qu'include : il
s'arrête net et vous dit quel fichier manque.
Un utilisateur se connecte, tout fonctionne. Il clique sur un lien vers
profil.php : le site le croit déconnecté et le renvoie au login. Pourtant sa
session existe toujours. Que manque-t-il ?
session_start() en tête de profil.php.
La session existe bel et bien côté serveur, et le navigateur envoie correctement son
cookie. Mais une page qui n'appelle pas session_start() ne demande jamais à y
accéder : pour elle, $_SESSION est un tableau vide. Le garde
conclut logiquement que personne n'est connecté.
La règle : session_start() en première ligne de
chaque page qui lit ou écrit la session — pas seulement celle qui l'a
ouverte.