ENI Service - WordPress - PHP

Paul Schuhmacher

Durée : 28h

Novembre 2025

Partie 2 : PHP pour Wordpress

bg right:35% w: 80%

Source du post

Partie 2 : PHP pour Wordpress #

bg right contain w: 80%

Partie 2 : PHP pour WordPress #

Objectif : mise à niveau sur PHP moderne, orienté WordPress.

PHP (re)voir les bases #

À parcourir en fonction des compĂ©tences Ă  consolider.

Portrait général de PHP #

D’oĂč vient PHP ? #

Plusieurs implémentations de PHP #

PHP dans le monde réel #

PHP fait tourner le web mondial !

Qui utilise PHP ? #

Installer et tester PHP #

Suivre les instructions donnĂ©es sur le site officiel en fonction de l’OS de votre machine.

Suivre le tutoriel officiel.

Votre premier script PHP #

Créer un fichier hello-world.php :

hello, world

Exécuter :

php hello-world.php

Votre premier script PHP avec du code php #

<html>
    <body>
        <?php echo "hello, world"; ?>
    </body>
</html>
<?php echo "hello, world"; ?>
php index.php

Que s’est-il passĂ© ? #

Un script PHP s’exĂ©cute sur la machine virtuelle ou interprĂ©teur PHP. Il analyse le fichier byte par byte :

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.

ModĂšle d’exĂ©cution #

bg right contain w: 80%

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

Machine virtuelle PHP #

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 :

Configurer PHP au runtime #

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=diff

Il existe de nombreuses directives pour configurer PHP Ă  l’exĂ©cution, utiles notamment pour adapter le comportement en fonction des environnements.

PHP (SAPI CLI) #

Exécuter directement le script dans le terminal :

php votre-script.php

La sortie (résultat) sera affichée dans le terminal.

PHP (SAPI CLI) #

Exécuter directement le script dans le terminal, en chargeant une configuration personnalisée :

php -c mon-fichier.ini votre-script.php

Serveur web intégré de PHP (SAPI CLI) #

PHP dispose d’un serveur web intĂ©grĂ©, utilisant la SAPI CLI.

  1. Ouvrir un terminal,
  2. Se placer Ă  la racine du projet, du fichier ou dossier Ă  servir comme un site web (au mĂȘme niveau) :
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 public

Le serveur web intĂ©grĂ© de PHP attend un fichier index.php par dĂ©faut. Penser Ă  toujours utiliser un fichier index.php comme point d’entrĂ©e de votre site web.

Exemple #

  1. Créer un dossier public;
  2. Dans le dossier public, créer un fichier index.php avec le contenu suivant :
<?php 
echo "<h1>Hello, world</h1>";
  1. À la racine, servir tout le dossier avec le serveur intĂ©grĂ© de PHP sur, par exemple, le port 8000 :
php -S localhost:8000 -t public
  1. Avec votre navigateur favori, se rendre Ă  l’URL http://localhost:8000. Vous devriez voir le rĂ©sultat de l’execution du script index.php.

Syntaxe et features du langage #

Les variables en 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 != $A

PHP est un langage typé dynamiquement #

En 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.

Inspecter #

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);

Sur les opérateurs PHP #

$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];

Opérateurs Null coalescing #

Null coalescing operator ??

// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

Opérateurs de comparaison #

Comparaison :

<?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;

Opérateurs de comparaison et float #

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";
}

Constantes #

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.

Structures de contrĂŽle : 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";
    }
}

Structures de contrĂŽle : 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

Structures de contrĂŽle : 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);

En savoir plus sur while, sur do-while

Structures de contrĂŽle : while et do-while, syntaxe alternative #

<?php

$i = 1;
while ($i <= 10) :
    echo $i++;  
endwhile;

Structures de contrĂŽle : 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;
}

En savoir plus sur switch

Syntaxe alternative #

$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 #

bg right contain
<?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 goto statements in the programs they produce”. xkcd: goto

Structures de contrĂŽle : 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";
}

En savoir plus sur foreach

Structures de contrĂŽle : 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;

Syntaxes alternatives dans les templates #

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>";

Structures de contrĂŽle, syntaxes alternatives dans les templates #

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


Syntaxes alternatives dans les templates (foreach) #

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.

En savoir plus sur la syntaxe alternative

Syntaxes alternatives dans les templates (if/else) #

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 concret d’usage de la syntaxe alternative dans les templates d’un thùme Wordpress #

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;?>

Les chaĂźnes de caractĂšres #

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

Quoting : simple vs double #

Les chaĂźnes de caractĂšres : simple quotes #

<?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';

Les chaĂźnes de caractĂšres : double quotes #

$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";

Syntaxe Heredoc #

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 !

Syntaxe Heredoc : stocker le résultat dans une variable #

$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;

Quelques fonctions utiles pour manipuler les chaĂźnes de caractĂšres #

Voir la liste des fonctions natives de PHP pour manipuler les chaines

Exemple Ă  copier/coller et parcourir #

<?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>';

Cas des chaĂźnes multi-bytes et encodage #

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.

echo strlen("été") . PHP_EOL; // 5
echo mb_strlen("été") . PHP_EOL; //3
echo mb_internal_encoding(); // UTF-8, par défaut

Encode 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

Utiliser la documentation officielle #

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): int

Ignorer 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 #

Les tableaux PHP sont trĂšs (trop !) puissants et versatiles :

Les tableaux PHP, exemple #

<?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 savoir plus

Les tableaux associatifs en PHP : parcourir, ajouter, supprimer #

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']);

(Quelques) Fonctions utiles sur les tableaux #

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 #

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 !";
}

En savoir plus

Déclarer et utiliser ses fonctions en PHP #

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');

En savoir plus sur les fonctions.

Hoisting (remontée en haut de la portée avant exécution) #

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";
  }
}

Portée des variables #

Portée locale vs globale #

<?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 !

Portée globale avec 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(); //1

Cette instruction sera utile car WordPress utilise intensivement plusieurs variables globales !

Functions as first class citizens #

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)

Exemple de closure #

<?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;
});

Exemple de closure #

<?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));

Fonctions dans les templates, alternance PHP / HTML #

<?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).

Passage par valeur #

En PHP, comme en C, tout argument est passé à une fonction par valeur (copie locale).

Passage par valeur #

<?php
function addToArray(array $arr){
  $arr[] = 'bar';
}

$array = ['foo'];

addToArray($array);

var_dump($array);  //?

Passage par valeur #

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; // ?

Passage par référence #

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.

PHP et son environnement : les variables Super Globales #

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.

RĂ©partir le code PHP dans diffĂ©rents fichiers et l’importer au besoin #

Pour utiliser le code PHP placé dans un autre fichier, on peut utiliser le construct require_once:

<?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 require et require_once.

PrĂ©cisions sur le mĂ©canisme d’import de script PHP #

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 :

PrĂ©cisions sur le mĂ©canisme d’import de script PHP #

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>
p

Type hinting #

PHP 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.

Type hinting en pratique #

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 !

Conversion automatique #

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)

Conversion automatique et typage 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 given

Typage strict #

En résumé #

Programmation orientée objet en PHP #

Les fondamentaux de la programmation orientée objet (POO) en PHP.

Sommaire #

MĂȘmes concepts que dans tous les langages populaires supportant la POO (Java, C#, C++, Python, Kotlin, Dart, etc.)

Classes et objets : un programme composĂ© d’objets #

$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Ă©).

Programmation orientĂ©e objet (vision originale d’Alan Kay) #

bg right:33% w: 80%

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

Classes #

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)

Classes et objets: instancier un objet new et accéder aux propriétés/méthodes #

//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.

Classes et objets: Signature d’une mĂ©thode #

Signature d’une mĂ©thode :

//La signature de la méthode `doStuff` de la classe Foo
public doStuff(array $stuffToDo):void

Classes et objets : Visibilité #

La 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Ă© :

Classes et objets : Visibilité #

w:500

Opérateur de résolution de portée :: #

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 :

Opérateur de résolution de portée :: #

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;

Différence entre 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();

Attributs et méthodes 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();

Null-safe operator #

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;

Exemple sans Null-safe operator #

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;
}

Exemple avec Null-safe operator #

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();

Héritage : 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 !

HĂ©ritage : ContrĂŽle sur l’hĂ©ritage avec le mot clef 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';}
}

Classes et méthodes abstraites: abstract #

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.

Les interfaces: interface et implements #

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 interface est un cas particulier de classe abstraite sans propriétés ni implémentations.

Le polymorphisme #

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)

Interface et type #

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 et type #

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

Quelques principes de design #

Objectifs

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)

Quelques principes de design POO #

Voyons quelques principes de design pour la POO.

Isoler ce qui change de ce qui ne change pas (trop) #

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.

Programmez vers une interface, pas une implémentation (late binding) #

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.

w:600 w:400

PrĂ©fĂ©rez la composition Ă  l’hĂ©ritage (quand c’est possible) #

Passez de la relation est-un et surtout se comporte comme (héritage) à la relation a-un (composition) entre deux classes.

POO et WordPress #

PHP 8.4 (ou 8.5) #

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.

Quelques nouvelles features en vrac #

Promotion du constructeur #

class Point {
  public function __construct(
    public float $x = 0.0,
    public float $y = 0.0,
    public float $z = 0.0,
  ) {}
}

Classes readonly #

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

Match #

echo match (8.0) {
  '8.0' => "Oh no!",
  8.0 => "This is what I expected",
};
//> This is what I expected

Union #

Type hinting permet de faire l’union de diffĂ©rents types de retour

class Number {
  public function __construct(
    private int|float $number
  ) {}
}

new Number('NaN'); // TypeError

Enums et backed enums #

Il 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';
}

En savoir plus sur les énumérations.

Nouveautés de PHP 8.4 #

Voir toutes les nouveautés de PHP 8.4

Nouveautés de PHP 8.5 #

PHP 8.5 sort en novembre 2025 !

Voir toutes les nouveautés à venir de PHP 8.5

Quelques exemples.

Pipe operator |> #

$resultat = "Hello World"
    |> htmlentities(...)
    |> str_split(...)
    |> fn($x) => array_map(strtoupper(...), $x)
    |> fn($x) => array_filter($x, fn($v) => $v !== 'O');

Primitives pour les tableaux : array_first et array_last #

$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"

Autres nouveautés #

PHP Standards Recommandations (PSR) #

Framework 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


PHP Standards Recommandations (PSR) #

Extraits recommandés des PSR-1 et PSR-12 pour le développement avec WordPress :

PHP Moderne : auto-loading et composants #

Pratique : Mini projet sans auto-loading #

Pratique : Mini projet sans auto-loading #

CrĂ©er un site web qui affiche une liste de publications sur sa page d’accueil.

  1. Préparer le projet avec la structure suivante :
demo_autoloading/
├── index.php            
├── config.php      
├── db.php (simulera une petite base de donnĂ©es)             
└── src/
    ├── Model/
        └── User.php
        └── Post.php   

Pratique : Mini projet sans auto-loading #

  1. Créer les modÚles suivants :
    1. User.php définit une classe User. Un User est défini par :
      1. un identifiant unique (int),
      2. un prénom et un nom.
      Un User peut ĂȘtre l’auteur·ice d’une ou plusieurs publications (de type Post).
    2. Post.php définit une classe Post. Une publication (post) est définie par :
      1. un identifiant unique (int),
      2. un slug,
      3. un titre,
      4. une date de publication (de type DateTimeImmutable),
      5. l’identifiant de son auteur·ice (de type User).

Les identifiants des User et Post commencent Ă  1 et doivent ĂȘtre incrĂ©mentĂ©s de maniĂšre automatique.

Pratique : Mini projet sans auto-loading #

  1. Les autres fichiers :
    1. Le fichier index.php est le point d’entrĂ©e de l’application (core et controller), produit la sortie,
    2. Le fichier config.php contient des éléments de configuration du projet:
      1. Définit une constante ABS_PATH égale à __DIR__ . '/'. Remarque : Cette constante vous sera utile pour importer vos scripts;
      2. DĂ©finit une constante POSTS_PER_PAGE Ă©gale Ă  5, indiquant le nombre maximum de posts qui doivent ĂȘtre affichĂ©s sur la page,
    3. Le fichier db.php simulera un accÚs à une source de données (base de données, service web, etc.). Il définit deux collections:
      1. $users : une collection de 2 User,
      2. $posts : une collection de 6 Posts.

Pratique : Mini projet sans auto-loading #

  1. À sa racine (/), 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

Pratique : Mini projet sans autoloading #

bg right contain
  1. Servir le projet avec le serveur intĂ©grĂ© de PHP sur l’URL localhost:5001;
  2. Tester : curl localhost:5001 ou avec votre navigateur favori.

Pratique : Mini projet sans autoloading #

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 !

Accéder à/Télécharger une proposition commentée

Les limites de l’import explicite #

<?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 :

Solution : L’autoloading #

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.

Les espaces de noms (Namespaces) #

Déclarer un namespace #

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 courant

Pour savoir dans quel namespace vous ĂȘtes, utiliser la constante NAMESPACE

Sous-namespaces et hiérarchie #

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.

Namespaces et fichiers #

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.

Namespaces et fichiers #

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.

Avant les namespaces #

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{
  ...
}

Utiliser les namespaces #

Utiliser 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);

Utiliser les namespaces: import et alias #

<?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);

Utiliser 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 use n’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 !

Namespace global #

Il n’est pas nĂ©cessaire d’ajouter le \ devant le nom qualifiĂ© dans une dĂ©claration avec le mot-clĂ© use car PHP assume qu’il est entiĂšrement qualifiĂ©.

Nom non qualifié, qualifié et entiÚrement qualifié #

namespace Bar;
class Foo{}

PHP recherche dans le namespace courant, si non trouvé recherche dans le namespace global

PHP ajoute le namespace courant comme préfixe

PHP l’interprĂšte comme Barsans faire aucune supposition (aucune ambiguĂŻtĂ©)

Exemple d’erreur classique #

<?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 ?

Solution #

<?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 \.

Exercice : se familiariser avec les namespaces et la résolution des noms #

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();

Solution #

<?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"

Gérer les dépendances entre fichiers #

L’autoloading avant, avec __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 !

L’autoloading avant : 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.

ProblĂšmes #

Solution ? Standardiser l’autoloading #

w:500px

xkcd : Standards

L’autoloading maintenant : l’autoloader PSR-4 #

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 : une stratĂ©gie standardisĂ©e #

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)

L’autoloader PSR-4 : une stratĂ©gie standardisĂ©e #

Cette recommandation impose une contrainte sur :

L’autoloader PSR-4 : une stratĂ©gie standardisĂ©e #

Principe : un use est associé à un require Pour cela, la hiérarchie des namespaces doit correspondre à la hiérarchie des fichiers sources.

L’autoloader PSR-4 en pratique #

  1. Point d’entrĂ©e: Faire correspondre un namespace de haut niveau ENI\App Ă  un rĂ©pertoire, par exemple src.
  2. A prĂ©sent PHP sait (grĂące Ă  l’autoloader) que toute classe ou interface dĂ©clarĂ©e dans le namespace ENI\App se trouve dans le dossier src
  3. Organisation de votre code: Faites correspondre les sous-namespace aux sous-dossiers.

Par exemple ENI\App\ModernPHP correspond au dossier src/ModernPHP. La classe ENI\App\ModernPHP\Foo correspond au fichier src/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, le gestionnaire de paquets moderne de PHP #

Composer

Composer est :

Packagist

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 :

Installer 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/composer

Dans un terminal :

composer -V
Composer version 2.3.8 2022-07-01 12:10:47

Utiliser Composer : installer et gérer ses dépendances #

Nous allons installer le composant guzzlehttp/guzzle, un client HTTP puissant et performant.

Les noms du composant

Packagist utilise la convention vendor/package pour éviter les collisions de nom.

Installer un composant: composer require #

composer require guzzlehttp/guzzle

Composer crée deux fichiers :

Versionner ces deux fichiers dans votre contrĂŽle des sources (Git) !

composer install et composer update #

composer //liste toutes les commandes

L’autoloading #

Comment utiliser les composants ?

Dans votre code

<?php
require 'vendor/autoload.php';

Et voilĂ  !

Exemple: utiliser le composant 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 Client existe bien dans le fichier src/Client.php

Créer un composant #

Pourquoi faire de son projet un composant ?

L’autoloading de fonctions #

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).

Remarques sur le versionnement #

Pensez Ă  :

Vous ne voulez pas pousser le code source de vos dépendances avec votre projet. Les fichiers composer.json et composer.lock sont là pour éviter cela !

RĂ©capitulatif : CrĂ©ation d’un projet PHP moderne #

  1. Initialiser votre projet avec composer init (création de composer.json et composer.lock);
  2. Renseigner un namespace (votre namespace <vendor>/<nom application>) mappé au dossier src dans le composer.json;
  3. Placer tout votre code source sous le namespace mappé au dossier src (étape 3);
  4. Versionner votre projet avec Git en incluant les fichiers 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

Pratique #

  1. Refactoriser le projet rĂ©alisĂ© prĂ©cĂ©demment demo_autoloading afin qu’il utilise l’autoloading PSR-4 et le gestionnaire de dĂ©pendances Composer :
    1. Initialiser le projet avec composer init;
    2. Appliquer les modifications nécessaires (retirer les require);
    3. DĂ©placer le fichier congig.php dans src pour qu’il soit chargĂ© automatiquement;
    4. Commentaires :
      1. index.php joue le rîle de client du contenu src. Il invoquera l’autoloader (require ./vendor/autoload.php).
      2. Laisser 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
└── vendor

Pratique #

  1. Installer le composant var_dumper
  2. L’utiliser dans index.php pour afficher la collection de posts.
  3. Créer un fichier .gitignore correcte pour versionner le projet.

En résumé #

La gestion des erreurs en PHP #

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 :

En cas d’erreur #

Niveaux d’erreur et comportements #

Exemple #

<?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 ?

La gestion des erreurs en PHP, what a mess
 #

Bubbling des erreurs #

bg right contain

Une erreur, une fois Ă©mise (throwned), remonte la pile d’exĂ©cution (bubbling de l’erreur) jusqu’à ĂȘtre attrapĂ©e:

Gestionnaire d’erreurs global #

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;
});

Gestionnaire d’erreurs local, avec un bloc 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;

La gestion des erreurs en PHP, what a mess
 #

<?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;
}

RĂšgles de gestion des erreurs #

Voici les rĂšgles que vous devriez suivre :

Exemple de configuration des logs #

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;
}

Configuration du reporting (DEV) #

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.log
php -c php-dev.ini error.php 

Voir les directives pour configurer PHP au runtime

Configuration du reporting (PROD) #

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.log
php -c php-prod.ini error.php 

Ne lancez pas des erreurs, gérez-les #

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.

Gestion des erreurs avec les exceptions #

bg right contain w: 60%

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.

En savoir plus sur le systùme d’exceptions de PHP

Créer ses propres exceptions #

Vous pouvez définir vos propres exceptions, en étendant la classe Exception ;

class MyException extends Exception { }

Gestion locale des Exceptions #

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.";
}

Gestion complĂšte des erreurs #

bg right contain

Pour palier à toutes les éventualités :

Gestion complĂšte des erreurs, exemple #

//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;
}

Tooling et qualité du code en PHP #

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

Configurer son IDE, avec VS Code #

{
    "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)"
 },
}

Configurer son IDE, avec VS Code #

PHP_CodeSniffer et coding standards #

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 :

Installer PHP_CodeSniffer (via Composer) #

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 global dit à Composer d’installer le module globalement pour qu’il soit accessible à tous les projets sur la machine hîte.

Tester l’installation de 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

Utilisation de PHP_CodeSniffer #

Avec les standards par défaut (standard PEAR) :

phpcs /path/to/code-directory

Avec d’autres standards, par exemple PSR12 :

phpcs --standard=PSR12 /path/to/code-directory

Appliquer un standard par défaut :

phpcs --config-set default_standard PSR12

À intĂ©grer dans une pipeline CI ! phpcs retourne un code non zĂ©ro en cas d’erreur (echo $?)

Pratique : Appliquer les codings standards PSR12 au projet #

# Lister les standards installés
phpcs -i
phpcs --standard=PSR12 src

Puis, pour corriger les violations qui peuvent l’ĂȘtre de maniĂšre automatique :

phpcbf --standard=PSR12 src

Corriger les autres manuellement.

VĂ©rifier jusqu’à ce que phpcs retourne un code Ă©gal Ă  0 (aucune erreur) : echo $?

Utiliser les standards de WordPress (core) #

  1. Installer les codings standards utilisés par WordPress avec composer;
  2. Utiliser les coding standards avec 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 

Utilisation dans VS Code #

  1. Installer l’extension PHP Sniffer & Beautifier

L’extension fournit une interface dans VS Code aux programmes phpcs et phpcbf (installĂ©s localement ou globalement), elles ne les installent pas !

  1. Choisir PHP Sniffer & Beautifier comme formateur par défaut.

Analyse statique de code avec PHPStan #

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 ! phpcs retourne un code non zĂ©ro en cas d’erreur (echo $?)

Générer de documentation à partir des Docblocks avec PHP Documentor #

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"

Pratique #

Générer la doc du dossier src :

docker run --rm -v "$(pwd):/data" phpdoc/phpdoc:3 -d src -t docs

Tests unitaires avec PHPUnit #

Tests et suites de test avec PHPUnit :

#installer
composer require --dev phpunit/phpunit ^12
#tester
./vendor/bin/phpunit --version

Suivre le guide

Pratique #

Cré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 $?

Pratique #

Si ce n’est pas encore le cas, tester ces outils sur le projet demo_autoloading.

En résumé #

PHP et WordPress #

PHP et WordPress #

PHP et WordPress, une histoire Ă  part #

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.

Conventions dans WordPress 1/3 #

Quelques conventions à appliquer pour développer sous WordPress :

function vendor_afficher_message() {}
class Vendor_Admin {}

Conventions dans WordPress 2/3 #

Conventions dans WordPress 3/3 #

class Walker_Category extends Walker {}
class WP_HTTP {}

interface Mailer_Interface {}
trait Forbid_Dynamic_Properties {}
enum Post_Status {}

La plupart de ces conventions de syntaxe peuvent ĂȘtre appliquĂ©es via les coding standards+linter !

Sécurisation des données #

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.

Sécurisation des données en PHP #

Pour sécuriser les données entrantes dans le site web, il existe plusieurs stratégies :

Échappement des caractĂšres spĂ©ciaux #

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 :

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

Sécurisation des données : Validation #

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";
}

Sécurisation des données : Validation dans WordPress #

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 !

SĂ©curisation des donnĂ©es : Échappement des caractĂšres spĂ©ciaux #

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.

e === ‘echo’ pour “display”.

Sanitization/Filtrage #

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

Sécurisation des données: Sanitization #

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()

Exemple sanitize_text_field #

Regardons sanitize_text_field :

En résumé #

Utiliser les conventions de WordPress pour toute le code interaggisant avec le core de WordPress :

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 :

Pratique : PHP et WordPress #

Utiliser le kit de développement et le thÚme de départ.

  1. Coder la Loop WordPress dans le fichier 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.

Pratique : PHP et WordPress #

  1. Réécrire le code prĂ©cĂ©dent en utilisant les template tags suivantes : 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.
  1. Que se passe-t-il si, dans Settings/Reading, vous changez l’option Your Home page displays à A static page (choisir une page) ? Pourquoi ?
  2. Mettre des balises HTML dans le titre d’un article. Par exemple <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 ?
  3. Listez quelques autres template tags permettant d’afficher ou de rĂ©cupĂ©rer les donnĂ©es d’un post.

Pratique : PHP et WordPress #

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 :

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 );
   }
  }
 }
}

Pratique : PHP et WordPress #

  1. Créer un dossier mon-plugin-debug-tools dans le dossier wp-content/plugins avec la structure suivante :
├─ mon-plugin-debug-tools.php
└─ src/
   └─ functions.php
  1. Dans ce dossier, initialiser un projet avec composer init;
  2. Installer le composant VarDumper;
  3. Ajouter le code de la fonction write_log Ă  functions.php;
  4. Regarder dans le guide comment créer un plugin. Utilisez les fonctions register_activation_hook et register_deactivation_hook;
  5. Tester votre plugin dans votre thĂšme.

C’est l’occasion de dĂ©couvrir les hooks et le dĂ©veloppement de plugins. Nous y reviendras plus tard dans la formation.

Annexe : Templating avec Twig #

Optionnelle, pour aller plus loin, donner à réfléchir


Templating avec Twig #

Pour écrire des templates :

Templating avec Twig #

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 #

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"

Pourquoi utiliser Twig et non PHP directement ? #

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 :

Exemple Twig vs PHP : échappement des caractÚres spéciaux #

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 }}

Langage orienté template : Boucles et conditions #

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 %}

Un mot sur l’échappement, sĂ©curitĂ© cĂŽtĂ© client #

DĂ©monstration de l’échappement et des risques Ă  oublier (souvent) de le faire.

Don’t try to sanitize input. Escape output

Langage Twig #

Twig vient avec son propre langage et son systĂšme de balises

Principalement trois systĂšmes de balise Ă  retenir:

{# 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

Templating : héritage et surcharge, vers une approche composants #

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.

Templating : héritage et surcharge #

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.

Templating : héritage et surcharge #

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 ou fragments dans d’autres templates #

Inclure des templates avec include :

{# Fichier child.html.twig #}
{% extends 'parent.html.twig' %}
{%block main %}
  {{include ('publicite.html.twig')}}
{% endblock %}

Le path des fichiers .twig inclus est relatif au path du dossier templates.

Déclarer et manipuler des variables locales au template #

Déclarer et assigner des variables dans un template.

{% set foo = 'bar'%}
{% set bar = {'key': 'value'} %}

Manipuler les données avec les filtres #

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 }}

Exemple d’usage de filtres #

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 %}

Fonctions #

Twig propose un ensemble de fonctions.

Organiser ses templates : quelques conventions, recommandations #

Outils utiles ? #

Pour VS Code:

Utiliser Twig dans une application WordPress #

Timber est un composant crĂ©e pour faire l’interface entre le core WordPress (template hierarchy, hooks) et Twig.

Utiliser Twig dans une application WordPress. Principe #

Installer Timber comme une dépendance :

composer require timber/timber

Charger 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 classique

Utiliser Twig dans une application WordPress. Principe #

Initialiser Timber :

//Dans functions.php
<?php
use Timber\Timber;
new Timber();

Utiliser Twig dans une application WordPress. Principe #

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>

Utiliser Twig dans une application WordPress, Warning #

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.

Ressources #

Liste de ressources utiles pour PHP et pour continuer Ă  se former.

Général #

Namespaces et Auto-loading #

Composants #

Coding standards, style guides #

Design (POO) #

Templating, échappement et sécurisation des données #

WordPress et PHP #

Ouvrages #