Non ordonnée
<ul>
<li>HTML</li>
<li>CSS</li>
</ul>FORMATION DÉVELOPPEMENT WEB
Cours complet et progressif : HTML5 (structure, sémantique, formulaires), CSS3 (Box Model, Flexbox, Grid, responsive), JavaScript (DOM, événements, fetch), MySQL (bases, CRUD, fonctions, jointures), PHP (PDO, CRUD, sécurité), Git & GitHub, puis un projet final complet.
Le point de départ de tout site web : structurer un document, écrire du texte, insérer des images, des listes, des tableaux et des formulaires avec un balisage propre et sémantique.
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Ma page</title>
</head>
<body>
<h1>Bonjour</h1>
</body>
</html>
L'attribut lang déclare la langue de la page, charset="utf-8" garantit l'affichage correct des accents, et la balise viewport rend la page utilisable sur mobile.
head : métadonnées + titre, non visible. charset : accents corrects. Sans viewport : page minuscule/zoom sur mobile.
<!doctype html> <html lang="fr"> <head><meta charset="utf-8"><title>Rina</title></head> <body><h1>Rina</h1></body> </html>
Critères : doctype + html lang + head avec charset + title + h1 avec prénom.
<h1>Titre principal</h1> <h2>Sous-titre</h2> <p>Un paragraphe de texte.</p>
<p>Ceci est <strong>important</strong> et ceci est <em>en italique</em>.</p>
<a href="page2.html">Page suivante</a> <a href="https://exemple.com" target="_blank">Site externe</a> <a href="#contact">Aller au contact</a>
Un seul <h1> par page, puis des <h2>, <h3>… pour organiser une hiérarchie logique, sans sauter de niveau.
strong = importance (SEO/lecteur), em = emphase. target=_blank = nouvel onglet. #... = ancre dans la page.
<h1>À propos</h1> <p>Paragraphe 1...</p> <p>Paragraphe 2...</p> <a href="index.html">Accueil</a>
<img src="photo.jpg" alt="Description de l'image" width="400">
L'attribut alt est obligatoire : il décrit l'image pour l'accessibilité et s'affiche si l'image ne charge pas.
<video src="demo.mp4" controls width="480"></video> <audio src="intro.mp3" controls></audio>
<a href="index.html">
<img src="logo.png" alt="Retour à l'accueil">
</a>
alt : accessibilité + affiché si image cassée. controls : affiche play/volume/plein écran.
<img src="img1.jpg" alt="Description 1" width="300"> <img src="img2.jpg" alt="Description 2" width="300"> <img src="img3.jpg" alt="Description 3" width="300"> <video src="demo.mp4" controls width="480"></video>
Chaque image a un alt différent et explicite.
<ul>
<li>HTML</li>
<li>CSS</li>
</ul><ol>
<li>Analyser</li>
<li>Développer</li>
</ol><table>
<tr><th>Cours</th><th>Durée</th></tr>
<tr><td>HTML5</td><td>2 semaines</td></tr>
</table>
Un tableau HTML sert à afficher des données tabulaires, jamais à construire la mise en page d'un site.
ul = ordre sans importance, ol = étapes ordonnées. th = en-tête gras/centré, td = donnée.
<table> <tr><th>Cours</th><th>Durée</th></tr> <tr><td>HTML5</td><td>2 semaines</td></tr> <tr><td>CSS3</td><td>3 semaines</td></tr> <tr><td>JavaScript</td><td>4 semaines</td></tr> </table>
<form action="traitement.php" method="post">
<label for="nom">Nom</label>
<input type="text" id="nom" name="nom" required>
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<button type="submit">Envoyer</button>
</form>
text / email → saisie libre
checkbox → case à cocher
radio → choix unique
select → liste déroulante
<input type="text" required minlength="3"> <input type="number" min="0" max="100">
L'attribut for d'un <label> doit correspondre à l'id du champ pour que le clic sur le texte active le champ.
GET = URL visible (recherche), POST = corps invisible (sensible). required = bloque l'envoi si vide.
<form method="post"> <label for="nom">Nom</label><input id="nom" name="nom" required minlength="3"> <label for="email">Email</label><input id="email" type="email" name="email" required> <label for="msg">Message</label><textarea id="msg" name="msg" required></textarea> <label><input type="checkbox" required> J'accepte</label> <button>Envoyer</button> </form>
<img src="photo.jpg"> <a href="page.html">clique</a>
1) alt manquant (accessibilité). 2) lien peu explicite. Corrige : alt="..." + texte « Voir la page cours ».
<header>...</header>
<nav>...</nav>
<main>
<section>...</section>
<article>...</article>
</main>
<aside>...</aside>
<footer>...</footer>
Fonctionne visuellement mais n'indique aucun sens : mauvais pour l'accessibilité et le référencement.
Décrivent la structure réelle : lecteurs d'écran et moteurs de recherche comprennent mieux la page.
C'est exactement cette structure (header, nav, main, section, footer) qui est utilisée dans ce cours depuis le cours HTML5.
section = sens (chapitre), div = neutre. Sémantique = lecteurs d'écran + SEO comprennent la page.
<header><nav>...</nav></header> <main><section><article>...</article></section></main> <footer>...</footer>
Remplace chaque div générique par header/nav/main/section/article/footer selon le sens.
Construire, en HTML seul, la structure complète d'un site vitrine prêt à être stylisé dans le cours CSS3.
Structure sémantique30%
Formulaire et validation native20%
Images et médias15%
Listes et tableaux15%
Navigation et liens10%
Validité du code HTML10%
L'élève apprend à transformer une structure HTML en interface propre et responsive. Chaque leçon suit : explication → exemple → pratique → validation → défi.
Syntaxe, sélecteurs, cascade, Box Model et mise en page.
Utiliser Flexbox, Grid, media queries et animations.
Réaliser des exercices puis un projet final sans framework CSS.
CSS signifie Cascading Style Sheets. HTML structure le contenu ; CSS contrôle principalement sa présentation : couleurs, tailles, espacements, bordures, positionnement et mise en page.
<p style="color:red">Bonjour</p>
Rapide pour un test, mais à éviter pour un projet important.
<style>
p { color:red; }
</style>Pratique pour une petite page ou une démonstration.
<link rel="stylesheet" href="style.css">
Le fichier CSS peut être partagé par plusieurs pages.
p {
color: blue;
font-size: 20px;
}
p → sélecteur
color → propriété
blue → valeur
color: blue; → déclaration
/* commentaire CSS */
.card { padding: 20px; }
HTML = structure, CSS = présentation. Externe = partagé, cache, maintenable. color:blue; = déclaration (propriété + valeur).
.profil{background:#fff;border:1px solid #dbe3ee;border-radius:14px;padding:24px;max-width:320px}
.profil h2{margin:0}.profil p{color:#64748b}p { color: blue; } /* élément */
.important { color:red; } /* classe */
#contact { padding:20px; } /* ID */
* { box-sizing:border-box; } /* universel */
h1, h2, h3 { font-family: Arial; }
.card p { color:#475569; }
.card > h2 { margin-top:0; }
input[type="email"] { border:2px solid blue; }
a:hover { text-decoration:underline; }
input:focus { outline:2px solid blue; }
li:first-child { font-weight:bold; }
li:nth-child(2) { color:blue; }
.title::after {
content:"";
display:block;
width:50px;
height:3px;
}
Plusieurs règles peuvent cibler le même élément. Le navigateur tient compte notamment de l'importance, de la spécificité et de l'ordre des règles.
p { color:blue; }
.message { color:green; }
#special { color:red; }
<p id="special" class="message">Bonjour</p>
Fort : #id
Moyen : classe, attribut, pseudo-classe
Faible : élément
À spécificité égale, une règle déclarée plus tard peut gagner. !important doit rester exceptionnel.
Classe = réutilisable (.), ID = unique (#). .card p = tous descendants, .card > p = enfants directs. #special = spécificité max.
#special gagne (ID = spécificité forte). À spécificité égale, la dernière règle gagne. !important à éviter.
.card {
width: 300px;
padding: 20px;
border: 2px solid #334155;
margin: 30px;
}
* { box-sizing: border-box; }
Avec border-box, la largeur déclarée inclut contenu, padding et bordure.
.card {
max-width: 600px;
min-height: 150px;
border-radius: 12px;
overflow: hidden;
}
.card{width:300px;box-sizing:border-box}
.c1{padding:10px;border:2px solid}.c2{padding:20px;border:5px solid}.c3{padding:30px;border:10px solid}Vérifie dans DevTools → onglet Computed → Box Model.
p { color: red; }
.box { color:#1d4ed8; background-color:rgb(241,245,249); }
.overlay { background-color:rgba(15,23,42,.8); }
:root {
--primary:#2563eb;
--text:#1e293b;
--surface:#ffffff;
}
body { color:var(--text); }
.button { background:var(--primary); }
body {
font-family:Arial, sans-serif;
font-size:16px;
line-height:1.6;
}
h1 { font-size:2.5rem; font-weight:700; }
p { letter-spacing:.01em; }
h1 { text-align:center; }
a { text-decoration:none; }
.upper { text-transform:uppercase; }
px — unité fixe
% — proportion
rem — relatif à la racine
em — relatif au contexte
vw / vh — fenêtre
:root{--primary:#2563eb;--text:#1e293b;--surface:#fff;--accent:#22c55e}
h1{color:var(--text)}p{color:var(--text)}.btn{background:var(--primary);color:#fff}.container { display:flex; }
.menu {
display:flex;
flex-direction:row;
gap:20px;
}
Avec row, l'axe principal est horizontal ; avec column, il est vertical.
.menu {
justify-content:space-between;
align-items:center;
}
Valeurs courantes de justify-content : flex-start, center, flex-end, space-between, space-around, space-evenly.
.cards {
display:flex;
flex-wrap:wrap;
gap:20px;
}
.card { flex:1; }
.zone{height:300px;display:flex;align-items:center;justify-content:center}.grid { display:grid; }
.grid {
display:grid;
grid-template-columns:repeat(3, 1fr);
gap:20px;
}
.gallery {
display:grid;
grid-template-columns:
repeat(auto-fit, minmax(220px, 1fr));
gap:20px;
}
.featured { grid-column:span 2; }
Excellent pour un alignement principalement sur un axe.
Excellent pour une organisation en deux dimensions.
.galerie{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:20px}<meta name="viewport" content="width=device-width, initial-scale=1.0">
@media (max-width:768px) {
.layout { grid-template-columns:1fr; }
.menu { flex-direction:column; }
}
.cards { grid-template-columns:1fr; }
@media (min-width:768px) {
.cards { grid-template-columns:repeat(2,1fr); }
}
@media (min-width:1100px) {
.cards { grid-template-columns:repeat(3,1fr); }
}
img {
max-width:100%;
height:auto;
}
@media(max-width:768px){.layout{grid-template-columns:1fr}.menu{flex-direction:column}}HTML inchangé : seule la CSS s'adapte.
.card { width:300px; padding:30px; border:5px solid; }Sans box-sizing:border-box la carte fait 370px, pas 300. Ajoute *{box-sizing:border-box}.
.button {
background:#2563eb;
transition:background-color .2s ease, transform .2s ease;
}
.button:hover {
background:#1d4ed8;
transform:translateY(-2px);
}
.card:hover { transform:translateY(-5px); }
.image:hover { transform:scale(1.05); }
Fonctions courantes : translate, scale, rotate, skew.
@keyframes pulse {
0% { transform:scale(1); }
50% { transform:scale(1.08); }
100% { transform:scale(1); }
}
.badge { animation:pulse 1.5s infinite; }
Respecter les utilisateurs qui préfèrent réduire les animations.
@media (prefers-reduced-motion:reduce) {
*,*::before,*::after {
animation-duration:.01ms !important;
transition-duration:.01ms !important;
}
}
.carte{transition:transform .3s,box-shadow .3s}
.carte:hover{transform:translateY(-5px);box-shadow:0 14px 30px rgba(0,0,0,.12)}
@keyframes apparait{from{opacity:0;transform:translateY(12px)}to{opacity:1}}Construire une page professionnelle comprenant hero, formations, avantages/statistiques, témoignages, appel à l'action et footer.
Structure HTML15%
Maîtrise CSS20%
Sélecteurs / Box Model15%
Flexbox / Grid15%
Responsive15%
Design / lisibilité10%
Explication / autonomie10%
L'élève doit être capable de prendre une page HTML simple et de produire seul une interface propre, responsive et cohérente en CSS avant de passer à JavaScript.
Donner vie aux pages HTML/CSS : variables, logique, tableaux, DOM, événements, formulaires, stockage local et communication avec une API.
let age = 20; const nom = "Nina"; age = 21; // let peut être réassigné // nom = "Léa"; // erreur : const est constant
On utilise let pour une valeur qui change et const par défaut. var est ancien et à éviter.
number → 12, 3.5
string → "bonjour"
boolean → true / false
undefined / null → valeur absente
let a = 5, b = 2;
console.log(a + b, a > b, a === "5");
const message = `${nom} a ${age} ans.`;
=== compare la valeur et le type, contrairement à ==.
let = réassignable, const = constant. var = fuites de scope. === = valeur + type.
const nom="Ordinateur", prix=250000, disponible=true;
console.log(`${nom} : ${prix} Ar, dispo : ${disponible}`);if (age >= 18) {
console.log("Majeur");
} else if (age >= 13) {
console.log("Adolescent");
} else {
console.log("Enfant");
}
const statut = age >= 18 ? "Majeur" : "Mineur";
switch (niveau) {
case "debutant": console.log("HTML5"); break;
case "intermediaire": console.log("JavaScript"); break;
default: console.log("Niveau inconnu");
}
Les opérateurs logiques &&, || et ! permettent de combiner plusieurs conditions.
Ternaire = 1 valeur courte. Sans break, tous les case suivants s'exécutent (fallthrough).
function mention(n){if(n>=16)return "A";if(n>=14)return "B";if(n>=10)return "C";return "D"}for (let i = 0; i < 5; i++) {
console.log(i);
}
let n = 3;
while (n > 0) {
console.log(n);
n--;
}
const fruits = ["pomme", "banane", "mangue"]; console.log(fruits.length); console.log(fruits[0]); fruits.forEach(f => console.log(f));
fruits.push("kiwi");
const majuscules = fruits.map(f => f.toUpperCase());const longs = fruits.filter(f => f.length > 5); const trouve = fruits.find(f => f === "kiwi");
for = répétitions connues, while = condition. map retourne un nouveau tableau, forEach non.
const eleves=[{nom:"Rina",note:14},{nom:"Toto",note:8}];
console.log(eleves.filter(e=>e.note>=10));function addition(a, b) {
return a + b;
}
const multiplier = (a, b) => a * b;
function saluer(nom = "invité") {
return `Bonjour ${nom}`;
}
const etudiant = {
nom: "Rina",
note: 15,
reussi() { return this.note >= 10; }
};
console.log(etudiant.nom, etudiant["note"], etudiant.reussi());
Classique = this dynamique, fléchée = this lexical. Accès : obj.prop ou obj["prop"].
const produit={nom:"Riz",prix:5000,stock:0,enRupture(){return this.stock===0}};const titre = document.querySelector("h1");
const carte = document.getElementById("carte-1");
const items = document.querySelectorAll(".item");
titre.textContent = "Nouveau titre"; carte.innerHTML = "<strong>Mis à jour</strong>";
carte.classList.add("actif");
carte.classList.toggle("cachee");
carte.style.color = "blue";
textContent = texte brut sûr, innerHTML = HTML (risque XSS). classList = sépare style et logique.
document.querySelectorAll(".card").forEach(c=>c.addEventListener("click",()=>c.classList.add("vu")));const bouton = document.querySelector(".btn");
bouton.addEventListener("click", () => {
console.log("Bouton cliqué");
});
document.querySelector("form").addEventListener("submit", (e) => {
e.preventDefault();
console.log("Formulaire intercepté");
});
click → clic souris
input → saisie en direct
submit → envoi de formulaire
keydown → touche pressée
preventDefault = empêche recharge/soumission native. input = chaque frappe, change = à la sortie du champ.
let compteur=0;btn.addEventListener("click",()=>{compteur++;affiche.textContent=compteur});if (note == "10") { console.log("Admis"); }== convertit les types : "10abc" peut passer. Utilise === + Number().
const email = document.querySelector("#email").value;
const age = Number(document.querySelector("#age").value);
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!email.includes("@")) {
erreur.textContent = "Email invalide";
return;
}
erreur.textContent = "";
console.log("Formulaire valide");
});
const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; console.log(regexEmail.test(email));
JS = contournable → PHP doit revalider. Erreur via textContent dans un <span> sans recharger.
if(nom.trim().length<3)errNom.textContent="Nom trop court"; if(!regexEmail.test(email))errEmail.textContent="Email invalide"; if(mdp.length<6)errMdp.textContent="6 caractères minimum";
localStorage.setItem("theme", "sombre");
console.log(localStorage.getItem("theme"));
localStorage.removeItem("theme");
const taches = [{ texte: "Réviser CSS", faite: false }];
localStorage.setItem("taches", JSON.stringify(taches));
const stockees = JSON.parse(localStorage.getItem("taches"));
localStorage ne stocke que du texte : un tableau ou objet doit être converti en chaîne JSON avant d'être sauvegardé.
localStorage = texte seul → stringify obligatoire. getItem inexistant = null.
const fav=JSON.parse(localStorage.getItem("favoris")||"[]");
function sauver(){localStorage.setItem("favoris",JSON.stringify(fav))}fetch("https://api.exemple.com/produits")
.then(reponse => reponse.json())
.then(donnees => console.log(donnees))
.catch(erreur => console.error(erreur));
async function chargerProduits() {
try {
const reponse = await fetch("https://api.exemple.com/produits");
const donnees = await reponse.json();
console.log(donnees);
} catch (erreur) {
console.error("Erreur :", erreur);
}
}
donnees.forEach(p => {
liste.innerHTML += `<li>${p.nom} — ${p.prix} €</li>`;
});
async/await = lecture linéaire. Erreur réseau = page vide/crash → toujours try/catch.
async function charger(){try{const r=await fetch("https://restcountries.com/v3.1/all");const d=await r.json();liste.innerHTML=d.slice(0,10).map(p=>`<li>${p.name.common}</li>`).join("")}catch(e){console.error(e)}}Construire une application de liste de tâches entièrement fonctionnelle côté navigateur, sans rechargement de page.
Logique JavaScript30%
Manipulation du DOM20%
Gestion des événements20%
localStorage / JSON15%
Interface et responsive15%
Stocker et interroger des données comme un pro : créer une base, construire des tables liées, pratiquer le CRUD, calculer avec COUNT / AVG / SUM et relier les tables avec ou sans INNER JOIN.
CREATE DATABASE formation CHARACTER SET utf8mb4; USE formation;
CREATE TABLE etudiants (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL,
age INT,
note DECIMAL(4,2) DEFAULT 0,
date_inscription DATE DEFAULT (CURRENT_DATE)
);
CREATE TABLE cours (
id INT AUTO_INCREMENT PRIMARY KEY,
titre VARCHAR(150) NOT NULL,
duree_heures INT DEFAULT 20,
prix DECIMAL(8,2) DEFAULT 0
);
CREATE TABLE inscriptions (
id INT AUTO_INCREMENT PRIMARY KEY,
etudiant_id INT NOT NULL,
cours_id INT NOT NULL,
date_inscription DATE DEFAULT (CURRENT_DATE),
FOREIGN KEY (etudiant_id) REFERENCES etudiants(id) ON DELETE CASCADE,
FOREIGN KEY (cours_id) REFERENCES cours(id) ON DELETE CASCADE
);
Types à retenir : INT pour les nombres entiers, VARCHAR(n) pour le texte court, DECIMAL pour les prix/notes, DATE pour les dates, AUTO_INCREMENT pour un identifiant automatique.
DATABASE = conteneur, TABLE = structure. PK = identifiant unique. UNIQUE = pas de doublon d'email.
CREATE DATABASE formation CHARACTER SET utf8mb4; USE formation; -- puis les 3 CREATE TABLE du cours, puis SHOW TABLES; doit afficher 3 lignes.
DELETE FROM etudiants;
Sans WHERE tu supprimes TOUTE la table ! Toujours DELETE ... WHERE id = ? + sauvegarde avant.
INSERT INTO etudiants (nom, email, age, note) VALUES
('Rina Rakoto', 'rina@ecole.mg', 21, 14.5),
('Léa Randria', 'lea@ecole.mg', 19, 16),
('Nina Razafy', 'nina@ecole.mg', 22, 9.5);
INSERT INTO etudiants (nom, email)
VALUES ('Toto', 'toto@ecole.mg');-- tout lire SELECT * FROM etudiants; -- lire filtré et trié SELECT id, nom, note FROM etudiants WHERE note >= 10 ORDER BY note DESC;
UPDATE etudiants SET note = 12.5 WHERE id = 3;
DELETE FROM etudiants WHERE id = 3;
SELECT * FROM etudiants WHERE age BETWEEN 18 AND 25; SELECT * FROM etudiants WHERE nom LIKE 'R%'; SELECT * FROM etudiants WHERE id IN (1, 2, 4); SELECT * FROM etudiants ORDER BY nom ASC LIMIT 5;
C=INSERT, R=SELECT, U=UPDATE, D=DELETE. Sans WHERE tu modifies/supprimes TOUTE la table !
INSERT INTO etudiants(nom,email,age,note) VALUES (...5 lignes...); SELECT * FROM etudiants WHERE note>=10; UPDATE etudiants SET note=15 WHERE id=1; DELETE FROM etudiants WHERE email='test@ecole.mg';
-- Nombre d'étudiants SELECT COUNT(*) AS total FROM etudiants; -- Moyenne des notes SELECT AVG(note) AS moyenne FROM etudiants; -- Somme des prix des cours SELECT SUM(prix) AS chiffre FROM cours; -- Meilleure et moins bonne note SELECT MAX(note) AS meilleure, MIN(note) AS moins_bonne FROM etudiants;
SELECT
CASE WHEN note >= 10 THEN 'Admis' ELSE 'Ajourné' END AS resultat,
COUNT(*) AS nb,
AVG(note) AS moyenne
FROM etudiants
GROUP BY resultat;
SELECT cours_id, COUNT(*) AS nb_inscrits FROM inscriptions GROUP BY cours_id HAVING COUNT(*) > 5;
ROUND(AVG(note),2) → arrondir à 2 décimales
UPPER(nom) / LOWER(nom) → majuscules / minuscules
CONCAT(nom,' (',email,')') → assembler du texte
NOW() / CURDATE() → date et heure actuelles
SELECT nom, UPPER(nom) AS nom_maj, ROUND(note,1) AS note_arrondie FROM etudiants; SELECT titre, prix, prix * 1.2 AS prix_ttc FROM cours;
WHERE = avant calcul, HAVING = après GROUP BY. COUNT(note) ignore les NULL, COUNT(*) les compte.
SELECT COUNT(*) total, AVG(note) moy, MAX(note) maxi, MIN(note) mini FROM etudiants WHERE note>=10;
-- SANS le mot JOIN : on cite les 2 tables, on relie avec WHERE SELECT etudiants.nom, cours.titre FROM etudiants, inscriptions, cours WHERE inscriptions.etudiant_id = etudiants.id AND inscriptions.cours_id = cours.id;
Sans le WHERE, MySQL fait un produit cartésien (chaque étudiant × chaque cours). Le WHERE garde uniquement les bonnes correspondances.
SELECT e.nom AS etudiant, c.titre AS cours, i.date_inscription FROM etudiants e, inscriptions i, cours c WHERE e.id = i.etudiant_id AND c.id = i.cours_id ORDER BY e.nom;
-- Nombre d'inscriptions par étudiant (sans JOIN) SELECT e.nom, COUNT(*) AS nb_cours FROM etudiants e, inscriptions i WHERE e.id = i.etudiant_id GROUP BY e.nom;
Sans WHERE = produit cartésien (explosion). Alias = raccourcis lisibles.
SELECT e.nom,c.titre FROM etudiants e,inscriptions i,cours c WHERE e.id=i.etudiant_id AND c.id=i.cours_id; SELECT c.titre,AVG(e.note) FROM etudiants e,inscriptions i,cours c WHERE e.id=i.etudiant_id AND c.id=i.cours_id GROUP BY c.titre;
-- AVEC INNER JOIN : relation écrite avec ON SELECT e.nom AS etudiant, c.titre AS cours FROM etudiants e INNER JOIN inscriptions i ON e.id = i.etudiant_id INNER JOIN cours c ON c.id = i.cours_id;
FROM etudiants, inscriptions WHERE etudiants.id = inscriptions.etudiant_id. Court mais mélange filtres et relations.
FROM etudiants INNER JOIN inscriptions ON .... Sépare la relation (ON) du filtre (WHERE) : plus lisible sur 3-4 tables.
-- Tous les étudiants, même ceux sans inscription SELECT e.nom, c.titre FROM etudiants e LEFT JOIN inscriptions i ON e.id = i.etudiant_id LEFT JOIN cours c ON c.id = i.cours_id;
INNER JOIN ne garde que les lignes qui correspondent des deux côtés. LEFT JOIN garde toutes les lignes de gauche, avec NULL à droite s'il n'y a rien.
SELECT c.titre,
COUNT(i.id) AS nb_inscrits,
ROUND(AVG(e.note),2) AS moyenne_notes
FROM cours c
LEFT JOIN inscriptions i ON c.id = i.cours_id
LEFT JOIN etudiants e ON e.id = i.etudiant_id
GROUP BY c.titre;
INNER JOIN = lisible dès 3 tables (ON vs WHERE). INNER = correspondances seules, LEFT = garde la gauche + NULL.
SELECT e.nom,c.titre FROM etudiants e INNER JOIN inscriptions i ON e.id=i.etudiant_id INNER JOIN cours c ON c.id=i.cours_id; SELECT e.nom,c.titre FROM etudiants e LEFT JOIN inscriptions i ON e.id=i.etudiant_id LEFT JOIN cours c ON c.id=i.cours_id;
Créer de zéro une base complète et prouver qu'on sait l'interroger.
Création base et tables25%
CRUD25%
Fonctions COUNT / AVG / SUM25%
Jointures avec et sans INNER JOIN25%
Passer du navigateur au serveur : traiter des formulaires, gérer des sessions, stocker des données dans une base MySQL et sécuriser une application.
<?php $nom = "Rina"; $age = 21; echo "Bonjour " . $nom; ?>
$prix = 19.90; $actif = true; echo "Prix : " . $prix . " € (actif : " . var_export($actif, true) . ")";
<p>Bonjour <?= $nom ?>, vous avez <?= $age ?> ans.</p>
Un fichier .php doit être exécuté par un serveur (ex. via XAMPP/MAMP ou php -S localhost:8000), pas ouvert directement dans le navigateur.
$nom avec $. echo affiche, print retourne 1. <?= = echo court.
<p>Bonjour <?=$nom?>, vous avez <?=$age?> ans, moyenne <?=$moy?></p>
function addition($a, $b) {
return $a + $b;
}
echo addition(3, 4);
$fruits = ["pomme", "banane", "mangue"]; $etudiant = ["nom" => "Léa", "note" => 15]; echo $fruits[0]; echo $etudiant["nom"];
foreach ($fruits as $fruit) {
echo $fruit . "<br>";
}
foreach ($etudiant as $cle => $valeur) {
echo "$cle : $valeur<br>";
}
Indexé = [0,1,2], associatif = ["nom"=>...]. Fonction = réutilisable, testable, lisible.
$produits=["Riz"=>5000,"Huile"=>12000];
foreach($produits as $n=>$p){echo "<tr><td>$n</td><td>$p</td></tr>";}<form method="post" action="traitement.php">
<input type="text" name="nom">
<button>Envoyer</button>
</form>
$nom = $_POST["nom"] ?? ""; echo "Bonjour " . htmlspecialchars($nom);
Données visibles dans l'URL, adaptées à une recherche ou un filtre.
Données invisibles dans l'URL, adaptées à un formulaire sensible (connexion, inscription).
htmlspecialchars = anti-XSS. GET = recherche/filtre, POST = données sensibles.
$nom=htmlspecialchars($_POST["nom"]??""); echo "Merci $nom, message reçu !";
<?php session_start(); $_SESSION["utilisateur"] = "Rina"; ?>
session_start();
if (isset($_SESSION["utilisateur"])) {
echo "Connecté en tant que " . $_SESSION["utilisateur"];
}
// déconnexion
session_destroy();
session_start();
if ($_POST["email"] === "test@ecole.com" && $_POST["mdp"] === "1234") {
$_SESSION["connecte"] = true;
header("Location: dashboard.php");
}
session_start avant tout HTML sinon "headers already sent". Protège avec if(empty($_SESSION["connecte"])) redirect.
// login.php : session_start(); if ok $_SESSION["connecte"]=true;
// dashboard.php : session_start(); if(empty($_SESSION["connecte"])){header("Location: login.php");exit;}CREATE TABLE etudiants (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE
);
CREATE TABLE inscriptions (
id INT AUTO_INCREMENT PRIMARY KEY,
etudiant_id INT,
cours_id INT,
FOREIGN KEY (etudiant_id) REFERENCES etudiants(id),
FOREIGN KEY (cours_id) REFERENCES cours(id)
);
PK = unicité. Table liaison = relation N-N (un étudiant → N cours).
etudiants(id PK, nom, email UNIQUE), cours(id PK, titre), inscriptions(id PK, etudiant_id FK, cours_id FK).
$pdo = new PDO(
"mysql:host=localhost;dbname=formation;charset=utf8",
"root",
""
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("SELECT * FROM etudiants WHERE id = ?");
$stmt->execute([$id]);
$etudiant = $stmt->fetch();
Une requête préparée sépare le code SQL des données envoyées : c'est la protection principale contre les injections SQL.
Variable collée = injection SQL. execute() envoie les valeurs séparément, protégées.
$pdo=new PDO("mysql:host=localhost;dbname=formation;charset=utf8","root","");
$stmt=$pdo->prepare("SELECT * FROM etudiants");$stmt->execute();
foreach($stmt->fetchAll() as $e){echo htmlspecialchars($e["nom"])."<br>";}$stmt = $pdo->prepare( "INSERT INTO etudiants (nom, email) VALUES (?, ?)" ); $stmt->execute([$nom, $email]);
$stmt = $pdo->query("SELECT * FROM etudiants");
$tous = $stmt->fetchAll();$stmt = $pdo->prepare( "UPDATE etudiants SET nom = ? WHERE id = ?" ); $stmt->execute([$nouveauNom, $id]);
$stmt = $pdo->prepare( "DELETE FROM etudiants WHERE id = ?" ); $stmt->execute([$id]);
C=INSERT, R=SELECT, U=UPDATE, D=DELETE. Préparée partout = injection impossible même en DELETE.
4 pages : ajouter (INSERT), lister (SELECT), modifier (UPDATE ... WHERE id=?), supprimer (DELETE ... WHERE id=?), toujours en préparée.
class Etudiant {
public string $nom;
public float $note;
public function __construct(string $nom, float $note) {
$this->nom = $nom;
$this->note = $note;
}
public function aReussi(): bool {
return $this->note >= 10;
}
}
$e = new Etudiant("Rina", 14);
echo $e->nom . " : " . ($e->aReussi() ? "Admis" : "Ajourné");
__construct = initialise l'objet. Classe = moule, objet = exemplaire.
class Cours{public function __construct(public string $titre,public int $duree){}
public function description():string{return "$this->titre ($this->duree h)";}}$sql = "SELECT * FROM users WHERE email = '$email'";
Injection SQL ! Utilise prepare("... WHERE email = ?") + execute([$email]).
// Dangereux :
$sql = "SELECT * FROM users WHERE email = '$email'";
// Sûr :
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
echo htmlspecialchars($commentaireUtilisateur);
$hash = password_hash($motDePasse, PASSWORD_DEFAULT); // à la connexion : password_verify($motDePasseSaisi, $hash);
Toujours : requêtes préparées
Toujours : htmlspecialchars à l'affichage
Jamais : mot de passe en clair
Clair = volé = compromis. Protection = préparée + htmlspecialchars + password_hash.
$hash=password_hash($_POST["mdp"],PASSWORD_DEFAULT);
$stmt=$pdo->prepare("INSERT INTO users(email,mdp) VALUES(?,?)");
$stmt->execute([$_POST["email"],$hash]);Développer une application de gestion d'étudiants avec base de données, en appliquant le CRUD complet et les bonnes pratiques de sécurité.
Base de données et modèle relationnel20%
PDO et requêtes préparées20%
CRUD fonctionnel25%
Sessions / authentification15%
Sécurité20%
Suivre l'historique d'un projet, travailler en branches et collaborer via GitHub comme dans un vrai environnement professionnel.
git init git status
git add index.html style.css git add . git commit -m "Ajout de la page d'accueil"
git log --oneline
Un commit est une photo datée et commentée du projet. Un message de commit clair explique ce qui a changé et pourquoi.
add = prépare, commit = photo. "modif" = inutile dans 6 mois, décris le pourquoi.
git init; git add .; git commit -m "Base CSS3"; ...modifs...; git commit -m "Ajoute Flexbox"; git commit -m "Ajoute Grid"; git log --oneline
git branch feature-navbar git checkout feature-navbar # ou en une commande : git checkout -b feature-navbar
git checkout main git merge feature-navbar
On garde main toujours stable et on développe chaque fonctionnalité dans sa propre branche, fusionnée une fois terminée et testée.
main = stable. merge = fusionne la branche dans la courante.
git checkout -b feature-section; ...travail...; git add .; git commit -m "Ajoute section avis"; git checkout main; git merge feature-section
git remote add origin https://github.com/utilisateur/projet.git git push -u origin main
git pull origin main
git clone https://github.com/utilisateur/projet.git
push = envoie, pull = récupère. clone = copie un existant, init = crée un nouveau.
git remote add origin https://github.com/pseudo/projet.git; git push -u origin main
git commit -m "modif"
Message inutile. Écris : git commit -m "Corrige le menu mobile (flex-direction)".
<<<<<<< HEAD <h1>Bienvenue sur le site</h1> ======= <h1>Accueil de la formation</h1> >>>>>>> feature-navbar
Git ne sait pas laquelle des deux versions garder : il faut modifier le fichier à la main, choisir le contenu final puis committer.
Une branche par fonctionnalité, fusionnée après relecture.
Proposition de fusion sur GitHub, discutée avant d'être acceptée.
Marqueurs <<</===/>>>. PR = demande de relecture avant fusion.
Repère <<<<<<<, =======, >>>>>>>. Garde la bonne version, supprime les marqueurs, git add + commit.
Transformer un projet local en dépôt GitHub propre et présentable, comme dans une équipe de développement.
Qualité des commits30%
Utilisation des branches25%
README et .gitignore20%
Organisation du dépôt25%
Coche quand tu sais faire seul, sans copier. Ta progression est sauvegardée dans ton navigateur.
| Compétence | Vu | Pratiqué | Autonome |
|---|---|---|---|
| Page HTML5 sémantique + formulaire | |||
| Interface CSS3 responsive (Flex/Grid) | |||
| Todo JS + localStorage + fetch | |||
| Base MySQL + CRUD + jointures | |||
| CRUD PHP/PDO sécurisé + sessions | |||
| Git : branches + GitHub + PR |
0% autonome
session_start() après du HTML → "headers already sent". Toujours en 1ʳᵉ ligne.
Supprime tout ! Toujours tester le SELECT d'abord.
Jamais ! password_hash + password_verify.
Vérifie tes ON et tes clés étrangères.
Écris du code et vois le résultat aussitôt. Aucune installation requise.
Teste SELECT, COUNT, AVG, WHERE sur des données d'exemple.
3 questions par cours. Objectif : 3/3 = validé, sinon revois la leçon indiquée. Le score est calculé en direct.
box-sizing:border-box fait quoi ?=== vs == ?Télécharge les cases cochées (compétences + checklists + QCM) en fichier texte pour le dossier de l'élève.
Entre le nom de l'élève, génère une attestation à afficher.
Formation Développement Web — HTML5 • CSS3 • JavaScript • MySQL • PHP • Git
Progression validée sur la plateforme. Projets : site vitrine, landing responsive, Todo App, base MySQL, CRUD PHP sécurisé, dépôt GitHub.
Le projet final réunit tous les modules : CSS3, JavaScript, PHP/MySQL et Git. L'objectif est de livrer un site complet de centre de formation, avec un espace d'administration relié à une base de données.
Rédiger un cahier des charges simple : pages nécessaires, fonctionnalités, utilisateurs (visiteur / administrateur), puis dessiner un wireframe rapide de chaque page.
Concevoir le schéma MySQL (formations, étudiants, inscriptions), créer les tables et insérer quelques données de test.
Construire le site avec HTML/CSS3 pour l'interface, JavaScript pour l'interactivité côté client, et PHP/PDO pour le CRUD des formations et des inscriptions.
Vérifier chaque formulaire, tester le site sur mobile/tablette/desktop, corriger les erreurs de sécurité (requêtes préparées, échappement) et de responsive.
Déposer le projet sur GitHub avec un historique de commits propre, préparer une courte soutenance expliquant les choix techniques.
Analyse et conception10%
Base de données15%
Front-end (HTML/CSS3/JS)25%
Back-end (PHP/MySQL)25%
Sécurité et tests15%
Git/GitHub et présentation10%
L'élève est désormais capable de concevoir, développer et livrer seul une application web complète : interface responsive, logique côté client, back-end sécurisé et gestion de version professionnelle.