Paul Schuhmacher
Durée : 28h
Novembre 2025
Objectif : mise à niveau sur PHP moderne, orienté WordPress.
à parcourir en fonction des compétences à consolider.
PHP Data Objects (PDO));mod_php, déprécié), PHP-FPM, FrankenPHP) le petit nouveau
!php -v pour afficher la version de PHP
et du Zend Engine;PHP fait tourner le web mondial !
Suivre les instructions donnĂ©es sur le site officiel en fonction de lâOS de votre machine.
Créer un fichier hello-world.php :
hello, worldExécuter :
php hello-world.phpPeu importe son environnement dâexĂ©cution, le code PHP est
toujours inclus entre une balise de ouvrante
<?php et une balise fermante
?>.
Voici le contenu dâun fichier index.php :
<html>
<body>
<?php echo "hello, world"; ?>
</body>
</html><?php echo "hello, world"; ?>index.php :php index.phpUn script PHP sâexĂ©cute sur la machine virtuelle ou interprĂ©teur PHP. Il analyse le fichier byte par byte :
<?php), il passe en mode
interprĂ©tation. Le code jusquâĂ la balise fermante
(?>) est alors :
Grùce à son fonctionnement, PHP est parfaitement adapté pour fabriquer des documents dynamiques (contenu dynamique placé entre balises PHP) : pages HTML, fichiers texte ASCII, PDF, JSON, XML, email, etc.
Il existe des SAPI PHP comme PHP-FPM ou FrankenPHP qui permettent dâoptimiser lâusage des processus PHP (pool de processus workers qui restent en mĂ©moire pour traiter plusieurs requĂȘtes successives), mais ces principes restent valables. Il est Ă©galement possible de faire du multithread (CLI) avec les extensions pthreads ou parallel
La machine virtuelle PHP, dont le cĆur est le Zend Engine, est conçue pour ĂȘtre Ă la fois hautement configurable, modulaire et adaptable Ă diffĂ©rents contextes dâexĂ©cution.
La machine virtuelle PHP :
php -i pour afficher configuration actuelle);php -m pour lister
modules installés);mod_php), déprécié,
- Quel fichier ini est utilisé par PHP ? Cela dépend justement de la SAPI !
Via des fichiers php.ini (format INI)
Dans un script, utiliser la fonction phpinfo() :
<?php
//Afficher toute la configuration de PHP. TrÚs pratique pour voir les fichiers ini chargés notamment.
phpinfo();Ou directement depuis lâinvite de commandes :
# Affiche toutes les informations (Ă grep)
php -i
# N'afficher que les fichiers de config chargés
php -i | grep -A15 "Configuration File (php.ini)"
# Avec PHP 8.5 ! Affiche directement les valeurs qui diffÚrent des valeurs par défaut.
php --ini
php --ini=diffIl existe de nombreuses directives pour configurer PHP Ă lâexĂ©cution, utiles notamment pour adapter le comportement en fonction des environnements.
Exécuter directement le script dans le terminal :
php votre-script.phpLa sortie (résultat) sera affichée dans le terminal.
Exécuter directement le script dans le terminal, en chargeant une configuration personnalisée :
php -c mon-fichier.ini votre-script.phpPHP dispose dâun serveur web intĂ©grĂ©, utilisant la SAPI CLI.
cd chemin/des/fichiers/php/a/servir
#Servir tout le contenu du dossier courant (attend un fichier index.php !)
php -S localhost:3000
#Servir uniquement le fichier 'monsite.php'
php -S localhost:3000 monsite.php
#Servir tout le dossier 'public'
php -S localhost:3000 -t publicLe serveur web intégré de PHP attend un fichier
index.phppar dĂ©faut. Penser Ă toujours utiliser un fichierindex.phpcomme point dâentrĂ©e de votre site web.
public;public, créer un
fichier index.php avec le contenu suivant :<?php
echo "<h1>Hello, world</h1>";php -S localhost:8000 -t publichttp://localhost:8000. Vous devriez voir le résultat de
lâexecution du script index.php.//La variable $message contient la valeur 'hello, world' de type 'string'
$string = 'hello, world';
//Un tableau (collection de valeurs)
$array = [1, 2, 3];
$int = 12;
$float = -1.5;
$_nom_variable_ok = 'foo';
$_123 = 'ok';
$12 = 'NON'; //Erreur
$a = 1;
$A = 2; // $a != $AEn PHP, il nâest pas nĂ©cessaire de dĂ©finir le type de donnĂ©es rĂ©fĂ©rencĂ©e par une variable, le type est dĂ©duit au runtime :
$message = "hello, world";
//Imprimer la valeur de la variable sur la sortie
echo $message;
//Imprimer le type de la variable sur la sortie
echo gettype($message);
echo gettype(new stdClass());
//Afficher le contenu et le type de la variable $message
var_dump($message);Types principaux : boolean, int,
double, string, array,
object, NULL (absence de valeur),
callable.
Voir tous les types du langage PHP.
Pour inspecter le contenu dâune variable et/ou son type. Utile pour dĂ©buger et comprendre son programme et regarder les valeurs
$name = "Jane Doe";
//Ecrire la valeur sur la sortie
echo $name;
$grades = [12, 10, 19, 8];
//Imprimer le contenu d'un tableau de maniĂšre lisible
print_r($grades);
//Imprimer le contenu de n'importe quelle variable et des types des données
var_dump($grades);
// Une variable non initialisée et non référencée (pas de contexte d'utilisation).
var_dump($unset_var);AND et OR
:
Pas les mĂȘmes prĂ©cĂ©dences que && et
|| !
PrĂ©cĂ©dence plus faible que les opĂ©rateurs dâaffectation !
$c = 8 > 5 AND 15 < 10 //$c vaut true ! car évalué comme ($c = (8 > 5)) AND (15 < 10)$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';Comparaison :
== : (essaie de convertir dans mĂȘme type et)
compare les valeurs;=== : compare les valeurs et
les types.<?php
//Opérateurs de comparaison :
//@link https://www.php.net/manual/en/language.operators.comparison.php
if(1 == '1') echo "1 == '1' est VRAI" . PHP_EOL;
if(1 === '1') echo "1 === '1' est FAUX" . PHP_EOL;
if(1 !== '1') echo "1 !== '1' est VRAI". PHP_EOL;Comme dans tout language implémentant le standard IEEE 754 double precision format, ne jamais comparer les nombres flottants directement, définir et utiliser une précision :
<?php
$a = 1.23456789;
$b = 1.23456780;
//Précision
$epsilon = 0.00001;
if (abs($a - $b) < $epsilon) {
echo "$a est égal à $b";
}Pour créer des constantes, utiliser la primitive
define() :
define("CONSTANT", "Hello world.");
echo CONSTANT; // outputs "Hello world."
define('ANIMALS', [
'dog',
'cat',
'bird'
]);
echo ANIMALS[1]; // outputs "cat"
//Afficher toutes les constantes définies et accessibles dans ce script
print_r(get_defined_constants());Par convention, écrire les constantes en
MAJUSCULES. On utilisera souvent des constantes en WordPress, notamment pour la configuration.
for #<?php
//Boucle for
for($i = 0 ; $i < 100; $i = $i + 1){
//Embranchement avec if/else (tests)
if($i % 2 === 0){
echo "$i est un nombre pair";
}else{
echo "$i est un nombre impair";
}
}for syntaxe alternative #<?php
//Boucle for
for($i = 0 ; $i < 100; $i = $i + 1) :
//Embranchement avec if/else (tests)
if($i % 2 === 0) :
echo "$i est un nombre pair";
else :
echo "$i est un nombre impair";
endif;
endfor;Sera utile dans les templates
while et do-while #<?php
$i = 1;
while ($i <= 10) {
echo $i++; /* La valeur affichée est $i avant l'incrémentation
(post-incrémentation) */
}
$i = 0;
do {
echo $i;
} while ($i > 0);while et do-while, syntaxe
alternative #<?php
$i = 1;
while ($i <= 10) :
echo $i++;
endwhile;switch #Alternative Ă if/else si les cas sont Ă©numĂ©rables et tests dâĂ©galitĂ© uniquement
<?php
$i=2;
switch ($i) {
case 0:
echo "i égal 0";
break;
case 1:
echo "i égal 1";
break;
case 2:
echo "i égal 2";
break;
}$i=2;
switch ($i) :
case 0:
echo "i égal 0";
break;
case 1:
echo "i égal 1";
break;
case 2:
echo "i égal 2";
break;
endswitch;goto a du sens en bas
niveau)<?php
for ($i = 0, $j = 50; $i < 100; $i++) {
while ($j--) {
if ($j == 17) {
goto hell;
}
}
}
echo "i = $i";
hell:
echo 'j hit 17';Le classique Edgar Dijkstra: Go To Statement Considered Harmful : âIâve been familiar with the observation that the quality of programmers is a decreasing function of the density of
gotostatements in the programs they produceâ. xkcd: goto
foreach #Syntaxe commode pour parcourir des collections dâitems. Permet de se passer ou non de lâindex.
<?php
$arr = array(1, 2, 3, 4);
//Parcourir un tableau en parcourant uniquement ses valeurs
foreach ($arr as $value) {
//$value est une variable locale Ă la boucle qui contiendra chaque valeur du tableau (1, puis 2, puis 3, puis 4)
//a chaque itération
echo $value . PHP_EOL;
}
//Parcourir un tableau en parcourant ses clefs ET ses valeurs
//Syntaxe: foreach(tableau as clef => valeur)
foreach($arr as $index => $value){
echo "Index: {$index} - Value: {$value} \n";
}foreach syntaxe alternative #Les accolades ouvrantes et fermantes de la boucle
sont remplacées par le caractÚre
: et par une instruction endforeach; :
<?php
$arr = array(1, 2, 3, 4);
// foreach($arr as $index => $value){
// echo "Index: {$index} - Value: {$value} \n";
// }
foreach($arr as $index => $value) :
echo "Index: {$index} - Value: {$value} \n";
endforeach;Lorsque lâon utilise PHP pour gĂ©nĂ©rer des pages HTML, les structures de contrĂŽle usuelles offrent une syntaxe alternative afin dâamĂ©liorer (un peu) la lisibilitĂ© du code source de la page.
Prenons un exemple :
//Tout le HTML est généré depuis un code PHP
$items = ['pomme', 'poire', 'raisin'];
echo "<ul>";
foreach($items as $item){
echo "<li>$item</li>";
}
echo "</ul>";Dans un template, le script PHP est essentiellement constitué de texte (code HTML), le contenu est donc imprimé sur la sortie sans modification. Les balises PHP ne sont ouvertes que pour insérer des données dynamiques :
//Le HTML est écrit directement, PHP est seulement utilisé pour les données dynamiques
<?php $items = ['pomme', 'poire', 'raisin']; ?>
<ul>
<?php foreach ($items as $item) { ?>
<li><?php echo $item ?></li>
<?php } ?>
</ul>Syntaxe encore difficile Ă lireâŠ
Idem que précédemment mais avec la syntaxe
alternative pour le foreach :
//Le HTML est écrit directement, PHP est seulement utilisé pour les données dynamiques
<?php $items = ['pomme', 'poire', 'raisin']; ?>
<ul>
<?php foreach ($items as $item) :?>
<li><?php echo $item ?></li>
<?php endforeach; ?>
</ul>Cette syntaxe alternative est plus lisible, notamment lorsque les pages à générer deviennent plus complexes.
La structure de contrĂŽle if/else (embranchement) dispose
Ă©galement dâune syntaxe alternative.
<?php $items = ['pomme', 'poire', 'raisin']; ?>
//...
//Syntaxe alternative pour le if
<?php if(!empty($items)) : ?>
<!-- Cette balise sera écrite dans la page web si la liste d'items n'est pas vide -->
<p> Il y a des items dans la liste ! </p>
<?php else : ?>
<!-- Cette balise sera écrite dans la page web sinon -->
<p> La liste est vide ! </p>
<?php endif; ?>Exemple dâusage de la syntaxe alternative pour la Loop Wordpress, un pattern au coeur du fonctionnement du CMS :
//Script PHP en charge d'afficher la liste des articles, d'un article, etc.
//S'il y a des posts (articles), parcourir les posts (while)
//Affiche le contenu du post (the_content()) et passer au post suivant (the_post())
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<div class="entry">
<?php the_content(); ?>
</div>
<?php endwhile;?>En interne, une chaßne PHP est stockée dans une structure
(zval) qui contient :
Pour connaĂźtre la taille dâune chaĂźne, il faut utiliser la fonction
strlen().
Attention, strlen ne retourne pas le nombre de
caractĂšres mais le nombre de bytes !
echo strlen('foo') . PHP_EOL; // 3
echo strlen('été') . PHP_EOL; // 5\', \\ :,` ;\n, \t,
\", etc."$var",
"{$var}s").<?php
echo 'ceci est une chaĂźne simple';
//Concaténation de chaßne avec l'opérateur .
//PHP_EOL est une constante fournie pour demander le caractĂšre fin de ligne
echo 'ceci est une chaĂźne simple' . PHP_EOL;
echo 'Vous pouvez également ajouter des nouvelles lignes
dans vos chaĂźnes
de cette façon';
// Ăchapper des caractĂšres spĂ©ciaux avec l'anti-slash (ici la guillemet simple avec \') pour signifier qu'il fait partie de la chaĂźne
echo 'Arnold a dit : "I\'ll be back"';
echo 'Voulez-vous supprimer C:\*.*?';
echo 'Ceci n\'affichera pas \n de nouvelle ligne';$firstName = "Jane";
$lastName = "Doe";
//Les simples quotes ne permettent aucune interprétation de caractÚres spéciaux dans la chaßne
echo 'La variable $firstName ne sera pas interpolée ici (remplacée par sa valeur)\n';
//Les doubles quotes effectuent des interprétations de caractÚres spéciaux (interpolation, caractÚre de retour à la ligne, etc.)
echo "La variable $firstName sera interpolée ici (remplacée par sa valeur)\n";
echo "Les variables {$firstName}/{$lastName} seront interpolées ici (remplacée par sa valeur)\n";Une autre façon de délimiter une chaßne de caractÚres est la
syntaxe Heredoc : <<<.
AprÚs cet opérateur, un identifiant est fourni (ici
END), suivi dâune nouvelle ligne. La chaĂźne de caractĂšres
en elle-mĂȘme vient ensuite, suivie du mĂȘme identifiant pour fermer la
notation.
<?php
// no indentation
echo <<<END
$$$$$$$\ $$\ $$\ $$$$$$$\
$$ __$$\ $$ | $$ |$$ __$$\
$$ | $$ |$$ | $$ |$$ | $$ |
$$$$$$$ |$$$$$$$$ |$$$$$$$ |
$$ ____/ $$ __$$ |$$ ____/
$$ | $$ | $$ |$$ |
$$ | $$ | $$ |$$ |
\__| \__| \__|\__|
END;Peut ĂȘtre utile dans des templates !
$text= <<<FOOBAR
Ă dâautres la satiĂ©tĂ© ! Toi, tu gardes ton dĂ©sir, dĂ©sir exubĂ©rant qui dĂ©borde toujours : moi qui te poursuis sans cesse, je viens par-dessus le marchĂ© faire addition Ă tes tendres caprices.
Toi, dont le désir est si large et si spacieux, ne daigneras-tu pas une fois absorber mon désir dans le tien ? Ton désir sera-t-il toujours si gracieux aux autres sans jeter sur mon désir un rayon de consentement ?
La mer, qui est toute eau, reçoit pourtant la pluie encore, et ajoute abondamment à ses réservoirs : ainsi toi, riche de désir, ajoute à tes désirs la goutte du mien, et élargis ton caprice.
Ne te laisse pas accabler par tant de tentations, bonnes ou mauvaises : confonds-les toutes en une, et aime Will dans ce désir unique (1).
FOOBAR;
echo $text;Voir la liste des fonctions natives de PHP pour manipuler les chaines
<?php
$x = 'bOnJoUr eT bIeNvEnUe';
echo "<p><code>strtolower('$x')</code> = " . strtolower($x) . '<br>';
echo "<code>strtoupper('$x')</code> = " . strtoupper($x) . '<br>';
echo "<code>ucwords('$x')</code> = " . ucwords($x) . '<br>';
echo "<code>ucwords(strtolower('$x'))</code> = " . ucwords(strtolower($x)) . ' </p>';
echo '<h5>Mise en forme avec <a href="http://php.net/manual/fr/function.sprintf.php"><code>sprintf()</code></a></h5>';
echo '<p>Mise en forme d\'une date : ' . sprintf('%02d/%02d/%04d', 1, 1, 1981) . '</p>';
echo '<h5>Mise en forme avec <a href="http://php.net/manual/fr/function.number-format.php"><code>number_format()</code></a></h5>';
$x = 1234.567;
echo "<p><code>number_format($x)</code> = " . number_format($x) . '<br>';
echo "<code>number_format($x,1)</code> = " . number_format($x, 1) . '<br>';
echo "<code>number_format($x,2,',',' ')</code> = " . number_format($x, 2, ',', ' ') . '</p>';
$mail = "paul.schuhmacher@monmail.fr";
$position = strpos($mail, 'h');
if ($position === false) {
echo "'h' est introuvable dans $mail<br>";
} else {
echo "'h' est Ă la position $position dans $mail<br>";
}
echo '<p><code>mktime(0, 0, 0, 1, 1, 1970)</code> = ' . mktime(0, 0, 0, 1, 1, 1970) . '</p>';
echo '<p>Création du timestamp pour le 4 octobre 2017 à 11h45 et 30 secondes<br>';
echo '<code>mktime(11, 45, 30, 4, 10, 2017)</code> = ' . mktime(11, 45, 30, 4, 10, 2017) . '</p>';
echo '<p><code>date("d/m/Y H:i:s", mktime(11, 45, 30, 4, 10, 2017))</code> = ' . date("d/m/Y H:i:s", mktime(11, 45, 30, 4, 10, 2017)) . '<br>';
echo 'Unix a fĂȘtĂ© sa milliardiĂšme seconde le ' . date("d/m/Y H:i:s", 1000000000) . '</p>';
echo '<p>Date du jour au format JJ/MM/AAAA : ' . date('d/m/Y') . '<p>';En PHP, une chaĂźne est une sĂ©quence de bytes, pas une sĂ©quence de caractĂšres. Avec lâencodage UTF-8, certains caractĂšres occupent plusieurs octets.
strlen() : compte les bytes;mb_strlen() : compte les caractĂšres
(si lâencodage est bien dĂ©fini).echo strlen("Ă©tĂ©") . PHP_EOL; // 5
echo mb_strlen("été") . PHP_EOL; //3
echo mb_internal_encoding(); // UTF-8, par dĂ©fautEncode par dĂ©faut UTF-8. Les chaines sont juste des bytes, il faut donc dĂ©finir/vĂ©rifier lâencodage pour les interprĂ©ter correctement ! En savoir plus
La documentation officielle de PHP est de qualité.
Par exemple, regardons la doc de la fonction mb_strlen :
//Signature
mb_strlen(string $string, ?string $encoding = null): intIgnorer la section
User Contributed Notes! DĂ©suĂšte, dĂ©prĂ©ciĂ©e, inutile. Va ĂȘtre supprimĂ©e dans la mise Ă jour prochaine du site web.
Dans VS Code, installer lâextension PHPIntelephense pour avoir accĂšs Ă la doc directement depuis lâIDE !
Les tableaux PHP sont trĂšs (trop !) puissants et versatiles :
0 par
défaut et utilise des entiers comme index ;count().<?php
$array = [1, 2, 3, ['a','b','c']];
//Retour Ă la ligne + nouvelle ligne (pour le terminal)
echo "Taille du tableau : " . count($array) . PHP_EOL;
//Ajouter un élément
$array[] = 4;
//Ajoute des éléments à la fin
array_push($array, 5, 6, 7);
//Ajoute des éléments au début
array_unshift($array, -1, 0);
print_r($array);
//Supprimer une clef et sa valeur !
unset($array[0]);
print_r($array);En PHP, un tableau est en réalité une map, collection de clé/valeurs, la fonction la plus fondamentale !
<?php
//Map, la fonction la plus fondamentale (application)
$square = [
0 => 0,
1 => 1,
2 => 4,
3 => 9
];
//Tableau : on déclare les clefs et les valeurs (slug => title)
$posts = [
'hello-world' => "Hello World !",
'how-to-use-array' => "How to" ,
];
foreach($posts as $slug => $title){
//Equivalent "string literals" avec les back tick en JS
echo "Slug: {$slug} - Title: {$title} \n";
}
//Ajouter un élément
$posts['add-element'] = "Ajouter un élément à un tableau PHP";
//Supprimer un élément
unset($posts['hello-world']);in_array,
implode,
explode,
array_map,
array_filter, array_reduce,
shuffle, sort, array_find,
array_first,
array_last, array_column,
array_diff,
etc.
$array = [1,2,3];
//implode: Construit une chaine à partir des éléments d'un tableau, avec un séparateur
$array = ['lastname', 'email', 'phone'];
//in_array : permet de tester si un élément est dans le tableau
if(in_array('email', $array)){
echo "email est dans le tableau";
}
//shuffle : mélange les éléments du tableau de maniÚre aléatoire
shuffle($array);
var_dump(implode(",", $array)); // string(20) "lastname,email,phone"Voir toutes les fonctions natives de PHP pour manipuler le tableaux
NULL est un type spécial et une valeur qui indique
lâabsence de valeur. La fonction isset() est
parfaitement adaptĂ©e pour tester la prĂ©sence dâune valeur ou dâune clef
dans un tableau.
$var = NULL;
//Test avec la fonction isset :
if(!isset($var)){
echo "la variable ne contient aucune valeur !";
}Une fonction, avant dâĂȘtre appelĂ©e (exĂ©cutĂ©e), doit
ĂȘtre dĂ©clarĂ©e avec le mot clef function. Une
fonction peut prendre des arguments (ou pas) et retourne
toujours quelque chose (si pas dâinstruction
return, elle retourne NULL)
On indique le type des données des arguments et de retour de la fonction (type hinting)
function sayHi(string $firstName = 'Jane', string $lastName = 'Doe') : string {
$msg = "Hello $firstName $lastName !";
return $msg;
}
//Appel de la fonction
echo sayHi();
echo sayHi('John');
echo sayHi('Rasmus', 'Lerdorf');Les fonctions nâont pas besoin dâĂȘtre dĂ©finies avant dâĂȘtre utilisĂ©es, SAUF lorsquâune fonction est dĂ©finie conditionnellement.
//Appel de la fonction avant sa définition ok !
echo sayHi();
function sayHi(string $firstName = 'Jane', string $lastName = 'Doe') : string {
$msg = "Hello $firstName $lastName !";
return $msg;
}
foo(); //Erreur ! Le bloc ci dessous DOIT ĂȘtre exĂ©cutĂ© pour que foo soit dĂ©finie !
if ($makefoo) {
function foo()
{
echo "Je n'existe pas tant que le programme n'est pas passé ici.\n";
}
}<?php
$a = 1; // portée globale
function test()
{
echo $a; // La variable $a est indéfinie car elle fait référence à une version locale de $a
}
test(); //Warning Undefined variable $a !global #Le mot-clé global est utilisé pour lier une variable de
la portée globale dans une portée locale.
<?php
$a = 1; // portée globale
function test()
{
global $a; // lier $a local Ă $a global
echo $a; // Ok
}
test(); //1Cette instruction sera utile car WordPress utilise intensivement plusieurs variables globales !
PHP supporte le paradigme fonctionnel. En PHP, une fonction est (peut ĂȘtre vue) une donnĂ©e comme les autres. On peut donc crĂ©er des closures, faire du currying, etc.
Une fonction peut :
Pour cela, PHP définit le type callable (implémenté sous forme de classe native)
<?php
$input = array(1, 2, 3, 4, 5, 6);
// CrĂ©e une nouvelle fonction anonyme et lâassigne Ă une variable
$filter_even = function($item) {
return ($item % 2) == 0;
};
$output = array_filter($input, $filter_even);
// Inutile de l'assigner Ă une variable, on peut utiliser directement une fonction anonyme :
$output = array_filter($input, function($item) {
return ($item % 2) == 0;
});<?php
/* Crée une fonction de filtre anonyme acceptant les éléments > $min
* Retourne un filtre unique parmi une famille de filtres "supérieur à n"
*/
function greater_than($min)
{
//Closure : accÚde à des variables fixées par son environnement ($min)
//avec l'instruction 'use' pour capturer la valeur de $min à la déclaration
return function($item) use ($min) {
return $item > $min;
};
}
$input = array(1, 2, 3, 4, 5, 6);
$output = array_filter($input, greater_than(3));<?php
function print_html(){
?>
<nav>
<ul>
<li><a href="/foo">foo</a></li>
<li><a href="/bar">bar</a></li>
<li><a href="/baz">baz</a></li>
</ul>
</nav>
<?php
}
print_html();Syntaxe trĂšs propre au fonctionnement de PHP. Utile pour Ă©crire des gros blocs de HTML sans Ă©chapper. PrivilĂ©gier nĂ©anmoins lâinclude de templates (plus lisible).
En PHP, comme en C, tout argument est passé à une fonction par valeur (copie locale).
<?php
function addToArray(array $arr){
$arr[] = 'bar';
}
$array = ['foo'];
addToArray($array);
var_dump($array); //?class Foo{
public function __construct(public int $counter = 0){}
}
function incFoo(Foo $foo): void{
$foo->counter++;
}
$foo = new Foo();
incFoo($foo);
echo $foo->counter . PHP_EOL; // ?On peut passer directement la rĂ©fĂ©rence dâune variable (chaĂźne,
tableau, etc.) en utilisant lâopĂ©rateur &.
//Passage par référence (explicite)
function addToArray(array &$arr){
$arr[] = "bar";
}
$array = ['foo'];
addToArray($array);
var_dump($array); //?Modification sur place.
Les variables Super Globales sont des variables fournies aux scripts PHP Ă lâexĂ©cution, contenant toutes les informations sur : lâenvironnement du script, la requĂȘte HTTP, le client, le serveur, etc.
Ce sont des tableaux associatifs PHP, qui commencent, par convention,
par un underscore (_).
#Un tableau associatif des valeurs passées au script courant via le protocole HTTP et la méthode POST
$_POST;
#Un tableau associatif des valeurs passées au script courant via le protocole HTTP et la méthode GET
$_GET;
#$_SERVER est un tableau contenant des informations telles que les en-tĂȘtes, les chemins et les emplacements de script. CrĂ©e par le serveur web (programme)
$_SERVER;
#Un tableau associatif des valeurs téléchargées au script courant via le protocole HTTP et la méthode POST
$_FILES;
#Un tableau associatif de variables, passé au script courant, via des cookies HTTP.
$_COOKIE;
#Un tableau associatif qui contient par défaut le contenu des variables $_GET, $_POST et $_COOKIE.
$_REQUEST;Ne modifiez jamais vous-mĂȘme ces tableaux, accĂ©dez-y en lecture seulement. Dans WordPress, on nâaura rarement (jamais ?) besoin dây accĂ©der directement. Le core nous en fournira une abstraction.
Pour utiliser le code PHP placé dans un autre fichier, on peut
utiliser le
construct require_once:
require_once lĂšve une
erreur fatale qui met fin a Ă lâexecution du script.<?php
//Inclure tout le contenu (UNE FOIS) du fichier foo.php dans le script courant
require_once './foo.php';
//Inclure tout le contenu (UNE FOIS) du fichier foo.php dans le script courant
require_once './bar.php';
//N'aura aucun effet
require_once './bar.php';Il existe dâautres construct pour inclure des scripts : include, include_once, require, etc.. PrivilĂ©gier
requireetrequire_once.
Le require_once nâagit pas comme un simple
copier/coller textuel brut du contenu du script importé (comme
#include en C), mais comme une évaluation du
script. Conséquences :
require sont visibles
dans le fichier inclus ;Fichier component.php :
<?php $last_component = "p"; ?>
<p>Un composant</p>Fichier index.php :
//require_once ouvre, compile et execute le script component.php comme si on avait fait 'php component.php'
require_once 'component.php';
echo $last_component;Exécuter le script index.php produit la sortie suivante
:
<p>Un composant</p>
pPHP est un langage typé dynamiquement, ce qui apporte de la flexibilité mais peut conduire à des comportements inattendus ! (bugs).
Depuis PHP 7, il est possible (et recommandé) de déclarer les types des arguments et retours de fonction.
Cela assure Ă lâexecution que la valeur passĂ©e en
argument Ă une fonction est du type attendu, sinon une erreur fatale
TypeError est lancée (si coercition désactivée) et
interrompt lâexĂ©cution du programme.
<?php
//Déclaration de fonction utilisant le *type hinting* : attend une chaßne et un entier, retourne une chaßne
function repeat(string $str, int $times): string {
if($times == 0)
return '';
return $str . repeat($str, $times - 1) ;
}
echo repeat("ha", 13);TOUJOURS utiliser le type hinting !
Attention, concernant les valeurs
scalaires ou primitives
(string, int, float, etc.), PHP
tente par défaut de convertir (coerce) vers le
type attendu (défini par le type hinting)
<?php
function sum(int $a, int $b): int {
return $a + $b;
}
var_dump(sum(1.5,2.5)); // Affiche int(3)strict #Pour empĂȘcher PHP de faire cette conversion (qui peut provoquer des
comportement inattendus!), on peut activer
le mode typage strict par fichier, avec
lâinstruction declare(strict_types=1);
Elle peut aussi Ă©viter des problĂšmes non anticipĂ©sâŠ
Dans ce mode, seule une variable correspondant exactement au
type attendu dans la déclaration sera acceptée sinon une
TypeError sera levée, sauf si la conversion
garantit lâintĂ©gritĂ© de la donnĂ©e
(int -> float)
<?php
declare(strict_types=1);
var_dump(sum(1.5, 2.5));Sortie :
PHP Fatal error: Uncaught TypeError: sum(): Argument #1 ($a) must be of type int, float givenstrict dans les
fichiers contenant de lâexĂ©cution de code que vous
contrÎlez;Les fondamentaux de la programmation orientée objet (POO) en PHP.
MĂȘmes concepts que dans tous les langages populaires supportant la POO (Java, C#, C++, Python, Kotlin, Dart, etc.)
class, new,
__construct(),__destruct())public, private,
protected)::staticextends)finalabstract)$foo = new Foo();
$bar = new Bar();
//$foo reçoit le message 'doStuff' dont le contenu est donné par $bar
$foo->doStuff($bar->stuffToDo());La difficultĂ© dâun systĂšme designĂ© avec de la programmation orientĂ© objet est de dĂ©composer le systĂšme en objets tout en gardant le systĂšme comprĂ©hensible et ouvert aux changements (flexibilitĂ©).
Dr. Alan Kay on the Meaning of Object-Oriented Programming :
âOOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all thingsâ
Source de lâimage : Alan Kay at OOPSLA 1997 - The computer revolution hasnt happened yet
La classe donne une dĂ©finition (signature) des propriĂ©tĂ©s et des mĂ©thodes ainsi quâune implĂ©mentation (ce que font les mĂ©thodes, le corps des mĂ©thodes). Une classe dĂ©finit Ă©galement un espace de nom.
class Foo{
//Une propriété (une variable de classe)
string $proprerty;
function __construct($property){
//La pseudo variable $this (mise à disposition par PHP) est disponible lorsqu'une méthode
//est appelée depuis un contexte objet. $this a pour valeur l'objet appelant.
$this->property = $property;
}
//Une méthode (une fonction de classe)
function doStuff(array $stuffToDo){
//instructions
}
}Une classe est une chose statique. Tous les langages orientés objets ne sont pas basés sur des classes (par ex : langages orientés prototypes comme JS, prototype est dynamique)
new et accéder aux
propriétés/méthodes #new. Ce
opérateur fait appel au constructeur __construct() pour
initialiser lâobjet.//CrĂ©ation d'un objet
$foo = new Foo('foo');
//Accéder à une propriété (l'encapsulation est brisée,
//les détails internes de l'objet sont exposés à sa surface et accessibles au monde entier)
$foo->property;
//Accéder à une méthode (i.e envoyer un message à l'objet)
$foo->doStuff(array(1,2,3))Dans un langage réellement POO, envoyer des messages (appeler des méthodes) est la seule maniÚre à notre disposition pour faire agir un objet (exécuter du code).
Les messages sont (et devraient!) ĂȘtre la seule maniĂšre de changer lâĂ©tat interne dâun objet. Ainsi, lâĂ©tat interne dâun objet est dit encapsulĂ©, ses rouages internes (propriĂ©tĂ©s, mĂ©thodes privĂ©es, etc.) sont invisibles depuis lâextĂ©rieur.
Signature dâune mĂ©thode :
public (API), private,
protected) ;void indique que la méthode ne
retourne rien);void
indique que la méthode ne prend rien en argument).//La signature de la méthode `doStuff` de la classe Foo
public doStuff(array $stuffToDo):voidLa visibilitĂ© dĂ©finit lâexposition des attributs et des mĂ©thodes dâun objet aux autres objets dans le programme. Il existe 3 modificateurs de visibilitĂ© :
public: câest la visibilitĂ© par dĂ©faut en PHP. Un
attribut/une méthode public est accessible depuis
lâextĂ©rieur de la classe, par tous les objets du programme.private: un attribut/une mĂ©thode private
nâest pas accessible depuis lâextĂ©rieur de la classe. Aucun objet dans
le programme ne peut y accĂ©der, Ă part lâobjet qui les porte. On ne peut
pas redéfinir une propriété/méthode privée dans une classe enfant.protected: un attribut/une méthode
protected nâest pas accessible depuis lâextĂ©rieur de la
classe sauf par les classes parent ou enfant.:: #LâopĂ©rateur
de rĂ©solution de portĂ©e :: est un moyen dâaccĂ©der aux
membres static, const (constantes
de classe) ainsi quâaux mĂ©thodes surchargĂ©es :
self::,
parent::, static::NomDeLaClasse:::: #class Foo
{
public function __construct()
{
echo 'Call Foo constructor' . PHP_EOL;
}
}
class Bar extends Foo
{
const BAZ = 'baz';
public function __construct()
{
//Appel au constructeur parent
parent::__construct();
echo 'Call Bar constructor' . PHP_EOL;
}
}
$bar = new Bar();
echo Bar::BAZ . PHP_EOL;self:: et static:: #<?php
/**
* Quelle est la différence entre static et self ?
* Tous deux font rĂ©fĂ©rence Ă la classe oĂč ces instructions sont utilisĂ©es.
* Mais elles se comportent différemment, notamment dans le cadre de l'héritage.
*/
class Furniture {
protected static $category = 'General';
public static function describe() {
//Changer static par self et observer le résultat
//static::$category : résolu à l'execution, fait référence à la classe sur laquelle la méthode 'describe' est appelée, et donc sur sa valeur de $category(Chair ici).
//Changement de comportement possible en héritage (late binding)
//self::$category : résolu à la compilation, fait référence à la classe sur laquelle le mot clef est utilisé, non à la classe sur laquelle la méthode est appelée (Furniture ici)
//Changement de comportement/valeur impossible via l'héritage
return 'This is a ' . self::$category . ' piece of furniture.' . PHP_EOL;
}
}
class Chair extends Furniture {
//Overriding de la valeur statique dans la classe enfant
protected static $category = 'Chair';
}
echo Furniture::describe();
echo Chair::describe();static #Une
propriĂ©tĂ© ou une mĂ©thode static peut ĂȘtre accĂ©dĂ©e sans
avoir besoin dâinstancier la classe (statique, car pas besoin
pour y accĂ©der dâutiliser lâopĂ©rateur new et une allocation
mĂ©moire, câest un accĂšs Ă froid)
class Foo
{
private static string $bar = 'bar';
public static function bar()
{
return static::$bar;
}
}
//Je peux appeler la méthode bar de la classe Foo sans instancier d'objet Foo
echo Foo::bar();Introduit en PHP 8, le Null-safe operator apporte du sucre syntaxique pour tester lâexistence dâune propriĂ©tĂ©/mĂ©thode sur un objet :
//Retourne NULL si l'une des expressions est NULL
$foo?->bar?->baz;class Customer {
public function __construct(public ?Address $address = NULL){
}
public function getAddress(): ?Address {
return $this->address;}
}
class Address {
public function getCountry(): string {}
}
$customer = new Customer();
$address = $customer->getAddress();
if ($address) {
$country = $address->getCountry();
}
else {
$country = null;
}class Customer {
public function __construct(public ?Address $address = NULL){
}
public function getAddress(): ?Address {
return $this->address;}
}
class Address {
public function getCountry(): string {}
}
$customer = new Customer();
//Silent NULL, et non une Fatal Error
$country = $customer->getAddress()?->getCountry();extends #Une classe qui étend une autre classe hérite à la fois :
Une classe enfant peut accéder et redéfinir les attributs/méthodes de
sa classe parente (sauf si private). LâhĂ©ritage est un
mécanisme de partage de code.
class Foo{
private $baz
public function baz(){}
}
//Bar étend Foo, Bar est une classe enfant de Foo
class Bar extends Foo{
//Bar dispose de la propriété $baz et de la méthode baz()
}LâhĂ©ritage est vu souvent comme une relation âis-aâ entre deux classes, mais câest surtout une relation âbehaves likeâ. Car la classe enfant hĂ©rite de lâimplĂ©mentation (comportement) de sa classe parent.
LâhĂ©ritage crĂ©e un couplage fort entre deux classes, tout en exposant les dĂ©tails internes de la classe parent Ă ses classes enfant. Ă utiliser avec prĂ©caution !
final #On peut contrĂŽler
lâhĂ©ritage avec le mot clef final. Une classe
final est une classe qui ne peut pas ĂȘtre Ă©tendue.
final class Foo{}
//Erreur, Foo ne peut pas ĂȘtre Ă©tendue
class Bar extends Foo{}Une mĂ©thode final est une mĂ©thode qui ne peut pas ĂȘtre
redéfinie dans une classe enfant.
class Foo(){
public final function foo(){ echo 'Foo call foo';}
public function bar(){ echo 'Foo call bar';}
}
class Baz extends Foo{
//Erreur, impossible
public function foo(){ echo 'Baz call foo';}
//Valide
public function bar(){ echo 'Baz call bar';}
}abstract #Une classe abstraite ne peut pas ĂȘtre instanciĂ©e (car son implĂ©mentation est incomplĂšte). Toute classe dĂ©finissant une mĂ©thode abstraite est abstraite par dĂ©finition.
Les mĂ©thodes abstraites dĂ©finissent simplement la signature de la mĂ©thode, non leur implĂ©mentation. Cette signature doit ĂȘtre respectĂ©e par lâimplĂ©mentation.
abstract class Foo{
protected int $foo;
abstract public function foo():int;
public function bar(){echo 'bar' . PHP_EOL;}
}
class Bar extends Foo{
//Bar définit l'implémentation de la méthode abstraite foo, en respectant sa signature
public function foo():int {
echo 'Implémentation de foo dans la classe Bar' . PHP_EOL;
return 10;
}
}
$bar = new Bar();
echo $bar->foo() . PHP_EOL;
$bar->bar();à la différence des
interface, une classe abstraite peut ĂȘtre partiellement implĂ©mentĂ©e et disposer de propriĂ©tĂ©s comme nâimporte quelle classe.
interface et implements #Les
interfaces permet de définir des méthodes (signatures)
quâune classe doit implĂ©menter (implements) sans
avoir à définir comment ces méthodes fonctionnent.
Une interface permet dâutiliser des objets de façon
interchangeable.
interface Foo{
public function foo(string $value): string;
}
interface Bar{
public function bar(string $value): string;
}
//Une classe peut implémenter plusieurs interfaces
class Baz implements Foo, Bar{
public function foo(string $value): string{
//instructions..
}
public function bar(string $value): string{
//instructions..
}
}Une
interfaceest un cas particulier de classe abstraite sans propriétés ni implémentations.
Le polymorphisme est la capacitĂ©, pour un mĂȘme message, de correspondre Ă plusieurs formes de traitements selon lâobjet auquel ce message est adressĂ©.
interface SomeBehavior{
function doStuff();
}
class Foo implements SomeBehavior{
function doStuff(){echo 'Foo do stuff this way';}
}
class Bar implements SomeBehavior{
function doStuff(){echo 'Bar do stuff in another way';}
}
// Le message 'doStuff' est envoyé à $foo,
// il affiche 'Foo do stuff this way' (traitement de Foo)
$foo = (new Foo())->doStuff();
// Le MĂME message 'doStuff' est envoyĂ© Ă bar,
// il affiche 'Bar do stuff in another way' (traitement de Bar)
$bar = (new Bar())->doStuff();Depuis PHP 8.4, on peut écrire
new Bar()->doStuff(), sans les parenthĂšses (sucre syntaxique)
Lâinterface dâun objet (on ne parle pas ici du construct PHP
interface) dĂ©signe lâensemble des signatures de mĂ©thode
dâun objet. Nâimporte quel message qui correspond Ă une signature de
lâinterface peut ĂȘtre envoyĂ© Ă cet objet. Lâinterface dâun objet
est synonyme de type de lâobjet.
Dans un systĂšme OO oĂč lâencapsulation est bien respectĂ©e, lâinterface est fondamentale, car les objets sont connus (comment les utiliser) seulement via leurs interfaces (ce quâils exposent Ă leur surface).
En particulier :
interface A{
function a();
}
interface B{
function b();
}
// Foo est de 'type' A, car il possĂšde l'interface de A
class Foo implements A{function a(){}}
// Bar est de type A ET de type B, car il possĂšde les deux interfaces.
// Je peux :
// - lui envoyer le message `b` (i.e appeler sa méthode b)
// - et le message `a`.
// On peut aussi dire que Bar est un sous-type de A (super-type),
// car son interface contient l'interface de A.
class Bar implements A, B{function a(){} function b(){} function bar(){}}
// Baz déclare une propriété de type A,
// une implémentation de A lui est fournie par injection de dépendance.
class Baz {private A $a; function construct(A $a){$this->a = $a;} function a(){$this->a->a();}}Un diagramme de classes UML permet de bien visualiser les interfaces des objets
Lâarchitecture logicielle est lâensemble des techniques et concepts pour rendre le systĂšme :
Fabriquer un systÚme simple est une chose⊠difficile !
Visioner cette excellente confĂ©rence âSimple Made Easyâ (2011) de Rich Hickey (crĂ©ateur de Clojure, Datomic), sur la diffĂ©rence entre simplicitĂ© et facilitĂ© (et la complexitĂ© logicielle et ses origines)
Voyons quelques principes de design pour la POO.
Si lâun des aspects de votre code est susceptible de changer, vous savez que vous ĂȘtes face Ă un comportement quâil faut isoler du reste de votre systĂšme. Vous pourrez ainsi les modifier plus tard sans affecter ce qui ne varie pas.
Si vous programmez vers une interface (câest-Ă -dire que votre code client consomme des interfaces et non des implĂ©mentations) vous pouvez envoyer le mĂȘme message aux objets que le client manipule (appeler les mĂȘmes mĂ©thodes) tout en obtenant un comportement diffĂ©rent.
Vous pouvez ainsi simplement changer dâimplĂ©mentation en fonction de vos besoins (plug-in architecture)
Ce principe dit donc utilisez le polymorphisme.
Le polymorphisme (dâavoir des primitives pour le mettre en place facilement) est le vĂ©ritable apport de la Programmation OrientĂ©e Objet.
Passez de la relation est-un et surtout se comporte comme (héritage) à la relation a-un (composition) entre deux classes.
WPdb,
WP_Query, WP_Post, etc.);readonly (immutable) Ă la
place pour vos modÚles (map documentées ou Value
Objects) + fonctions pour les manipuler;DerniĂšre version actuelle : 8.5 (12/25 !)
PHP 8.4, sortie en fin dâannĂ©e 2024, est une mise Ă jour majeure du langage PHP.
PHP 7 (2015) (Perfs, Exceptions, Type Hinting, Classe anonymes) et PHP 8 (2020) (arguments nommés, attributs, promotion constructeur, unions de types, NullSafe operator, throw, JIT) avaient également été des nouvelles versions majeures.
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}Pour faire des object values immutables (approche fonctionnelle)
readonly class Test {
public string $prop;
}TrÚs bon ! Pour faire des Value Objects (clé/valeur) immutables
echo match (8.0) {
'8.0' => "Oh no!",
8.0 => "This is what I expected",
};
//> This is what I expectedType hinting permet de faire lâunion de diffĂ©rents types de retour
class Number {
public function __construct(
private int|float $number
) {}
}
new Number('NaN'); // TypeErrorIl fallu attendre PHP 8 pour avoir des Enums propres en PHPâŠ
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}DateTime et
DateTimeImmutable (API simplifiée et homogénéisée);new MyClass()->method()
sans parenthĂšses.PHP 8.5 sort en novembre 2025 !
Voir toutes les nouveautés à venir de PHP 8.5
Quelques exemples.
|> #$resultat = "Hello World"
|> htmlentities(...)
|> str_split(...)
|> fn($x) => array_map(strtoupper(...), $x)
|> fn($x) => array_filter($x, fn($v) => $v !== 'O');$panier = [
'SKU-902' => 'T-shirt PHP',
'SKU-123' => 'Mug ĂlĂ©phant'
];
$premierProduit = array_first($panier); // "T-shirt PHP"
$premierProduit = array_last($panier); // "Mug ĂlĂ©phant"php --ini=diff. Affiche
directement les valeurs qui diffĂšrent des valeurs par dĂ©faut;parse_urlFramework Interoperability Group (PHP-FIG), un groupe de travail rĂ©unissant la plupart des vendor PHP pour travailler sur la normalisation des pratiques de dĂ©veloppement. Le but est dâassurer lâinteropĂ©rabilitĂ© entre bibliothĂšques et frameworks PHP pour faciliter la maintenance, la lecture et la rĂ©utilisation du code.
Les PSR-1 et PSR-12 sont des standards utiles, Ă ne pas suivre religieusement⊠La gouvernance PHP-FIG est nĂ©anmoins contestĂ©eâŠ
Extraits recommandés des PSR-1 et PSR-12 pour le développement avec WordPress :
?> tag MUST be omitted
from files containing only PHP;CrĂ©er un site web qui affiche une liste de publications sur sa page dâaccueil.
demo_autoloading/
âââ index.php
âââ config.php
âââ db.php (simulera une petite base de donnĂ©es)
âââ src/
âââ Model/
âââ User.php
âââ Post.php User.php dĂ©finit une classe User. Un
User est défini par :
int),User peut ĂȘtre lâauteur·ice dâune ou plusieurs
publications (de type Post).Post.php définit une classe Post. Une
publication (post) est définie par :
int),DateTimeImmutable),User).Les identifiants des User et Post
commencent Ă 1 et doivent ĂȘtre incrĂ©mentĂ©s de maniĂšre
automatique.
index.php est le point dâentrĂ©e de
lâapplication (core et controller), produit
la sortie,config.php contient des éléments de
configuration du projet:
ABS_PATH Ă©gale Ă
__DIR__ . '/'. Remarque : Cette constante vous sera
utile pour importer vos scripts;POSTS_PER_PAGE Ă©gale Ă
5, indiquant le nombre maximum de posts qui doivent
ĂȘtre affichĂ©s sur la page,db.php simulera un accĂšs Ă une source de
données (base de données, service web, etc.). Il définit deux
collections:
$users : une collection de 2 User,$posts : une collection de 6 Posts./), le site web doit produire une
page HTML affichant la liste des publications du plus
récent au plus ancien, avec le template HTML
suivant :...
<h1> DerniĂšres publications</h1>
<ul>
<li>
<article id="{id article}">
<h2>{titre article}</h2>
<p>
<a href="/{slug}">Lire l'article </a>
</p>
<footer>
<p>
Publié le <time datetime="{date publication au format d:m:Y H:i}">{date en français. Ex : 16 mai}</time> par {prénom auteur·ice} {nom auteur·ice}.
</p>
</footer>
</article>
</li>
...
</ul>
...Pour trier les posts, trouver la fonction primitive PHP la plus adaptée parmi cette liste.
Les données dynamiques sont indiquées entre accolades. Conseil : pour la date en français, utiliser un formateur international de dates
IntlDateFormatter
localhost:5001;curl localhost:5001 ou avec votre navigateur favori.Que remarquez-vous sur la gestion des dépendances entre scripts ? Est-ce simple ? Est-ce fun ?
Lâautoloading a pour but de rĂ©gler tous ces problĂšmes, une bonne fois pour toutes, pour toujours !
Garder ce projet, nous le réutiliserons !
<?php
include '../../path/to/file1.php';
include '../path/to/file2.php';
include __DIR__ .'/path/to/file3.php';
require ABS_PATH .'path/to/file3.php';
include ...
//Et si on doit inclure 100 fichiers ? DĂ©placer ce fichier dans un autre repertoire ?ProblĂšme avec lâimportation explicite :
Lâautoloading dĂ©finit une stratĂ©gie pour que lâinterprĂ©teur PHP trouve une classe, une interface, une fonction et la charger Ă la demande, au moment de lâexĂ©cution.
Un autoloader est un composant qui permet de charger des
objets (fonctions, classes, enum, etc.) sans devoir
explicitement dire quels fichiers inclure (avec
require, require_once, include ou
include_once)
En PHP, lâautoloading standardisĂ© (PSR-4) est basĂ© sur lâutilisation des espaces de noms (namespaces).
Il convient donc dâaborder dâabord les namespaces.
symfony.Toute classe, interface, fonction ou constante vit dans un namespace (ou sous-namespace).
La dĂ©claration dâun namespace se fait au dĂ©but dâun fichier PHP sur
une nouvelle ligne juste aprĂšs le tag dâouverture
<?php
<?php
namespace Foo;
echo __NAMESPACE__; //affiche le namespace courantPour savoir dans quel namespace vous ĂȘtes, utiliser la constante NAMESPACE
On peut dĂ©clarer un sous-namespace Ă lâintĂ©rieur dâun namespace (comme on peut crĂ©er un sous-dossier dans un dossier) :
<?php
namespace Foo\Bar;Les sous-namespaces sont utiles pour organiser votre code.
Le namespace le plus important est votre namespace vendor (identifiant de votre marque ou de votre organisation). Il doit ĂȘtre unique pour distribuer votre code et permettre aux autres de lâutiliser sans crĂ©er de conflits avec ses propres sources ou dĂ©pendances.
Les classes, interfaces, etc. dâun mĂȘme namespace nâont pas besoin dâĂȘtre dĂ©clarĂ©es dans le mĂȘme fichier.
Si vous déclarez un namespace dans votre fichier, tout ce que contient votre fichier appartiendra à ce namespace.
Fichier Baz.php :
<?php
namespace Foo
class Baz{}Fichier Bar.php :
<?php
namespace Foo
class Bar{}Les classes Bar et Baz sont déclarées dans
deux fichiers diffĂ©rents, mais elles appartiennent au mĂȘme namespace
Foo.
Pour éviter les problÚmes de collisions de nom, on utilisait avant une convention, appelée Zend-style class names (popularisée par le framework Zend) :
class Zend_Cloud_Document_Service_Adapter_WindowAzure_query{
...
}_) permettent de retrouver le
path du fichier contenant la classe =>
Zend/Cloud/Document/Service/Adapter/WindowAzure/Query.phpUtiliser une classe placée dans un namespace : Renseigner le nom complÚtement qualifié de la classe (nom namespace + nom de la classe).
<?php
//Je dis que j'utilise la classe Response du namespace Symfony\Component\HttpFoundation
$response = new \Symfony\Component\HttpFoundation\Response('Foo', 400);use): Dire
Ă PHP quelle namespace, classe, interface, fonction ou constante je vais
utiliser dans ce fichier.as): dire Ă PHP que je vais
référencer une classe, interface, fonction ou constante importée avec un
nom plus court (généralement son nom non qualifié);<?php
//Importer avec un alias par défaut
use Symfony\Component\HttpFoundation\Response;
//Ăquivalent Ă
use Symfony\Component\HttpFoundation\Response as Response;
$response = new Response('Foo', 400);<?php
//Namespace avec alias customisé (à éviter)
use Symfony\Component\HttpFoundation\Response as Res;
$response = new Res('Foo', 400);use #On importe du code avec le mot clef
use.
Il doit ĂȘtre placĂ© immĂ©diatement aprĂšs le tag
ouvrant php <?php ou la déclaration du namespace
Attention, âimporterâ avec
usenâest pas Ă©quivalent Ărequire! Câest seulement une dĂ©claration, pas une instruction pour lâinterprĂ©teur PHP. Lâautoloading que nous verrons aprĂšs permet de charger une classe (require) Ă partir dâun use.
use IS NOT require
#Fichier Foo.php :
namespace Bar;
class Foo{}Fichier index.php :
<?php
use Bar\Foo;
new Foo();// Fatal error ! index.php ne sait pas oĂč trouver la classe Foo !Si on ne prĂ©cise pas de namespace, la classe, interface, fonction ou constante existe dans le namespace global, le namespace par dĂ©faut.
Si lâon fait rĂ©fĂ©rence Ă une classe, interface, fonction ou constante sans namespace, PHP assume quâelle existe dans le namespace courant (le namespace oĂč la rĂ©fĂ©rence est faite).
Si vous voulez faire référence à une classe dans un namespace
depuis un autre namespace, vous devez utiliser le nom
entiÚrement qualifié =
\Namespace\Nom de la classe. Par exemple,
\Bar\Foo.
Pour faire référence à quelque chose vivant dans le namespace
global, vous devez ajouter un \ au début du nom qualifié
pour le rendre complet.
Il nâest pas nĂ©cessaire dâajouter le
\devant le nom qualifiĂ© dans une dĂ©claration avec le mot-clĂ©usecar PHP assume quâil est entiĂšrement qualifiĂ©.
namespace Bar;
class Foo{}FooPHP recherche dans le namespace courant, si non trouvé recherche dans le namespace global
Bar\FooPHP ajoute le namespace courant comme préfixe
\Bar\FooPHP lâinterprĂšte comme Barsans faire aucune supposition (aucune ambiguĂŻtĂ©)
<?php
namespace Bar;
class Foo{
public function doSomething(){
$exception = throw new Exception(); // Fatal error !
}
}
new Foo()->doSomething();Résultat :
PHP Fatal error: Uncaught Error: Class "Bar\Exception" not found in ...Le code crash, pourquoi ?
<?php
namespace Bar;
class Foo{
public function doSomething(){
$exception = throw new \Exception(); // OK !
}
}Dans lâexemple prĂ©cĂ©dent, PHP cherche la classe
Exception dans le namespace courant Foo, or
elle nâexiste pas ici. Ici on veut bien utiliser la classe
built-in Exception de PHP qui existe dans le
namespace global. Il faut donc indiquer le nom qualifié complet avec le
\.
Pour chaque appel, dites quelle classe ou fonction PHP essaie dâinvoquer.
<?php
namespace A;
use B\D, C\E as F;
foo();
\foo();
my\foo();
F();
new B();
new D();
new F();
new \B();
new \D();
new \F();
B\foo();<?php
namespace A;
use B\D, C\E as F;
foo(); // tente d'appeler la fonction "foo" dans l'espace de noms "A"
// puis appelle la fonction globale "foo" (uniquement pour les fonctions)
\foo(); // appelle la fonction "foo" définie dans l'espace de noms global
my\foo(); // appelle la fonction "foo" définie dans l'espace de noms "A\my"
F(); // tente d'appeler la fonction "F" définie dans l'espace "A"
// puis tente d'appeler la fonction globale "F"
new B(); // crée un objet de la classe "B" définie dans l'espace de noms "A"
new D(); // crée un objet de la classe "D" définie dans l'espace de noms "B"
new F(); // crée un objet de la classe "E" définie dans l'espace de noms "C"
new \B(); // crée un objet de la classe "B" définie dans l'espace de noms global
new \D(); // crée un objet de la classe "D" définie dans l'espace de noms global
new \F(); // crée un objet de la classe "F" définie dans l'espace de noms global
B\foo(); // appelle la fonction "foo" de l'espace de noms "A\B"__autoload() #PHP 5 avait introduit la fonction âmagiqueâ __autoload().
Ne pas utiliser. Elle est devenue obsolÚte et a été supprimée dans PHP 8.0.0 !
Elle servait de hook pour implémenter une stratégie de chargement de classes :
<?php
function __autoload($className) {
include $className . '.php';
}
//__autoload() va automatiquement ĂȘtre appelĂ©e par l'interprĂ©teur PHP car la classe Foo n'est pas connue
$foo = new Foo();ProblĂšme : vous ne pouviez la dĂ©finir quâune fois !
spl_autoload_register() #La fonction spl_autoload_register()
permet dâenregistrer une implĂ©mentation dâ__autoload() dans
une pile dâappels (file dâattente)
function my_autoloader($class) {
include 'classes/' . $class . '.class.php';
}
spl_autoload_register('my_autoloader');
// Ou, en utilisant une fonction anonyme
spl_autoload_register(function ($class) {
include 'classes/' . $class . '.class.php';
});Si vous devez utiliser plusieurs fonctions dâautochargement, la fonction
spl_autoload_register()est faite pour cela. Elle crĂ©e une file dâattente de fonctions dâautochargement, et les exĂ©cute les unes aprĂšs les autres, dans lâordre oĂč elles ont Ă©tĂ© dĂ©finies.
Le PHP-FIG a proposĂ© une spĂ©cification dâautoloader pour standardiser la stratĂ©gie dâautoloading dans le PSR-4.
Avec le PSR-4 : Autoloading standard et interopérable.
Nâimporte qui peut utiliser des composants, frameworks avec un et un seul autoloader !
Si vous écrivez et distribuez des composants PHP, faites en sorte de respecter le PSR-4 sinon personne ne voudra utiliser votre code. La majeure partie des organisations le font : Symfony, Doctrine, Monolog, Twig, Guzzle, PHPUnit, Carbon, etc.
Lâautoloader PSR-4 implĂ©mente une stratĂ©gie pour
trouver et charger
(require) automatiquement :
Cette stratégie est basée sur les namespaces.
On ne peut pas autoloader de simple fonctions, variables ou constantes (define)
Cette recommandation impose une contrainte sur :
Principe : un use est associé à un require
Pour cela, la hiérarchie des namespaces doit correspondre à la
hiérarchie des fichiers sources.
ENI\App à un répertoire, par exemple
src.ENI\App se trouve
dans le dossier srcPar exemple
ENI\App\ModernPHPcorrespond au dossiersrc/ModernPHP. La classeENI\App\ModernPHP\Foocorrespond au fichiersrc/ModernPHP/Foo.php
Vous nâavez pas besoin dâĂ©crire votre propre autoloader, vous pouvez en utiliser un gĂ©nĂ©rĂ© automatiquement par Composer, le gestionnaire de dĂ©pendances. Nous y reviendrons.
Lâautoloader PSR-4 utilise, Ă©videmment, sous le capĂŽt la fonction
spl_autoload_register()!
Composer est :
Packagist est le dĂ©pĂŽt public principal oĂč sont publiĂ©s des composants PHP. On y retrouve tous les vendor majeurs.
à ne pas confondre avec PECL, le dépÎt des extensions de la machine virtuelle PHP !
Vous dites Ă Composer quel composant vous voulez et Composer :
Suivre les instructions ici pour installer et ici pour le télécharger
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae')
{ echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"Pour lâinstaller globalement (Ubuntu/Debian) :
sudo mv composer.phar /usr/local/bin/composerDans un terminal :
composer -V
Composer version 2.3.8 2022-07-01 12:10:47Nous allons installer le composant guzzlehttp/guzzle, un client HTTP puissant et performant.
guzzlehttpguzzleguzzlehttp/guzzlePackagist utilise la convention
vendor/packagepour éviter les collisions de nom.
composer require #composer require guzzlehttp/guzzleComposer crée deux fichiers :
composer.json : composants installés du projet (prod,
dev);composer.lock : liste toutes les dépendances avec leur
version utilisĂ©e (permet de cloner le projet dans le mĂȘme Ă©tat).Versionner ces deux fichiers dans votre contrĂŽle des sources (Git) !
composer install
et composer update #composer //liste toutes les commandescomposer install : installe toutes les
dépendances du projet en respectant les versions déclarées dans
le composer.lock;composer update : télécharge les derniÚres
versions des composants (dans les limites de votre politique de
maj semver), et met Ă jour composer.lockComment utiliser les composants ?
Dans votre code
<?php
require 'vendor/autoload.php';Et voilĂ !
guzzle #On a installé guzzlehttp/guzzle. Dans notre projet :
<?php
require 'vendor/autoload.php';
$client = new \GuzzleHttp\Client();ou
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();La classe
Clientexiste bien dans le fichiersrc/Client.php
Pourquoi faire de son projet un composant ?
require ni de path !);LâĂ©cosystĂšme actuel de PHP et son systĂšme de gestion de dĂ©pendances via composer est fortement basĂ© sur lâorientĂ© objet (classes, interfaces, traits, enums). Une classe peut ĂȘtre vu ici comme un namespace regroupant un ensemble de fonctions.
AmenĂ© Ă changer dans le futur⊠On a pas toujours envie dâutiliser la POO !
Si lâon ne souhaite pas utiliser de POO, il est
possible dââautoloaderâ de simple fonctions. Pour cela, utiliser la clef
files de lâautoload dans
composer.json :
"autoload": {
"psr-4": {
"Vendor\\Package\\": "src/"
},
"files": ["src/functions_include.php"] <==== ICI
},Tout fichier listé ici sera inclus (require)
automatiquement au démarrage (utile pour des constantes également).
Pensez Ă :
composer.json
et composer.lock;vendor en
lâajoutant au .gitignore.Vous ne voulez pas pousser le code source de vos dĂ©pendances avec votre projet. Les fichiers
composer.jsonetcomposer.locksont là pour éviter cela !
composer init (création
de composer.json et composer.lock);<vendor>/<nom application>) mappé au dossier
src dans le composer.json;src (étape 3);composer.json et composer.lock, et en
excluant vendor dans un fichier
.gitignore;Le namespace renseignĂ© Ă lâĂ©tape 3 ne doit pas nĂ©cessairement correspondre Ă votre vendor/package name. Ce nom sert seulement Ă Composer et Packagist pour identifier votre paquet parmi tous les paquets prĂ©sents sur Packagist et dans votre projet.
Toutes vos classes, interfaces doivent vivre dans le dossier
src et sous le namespace renseigné dans le fichier
composer.json
demo_autoloading afin quâil utilise lâautoloading
PSR-4 et le gestionnaire de dépendances Composer :
composer init;require);congig.php dans src
pour quâil soit chargĂ© automatiquement;index.php joue le rĂŽle de client du contenu
src. Il invoquera lâautoloader
(require ./vendor/autoload.php).db.php en dehors de la stratégie
dâautoloading.Voici la structure finale du projet :
âââ composer.json
âââ db.php
âââ index.php
âââ README.md
âââ src
â  âââ config.php
â  âââ Models
â  âââ Post.php
â  âââ User.php
âââ vendorindex.php pour
afficher la collection de posts..gitignore correcte
pour versionner le projet.Composer, Packagist et
lâautoloading;Une erreur se dĂ©clenche lorsque votre script PHP ne peut pas sâexĂ©cuter comme prĂ©vu (par exemple un script Ă charger nâexiste pas, une autorisation de lecture de fichier est refusĂ©e, lâespace disque est insuffisant, une classe nâexiste pas, etc.).
Une erreur est caractérisée par 4 valeurs :
E_. Par exemple, une
erreur de niveau 1 est une erreur fatale définie par la
constante E_ERROR.<?php
//Le fichier foo.php n'existe pas
require 'foo.php';
echo 'Tout va bien' . PHP_EOL;Que va-t-il se passer ? Comment cela va-t-il mal se passer ? Et si on
remplace require par include ?
Error + interface
Throwable. La majorité des erreurs
internes (mais pas toutes, comme fopenâŠ)
deviennent des objets de type Error
(TypeError, ArithmeticError, etc.),
attrapables, comme les exceptions.Une erreur, une fois Ă©mise (throwned), remonte la pile dâexĂ©cution (bubbling de lâerreur) jusquâĂ ĂȘtre attrapĂ©e:
try/catch(Error);Toujours en enregistrer un, si vous manquez de
catch une Error localement.
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
echo "Nous sommes désolés, un problÚme vient de survenir :/ \n Nous vous invitons à revenir plus tard." . PHP_EOL;
//Si c'est une erreur de niveau 2 (avertissement), lâexĂ©cution peut "en principe" continuer
// mais on dĂ©cide, par prĂ©caution, d'arrĂȘter lĂ les frais
if ($errno === E_WARNING)
die;
});try/catch #<?php
try {
//Le fichier foo.php n'existe TOUJOURS pas
require 'foo.php';
echo 'Cette instruction ne sera pas executée si require émet une erreur' . PHP_EOL;
} catch (Error $e) {
echo "L'erreur " . $e->getMessage() . " est gérée localement" . PHP_EOL;
}
echo 'Le programme continue ici' . PHP_EOL;<?php
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
echo 'Par contre, fopen, comme tout le monde, reporte bien ses erreurs auprĂšs du gestionnaire d\'erreurs global.' . PHP_EOL;
});
try {
//On essaie d'ouvrir un fichier foo.php qui n'existe pas. Fatal Error
fopen('foo.php', 'r');
} catch (Error $e) {
echo 'Vous ne verrez jamais ce message car fopen n\'émet pas d\'erreurs de type Error. Cette fonction utilise toujours l\'ancien systÚme de reporting' . PHP_EOL;
}Voici les rĂšgles que vous devriez suivre :
Script error.php :
<?php
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
error_log("[$errno] $errstr in $errfile:$errline");
return false;
});
try {
//On essaie d'ouvrir un fichier foo.php qui n'existe pas. Fatal Error
require 'foo.php';
} catch (Error $e) {
echo 'Impossible de charger foo.php. Ce n\'est pas grave, on continue.'. PHP_EOL;
}Fichier de configuration php-dev.ini :
;Afficher les erreurs
display_startup_errors = On
display_errors = On
;Rapporter toutes les erreurs
error_reporting = -1
;Activer le log d'erreurs
log_errors = On
;Placé dans le repertoire courant pour la démo
error_log = ./php-cli.logphp -c php-dev.ini error.php Voir les directives pour configurer PHP au runtime
Fichier de configuration php-prod.ini :
;N'afficher SURTOUT PAS les erreurs
display_startup_errors = Off
display_errors = Off
;Rapporter toutes les erreurs SAUF les notice (pour ne pas polluer vos logs)
error_reporting = E_ALL & ~E_NOTICE
;Activer le log d'erreurs
log_errors = On
;Placé dans le repertoire courant pour la démo
error_log = ./php-cli-prod.logphp -c php-prod.ini error.php Il nâest pas recommandĂ© dâutiliser les erreurs PHP lorsque lâon souhaite Ă©mettre une erreur (mais plutĂŽt les exceptions). Les erreurs doivent ĂȘtre uniquement gĂ©rĂ©es pour les fonctions natives de PHP.
Error est réservé à PHP
lui-mĂȘme ;Error (il doit lancer des objets de type
Exception)Aujourdâhui, toute la communautĂ© utilise les exceptions dans le code applicatif.
Une exception non traitĂ©e provoque une erreur fatale et lâarrĂȘt du programme (crash)
throw new Exception("Une exception"); //Si non attrapée, crash le programme.try/catch;Vous pouvez définir vos propres exceptions, en
étendant la classe Exception ;
class MyException extends Exception { }try {
$pdo = new PDO('mysql://host=foo;dbname=bar');
} catch (Exception $e) {
//Inspecter l'exception
$message = $e->getMessage();
echo "Mmmm, impossible d'accéder à la base de données. Veuillez rééssayer plus tard.";
}Pour palier à toutes les éventualités :
//Définition d'un gestionnaire d'erreurs global
//Remarque: certaine fonctions PHP utilisent encore le systĂšme de reporting, comme fopen
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
//Transformer l'erreur en Exception avec la classe dédiée ErrorException
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
});
//Définition d'un gestionnaire d'exceptions global
set_exception_handler(function (\Throwable $e) {
echo 'Une Exception a été détectée. Nous mettons fin au programme.' . PHP_EOL;
var_dump($e->getMessage());
});
try {
//On essaie d'ouvrir un fichier foo.php qui n'existe pas. Fatal Error (ancien systĂšme)
fopen('foo.php', 'r');
} catch (Error $e) {
echo 'Vous ne verrez jamais ce message car fopen n\'émet pas d\'erreurs de type Error. Cette fonction utilise toujours l\'ancien systÚme de reporting' . PHP_EOL;
}PHP est un langage :
Sans compilateur, les erreurs arrivent donc⊠à lâexecution !
SâĂ©quiper des bons outils :
PHP, grĂące Ă sa longĂ©vitĂ© et communautĂ© dispose de nombreux outils trĂšs puissants et matures pour assister le dĂ©veloppement ! Voir dâautres outils utiles
Preferences/Configure Snippets
(php.json):{
"php": {
"prefix": "php",
"body": [
"<?php $0 ?>"
],
"description": "Open and close a php tag"
},
"ph": {
"prefix": "ph",
"body": [
"<?php",
"$0"
],
"description": "Open a php tag (without closing it)"
},
}Utiliser un linter, comme PHP_CodeSniffer. Outil essentiel de dĂ©veloppement pour assurer un code propre et cohĂ©rent, pour le versionnement (mĂȘme rĂšgles pour toute lâĂ©quipe).
PHP_CodeSniffer est composé de deux programmes :
phpcs : détecte la violation de
standards dans les fichiers PHP, CSS et JS
(code sniffer)phpcbf : corrige automatiquement les
violations détéctées (code
beautifier and fixer)Remarque : Le projet a récemment changé de mainteneur officiel !
Installation globale
composer global require "squizlabs/php_codesniffer=*"ou locale au projet :
composer require --dev "squizlabs/php_codesniffer=*"Le mot clef
globaldit Ă Composer dâinstaller le module globalement pour quâil soit accessible Ă tous les projets sur la machine hĂŽte.
PHP_CodeSniffer #Si installĂ© globalement, sâassurer que le rĂ©pertoire
composer global est sur le PATH, puis :
phpcs -h
phpcbf -h
Si installé localement, à la racine du projet :
./vendor/phpcs -h
./vendor/phpcbf -h
PHP_CodeSniffer #Avec les standards par défaut (standard PEAR) :
phpcs /path/to/code-directoryAvec dâautres standards, par exemple PSR12 :
phpcs --standard=PSR12 /path/to/code-directoryAppliquer un standard par défaut :
phpcs --config-set default_standard PSR12à intégrer dans une pipeline CI !
phpcsretourne un code non zĂ©ro en cas dâerreur (echo $?)
# Lister les standards installés
phpcs -i
phpcs --standard=PSR12 srcPuis, pour corriger les violations qui peuvent lâĂȘtre de maniĂšre automatique :
phpcbf --standard=PSR12 srcCorriger les autres manuellement.
VĂ©rifier jusquâĂ ce que phpcs retourne un code Ă©gal
Ă 0 (aucune erreur) : echo $?
phpcs et
phpcbf :# Vérifier que le standard WordPress est bien installé
phpcs -i
# Vérifier que la config utilisée
phpcs --config-show
# "Sniffer" avec progress bar
phpcs -ps --standard=WordPress src Lâextension fournit une interface dans VS Code aux programmes
phpcsetphpcbf(installés localement ou globalement), elles ne les installent pas !
PHP Sniffer & Beautifier comme
formateur par dĂ©faut.Analyse statique de code pour dĂ©tecter des bugs, erreurs avant lâexecution.
./vendor/bin/phpstan analyze -l8 srcà intégrer dans une pipeline CI !
phpcsretourne un code non zĂ©ro en cas dâerreur (echo $?)
Utiliser PHP Documentor pour générer automatiquement la documentantion de référence du projet. Basé sur les DocBlock
DocBlock nâest pas natif au PHP. Format de commentaire.
Version standalone avec Docker :
#télécharger et éxecuter l'image docker phpdoc
docker run --rm -v "$(pwd):/data" "phpdoc/phpdoc:3"
#créer un alias pour l'usage
alias phpdoc="docker run --rm -v $(pwd):/data phpdoc/phpdoc:3"Générer la doc du dossier src :
docker run --rm -v "$(pwd):/data" phpdoc/phpdoc:3 -d src -t docsTests et suites de test avec PHPUnit :
#installer
composer require --dev phpunit/phpunit ^12
#tester
./vendor/bin/phpunit --versionCréer un test unitaire dans le fichier
tests/PostTest.php :
<?php
use PHPUnit\Framework\TestCase;
use Ps\App\Models\Post;
final class PostTest extends TestCase
{
protected function setUp(): void
{
// Réinitialiser $last_id avant chaque test
$reflection = new \ReflectionProperty(Post::class, 'last_id');
$reflection->setAccessible(true);
$reflection->setValue(null, 1);
}
public function testAutomaticIdIncrement(): void
{
$post1 = new Post(slug: 'first', title: 'First Post', date_publication: new \DateTimeImmutable(), author: 1);
$post2 = new Post(slug: 'second', title: 'Second Post', date_publication: new \DateTimeImmutable(), author: 2);
$this->assertSame(1, $post1->id);
$this->assertSame(2, $post2->id);
}
}Lancer les tests :
./vendor/bin/phpunit --testdox tests
echo $?Si ce nâest pas encore le cas, tester ces outils sur
le projet demo_autoloading.
WordPress 1.0 date de 2004. A cette époque, PHP était à la version 4.X !
WordPress a construit son écosystÚme et ses conventions pour compenser les limitations de PHP 4, tout en voulant rester rétrocompatible avec les anciennes versions de PHP :
âPHP is deadâ = âPHP 4 is deadâ ! PHP moderne commence avec PHP 5 et parachevĂ© avec PHP 7.
Quelques conventions à appliquer pour développer sous WordPress :
function vendor_afficher_message() {}
class Vendor_Admin {}<?php et
?> (pas de <?, ni
<?=);Utiliser uniquement des lettres minuscules pour les
variables, les actions/filtres et les fonctions (ne jamais
utiliser le camelCase) :
function some_name( $some_variable ) {}Les fichiers doivent ĂȘtre nommĂ©s de maniĂšre
descriptive en utilisant des lettres
minuscules. Des tirets doivent séparer les
mots (mon-plugin-nom.php);
class Walker_Category extends Walker {}
class WP_HTTP {}
interface Mailer_Interface {}
trait Forbid_Dynamic_Properties {}
enum Post_Status {}Les fichiers contenant des classes préfixées
:class-*.php
Les tableaux doivent ĂȘtre dĂ©clarĂ©s en utilisant la syntaxe longue :
$arr = [ 1, 2, 3 ]; //Non (sort array syntax)
$arr = array(1, 2, 3); //Oui (long array syntax)Utiliser le type hinting;
En gĂ©nĂ©ral, la lisibilitĂ© prime sur lâingĂ©niositĂ© ou la concision (pas de code âcleverâ);
Pas de commandes shell (no backtick operator)
La plupart de ces conventions de syntaxe peuvent ĂȘtre appliquĂ©es via les coding standards+linter !
Les formulaires exposĂ©es par un service web permettent Ă nâimporte quel client de soumettre des donnĂ©es. Le client peut envoyer nâimporte quelle donnĂ©e, autant de fois quâil le dĂ©sire et peut essayer, sâil est mal intentionnĂ©, de briser ou de pĂ©nĂ©trer dans votre service web.
Chaque formulaire, requĂȘte auprĂšs dâune base de donnĂ©es, dâun service web, etc. agrandit la surface dâattaque de votre service web.
RĂšgle dâor : NE JAMAIS FAIRE CONFIANCE AU CLIENT, NI Ă AUCUNE DONNEE VENANT DE LâEXTERIEUR.
Toute donnĂ©e provenant de lâextĂ©rieur doit ĂȘtre validĂ©e.
Pour sécuriser les données entrantes dans le site web, il existe plusieurs stratégies :
LâĂ©chappement est, avec la validation, le meilleur mĂ©canisme de dĂ©fense pour votre application. Contrairement au filtrage (sanitization), lâĂ©chappement ne modifie pas les donnĂ©es entrantes mais sâassure que, dans un contexte donnĂ©, cette donnĂ©e soit utilisĂ©e de maniĂšre sĂ©curisĂ©e en Ă©chappant (rendant inoffensif) les caractĂšres jugĂ©s dangereux pour ce contexte.
Par exemple :
Dans un contexte de page HTML (ce qui nous intéresse ici), il
faut Ă©chapper tous les caractĂšres permettant dâinjecter des balises :
<, >, />, etc. Pour
cela, utiliser toujours la
fonction PHP htmlentities() quand vous générer une page
HTML avec des donnĂ©es provenant de lâextĂ©rieur du script
(données envoyées par le client, données récupérées depuis une base de
données).
Dans un contexte de requĂȘte SQL, il faut Ă©chapper les caractĂšres
qui essayent de modifier la structure de la requĂȘte SQL: ',
--, ", _, etc. Cette attaque est
connue sous le nom dâinjection
SQL. Pour échapper ces caractÚres, on utilise des
requĂȘtes prĂ©parĂ©es.
On verra les abstractions que nous offre WordPress pour cela (santitize, escape)
Recommandation/Mantra : Donât sanitize input. Escape output.
Pour lâĂ©chappement, on pourra utiliser un moteur de Templates Twig
Identifier clairement les donnĂ©es dans votre script PHP qui proviennent de lâextĂ©rieur (la base de donnĂ©es est aussi lâexterieur !), et sont potentiellement dangereuses pour votre service, et celles qui ont Ă©tĂ© validĂ©es.
La validation est le cas idĂ©al et doit ĂȘtre utilisĂ©e dĂšs que possible. Cela consiste Ă comparer la valeur fournie Ă une liste de valeurs acceptĂ©es. Si la valeur fournie nâest pas dans la liste, elle est rejetĂ©e (liste blanche)
//Liste des valeurs acceptées
$acceptedColors = ['red', 'white','blue'];
//Si la donnée 'color' soumise est dans la liste, on l'accepte et on poursuit le traitement
if(isset($_POST['color']) && in_array($_POST['color'], $acceptedColors)){
$clean['color'] = $_POST['color'];
}else{
//Indiquer une erreur
echo "Couleur invalide";
}WordPress nous fournit quelques
fonctions utiles (formatting.php) pour valider les
données :
is_email()
term_exists()
username_exists()
validate_file()
etc...
// Voir toutes les fonctions de la forme *_exists(), *_validate(), and is_*()Ă utiliser au maximum !
WordPress nous fournit un
ensemble de fonctions utiles dans formatting.php pour
échapper dans le contexte HTML (templates) :
esc_html__(): string // Retrieves the translation of $text and escapes it for safe use in HTML output.
esc_html_e() : void // Displays translated text that has been escaped for safe use in HTML output.
esc_attr__() : string // Retrieves the translation of $text and escapes it for safe use in an attribute.
esc_attr_e() : void // Displays translated text that has been escaped for safe use in an attribute.htmlentities() !e === âechoâ pour âdisplayâ.
Lorsquâil nâest pas possible de valider des donnĂ©es (un nom, un prĂ©nom par exemple), i.e quand le nombre possible de rĂ©ponses est impossible Ă dĂ©terminer Ă lâavance, on peut ajouter une couche de filtrage des donnĂ©es.
Filtrer (sanitize) les données consister à modifier
les données entrantes pour les rendre inoffensives, généralement en
supprimant ou remplaçant des caractÚres. Par exemple, en
retirant tous les caractĂšres spĂ©ciaux qui pourraient ĂȘtre utilisĂ©s pour
former des balises HTML, Ă savoir <, >
et />.
Pour cela, en PHP, on peut utiliser les fonctions filter_var, filter_input, filter_input_array
WordPress nous fournit tout un
ensemble de fonctions utiles dans formatting.php
sanitize des données :
sanitize_email()
sanitize_file_name()
sanitize_hex_color()
sanitize_hex_color_no_hash()
sanitize_html_class()
sanitize_key()
sanitize_meta()
sanitize_mime_type()
sanitize_option()
sanitize_sql_orderby()
sanitize_term()
sanitize_term_field()
sanitize_text_field()
sanitize_textarea_field()
sanitize_title()
sanitize_title_for_query()
sanitize_title_with_dashes()
sanitize_user()
sanitize_url()Regardons sanitize_text_field :
Utiliser les conventions de WordPress pour toute le code interaggisant avec le core de WordPress :
snake_case pour les fonctions et
hooks, Pascal_Case pour les classes.Appliquer les mĂȘmes rĂšgles dans tout votre projet (cohĂ©rence), appliquer la plupart des rĂšgles du core a des bĂ©nĂ©fices :
Continuité/Homogénéité entre votre code et les sources du core (smooth transition);
Mieux comprendre le code source du core;
Conventions utilisĂ©es (gĂ©nĂ©ralementâŠ) par les autres dev WordPress;
Penser à sécuriser les données (validation, échappement et sanitization) avec les primitives fournies soit par WordPress, le cas échéant par PHP.
Utiliser le kit de développement et le thÚme de départ.
index.php du thĂšme pour afficher la liste des
posts.//fichier web/wp-content/themes/mon-theme/index.php
<?php get_header(); ?>
//Votre code ici...
<?php get_footer(); ?>Utiliser le template HTML suivant pour chaque post:
...
<article>
<h2>{titre}</h2>
<p>{résumé (excerpt)}</p>
<a href="{permalien}">Lire la suite</a>
</article>
...Sâil nây pas de posts publiĂ©s, la page doit afficher
"Aucun contenu trouvé". Vérifier que dans
Settings/Reading lâoption
Your Home page displays soit Ă©gale Ă
Your Latest Posts.
the_title(),
the_exceprt() et the_permalink(). Les
template tags (vue à la prochaine séance) sont des fonctions
WordPress qui permettent de rĂ©cupĂ©rer ou dâafficher des contenus
dynamiques, principalement dans la Loop WordPress.Settings/Reading, vous
changez lâoption Your Home page displays Ă
A static page (choisir une page) ? Pourquoi ?<em>Hello !</em>.
Observez le rĂ©sultat sur la page dâaccueil. Est-ce que
le template tag the_title échappe le contenu ?
Est-ce normal ? Consultez
la documentation pour le savoir. Que faut-il en déduire ?Créer un plugin de développement dédié au debug. Ce plugin servira à centraliser les outils de debug et pourra embarquer des composants installés via Composer. Il devra fournir :
write_log améliorée,
plus pratique que la fonction native error_log :if ( ! function_exists( 'write_log' ) ) {
function write_log( $log ) {
if ( true === WP_DEBUG ) {
if ( is_array( $log ) || is_object( $log ) ) {
error_log( print_r( $log, true ) );
} else {
error_log( $log );
}
}
}
}mon-plugin-debug-tools dans le dossier
wp-content/plugins avec la structure suivante :ââ mon-plugin-debug-tools.php
ââ src/
ââ functions.phpcomposer init;write_log Ă
functions.php;register_activation_hook et
register_deactivation_hook;Câest lâoccasion de dĂ©couvrir les hooks et le dĂ©veloppement de plugins. Nous y reviendras plus tard dans la formation.
Optionnelle, pour aller plus loin, donner Ă rĂ©flĂ©chirâŠ
Pour écrire des templates :
Le principe dâun template est dâĂ©laborer des modĂšles statiques de pages HTML dans lesquels viendront sâinsĂ©rer des donnĂ©es dynamiques, rĂ©sultat de lâexĂ©cution dâun programme.
Le templating permet de faire la séparation entre données statiques (présentation) et données dynamiques (résultat du traitement).
Nâest-ce pas une dĂ©finition possible de PHP et lâorigine de son succĂšs ?!
Twig (3.X) est un moteur de templates fast (compilé), secure (échappement par défaut) and flexible (on va voir pourquoi).
composer require "twig/twig:^3.0"PHP est par définition un moteur de templates. Alors, pourquoi ne pas utiliser directement PHP ?
Fabien Potencier, créateur (entre autres) du framework Symfony, a écrit un long billet intéressant à ce sujet.
InconvĂ©nients dâutiliser vanilla PHP comme moteur de template :
En PHP, pour Ă©chapper les caractĂšres spĂ©ciaux (et dangereux dans le contexte dâune page web !), il faut utiliser la fonction htmlspecialchars, qui convertit les caractĂšres spĂ©ciaux en entitĂ©s HTML, inoffensives.
Template en PHP :
<?php echo $var ?>
<?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>Template Twig :
{# Par défaut Twig échappe les caractÚres spéciaux #}
{{ var }}
PHP :
<?php if ($products): ?>
<?php foreach ($products as $pdocut): ?>
<?php if($product->isAvailable()):>
* <?php echo $item ?>
<?php endif;>
<?php endforeach; ?>
<?php else: ?>
No product has been found.
<?php endif; ?>Twig :
{% for product in products | product.isAvailable %}
* {{ product }}
{% else %}
No product has been found.
{% endfor %}
DĂ©monstration de lâĂ©chappement et des risques Ă oublier (souvent) de le faire.
Twig vient avec son propre langage et son systĂšme de balises
Principalement trois systĂšmes de balise Ă retenir:
{# ... #} : un commentaire ;{% ... %} : executer une instruction ;{{ ... }} : écrire quelque chose sur la sortie
standard.{# Execute une instruction: block, extends, function #}
{% ... %}
{# Print quelque chose}
{{ ... }}
Pour en savoir plus consulter la documentation.
Ătendre et réécrire un template existant: extends et block
Twig permet dâĂ©crire des templates de maniĂšre plus sĂ©curisĂ©e et plus lisible que PHP.
Il permet surtout dâĂ©tendre et de réécrire une partie dâun template existant.
Vous pouvez réutiliser des templates (avec
lâinstruction extends) et les surcharger (en redĂ©finir des
sections) via le systĂšme dâhĂ©ritage et de block.
Template parent/principal:
{# Fichier parent.html.twig #}
{# Définition d'un block: un espace réinscriptible dans le template #}
{% block main %}
Contenu de parent.html.twig
{% endblock %}
Template réutilisant le template parent:
{# Fichier child.html.twig #}
{% extends 'parent.html.twig' %}
{%block main %}
Ce contenu écrase le contenu du template parent
{% endblock %}
Ce systÚme, simple et puissant, permet de développer une approche composant des templates et de mutualiser les éléments communs.
Template parent/principal:
{# Fichier parent.html.twig #}
{# Définition d'un block: un espace réinscriptible dans le template #}
{% block main %}
Contenu de parent.html.twig
{% endblock %}
Template réutilisant le template parent et le contenu du bloc
main parent (fonction parent()):
{# Fichier child.html.twig #}
{% extends 'parent.html.twig' %}
{%block main %}
{# Utiliser le contenu du bloc parent #}
{{parent()}}
Ajouter le contenu spécifique au template
{% endblock %}
Inclure des templates avec include :
{# Fichier child.html.twig #}
{% extends 'parent.html.twig' %}
{%block main %}
{{include ('publicite.html.twig')}}
{% endblock %}
Le path des fichiers
.twiginclus est relatif au path du dossiertemplates.
Déclarer et assigner des variables dans un template.
{% set foo = 'bar'%}
{% set bar = {'key': 'value'} %}
Twig propose tout un mĂ©canisme de filtres trĂšs puissant (built-in et que lâon peut dĂ©velopper soi-mĂȘme), permettant dâĂ©crire des templates concis et efficaces pour prĂ©senter les donnĂ©es.
Ă lâinstar de la philosophie
des programmes Unix, les filtres utilisent le systĂšme de
pipe (|), pour pouvoir se servir dâune sortie dâun
filtre comme entrĂ©e dâun autre filtre.
{# Exemples d'utilisation de filtre: sans paramÚtre, avec paramÚtres, enchaßnés via le pipe}
{{ variable | nom_du_filtre }}
{{ variable | nom_du_filtre('param1','param2') }}
{{ variable | filtre1 | filtre2 | filtre3 }}
Exercice : Générer une page web qui affiche 1000 nombres aléatoires compris entre 0 et 100. Les présenter dans un template Twig, puis afficher uniquement les nombres supérieurs à 50 et impairs.
{# On a donné à notre template Tiwg le tableau contenant les nombres aléatoires sous la variable numbers #}
<h2>Random Numbers !</h2>
{% for number in numbers %}
{{ number }}
{% endfor %}
<h2>Nombres impairs supérieurs à 50</h2>
{% for number in numbers | filter (number => number > 50 && is odd) %}
{{ number }}
{% endfor %}
Twig propose un ensemble de fonctions.
path(): retourne une URL relativeurl():retourne une url absolue (avec le nom de
domaine)form():permet de présenter un formulaire HTMLdump():print pour le debugasset(path): permet de charger un asset (fichier CSS,
JS, image, font, etc.) en fournissant son cheminsnake_case, par exemple
navbar_offre_speciale.html.twig;extends) a la racine
du dossier templates;include) dans un
dossier template/parts;template/nom_controleur/nom_du_template.html.twig;Pour VS Code:
.twig : File > Preferences > Settings
> Emmet > Include Languages : ajouter Item:
twig Value: html;Timber est un composant crĂ©e pour faire lâinterface entre le core WordPress (template hierarchy, hooks) et Twig.
Installer Timber comme une dépendance :
composer require timber/timberCharger les dépendances, par exemple dans wp-config.php
:
require_once __DIR__ . '/vendor/autoload.php';Structure du thĂšme :
mon-theme/
functions.php
style.css
views/
index.twig <= template Twig associé (map un à un)
index.php <= template WordPress classiqueInitialiser Timber :
//Dans functions.php
<?php
use Timber\Timber;
new Timber();Template WordPress (index.php) :
//index.php
<?php
//Contient tout le contexte ($wp_query)
//Ajouter données (logique métier) au context ici
$context = Timber::context();
Timber::render('index.twig', $context);Template Twig associé (views/index.twig) :
<!DOCTYPE html>
<html>
<body>
<h1>{{ site.name }}</h1>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
{{ post.content }}
</article>
{% endfor %}
</body>
</html>
Attention, Timber nâest pas forcĂ©ment la solution la plus adaptĂ©e en fonction de la nature et de lâĂ©tat de votre projet :
Timber/Twig est idĂ©al pour les thĂšmes custom maĂźtrisĂ©s, oĂč lâon contrĂŽle le markup et lâarchitecture.
Il est toujours possible dâutiliser Timber/Twig. Seulement il faut mesurer le coĂ»t/bĂ©nĂ©fices en fonction du projet. Si le projet repose sur un thĂšme existant, FSE, un page builder, ou un plugin imposant son rendu, la bascule vers Twig a un coĂ»t Ă©levĂ© et doit ĂȘtre pesĂ©e soigneusement.
Liste de ressources utiles pour PHP et pour continuer Ă se former.