0% ont trouvé ce document utile (0 vote)
470 vues153 pages

Cours Complet PHP

Transféré par

alvesfernandes594
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
470 vues153 pages

Cours Complet PHP

Transféré par

alvesfernandes594
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
Vous êtes sur la page 1/ 153

COURS COMPLET PHP

Cours complet de PHP - BTS SIO option SLAM


(1ère et 2ème année)
Table des matières
Première année
1. Introduction à PHP
2. Variables et types de données
3. Structures de contrôle
4. Fonctions
5. Tableaux
6. Manipulation de chaînes de caractères
7. Formulaires et méthodes HTTP
8. Sessions et cookies
9. Inclusion de fichiers
10. Manipulation de fichiers

Deuxième année
11. Programmation orientée objet en PHP
12. Bases de données et MySQL
13. PDO (PHP Data Objects)
14. Sécurité en PHP
15. Gestion des erreurs et exceptions
16. API REST avec PHP
17. Frameworks PHP (introduction à Laravel)
18. Tests unitaires en PHP
19. Déploiement d'applications PHP
20. Bonnes pratiques et patterns de conception

Première année
1. Introduction à PHP
1.1 Qu'est-ce que PHP ?
PHP (Hypertext Preprocessor) est un langage de script côté serveur conçu pour le
développement web. Il peut être intégré directement dans le HTML et est largement utilisé pour
créer des sites web dynamiques.

1.2 Histoire et évolution de PHP

Créé par Rasmus Lerdorf en 1994


Évolution des versions majeures (PHP 3, 4, 5, 7, 8)
Popularité et utilisation dans le développement web moderne

1.3 Configuration de l'environnement de développement

Installation de XAMPP (ou alternatives comme WAMP, MAMP)


Configuration du serveur web Apache
Test de l'installation PHP

1.4 Premier script PHP


Créons notre premier script PHP :

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon premier script PHP</title>
</head>
<body>
<h1>Bienvenue dans le monde de PHP !</h1>
<?php
echo "<p>Ceci est généré par PHP : " . phpversion() . "</p>";
?>
</body>
</html>

Exercice 1.1
Créez un script PHP qui affiche "Bonjour, [votre nom] !" en utilisant une variable pour stocker
votre nom.

Corrigé 1.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 1.1</title>
</head>
<body>
<?php
$nom = "Alice"; // Remplacez par votre nom
echo "<h1>Bonjour, $nom !</h1>";
?>
</body>
</html>

1.5 Commentaires en PHP

PHP supporte plusieurs types de commentaires :

<?php
// Ceci est un commentaire sur une ligne

# Ceci est aussi un commentaire sur une ligne

/*
Ceci est un commentaire
sur plusieurs lignes
*/

// Les commentaires sont ignorés par l'interpréteur PHP


echo "Ce texte sera affiché"; // Ce commentaire n'affectera pas l'exécution
?>

Exercice 1.2
Créez un script PHP qui calcule et affiche l'aire d'un rectangle. Utilisez des variables pour la
longueur et la largeur, et ajoutez des commentaires pour expliquer votre code.

Corrigé 1.2

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 1.2 - Calcul de l'aire d'un rectangle</title>
</head>
<body>
<?php
// Définition des dimensions du rectangle
$longueur = 5; // en mètres
$largeur = 3; // en mètres

// Calcul de l'aire
$aire = $longueur * $largeur;

// Affichage du résultat
echo "<p>L'aire d'un rectangle de longueur $longueur m et de largeur
$largeur m est de $aire m².</p>";
?>
</body>
</html>

2. Variables et types de données


2.1 Déclaration et affectation de variables
En PHP, les variables sont précédées du symbole $ et sont sensibles à la casse.

<?php
$age = 25;
$nom = "Alice";
$estEtudiant = true;
?>

2.2 Types de données de base


PHP est un langage à typage dynamique. Les principaux types de données sont :

Entiers (integer)
Nombres à virgule flottante (float)
Chaînes de caractères (string)
Booléens (boolean)
Tableaux (array)
Objets (object)
NULL

<?php
$entier = 42;
$flottant = 3.14;
$chaine = "Bonjour";
$booleen = true;
$tableau = [1, 2, 3];
$objet = new stdClass();
$nul = NULL;

// Affichage du type de variable


echo gettype($entier); // Affiche "integer"
?>

2.3 Conversion de types


PHP permet la conversion explicite et implicite entre les types de données.

<?php
$nombre = "42";
$nombreEntier = (int)$nombre; // Conversion explicite en entier

$somme = 5 + "10"; // Conversion implicite, $somme vaut 15 (integer)


?>

2.4 Constantes
Les constantes sont des identifiants pour des valeurs simples. Contrairement aux variables,
leur valeur ne peut pas être modifiée pendant l'exécution du script.
<?php
define("PI", 3.14159);
const MA_CONSTANTE = "Valeur constante";

echo PI; // Affiche 3.14159


echo MA_CONSTANTE; // Affiche "Valeur constante"
?>

Exercice 2.1
Créez un script PHP qui déclare des variables de différents types (entier, flottant, chaîne,
booléen) et affichez leur type et leur valeur.

Corrigé 2.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 2.1 - Types de variables</title>
</head>
<body>
<?php
$entier = 42;
$flottant = 3.14;
$chaine = "Hello, PHP!";
$booleen = true;

echo "<h2>Types et valeurs des variables :</h2>";


echo "<p>Entier : " . gettype($entier) . " - Valeur : $entier</p>";
echo "<p>Flottant : " . gettype($flottant) . " - Valeur : $flottant</p>";
echo "<p>Chaîne : " . gettype($chaine) . " - Valeur : $chaine</p>";
echo "<p>Booléen : " . gettype($booleen) . " - Valeur : " . ($booleen ?
'true' : 'false') . "</p>";
?>
</body>
</html>

Exercice 2.2
Créez un script PHP qui calcule le périmètre et l'aire d'un cercle. Utilisez une constante pour π
et une variable pour le rayon. Affichez les résultats avec 2 décimales.

Corrigé 2.2

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 2.2 - Calcul du cercle</title>
</head>
<body>
<?php
// Définition de la constante PI
define("PI", 3.14159);

// Rayon du cercle
$rayon = 5;

// Calculs
$perimetre = 2 * PI * $rayon;
$aire = PI * $rayon * $rayon;

// Affichage des résultats


echo "<h2>Calculs pour un cercle de rayon $rayon :</h2>";
echo "<p>Périmètre : " . number_format($perimetre, 2) . " unités</p>";
echo "<p>Aire : " . number_format($aire, 2) . " unités²</p>";
?>
</body>
</html>

3. Structures de contrôle
Les structures de contrôle permettent de modifier le flux d'exécution d'un script PHP en fonction
de certaines conditions.

3.1 Conditions (if, else, elseif)

<?php
$age = 18;
if ($age < 18) {
echo "Vous êtes mineur.";
} elseif ($age == 18) {
echo "Vous venez d'atteindre la majorité.";
} else {
echo "Vous êtes majeur.";
}
?>

3.2 Switch

<?php
$jour = "Mardi";

switch ($jour) {
case "Lundi":
echo "C'est le début de la semaine.";
break;
case "Mardi":
case "Mercredi":
case "Jeudi":
echo "C'est le milieu de la semaine.";
break;
case "Vendredi":
echo "C'est bientôt le week-end.";
break;
default:
echo "C'est le week-end !";
}
?>

3.3 Boucles (while, do-while, for, foreach)

<?php
// Boucle while
$i = 0;
while ($i < 5) {
echo $i . " ";
$i++;
}

// Boucle do-while
$j = 0;
do {
echo $j . " ";
$j++;
} while ($j < 5);

// Boucle for
for ($k = 0; $k < 5; $k++) {
echo $k . " ";
}

// Boucle foreach
$fruits = ["pomme", "banane", "orange"];
foreach ($fruits as $fruit) {
echo $fruit . " ";
}
?>

3.4 Break et Continue

break permet de sortir d'une boucle.


continue passe directement à l'itération suivante.

<?php
for ($i = 0; $i < 10; $i++) {
if ($i == 5) {
continue; // Saute l'itération quand $i est 5
}
if ($i == 8) {
break; // Sort de la boucle quand $i est 8
}
echo $i . " ";
}
?>

Exercice 3.1
Écrivez un script PHP qui affiche les nombres de 1 à 100. Pour les multiples de 3, affichez
"Fizz" au lieu du nombre, et pour les multiples de 5, affichez "Buzz". Pour les nombres qui sont
à la fois des multiples de 3 et 5, affichez "FizzBuzz".
Corrigé 3.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 3.1 - FizzBuzz</title>
</head>
<body>
<h2>FizzBuzz</h2>
<?php
for ($i = 1; $i <= 100; $i++) {
if ($i % 3 == 0 && $i % 5 == 0) {
echo "FizzBuzz ";
} elseif ($i % 3 == 0) {
echo "Fizz ";
} elseif ($i % 5 == 0) {
echo "Buzz ";
} else {
echo $i . " ";
}
}
?>
</body>
</html>

Exercice 3.2

Créez un script PHP qui génère une table de multiplication pour les nombres de 1 à 10. Utilisez
des boucles imbriquées pour créer un tableau HTML.

Corrigé 3.2

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 3.2 - Table de multiplication</title>
<style>
table { border-collapse: collapse; }
td { border: 1px solid black; padding: 5px; text-align: center; }
</style>
</head>
<body>
<h2>Table de multiplication</h2>
<table>
<?php
for ($i = 1; $i <= 10; $i++) {
echo "<tr>";
for ($j = 1; $j <= 10; $j++) {
echo "<td>" . ($i * $j) . "</td>";
}
echo "</tr>";
}
?>
</table>
</body>
</html>

4. Fonctions
Les fonctions sont des blocs de code réutilisables qui effectuent une tâche spécifique.

4.1 Définition et appel de fonctions

<?php
// Définition d'une fonction
function saluer($nom) {
return "

### 4. Fonctions

Les fonctions sont des blocs de code réutilisables qui effectuent une tâche
spécifique. Elles permettent d'organiser et de structurer le code, facilitant
ainsi sa maintenance et sa réutilisation.

#### 4.1 Définition et appel de fonctions

```php
<?php
// Définition d'une fonction
function saluer($nom) {
return "Bonjour, " . $nom . " !";
}
// Appel de la fonction
echo saluer("Alice"); // Affiche : Bonjour, Alice !
?>

4.2 Paramètres de fonction


Les fonctions peuvent accepter des paramètres, qui sont des variables utilisées à l'intérieur de
la fonction.

<?php
function additionner($a, $b) {
return $a + $b;
}

echo additionner(5, 3); // Affiche : 8


?>

4.3 Paramètres optionnels et valeurs par défaut

<?php
function puissance($base, $exposant = 2) {
return pow($base, $exposant);
}

echo puissance(3); // Affiche : 9 (3^2)


echo puissance(2, 3); // Affiche : 8 (2^3)
?>

4.4 Retour de valeurs multiples


PHP ne permet pas de retourner plusieurs valeurs directement, mais on peut utiliser un tableau
ou un objet pour contourner cette limitation.

<?php
function calculerStatistiques($nombres) {
$somme = array_sum($nombres);
$moyenne = $somme / count($nombres);
$max = max($nombres);
$min = min($nombres);
return [
'somme' => $somme,
'moyenne' => $moyenne,
'max' => $max,
'min' => $min
];
}

$resultats = calculerStatistiques([1, 2, 3, 4, 5]);


echo "Somme : " . $resultats['somme'] . "<br>";
echo "Moyenne : " . $resultats['moyenne'] . "<br>";
?>

4.5 Portée des variables


La portée d'une variable définit où cette variable peut être accessible dans le code.

<?php
$variableGlobale = "Je suis globale";

function testPortee() {
$variableLocale = "Je suis locale";
global $variableGlobale;
echo $variableGlobale; // Accessible
echo $variableLocale; // Accessible
}

testPortee();
echo $variableGlobale; // Accessible
echo $variableLocale; // Non accessible (erreur)
?>

4.6 Fonctions anonymes et closures

Les fonctions anonymes, également appelées closures, sont des fonctions sans nom qui
peuvent être assignées à des variables.

<?php
$dire = function($mot) {
echo "Vous avez dit : $mot";
};
$dire("Bonjour"); // Affiche : Vous avez dit : Bonjour
?>

Exercice 4.1
Créez une fonction calculerIMC qui prend en paramètres le poids (en kg) et la taille (en m)
d'une personne et retourne son IMC (Indice de Masse Corporelle). Ensuite, créez une fonction
interpreterIMC qui prend l'IMC en paramètre et retourne une interprétation selon l'échelle
suivante :

Moins de 18.5 : Insuffisance pondérale


18.5 à 24.9 : Corpulence normale
25 à 29.9 : Surpoids
30 et plus : Obésité

Corrigé 4.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 4.1 - Calcul de l'IMC</title>
</head>
<body>
<?php
function calculerIMC($poids, $taille) {
return $poids / ($taille * $taille);
}

function interpreterIMC($imc) {
if ($imc < 18.5) {
return "Insuffisance pondérale";
} elseif ($imc < 25) {
return "Corpulence normale";
} elseif ($imc < 30) {
return "Surpoids";
} else {
return "Obésité";
}
}

$poids = 70; // en kg
$taille = 1.75; // en m

$imc = calculerIMC($poids, $taille);


$interpretation = interpreterIMC($imc);

echo "<h2>Résultats du calcul de l'IMC</h2>";


echo "<p>Pour un poids de $poids kg et une taille de $taille m :</p>";
echo "<p>IMC : " . number_format($imc, 2) . "</p>";
echo "<p>Interprétation : $interpretation</p>";
?>
</body>
</html>

Exercice 4.2
Créez une fonction genererMotDePasse qui génère un mot de passe aléatoire. La fonction doit
prendre en paramètres la longueur du mot de passe et des booléens indiquant si le mot de
passe doit contenir des majuscules, des chiffres et des caractères spéciaux. Utilisez une
fonction anonyme pour définir les caractères possibles.

Corrigé 4.2

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 4.2 - Générateur de mot de passe</title>
</head>
<body>
<?php
function genererMotDePasse($longueur, $inclureMajuscules = true,
$inclureChiffres = true, $inclureSpeciaux = true) {
$getCaracteres = function() use ($inclureMajuscules, $inclureChiffres,
$inclureSpeciaux) {
$chars = 'abcdefghijklmnopqrstuvwxyz';
if ($inclureMajuscules) $chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($inclureChiffres) $chars .= '0123456789';
if ($inclureSpeciaux) $chars .= '!@#$%^&*()_+-=[]{}|;:,.<>?';
return $chars;
};

$caracteres = $getCaracteres();
$motDePasse = '';
$max = strlen($caracteres) - 1;
for ($i = 0; $i < $longueur; $i++) {
$motDePasse .= $caracteres[rand(0, $max)];
}
return $motDePasse;
}

$longueur = 12;
$motDePasse = genererMotDePasse($longueur, true, true, true);

echo "<h2>Générateur de mot de passe</h2>";


echo "<p>Mot de passe généré (longueur $longueur) : $motDePasse</p>";
?>
</body>
</html>

5. Tableaux
Les tableaux en PHP sont des structures de données très flexibles qui peuvent contenir
plusieurs valeurs dans une seule variable.

5.1 Création de tableaux


Il existe plusieurs façons de créer des tableaux en PHP :

<?php
// Création d'un tableau indexé
$fruits = array("pomme", "banane", "orange");
// ou
$legumes = ["carotte", "brocoli", "tomate"];

// Création d'un tableau associatif


$personne = array(
"nom" => "Dupont",
"prenom" => "Jean",
"age" => 30
);
// ou
$voiture = [
"marque" => "Renault",
"modele" => "Clio",
"annee" => 2020
];
?>

5.2 Accès aux éléments d'un tableau

<?php
$fruits = ["pomme", "banane", "orange"];
echo $fruits[0]; // Affiche : pomme

$personne = ["nom" => "Dupont", "prenom" => "Jean", "age" => 30];
echo $personne["prenom"]; // Affiche : Jean
?>

5.3 Parcourir un tableau

<?php
$fruits = ["pomme", "banane", "orange"];

// Utilisation de la boucle for


for ($i = 0; $i < count($fruits); $i++) {
echo $fruits[$i] . "<br>";
}

// Utilisation de la boucle foreach


foreach ($fruits as $fruit) {
echo $fruit . "<br>";
}

// Pour les tableaux associatifs


$personne = ["nom" => "Dupont", "prenom" => "Jean", "age" => 30];
foreach ($personne as $cle => $valeur) {
echo "$cle : $valeur<br>";
}
?>

5.4 Fonctions utiles pour les tableaux


PHP offre de nombreuses fonctions intégrées pour manipuler les tableaux :
<?php
$nombres = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3];

echo count($nombres); // Affiche le nombre d'éléments


sort($nombres); // Trie le tableau
print_r($nombres); // Affiche le contenu du tableau de façon lisible

$fruits = ["pomme", "banane", "orange"];


if (in_array("banane", $fruits)) {
echo "La banane est dans le tableau";
}

$position = array_search("orange", $fruits);


echo "L'orange est à la position : $position";

$nouveauxFruits = array_merge($fruits, ["kiwi", "fraise"]);


print_r($nouveauxFruits);
?>

5.5 Tableaux multidimensionnels


Les tableaux peuvent contenir d'autres tableaux, créant ainsi des structures de données
complexes.

<?php
$etudiants = [
["nom" => "Dupont", "prenom" => "Jean", "notes" => [15, 12, 18]],
["nom" => "Martin", "prenom" => "Sophie", "notes" => [14, 16, 13]],
["nom" => "Bernard", "prenom" => "Emma", "notes" => [17, 15, 19]]
];

foreach ($etudiants as $etudiant) {


echo $etudiant["prenom"] . " " . $etudiant["nom"] . " : ";
echo "Moyenne = " . array_sum($etudiant["notes"]) /
count($etudiant["notes"]);
echo "<br>";
}
?>

Exercice 5.1
Créez un tableau associatif représentant un panier d'achats. Chaque élément du panier doit
avoir un nom, un prix unitaire et une quantité. Calculez et affichez le total du panier.

Corrigé 5.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 5.1 - Panier d'achats</title>
</head>
<body>
<h2>Panier d'achats</h2>
<?php
$panier = [
["nom" => "Livre PHP", "prix" => 29.99, "quantite" => 2],
["nom" => "Clé USB", "prix" => 14.99, "quantite" => 3],
["nom" => "Casque audio", "prix" => 79.99, "quantite" => 1],
["nom" => "Souris sans fil", "prix" => 24.99, "quantite" => 1]
];

$total = 0;

echo "<table border='1'>";


echo "<tr><th>Produit</th><th>Prix unitaire</th><th>Quantité</th><th>Sous-
total</th></tr>";

foreach ($panier as $article) {


$sousTotal = $article["prix"] * $article["quantite"];
$total += $sousTotal;

echo "<tr>";
echo "<td>" . $article["nom"] . "</td>";
echo "<td>" . number_format($article["prix"], 2) . " €</td>";
echo "<td>" . $article["quantite"] . "</td>";
echo "<td>" . number_format($sousTotal, 2) . " €</td>";
echo "</tr>";
}

echo "<tr><td colspan='3'><strong>Total</strong></td><td><strong>" .


number_format($total, 2) . " €</strong></td></tr>";
echo "</table>";
?>
</body>
</html>

Exercice 5.2
Créez un tableau multidimensionnel représentant une grille de jeu du morpion (3x3).
Remplissez-le aléatoirement avec des 'X', des 'O' et des cases vides (' '). Affichez la grille et
vérifiez s'il y a un gagnant (3 symboles identiques alignés) ou si la partie est nulle.

Corrigé 5.2

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 5.2 - Morpion</title>
<style>
table { border-collapse: collapse; }
td { width: 50px; height: 50px; text-align: center; font-size: 24px;
border: 1px solid black; }
</style>
</head>
<body>
<h2>Jeu du Morpion</h2>
<?php
function creerGrille() {
$symboles = ['X', 'O', ' '];
$grille = [];
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
$grille[$i][$j] = $symboles[array_rand($symboles)];
}
}
return $grille;
}

function afficherGrille($grille) {
echo "<table>";
foreach ($grille as $ligne) {
echo "<tr>";
foreach ($ligne as $cellule) {
echo "<td>" . $cellule . "</td>";
}
echo "</tr>";
}
echo "</table>";
}

function verifierGagn

function verifierGagnant($grille) {
// Vérifier les lignes et les colonnes
for ($i = 0; $i < 3; $i++) {
if ($grille[$i][0] != ' ' && $grille[$i][0] == $grille[$i][1] &&
$grille[$i][1] == $grille[$i][2]) {
return $grille[$i][0];
}
if ($grille[0][$i] != ' ' && $grille[0][$i] == $grille[1][$i] &&
$grille[1][$i] == $grille[2][$i]) {
return $grille[0][$i];
}
}
// Vérifier les diagonales
if ($grille[0][0] != ' ' && $grille[0][0] == $grille[1][1] &&
$grille[1][1] == $grille[2][2]) {
return $grille[0][0];
}
if ($grille[0][2] != ' ' && $grille[0][2] == $grille[1][1] &&
$grille[1][1] == $grille[2][0]) {
return $grille[0][2];
}
// Vérifier s'il y a match nul
foreach ($grille as $ligne) {
if (in_array(' ', $ligne)) {
return null; // Le jeu n'est pas terminé
}
}
return 'Nul';
}

$grille = creerGrille();
afficherGrille($grille);

$resultat = verifierGagnant($grille);
if ($resultat === null) {
echo "<p>Le jeu n'est pas terminé.</p>";
} elseif ($resultat === 'Nul') {
echo "<p>Match nul !</p>";
} else {
echo "<p>Le joueur $resultat a gagné !</p>";
}
?>
</body>
</html>

6. Manipulation de chaînes de caractères


Les chaînes de caractères sont fréquemment utilisées en PHP. Voici quelques fonctions et
techniques utiles pour les manipuler.

6.1 Concaténation de chaînes

<?php
$prenom = "Jean";
$nom = "Dupont";
$nomComplet = $prenom . " " . $nom; // Utilisation de l'opérateur de
concaténation
echo $nomComplet; // Affiche : Jean Dupont

$age = 30;
echo "Je m'appelle $prenom et j'ai $age ans."; // Interpolation de variables
?>

6.2 Fonctions de base pour les chaînes

<?php
$phrase = "Bonjour tout le monde";

echo strlen($phrase); // Longueur de la chaîne


echo strtolower($phrase); // Convertit en minuscules
echo strtoupper($phrase); // Convertit en majuscules
echo ucfirst($phrase); // Première lettre en majuscule
echo ucwords($phrase); // Première lettre de chaque mot en majuscule

$mot = "PHP";
echo str_repeat($mot, 3); // Répète une chaîne (PHPPHPPHP)

echo str_replace("monde", "les amis", $phrase); // Remplace une partie de la


chaîne
?>

6.3 Extraction de sous-chaînes

<?php
$texte = "PHP est un langage de programmation puissant";

echo substr($texte, 0, 3); // Extrait les 3 premiers caractères (PHP)


echo substr($texte, -8); // Extrait les 8 derniers caractères (puissant)

$position = strpos($texte, "langage"); // Trouve la position d'une sous-chaîne


if ($position !== false) {
echo "Le mot 'langage' est à la position $position";
}
?>

6.4 Explode et Implode

Ces fonctions permettent de convertir entre chaînes et tableaux.

<?php
$phrase = "PHP,HTML,CSS,JavaScript";
$langages = explode(",", $phrase); // Convertit la chaîne en tableau
print_r($langages);

$nouvellePhrase = implode(" - ", $langages); // Convertit le tableau en chaîne


echo $nouvellePhrase;
?>

6.5 Expressions régulières


PHP supporte les expressions régulières via les fonctions preg_* .

<?php
$texte = "Mon adresse email est [email protected]";
$pattern = "/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/";

if (preg_match($pattern, $texte, $matches)) {


echo "Adresse email trouvée : " . $matches[0];
}

$nouveauTexte = preg_replace("/[0-9]+/", "X", "J'ai 25 ans et 3 chats.");


echo $nouveauTexte; // Affiche : J'ai X ans et X chats.
?>

Exercice 6.1
Créez une fonction formatTelephone qui prend en paramètre une chaîne représentant un
numéro de téléphone (par exemple "0123456789") et la formate selon le modèle "01 23 45 67
89".

Corrigé 6.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 6.1 - Formatage de numéro de téléphone</title>
</head>
<body>
<h2>Formatage de numéro de téléphone</h2>
<?php
function formatTelephone($numero) {
$numero = preg_replace("/[^0-9]/", "", $numero); // Supprime tous les
caractères non numériques
if (strlen($numero) != 10) {
return "Numéro invalide";
}
return implode(" ", str_split($numero, 2));
}

$numeros = [
"0123456789",
"01 23 45 67 89",
"0123 45 67 89",
"012345678", // Invalide
"01234567890" // Invalide
];

foreach ($numeros as $numero) {


echo "Original : $numero<br>";
echo "Formaté : " . formatTelephone($numero) . "<br><br>";
}
?>
</body>
</html>

Exercice 6.2
Créez une fonction censurerMots qui prend en paramètre une phrase et un tableau de mots à
censurer. La fonction doit remplacer chaque occurrence des mots à censurer par des
astérisques (*) de même longueur.

Corrigé 6.2

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 6.2 - Censure de mots</title>
</head>
<body>
<h2>Censure de mots</h2>
<?php
function censurerMots($phrase, $motsCensures) {
foreach ($motsCensures as $mot) {
$remplacement = str_repeat("*", strlen($mot));
$phrase = str_ireplace($mot, $remplacement, $phrase);
}
return $phrase;
}

$phrase = "Cette fichue voiture ne veut pas démarrer. C'est vraiment


embêtant !";
$motsCensures = ["fichue", "embêtant"];

echo "Phrase originale : $phrase<br>";


echo "Phrase censurée : " . censurerMots($phrase, $motsCensures);
?>
</body>
</html>

7. Formulaires et méthodes HTTP


Les formulaires HTML sont un moyen courant d'interagir avec les utilisateurs dans les
applications web. PHP offre des moyens simples de traiter les données de formulaire.

7.1 Création d'un formulaire HTML

<form action="traitement.php" method="POST">


<label for="nom">Nom :</label>
<input type="text" id="nom" name="nom" required><br>

<label for="email">Email :</label>


<input type="email" id="email" name="email" required><br>

<label for="message">Message :</label>


<textarea id="message" name="message" required></textarea><br>

<input type="submit" value="Envoyer">


</form>

7.2 Traitement des données de formulaire

Dans le fichier traitement.php :

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$nom = $_POST["nom"];
$email = $_POST["email"];
$message = $_POST["message"];

// Traitement des données (par exemple, envoi d'un email, sauvegarde en


base de données, etc.)
echo "Merci $nom pour votre message !";
}
?>

7.3 Méthodes GET vs POST

GET : Les données sont envoyées via l'URL. Utile pour les requêtes qui ne modifient pas
les données.
POST : Les données sont envoyées dans le corps de la requête HTTP. Préférable pour les
formulaires avec des données sensibles ou volumineuses.
7.4 Sécurisation des données de formulaire

<?php
$nom = htmlspecialchars($_POST["nom"]);
$email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL);

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "Adresse email invalide";
}
?>

Exercice 7.1

Créez un formulaire d'inscription avec les champs suivants : nom, prénom, email, mot de
passe, confirmation du mot de passe. Ajoutez une validation côté serveur pour s'assurer que
tous les champs sont remplis, que l'email est valide et que les deux mots de passe
correspondent.

Corrigé 7.1

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Exercice 7.1 - Formulaire d'inscription</title>
</head>
<body>
<h2>Formulaire d'inscription</h2>

<?php
$erreurs = [];
$succes = false;

if ($_SERVER["REQUEST_METHOD"] == "POST") {
$nom = htmlspecialchars($_POST["nom"] ?? "");
$prenom = htmlspecialchars($_POST["prenom"] ?? "");
$email = filter_var($_POST["email"] ?? "", FILTER_SANITIZE_EMAIL);
$motDePasse = $_POST["mot_de_passe"] ?? "";
$confirmationMotDePasse = $_POST["confirmation_mot_de_passe"] ?? "";

// Validation
if (empty($nom)) $erreurs[] = "Le nom est requis.";
if (empty($prenom)) $erreurs[] = "Le prénom est requis.";
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL))
$erreurs[] = "L'email est invalide.";
if (empty($motDePasse)) $erreurs[] = "Le mot de passe est requis.";
if ($motDePasse !== $confirmationMotDePasse) $erreurs[] = "Les mots de
passe ne correspondent pas.";

if (empty($erreurs)) {
$succes = true;
// Ici, vous pourriez enregistrer les données dans une base de
données
}
}
?>

<?php if ($succes): ?>


<p style="color: green;">Inscription réussie !</p>
<?php else: ?>
<?php
if (!empty($erreurs)) {
echo "<ul style='color: red;'>";
foreach ($erreurs as $erreur) {
echo "<li>$erreur</li>";
}
echo "</ul>";
}
?>

<form action="" method="POST">


<label for="nom">Nom :</label>
<input type="text" id="nom" name="nom" required><br>

<label for="prenom">Prénom :</label>


<input type="text" id="prenom" name="prenom" required><br>

<label for="email">Email :</label>


<input type="email" id="email" name="email" required><br>

<label for="mot_de_passe">Mot de passe :</label>


<input type="password" id="mot_de_passe" name="mot_de_passe"
required><br>

<label for="confirmation_mot_de_passe">Confirmez le mot de passe :


</label>
<input type="password" id="confirmation_mot_de_passe"
name="confirmation_mot_de_passe" required><br>
<input type="submit" value="S'inscrire">
</form>
<?php endif; ?>
</body>
</html>

11. Programmation orientée objet en PHP


La programmation orientée objet (POO) est un paradigme de programmation qui utilise les
"objets" pour modéliser des concepts du monde réel. PHP supporte la POO depuis sa version
5, et cette approche est largement utilisée dans le développement moderne.

11.1 Concepts de base de la POO


11.1.1 Classes et objets

Une classe est un modèle pour créer des objets. Un objet est une instance d'une classe.

<?php
class Voiture {
// Propriétés
public $marque;
public $modele;
public $couleur;

// Méthode
public function afficherInfos() {
echo "Cette voiture est une $this->marque $this->modele de couleur
$this->couleur.";
}
}

// Création d'un objet


$maVoiture = new Voiture();
$maVoiture->marque = "Renault";
$maVoiture->modele = "Clio";
$maVoiture->couleur = "rouge";

$maVoiture->afficherInfos();
?>
11.1.2 Constructeur
Le constructeur est une méthode spéciale appelée lors de la création d'un objet.

<?php
class Voiture {
public $marque;
public $modele;
public $couleur;

public function __construct($marque, $modele, $couleur) {


$this->marque = $marque;
$this->modele = $modele;
$this->couleur = $couleur;
}

public function afficherInfos() {


echo "Cette voiture est une $this->marque $this->modele de couleur
$this->couleur.";
}
}

$maVoiture = new Voiture("Peugeot", "308", "bleu");


$maVoiture->afficherInfos();
?>

11.1.3 Encapsulation

L'encapsulation consiste à restreindre l'accès direct aux propriétés d'un objet.

<?php
class CompteBancaire {
private $solde;

public function __construct($soldeInitial) {


$this->solde = $soldeInitial;
}

public function depot($montant) {


if ($montant > 0) {
$this->solde += $montant;
return true;
}
return false;
}

public function retrait($montant) {


if ($montant > 0 && $this->solde >= $montant) {
$this->solde -= $montant;
return true;
}
return false;
}

public function getSolde() {


return $this->solde;
}
}

$compte = new CompteBancaire(1000);


$compte->depot(500);
echo $compte->getSolde(); // Affiche 1500
?>

11.2 Héritage
L'héritage permet à une classe d'hériter des propriétés et méthodes d'une autre classe.

<?php
class Animal {
protected $nom;

public function __construct($nom) {


$this->nom = $nom;
}

public function seDeplacer() {


echo "$this->nom se déplace.";
}
}

class Chien extends Animal {


public function aboyer() {
echo "$this->nom aboie : Woof!";
}
}

$monChien = new Chien("Rex");


$monChien->seDeplacer(); // Affiche "Rex se déplace."
$monChien->aboyer(); // Affiche "Rex aboie : Woof!"
?>

11.3 Polymorphisme
Le polymorphisme permet à des objets de classes différentes d'être traités de manière
uniforme.

<?php
abstract class Forme {
abstract public function calculerAire();
}

class Cercle extends Forme {


private $rayon;

public function __construct($rayon) {


$this->rayon = $rayon;
}

public function calculerAire() {


return pi() * $this->rayon * $this->rayon;
}
}

class Rectangle extends Forme {


private $longueur;
private $largeur;

public function __construct($longueur, $largeur) {


$this->longueur = $longueur;
$this->largeur = $largeur;
}

public function calculerAire() {


return $this->longueur * $this->largeur;
}
}

$formes = [
new Cercle(5),
new Rectangle(4, 6)
];
foreach ($formes as $forme) {
echo "L'aire est : " . $forme->calculerAire() . "<br>";
}
?>

11.4 Interfaces
Une interface définit un contrat que les classes doivent respecter.

<?php
interface Deplacement {
public function avancer();
public function reculer();
}

class Voiture implements Deplacement {


public function avancer() {
echo "La voiture avance.";
}

public function reculer() {


echo "La voiture recule.";
}
}

class Velo implements Deplacement {


public function avancer() {
echo "Le vélo avance.";
}

public function reculer() {


echo "Le vélo recule.";
}
}

function faireDeplacer(Deplacement $objet) {


$objet->avancer();
$objet->reculer();
}

$voiture = new Voiture();


$velo = new Velo();
faireDeplacer($voiture);
faireDeplacer($velo);
?>

11.5 Traits
Les traits permettent de réutiliser du code dans plusieurs classes.

<?php
trait Logger {
public function log($message) {
echo date("Y-m-d H:i:s") . " : " . $message . "<br>";
}
}

class Utilisateur {
use Logger;

public function seConnecter() {


$this->log("Utilisateur connecté");
}
}

class Produit {
use Logger;

public function ajouter() {


$this->log("Produit ajouté");
}
}

$user = new Utilisateur();


$user->seConnecter();

$produit = new Produit();


$produit->ajouter();
?>

Exercice 11.1
Créez une classe Etudiant avec les propriétés suivantes : nom, prénom, notes (tableau).
Ajoutez des méthodes pour calculer la moyenne et déterminer si l'étudiant a réussi (moyenne
>= 10). Créez ensuite une classe Promotion qui contient un tableau d'étudiants et des
méthodes pour ajouter un étudiant, calculer la moyenne de la promotion et afficher le
classement des étudiants.

Corrigé 11.1

<?php
class Etudiant {
private $nom;
private $prenom;
private $notes;

public function __construct($nom, $prenom, $notes) {


$this->nom = $nom;
$this->prenom = $prenom;
$this->notes = $notes;
}

public function calculerMoyenne() {


return array_sum($this->notes) / count($this->notes);
}

public function aReussi() {


return $this->calculerMoyenne() >= 10;
}

public function getNomComplet() {


return $this->prenom . " " . $this->nom;
}
}

class Promotion {
private $etudiants = [];

public function ajouterEtudiant(Etudiant $etudiant) {


$this->etudiants[] = $etudiant;
}

public function calculerMoyennePromotion() {


$somme = 0;
foreach ($this->etudiants as $etudiant) {
$somme += $etudiant->calculerMoyenne();
}
return $somme / count($this->etudiants);
}

public function afficherClassement() {


usort($this->etudiants, function($a, $b) {
return $b->calculerMoyenne() - $a->calculerMoyenne();
});

echo "Classement de la promotion :<br>";


foreach ($this->etudiants as $index => $etudiant) {
$rang = $index + 1;
$moyenne = number_format($etudiant->calculerMoyenne(), 2);
echo "$rang. " . $etudiant->getNomComplet() . " - Moyenne :
$moyenne<br>";
}
}
}

// Utilisation
$promotion = new Promotion();

$etudiants = [
new Etudiant("Dupont", "Jean", [12, 14, 10, 15, 11]),
new Etudiant("Martin", "Sophie", [16, 15, 17, 14, 18]),
new Etudiant("Bernard", "Luc", [9, 11, 10, 13, 12]),
new Etudiant("Petit", "Marie", [14, 13, 15, 16, 12])
];

foreach ($etudiants as $etudiant) {


$promotion->ajouterEtudiant($etudiant);
}

echo "Moyenne de la promotion : " . number_format($promotion-


>calculerMoyennePromotion(), 2) . "<br><br>";
$promotion->afficherClassement();
?>

Exercice 11.2
Créez une hiérarchie de classes pour représenter différents types de véhicules. Commencez
par une classe abstraite Vehicule , puis créez des sous-classes comme Voiture , Moto , et
Camion . Chaque véhicule doit avoir une méthode pour calculer sa consommation de carburant.
Utilisez le polymorphisme pour créer une flotte de véhicules et calculer la consommation totale.
Corrigé 11.2

<?php
abstract class Vehicule {
protected $marque;
protected $modele;
protected $kilometrage;

public function __construct($marque, $modele, $kilometrage) {


$this->marque = $marque;
$this->modele = $modele;
$this->kilometrage = $kilometrage;
}

abstract public function calculerConsommation();

public function getInfos() {


return "$this->marque $this->modele";
}
}

class Voiture extends Vehicule {


private $nombrePortes;

public function __construct($marque, $modele, $kilometrage, $nombrePortes)


{
parent::__construct($marque, $modele, $kilometrage);
$this->nombrePortes = $nombrePortes;
}

public function calculerConsommation() {


// Consommation fictive : 5L/100km + 0.1L par 10000km
return 5 + (0.1 * floor($this->kilometrage / 10000));
}
}

class Moto extends Vehicule {


private $cylindree;

public function __construct($marque, $modele, $kilometrage, $cylindree) {


parent::__construct($marque, $modele, $kilometrage);
$this->cylindree = $cylindree;
}

public function calculerConsommation() {


// Consommation fictive : 3L/100km + 0.5L par 100cc
return 3 + (0.5 * floor($this->cylindree / 100));
}
}

class Camion extends Vehicule {


private $poidsMaximal;

public function __construct($marque, $modele, $kilometrage, $poidsMaximal)


{
parent::__construct($marque, $modele, $kilometrage);
$this->poidsMaximal = $poidsMaximal;
}

public function calculerConsommation() {


// Consommation fictive : 20L/100km + 1L par tonne
return 20 + $this->poidsMaximal;
}
}

class Flotte {
private $vehicules = [];

public function ajouterVehicule(Vehicule $vehicule) {


$this->vehicules[] = $vehicule;
}

public function calculerConsommationTotale() {


$consommationTotale = 0;
foreach ($this->vehicules as $vehicule) {
$consommationTotale += $vehicule->calculerConsommation();
}
return $consommationTotale;
}

public function afficherConsommations() {


foreach ($this->vehicules as $vehicule) {
echo $vehicule->getInfos() . " - Consommation : " .
$vehicule->calculerConsommation() . " L/100km<br>";
}
}
}

// Utilisation
$flotte = new Flotte();

$flotte->ajouterVehicule(new Voiture("Renault", "Clio", 50000, 5));


$flotte->ajouterVehicule(new Moto("Yamaha", "MT-07", 20000, 700));
$flotte->ajouterVehicule(new Camion("Volvo", "FH", 100000, 20));

echo "Consommations individuelles :<br>";


$flotte->afficherConsommations();

echo "<br>Consommation totale de la flotte : " .


$flotte->calculerConsommationTotale() . " L/100km";
?>

11.6 Méthodes et propriétés statiques


Les méthodes et propriétés statiques appartiennent à la classe plutôt qu'à une instance
spécifique de la classe.

<?php
class Compteur {
private static $count = 0;

public static function incrementer() {


self::$count++;
}

public static function getCount() {


return self::$count;
}
}

Compteur::incrementer();
Compteur::incrementer();
echo Compteur::getCount(); // Affiche 2
?>

11.7 Constantes de classe


Les constantes de classe sont des valeurs qui ne changent pas et sont associées à une classe.

<?php
class Math {
const PI = 3.14159;

public static function calculerCirconference($rayon) {


return 2 * self::PI * $rayon;
}
}

echo Math::PI; // Affiche 3.14159


echo Math::calculerCirconference(5); // Affiche 31.4159
?>

11.8 Espaces de noms (Namespaces)


Les espaces de noms permettent d'organiser le code et d'éviter les conflits de noms.

<?php
// Fichier: Animaux/Chien.php
namespace Animaux;

class Chien {
public function aboyer() {
echo "Woof!";
}
}

// Fichier: main.php
require_once 'Animaux/Chien.php';

use Animaux\Chien;

$monChien = new Chien();


$monChien->aboyer();
?>

11.9 Autoloading de classes


L'autoloading permet de charger automatiquement les classes lorsqu'elles sont utilisées.

<?php
spl_autoload_register(function($className) {
include_once $className . '.php';
});

// Maintenant, vous pouvez utiliser vos classes sans les require/include


explicites
$monChien = new Animaux\Chien();
$monChien->aboyer();
?>

11.10 Design Patterns


Les design patterns sont des solutions réutilisables à des problèmes courants en POO.

11.10.1 Singleton
Le pattern Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès
global à cette instance.

<?php
class Database {
private static $instance = null;
private $connection;

private function __construct() {


$this->connection = new PDO('mysql:host=localhost;dbname=test',
'user', 'password');
}

public static function getInstance() {


if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}

public function query($sql) {


// Exécute la requête SQL
}
}

$db = Database::getInstance();
$db->query("SELECT * FROM users");
?>

11.10.2 Factory
Le pattern Factory définit une interface pour créer un objet, mais laisse les sous-classes
décider quelle classe instancier.

<?php
interface Animal {
public function parler();
}

class Chien implements Animal {


public function parler() {
return "Woof!";
}
}

class Chat implements Animal {


public function parler() {
return "Miaou!";
}
}

class AnimalFactory {
public static function creerAnimal($type) {
switch ($type) {
case 'chien':
return new Chien();
case 'chat':
return new Chat();
default:
throw new Exception("Animal inconnu");
}
}
}

$animal = AnimalFactory::creerAnimal('chien');
echo $animal->parler(); // Affiche "Woof!"
?>

Exercice 11.3
Créez un système de gestion de logs utilisant le pattern Singleton. La classe Logger doit avoir
des méthodes pour logger des messages d'information, d'avertissement et d'erreur. Les logs
doivent être écrits dans un fichier avec la date et l'heure de chaque message.
Corrigé 11.3

<?php
class Logger {
private static $instance = null;
private $logFile;

private function __construct() {


$this->logFile = 'application.log';
}

public static function getInstance() {


if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}

private function log($level, $message) {


$date = date('Y-m-d H:i:s');
$logMessage = "[$date] [$level] $message" . PHP_EOL;
file_put_contents($this->logFile, $logMessage, FILE_APPEND);
}

public function info($message) {


$this->log('INFO', $message);
}

public function warning($message) {


$this->log('WARNING', $message);
}

public function error($message) {


$this->log('ERROR', $message);
}
}

// Utilisation
$logger = Logger::getInstance();
$logger->info("L'application a démarré");
$logger->warning("Attention, l'espace disque est faible");
$logger->error("Erreur critique : impossible de se connecter à la base de
données");
// Vérifiez le contenu du fichier application.log
?>

Exercice 11.4
Implémentez un système de gestion de produits en utilisant le pattern Factory. Créez une
interface Produit et plusieurs classes de produits (par exemple, Livre , DVD , Vetement ).
Utilisez une ProduitFactory pour créer les différents types de produits. Ajoutez une méthode
pour afficher les détails de chaque produit.

Corrigé 11.4

<?php
interface Produit {
public function afficherDetails();
}

class Livre implements Produit {


private $titre;
private $auteur;
private $isbn;

public function __construct($titre, $auteur, $isbn) {


$this->titre = $titre;
$this->auteur = $auteur;
$this->isbn = $isbn;
}

public function afficherDetails() {


return "Livre: '$this->titre' par $this->auteur, ISBN: $this->isbn";
}
}

class DVD implements Produit {


private $titre;
private $realisateur;
private $duree;

public function __construct($titre, $realisateur, $duree) {


$this->titre = $titre;
$this->realisateur = $realisateur;
$this->duree = $duree;
}
public function afficherDetails() {
return "DVD: '$this->titre' réalisé par $this->realisateur, Durée:
$this->duree min";
}
}

class Vetement implements Produit {


private $type;
private $taille;
private $couleur;

public function __construct($type, $taille, $couleur) {


$this->type = $type;
$this->taille = $taille;
$this->couleur = $couleur;
}

public function afficherDetails() {


return "Vêtement: $this->type, Taille: $this->taille, Couleur: $this-
>couleur";
}
}

class ProduitFactory {
public static function creerProduit($type, $details) {
switch ($type) {
case 'livre':
return new Livre($details['titre'], $details['auteur'],
$details['isbn']);
case 'dvd':
return new DVD($details['titre'], $details['realisateur'],
$details['duree']);
case 'vetement':
return new Vetement($details['type'], $details['taille'],
$details['couleur']);
default:
throw new Exception("Type de produit inconnu");
}
}
}

// Utilisation
$produits = [
ProduitFactory::creerProduit('livre', [
'titre' => '1984',
'auteur' => 'George Orwell',
'isbn' => '978-0451524935'
]),
ProduitFactory::creerProduit('dvd', [
'titre' => 'Inception',
'realisateur' => 'Christopher Nolan',
'duree' => 148
]),
ProduitFactory::creerProduit('vetement', [
'type' => 'T-shirt',
'taille' => 'M',
'couleur' => 'Bleu'
])
];

foreach ($produits as $produit) {


echo $produit->afficherDetails() . PHP_EOL;
}
?>

12. Bases de données et MySQL


12.1 Introduction à MySQL
MySQL est un système de gestion de base de données relationnelle (SGBDR) largement utilisé
dans le développement web. Il est open-source, rapide et fiable.

12.2 Concepts de base des bases de données relationnelles


Tables : Structures pour stocker les données
Colonnes : Définissent les types de données
Lignes : Représentent des enregistrements individuels
Clés primaires : Identifient de manière unique chaque enregistrement
Clés étrangères : Établissent des relations entre les tables

12.3 Opérations CRUD


CRUD représente les quatre opérations de base sur les données :

Create (Créer) : Insérer de nouvelles données


Read (Lire) : Récupérer des données existantes
Update (Mettre à jour) : Modifier des données existantes
Delete (Supprimer) : Supprimer des données existantes

13. PDO (PHP Data Objects)


PDO est une extension PHP qui fournit une interface cohérente pour accéder aux bases de
données. Elle supporte plusieurs systèmes de bases de données, dont MySQL.

13.1 Connexion à une base de données avec PDO

<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=ma_base', 'utilisateur',
'mot_de_passe');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connexion réussie";
} catch(PDOException $e) {
echo "Erreur de connexion : " . $e->getMessage();
}
?>

13.2 Exécution de requêtes avec PDO


13.2.1 Requêtes simples

<?php
$query = "SELECT * FROM utilisateurs";
$stmt = $pdo->query($query);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['nom'] . " " . $row['prenom'] . "<br>";
}
?>

13.2.2 Requêtes préparées

Les requêtes préparées sont plus sécurisées et performantes pour les requêtes répétitives.
<?php
$query = "INSERT INTO utilisateurs (nom, prenom, email) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($query);
$stmt->execute(['Dupont', 'Jean', '[email protected]']);
?>

13.3 Gestion des transactions


Les transactions permettent d'exécuter plusieurs requêtes comme une seule opération
atomique.

<?php
try {
$pdo->beginTransaction();

$stmt = $pdo->prepare("UPDATE comptes SET solde = solde - ? WHERE id =


?");
$stmt->execute([100, 1]);

$stmt = $pdo->prepare("UPDATE comptes SET solde = solde + ? WHERE id =


?");
$stmt->execute([100, 2]);

$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo "Erreur : " . $e->getMessage();
}
?>

13.4 Récupération des résultats


PDO offre plusieurs méthodes pour récupérer les résultats :

<?php
$stmt = $pdo->query("SELECT * FROM utilisateurs");

// Récupérer une ligne


$row = $stmt->fetch(PDO::FETCH_ASSOC);

// Récupérer toutes les lignes


$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

// Récupérer une seule colonne


$noms = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
?>

13.5 Gestion des erreurs


PDO peut être configuré pour lancer des exceptions en cas d'erreur :

<?php
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

try {
// Code susceptible de générer une erreur
} catch (PDOException $e) {
echo "Erreur : " . $e->getMessage();
}
?>

Exercice 13.1
Créez une classe DatabaseManager qui encapsule la connexion PDO et fournit des méthodes
pour exécuter des requêtes courantes (select, insert, update, delete). Utilisez cette classe pour
créer un système simple de gestion des utilisateurs (ajout, modification, suppression, liste).

Corrigé 13.1

<?php
class DatabaseManager {
private $pdo;

public function __construct($host, $dbname, $user, $pass) {


try {
$this->pdo = new PDO("mysql:host=$host;dbname=$dbname", $user,
$pass);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Erreur de connexion : " . $e->getMessage());
}
}

public function select($table, $conditions = [], $fields = "*") {


$sql = "SELECT $fields FROM $table";
if (!empty($conditions)) {
$sql .= " WHERE " . implode(' AND ', array_map(function ($key) {
return "$key = :$key";
}, array_keys($conditions)));
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($conditions);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

public function insert($table, $data) {


$fields = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO $table ($fields) VALUES ($placeholders)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute($data);
return $this->pdo->lastInsertId();
}

public function update($table, $data, $conditions) {


$set = implode(', ', array_map(function ($key) {
return "$key = :$key";
}, array_keys($data)));
$where = implode(' AND ', array_map(function ($key) {
return "$key = :condition_$key";
}, array_keys($conditions)));
$sql = "UPDATE $table SET $set WHERE $where";
$stmt = $this->pdo->prepare($sql);
$stmt->execute(array_merge($data, array_combine(
array_map(function($key) { return "condition_$key"; },
array_keys($conditions)),
$conditions
)));
return $stmt->rowCount();
}

public function delete($table, $conditions) {


$where = implode(' AND ', array_map(function ($key) {
return "$key = :$key";
}, array_keys($conditions)));
$sql = "DELETE FROM $table WHERE $where";
$stmt = $this->pdo->prepare($sql);
$stmt->execute($conditions);
return $stmt->rowCount();
}
}

// Utilisation de la classe DatabaseManager

$db = new DatabaseManager('localhost', 'ma_base', 'utilisateur',


'mot_de_passe');

// Ajouter un utilisateur
$id = $db->insert('utilisateurs', [
'nom' => 'Dupont',
'prenom' => 'Jean',
'email' => '[email protected]'
]);
echo "Nouvel utilisateur ajouté avec l'ID : $id\n";

// Lister les utilisateurs


$utilisateurs = $db->select('utilisateurs');
foreach ($utilisateurs as $user) {
echo $user['nom'] . " " . $user['prenom'] . "\n";
}

// Mettre à jour un utilisateur


$updated = $db->update('utilisateurs',
['email' => '[email protected]'],
['id' => $id]
);
echo "Utilisateur mis à jour : $updated ligne(s) affectée(s)\n";

// Supprimer un utilisateur
$deleted = $db->delete('utilisateurs', ['id' => $id]);
echo "Utilisateur supprimé : $deleted ligne(s) affectée(s)\n";
?>

Exercice 13.2
Créez une application simple de gestion de tâches (todo list) utilisant PDO. L'application doit
permettre d'ajouter des tâches, de les marquer comme terminées, de les supprimer et d'afficher
la liste des tâches en cours et terminées. Utilisez des transactions pour vous assurer que les
opérations critiques sont atomiques.
Corrigé 13.2

<?php
class TodoManager {
private $pdo;

public function __construct($host, $dbname, $user, $pass) {


try {
$this->pdo = new PDO("mysql:host=$host;dbname=$dbname", $user,
$pass);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Erreur de connexion : " . $e->getMessage());
}
}

public function ajouterTache($description) {


$sql = "INSERT INTO taches (description, terminee) VALUES
(:description, 0)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute(['description' => $description]);
return $this->pdo->lastInsertId();
}

public function marquerTerminee($id) {


$this->pdo->beginTransaction();
try {
$sql = "UPDATE taches SET terminee = 1 WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
return false;
}
}

public function supprimerTache($id) {


$this->pdo->beginTransaction();
try {
$sql = "DELETE FROM taches WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->execute(['id' => $id]);
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
return false;
}
}

public function listerTaches($terminee = false) {


$sql = "SELECT * FROM taches WHERE terminee = :terminee ORDER BY id
DESC";
$stmt = $this->pdo->prepare($sql);
$stmt->execute(['terminee' => $terminee ? 1 : 0]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}

// Utilisation de la classe TodoManager

$todo = new TodoManager('localhost', 'ma_base', 'utilisateur',


'mot_de_passe');

// Ajouter une tâche


$id = $todo->ajouterTache("Apprendre PDO");
echo "Nouvelle tâche ajoutée avec l'ID : $id\n";

// Lister les tâches en cours


echo "Tâches en cours :\n";
foreach ($todo->listerTaches() as $tache) {
echo "- " . $tache['description'] . "\n";
}

// Marquer une tâche comme terminée


if ($todo->marquerTerminee($id)) {
echo "Tâche marquée comme terminée\n";
}

// Lister les tâches terminées


echo "Tâches terminées :\n";
foreach ($todo->listerTaches(true) as $tache) {
echo "- " . $tache['description'] . "\n";
}

// Supprimer une tâche


if ($todo->supprimerTache($id)) {
echo "Tâche supprimée\n";
}
?>

14. Sécurité en PHP


La sécurité est un aspect crucial du développement web. En tant que développeurs PHP, il est
essentiel de comprendre les menaces courantes et les meilleures pratiques pour sécuriser vos
applications.

14.1 Injection SQL


L'injection SQL est l'une des vulnérabilités les plus dangereuses dans les applications web.

14.1.1 Exemple de code vulnérable

$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = $mysqli->query($query);

14.1.2 Prevention avec PDO et requêtes préparées

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");


$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();

14.2 Cross-Site Scripting (XSS)


Le XSS permet à un attaquant d'injecter du code malveillant dans une page web.

14.2.1 Exemple de code vulnérable

echo "Bienvenue, " . $_GET['name'] . "!";

14.2.2 Prevention avec échappement


echo "Bienvenue, " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8') .
"!";

14.3 Cross-Site Request Forgery (CSRF)


Le CSRF force un utilisateur à exécuter des actions indésirables sur une application web dans
laquelle il est authentifié.

14.3.1 Prevention avec des jetons CSRF

session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Dans le formulaire HTML


<input type="hidden" name="csrf_token" value="<?php echo
$_SESSION['csrf_token']; ?>">

// Vérification lors de la soumission du formulaire


if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF token invalide');
}

14.4 Gestion sécurisée des mots de passe


14.4.1 Hachage des mots de passe
Utilisez toujours des fonctions de hachage sécurisées pour stocker les mots de passe.

$password = 'mot_de_passe_utilisateur';
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Vérification
if (password_verify($password, $hashed_password)) {
echo "Mot de passe correct";
}
14.5 Gestion des sessions
14.5.1 Configuration sécurisée des sessions

ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_secure', 1);
session_start();

14.5.2 Régénération de l'ID de session

session_regenerate_id(true);

14.6 Validation et filtrage des entrées


Toujours valider et filtrer les entrées utilisateur.

$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);


if ($email === false) {
die('Email invalide');
}

14.7 Protection contre les attaques par force brute


Limitez le nombre de tentatives de connexion.

session_start();
if (!isset($_SESSION['login_attempts'])) {
$_SESSION['login_attempts'] = 0;
}

if ($_SESSION['login_attempts'] > 5) {
die('Trop de tentatives. Réessayez plus tard.');
}

// Après une tentative de connexion


$_SESSION['login_attempts']++;
14.8 Sécurisation des en-têtes HTTP
Utilisez des en-têtes HTTP pour améliorer la sécurité de votre application.

header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");

Exercice 14.1
Créez une classe SecurityHelper qui fournit des méthodes statiques pour :

1. Générer et vérifier des jetons CSRF


2. Valider et assainir différents types d'entrées (email, nom d'utilisateur, URL)
3. Générer un hachage sécurisé d'un mot de passe
4. Vérifier un mot de passe par rapport à son hachage

Corrigé 14.1

<?php
class SecurityHelper {
public static function generateCsrfToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}

public static function verifyCsrfToken($token) {


return hash_equals($_SESSION['csrf_token'], $token);
}

public static function validateEmail($email) {


return filter_var($email, FILTER_VALIDATE_EMAIL);
}

public static function sanitizeUsername($username) {


return preg_replace('/[^a-zA-Z0-9_]/', '', $username);
}

public static function validateUrl($url) {


return filter_var($url, FILTER_VALIDATE_URL);
}

public static function hashPassword($password) {


return password_hash($password, PASSWORD_DEFAULT);
}

public static function verifyPassword($password, $hash) {


return password_verify($password, $hash);
}
}

// Utilisation
session_start();

// Générer un token CSRF


$csrfToken = SecurityHelper::generateCsrfToken();
echo "CSRF Token: $csrfToken\n";

// Valider un email
$email = "[email protected]";
if (SecurityHelper::validateEmail($email)) {
echo "Email valide\n";
} else {
echo "Email invalide\n";
}

// Assainir un nom d'utilisateur


$username = "John123!@#";
$safeUsername = SecurityHelper::sanitizeUsername($username);
echo "Username assaini: $safeUsername\n";

// Hacher et vérifier un mot de passe


$password = "motDePasse123!";
$hashedPassword = SecurityHelper::hashPassword($password);
echo "Mot de passe haché: $hashedPassword\n";

if (SecurityHelper::verifyPassword($password, $hashedPassword)) {
echo "Mot de passe vérifié avec succès\n";
} else {
echo "Échec de la vérification du mot de passe\n";
}
?>
Exercice 14.2
Créez une classe LoginManager qui gère la connexion des utilisateurs de manière sécurisée.
Cette classe doit :

1. Vérifier les identifiants de l'utilisateur


2. Protéger contre les attaques par force brute en limitant les tentatives de connexion
3. Générer et gérer des jetons de session sécurisés après une connexion réussie
4. Fournir une méthode pour la déconnexion sécurisée

Corrigé 14.2

<?php
class LoginManager {
private $pdo;
private $maxAttempts = 5;
private $lockoutTime = 900; // 15 minutes

public function __construct($pdo) {


$this->pdo = $pdo;
}

public function login($username, $password) {


session_start();

if ($this->isLockedOut()) {
return "Compte verrouillé. Réessayez plus tard.";
}

$user = $this->getUserByUsername($username);
if ($user && password_verify($password, $user['password'])) {
$this->resetLoginAttempts();
$this->setLoggedInSession($user['id']);
return "Connexion réussie";
} else {
$this->incrementLoginAttempts();
return "Identifiants invalides";
}
}

private function isLockedOut() {


if (!isset($_SESSION['login_attempts']) ||
!isset($_SESSION['last_attempt_time'])) {
return false;
}
if ($_SESSION['login_attempts'] >= $this->maxAttempts) {
$lockoutExpires = $_SESSION['last_attempt_time'] + $this-
>lockoutTime;
if (time() < $lockoutExpires) {
return true;
}
// Réinitialiser si le temps de verrouillage est passé
$this->resetLoginAttempts();
}
return false;
}

private function incrementLoginAttempts() {


if (!isset($_SESSION['login_attempts'])) {
$_SESSION['login_attempts'] = 1;
} else {
$_SESSION['login_attempts']++;
}
$_SESSION['last_attempt_time'] = time();
}

private function resetLoginAttempts() {


unset($_SESSION['login_attempts']);
unset($_SESSION['last_attempt_time']);
}

private function setLoggedInSession($userId) {


session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
$_SESSION['last_activity'] = time();
}

public function logout() {


session_start();
$_SESSION = array();
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
}
private function getUserByUsername($username) {
$stmt = $this->pdo->prepare("SELECT id, password FROM users WHERE
username = ?");
$stmt->execute([$username]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}

// Utilisation
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$loginManager = new LoginManager($pdo);

// Exemple de connexion
echo $loginManager->login("username", "password") . "\n";

// Exemple de déconnexion
$loginManager->logout();
echo "Déconnexion effectuée\n";

} catch (PDOException $e) {


echo "Erreur de base de données : " . $e->getMessage();
}
?>

14. Sécurité en PHP


La sécurité est un aspect crucial du développement web. En tant que développeurs PHP, il est
essentiel de comprendre les menaces courantes et les meilleures pratiques pour sécuriser vos
applications.

14.1 Injection SQL


L'injection SQL est l'une des vulnérabilités les plus dangereuses dans les applications web.

14.1.1 Exemple de code vulnérable


$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = $mysqli->query($query);

14.1.2 Prevention avec PDO et requêtes préparées

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");


$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();

14.2 Cross-Site Scripting (XSS)


Le XSS permet à un attaquant d'injecter du code malveillant dans une page web.

14.2.1 Exemple de code vulnérable

echo "Bienvenue, " . $_GET['name'] . "!";

14.2.2 Prevention avec échappement

echo "Bienvenue, " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8') .


"!";

14.3 Cross-Site Request Forgery (CSRF)


Le CSRF force un utilisateur à exécuter des actions indésirables sur une application web dans
laquelle il est authentifié.

14.3.1 Prevention avec des jetons CSRF

session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Dans le formulaire HTML
<input type="hidden" name="csrf_token" value="<?php echo
$_SESSION['csrf_token']; ?>">

// Vérification lors de la soumission du formulaire


if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF token invalide');
}

14.4 Gestion sécurisée des mots de passe


14.4.1 Hachage des mots de passe
Utilisez toujours des fonctions de hachage sécurisées pour stocker les mots de passe.

$password = 'mot_de_passe_utilisateur';
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Vérification
if (password_verify($password, $hashed_password)) {
echo "Mot de passe correct";
}

14.5 Gestion des sessions


14.5.1 Configuration sécurisée des sessions

ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_secure', 1);
session_start();

14.5.2 Régénération de l'ID de session

session_regenerate_id(true);

14.6 Validation et filtrage des entrées


Toujours valider et filtrer les entrées utilisateur.

$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);


if ($email === false) {
die('Email invalide');
}

14.7 Protection contre les attaques par force brute


Limitez le nombre de tentatives de connexion.

session_start();
if (!isset($_SESSION['login_attempts'])) {
$_SESSION['login_attempts'] = 0;
}

if ($_SESSION['login_attempts'] > 5) {
die('Trop de tentatives. Réessayez plus tard.');
}

// Après une tentative de connexion


$_SESSION['login_attempts']++;

14.8 Sécurisation des en-têtes HTTP


Utilisez des en-têtes HTTP pour améliorer la sécurité de votre application.

header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");

Exercice 14.1

Créez une classe SecurityHelper qui fournit des méthodes statiques pour :

1. Générer et vérifier des jetons CSRF


2. Valider et assainir différents types d'entrées (email, nom d'utilisateur, URL)
3. Générer un hachage sécurisé d'un mot de passe
4. Vérifier un mot de passe par rapport à son hachage

Corrigé 14.1

<?php
class SecurityHelper {
public static function generateCsrfToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}

public static function verifyCsrfToken($token) {


return hash_equals($_SESSION['csrf_token'], $token);
}

public static function validateEmail($email) {


return filter_var($email, FILTER_VALIDATE_EMAIL);
}

public static function sanitizeUsername($username) {


return preg_replace('/[^a-zA-Z0-9_]/', '', $username);
}

public static function validateUrl($url) {


return filter_var($url, FILTER_VALIDATE_URL);
}

public static function hashPassword($password) {


return password_hash($password, PASSWORD_DEFAULT);
}

public static function verifyPassword($password, $hash) {


return password_verify($password, $hash);
}
}

// Utilisation
session_start();

// Générer un token CSRF


$csrfToken = SecurityHelper::generateCsrfToken();
echo "CSRF Token: $csrfToken\n";
// Valider un email
$email = "[email protected]";
if (SecurityHelper::validateEmail($email)) {
echo "Email valide\n";
} else {
echo "Email invalide\n";
}

// Assainir un nom d'utilisateur


$username = "John123!@#";
$safeUsername = SecurityHelper::sanitizeUsername($username);
echo "Username assaini: $safeUsername\n";

// Hacher et vérifier un mot de passe


$password = "motDePasse123!";
$hashedPassword = SecurityHelper::hashPassword($password);
echo "Mot de passe haché: $hashedPassword\n";

if (SecurityHelper::verifyPassword($password, $hashedPassword)) {
echo "Mot de passe vérifié avec succès\n";
} else {
echo "Échec de la vérification du mot de passe\n";
}
?>

Exercice 14.2
Créez une classe LoginManager qui gère la connexion des utilisateurs de manière sécurisée.
Cette classe doit :

1. Vérifier les identifiants de l'utilisateur


2. Protéger contre les attaques par force brute en limitant les tentatives de connexion
3. Générer et gérer des jetons de session sécurisés après une connexion réussie
4. Fournir une méthode pour la déconnexion sécurisée

Corrigé 14.2

<?php
class LoginManager {
private $pdo;
private $maxAttempts = 5;
private $lockoutTime = 900; // 15 minutes

public function __construct($pdo) {


$this->pdo = $pdo;
}

public function login($username, $password) {


session_start();

if ($this->isLockedOut()) {
return "Compte verrouillé. Réessayez plus tard.";
}

$user = $this->getUserByUsername($username);
if ($user && password_verify($password, $user['password'])) {
$this->resetLoginAttempts();
$this->setLoggedInSession($user['id']);
return "Connexion réussie";
} else {
$this->incrementLoginAttempts();
return "Identifiants invalides";
}
}

private function isLockedOut() {


if (!isset($_SESSION['login_attempts']) ||
!isset($_SESSION['last_attempt_time'])) {
return false;
}
if ($_SESSION['login_attempts'] >= $this->maxAttempts) {
$lockoutExpires = $_SESSION['last_attempt_time'] + $this-
>lockoutTime;
if (time() < $lockoutExpires) {
return true;
}
// Réinitialiser si le temps de verrouillage est passé
$this->resetLoginAttempts();
}
return false;
}

private function incrementLoginAttempts() {


if (!isset($_SESSION['login_attempts'])) {
$_SESSION['login_attempts'] = 1;
} else {
$_SESSION['login_attempts']++;
}
$_SESSION['last_attempt_time'] = time();
}

private function resetLoginAttempts() {


unset($_SESSION['login_attempts']);
unset($_SESSION['last_attempt_time']);
}

private function setLoggedInSession($userId) {


session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
$_SESSION['last_activity'] = time();
}

public function logout() {


session_start();
$_SESSION = array();
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
}

private function getUserByUsername($username) {


$stmt = $this->pdo->prepare("SELECT id, password FROM users WHERE
username = ?");
$stmt->execute([$username]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}

// Utilisation
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$loginManager = new LoginManager($pdo);

// Exemple de connexion
echo $loginManager->login("username", "password") . "\n";
// Exemple de déconnexion
$loginManager->logout();
echo "Déconnexion effectuée\n";

} catch (PDOException $e) {


echo "Erreur de base de données : " . $e->getMessage();
}
?>

15. Gestion des fichiers et des répertoires en PHP


La manipulation des fichiers et des répertoires est une compétence essentielle pour tout
développeur PHP. Elle permet de gérer le stockage, la lecture et l'écriture de données, ainsi
que l'organisation des fichiers sur le serveur.

15.1 Lecture de fichiers


PHP offre plusieurs méthodes pour lire le contenu des fichiers.

15.1.1 Lecture complète d'un fichier

<?php
// Lecture du contenu entier d'un fichier
$contenu = file_get_contents('mon_fichier.txt');
echo $contenu;

// Lecture ligne par ligne


$lignes = file('mon_fichier.txt');
foreach ($lignes as $ligne) {
echo $ligne;
}
?>

15.1.2 Lecture avec fopen et fread

<?php
$fichier = fopen('mon_fichier.txt', 'r');
if ($fichier) {
while (($ligne = fgets($fichier)) !== false) {
echo $ligne;
}
fclose($fichier);
}
?>

15.2 Écriture dans des fichiers


L'écriture dans des fichiers permet de stocker des données de manière persistante.

15.2.1 Écriture simple

<?php
$contenu = "Ceci est un nouveau contenu.";
file_put_contents('nouveau_fichier.txt', $contenu);

// Ajouter du contenu à un fichier existant


file_put_contents('fichier_existant.txt', $contenu, FILE_APPEND);
?>

15.2.2 Écriture avec fopen et fwrite

<?php
$fichier = fopen('mon_fichier.txt', 'w');
if ($fichier) {
fwrite($fichier, "Première ligne\n");
fwrite($fichier, "Deuxième ligne\n");
fclose($fichier);
}
?>

15.3 Manipulation de répertoires


PHP offre des fonctions pour créer, lire et supprimer des répertoires.

15.3.1 Création de répertoires


<?php
if (!file_exists('mon_dossier')) {
mkdir('mon_dossier', 0755);
}
?>

15.3.2 Lecture du contenu d'un répertoire

<?php
$dossier = 'mon_dossier';
if (is_dir($dossier)) {
if ($handle = opendir($dossier)) {
while (($fichier = readdir($handle)) !== false) {
if ($fichier != "." && $fichier != "..") {
echo $fichier . "\n";
}
}
closedir($handle);
}
}
?>

15.4 Gestion des droits et permissions


La gestion des droits est cruciale pour la sécurité des fichiers et des répertoires.

<?php
// Changer les permissions d'un fichier
chmod('mon_fichier.txt', 0644);

// Vérifier les permissions


if (is_readable('mon_fichier.txt')) {
echo "Le fichier est lisible";
}
if (is_writable('mon_fichier.txt')) {
echo "Le fichier est modifiable";
}
?>

15.5 Manipulation de chemins


PHP fournit des fonctions utiles pour manipuler les chemins de fichiers.

<?php
$chemin = "/home/user/documents/fichier.txt";

echo dirname($chemin); // Retourne le chemin du dossier parent


echo basename($chemin); // Retourne le nom du fichier
echo pathinfo($chemin, PATHINFO_EXTENSION); // Retourne l'extension du fichier
?>

15.6 Upload de fichiers


La gestion des uploads de fichiers est courante dans les applications web.

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (isset($_FILES["fichier"]) && $_FILES["fichier"]["error"] == 0) {
$dossier_cible = "uploads/";
$fichier_cible = $dossier_cible . basename($_FILES["fichier"]
["name"]);

if (move_uploaded_file($_FILES["fichier"]["tmp_name"],
$fichier_cible)) {
echo "Le fichier ". basename($_FILES["fichier"]["name"]) . " a été
uploadé.";
} else {
echo "Erreur lors de l'upload du fichier.";
}
}
}
?>

15.7 Sécurité dans la manipulation de fichiers


Il est crucial de prendre en compte la sécurité lors de la manipulation de fichiers.

<?php
// Vérification du type MIME
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['fichier']['tmp_name']);
finfo_close($finfo);
$types_autorises = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($type, $types_autorises)) {
die("Type de fichier non autorisé");
}

// Nettoyage du nom de fichier


$nom_fichier = preg_replace("/[^a-zA-Z0-9.]/", "", $_FILES['fichier']
['name']);
?>

Exercice 15.1
Créez une classe FileManager qui offre des méthodes pour :

1. Lire le contenu d'un fichier


2. Écrire dans un fichier (avec option d'ajout ou de remplacement)
3. Lister les fichiers d'un répertoire
4. Créer un répertoire
5. Supprimer un fichier ou un répertoire

Assurez-vous que la classe gère les erreurs et les exceptions de manière appropriée.

Corrigé 15.1

<?php
class FileManager {
public function readFile($filename) {
if (!file_exists($filename)) {
throw new Exception("Le fichier $filename n'existe pas.");
}
return file_get_contents($filename);
}

public function writeFile($filename, $content, $append = false) {


$flag = $append ? FILE_APPEND : 0;
if (file_put_contents($filename, $content, $flag) === false) {
throw new Exception("Impossible d'écrire dans le fichier
$filename.");
}
return true;
}
public function listDirectory($directory) {
if (!is_dir($directory)) {
throw new Exception("Le répertoire $directory n'existe pas.");
}
$files = scandir($directory);
return array_diff($files, array('.', '..'));
}

public function createDirectory($directory) {


if (file_exists($directory)) {
throw new Exception("Le répertoire $directory existe déjà.");
}
if (!mkdir($directory, 0755, true)) {
throw new Exception("Impossible de créer le répertoire
$directory.");
}
return true;
}

public function delete($path) {


if (!file_exists($path)) {
throw new Exception("Le chemin $path n'existe pas.");
}
if (is_dir($path)) {
if (!rmdir($path)) {
throw new Exception("Impossible de supprimer le répertoire
$path.");
}
} else {
if (!unlink($path)) {
throw new Exception("Impossible de supprimer le fichier
$path.");
}
}
return true;
}
}

// Utilisation
try {
$fm = new FileManager();

// Lire un fichier
echo $fm->readFile('test.txt') . "\n";

// Écrire dans un fichier


$fm->writeFile('nouveau.txt', "Contenu du fichier\n");
$fm->writeFile('nouveau.txt', "Nouvelle ligne\n", true);

// Lister un répertoire
print_r($fm->listDirectory('.'));

// Créer un répertoire
$fm->createDirectory('nouveau_dossier');

// Supprimer un fichier
$fm->delete('fichier_a_supprimer.txt');

// Supprimer un répertoire
$fm->delete('dossier_vide');

} catch (Exception $e) {


echo "Erreur : " . $e->getMessage();
}
?>

Exercice 15.2
Créez un script PHP qui implémente un gestionnaire de fichiers web simple. Ce script doit
permettre :

1. D'afficher le contenu d'un répertoire


2. De créer des fichiers et des répertoires
3. De télécharger des fichiers
4. De supprimer des fichiers et des répertoires

Assurez-vous d'implémenter des mesures de sécurité de base (par exemple, limiter l'accès à
un répertoire spécifique).

Corrigé 15.2

<?php
session_start();

class WebFileManager {
private $root_dir;

public function __construct($root_dir) {


$this->root_dir = realpath($root_dir);
}

public function listDirectory($dir = '') {


$path = $this->getFullPath($dir);
if (!is_dir($path)) {
throw new Exception("Répertoire invalide.");
}
$items = scandir($path);
$files = [];
foreach ($items as $item) {
if ($item != "." && $item != "..") {
$fullPath = $path . DIRECTORY_SEPARATOR . $item;
$files[] = [
'name' => $item,
'type' => is_dir($fullPath) ? 'dir' : 'file',
'size' => is_file($fullPath) ? filesize($fullPath) : '-'
];
}
}
return $files;
}

public function createItem($name, $type, $dir = '') {


$path = $this->getFullPath($dir . DIRECTORY_SEPARATOR . $name);
if ($type === 'dir') {
if (!mkdir($path)) {
throw new Exception("Impossible de créer le répertoire.");
}
} elseif ($type === 'file') {
if (file_put_contents($path, '') === false) {
throw new Exception("Impossible de créer le fichier.");
}
}
}

public function deleteItem($name, $dir = '') {


$path = $this->getFullPath($dir . DIRECTORY_SEPARATOR . $name);
if (is_dir($path)) {
if (!rmdir($path)) {
throw new Exception("Impossible de supprimer le répertoire.");
}
} elseif (is_file($path)) {
if (!unlink($path)) {
throw new Exception("Impossible de supprimer le fichier.");
}
}
}

public function downloadFile($name, $dir = '') {


$path = $this->getFullPath($dir . DIRECTORY_SEPARATOR . $name);
if (!is_file($path)) {
throw new Exception("Fichier non trouvé.");
}
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($path)
. '"');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;
}

private function getFullPath($path) {


$fullPath = realpath($this->root_dir . DIRECTORY_SEPARATOR . $path);
if ($fullPath === false || strpos($fullPath, $this->root_dir) !== 0) {
throw new Exception("Chemin non autorisé.");
}
return $fullPath;
}
}

// Utilisation
$root_dir = './files'; // Répertoire racine pour le gestionnaire de fichiers
$fm = new WebFileManager($root_dir);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {


try {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create':
$fm->createItem($_POST['name'], $_POST['type'],
$_POST['dir']);
break;
case 'delete':
$fm->deleteItem($_POST['name'], $_POST['dir']);
break;
}
} elseif (isset($_FILES['upload'])) {
$uploadDir = $fm->getFullPath($_POST['dir']);
$uploadFile = $uploadDir . DIRECTORY_SEPARATOR .
basename($_FILES['upload']['name']);
if (move_uploaded_file($_FILES['upload']['tmp_name'],
$uploadFile)) {
echo "Fichier uploadé avec succès.";
} else {
throw new Exception("Échec de l'upload du fichier.");
}
}
} catch (Exception $e) {
echo "Erreur : " . $e->getMessage();
}
}

if (isset($_GET['download'])) {
try {
$fm->downloadFile($_GET['download'], $_GET['dir']);
} catch (Exception $e) {
echo "Erreur : " . $e->getMessage();
}
}

$currentDir = isset($_GET['dir']) ? $_GET['dir'] : '';


try {
$files = $fm->listDirectory($currentDir);
} catch (Exception $e) {
echo "Erreur : " . $e->getMessage();
$files = [];
}

// Affichage HTML du gestionnaire de fichiers


?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Gestionnaire de fichiers Web</title>
</head>
<body>
<h1>Gestionnaire de fichiers</h1>

<h2>Contenu du répertoire : <?php echo $currentDir ? $currentDir :


'racine'; ?></h2>
<table border="1">
<tr>
<th>Nom</th>
<th>Type</th>
<th>Taille</th>
<th>Actions</th>
</tr>
<?php foreach ($files as $file): ?>
<tr>
<td><?php echo htmlspecialchars($file['name']); ?></td>
<td><?php echo $file['type']; ?></td>
<td><?php echo $file['size']; ?></td>
<td>
<?php if ($file['type'] === 'dir'): ?>
<a href="?dir=<?php echo urlencode($currentDir

```html
<?php if ($file['type'] === 'dir'): ?>
<a href="?dir=<?php echo urlencode($currentDir . '/' .
$file['name']); ?>">Ouvrir</a>
<?php else: ?>
<a href="?download=<?php echo urlencode($file['name']); ?
>&dir=<?php echo urlencode($currentDir); ?>">Télécharger</a>
<?php endif; ?>
<form method="post" style="display:inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="name" value="<?php echo
htmlspecialchars($file['name']); ?>">
<input type="hidden" name="dir" value="<?php echo
htmlspecialchars($currentDir); ?>">
<input type="submit" value="Supprimer">
</form>
</td>
</tr>
<?php endforeach; ?>
</table>

<h3>Créer un nouvel élément</h3>


<form method="post">
<input type="hidden" name="action" value="create">
<input type="hidden" name="dir" value="<?php echo
htmlspecialchars($currentDir); ?>">
<input type="text" name="name" required placeholder="Nom">
<select name="type">
<option value="file">Fichier</option>
<option value="dir">Répertoire</option>
</select>
<input type="submit" value="Créer">
</form>

<h3>Uploader un fichier</h3>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="dir" value="<?php echo
htmlspecialchars($currentDir); ?>">
<input type="file" name="upload" required>
<input type="submit" value="Uploader">
</form>

<?php if ($currentDir): ?>


<p><a href="?dir=<?php echo urlencode(dirname($currentDir)); ?
>">Retour au répertoire parent</a></p>
<?php endif; ?>
</body>
</html>

Ce code HTML complète le gestionnaire de fichiers web en ajoutant les fonctionnalités


suivantes :

1. Navigation dans les répertoires


2. Téléchargement de fichiers
3. Suppression de fichiers et de répertoires
4. Création de nouveaux fichiers et répertoires
5. Upload de fichiers

Notez que ce script est un exemple de base et devrait être amélioré pour une utilisation en
production, notamment en termes de sécurité et de gestion des erreurs.

15.8 Bonnes pratiques pour la gestion des fichiers


Lors de la manipulation de fichiers en PHP, il est important de suivre certaines bonnes
pratiques pour assurer la sécurité et l'efficacité de votre application.

15.8.1 Validation des chemins

Toujours valider et sanitiser les chemins de fichiers pour éviter les attaques de type "directory
traversal".

<?php
function securePath($path) {
$path = realpath($path);
$root = realpath('/chemin/vers/racine/autorisee');
if ($path === false || strpos($path, $root) !== 0) {
throw new Exception("Chemin non autorisé");
}
return $path;
}
?>

15.8.2 Gestion des permissions


Assurez-vous que les permissions des fichiers et répertoires sont correctement configurées.

<?php
// Définir des permissions restrictives pour les nouveaux fichiers
umask(0077);

// Vérifier les permissions avant d'effectuer des opérations


if (!is_writable($file)) {
throw new Exception("Impossible d'écrire dans le fichier");
}
?>

15.8.3 Utilisation de flux (streams)

Pour les fichiers volumineux, utilisez des flux pour une meilleure gestion de la mémoire.

<?php
$handle = fopen("gros_fichier.txt", "r");
if ($handle) {
while (($line = fgets($handle)) !== false) {
// Traiter la ligne
}
fclose($handle);
}
?>

15.8.4 Gestion des erreurs


Utilisez la gestion des exceptions pour traiter les erreurs de manière élégante.

<?php
try {
$content = file_get_contents('fichier.txt');
if ($content === false) {
throw new Exception("Impossible de lire le fichier");
}
// Traiter le contenu
} catch (Exception $e) {
error_log("Erreur de lecture de fichier : " . $e->getMessage());
// Gérer l'erreur de manière appropriée
}
?>

Exercice 15.3
Créez une classe LogManager qui gère un système de logs pour une application. Cette classe
doit :

1. Écrire des messages de log dans un fichier avec horodatage


2. Permettre de définir différents niveaux de log (INFO, WARNING, ERROR)
3. Faire une rotation des fichiers de log (créer un nouveau fichier chaque jour)
4. Limiter la taille du fichier de log

Corrigé 15.3

<?php
class LogManager {
private $logFile;
private $maxFileSize;

const LOG_INFO = 'INFO';


const LOG_WARNING = 'WARNING';
const LOG_ERROR = 'ERROR';

public function __construct($logDir, $maxFileSize = 5242880) { // 5 Mo par


défaut
$this->maxFileSize = $maxFileSize;
$this->setLogFile($logDir);
}

private function setLogFile($logDir) {


$date = date('Y-m-d');
$this->logFile = $logDir . '/log_' . $date . '.txt';

if (!file_exists($this->logFile)) {
touch($this->logFile);
chmod($this->logFile, 0644);
}
}

public function log($message, $level = self::LOG_INFO) {


$this->rotateLogIfNeeded();

$date = date('Y-m-d H:i:s');


$logMessage = "[$date] [$level] $message" . PHP_EOL;

file_put_contents($this->logFile, $logMessage, FILE_APPEND);


}

private function rotateLogIfNeeded() {


if (file_exists($this->logFile) && filesize($this->logFile) > $this-
>maxFileSize) {
$info = pathinfo($this->logFile);
$newName = $info['dirname'] . '/' . $info['filename'] . '_' .
time() . '.' . $info['extension'];
rename($this->logFile, $newName);
touch($this->logFile);
chmod($this->logFile, 0644);
}
}

public function getLogContent() {


if (file_exists($this->logFile)) {
return file_get_contents($this->logFile);
}
return "Aucun log disponible.";
}
}

// Utilisation
$logManager = new LogManager('./logs');

$logManager->log("Application démarrée");
$logManager->log("Attention : espace disque faible", LogManager::LOG_WARNING);
$logManager->log("Erreur critique : base de données inaccessible",
LogManager::LOG_ERROR);

echo $logManager->getLogContent();
?>
16. Frameworks PHP populaires
Les frameworks PHP sont des outils essentiels pour le développement web moderne, offrant
une structure organisée et des fonctionnalités prêtes à l'emploi pour accélérer le
développement d'applications.

16.1 Introduction aux frameworks PHP


16.1.1 Qu'est-ce qu'un framework PHP ?

Un framework PHP est un ensemble de composants et d'outils qui fournit une structure de base
pour développer des applications web. Il offre généralement :

Une architecture MVC (Modèle-Vue-Contrôleur)


Des outils pour la gestion de base de données
Des mécanismes de routage
Des fonctionnalités de sécurité
Des outils de gestion des formulaires
Des mécanismes de cache

16.1.2 Avantages de l'utilisation d'un framework

Accélération du développement
Code plus organisé et maintenable
Sécurité renforcée
Respect des bonnes pratiques de développement
Communauté active et support

16.1.3 Frameworks PHP populaires

1. Laravel
2. Symfony
3. CodeIgniter
4. Yii
5. CakePHP

16.2 Laravel : Un framework PHP moderne


Laravel est l'un des frameworks PHP les plus populaires, connu pour son élégance et sa
puissance.

16.2.1 Installation de Laravel


Pour installer Laravel, vous devez d'abord installer Composer, puis exécuter :

composer create-project laravel/laravel mon-projet


cd mon-projet
php artisan serve

16.2.2 Structure d'un projet Laravel

app/ : Contient le cœur de l'application


config/ : Fichiers de configuration
database/ : Migrations et seeds de la base de données
public/ : Point d'entrée de l'application
resources/ : Vues, fichiers CSS, JS, et langues
routes/ : Définition des routes
storage/ : Fichiers générés par l'application
tests/ : Tests unitaires et d'intégration

16.2.3 Routage dans Laravel


Le routage définit comment l'application répond aux requêtes client.

// routes/web.php
Route::get('/', function () {
return view('welcome');
});

Route::get('/utilisateurs', 'UserController@index');

16.2.4 Contrôleurs
Les contrôleurs gèrent la logique de l'application.
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;

use App\Models\User;

class UserController extends Controller


{
public function index()
{
$users = User::all();
return view('users.index', ['users' => $users]);
}
}

16.2.5 Modèles et Eloquent ORM


Eloquent est l'ORM de Laravel pour interagir avec la base de données.

// app/Models/User.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model


{
protected $fillable = ['name', 'email'];
}

// Utilisation
$users = User::where('active', 1)->get();

16.2.6 Vues et Blade

Blade est le moteur de template de Laravel.

<!-- resources/views/users/index.blade.php -->


@extends('layouts.app')

@section('content')
<h1>Utilisateurs</h1>
<ul>
@foreach($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
@endsection

16.2.7 Migrations
Les migrations permettent de gérer la structure de la base de données.

// database/migrations/2023_01_01_create_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration


{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
}

public function down()


{
Schema::dropIfExists('users');
}
}

16.3 Comparaison avec d'autres frameworks


16.3.1 Symfony

Plus complexe mais très flexible


Utilisé pour de grands projets d'entreprise
Composants réutilisables indépendamment
16.3.2 CodeIgniter

Léger et rapide
Facile à apprendre
Moins de fonctionnalités que Laravel ou Symfony

16.3.3 Yii

Performances élevées
Génération de code intégrée
Bonne pour les applications à grande échelle

Exercice 16.1
Créez une application Laravel simple de gestion de tâches (Todo List). L'application doit
permettre de :

1. Afficher une liste de tâches


2. Ajouter une nouvelle tâche
3. Marquer une tâche comme terminée
4. Supprimer une tâche

Utilisez les migrations pour créer la table des tâches, un modèle Eloquent pour interagir avec la
base de données, et les vues Blade pour l'interface utilisateur.

Corrigé 16.1

1. Création du projet Laravel :

composer create-project laravel/laravel todo-app


cd todo-app

2. Création de la migration pour la table des tâches :

php artisan make:migration create_tasks_table

Contenu de la migration :
// database/migrations/xxxx_xx_xx_create_tasks_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTasksTable extends Migration


{
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}

public function down()


{
Schema::dropIfExists('tasks');
}
}

Exécutez la migration :

php artisan migrate

3. Création du modèle Task :

php artisan make:model Task

Contenu du modèle :

// app/Models/Task.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Task extends Model


{
protected $fillable = ['title', 'completed'];
}
4. Création du contrôleur TaskController :

php artisan make:controller TaskController

Contenu du contrôleur :

// app/Http/Controllers/TaskController.php
namespace App\Http\Controllers;

use App\Models\Task;
use Illuminate\Http\Request;

class TaskController extends Controller


{
public function index()
{
$tasks = Task::orderBy('created_at', 'desc')->get();
return view('tasks.index', compact('tasks'));
}

public function store(Request $request)


{
$request->validate(['title' => 'required|max:255']);
Task::create($request->all());
return redirect()->route('tasks.index');
}

public function update(Task $task)


{
$task->completed = !$task->completed;
$task->save();
return redirect()->route('tasks.index');
}

public function destroy(Task $task)


{
$task->delete();
return redirect()->route('tasks.index');
}
}

5. Définition des routes :


// routes/web.php
use App\Http\Controllers\TaskController;

Route::get('/', [TaskController::class, 'index'])->name('tasks.index');


Route::post('/tasks', [TaskController::class, 'store'])->name('tasks.store');
Route::put('/tasks/{task}', [TaskController::class, 'update'])-
>name('tasks.update');
Route::delete('/tasks/{task}', [TaskController::class, 'destroy'])-
>name('tasks.destroy');

6. Création de la vue :

<!-- resources/views/tasks/index.blade.php -->


<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>

<form action="{{ route('tasks.store') }}" method="POST">


@csrf
<input type="text" name="title" placeholder="Nouvelle tâche" required>
<button type="submit">Ajouter</button>
</form>

<ul>
@foreach($tasks as $task)
<li>
<form action="{{ route('tasks.update', $task) }}"
method="POST" style="display: inline;">
@csrf
@method('PUT')
<input type="checkbox" onChange="this.form.submit()" {{
$task->completed ? 'checked' : '' }}>
<span style="{{ $task->completed ? 'text-decoration: line-
through;' : '' }}">
{{ $task->title }}
</span>
</form>
<form action="{{ route('tasks.destroy', $task) }}"
method="POST" style="display: inline;">
@csrf
@method('DELETE')
<button type="submit">Supprimer</button>
</form>
</li>
@endforeach
</ul>
</body>
</html>

7. Lancement de l'application :

php artisan serve

Cet exercice couvre les bases de Laravel, incluant le routage, les contrôleurs, les modèles
Eloquent, les migrations et les vues Blade. Il démontre comment créer rapidement une
application CRUD simple avec Laravel.

17. Sessions et Cookies en PHP


Les sessions et les cookies sont des mécanismes essentiels pour gérer l'état et les données
utilisateur dans les applications web PHP.

17.1 Cookies
Les cookies sont de petits fichiers de données stockés côté client (dans le navigateur de
l'utilisateur).

17.1.1 Création d'un cookie

setcookie("nom_du_cookie", "valeur_du_cookie", time() + 3600, "/", "", true,


true);

Paramètres :

Nom du cookie
Valeur du cookie
Temps d'expiration (timestamp Unix)
Chemin sur le serveur
Domaine
Secure (HTTPS seulement)
HttpOnly (inaccessible via JavaScript)

17.1.2 Lecture d'un cookie

if(isset($_COOKIE['nom_du_cookie'])) {
$valeur = $_COOKIE['nom_du_cookie'];
echo "Valeur du cookie : " . $valeur;
}

17.1.3 Modification d'un cookie

Pour modifier un cookie, il suffit de le recréer avec le même nom :

setcookie("nom_du_cookie", "nouvelle_valeur", time() + 3600);

17.1.4 Suppression d'un cookie


Pour supprimer un cookie, on le récrée avec une date d'expiration dans le passé :

setcookie("nom_du_cookie", "", time() - 3600);

17.2 Sessions
Les sessions permettent de stocker des données côté serveur pour un utilisateur spécifique.

17.2.1 Démarrage d'une session

session_start();

Cette fonction doit être appelée avant tout output HTML.

17.2.2 Stockage de données dans une session


$_SESSION['nom_utilisateur'] = "Alice";
$_SESSION['id_utilisateur'] = 123;

17.2.3 Lecture de données de session

if(isset($_SESSION['nom_utilisateur'])) {
echo "Bienvenue, " . $_SESSION['nom_utilisateur'];
}

17.2.4 Suppression de données de session

// Supprimer une variable spécifique


unset($_SESSION['nom_utilisateur']);

// Supprimer toutes les variables de session


session_unset();

// Détruire complètement la session


session_destroy();

17.2.5 Configuration de la session

// Définir la durée de vie de la session


ini_set('session.gc_maxlifetime', 3600);

// Définir le nom du cookie de session


session_name('ma_session');

// Définir les paramètres du cookie de session


session_set_cookie_params([
'lifetime' => 3600,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true
]);
17.3 Sécurité des sessions et des cookies
17.3.1 Protection contre le vol de session

session_start();
if (!isset($_SESSION['created'])) {
$_SESSION['created'] = time();
} else if (time() - $_SESSION['created'] > 1800) {
// Régénérer l'ID de session après 30 minutes
session_regenerate_id(true);
$_SESSION['created'] = time();
}

17.3.2 Cookies sécurisés


Toujours utiliser les options secure et httponly pour les cookies sensibles :

setcookie("auth_token", $token, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);

17.3.3 Protection contre les attaques CSRF

Utiliser des tokens CSRF dans les formulaires :

session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Dans le formulaire HTML


echo '<input type="hidden" name="csrf_token" value="' .
$_SESSION['csrf_token'] . '">';

// Vérification lors de la soumission du formulaire


if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF token invalide');
}
// Traitement du formulaire
}

17.4 Bonnes pratiques


1. Toujours utiliser session_start() au début de chaque script qui utilise des sessions.
2. Ne jamais stocker d'informations sensibles (comme des mots de passe) dans les cookies.
3. Utiliser des cookies HttpOnly et Secure pour les informations importantes.
4. Régénérer l'ID de session après login et à intervalles réguliers.
5. Nettoyer et valider toutes les données stockées dans les sessions et les cookies.

Exercice 17.1

Créez un système simple d'authentification utilisant les sessions. Le système doit permettre à
un utilisateur de se connecter, de voir une page protégée, et de se déconnecter. Utilisez des
cookies pour implémenter une fonctionnalité "Se souvenir de moi".

Corrigé 17.1

<?php
// config.php
session_start();
ini_set('session.cookie_lifetime', 3600);
ini_set('session.gc_maxlifetime', 3600);

// Fonction pour vérifier si l'utilisateur est connecté


function isLoggedIn() {
return isset($_SESSION['user_id']);
}

// Fonction pour vérifier le cookie "Se souvenir de moi"


function checkRememberMe() {
if (!isLoggedIn() && isset($_COOKIE['remember_me'])) {
// Dans un vrai système, vous vérifieriez ce token dans une base de
données
$token = $_COOKIE['remember_me'];
// Simulation de vérification
if ($token === 'valid_token') {
$_SESSION['user_id'] = 1;
$_SESSION['username'] = 'Alice';
}
}
}

// login.php
require_once 'config.php';
checkRememberMe();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {


$username = $_POST['username'];
$password = $_POST['password'];
$remember = isset($_POST['remember']);

// Simulation de vérification des identifiants (à remplacer par une vraie


vérification)
if ($username === 'Alice' && $password === 'password123') {
$_SESSION['user_id'] = 1;
$_SESSION['username'] = $username;

if ($remember) {
setcookie('remember_me', 'valid_token', time() + 30 * 24 * 60 *
60, '/', '', true, true);
}

header('Location: protected.php');
exit;
} else {
$error = "Identifiants invalides";
}
}
?>

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Connexion</title>
</head>
<body>
<h1>Connexion</h1>
<?php if (isset($error)) echo "<p>$error</p>"; ?>
<form method="post">
<input type="text" name="username" placeholder="Nom d'utilisateur"
required><br>
<input type="password" name="password" placeholder="Mot de passe"
required><br>
<label>
<input type="checkbox" name="remember"> Se souvenir de moi
</label><br>
<button type="submit">Se connecter</button>
</form>
</body>
</html>

<?php
// protected.php
require_once 'config.php';
checkRememberMe();

if (!isLoggedIn()) {
header('Location: login.php');
exit;
}
?>

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Page protégée</title>
</head>
<body>
<h1>Page protégée</h1>
<p>Bienvenue, <?php echo htmlspecialchars($_SESSION['username']); ?>!</p>
<a href="logout.php">Se déconnecter</a>
</body>
</html>

<?php
// logout.php
require_once 'config.php';

$_SESSION = array();

if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}

session_destroy();

setcookie('remember_me', '', time() - 3600, '/');

header('Location: login.php');
exit;
?>

Ce code implémente un système d'authentification de base avec une fonctionnalité "Se


souvenir de moi" en utilisant les sessions et les cookies. Il inclut :

1. Une page de connexion ( login.php )


2. Une page protégée ( protected.php )
3. Une page de déconnexion ( logout.php )
4. Un fichier de configuration ( config.php ) avec des fonctions utilitaires

Note : Ce code est une démonstration et ne doit pas être utilisé tel quel en production. Dans un
environnement réel, vous devriez utiliser une base de données pour stocker les informations
d'utilisateur et les tokens "Se souvenir de moi", ainsi que des mesures de sécurité
supplémentaires comme le hachage des mots de passe.

Exercice 17.2
Créez un système de panier d'achat simple utilisant les sessions. Le système doit permettre
d'ajouter des articles au panier, de les supprimer, et d'afficher le contenu du panier. Utilisez
également un cookie pour sauvegarder le contenu du panier pendant une semaine, même si
l'utilisateur ferme son navigateur.

Corrigé 17.2

<?php
// config.php
session_start();

// Fonction pour initialiser le panier


function initCart() {
if (!isset($_SESSION['cart'])) {
if (isset($_COOKIE['saved_cart'])) {
$_SESSION['cart'] = json_decode($_COOKIE['saved_cart'], true);
} else {
$_SESSION['cart'] = [];
}
}
}

// Fonction pour sauvegarder le panier dans un cookie


function saveCartToCookie() {
setcookie('saved_cart', json_encode($_SESSION['cart']), time() + 7 * 24 *
60 * 60, '/', '', true, true);
}

// index.php
require_once 'config.php';
initCart();

$products = [
1 => ['name' => 'Produit A', 'price' => 10.99],
2 => ['name' => 'Produit B', 'price' => 24.99],
3 => ['name' => 'Produit C', 'price' => 5.99],
];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {


if (isset($_POST['add_to_cart'])) {
$product_id = $_POST['product_id'];
if (isset($products[$product_id])) {
if (!isset($_SESSION['cart'][$product_id])) {
$_SESSION['cart'][$product_id] = 0;
}
$_SESSION['cart'][$product_id]++;
saveCartToCookie();
}
} elseif (isset($_POST['remove_from_cart'])) {
$product_id = $_POST['product_id'];
if (isset($_SESSION['cart'][$product_id])) {
$_SESSION['cart'][$product_id]--;
if ($_SESSION['cart'][$product_id] <= 0) {
unset($_SESSION['cart'][$product_id]);
}
saveCartToCookie();
}
}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Boutique en ligne</title>
</head>
<body>
<h1>Produits disponibles</h1>
<ul>
<?php foreach ($products as $id => $product): ?>
<li>
<?php echo htmlspecialchars($product['name']); ?> -
<?php echo number_format($product['price'], 2); ?> €
<form method="post" style="display: inline;">
<input type="hidden" name="product_id" value="<?php echo
$id; ?>">
<button type="submit" name="add_to_cart">Ajouter au
panier</button>
</form>
</li>
<?php endforeach; ?>
</ul>

<h2>Votre panier</h2>
<?php if (empty($_SESSION['cart'])): ?>
<p>Votre panier est vide.</p>
<?php else: ?>
<ul>
<?php foreach ($_SESSION['cart'] as $id => $quantity): ?>
<li>
<?php echo htmlspecialchars($products[$id]['name']); ?> -
Quantité : <?php echo $quantity; ?> -
Prix : <?php echo number_format($products[$id]['price'] *
$quantity, 2); ?> €
<form method="post" style="display: inline;">
<input type="hidden" name="product_id" value="<?php
echo $id; ?>">
<button type="submit"
name="remove_from_cart">Supprimer</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<p>
Total : <?php
$total = array_sum(array_map(function($id, $quantity) use
($products) {
return $products[$id]['price'] * $quantity;
}, array_keys($_SESSION['cart']), $_SESSION['cart']));
echo number_format($total, 2);
?> €
</p>
<?php endif; ?>
</body>
</html>

Ce code implémente un système de panier d'achat simple en utilisant les sessions PHP et les
cookies. Il comprend :

1. Une liste de produits disponibles


2. La possibilité d'ajouter des produits au panier
3. L'affichage du contenu du panier avec la possibilité de supprimer des articles
4. 4. La sauvegarde automatique du panier dans un cookie pour une durée d'une semaine
5. Le calcul du total du panier

Explications supplémentaires sur le code du panier d'achat :

17.5 Analyse détaillée du système de panier d'achat


17.5.1 Initialisation du panier
La fonction initCart() est appelée au début du script pour s'assurer que le panier existe :

function initCart() {
if (!isset($_SESSION['cart'])) {
if (isset($_COOKIE['saved_cart'])) {
$_SESSION['cart'] = json_decode($_COOKIE['saved_cart'], true);
} else {
$_SESSION['cart'] = [];
}
}
}

Cette fonction vérifie d'abord si le panier existe déjà dans la session. Si ce n'est pas le cas, elle
cherche un panier sauvegardé dans les cookies. Si un cookie existe, son contenu est décodé et
utilisé pour initialiser le panier dans la session. Sinon, un panier vide est créé.
17.5.2 Sauvegarde du panier dans un cookie
La fonction saveCartToCookie() est appelée chaque fois que le panier est modifié :

function saveCartToCookie() {
setcookie('saved_cart', json_encode($_SESSION['cart']), time() + 7 * 24 *
60 * 60, '/', '', true, true);
}

Cette fonction encode le contenu du panier en JSON et le sauvegarde dans un cookie. Le


cookie est configuré pour expirer après une semaine (7 24 60 * 60 secondes), et les options
secure et httpOnly sont activées pour plus de sécurité.

17.5.3 Gestion des actions sur le panier

Le script vérifie les actions POST pour ajouter ou supprimer des articles du panier :

if ($_SERVER['REQUEST_METHOD'] === 'POST') {


if (isset($_POST['add_to_cart'])) {
// Logique pour ajouter au panier
} elseif (isset($_POST['remove_from_cart'])) {
// Logique pour supprimer du panier
}
}

Lors de l'ajout ou de la suppression d'un article, le panier est mis à jour dans la session, puis
sauvegardé dans le cookie.

17.5.4 Affichage du panier


Le contenu du panier est affiché en parcourant le tableau $_SESSION['cart'] :

<?php foreach ($_SESSION['cart'] as $id => $quantity): ?>


<li>
<?php echo htmlspecialchars($products[$id]['name']); ?> -
Quantité : <?php echo $quantity; ?> -
Prix : <?php echo number_format($products[$id]['price'] * $quantity,
2); ?> €
<!-- Formulaire pour supprimer -->
</li>
<?php endforeach; ?>
17.5.5 Calcul du total du panier
Le total du panier est calculé en utilisant array_sum et array_map :

$total = array_sum(array_map(function($id, $quantity) use ($products) {


return $products[$id]['price'] * $quantity;
}, array_keys($_SESSION['cart']), $_SESSION['cart']));

Cette approche calcule le prix total pour chaque article (prix unitaire * quantité) puis fait la
somme de tous ces totaux.

17.6 Améliorations possibles et considérations de sécurité


1. Validation des entrées : Ajouter une validation plus stricte des ID de produits et des
quantités.
2. Protection contre CSRF : Implémenter des tokens CSRF pour les formulaires.
3. Gestion des stocks : Ajouter une vérification de la disponibilité des produits avant de les
ajouter au panier.
4. Cryptage du cookie : Envisager de crypter le contenu du cookie pour plus de sécurité.
5. Limite de taille du panier : Implémenter une limite sur le nombre d'articles ou le montant
total du panier.
6. Nettoyage des données : S'assurer que toutes les données affichées sont correctement
échappées pour prévenir les attaques XSS.

Exercice 17.3

Améliorez le système de panier d'achat en ajoutant les fonctionnalités suivantes :

1. Un token CSRF pour les formulaires d'ajout et de suppression d'articles.


2. Une vérification de la validité des ID de produits avant l'ajout au panier.
3. Une limite de 10 articles maximum par produit dans le panier.
4. Une page de "paiement" qui vide le panier et affiche un résumé de la commande.

Corrigé 17.3
Voici les modifications à apporter au code précédent pour implémenter ces améliorations :
<?php
// config.php
session_start();

function initCart() {
if (!isset($_SESSION['cart'])) {
if (isset($_COOKIE['saved_cart'])) {
$_SESSION['cart'] = json_decode($_COOKIE['saved_cart'], true);
} else {
$_SESSION['cart'] = [];
}
}
}

function saveCartToCookie() {
setcookie('saved_cart', json_encode($_SESSION['cart']), time() + 7 * 24 *
60 * 60, '/', '', true, true);
}

function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}

function verifyCSRFToken($token) {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}

// index.php
require_once 'config.php';
initCart();

$products = [
1 => ['name' => 'Produit A', 'price' => 10.99],
2 => ['name' => 'Produit B', 'price' => 24.99],
3 => ['name' => 'Produit C', 'price' => 5.99],
];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {


if (!verifyCSRFToken($_POST['csrf_token'])) {
die('Token CSRF invalide');
}
if (isset($_POST['add_to_cart'])) {
$product_id = (int)$_POST['product_id'];
if (isset($products[$product_id])) {
if (!isset($_SESSION['cart'][$product_id])) {
$_SESSION['cart'][$product_id] = 0;
}
if ($_SESSION['cart'][$product_id] < 10) {
$_SESSION['cart'][$product_id]++;
saveCartToCookie();
}
}
} elseif (isset($_POST['remove_from_cart'])) {
$product_id = (int)$_POST['product_id'];
if (isset($_SESSION['cart'][$product_id])) {
$_SESSION['cart'][$product_id]--;
if ($_SESSION['cart'][$product_id] <= 0) {
unset($_SESSION['cart'][$product_id]);
}
saveCartToCookie();
}
}
}

$csrf_token = generateCSRFToken();
?>

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Boutique en ligne</title>
</head>
<body>
<h1>Produits disponibles</h1>
<ul>
<?php foreach ($products as $id => $product): ?>
<li>
<?php echo htmlspecialchars($product['name']); ?> -
<?php echo number_format($product['price'], 2); ?> €
<form method="post" style="display: inline;">
<input type="hidden" name="csrf_token" value="<?php echo
$csrf_token; ?>">
<input type="hidden" name="product_id" value="<?php echo
$id; ?>">
<button type="submit" name="add_to_cart">Ajouter au
panier</button>
</form>
</li>
<?php endforeach; ?>
</ul>

<h2>Votre panier</h2>
<?php if (empty($_SESSION['cart'])): ?>
<p>Votre panier est vide.</p>
<?php else: ?>
<ul>
<?php foreach ($_SESSION['cart'] as $id => $quantity): ?>
<li>
<?php echo htmlspecialchars($products[$id]['name']); ?> -
Quantité : <?php echo $quantity; ?> -
Prix : <?php echo number_format($products[$id]['price'] *
$quantity, 2); ?> €
<form method="post" style="display: inline;">
<input type="hidden" name="csrf_token" value="<?php
echo $csrf_token; ?>">
<input type="hidden" name="product_id" value="<?php
echo $id; ?>">
<button type="submit"
name="remove_from_cart">Supprimer</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<p>
Total : <?php
$total = array_sum(array_map(function($id, $quantity) use
($products) {
return $products[$id]['price'] * $quantity;
}, array_keys($_SESSION['cart']), $_SESSION['cart']));
echo number_format($total, 2);
?> €
</p>
<a href="checkout.php">Procéder au paiement</a>
<?php endif; ?>
</body>
</html>

<?php
// checkout.php
require_once 'config.php';
initCart();
$products = [
1 => ['name' => 'Produit A', 'price' => 10.99],
2 => ['name' => 'Produit B', 'price' => 24.99],
3 => ['name' => 'Produit C', 'price' => 5.99],
];

$total = 0;

if ($_SERVER['REQUEST_METHOD'] === 'POST' &&


verifyCSRFToken($_POST['csrf_token'])) {
// Simulation du processus de paiement
$order_summary = $_SESSION['cart'];
$_SESSION['cart'] = [];
saveCartToCookie();
$payment_successful = true;
} else {
$order_summary = $_SESSION['cart'];
$payment_successful = false;
}

$csrf_token = generateCSRFToken();
?>

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Paiement</title>
</head>
<body>
<h1>Résumé de la commande</h1>
<?php if ($payment_successful): ?>
<p>Paiement réussi ! Voici le résumé de votre commande :</p>
<?php else: ?>
<p>Veuillez confirmer votre commande :</p>
<?php endif; ?>

<ul>
<?php foreach ($order_summary as $id => $quantity): ?>
<li>
<?php echo htmlspecialchars($products[$id]['name']); ?> -
Quantité : <?php echo $quantity; ?> -
Prix : <?php echo number_format($products[$id]['price'] *
$quantity, 2); ?> €
</li>
<?php $total += $products[$id]['price'] * $quantity; ?>
<?php endforeach; ?>
</ul>

<p>Total : <?php echo number_format($total, 2); ?> €</p>

<?php if (!$payment_successful): ?>


<form method="post">
<input type="hidden" name="csrf_token" value="<?php echo
$csrf_token; ?>">
<button type="submit">Confirmer le paiement</button>
</form>
<?php endif; ?>

<p><a href="index.php">Retour à la boutique</a></p>


</body>
</html>

Ces modifications apportent les améliorations suivantes :

1. Ajout d'un token CSRF pour tous les formulaires.


2. Vérification et conversion en entier des ID de produits.
3. Limite de 10 articles maximum par produit dans le panier.
4. Ajout d'une page de paiement qui vide le panier et affiche un résumé de la commande.

Ces améliorations rendent le système de panier plus sécurisé et plus robuste, tout en offrant
une meilleure expérience utilisateur.

18. Gestion des erreurs et exceptions en PHP


La gestion efficace des erreurs et des exceptions est essentielle pour créer des applications
PHP robustes et fiables. Elle permet de gérer les situations imprévues de manière élégante et
de fournir des informations utiles pour le débogage.

18.1 Types d'erreurs en PHP


PHP distingue plusieurs types d'erreurs :

1. Notices : Avertissements non critiques


2. Warnings : Avertissements plus sérieux, mais non fatals
3. Fatal Errors : Erreurs qui arrêtent l'exécution du script
4. Parse Errors : Erreurs de syntaxe qui empêchent l'exécution du script
18.2 Configuration de la gestion des erreurs
PHP offre plusieurs directives pour configurer la gestion des erreurs :

<?php
// Afficher toutes les erreurs
error_reporting(E_ALL);
ini_set('display_errors', 1);

// En production, ne pas afficher les erreurs mais les logger


error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/chemin/vers/error.log');
?>

18.3 Exceptions en PHP


Les exceptions permettent de gérer les erreurs de manière structurée.

18.3.1 Try-Catch

<?php
try {
// Code susceptible de générer une exception
$result = 10 / 0;
} catch (Exception $e) {
echo "Une erreur s'est produite : " . $e->getMessage();
}
?>

18.3.2 Throw
Vous pouvez lancer vos propres exceptions :

<?php
function diviser($a, $b) {
if ($b == 0) {
throw new Exception("Division par zéro impossible");
}
return $a / $b;
}

try {
echo diviser(10, 0);
} catch (Exception $e) {
echo "Erreur : " . $e->getMessage();
}
?>

18.3.3 Finally
Le bloc finally s'exécute toujours, qu'une exception soit levée ou non :

<?php
try {
// Code susceptible de générer une exception
} catch (Exception $e) {
// Gestion de l'exception
} finally {
// Ce code s'exécute toujours
}
?>

18.4 Exceptions personnalisées


Vous pouvez créer vos propres classes d'exceptions :

<?php
class DatabaseException extends Exception {}
class FileNotFoundException extends Exception {}

try {
// Simulation d'une erreur de base de données
throw new DatabaseException("Erreur de connexion à la base de données");
} catch (DatabaseException $e) {
echo "Erreur de BDD : " . $e->getMessage();
} catch (FileNotFoundException $e) {
echo "Fichier non trouvé : " . $e->getMessage();
} catch (Exception $e) {
echo "Autre erreur : " . $e->getMessage();
}
?>

18.5 Logging des erreurs


Il est souvent préférable de logger les erreurs plutôt que de les afficher directement :

<?php
function customErrorHandler($errno, $errstr, $errfile, $errline) {
$message = date("Y-m-d H:i:s") . " - Erreur [$errno] : $errstr dans
$errfile à la ligne $errline\n";
error_log($message, 3, "error.log");
}

set_error_handler("customErrorHandler");

// Exemple d'utilisation
$undefined_variable;
?>

18.6 Bonnes pratiques


1. Utilisez toujours des blocs try-catch pour le code susceptible de générer des exceptions.
2. Créez des exceptions personnalisées pour différents types d'erreurs dans votre
application.
3. Loggez les erreurs et les exceptions de manière appropriée.
4. En production, n'affichez jamais les détails des erreurs aux utilisateurs.
5. Utilisez des messages d'erreur clairs et informatifs.

Exercice 18.1
Créez une classe BanqueCompte avec une méthode retirer qui lance une exception
personnalisée SoldeInsuffisantException si le retrait demandé dépasse le solde disponible.
Implémentez également une méthode deposer . Utilisez cette classe dans un script qui gère
les exceptions et affiche des messages appropriés.

Corrigé 18.1
<?php
class SoldeInsuffisantException extends Exception {}

class BanqueCompte {
private $solde;
private $numero;

public function __construct($numero, $soldeInitial = 0) {


$this->numero = $numero;
$this->solde = $soldeInitial;
}

public function deposer($montant) {


if ($montant <= 0) {
throw new InvalidArgumentException("Le montant du dépôt doit être
positif");
}
$this->solde += $montant;
return $this->solde;
}

public function retirer($montant) {


if ($montant <= 0) {
throw new InvalidArgumentException("Le montant du retrait doit
être positif");
}
if ($this->solde < $montant) {
throw new SoldeInsuffisantException("Solde insuffisant pour
effectuer le retrait");
}
$this->solde -= $montant;
return $this->solde;
}

public function getSolde() {


return $this->solde;
}
}

// Utilisation
try {
$compte = new BanqueCompte("123456", 1000);
echo "Solde initial : " . $compte->getSolde() . " €\n";

$compte->deposer(500);
echo "Après dépôt : " . $compte->getSolde() . " €\n";
$compte->retirer(300);
echo "Après retrait : " . $compte->getSolde() . " €\n";

// Tentative de retrait excessif


$compte->retirer(2000);
} catch (SoldeInsuffisantException $e) {
echo "Erreur : " . $e->getMessage() . "\n";
} catch (InvalidArgumentException $e) {
echo "Erreur : " . $e->getMessage() . "\n";
} catch (Exception $e) {
echo "Une erreur inattendue s'est produite : " . $e->getMessage() . "\n";
}

echo "Solde final : " . $compte->getSolde() . " €\n";


?>

Ce code démontre l'utilisation d'exceptions personnalisées et la gestion de différents types


d'erreurs dans un contexte bancaire simplifié.

Exercice 18.2
Créez une fonction lireFichierConfig qui lit un fichier de configuration et retourne son
contenu sous forme de tableau associatif. Cette fonction doit gérer les exceptions pour les
erreurs courantes (fichier non trouvé, permissions insuffisantes, etc.) et utiliser un logger
personnalisé pour enregistrer ces erreurs.

Corrigé 18.2

<?php
class ConfigFileException extends Exception {}

function logger($message, $type = 'INFO') {


$logFile = 'application.log';
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[$timestamp] [$type] $message\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}

function lireFichierConfig($fichier) {
try {
if (!file_exists($fichier)) {
throw new ConfigFileException("Le fichier de configuration
n'existe pas");
}

if (!is_readable($fichier)) {
throw new ConfigFileException("Le fichier de configuration n'est
pas lisible");
}

$contenu = file_get_contents($fichier);
if ($contenu === false) {
throw new ConfigFileException("Impossible de lire le contenu du
fichier de configuration");
}

$config = parse_ini_string($contenu, true);


if ($config === false) {
throw new ConfigFileException("Le format du fichier de
configuration est invalide");
}

logger("Fichier de configuration lu avec succès : $fichier");


return $config;
} catch (ConfigFileException $e) {
logger($e->getMessage(), 'ERROR');
throw $e; // Relancer l'exception pour la gestion au niveau supérieur
} catch (Exception $e) {
logger("Erreur inattendue lors de la lecture du fichier de
configuration : " . $e->getMessage(), 'ERROR');
throw new ConfigFileException("Erreur lors de la lecture du fichier de
configuration", 0, $e);
}
}

// Utilisation
try {
$config = lireFichierConfig('config.ini');
print_r($config);
} catch (ConfigFileException $e) {
echo "Erreur de configuration : " . $e->getMessage() . "\n";
// Ici, on pourrait charger une configuration par défaut ou arrêter
l'application
} catch (Exception $e) {
echo "Erreur inattendue : " . $e->getMessage() . "\n";
}
?>
Ce code illustre :

La création d'une exception personnalisée ConfigFileException


L'utilisation d'un logger personnalisé
La gestion de différents types d'erreurs lors de la lecture d'un fichier de configuration
La propagation des exceptions pour une gestion au niveau supérieur

Ces exercices permettent aux étudiants de pratiquer la gestion des erreurs et des exceptions
dans des scénarios réalistes, tout en appliquant les bonnes pratiques de logging et de gestion
des exceptions personnalisées.

19. Manipulation de fichiers XML et JSON en PHP


XML (eXtensible Markup Language) et JSON (JavaScript Object Notation) sont deux formats
couramment utilisés pour structurer et échanger des données. PHP offre des outils puissants
pour travailler avec ces formats.

19.1 Manipulation de XML


19.1.1 Parsing XML avec SimpleXML
SimpleXML est une extension PHP qui permet de manipuler facilement des documents XML.

<?php
$xml_string = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<livres>
<livre>
<titre>PHP pour les débutants</titre>
<auteur>John Doe</auteur>
<annee>2020</annee>
</livre>
<livre>
<titre>Maîtriser XML en PHP</titre>
<auteur>Jane Smith</auteur>
<annee>2019</annee>
</livre>
</livres>
XML;

$xml = simplexml_load_string($xml_string);
foreach ($xml->livre as $livre) {
echo $livre->titre . " par " . $livre->auteur . " (" . $livre->annee .
")\n";
}
?>

19.1.2 Création de XML avec SimpleXML

<?php
$xml = new SimpleXMLElement('<livres/>');

$livre1 = $xml->addChild('livre');
$livre1->addChild('titre', 'PHP avancé');
$livre1->addChild('auteur', 'Alice Johnson');
$livre1->addChild('annee', '2021');

$livre2 = $xml->addChild('livre');
$livre2->addChild('titre', 'Bases de données et PHP');
$livre2->addChild('auteur', 'Bob Williams');
$livre2->addChild('annee', '2022');

echo $xml->asXML();
?>

19.1.3 Utilisation de XMLReader pour les grands fichiers

XMLReader est plus efficace pour traiter de grands fichiers XML.

<?php
$reader = new XMLReader();
$reader->open('grand_fichier.xml');

while ($reader->read()) {
if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'livre') {
$node = $reader->expand();
$titre = $node->getElementsByTagName('titre')->item(0)->nodeValue;
$auteur = $node->getElementsByTagName('auteur')->item(0)->nodeValue;
echo "Livre : $titre par $auteur\n";
}
}
$reader->close();
?>

19.2 Manipulation de JSON


JSON est un format léger et facile à lire/écrire, souvent utilisé dans les API web.

19.2.1 Encodage JSON

<?php
$data = [
'nom' => 'Dupont',
'prenom' => 'Jean',
'age' => 30,
'hobbies' => ['lecture', 'sport', 'voyage']
];

$json = json_encode($data, JSON_PRETTY_PRINT);


echo $json;
?>

19.2.2 Décodage JSON

<?php
$json_string = '{"nom":"Dupont","prenom":"Jean","age":30,"hobbies":
["lecture","sport","voyage"]}';

$data = json_decode($json_string, true); // true pour obtenir un tableau


associatif

echo "Nom : " . $data['nom'] . "\n";


echo "Prénom : " . $data['prenom'] . "\n";
echo "Hobbies : " . implode(', ', $data['hobbies']) . "\n";
?>

19.2.3 Gestion des erreurs JSON

<?php
$json_invalide = '{"nom":"Dupont","prenom":}'; // JSON invalide
$data = json_decode($json_invalide);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "Erreur JSON : " . json_last_error_msg();
}
?>

19.3 Comparaison XML vs JSON


XML :
Plus verbeux, mais plus expressif
Meilleur pour les données complexes et hiérarchiques
Supporte les attributs et les espaces de noms
JSON :
Plus léger et plus rapide à parser
Meilleure intégration avec JavaScript
Structure plus simple, idéal pour les API RESTful

Exercice 19.1
Créez une classe GestionnaireLivres qui peut lire et écrire une liste de livres à la fois en
XML et en JSON. La classe doit avoir des méthodes pour ajouter un livre, supprimer un livre, et
afficher tous les livres. Implémentez également des méthodes pour sauvegarder la liste en XML
et en JSON, ainsi que pour charger la liste à partir de ces formats.

Corrigé 19.1

<?php
class Livre {
public $titre;
public $auteur;
public $annee;

public function __construct($titre, $auteur, $annee) {


$this->titre = $titre;
$this->auteur = $auteur;
$this->annee = $annee;
}
}
class GestionnaireLivres {
private $livres = [];

public function ajouterLivre(Livre $livre) {


$this->livres[] = $livre;
}

public function supprimerLivre($index) {


if (isset($this->livres[$index])) {
unset($this->livres[$index]);
$this->livres = array_values($this->livres); // Réindexer le
tableau
}
}

public function afficherLivres() {


foreach ($this->livres as $index => $livre) {
echo "$index: {$livre->titre} par {$livre->auteur} ({$livre-
>annee})\n";
}
}

public function sauvegarderXML($filename) {


$xml = new SimpleXMLElement('<livres/>');
foreach ($this->livres as $livre) {
$xmlLivre = $xml->addChild('livre');
$xmlLivre->addChild('titre', $livre->titre);
$xmlLivre->addChild('auteur', $livre->auteur);
$xmlLivre->addChild('annee', $livre->annee);
}
$xml->asXML($filename);
}

public function chargerXML($filename) {


$this->livres = [];
$xml = simplexml_load_file($filename);
foreach ($xml->livre as $xmlLivre) {
$this->livres[] = new Livre(
(string)$xmlLivre->titre,
(string)$xmlLivre->auteur,
(int)$xmlLivre->annee
);
}
}
public function sauvegarderJSON($filename) {
$data = array_map(function($livre) {
return [
'titre' => $livre->titre,
'auteur' => $livre->auteur,
'annee' => $livre->annee
];
}, $this->livres);
file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
}

public function chargerJSON($filename) {


$this->livres = [];
$data = json_decode(file_get_contents($filename), true);
foreach ($data as $item) {
$this->livres[] = new Livre($item['titre'], $item['auteur'],
$item['annee']);
}
}
}

// Utilisation
$gestionnaire = new GestionnaireLivres();

$gestionnaire->ajouterLivre(new Livre("PHP pour les débutants", "John Doe",


2020));
$gestionnaire->ajouterLivre(new Livre("Maîtriser XML en PHP", "Jane Smith",
2019));
$gestionnaire->ajouterLivre(new Livre("JSON et API RESTful", "Bob Johnson",
2021));

echo "Liste initiale des livres :\n";


$gestionnaire->afficherLivres();

$gestionnaire->sauvegarderXML('livres.xml');
$gestionnaire->sauvegarderJSON('livres.json');

$gestionnaire->supprimerLivre(1);

echo "\nAprès suppression :\n";


$gestionnaire->afficherLivres();

$gestionnaire->chargerXML('livres.xml');

echo "\nAprès chargement du XML :\n";


$gestionnaire->afficherLivres();
$gestionnaire->chargerJSON('livres.json');

echo "\nAprès chargement du JSON :\n";


$gestionnaire->afficherLivres();
?>

Cet exercice permet aux étudiants de pratiquer la manipulation de XML et JSON dans un
contexte concret de gestion de données. Il couvre la création, la lecture, la modification et la
sauvegarde de données dans ces deux formats, tout en appliquant des concepts de
programmation orientée objet.

Exercice 19.2
Créez un script PHP qui consomme une API RESTful publique (par exemple, l'API de GitHub
ou OpenWeatherMap) qui renvoie des données en JSON. Le script doit récupérer les données,
les parser, et afficher certaines informations de manière formatée. Gérez également les erreurs
potentielles lors de l'appel à l'API et du parsing des données.

Corrigé 19.2

<?php
function appelerAPI($url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'PHP Script');
$response = curl_exec($ch);

if (curl_errno($ch)) {
throw new Exception(curl_error($ch));
}

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);


if ($httpCode !== 200) {
throw new Exception("Erreur HTTP : $httpCode");
}

curl_close($ch);
return $response;
}

function afficherMeteo($ville) {
$apiKey = 'VOTRE_CLE_API'; // Remplacez par votre clé API OpenWeatherMap
$url = "http://api.openweathermap.org/data/2.5/weather?
q=$ville&appid=$apiKey&units=metric&lang=fr";

try {
$response = appelerAPI($url);
$data = json_decode($response, true);

if (json_last_error() !== JSON_ERROR_NONE) {


throw new Exception("Erreur lors du parsing JSON : " .
json_last_error_msg());
}

echo "Météo pour {$data['name']} :\n";


echo "Température : {$data['main']['temp']}°C\n";
echo "Ressenti : {$data['main']['feels_like']}°C\n";
echo "Description : {$data['weather'][0]['description']}\n";
echo "Humidité : {$data['main']['humidity']}%\n";
echo "Vitesse du vent : {$data['wind']['speed']} m/s\n";

} catch (Exception $e) {


echo "Erreur : " . $e->getMessage() . "\n";
}
}

// Utilisation
$ville = 'Paris'; // Vous pouvez changer la ville ici
afficherMeteo($ville);
?>

Cet exercice permet aux étudiants de :

1. Travailler avec une API RESTful réelle


2. Utiliser cURL pour faire des requêtes HTTP
3. Parser et manipuler des données JSON
4. Gérer les erreurs potentielles lors des appels API et du parsing JSON
5. Formater et afficher les données de manière lisible

Note : Pour utiliser ce script, les étudiants devront s'inscrire sur OpenWeatherMap et obtenir
une clé API gratuite. Cela leur donnera également une expérience pratique de l'utilisation des
clés API, un concept important dans le développement web moderne.

Ces exercices offrent une expérience pratique de la manipulation de données XML et JSON,
ainsi que de l'interaction avec des API web, des compétences essentielles pour les
développeurs PHP modernes.

20. Programmation asynchrone en PHP


La programmation asynchrone permet d'exécuter des tâches de manière non bloquante,
améliorant ainsi les performances et la réactivité des applications, en particulier pour les
opérations d'entrée/sortie intensives.

20.1 Concepts de base


20.1.1 Synchrone vs Asynchrone

Synchrone : Les tâches s'exécutent séquentiellement, chacune attendant que la


précédente soit terminée.
Asynchrone : Les tâches peuvent s'exécuter indépendamment, sans bloquer l'exécution
du programme principal.

20.1.2 Callbacks

Les callbacks sont des fonctions passées en argument à d'autres fonctions, qui seront
exécutées une fois une tâche asynchrone terminée.

function tacheAsynchrone($callback) {
// Simulation d'une tâche asynchrone
sleep(2);
$callback("Tâche terminée");
}

tacheAsynchrone(function($resultat) {
echo $resultat;
});
echo "Cette ligne s'affiche immédiatement";

20.2 Utilisation de ReactPHP


ReactPHP est une bibliothèque qui permet d'écrire du code asynchrone en PHP.

20.2.1 Installation de ReactPHP


composer require react/event-loop

20.2.2 Exemple de base avec ReactPHP

<?php
require 'vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$loop->addTimer(2, function () {
echo "2 secondes se sont écoulées\n";
});

$loop->addPeriodicTimer(1, function () {
echo "Tic\n";
});

echo "Démarrage du programme\n";


$loop->run();

20.3 Promesses en PHP


Les promesses représentent une valeur qui pourrait ne pas être disponible immédiatement.

20.3.1 Utilisation de la bibliothèque Guzzle Promises

composer require guzzlehttp/promises

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Promise\Promise;

$promise = new Promise(function () use (&$promise) {


// Simulation d'une opération asynchrone
sleep(2);
$promise->resolve('Résultat');
});
$promise->then(function ($result) {
echo $result;
});

echo "En attente du résultat...\n";


$promise->wait(); // Attente bloquante pour cet exemple

20.4 Générateurs et Coroutines


Les générateurs peuvent être utilisés pour écrire du code asynchrone de manière plus linéaire.

<?php
function generateur() {
yield "Première étape";
yield "Deuxième étape";
yield "Troisième étape";
}

$gen = generateur();
foreach ($gen as $etape) {
echo $etape . "\n";
}

20.5 Async/Await en PHP (PHP 8.1+)


PHP 8.1 introduit les mots-clés async et await pour une syntaxe plus élégante de la
programmation asynchrone.

<?php
// Notez que ceci est un exemple conceptuel et ne fonctionnera pas tel quel
dans PHP actuel
async function obtenirDonnees() {
$resultat = await appelerAPIAsynchrone();
return $resultat;
}

$donnees = await obtenirDonnees();


echo $donnees;

20.6 Considérations sur les performances


La programmation asynchrone est particulièrement utile pour les opérations I/O intensives.
Elle peut améliorer significativement les performances des applications web traitant de
nombreuses requêtes simultanées.
Cependant, elle ajoute de la complexité au code et peut rendre le débogage plus difficile.

Exercice 20.1
Créez un script PHP utilisant ReactPHP pour simuler le traitement asynchrone de plusieurs
tâches. Le script doit :

1. Démarrer 3 "tâches" asynchrones avec des durées différentes.


2. Afficher un message quand chaque tâche commence et se termine.
3. Continuer à afficher un message "En cours d'exécution..." toutes les secondes pendant
que les tâches s'exécutent.

Corrigé 20.1

<?php
require 'vendor/autoload.php';

use React\EventLoop\Factory;

$loop = Factory::create();

function tacheAsynchrone($nom, $duree, $loop) {


$loop->addTimer(0, function () use ($nom) {
echo "Démarrage de la tâche $nom\n";
});

$loop->addTimer($duree, function () use ($nom) {


echo "Fin de la tâche $nom\n";
});
}

tacheAsynchrone("A", 2, $loop);
tacheAsynchrone("B", 4, $loop);
tacheAsynchrone("C", 3, $loop);

$loop->addPeriodicTimer(1, function () {
echo "En cours d'exécution...\n";
});
$loop->addTimer(5, function () use ($loop) {
$loop->stop();
});

echo "Démarrage du programme\n";


$loop->run();
echo "Fin du programme\n";

Ce script démontre l'utilisation de ReactPHP pour exécuter des tâches asynchrones et


périodiques. Il simule trois tâches de durées différentes et affiche l'état d'exécution toutes les
secondes.

Exercice 20.2
Créez une classe AsyncHttpClient qui utilise Guzzle Promises pour effectuer des requêtes
HTTP asynchrones. La classe doit avoir une méthode pour effectuer plusieurs requêtes GET en
parallèle et retourner les résultats. Testez la classe en effectuant des requêtes vers plusieurs
API publiques.

Corrigé 20.2

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Promise;

class AsyncHttpClient {
private $client;

public function __construct() {


$this->client = new Client();
}

public function getAsync($urls) {


$promises = [];
foreach ($urls as $key => $url) {
$promises[$key] = $this->client->getAsync($url);
}

return Promise\Utils::settle($promises)->wait();
}
}

// Utilisation
$client = new AsyncHttpClient();

$urls = [
'users' => 'https://jsonplaceholder.typicode.com/users',
'posts' => 'https://jsonplaceholder.typicode.com/posts',
'comments' => 'https://jsonplaceholder.typicode.com/comments'
];

$results = $client->getAsync($urls);

foreach ($results as $key => $result) {


if ($result['state'] === 'fulfilled') {
echo "$key: " . substr($result['value']->getBody(), 0, 100) . "...\n";
} else {
echo "$key: " . $result['reason']->getMessage() . "\n";
}
}

Cet exercice permet aux étudiants de créer un client HTTP asynchrone simple utilisant Guzzle
Promises. Il démontre comment effectuer plusieurs requêtes HTTP en parallèle et gérer leurs
résultats de manière asynchrone.

Ces exercices offrent une introduction pratique à la programmation asynchrone en PHP,


couvrant l'utilisation de ReactPHP pour la gestion d'événements asynchrones et de Guzzle
Promises pour les requêtes HTTP asynchrones. Ces compétences sont précieuses pour
développer des applications PHP performantes et réactives.

21. Tests unitaires en PHP


Les tests unitaires sont une pratique essentielle dans le développement logiciel moderne. Ils
permettent de vérifier le bon fonctionnement de parties isolées du code (unités), généralement
des méthodes ou des fonctions.

21.1 Introduction aux tests unitaires


21.1.1 Pourquoi les tests unitaires ?

Détection précoce des bugs


Facilitation des refactorisations
Documentation vivante du code
Amélioration de la conception du code
Confiance accrue lors des déploiements

21.1.2 Caractéristiques d'un bon test unitaire

Rapide à exécuter
Indépendant des autres tests
Répétable (même résultat à chaque exécution)
Auto-vérifiable (pas d'intervention manuelle nécessaire)
Écrit au bon moment (idéalement avant ou pendant le développement de la fonctionnalité)

21.2 PHPUnit
PHPUnit est le framework de test unitaire le plus populaire pour PHP.

21.2.1 Installation de PHPUnit


Utilisez Composer pour installer PHPUnit :

composer require --dev phpunit/phpunit ^9.5

21.2.2 Structure de base d'un test PHPUnit

<?php
use PHPUnit\Framework\TestCase;

class MaClasseTest extends TestCase


{
public function testUneMethode()
{
$resultatAttendu = 42;
$resultatObtenu = maFonction();
$this->assertEquals($resultatAttendu, $resultatObtenu);
}
}
21.3 Assertions PHPUnit
PHPUnit fournit de nombreuses méthodes d'assertion pour vérifier les résultats des tests :

$this->assertTrue($condition);
$this->assertFalse($condition);
$this->assertEquals($attendu, $obtenu);
$this->assertNull($valeur);
$this->assertContains($aiguille, $meule);
$this->assertCount($nombreAttendu, $tableau);

21.4 Organisation des tests


21.4.1 Méthodes de configuration

class MaClasseTest extends TestCase


{
protected $objet;

protected function setUp(): void


{
$this->objet = new MaClasse();
}

protected function tearDown(): void


{
$this->objet = null;
}

// Tests...
}

21.4.2 Groupes de tests

/**
* @group slow
*/
public function testOperationLongue()
{
// Test long...
}
21.5 Tests de données
21.5.1 Tests paramétrés

/**
* @dataProvider additionProvider
*/
public function testAddition($a, $b, $resultatAttendu)
{
$this->assertEquals($resultatAttendu, $a + $b);
}

public function additionProvider()


{
return [
[0, 0, 0],
[0, 1, 1],
[1, 1, 2],
[1, 2, 3]
];
}

21.6 Mocks et Stubs


Les mocks et les stubs permettent d'isoler l'unité testée en simulant le comportement des
dépendances.

public function testAvecMock()


{
$mock = $this->createMock(DependanceClass::class);
$mock->method('uneMethode')->willReturn('valeur simulée');

$objet = new ClasseTestee($mock);


$resultat = $objet->methodeUtilisantLaDependance();

$this->assertEquals('résultat attendu', $resultat);


}

21.7 Couverture de code


PHPUnit peut générer des rapports de couverture de code pour voir quelles parties du code
sont testées.

./vendor/bin/phpunit --coverage-html coverage

Exercice 21.1
Créez une classe Calculator avec des méthodes pour l'addition, la soustraction, la
multiplication et la division. Écrivez ensuite une classe de test CalculatorTest qui teste toutes
ces méthodes, y compris les cas limites (comme la division par zéro).

Corrigé 21.1

<?php
// Calculator.php
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}

public function subtract($a, $b)


{
return $a - $b;
}

public function multiply($a, $b)


{
return $a * $b;
}

public function divide($a, $b)


{
if ($b == 0) {
throw new InvalidArgumentException("Division by zero");
}
return $a / $b;
}
}

// CalculatorTest.php
use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase


{
protected $calculator;

protected function setUp(): void


{
$this->calculator = new Calculator();
}

public function testAdd()


{
$this->assertEquals(4, $this->calculator->add(2, 2));
$this->assertEquals(0, $this->calculator->add(-1, 1));
}

public function testSubtract()


{
$this->assertEquals(0, $this->calculator->subtract(2, 2));
$this->assertEquals(-2, $this->calculator->subtract(-1, 1));
}

public function testMultiply()


{
$this->assertEquals(4, $this->calculator->multiply(2, 2));
$this->assertEquals(-2, $this->calculator->multiply(-1, 2));
}

public function testDivide()


{
$this->assertEquals(1, $this->calculator->divide(2, 2));
$this->assertEquals(2, $this->calculator->divide(4, 2));
}

public function testDivideByZero()


{
$this->expectException(InvalidArgumentException::class);
$this->calculator->divide(1, 0);
}
}

Pour exécuter ces tests :

./vendor/bin/phpunit CalculatorTest.php
Exercice 21.2
Créez une classe UserManager qui gère les utilisateurs (ajout, suppression, recherche) en
utilisant une base de données. Utilisez des mocks pour simuler la base de données dans vos
tests unitaires.

Corrigé 21.2

<?php
// UserManager.php
class UserManager
{
private $db;

public function __construct($db)


{
$this->db = $db;
}

public function addUser($name, $email)


{
return $this->db->insert('users', ['name' => $name, 'email' =>
$email]);
}

public function deleteUser($id)


{
return $this->db->delete('users', ['id' => $id]);
}

public function findUser($id)


{
return $this->db->selectOne('users', ['id' => $id]);
}
}

// UserManagerTest.php
use PHPUnit\Framework\TestCase;

class UserManagerTest extends TestCase


{
public function testAddUser()
{
$dbMock = $this->createMock(DatabaseInterface::class);
$dbMock->expects($this->once())
->method('insert')
->with('users', ['name' => 'John Doe', 'email' =>
'[email protected]'])
->willReturn(1);

$userManager = new UserManager($dbMock);


$result = $userManager->addUser('John Doe', '[email protected]');

$this->assertEquals(1, $result);
}

public function testDeleteUser()


{
$dbMock = $this->createMock(DatabaseInterface::class);
$dbMock->expects($this->once())
->method('delete')
->with('users', ['id' => 1])
->willReturn(true);

$userManager = new UserManager($dbMock);


$result = $userManager->deleteUser(1);

$this->assertTrue($result);
}

public function testFindUser()


{
$expectedUser = ['id' => 1, 'name' => 'John Doe', 'email' =>
'[email protected]'];

$dbMock = $this->createMock(DatabaseInterface::class);
$dbMock->expects($this->once())
->method('selectOne')
->with('users', ['id' => 1])
->willReturn($expectedUser);

$userManager = new UserManager($dbMock);


$result = $userManager->findUser(1);

$this->assertEquals($expectedUser, $result);
}
}

Ces exercices permettent aux étudiants de pratiquer l'écriture de tests unitaires pour des
classes simples et plus complexes, en utilisant des assertions de base et des mocks pour
simuler des dépendances externes comme une base de données.

22. Déploiement d'applications PHP


Le déploiement est le processus qui consiste à mettre une application en production, la rendant
accessible aux utilisateurs finaux. Un déploiement réussi nécessite une planification minutieuse
et la prise en compte de nombreux facteurs.

22.1 Préparation au déploiement


22.1.1 Environnements de développement

Développement local
Environnement de test (staging)
Production

22.1.2 Gestion de versions


Utilisation de systèmes de contrôle de version comme Git :

git clone https://github.com/votre-repo/votre-projet.git


git checkout -b feature/nouvelle-fonctionnalite
# Après les modifications
git add .
git commit -m "Ajout de la nouvelle fonctionnalité"
git push origin feature/nouvelle-fonctionnalite

22.1.3 Gestion des dépendances

Utilisation de Composer pour gérer les dépendances PHP :

composer install --no-dev --optimize-autoloader

22.2 Configuration du serveur


22.2.1 Serveur web (Apache ou Nginx)
Configuration Apache (.htaccess) :

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

Configuration Nginx :

location / {
try_files $uri $uri/ /index.php?$query_string;
}

22.2.2 PHP-FPM
Configuration de base de PHP-FPM :

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

22.2.3 Base de données

Sécurisation de MySQL :

CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password';


GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO
'app_user'@'localhost';
FLUSH PRIVILEGES;

22.3 Processus de déploiement


22.3.1 Déploiement manuel
1. Sauvegarde des données existantes
2. Mise à jour du code source
3. Mise à jour des dépendances
4. Application des migrations de base de données
5. Mise à jour de la configuration
6. Redémarrage des services

22.3.2 Déploiement automatisé

Utilisation d'outils comme Deployer :

<?php
namespace Deployer;

require 'recipe/laravel.php';

set('application', 'Mon Application');


set('repository', '[email protected]:username/repository.git');
set('git_tty', true);
set('keep_releases', 5);

host('project.com')
->set('deploy_path', '/var/www/project');

after('deploy:failed', 'deploy:unlock');

desc('Deploy your project');


task('deploy', [
'deploy:info',
'deploy:prepare',
'deploy:lock',
'deploy:release',
'deploy:update_code',
'deploy:shared',
'deploy:vendors',
'deploy:writable',
'artisan:storage:link',
'artisan:view:cache',
'artisan:config:cache',
'artisan:migrate',
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);

22.4 Optimisation des performances


22.4.1 Mise en cache
Utilisation de Redis pour le cache :

$redis = new Redis();


$redis->connect('127.0.0.1', 6379);
$redis->set('key', 'value');
$value = $redis->get('key');

22.4.2 Optimisation des assets


Utilisation de Webpack pour le bundling et la minification :

// webpack.config.js
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new MiniCssExtractPlugin(),
],
// ...
};

22.5 Surveillance et maintenance


22.5.1 Logging
Utilisation de Monolog pour le logging :

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));

$log->warning('Attention : événement important');


$log->error('Une erreur est survenue', ['code' => 500]);

22.5.2 Monitoring
Mise en place d'outils de monitoring comme New Relic ou Datadog.

22.6 Sécurité post-déploiement


Mise à jour régulière des dépendances
Configuration du pare-feu
Utilisation de HTTPS
Mise en place de politiques de sauvegarde

Exercice 22.1

Créez un script de déploiement bash simple qui effectue les étapes suivantes :

1. Mise à jour du code depuis un dépôt Git


2. Installation/mise à jour des dépendances avec Composer
3. Application des migrations de base de données
4. Nettoyage du cache
5. Redémarrage du serveur web

Corrigé 22.1

#!/bin/bash

# Configuration
APP_DIR="/var/www/monapp"
REPO_URL="https://github.com/username/monapp.git"

# Fonction pour logger les messages


log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Mise à jour du code
log "Mise à jour du code source"
cd $APP_DIR
git pull origin master

# Installation/mise à jour des dépendances


log "Mise à jour des dépendances"
composer install --no-dev --optimize-autoloader

# Application des migrations


log "Application des migrations de base de données"
php artisan migrate --force

# Nettoyage du cache
log "Nettoyage du cache"
php artisan cache:clear
php artisan config:clear
php artisan view:clear

# Redémarrage du serveur web (exemple pour Apache)


log "Redémarrage du serveur web"
sudo service apache2 restart

log "Déploiement terminé avec succès"

Pour utiliser ce script :

1. Sauvegardez-le dans un fichier (par exemple deploy.sh )


2. Rendez-le exécutable : chmod +x deploy.sh
3. Exécutez-le : ./deploy.sh

Exercice 22.2
Configurez un pipeline CI/CD simple avec GitHub Actions pour une application PHP. Le pipeline
doit :

1. Exécuter les tests unitaires


2. Vérifier le style du code (PSR-12)
3. Déployer l'application sur un serveur de staging si tous les tests passent

Corrigé 22.2
Créez un fichier .github/workflows/ci-cd.yml dans votre projet avec le contenu suivant :

name: CI/CD Pipeline

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Setup PHP


uses: shivammathur/setup-php@v2
with:
php-version: '8.0'

- name: Install Dependencies


run: composer install --prefer-dist --no-progress

- name: Run Tests


run: vendor/bin/phpunit

- name: Check Coding Standards


run: vendor/bin/phpcs --standard=PSR12 src/

deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

steps:
- uses: actions/checkout@v2

- name: Deploy to Staging


uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/staging
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache

Pour que ce pipeline fonctionne, vous devrez :

1. Configurer les secrets dans les paramètres de votre dépôt GitHub (HOST, USERNAME,
SSH_PRIVATE_KEY)
2. Avoir PHPUnit et PHP_CodeSniffer installés dans votre projet
3. Avoir un serveur de staging configuré avec un accès SSH

Ce pipeline exécutera les tests, vérifiera le style du code, et si tout est OK, déploiera
l'application sur le serveur de staging.

Ces exercices permettent aux étudiants de mettre en pratique les concepts de déploiement, à
la fois manuellement avec un script bash et de manière automatisée avec un pipeline CI/CD, ce
qui est essentiel dans le développement web moderne.

23. Bonnes pratiques et patterns de conception en PHP


Les bonnes pratiques et les patterns de conception sont essentiels pour écrire du code propre,
maintenable et évolutif. Ils aident à résoudre des problèmes récurrents de manière efficace et
éprouvée.

23.1 Principes SOLID


Les principes SOLID sont un ensemble de lignes directrices pour le développement orienté
objet.

23.1.1 Single Responsibility Principle (SRP)

Une classe ne devrait avoir qu'une seule raison de changer.

// Mauvais exemple
class User {
public function saveUser() { /* ... */ }
public function generateReport() { /* ... */ }
}
// Bon exemple
class User {
public function save() { /* ... */ }
}

class ReportGenerator {
public function generateUserReport(User $user) { /* ... */ }
}

23.1.2 Open/Closed Principle (OCP)


Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification.

abstract class Shape {


abstract public function area();
}

class Rectangle extends Shape {


private $width, $height;

public function area() {


return $this->width * $this->height;
}
}

class Circle extends Shape {


private $radius;

public function area() {


return pi() * $this->radius ** 2;
}
}

23.1.3 Liskov Substitution Principle (LSP)

Les objets d'une superclasse doivent pouvoir être remplacés par des objets de ses sous-
classes sans affecter la cohérence du programme.

23.1.4 Interface Segregation Principle (ISP)


Plusieurs interfaces spécifiques sont préférables à une seule interface générale.

23.1.5 Dependency Inversion Principle (DIP)


Dépendre des abstractions, pas des implémentations concrètes.

interface Logger {
public function log($message);
}

class FileLogger implements Logger {


public function log($message) {
// Écrire dans un fichier
}
}

class UserManager {
private $logger;

public function __construct(Logger $logger) {


$this->logger = $logger;
}

public function createUser($userData) {


// Créer l'utilisateur
$this->logger->log("Utilisateur créé");
}
}

23.2 Patterns de conception courants


23.2.1 Singleton
Garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à cette
instance.

class Database {
private static $instance = null;
private $connection;

private function __construct() {


$this->connection = new PDO(/* ... */);
}

public static function getInstance() {


if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}

public function query($sql) {


// Exécuter la requête
}
}

// Utilisation
$db = Database::getInstance();
$result = $db->query("SELECT * FROM users");

23.2.2 Factory
Définit une interface pour créer un objet, mais laisse les sous-classes décider quelle classe
instancier.

interface Animal {
public function makeSound();
}

class Dog implements Animal {


public function makeSound() {
return "Woof!";
}
}

class Cat implements Animal {


public function makeSound() {
return "Meow!";
}
}

class AnimalFactory {
public static function createAnimal($type) {
switch ($type) {
case 'dog':
return new Dog();
case 'cat':
return new Cat();
default:
throw new Exception("Animal type not supported");
}
}
}

// Utilisation
$dog = AnimalFactory::createAnimal('dog');
echo $dog->makeSound(); // Woof!

23.2.3 Observer
Définit une dépendance un-à-plusieurs entre objets, de sorte que lorsqu'un objet change d'état,
tous ses dépendants sont notifiés et mis à jour automatiquement.

interface Observer {
public function update($data);
}

class Subject {
private $observers = [];
private $state;

public function attach(Observer $observer) {


$this->observers[] = $observer;
}

public function setState($state) {


$this->state = $state;
$this->notify();
}

private function notify() {


foreach ($this->observers as $observer) {
$observer->update($this->state);
}
}
}

class ConcreteObserver implements Observer {


public function update($data) {
echo "Nouvelle mise à jour reçue : $data\n";
}
}

// Utilisation
$subject = new Subject();
$observer1 = new ConcreteObserver();
$observer2 = new ConcreteObserver();

$subject->attach($observer1);
$subject->attach($observer2);

$subject->setState("Nouvel état");

23.3 Bonnes pratiques de codage


23.3.1 PSR (PHP Standard Recommendations)
Suivre les standards PSR, notamment PSR-1 (Basic Coding Standard) et PSR-12 (Extended
Coding Style).

23.3.2 DRY (Don't Repeat Yourself)


Éviter la duplication de code en extrayant les parties communes dans des fonctions ou des
classes réutilisables.

23.3.3 KISS (Keep It Simple, Stupid)

Garder le code aussi simple que possible. La simplicité rend le code plus facile à comprendre
et à maintenir.

23.3.4 YAGNI (You Ain't Gonna Need It)


Ne pas ajouter de fonctionnalités jusqu'à ce qu'elles soient vraiment nécessaires.

23.4 Gestion des erreurs et exceptions


Utiliser les exceptions pour gérer les erreurs de manière élégante et informative.
class UserNotFoundException extends Exception {}

function getUser($id) {
$user = // recherche de l'utilisateur
if (!$user) {
throw new UserNotFoundException("Utilisateur avec l'ID $id non
trouvé");
}
return $user;
}

try {
$user = getUser(123);
} catch (UserNotFoundException $e) {
// Gérer l'erreur
log($e->getMessage());
}

Exercice 23.1
Refactorez le code suivant en appliquant le principe de responsabilité unique (SRP) et le
pattern Factory :

class User {
public function createUser($data) {
// Logique de création d'utilisateur
}

public function sendEmail($to, $subject, $body) {


// Logique d'envoi d'email
}

public function generateReport() {


// Logique de génération de rapport
}
}

// Utilisation
$user = new User();
$user->createUser($userData);
$user->sendEmail("[email protected]", "Bienvenue", "Contenu du message");
$user->generateReport();
Corrigé 23.1

class User {
private $id;
private $name;
private $email;

public function __construct($id, $name, $email) {


$this->id = $id;
$this->name = $name;
$this->email = $email;
}

// Getters et setters...
}

class UserFactory {
public static function createUser($data) {
// Vérification et validation des données
return new User($data['id'], $data['name'], $data['email']);
}
}

class EmailService {
public function sendEmail($to, $subject, $body) {
// Logique d'envoi d'email
}
}

class ReportGenerator {
public function generateUserReport(User $user) {
// Logique de génération de rapport pour un utilisateur
}
}

// Utilisation
$userFactory = new UserFactory();
$user = $userFactory->createUser($userData);

$emailService = new EmailService();


$emailService->sendEmail($user->getEmail(), "Bienvenue", "Contenu du
message");

$reportGenerator = new ReportGenerator();


$report = $reportGenerator->generateUserReport($user);
Exercice 23.2
Implémentez le pattern Observer pour créer un système simple de notification. Créez une
classe Newsletter (sujet) et une classe Subscriber (observateur). Lorsqu'une nouvelle
newsletter est publiée, tous les abonnés doivent être notifiés.

Corrigé 23.2

interface Observer {
public function update($data);
}

class Newsletter {
private $observers = [];
private $content;

public function attach(Observer $observer) {


$this->observers[] = $observer;
}

public function detach(Observer $observer) {


$key = array_search($observer, $this->observers, true);
if ($key !== false) {
unset($this->observers[$key]);
}
}

public function publish($content) {


$this->content = $content;
$this->notify();
}

private function notify() {


foreach ($this->observers as $observer) {
$observer->update($this->content);
}
}
}

class Subscriber implements Observer {


private $name;

public function __construct($name) {


$this->name = $name;
}

public function update($content) {


echo "{$this->name} a reçu une nouvelle newsletter : {$content}\n";
}
}

// Utilisation
$newsletter = new Newsletter();

$alice = new Subscriber("Alice");


$bob = new Subscriber("Bob");
$charlie = new Subscriber("Charlie");

$newsletter->attach($alice);
$newsletter->attach($bob);
$newsletter->attach($charlie);

$newsletter->publish("Nouvelles fonctionnalités disponibles !");

$newsletter->detach($bob);

$newsletter->publish("Mise à jour importante de sécurité");

Ces exercices permettent aux étudiants de mettre en pratique les concepts de SOLID, les
patterns de conception, et les bonnes pratiques de développement dans des scénarios
réalistes.

Vous aimerez peut-être aussi