Développeur web freelance depuis 5 ans
Expérience en agence web, E-Commerce (annonceur)
Formation Informatique et Autodidacte
Formateur et fan des OSS, contributeur à mes heures perdues
Solutions phares : Wordpress, Symfony, Magento, Prestashop, Angular
Langages : PHP, MySQL, HTML, CSS, JS
PHP signifie "Hypertext Preprocessor" (acronyme récursif).
C'est un langage de programmation qui est utilisé pour créer des pages web dynamiques. Il est compilé à la volée avec un serveur HTTP.
Il est utilisé par des millions de sites web dans le monde dont Facebook et Wikipédia.
- Un espace membre
- Un site ecommerce
- Un forum
- Un compteur de visites
- Envoi de fichier via un formulaire
Structure et contenu
Animations
Style et design
Dynamique
Base de données
<=
Il a été créé en 1994 par Rasmus Lerdorf. Personal Home Page Tools
Historique des versions de PHP :
1.0 : 1995 - 2.0 : 1997
3.0 : 1998 - 4.0 : 2000
5.0 : 2004 - 7.0 : 2015
Versions de PHP conseillées en 2022
7.4, 8.0, 8.1
Site officiel : https://php.net
Soyez prudents et critiques face aux codes proposés par la communauté. Attention aux commentaires et à la date de publication.
- MacOS : MAMP
- Windows : WAMP
- Linux : LAMP
- Tous : XAMPP / Laragon
La première lettre représente le système d'exploitation.
AMP signifie Apache, MySQL, PHP
- Un fichier PHP doit toujours avoir l'extension .php
- Les fichiers PHP doivent être dans le dossier htdocs de notre XAMP
- Le serveur Apache (HTTP) doit être lancé sur le port 80
- Les fichiers de configuration peuvent être php.ini pour PHP, apache.conf pour Apache, my.cnf pour MySQL
- On peut accéder à notre site via http://localhost et si un fichier index.php est présent, il sera chargé
?>
- Un code PHP doit toujours commencer par l'ouverture de balise PHP
<?php
- Un code PHP peut se terminer par une fermeture de balise PHP (Pas obligatoire si le fichier se termine par du code PHP)
- Entre ces 2 balises, on écrira notre code PHP
- On peut ouvrir et fermer les balises PHP plusieurs fois d'affilée
<?php
echo 'Texte entre quotes';
echo "ou bien entre double quotes";
?>
Une instruction est un ordre que l'on donne au langage
Chaque instruction PHP se termine par un ;
On peut afficher une chaine de caractères avec une instruction "echo"
Dans le navigateur, on ne verra que le texte et pas le code PHP
<?php
// Un commentaire sur une seule ligne
/*
Un commentaire
sur plusieurs
lignes
*/
// Un bout de code commenté
/*
echo 'test';
*/
Commenter le code peut aider à se repérer dans son code ou aussi à rechercher les erreurs
<!DOCTYPE html>
<html>
<head>
<title>Titre</title>
</head>
<body>
<?php
echo 'Chaine affichée en PHP';
// Si on veut utiliser les quotes dans la chaine, il faut les échapper avec un \
echo 'L\'HTML dans le PHP';
// On peut utiliser du HTML dans le PHP
echo 'Chaine affichée en PHP <br />';
?>
Chaine affichée en HTML
</body>
</html>
On peut continuer à faire de l'HTML sans soucis
Une variable est une boîte avec une étiquette contenant une donnée.
<?php
$age = 26; // Initialise une variable et lui affecte la valeur 26
$mon_age = 26; // Underscore
$monAge = 26; // lowerCamelCase
$doubleAge = $monAge * 2; // $doubleAge vaut 52
echo $doubleAge; // Affiche 52
Il existe 8 types de données en PHP
<?php
$string = 'Hello';
$integer = 26;
$float = 3.14;
$boolean = true;
$array = [1, 2, 3];
$array2 = array(1, 2, 3);
// Tableau associatif
$array3 = ['a' => 1, 'b' => 2, 'c' => 3];
$object = new Object();
$null = null;
$resource = fopen('C:\fichier.txt');
Attention au typage faible
La concaténakwa ?
La concaténation permet de mettre à bout au moins 2 chaines de caractères
<?php
/* Opérateur de concaténation */
$a = 'Hello ';
$b = $a . 'World !'; // $a ne change pas mais $b vaut désormais "Hello World !"
/* Opérateur d'affectation concaténant */
$c = 'Hello ';
// On concatène cette chaîne à la précédente, $c vaut désormais "Hello World !"
$c .= 'World !';
/* Il est tout à fait possible de combiner les deux */
$d = 'Hello ';
$e = 'World ';
$d .= $e . '!'; // $e ne change pas, mais $d vaut désormais "Hello World !"
echo $d;
Opérateurs arithmétiques
<?php
$a = 10; // Ici, c'est l'opérateur d'affectation
// Ci-dessous, opérateurs arithmétiques
$a + 30; // Addition 40
echo 10 - 5; // Soustraction 5
echo 3 * 2; // Multiplication 6
$a = 3 + 3; // on peut modifier une variable en cours de route
echo $a / 2; // Division 3 car a vaut 6
echo 10 % 3; // Modulo (Reste de la division) 1
echo 10 ** 2; // Exponentielle: 10 à la puissance 2 (PHP 5.6+) 100
echo -$a; // -10
$a += $b; // équivaut à $a = $a + $b;
$a -= $b; // équivaut à $a = $a - $b;
$a *= $b; // équivaut à $a = $a * $b;
$a /= $b; // équivaut à $a = $a / $b;
$a %= $b; // équivaut à $a = $a % $b;
Les opérateurs de comparaison renvoient true ou false
<?php
$a == $b; // vrai si $a est égal à $b
// vrai si $a est égal à $b et qu'ils sont du même type (integer, string, etc.)
$a === $b;
// vrai si $a est différent de $b
$a != $b; /*ou*/ echo $a <> $b;
$a !== $b; // vrai si $a est différent de $b ou qu'ils ne sont pas du même type
$a < $b; // vrai si $a est strictement inférieur à $b
$a > $b; // vrai si $a est strictement supérieur à $b
$a <= $b; // vrai si $a est inférieur ou égal à $b
$a >= $b; // vrai si $a est supérieur ou égal à $b
!$a; // vrai si $a vaut false
Cas d'utilisation des opérateurs de comparaison.
Pour rappel, une condition est "SI quelque chose est vrai FAIS quelque chose SINON FAIS autre chose
Attention, les blocs d'instructions { } ne se termine pas par un ;
<?php
$a = 3;
$condition = 0 === false;
if ($a == 0) {
// Si $a vaut 0, les instructions présentes ici seront exécutées
} elseif ($a > 12 && $a <= 42) {
// Si $a est supérieur à 12 et inférieur ou égal à 42,
// les instructions présentes ici seront exécutées.
} else if ($condition) {
// Si $condition est vraie, l'instruction présente ici sera exécutée.
} else {
// Sinon, si aucune des 2 conditions précédentes n'est remplie,
// les instructions présentes ici seront exécutées.
// Ici, $a vaut 3, il ne vaut donc pas 0 et n'est pas compris entre 12 et 42 inclus.
// C'est donc ce bloc d'instruction qui sera exécuté et non les 2 précédents.
}
<?php
$a && $b; $a and $b; // True si $a ET $b sont true
$a || $b; $a or $b; // True si $a OU $b est true
$a xor $b; // True si $a OU $b est true mais pas les 2 à la fois
if ($a && $b && $c) {
// On fais quelque chose
}
// Attention aux priorités, le AND est comme une multiplication (prioritaire)
// Et le OR comme une addition (pas prioritaire)
if ($a || $b && $c) {
}
Permet d'associer des opérations
<?php
$a = 0;
++$a; // La variable est incrémentée de 1, puis elle est retournée
$a++; // La variable est retournée, puis incrémentée de 1
--$a; // La variable est décrémentée de 1, puis elle est retournée
$a--; // La variable est retournée, puis décrémentée de 1
L'incrémentation (et la décrémentation) permettent d'ajouter ou d'enlever 1 à un nombre ou une chaine de caractères.
<?php
echo $a; // Affiche une NOTICE, mais le script ne s'arrête pas
echo 1 / 0; // Affiche un WARNING, division par zéro
echo 3;
echo 4 // Affiche une ERROR Parse error: syntax error
echo 6;
Il est normal de faire des erreurs, le plus important est de savoir les debuger.
3 niveaux d'erreurs : NOTICE, WARNING, ERROR
<?php
$tableau = [1, 2, 3];
print_r($tableau); // Affiche le contenu du tableau non formaté
die('Stop le script'); // Arrête le script avec un message ou pas
var_dump($tableau); // Affiche le contenu du tableau formaté
Il existe 3 fonctions importantes en PHP pour s'aider dans le debug d'erreur
15 + 5 + 8 = 28
15 x (8 - 5) = 45
(8 + 5) / 15 = 0.86
Si une des opérations a un résultat inférieur à 20, afficher "Une des opérations renvoie moins de 20" en bas de la page
HTTP (HyperText Transfer Protocol) est un langage qui permet à 2 machines de communiquer ensemble, le client et le serveur.
Le client envoie une requête
Le serveur envoie une réponse
GET / HTTP/1.1
Host: google.fr
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)
HTTP/1.1 200 OK
Date: Sun, 20 May2018 14:05:05 GMT
Server: apache/2.4.33
Content-Type: text/html
<html>
Vous êtes sur Google
</html>
100 : Information
200 : Succès
300 : Redirection
400 : Erreur du client
500 : Erreur du serveur
Le serveur renvoie toujours un code dans la réponse (200, 404, 403, 500).
<?php
/* for
elle est composée d'une instruction d'initialisation, d'une condition d'exécution
et d'une instruction à exécuter à chaque itération après le bloc d'instruction de la boucle
*/
for ($i = 0; $i < 10; $i++) {
// instructions
}
/* foreach
particulièrement utile, elle permet de parcourir un tableau (ou les propriétés d'un objet)
*/
$a = array(1, 2, 3);
foreach ($a as $item) {
// instructions
}
// foreach permet également de récupérer la clé associée à la valeur dans le tableau
$fruits = ['A' => 'abricot', 'B' => 'banane', 'C' => 'cerise'];
foreach ($fruits as $lettre => $fruit) {
echo $lettre . ' : ' . $fruit;
// Affichera pour la première itération "A : abricot"
}
Permet de répéter des instructions en fonction d'une ou plusieurs conditions particulière
<?php
/* while
elle est similaire à for si ce n'est
qu'elle n'a comme paramètre que la condition d'exécution
*/
$i = 0;
while ($i < 10) {
// instructions
$i++;
}
/* do…while
fonctionne comme while, excepté que les instructions
qu'elle conditionne sont exécutées 1 fois avant que la condition soit testée
*/
$i = 0;
do {
// instructions
$i++;
} while($i < 10);
1. Ecrire une boucle qui affiche les nombres de 10 à 1
2. Ecrire une boucle qui affiche uniquement les nombres pairs entre 1 et 100
3. Ecrire le code permettant de trouver le PGCD de 2 nombres
4. Coder le jeu du FizzBuzz
Parcourir les nombres de 0 à 100
Quand le nombre est un multiple de 3, afficher Fizz.
Quand le nombre est un multiple de 5, afficher Buzz.
Quand le nombre est un multiple de 15, afficher FizzBuzz.
Sinon, afficher le nombre
1. Afficher la table de multiplication du chiffre 5
5 x 1 = 5
5 x 2 = 10
...
2. Afficher l'ensemble des tables de multiplications de 1 à 10.
<?php
// Pour vous aider
for ($i = 1; $i <= 10; $i++) {
echo '1 x ' . $i;
}
Grâce aux boucles et un peu d'HTML, essayez de reproduire ce tableau :
<?php
/* isset() Si la variable $a n'existe pas déjà, on la défini avec comme valeur 42. */
if (!isset($a)) {
$a = 42;
}
/* empty() Si la variable $a n'est pas vide, on affiche sa valeur. */
if (!empty($a)) {
echo $a;
}
/* unset() Maintenant qu'on a défini puis utilisé la variable $a, on la détruit. */
unset($a);
/* time(); affecte à $t le nombre de secondes écoulées depuis le 01/01/1970
à l'instant ou l'instruction est exécutée. */
$t = time();
/* date() Affichera la date au format JJ / MM / AAAA, par exemple 21 / 05 / 2018. */
echo date('d / m / Y', $t);
/* count() Affichera 3, car $b contient 3 valeurs. */
$b = array('pomme', 'poire', 'abricot');
echo count($b);
/* strlen() Affichera 13, car $c contient 13 caractères (cela inclut les espaces) */
$c = 'Hello World !';
echo strlen($c);
<?php
// La fonction date permet d'afficher une date sous un certain format
// Elle prend en second paramètre un timestamp de la date désirée
// Par défaut, c'est le timestamp courant
echo date('c'); // Affichera "2004-02-12T15:19:21+00:00"
echo "Nous sommes à la semaine " . date('W'); // Affichera "Nous sommes à la semaine 42"
echo date('d m Y H:i'); // Affichera "03 09 2015 18:48"
echo date('H\h i\m s\s'); // Affichera "18h 55m 36s"
time(); // Renvoie le timestamp courant
// strtotime est l'inverse de date, on peut passer un texte plutôt que le timestamp
// Usage simple
echo strtotime('now'); // Affichera le timestamp actuel, équivalent à time()
echo strtotime('9 january 2007'); // Affichera le timestamp de cette date, 1168297200
// Usage relatif
// Affichera le timestamp du lendemain du jour d'exécution du script
echo strtotime('+1 day');
// Affichera le timestamp relatif au jour d'exécution
// du script avec 2 semaines 1 jour 5 heures et 30 secondes d'ajoutées à celui-ci
echo strtotime('+2 weeks 1 day 5 hours 30 seconds');
// Affichera le timestamp du lundi précédent le plus proche du jour d'exécution du script
echo strtotime('last monday');
// Usage avec paramètre
// Affichera le timestamp correspondant à 6 mois après celui passé en paramètre
echo strtotime('+6 months', $timestamp);
1. Nous cherchons à afficher la date du jour au format "Tuesday 16 february 2021, il est 10h51 et 12 secondes". Cherchez sur Google la fonction PHP permettant de faire cela. Comment choisir le format de la date ?
2. Nous voulons récupérer le jour qu'il sera dans 10 jours exactement. Pensez que strtotime renvoie un timestamp.
3. Combien de jours reste-t-il avant Noël ?
<?php
// Tableau numérique à une dimension
$notes = [10, 20, 15];
// Comment récupérer le 20 ?
$notes[1];
// Tableau associatif à une dimension
$notes = ['Jean' => 10, 'Eric' => 20, 'Matthieu' => 15];
// Comment récupérer le 20 ?
$notes['Eric'];
// Tableau numérique à 2 dimensions
$morpion = [
['X', 'O', 'X'],
['X', 'Xa', 'O'],
['X', 'O', 'O']
];
// Alternative
$morpion = array(); // J'initialise un tableau vide
$morpion[0] = array('X', 'O', 'X');
$morpion[1] = array('X', 'Xa', 'O');
$morpion[2] = array('X', 'O', 'O');
// Comment récupérer Xa ?
$morpion[1][1];
Déclarer un tableau qui stocke les informations suivantes :
Afficher le résultat suivant en utilisant la boucle foreach :
La capitale de France est Paris.
La capitale de Espagne est Madrid.
La capitale de Italie est Rome.
<?php
$capitales = [
'France' => 'Paris',
'Espagne' => 'Madrid',
'Italie' => 'Rome',
];
D'après le tableau suivant :
<?php
$population = [
'France' => 67595000,
'Suede' => 9998000,
'Suisse' => 8417000,
'Kosovo' => 1820631,
'Malte' => 434403,
'Mexique' => 122273500,
'Allemagne' => 82800000,
];
1. Lister les pays ayant plus ou 20 millions d'habitants
2. Donner la population totale des pays Européens
<?php
$eleves = [
0 => [
'nom' => 'Matthieu',
'notes' => [10, 8, 16, 20, 17, 16, 15, 2]
],
1 => [
'nom' => 'Thomas',
'notes' => [4, 18, 12, 15, 13, 7]
],
2 => [
'nom' => 'Jean',
'notes' => [17, 14, 6, 2, 16, 18, 9]
],
3 => [
'nom' => 'Enzo',
'notes' => [1, 14, 6, 2, 1, 8, 9]
]
];
1/ Afficher la liste de tous les éléves avec leurs notes.
Exemple :
Matthieu a eu 10, 8, 16, 20, 17, 16, 15 et 2
Thomas a eu 4, 18, 12, 15, 13 et 7
Jean a eu 17, 14, 6, 2, 16, 18 et 9
2/ Calculer la moyenne de Jean. On part de $eleves[2]['notes']
La fonction count permet de compter les éléments d'un tableau
3/ Combien d'élèves ont la moyenne ?
Exemple :
Matthieu a la moyenne
Jean n'a pas la moyenne
Thomas a la moyenne
Nombre d'éléves avec la moyenne : 2
4/ Quel(s) éléve(s) a(ont) la meilleure note ?
Exemple: Thomas a la meilleure note : 19
5/ Qui a eu au moins un 20 ?
Exemple: Personne n'a eu 20
Quelqu'un a eu 20
On peut créer des fonctions afin d'y ranger une portion de code que l'on souhaite réutiliser plus tard.
<?php
// Déclare une fonction hello
function hello($argument) {
return 'Hello ' . $argument;
}
echo hello('world!'); // L'appel de la fonction affiche Hello world!
// Attention, on ne peut pas nommer 2 fonctions avec le même nom
function say($argument, $argument2) {
return $argument . ' ' . $argument2;
}
echo say('Hello', 'world!'); // L'appel de la fonction affiche Hello world!
La portée d'une variable est l'existence de celle-ci dans un contexte (dans le script ou dans une fonction)
<?php
$a = 10; // Portée globale
function toto()
{
$b = 2; /* Portée locale*/
echo $a; // Erreur
}
toto();
echo $b; // Erreur
// Comment accèder à $a dans la fonction ?
function toto()
{
global $a;
echo $a; // Fonctionne
}
<?php
$_GET; // Tableau contenant toutes les données passée dans l'URL
// index.php?key=valeur&key2=valeur2 donne $_GET = ['key' => 'valeur', 'key2' => 'valeur2'];
$_POST; // Tableau contenant toutes les données passée dans un formulaire
// <input name="key" value="valeur"> donne $_POST = ['key' => 'valeur'];
// Attention, la méthode du formulaire doit être en POST sinon on est
// dans l'exemple du dessus et les données du formulaire seront exposées
// dans l'URL. En POST, les données sont masquées (mais pas chiffrées).
$_GET et $_POST sont des variables superglobales.
$_SERVER contient des informations sur le serveur et le client. $_SESSION sur la session et $_COOKIE sur les cookies.
http://localhost/php/search.php?q=query
<?php
if (isset($_GET['q'])) {
echo 'on cherche ' . $_GET['q'];
}
<?php
if (isset($_POST)) {
// $_POST contient ['nom' => 'Valeur', 'prenom' => 'Valeur']
}
?>
<!-- L'attribut action permet de rediriger l'URL vers un autre fichier -->
<form method="POST" action="index.php">
<input type="text" name="nom">
<input type="text" name="prenom">
</form>
Pour un formulaire, l'attribut name est important.
En tant que développeur, il est très important de pouvoir valider les données afin de protéger le site d'attaques potentielles.
<?php
$nom = '';
$nombre1 = 'a';
$email = 'matthieu';
if (strlen($nom) == 0) {
exit('Votre nom ne doit pas être vide');
}
if (!ctype_digit($nombre1)) {
exit('Le nombre1 n\'est pas un nombre valide');
}
if (false == filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit('Cet email n\'est pas valide');
}
$errors[] = 'Une erreur';
$errors['email'] = 'Une erreur';
<?php
$email = 'matthieu@boxydev.com';
echo strlen($email); // Affiche 20
// Affiche 'Un nombre 10 et une chaine toto'.
echo sprintf('Un nombre %d et une chaine %s.', 10, 'toto');
// Affiche '@boxydev.com', stristr idem mais insensible à la casse
echo strstr($email, '@');
// Affiche 8, la taille de la chaine avant @, stripos idem mais insensible à la casse
echo strpos($email, '@');
// Affiche matthieu@gmail.com, str_ireplace idem mais insensible à la casse
echo str_replace('boxydev', 'gmail', $email);
echo substr('abcdef', 2, 2); // Affiche "cd"
echo substr('abcdef', 1, 3); // Affiche "bcd"
echo substr('abcdef', 3); // Affiche "def"
echo substr("abcdef", -1); // Affiche "f"
echo substr("abcdef", -2); // Affiche "ef"
echo substr("abcdef", -3, 1); // Affiche "d"
echo strrev('Hello world!'); // Affiche "!dlrow olleH"
// Une chaine est un tableau
echo $email[8]; // @
<?php
$keywords = "php, js, html, css";
$array_keywords = explode(', ', $keywords);
echo $array_keywords[0]; // Affiche 'php'
echo $array_keywords[1]; // Affiche 'js'
echo $array_keywords[2]; // Affiche 'html'
echo $array_keywords[3]; // Affiche 'css'
$keywords = ['php', 'js', 'html', 'css'];
$string_keywords = implode(', ', $keywords);
echo $string_keywords; // Affiche "php, js, html, css"
$message = 'Hello World';
echo strtolower($message); // Affiche 'hello world';
echo strtoupper($message); // Affiche 'HELLO WORLD';
$message = ' Hello World ';
echo trim($message); // Affiche 'Hello World';
$message = '<h1>Hello World, </h1><p>How are you ?</p>';
echo strip_tags($message); // Affiche 'Hello World, How are you ?'
echo strip_tags($message, '<h1>'); // Affiche '<h1>Hello World, </h1>How are you ?'
Vous allez bien sûr créer un formulaire pour chacun de ces exercices afin que cela soit bien dynamique.
3. Statistiques : On va créer un tableau avec des adresses e-mail. Le but est d'extraire le nom du serveur après le @. On calculera ensuite le pourcentage d'utilisation de chaque fournisseur. Par exemple :
Gmail : 40%
Outlook: 20%
Orange: 20%
Autre: 20%
BONUS: Générer une liste de checkboxes pour chaque fournisseur. On pourra choisir plusieurs fournisseurs et cliquer sur un bouton nous permettant de filtrer les emails par fournisseurs.
<input type="checkbox" name="filters[]" value="blue">
- Créer un formulaire avec un champ langages préférés. On pourra saisir "react, php, js".
- On affichera ensuite cette liste sous forme de ul li en ayant pris soin de transformer la chaine en tableau pour pouvoir le faire facilement.
- On vérifiera les erreurs, par exemple, si on saisit "php,,,js,,", on aura php et js qui seront dans la liste.
- Aussi, on pourra prévoir une liste de langages valides parmi lesquelles l'utilisateur pourra faire son choix. On s'assurera donc qu'il saisit bien un langage valide dans cette liste.
Créer un tableau contenant une liste de couleurs.
Afficher cette liste dans un formulaire avec des checkboxes. Chaque checkbox sera associée à une couleur.
L'utilisateur pourra choisir ses couleurs préférées. Au moment de la soumission du formulaire, on affichera la liste des couleurs choisies ainsi qu'un petit carré représentant la couleur.
<input type="checkbox" name="colors[]" value="blue">
On peut organiser notre code PHP dans plusieurs fichiers grâce aux inclusions.
<?php
/**
* Incluera le contenu de header.php
* Génère une erreur fatale en cas d'erreur
*/
// Exécute le contenu de header.php
require('header.php');
/**
* Incluera le contenu de header.php
* Déclenche un warning en cas d'erreur
*/
// Exécute le contenu de header.php
include('header.php');
<?php
/**
* Le fichier header.php
* ne sera inclus qu'à la premier instruction.
*/
include_once('header.php');
include_once('header.php');
include_once('header.php');
/*
PHP met à notre disposition des constantes magiques qui peuvent
s'avérer très utiles pour l'inclusion :
__DIR__ - Le répertoire du script actuel
__FILE__ - Le fichier du script actuel
__LINE__ - La ligne actuelle dans le script
__FUNCTION__ - Le nom de la fonction actuelle
*/
/**
* Inclus le fichier header.php qui se situe dans
* le même répértoire que celui du script
*/
// Permet d'éviter les ambiguités
include(__DIR__.'/header.php');
.
├── contact.php
├── index.php
└── partials
├── footer.php
└── header.php
2. index.php et contact.php doivent inclure header.php et footer.php
3. Intégrer la page HTML suivante en prenant soin de cleaner la page ou alors intégrer votre propre design avec Tailwind
4. Intégrer l'exercice des vidéos dans une page différente en intégrant la page au menu
5. Intégrer le formulaire de contact sur la page contact.php
Ici, je peux vous donner des exercices de révisions.
L'extension PHP Data Objects (PDO) est une interface permettant d'interroger différents types de base de données. Elle est généralement utilisée en interface pour MySQL.
On peut facilement switcher entre MySQL, sqlite, PostgreSQL sans modifier le code.
Performant et possède des méthodes permettant de se prémunir des injections SQL.
ATTENTION, ne plus utiliser mysql_connect()
PDO est une classe. Elle doit être instanciée. Son instance représente la connexion de PHP à la base de données.
<?php
// $db est un objet PDO
// Le premier paramètre est le DSN (Data Source Name)
$db = new PDO('mysql:host=localhost;dbname=ma_bdd', 'login', 'password');
// $query est un objet PDOStatement
$query = $db->query('SELECT * FROM ma_table');
// $result et $results sont des tableaux / objets
// contenant les colonnes en clé et les données en valeur
$result = $query->fetch(); // Renvoie une seule ligne de résultat
$results = $query->fetchAll(); // Renvoie toutes les lignes de résultat
Il peut y avoir des erreurs à la connexion. Dans ce cas, il faut vérifier la syntaxe ou alors que les accès soient corrects.
<?php
$db = new PDO('mysql:host=localhost;dbname=ma_bdd', 'login', 'password');
// [2002] php_network_getaddresses: getaddrinfo failed: Hôte inconnu
// [2002] Une tentative de connexion a échoué car le parti connecté
// n'a pas répondu convenablement au-delà d'une certaine durée
// ou une connexion établie a échoué car l'hôte de connexion n'a pas répondu.
// Erreur de couple login/mot de passe
// [1045] Access denied for user 'root'@'localhost' (using password: YES)
// La base de données n'existe pas
// [1049] Unknown database 'db_name'
<?php
$db = new PDO('mysql:host=localhost;dbname=ma_bdd', 'login', 'password', [
// On récupère tous les résultats en tableau associatif
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$db->query('SELECT * FROM table');
// Renvoie toutes les lignes de résultat
// sous forme d'un tableau indexé numériquement
$db->fetch(PDO::FETCH_NUM);
// PDO::FETCH_BOTH Renvoie toutes les lignes de résultat
// sous forme d'un tableau associatif
On peut modifier la façon dont on récupère les résultats.
L'injection SQL consiste à profiter d'une faille dans les paramètres d'une requête SQL afin d'exécuter un code malicieux.
<?php
$id = $_GET['id']; // Si je passe "10; DROP DATABASE site;" dans l'url ??
$query = $db->query('SELECT * FROM user WHERE id = '.$id);
// La requête sera SELECT * FROM user WHERE id = 10; DROP DATABASE site;
$user = $query->fetch();
On peut utiliser une requête préparée afin de se prémunir des injections SQL.
<?php
$id = intval($_GET['id']);
// Si je passe "10; DROP DATABASE site;" dans l'url,
// cela renverra 10
$query = $db->query('SELECT * FROM user WHERE id = '.$id);
// La requête sera SELECT * FROM user WHERE id = 10
$user = $query->fetch();
// C'est mieux, mais il vaut mieux utiliser une requête préparée.
$search = $_GET['q'];
$query = $db->prepare('SELECT * FROM articles WHERE title LIKE :search');
// Si je passe "chaine; DROP DATABASE site" dans la requête
// SELECT * FROM articles WHERE title LIKE "chaine; DROP DATABASE site"
$query->bindValue(':search', $search, PDO::PARAM_STR);
$query->execute(); // Ne pas oublier d'exécuter la requête
$search_results = $query->fetchAll();
L'attaque XSS (cross-site scripting) consiste à injecter du contenu dans une page comme une balise script par exemple.
<?php
$pseudo = $_POST['pseudo'];
$query = $db->prepare('INSERT INTO
user (pseudo)
VALUES (:pseudo)');
$query->bindValue(':pseudo', $pseudo, PDO::PARAM_STR);
$query->execute();
?>
<form method="POST">
<input name="pseudo" type="text" />
<input type="submit" value="OK" />
</form>
<?php
$query = $db->query('SELECT pseudo FROM user');
$users = $query->fetchAll();
foreach($users as $user) {
echo $user['pseudo'].'<br>';
}
Que se passe t-il si j'ajoute un utilisateur avec le pseudo <script>location.href = 'http://www.google.fr';</script>
Pour se prémunir des attaques XSS, il faut utiliser des fonctions PHP
<?php
// Supprime toutes les balises HTML
$pseudo = strip_tags($_POST['pseudo']);
// Remplace tous les caractères HTML par leur équivalent en HTML entities
$pseudo = htmlspecialchars($_POST['pseudo']);
$query = $db->prepare('INSERT INTO
user (pseudo)
VALUES (:pseudo)');
$query->bindValue(':pseudo', $pseudo, PDO::PARAM_STR);
$query->execute();
?>
<form method="POST">
<input name="pseudo" type="text" />
<input type="submit" value="OK" />
</form>
<?php
$query = $db->prepare('SELECT * FROM ma_table WHERE id = :id');
// La méthode bindValue() permet de transmettre la valeur
// des paramètres d'une requête préparée.
// Elle prends 3 arguments
// Le nom du paramètre sous la forme :nom;
// La valeur du paramètre qui peut être une chaîne ou un nombre;
// Le type du paramètre
// (PDO::PARAM_STR pour les chaînes, PDO::PARAM_INT pour les nombres);
$query->bindValue(':id', 42, PDO::PARAM_INT);
// Avec des points d'intérrogation
// $query = $db->prepare('SELECT * FROM ma_table WHERE id = ?');
// $query->bindValue(1, 42, PDO::PARAM_INT);
$query->execute();
$result = $query->fetch();
// bindParam() est similaire mais permet de gagner en performances
// quand on a de nombreuses requêtes. le paramètre est lié à une
// variable qui n'est pas encore définie.
$query = $db->prepare('INSERT INTO ma_table SET param = :param');
$query->bindParam(':param', $param, PDO::PARAM_INT);
for($i = 0; $i < 10; $i++) {
$param = $i;
$query->execute();
}
<?php
// Insertion
$query = $db->prepare('INSERT INTO ma_table (col1, col2) VALUES (:value1, :value2)');
$query->bindValue(':value1', 123, PDO::PARAM_INT);
$query->bindValue(':value2', 'test', PDO::PARAM_STR);
$query->execute();
// Retourne l'identifiant inséré par la requête
// (nécessite une clé primaire en AUTO_INCREMENT)
$insert_id = $db->lastInsertId();
// Update
$query = $db->prepare('UPDATE ma_table SET col = :value WHERE id = :id');
$query->bindValue(':value', 'test', PDO::PARAM_STR);
$query->bindValue(':id', 123, PDO::PARAM_INT);
$query->execute();
// Retourne le nombre de lignes affectées par la requête
$affected_rows = $query->rowCount();
// Suppression
$query = $db->prepare('DELETE FROM ma_table WHERE id = :id');
$query->bindValue(':id', 123, PDO::PARAM_INT);
$query->execute();
// Retourne le nombre de lignes affectées par la requête
$affected_rows = $query->rowCount();
Ici, je peux vous donner un exercice sur des produits dans une base de données.
Nous savons envoyer des données en $_POST. Il est également possible d'envoyer des fichiers.
Un formulaire d'upload doit absolument contenir l'attribut "enctype".
<!-- L'attribut enctype doit absolument être "multipart/form-data",
et la méthode doit être POST -->
<form method="POST" enctype="multipart/form-data">
<!-- La balise input type="file" permet de
sélectionner un fichier sur son ordinateur.
Ne pas oublier l'attribut "name"-->
<input type="file" name="picture" />
<input type="submit" value="Envoyer"/>
</form>
Le fichier uploadé n'est pas disponible dans $_POST mais dans $_FILES.
Array
(
[picture] => Array
(
// Nom original du fichier uploadé via le formulaire
[name] => MonFichierOriginal.jpg
// Type "mime" du fichier, plus fiable que l'extension
[type] => image/jpg
// Chemin complet du fichier uploadé sur le serveur
[tmp_name] => chemin_complet_du_fichier_sur_le_serveur
// Un code d'erreur si il y a une erreur, 0 si tout est ok
[error] => 0
// La taille en octets du fichier uploadé
[size] => 1000
)
)
// Retourne l'emplacement du fichier
$file = $_FILES['picture']['tmp_name'];
// Récupère le nom du fichier
$originalName = $_FILES['picture']['name'];
// Récupère l'extension du fichier
$extension = pathinfo($originalName)['extension'];
// Renomme le fichier
$md5 = md5($originalName.uniqid());
$filename = $md5.'.'.$extension;
// Déplace le fichier vers un répertoire
move_uploaded_file($file, __DIR__.'/upload/'.$filename);
Renommer le fichier peut être pratique afin d'être sûr qu'il porte un nom propre et unique.
1. Nous allons créer un formulaire de candidature à une offre d'emploi
2. L'utilisateur pourra envoyer son CV en PDF. On devra vérifier le format et la taille (limité à 5mo).
3. L'utilisateur pourra saisir son prénom, on renommera le CV avec son prénom hashé en MD5 et une partie unique.
4. On stockera les utilisateurs dans une base de données avec le chemin du CV.
5. BONUS : On ajoutera l'upload d'une image. Idéalement, on essaiera de redimensionner l'image en 300x300 dans un nouveau fichier. Voir l'extension GD.
// Ouvre le fichier comme dans Word et retourne une ressource
$file = fopen('C:/xampp/htdocs/', 'r');
// Le 2ème paramètre est le mode de lecture
// "r" : "read-only". Ouvre le ficher en lecture seule.
// "r+" : Ouvre le ficher pour l'écriture et la lecture.
// (un "niveau" de plus que "r")
// "w+" : "write". Ouvre le fichier pour l'écriture et la lecture, et
// commence par le vider. Si le fichier n'existe pas, PHP tente de le créer.
// "a" : "append". Ouvre le fichier en écriture seule
// (voir un exemple plus loin), et place le pointeur à la fin du fichier.
// "a+" : "append". Ouvre le fichier en lecture et écriture,
// et place le pointeur à la fin du fichier.
PHP est un langage côté serveur et permet donc de manipuler des fichiers.
// Ouvrir le fichier et placer le pointeur (le curseur) à la fin
$fileHandler = fopen('/var/log/db.log', 'a+');
// Ajouter une ligne pour la date d'aujourd'hui
$text = date('Y-m-d H:i:s') . ' : Une connexion a été ouverte';
fwrite($fileHandler, $text);
// Ouvrir le fichier et placer le pointeur au début
$fileHandler = fopen('/var/log/db.log', 'r');
// Enregistrer les dix premiers caractères
$start = fread($fileHandler, 10);
// Afficher le résultat
echo $start;
// On peut/doit fermer le fichier comme on ferme Word
fclose($fileHandler);
// Le code suivant récupère, dans la variable $fileContent,
// la totalité du fichier log.txt.
$fileContent = file_get_contents('log.txt');
// Le code suivant va enregistrer dans la variable $fileSection
// les dix caractères à la vingtième position du fichier
// log.txt (les caractères 20 à 29) :
$fileSection = file_get_contents('log.txt', NULL, NULL, 20, 10);
// Récupère l'intégralité du contenu du fichier
$fruits = file_get_contents('fruits.txt');
// Ajoute une ligne
$fruits .= "Cerise\n";
// Écrit le contenu mis à jour dans le fichier ouvert
file_put_contents('fruits.txt', $fruits);
// Pour ajouter le contenu dans un fichier existant
file_put_contents('fruits.txt', 'Fraise'.PHP_EOL, FILE_APPEND);
// Supprime le fichier
unlink('log.txt');
// Vérifie qu'un fichier existe
file_exists('./img/image.jpg');
// Modifie les permissions d'un fichier
chmod('./img/image.jpg', 0644);
// Vérifie qu'un dossier existe
is_dir('/var/site/img');
// Copie un fichier
copy('./img/image.jpg', './backup/img/image.jpg');
// Déplace ou renomme un fichier
rename('./img.png', '../img.png');
// Donne le nom du dossier d'un fichier
dirname(__FILE__) === __DIR__;
// Date de dernière modification d'un fichier
$filename = 'access.log';
if (file_exists($filename)) {
echo "$filename a été modifié le " . date('d/m/Y à H:i:s.', filemtime($filename));
}
1. Nous allons créer un formulaire de commande Click'N'Collect
2. L'utilisateur pourra saisir son email et choisir parmi une liste de produits (Select, checkbox ou textarea)
3. On stockera l'ensemble des demandes dans un fichier .txt que le vendeur pourra retrouver avec la date et l'heure
4. Idéalement, on créera une page pour le vendeur afin qu'il puisse retrouver l'ensemble des commandes (en lisant le contenu du fichier)
5. Chaque produit sera associé à un prix, sur la page du vendeur, on affichera donc le total de toutes les commandes.
1. Nous allons créer un système de compteur
2. A chaque visite sur le site, on va stocker une valeur dans un fichier
3. A chaque nouvelle visite, on devra incrémenter la valeur actuelle du fichier de +1
4. On affichera bien sûr le nombre de visites sur le site
5. Si le visiteur est le 10ème, on affichera un petit message pour le féliciter
// Spaceship operator : <=>
// 0 si les deux opérandes sont égaux,
// 1 si celui de gauche est plus grand
// -1 si celui de droite est plus grand.
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
// Null coalesce operator : ??
// Avant
$var = isset($maVar) ? $maVar : 'valeur_par_defaut';
// Après
$var = $maVar ?? 'valeur_par_defaut';
// Si cette ligne est présente, les paramètres ne sont
// pas convertis vers le type attendu et cela
// retourne une erreur fatale
declare(strict_types=1);
// On spécifie que $nom doit être un string et $age un integer
function identite(string $nom, int $age) {
echo $nom. 'a '.$age.' ans';
}
// On spécifie que la valeur du retour doit être un bolean.
function isAdult(int $age) : bool {
return $age >= 18;
}
PHP 7 ne lève que des exceptions lors des erreurs, ce qui permet de catcher plus facilement les erreurs pour le développeur.
Les fonctions en mysql_* n'existent plus (extension mysql).
Les fonctions en ereg_* n'existent plus (extension ereg).
mcrypt est déprécié en 7.1 et supprimé en 7.2
Chaque utilisateur d'un site se voit attribué une session avec un identifiant unique. Les sessions expirent après quelques minutes.
// Pour utiliser les sessions
session_start();
// Affiche toutes les informations dans la session courante
print_r($_SESSION);
// Enregistrer une valeur en session
$_SESSION['ma_cle_tableau'] = 'toto';
// Accèder à une variable de session
echo $_SESSION['ma_cle_tableau'];
// Supprimer une variable de session
unset($_SESSION['ma_cle_tableau']);
// Redirection
header('Location: index.php');
Un cookie est une session qui est stockée sur la machine de l'utilisateur. Le cookie peut avoir une durée d'expiration.
// Pour créer un cookie qui expire dans 1 heure
setcookie('fiofio', 'Ici, je peux stocker des trucs', time() + 3600);
// Affiche tous les cookies du site
print_r($_COOKIE);
// Accèder à la valeur d'un cookie
echo $_COOKIE['fiofio'];
// Supprimer un cookie
setcookie('fiofio', '', 1);
1. Créer un formulaire (index.php) où on demande à l'utilisateur login et mot de passe.
2. Si l'utilisateur se connecte avec admin et admin, c'est correct sinon on renvoie une erreur.
3. Quand il est connecté, on le redirige vers une page connecte.php en ayant au préalable enregistré une valeur en session.
4. On ajoute un bouton "Déconnexion" qui doit supprimer une variable de session pour déconnecter et rediriger l'utilisateur vers la page de connexion.
5. Créer un nouveau formulaire d'inscription (Login et mot de passe)
6. On ajoutera l'utilisateur dans une table. On doit stocker le login et le mot de passe. L'utilisateur devra confirmer son mdp. On affichera les erreurs ou un succès.
7. Dans le formulaire de login, on peut maintenant vérifier que la personne qui se connecte est présente dans la table grâce à un SELECT et un WHERE.
8. Si la personne est présente avec le bon mot de passe, elle se connecte.
9. Vérifier qu'un utilisateur ne puisse pas s'inscrire 2 fois avec le même pseudo.
10. On voudrait avoir le formulaire de login et d'inscription sur la même page. Comment pourrait-on faire ?
11. Pour plus de sécurité, on devra hasher le mot de passe à l'inscription. Cela veut dire qu'il faudra comparer le hash lors du login pour voir s'il correspond au mot de passe saisi.
12. Ajouter une case "Se rappeler de moi", au moment du login, on doit créer un cookie valide 1 année. A chaque requête sur le PHP, on regarde si un cookie est présent mais que la session est vide. Dans ce cas, on connecte l'utilisateur du cookie automatiquement. Attention aux failles de sécurité.
// Hachage de mot de passe selon l'algorithme bcrypt
$password = password_hash('toto', PASSWORD_DEFAULT);
// Retourne true ou false si correspondance
password_verify('toto', $password);
On ne conserve jamais les mots de passe en clair dans une base de données. On préférera les hacher.
On ne doit pas pouvoir retrouver le mot de passe. On doit comparer le vrai mot de passe et vérifier s'il y a une correspondance avec un hash.
NE PAS UTILISER md5 OU sha1
Gestionnaire de dépendances PHP
Déclarer et installer les bibliothèques/frameworks nécessaires pour un projet PHP
Equivalent de NPM pour le PHP
Mettre à jour facilement les librairies
S'assurer que tout le monde utilise la même version
Pour installer Composer : https://getcomposer.org/doc/00-intro.md
Composer s'utilise en CLI
# Je me déplace dans mon projet
cd monProjet/
# J'initialise composer comme pour git
composer init
# On répond aux différentes questions
# nous permettant de configurer notre projet
Le fichier composer.json définit notre projet ainsi que ses dépendances.
On peut trouver des dépendances sur Packagist.org
{
"name": "matthieu/test",
"authors": [
{
"name": "Matthieu Mota",
"email": "matthieumota@gmail.com"
}
],
"require": {
"fzaninotto/faker": "^1.7"
}
}
Pour gérer nos dépendances
# La commande va modifier notre fichier composer.json
# et installer la dépendance
composer require swiftmailer/swiftmailer
# Un dossier vendor sera crée, il possède toutes nos dépendances
# Si on récupére un projet existant,
# on peut installer les dépendances
# composer partira du fichier composer.lock
# qui vérouille les versions à installer
composer install
# Pour mettre à jour les dépendances sans
# se soucier du composer.lock
composer update
# On peut également modifier la section
# require du fichier composer.json et lancer
composer update
"require": {
"vendor/package": "1.3.2", // seulement la 1.3.2
// >, <, >=, <=
"vendor/package": ">=1.3.2", // supérieur ou égal à 1.3.2
"vendor/package": "<1.3.2", // inférieur à 1.3.2
// *
"vendor/package": "1.3.*", // >=1.3.0 <1.4.0
// ~
"vendor/package": "~1.3.2", // >=1.3.2 <1.4.0
"vendor/package": "~1.3", // >=1.3.0 <2.0.0
// ^
"vendor/package": "^1.3.2", // >=1.3.2 <2.0.0
"vendor/package": "^0.3.2", // >=0.3.2 <0.4.0 // sauf si la version majeur est 0
}
Possibilité d'utiliser des contraintes sur l'utilisation de versions
Pour tester : https://semver.mwl.be