Technologies Web - GLIRT INSAT

Le Resume Ultime
du WebDev

HTML, CSS, JavaScript, PHP, Git et Symfony expliques simplement: imagine que le Web est un restaurant. HTML construit les tables, CSS les decore, JS fait bouger les serveurs, PHP cuisine cote serveur, Git garde l'historique, Symfony organise toute la cuisine.

La grande carte mentale

Ne memorise pas en vrac. Range chaque notion dans son etage, sinon ton cerveau fait un gros panier melange.

Le Web en une histoire tres simple

Navigateur --requete HTTP--> Serveur Navigateur <--reponse HTML/CSS/JS-- Serveur HTML = squelette de la page CSS = habits et couleurs JS = actions dans le navigateur PHP = logique cote serveur BD = grand cahier ou on garde les donnees Symfony = usine bien rangee pour PHP
Idee fausse a jeter HTML n'est pas un langage de programmation. Il ne calcule pas. Il decrit. Dire "je programme en HTML" dans un examen, c'est techniquement faible.

Front-end

C'est ce que l'utilisateur voit et touche: HTML, CSS, JS, formulaires, boutons, couleurs, evenements.

Navigateur DOM UI

Back-end

C'est la cuisine cachee: PHP, controleurs, sessions, base de donnees, securite, reponses serveur.

PHP Symfony HTTP

Outils

Git garde les versions. Composer installe les paquets. Doctrine parle a la base comme si c'etait des objets.

Git Composer ORM

HTML5: les briques de la maison

HTML dit au navigateur: "ceci est un titre", "ceci est une image", "ceci est un formulaire". Il ne choisit pas la beaute, il choisit le sens.

Structure minimale

<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <title>Ma page</title> </head> <body> <h1>Titre principal</h1> <p>Un paragraphe.</p> </body> </html>
Version enfant Le head, c'est l'etiquette sur la boite. Le body, c'est ce qu'il y a dans la boite.

Balises a connaitre

  • h1 a h6: titres, du plus important au plus petit.
  • p: paragraphe.
  • a href: lien vers une autre page, un endroit de la meme page, ou une ressource.
  • img src alt: image avec texte alternatif obligatoire pour l'accessibilite.
  • ul, ol, li: listes.
  • table, tr, th, td: tableau.

HTML5 semantique

Les balises semantiques donnent du sens, pas juste une forme.

BaliseImage simple
headerLe panneau d'entree.
navLa carte des chemins.
mainLe contenu principal.
sectionUn chapitre.
articleUn contenu autonome.
footerLe bas de page.

Formulaires: la boite ou l'utilisateur met des infos

<form action="/traiter.php" method="post"> <label for="email">Email</label> <input id="email" name="email" type="email" required> <button type="submit">Envoyer</button> </form>

GET

Les donnees voyagent dans l'URL. Bien pour chercher ou filtrer.

POST

Les donnees voyagent dans le corps de la requete. Bien pour inscrire, modifier, envoyer un formulaire.

Piege examen GET n'est pas "securise" juste parce que ca marche. Il affiche les donnees dans l'URL. Pour mot de passe ou inscription, pense POST.

CSS3: habiller la maison

CSS prend les briques HTML et decide leur apparence: taille, couleur, position, bordure, ombre, espace.

La cascade: qui gagne quand deux regles se battent ?

  1. Importance
    Une regle !important crie tres fort, mais n'en abuse pas.
  2. Specificite
    #id gagne souvent contre .classe, qui gagne contre une balise simple.
  3. Ordre
    Si deux regles ont la meme force, la derniere ecrite gagne.

Selecteurs a maitriser

SelecteurSignificationExemple enfant
pTous les paragraphesTous les bonbons rouges.
.noteElements avec class noteTous ceux qui portent un badge.
#menuElement avec id menuLe seul enfant appele menu.
a[href*="insat"]Liens dont href contient insatTout lien avec ce mot dedans.
p:hoverQuand la souris passe dessusLe bouton change quand tu le touches.
li:first-childPremier enfantLe premier dans la file.
Question web25 typique .paragraphe selectionne une classe. #paragraphe selectionne un id. Si le HTML a une classe et ton JS cherche un id, il ne trouve rien.

Box model

content = le jouet padding = le coussin autour du jouet border = la boite margin = l'espace avec les autres boites

Avec box-sizing: border-box, la largeur inclut le padding et la bordure. C'est plus simple.

Couleurs, fonds, bordures

  • color: couleur du texte.
  • background: fond.
  • border-radius: coins arrondis.
  • box-shadow: ombre.
  • font-family: police, avec plusieurs choix de secours.

JavaScript: faire bouger la page

JavaScript parle au navigateur. Il peut lire la page, changer les styles, reagir au clic, calculer, afficher dans la console.

Variables: petites boites avec des noms

MotRolePiege
letVariable qui peut changerExiste dans son bloc.
constReference qui ne change pasUn tableau const peut encore changer son contenu.
varAncienne variableHoisting: elle monte en haut et vaut undefined au debut.
var x = 3; function f() { console.log(x); // undefined: le var local est hisse var x = 7; console.log(x); // 7 }

Syntaxe JS de base: lire du code sans paniquer

// 1) Condition: si / sinon const age = 19; if (age >= 18) { console.log("majeur"); } else { console.log("mineur"); } // 2) Boucle: repeter const notes = [12, 15, 9]; for (let i = 0; i < notes.length; i++) { console.log(notes[i]); } // 3) Fonction: petite machine reutilisable function somme(a, b) { return a + b; } console.log(somme(2, 3)); // 5
Version enfant Une variable est une boite. Une condition est une porte: si la condition est vraie, tu passes. Une boucle est un tapis roulant: elle repete la meme action.

Conversions automatiques

console.log("2" + "3"); // "23" console.log("2" - "3"); // -1

+ avec des textes colle. - force les nombres. C'est bizarre, mais l'examen adore ce piege.

Tableaux

const tab = [11, 4, 21, 3, 31, 13, 0, 1]; tab.sort(); // tri alphabetique par defaut // [0, 1, 11, 13, 21, 3, 31, 4]

Pour trier des nombres correctement: tab.sort((a, b) => a - b).

Fonctions: declaration, expression, flechee

// Declaration: utilisable meme avant sa ligne grace au hoisting function double(x) { return x * 2; } // Expression: stockee dans une variable const triple = function (x) { return x * 3; }; // Fonction flechee: version courte const carre = (x) => x * x; console.log(double(4)); // 8 console.log(triple(4)); // 12 console.log(carre(4)); // 16
Piege prof Une fonction qui ne fait que console.log() affiche quelque chose, mais retourne undefined si elle n'a pas de return. C'est exactement le genre de detail qui casse les QCM.

Objets et classes en JS

// Objet simple: sac avec des proprietes const user = { nom: "Aymen", age: 20, saluer: function () { return "Bonjour " + this.nom; } }; console.log(user.nom); // Aymen console.log(user["age"]); // 20 console.log(user.saluer()); // Bonjour Aymen // Classe JS: moule pour fabriquer plusieurs objets class Etudiant { constructor(nom, section) { this.nom = nom; this.section = section; } presenter() { return this.nom + " est en " + this.section; } } const e1 = new Etudiant("Aymen", "GLIRT"); console.log(e1.presenter());
Detail important Dans une methode, this veut dire "l'objet actuel". Si tu perds this, c'est comme parler d'un enfant sans dire lequel.

DOM et evenements

const para = document.querySelector("p"); // le premier p seulement para.addEventListener("mouseover", function () { para.style.fontSize = "30px"; }); para.addEventListener("mouseout", function () { para.style.fontSize = "15px"; });
Debunk direct Si tu dis "ca modifie tous les paragraphes", c'est faux. querySelector("p") prend le premier. Pour tous, il faut querySelectorAll("p") puis une boucle.

PHP: la cuisine cote serveur

PHP s'execute sur le serveur. Le navigateur ne voit pas ton PHP, il recoit seulement le HTML que PHP fabrique.

Superglobales

  • $_GET: infos envoyees dans l'URL.
  • $_POST: infos envoyees dans le corps de la requete.
  • $_SESSION: infos gardeess pour un utilisateur connecte.
  • $_COOKIE: petite info gardee chez le navigateur.
  • $_FILES: fichiers envoyes par formulaire.

Sessions

session_start(); $_SESSION["nomUser"] = "Aymen"; if (isset($_SESSION["nomUser"])) { echo "Bienvenue " . $_SESSION["nomUser"]; }

Une session, c'est comme un bracelet d'entree: le serveur reconnait l'utilisateur d'une page a l'autre.

Comment une session PHP marche vraiment

Une session PHP garde des donnees sur le serveur. Le navigateur garde seulement un petit identifiant, souvent dans un cookie appele PHPSESSID. Donc le navigateur dit "je suis le ticket 123", et le serveur retrouve le bon petit tiroir de donnees.

  1. Demarrer
    session_start() lit ou cree la session. Fais-le avant d'afficher du HTML.
  2. Ecrire
    $_SESSION["nom"] = "Aymen" met une valeur dans le tiroir de cet utilisateur.
  3. Lire
    isset($_SESSION["nom"]) verifie si la valeur existe avant de l'utiliser.
  4. Proteger
    Si l'utilisateur n'est pas connecte, tu le rediriges vers login.php.
  5. Deconnecter
    Tu vides $_SESSION, tu detruis la session, puis tu supprimes le cookie si besoin.
Piege tres classique Si tu oublies session_start(), $_SESSION ne travaille pas correctement. C'est comme chercher un cahier sans ouvrir le cartable.

Mini-systeme login avec session

1. Connexion

<?php // login.php session_start(); if ($_POST["email"] === "aymen@insat.tn" && $_POST["password"] === "1234") { $_SESSION["user"] = [ "email" => $_POST["email"], "role" => "ROLE_USER" ]; header("Location: profile.php"); exit; } echo "Email ou mot de passe incorrect";

2. Page protegee

<?php // profile.php session_start(); if (!isset($_SESSION["user"])) { header("Location: login.php"); exit; } echo "Bienvenue " . $_SESSION["user"]["email"];

3. Deconnexion propre

<?php // logout.php session_start(); $_SESSION = []; if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie( session_name(), "", time() - 3600, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } session_destroy(); header("Location: login.php"); exit;
Version enfant Login met ton nom dans le cahier. La page protegee verifie si ton nom est dans le cahier. Logout arrache la page du cahier.

Sessions: erreurs a ne pas faire

ErreurPourquoi c'est fauxCorrection
Afficher du HTML avant session_start() PHP doit envoyer les headers/cookies avant le contenu. Mettre session_start() tout en haut.
Utiliser $_SESSION["x"] sans verifier Si la cle n'existe pas, tu risques warning ou logique fausse. Utiliser isset($_SESSION["x"]).
Faire header("Location: ...") sans exit Le script peut continuer apres la redirection. Ajouter exit; juste apres.
Stocker le mot de passe en session Inutile et dangereux. Stocker id, email, role; jamais le mot de passe brut.

Fonctions et references

function sommeProduit($som, &$prod, $x, $y) { $som = $x + $y; // copie: ne change pas $s dehors $prod = $x * $y; // reference: change $p dehors } $s = 0; $p = 0; sommeProduit($s, $p, -3.4, 2); // $s = 0 et $p = -6.8
Version enfant Sans &, tu donnes une photocopie. Avec &, tu donnes le vrai cahier, donc la fonction peut ecrire dedans.

PHP Objet

ConceptExplication simplePiege
ClasseLe moule a gateau.Ce n'est pas encore un objet.
ObjetLe gateau cree avec le moule.== compare les valeurs, === compare la meme instance.
StaticAppartient a la classe.$this n'existe pas dans une methode static.
AbstractClasse incomplete qui sert de base.On ne l'instancie pas directement.
InterfaceContrat de methodes.Elle impose ce qu'une classe doit fournir.
TraitMorceau de code reutilisable.Ce n'est pas une vraie relation d'heritage.

Classe PHP complete: le moule, puis l'objet

<?php class User { // Proprietes = les donnees de l'objet private string $name; private string $email; // Constructeur = appele quand on fait new User(...) public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } // Getter = lire une propriete privee public function getName(): string { return $this->name; } // Setter = modifier une propriete privee public function setEmail(string $email): void { $this->email = $email; } public function presenter(): string { return "Je suis " . $this->name; } } $user = new User("Aymen", "aymen@insat.tn"); echo $user->getName(); echo $user->presenter();
Version enfant class User est le moule. new User(...) fabrique un vrai objet. $this veut dire "cet objet-la, celui qu'on vient de fabriquer".

Visibilite: public, private, protected

MotQui peut toucher ?Image simple
publicTout le monde.Objet pose sur la table.
privateSeulement la classe elle-meme.Journal intime ferme.
protectedLa classe et ses enfants.Cahier de famille.
class Compte { private float $solde = 0; public function deposer(float $montant): void { if ($montant > 0) { $this->solde += $montant; } } } $c = new Compte(); $c->deposer(50); // $c->solde = 999999; // interdit: solde est private
Debunk direct Mettre toutes les proprietes en public parce que "c'est plus facile" est une mauvaise habitude. Tu casses l'encapsulation: n'importe quel code peut mettre l'objet dans un etat idiot.

Static: appartient a la classe, pas a l'objet

class Compteur { public static int $nb = 0; public function __construct() { self::$nb++; } public static function combien(): int { return self::$nb; // Pas de $this ici: on n'est pas dans un objet precis. } } new Compteur(); new Compteur(); echo Compteur::combien(); // 2
Piege web25 Dans une methode static, $this n'existe pas. Utilise self:: pour la classe actuelle ou static:: pour le late static binding.

Heritage et abstract

abstract class Personne { public function __construct(protected string $nom) {} abstract public function role(): string; } class Etudiant extends Personne { public function role(): string { return "Etudiant: " . $this->nom; } } $e = new Etudiant("Aymen"); echo $e->role();

Une classe abstraite est un moule incomplet. Elle force les enfants a terminer certaines methodes.

Interface et trait

interface Payable { public function payer(float $montant): bool; } trait Horodatage { public function maintenant(): string { return date("Y-m-d H:i:s"); } } class Facture implements Payable { use Horodatage; public function payer(float $montant): bool { return $montant > 0; } }

Une interface est un contrat. Un trait est un morceau de code colle dans une classe.

Securite minimale Pour eviter l'injection SQL, utilise des requetes preparees. Ne colle jamais directement une saisie utilisateur dans une requete SQL.

Git: la machine a remonter le temps

Git garde les versions de ton projet. Si tu casses quelque chose, tu peux comprendre quand, ou, et par qui.

Le cycle vital

git status # voir ce qui a change git add fichier.html # mettre dans le panier git commit -m "message" # prendre une photo du panier git log # voir les photos git diff # voir les changements exacts

Branches

Une branche, c'est une route parallele. Tu testes une idee sans casser la route principale.

git branch feature-login git switch feature-login git merge feature-login

Depot distant

Le remote, c'est le cartable partage sur internet, souvent GitHub.

git remote -v git push git pull
Conflit Un conflit arrive quand deux personnes modifient la meme zone. Git ne devine pas le bon choix. Toi, tu dois lire et decider.

Symfony: PHP bien range

Symfony evite le code spaghetti. Il range les routes, controleurs, templates, formulaires, base de donnees et securite.

Le voyage d'une requete

  1. L'utilisateur demande une URL
    Exemple: /article/5.
  2. Le routeur cherche la bonne route
    Il lit les attributs, YAML, XML ou PHP.
  3. Le controleur agit
    Il recupere les donnees, appelle les services, prepare la reponse.
  4. Twig affiche
    Le template transforme les variables en HTML propre.
  5. Doctrine parle a la BD si besoin
    Repository pour lire, EntityManager pour ajouter/modifier/supprimer.

Routing

#[Route("/Examen/{year<\\d{2}>}/{univ?insat}")] public function exam(string $year, string $univ) { ... }

year<\\d{2}> veut exactement deux chiffres. Donc /Examen/24 marche, /Examen/2024 ne marche pas.

Controleur

#[Route("/hello", name: "hello")] public function hello(): Response { return $this->render("hello/index.html.twig", [ "name" => "GLIRT" ]); }

Le controleur est le chef de table: il recoit la demande et choisit quoi renvoyer.

Twig

SyntaxeRole
{{ variable }}Afficher une variable.
{% if condition %}Executer une instruction.
{% extends "base.html.twig" %}Heriter d'un template parent.
{% block body %}Zone remplacable par l'enfant.
{{ parent() }}Recuperer le contenu du bloc parent.
{{ path("route") }}Generer une URL interne relative.
{{ absolute_url(path("route")) }}Generer un lien complet, utile pour email.
Piege enfant/parent Dans Twig, parent() s'affiche avec {{ }}, pas avec {% %}, parce qu'il produit du contenu.

Doctrine: ORM + Unit of Work

Doctrine te laisse manipuler des objets PHP, puis il synchronise avec les tables SQL.

$user = new User(); $user->setEmail("aymen@insat.tn"); $entityManager->persist($user); // prepare $entityManager->flush(); // execute en BD

Repository

Lit la BD: find, findAll, findBy, requetes select.

EntityManager

Ecrit dans la BD: persist, remove, flush.

Transaction Doctrine Plusieurs insert/update/delete dans un flush() sont regroupes. Si une operation echoue, tout est annule. C'est pour ca que deux emails uniques identiques donnent "aucun user ajoute".

Migrations et fixtures

  • Migration: photo de l'evolution de la structure BD. Permet avancer ou revenir entre versions.
  • Fixture: donnees de depart ou de test pour remplir la BD.

Relations Doctrine

  • ManyToMany cree une table de jointure automatiquement.
  • Si la relation a ses propres champs, cree une entite intermediaire.
  • Une relation n'est pas obligee d'etre bidirectionnelle.

Formulaires Symfony

ElementRole
createForm / createFormBuilderConstruire le formulaire.
handleRequest($request)Prendre les donnees de la requete et les mettre dans l'objet.
isSubmitted()Verifier si le formulaire a ete envoye.
isValid()Lancer/verifier la validation.
mapped => falseChamp sans propriete correspondante dans l'entite.
query_builderPersonnaliser la liste d'un champ lie a une entite.
Correction importante Un formulaire Symfony n'est pas toujours associe a une entite. Il peut aussi recuperer des informations independantes.

Securite Symfony

  • User Provider: charge l'utilisateur.
  • Authenticator: verifie l'identite.
  • access_control: protege les routes par roles.
  • role_hierarchy: un role peut heriter d'autres roles.
access_control: - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/profile/delete, roles: ROLE_ADMIN } - { path: ^/profile, roles: ROLE_USER }

Symfony lit les controles d'acces de haut en bas et prend le premier qui correspond. Pour /profile/delete, c'est ROLE_ADMIN.

QCM corrige (AI)

Corrige base sur les trois photos fournies: Q1-Q9, Q10-Q23, puis Q24-Q40. L'ancien corrige du PDF a ete remplace.

Regle de l'examen Chaque question peut avoir 0 ou plusieurs bonnes reponses. Ne cherche pas "la seule bonne case" par habitude: certaines questions ont plusieurs cases.

Corrige rapide Q1 - Q40

QReponsePourquoi, version tres simple
1CDans Twig, un template enfant qui herite ne peut pas ecrire du contenu hors des blocs: erreur.
2A: 1La session est mise a 1 au premier appel; ensuite le code incremente une copie locale sans refaire set.
3ALa session contient nomUser, donc le message de bienvenue est affiche.
4ALes trois p sont enfants directs de body, donc body > p applique le fond jaune aux trois.
5ELe selecteur JS ne cible pas la classe .paragraphe; les couleurs inline restent vert, bleu, vert.
6AquerySelector("p") prend le premier paragraphe; le paragraphe para3 ne change pas.
7A, BUne Promise recoit resolve/reject; le succes se traite avec then.
8BLa boucle affiche 2, 3, 4. Elle s'arrete avant 5.
9C"2" + "3" donne "23"; puis "23" - "2" donne 21.
10BDans le setTimeout(function(){}), this n'est plus la voiture: on obtient 100 puis NaN.
11ALa fonction flechee garde le this du constructeur: 100 puis 110.
12Cvar g est hisse mais vaut undefined au moment de l'appel: g is not a function.
13Avar x local est hisse: premier affichage undefined, puis 7.
14A, B, Ca[href*="insat"] prend tout lien dont le href contient "insat".
15Dsort() sans comparateur trie comme du texte: 0, 1, 11, 13, 21, 3, 31, 4.
16BAucun parametre n'est passe par reference; $s et $p restent a 0.
17A, BEn JS, on accede a une propriete par user.name ou user["name"].
18A, Bsession_start() peut initialiser une nouvelle session ou reprendre une session existante.
19A, BLes fixtures remplissent la BD avec des donnees de test ou des donnees initiales.
20BPlusieurs select Doctrine sont executes separement.
21ALes operations de persistance Doctrine dans un flush() sont regroupees en transaction.
22A, D, Eyear doit avoir 2 chiffres; section et univ ont des valeurs par defaut.
23BLa table ManyToMany automatique n'est pas faite pour recevoir des attributs; il faut une entite intermediaire.
24A, BUn trait centralise du code et peut contenir methodes/proprietes; ce n'est pas un vrai type parent.
25A, BLe polymorphisme PHP passe par l'heritage et par les interfaces.
26B, Clet a une portee de bloc; const doit etre initialise tout de suite, mais ne bloque pas les attributs d'un objet.
27BLes requetes preparees protegent contre SQL Injection, pas contre XSS ou DoS.
28A, BLes droits par role se declarent dans security.yaml ou se verifient dans le controleur.
29DLa hierarchie est transitive: admin herite aussi de secretary, commercial et agent.
30A, B, COrdre des blocs peu important; inclusion possible; un enfant ne remplit que les blocs herites.
31BDans un email d'activation, il faut une URL absolue avec url(), pas seulement un chemin relatif.
32AUn flash message est une variable de session courte, lue une seule fois.
33Bquery_builder personnalise la requete qui alimente une liste de choix du formulaire.
34A, Bmapped => false sert pour un champ sans propriete directe, comme upload ou mot de passe non stocke brut.
35B, CLes validateurs peuvent etre poses dans le form ou dans l'entite.
36B: FauxUn formulaire Symfony peut etre lie a une entite ou juste recuperer des donnees independantes.
37A, B, C, DUn controleur Symfony peut retourner HTML, JSON, image ou PDF via une Response.
38A, BDans Twig, on peut acceder a la session via app ou passer la donnee depuis le controleur.
39A: VraiLe champ name d'une route est optionnel.
40CEmail unique + flush() transactionnel: si le deuxieme insert echoue, aucun user n'est ajoute.

Les pieges les plus rentables a reviser

JS

  • querySelector prend le premier, querySelectorAll prend une liste.
  • #id n'est pas .class.
  • var est hisse et peut donner undefined.
  • sort() trie en texte sans comparateur numerique.

Symfony / PHP

  • flush() Doctrine = transaction d'ecriture.
  • handleRequest ne remplace pas isValid.
  • mapped => false pour champ sans entite.
  • $this interdit en contexte static.

Checklist avant l'examen

Si tu sais faire ces verifications, tu arretes de repondre au feeling. Le feeling est mauvais en QCM technique.

Quand tu lis du code

  1. Repere le langage
    JS, PHP, Twig et YAML n'ont pas les memes regles.
  2. Trace les variables
    Demande: est-ce une copie, une reference, une session, une propriete static ?
  3. Lis les selecteurs exactement
    #x, .x, x ne selectionnent pas la meme chose.
  4. Regarde ce qui est vraiment appele
    Un code defini mais jamais appele ne produit rien.
  5. Verifie les effets caches
    Transaction, hoisting, conversion automatique, ordre des access_control.

Questions flash

Difference entre HTML et CSS ?
HTML donne le sens et la structure. CSS donne l'apparence.
Difference entre Repository et EntityManager ?
Repository lit. EntityManager prepare/ecrit/supprime avec persist, remove, flush.
Pourquoi une migration n'est pas une fixture ?
Migration change la structure/version de la BD. Fixture ajoute des donnees.
Pourquoi l'email doit avoir un lien absolu ?
Dans un email, le navigateur n'est pas deja sur ton site. Il lui faut le domaine complet.
Pourquoi prepared statement ?
Pour separer la requete SQL des valeurs utilisateur et eviter l'injection SQL.

Sources utilisees dans ce workspace

Cours Web 1 HTML5, Cours Web 2 CSS3, Cours Web 3 JS, Cours Web 4 PHP, ateliers HTML/CSS/JS/PHP, Git.pdf, Symfony Cours 1 Introduction, Cours 2 Controleurs, Cours 3 Routing, Cours 4 Twig, Cours 5 Doctrine, Cours 6 Formulaires, Cours 7 Securite, et les trois photos QCM fournies.