Les Expressions Régulières
Introduction aux expressions régulières
Les expressions régulières, souvent abrégées en regex, sont des outils puissants pour rechercher, valider ou manipuler du texte. Elles sont très utilisées en programmation, en analyse de données ou encore pour automatiser des traitements sur des chaînes de caractères.
Une expression régulière décrit un motif, c'est-à-dire une structure qu'on cherche à reconnaître dans une chaîne. Ce motif peut être simple (comme un mot précis) ou très complexe (comme une adresse email ou un numéro de carte bancaire).
Recherche de motifs dans une chaîne
En PHP, une expression régulière est généralement une chaîne de caractères encadrée par des barres obliques (/.../). Voici un exemple qui cherche le mot clette dans une chaîne :
<?php
// Motif à rechercher (ici, la chaîne littérale "clette").
$regex = "/clette/";
?>
Vous pouvez utiliser ce motif avec la fonction preg_match(), qui vérifie s'il y a une correspondance dans une chaîne donnée. Cette fonction retourne 1 si une correspondance est trouvée avec le motif donné. Si aucune correspondance n'est détectée, elle retourne 0 :
<?php
// Motif à rechercher (ici, la chaîne littérale "clette").
$regex = "/clette/";
// Chaîne de texte dans laquelle on effectue la recherche.
$texte = "Quelle clette ce pey !";
// Vérifie si le motif "clette" est présent dans la chaîne "$texte".
$correspondance = preg_match($regex, $texte);
// Affiche le résultat de la recherche (1 = trouvé, 0 = non trouvé)
echo $correspondance; // Affiche : 1
?>
Notez que le motif clette ne recherche pas uniquement le mot clette en tant que mot isolé. Il suffit que cette séquence de lettres apparaisse quelque part dans un mot plus long, comme raclette, pour qu'elle soit considérée comme une correspondance. Dans ce cas, la fonction preg_match() retournera 1, ce qui signifie qu'une correspondance a été trouvée.
On peut également récupérer le résultat exact de la correspondance grâce à un troisième argument, par exemple $match. Il s'agit d'un tableau qui contiendra ce qui a été trouvé :
<?php
$regex = "/clette/";
$texte = "Quelle clette ce pey !";
// Vérifie si le motif "clette" est présent dans la chaîne "$texte".
// Si une correspondance est trouvée, elle est stockée dans le tableau "$match".
if (preg_match($regex, $texte, $match))
{
// Affiche le contenu du tableau "$match" contenant la ou les correspondances trouvées.
print_r($match);
/*
Affiche :
Array
(
[0] => clette
)
*/
}
?>
Les classes de caractères
Jusqu'ici, nous avons utilisé un motif littéral (/clette/), qui cherchent une séquence exacte dans le texte, sans aucune variation possible.
Ce type de recherche fonctionne pour des cas simples, mais il devient vite limité dès qu'on souhaite repérer des formes plus souples. Par exemple, si l'on veut détecter une majuscule placée n'importe où, un chiffre isolé ou une lettre parmi plusieurs, il faut pouvoir désigner un ensemble de caractères acceptés plutôt qu'un mot précis.
C'est exactement ce que permettent les classes de caractères, elles offrent un moyen d'indiquer, les caractères autorisés ou exclus. Elles font partie des fondations des expressions régulières, car elles permettent d'exprimer des choix sans complexifier la structure du motif.
Syntaxe des classes de caractères
- [abc] : correspond à un seul caractère, qui peut être a, b ou c. Si l'un d'eux est rencontré dans la chaîne, cela déclenche une correspondance.
- [^abc] : en plaçant le caractère ^ juste après le crochet ouvrant, on crée une classe négative. Ici, la correspondance se fera sur tout caractère autre que a, b ou c.
- [0-9] : les tirets permettent de définir des plages. Cette classe correspond à n'importe quel chiffre de 0 à 9.
- [a-z] : correspond à n'importe quelle lettre minuscule de l'alphabet. Notez que les lettres diacritiques ne sont pas incluses dans cette plage (accent, cédille, tréma).
- [A-Za-z] : combine deux plages pour couvrir toutes les lettres majuscules et minuscules.
Raccourcis de classes de caractères
- \d : raccourci équivalent à [0-9]. Il désigne un chiffre.
- \w : correspond à tout caractère alphanumérique ou au soulignement (_). Équivalent à [A-Za-z0-9_].
- \s : représente un caractère espace blanc : espace, tabulation, retour à la ligne, retour chariot, etc.
-
\D, \W et \S : sont les négations des raccourcis précédents.
- \D : tout sauf un chiffre (contraire de \d)
- \W : tout sauf un caractère alphanumérique ou souligné (contraire de \w)
- \S : tout sauf un espace blanc (contraire de \s)
Ces classes de caractères sont très pratiques si l'on désire sélectionner facilement plusieurs types de caractères sans devoir les énumérer un à un. Par exemple, supposons que l'on demande à l'utilisateur d'entrer son numéro de téléphone dans un formulaire, mais qu'il y insère des espaces, des tirets ou d'autres caractères non numériques : +32 123-45 67 89. Pour enregistrer ce numéro dans une base de données ou le comparer, il est souvent nécessaire de récupérer uniquement les chiffres, sans les autres symboles.
Avec une expression régulière comme /\d/, on peut facilement extraire chaque chiffre :
<?php
$regex = "/\d/";
$telephone = "+32 123-45 67 89";
// Vérifie si le motif "\d" est présent dans la chaîne "$telephone".
// Si des correspondances sont trouvées, elles sont stockées dans le tableau "$match".
if (preg_match_all($regex, $telephone, $matches))
{
// Affiche le contenu du tableau "$match" contenant la ou les correspondances trouvées.
print_r($matches);
/*
Affiche :
Array
(
[0] => 3
[1] => 2
[2] => 1
[3] => 2
[4] => 3
[5] => 4
[6] => 5
[7] => 6
[8] => 7
[9] => 8
[10] => 9
)
*/
}
?>
Pour récupérer toutes les correspondances présentes dans une chaîne, il suffit d'utiliser la fonction prédéfinie preg_match_all à la place de preg_match, qui s'arrête dès la première trouvée. Dans notre exemple, chaque chiffre est extrait individuellement, caractère par caractère. Il existe aussi un moyen de regrouper plusieurs chiffres à la suite, en jouant sur la taille des correspondances à extraire. Ce mécanisme repose sur ce qu'on appelle les quantificateurs, que nous découvrirons un peu plus loin.
Les Métacaractères de Position
Si les classes de caractères servent à contrôler quels caractères sont autorisés ou exclus, les métacaractères de position permettent d'indiquer où un motif doit apparaître dans la chaîne. Ils servent, par exemple, à vérifier qu'un motif se trouve au tout début ou à la toute fin d'une chaîne, ou encore à s'assurer qu'une séquence de caractères est bien isolée, c'est-à-dire qu'elle ne fait pas partie d'un autre mot mais qu'elle est séparée par un espace, une ponctuation ou toute autre limite de mot.
Les ancres de début/fin de chaîne
- ^ : Utilisé en tant que premier caractère, il correspond au début de la chaîne. Par exemple, /^abc/ correspondrait à abc si abc est au début de la chaîne.
- $ : Utilisé en tant que dernier caractère, il correspond à la fin de la chaîne. Par exemple, /abc$/ correspondrait à abc si abc est à la fin de la chaîne.
Par exemple, imaginons que l'on veuille valider des codes postaux belges, qui doivent toujours contenir exactement 4 chiffres dont le premier est compris entre 1 et 9 (et donc jamais 0).
L'expression /^[1-9]\d\d\d$/ vérifiera que la chaîne contient exactement quatre chiffres, dont le premier n'est jamais un zéro. Ainsi, la chaîne 1300 sera acceptée, tandis que 0123, ou encore 1300 Houtsiplou-les-bains-de-pieds seront refusées.
- ^ : La chaîne de caractère débute par le motif qui va suivre ce symbole.
- [1-9] : Le premier chiffre sera différent de 0.
- \d\d\d : Les trois caractères suivants sont des chiffres (0 à 9 inclus).
- $ : La chaîne doit s'arrêter exactement après ces quatre chiffres.
<?php
$regex = "/^[1-9]\d\d\d$/";
$codePostal = "1300";
// Vérifie si le motif "^[1-9]\d\d\d$" est présent dans la chaîne "$codePostal".
// Si une correspondance est trouvée, elle est stockée dans le tableau "$match".
if (preg_match($regex, $codePostal, $match))
{
// Affiche le contenu du tableau "$match" contenant la ou les correspondances trouvées.
print_r($match);
/*
Affiche :
Array
(
[0] => 1300
)
*/
}
?>
Comme nous ne cherchons qu'une seule correspondance dans la chaîne $codePostal et que le motif exprime précisément de quoi doit être composé le code postal, il n'est pas nécessaire d'utiliser la fonction preg_match_all, nous récupérerons la correspondance en une seule entité.
Notez que le motif contient une répétition \d\d\d pour s'assurer que les trois caractères qui suivent le premier chiffre (compris entre 1 et 9 inclus) sont aussi des chiffres, mais eux compris entre 0 et 9 inclus. Cette répétition pourrait être remplacée par un quantificateur, mais nous verrons cela plus tard.
Les frontières de mot
- \b : Correspond à une limite de mot, c'est-à-dire à la frontière entre un caractère dit « de mot » (lettre, chiffre ou trait d'union) et tout autre caractère (ponctuation, espace, début ou fin de chaîne, etc.). Autrement dit, ce métacaractère permet de vérifier si un motif commence ou termine un mot. Par exemple :
-
/port/ :
port peut apparaître n'importe où dans le mot.- port : correspondance trouvée
- portrait : correspondance trouvée
- aéroport : correspondance trouvée
- important : correspondance trouvée
-
/\bport/ :
port doit débuter à une limite de mot.- port : correspondance trouvée
- portrait : correspondance trouvée
aéroport : correspondance non trouvéeimportant : correspondance non trouvée
-
/port\b/ :
port doit se terminer à une limite de mot.- port : correspondance trouvée
portrait : correspondance non trouvée- aéroport : correspondance trouvée
important : correspondance non trouvée
-
/\bport\b/ :
port doit apparaître de manière isolée, c'est-à-dire délimité à gauche et à droite.- port : correspondance trouvée
portrait : correspondance non trouvéeaéroport : correspondance non trouvéeimportant : correspondance non trouvée
- \B : Opposé de \b, c'est à dire qu'il permet de vérifier qu'un motif ne commence et/ou ne termine une suite de caractères. Par exemple, avec /\Bport\B/, on détecte le motif port uniquement lorsqu'il est entouré d'autres caractères dans un mot, comme dans important.
Imaginons que l'on souhaite mettre en place un filtre anti-insultes dans une application web. L'objectif est de bloquer les messages contenant précisément le mot clette, sans pour autant déclencher le filtre sur des mots comme raclette ou biclette. Pour cela, il faut utiliser les frontières de mot (\b).
<?php
$regex = "/\bclette\b/";
$texte = "Il voulait aller chercher sa raclette en biciclette.";
// Vérifie si le motif "\bclette\b" est présent dans la chaîne "$texte".
// Si une correspondance est trouvée, elle est stockée dans le tableau "$match".
if (preg_match($regex, $texte, $match))
{
// Affiche le contenu du tableau "$match" contenant la ou les correspondances trouvées.
// Le tableau des correspondances est vide même si la chaîne contient les mots "raclette"
// et "biciclette", qui incluent la séquence "clette". Le motif littéral "clette" étant
// encadré par les métacaractères de frontière de mot "\b", seules les occurrences de "clette"
// apparaissant de manière isolée seraient détectées.
print_r($match);
/*
Affiche :
Array
(
)
*/
}
?>
Sans l'utilisation de \b, c'est-à-dire avec le motif /clette/, le filtre bloquerait tous les messages contenant cette suite de lettres, même au sein d'autres mots comme raclette ou biclette, ce qui n'est généralement pas souhaité dans une vraie application.
Le métacaractère de correspondance
Il arrive qu'on veuille accepter n'importe quel caractère à un emplacement donné, sans se soucier de sa nature précise. Pour cela, les expressions régulières proposent un métacaractère dédié : le point ..
Ce caractère spécial correspond à exactement un caractère quelconque, sauf le saut de ligne. Il est utile lorsqu'une portion du texte peut varier, mais que sa position ou sa longueur reste fixe.
Par exemple, le motif /a.c/ correspondra aux séquences aac, abc, acc, a2c, ou encore a c, car entre le a et le c, un seul caractère est présent, quel qu'il soit (sauf un retour à la ligne).
L'Opérateur d'Alternative
Le symbole | permet de proposer plusieurs chemins possibles dans un motif. Il agit comme un OU logique.
Par exemple, imaginons que l'on développe une application qui doit manipuler des fichiers dont le nom commence soit par INV (pour les factures), soit par DEV (pour les devis). On peut utiliser le symbole | pour écrire le motif /^INV|^DEV/, ce qui permet de cibler facilement les deux types de fichiers lors du traitement.
<?php
$regex = "/^INV|^DEV/";
$reference = "DEV25012025-001.pdf";
// Vérifie si le motif "/^INV|^DEV/" est présent dans la chaîne "$reference".
// Si une correspondance est trouvée, elle est stockée dans le tableau "$match".
if (preg_match($regex, $reference, $match))
{
echo "$match : référence valide.";
}
/*
Affiche :
DEV202405-001.pdf : référence valide.
*/
?>
Grâce au |, on accepte ici l'une ou l'autre des deux structures de référence attendues.
Échapper les caractères
Pour utiliser un caractère spécial des expressions régulières comme s'il s'agissait d'un simple caractère littéral, il faut l'échapper en le précédant d'un antislash \.
Par exemple, pour détecter une balise HTML de fermeture, le motif doit contenir un slash /. Or, ce caractère est aussi utilisé par PHP pour délimiter le début et la fin d'une expression régulière.
Si vous écrivez le motif /</b>/, le moteur pensera que l'expression régulière se termine après <. Il interprétera donc le motif comme /</, et considérera ce qui suit (ici b>/) comme une suite de modificateurs. Le motif souhaité sera invalide et provoquera une erreur Warning, car b ou > ne sont pas des modificateurs valides.
Pour éviter cette confusion, il faut échapper le slash présent dans le motif HTML, en le précédant d'un antislash. Le motif devient alors /<\/b>/. Ainsi, le moteur reconnaît bien le / comme un caractère à rechercher, et non comme la fin de l'expression.
<?php
$texte = "Voici <b>du texte</b>";
$regex = "/<\/b>/";
echo preg_match($regex, $texte); // Affiche : 1
?>
Il en va de même pour tous les autres caractères réservés par les expressions régulières. Ceux que nous avons déjà rencontrés (comme [, ], ^, \, ., etc.), doivent être échappés s'ils sont utilisés comme simples caractères. Ce sera également le cas pour d'autres caractères que nous verrons plus loin (comme (, ), +, *, etc).
Les quantificateurs
Les quantificateurs permettent d'indiquer combien de fois un caractère ou un groupe doit apparaître. Ils se placent immédiatement après l'élément concerné. Par défaut, les quantificateurs sont dits gourmands : ils essaient de capturer le plus de caractères possible, puis reviennent en arrière si nécessaire (c'est ce qu'on appelle le backtracking).
-
* : zéro ou plusieurs répétitions.
Exemple : /ab*/ correspond à a, ab, abb, abbbb, etc. -
+ : une ou plusieurs répétitions.
Exemple : /ab+/ correspond à ab, abb, abbbb, etc. -
? : zéro ou une occurrence.
Exemple : /ab?/ correspond à a ou ab. -
{n} : correspond à exactement n répétitions.
Exemple : /ab{3}/ correspond uniquement à abbb. -
{n,} : au moins n répétitions.
Exemple : /ab{2,}/ correspond à abb, abbbb, etc. -
{n,m} : entre n et m répétitions.
Exemple : /ab{2,4}/ correspond à abb, abbb ou abbbb.
Comme on peut le voir dans les exemples précédents, le quantificateur s'applique uniquement à l'entité qui le précède, et non à toute la séquence. En l'occurrence, lorsque l'on utilise le motif /ab+/, le quantificateur + s'applique uniquement au caractère b.
Si l'on souhaite vérifier une séquence littérale (comme un mot), il faut utiliser les groupes (une notion que nous approfondirons plus tard). Pour déclarer un groupe de caractères comme une entité, il suffit de l'encadrer par des parenthèses (...). Par exemple, si nous voulons que la séquence ab puisse être répétée une ou plusieurs fois, on écrira /(ab)+/. Ce motif correspondra aux chaînes suivantes : ab, abab, ababab, etc.
Notez que si vous utilisez une classe de caractères, comme dans /[ab]+/, le quantificateur s'applique à n'importe quel caractère présent dans la classe. Ce motif acceptera donc des chaînes composées d'au moins une lettre a ou b, dans n'importe quel ordre. Il reconnaîtra par exemple a, b, aa, bb, ab, ba, aab, abb, aba, bab, etc.
Pour illustrer l'utilité des quantificateurs, reprenons l'exemple des codes postaux belges vu précédemment dans la section des classes de caractères. Plutôt que d'écrire \d\d\d pour indiquer “trois chiffres”, on peut utiliser le quantificateur {3} juste après la classe \d. Le motif /^[1-9]\d{3}$/ permet ainsi d'exiger un chiffre entre 1 et 9 suivi de exactement trois chiffres. Cela rend le motif plus lisible, plus concis et surtout plus flexible si le nombre de chiffres devait changer plus tard.
<?php
$regex = "/^[1-9]\d{3}$/";
$codePostal = "1300";
// Vérifie si le motif "^[1-9]\d{3}$" est présent dans la chaîne "$codePostal".
// Si une correspondance est trouvée, elle est stockée dans le tableau "$match".
if (preg_match($regex, $codePostal, $match))
{
// Affiche le contenu du tableau "$match" contenant la ou les correspondances trouvées.
print_r($match);
/*
Affiche :
Array
(
[0] => 1300
)
*/
}
?>
Quantificateurs gourmands et backtracking
Lorsqu'on utilise un quantificateur gourmand comme + ou *, le moteur des expressions régulières essaie d'abord de consommer le plus de caractères possible pour cette partie du motif.
Si le reste de l'expression ne correspond pas, le moteur ne s'arrête pas là. Il revient en arrière étape par étape pour réduire progressivement la portion capturée et laisser une chance au reste du motif de s'appliquer. Ce mécanisme s'appelle le backtracking (recul).
Exemple : que se passe-t-il si on teste le motif /<b>.*<\/b>/ avec le texte Voici <b>un texte</b> en gras. ?
<?php
$regex = "/<b>.*<\/b>/";
$texte = "Voici <b>un texte</b> en gras.";
echo preg_match($regex, $texte); // Affiche : 1 (correspondance trouvée)
?>
Composition du motif :
- <b> : Motif littéral, c'est-à-dire recherché tel quel. La séquence à détecter doit donc commencer par la balise <b>.
- .* : Correspond à n'importe quel caractère (sauf retour à la ligne) autant de fois que possible (>= 0).
- </b> : Motif littéral, c'est-à-dire recherché tel quel. La séquence à détecter doit donc terminer par la balise </b>.
Voici comment le moteur va analyser le texte :
- <b> : Correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .* :
- correspondance trouvée -> "Voici <b> un texte</b> en gras."
- correspondance trouvée -> "Voici <b> un texte</b> en gras."
- correspondance trouvée -> "Voici <b> un texte</b> en gras."
- correspondance trouvée -> "Voici <b> un texte</b> en gras."
- ...
- tant que le motif gourmand .* trouve une correspondance, la recherche continue caractère après caractère...
- ...
- correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .* </b> :
- correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- backtracking -> correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- backtracking -> correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- backtracking -> correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- ...
- tant qu'aucune correspondance n'est trouvée avec le motif </b> et que la correspondance reste valide avec le motif groumant .*, le backtracking continue...
- ...
- backtracking -> correspondance trouvée -> "Voici <b> un texte </b> en gras."
Les quantificateurs gourmands fonctionnent bien dans de nombreux cas, mais ils peuvent aussi poser des problèmes :
- Ils capturent parfois trop de texte, au point de dépasser ce qu'on voulait isoler.
- Ils provoquent parfois des ralentissements lorsque plusieurs alternatives doivent être testées.
Pour y remédier, il existe deux variantes qui permettent un contrôle plus précis : les quantificateurs non-gourmands et les quantificateurs possessifs.
Quantificateurs non-gourmands
Un quantificateur non-gourmand (ou lazzy) commence par capturer le minimum, et n'élargit sa capture que si le reste du motif ne correspond pas.
On obtient la version non-gourmande d'un quantificateur en ajoutant un ? après celui-ci.
- *? : de 0 à n fois, mais aussi court que possible
- +? : au moins 1 fois, mais aussi court que possible
- {n,m}? : entre n et m fois, mais commence par n
Quantificateurs non-gourmands
Un quantificateur non-gourmand (aussi appelé lazy) essaie de capturer le moins de caractères possible, contrairement au quantificateur gourmand qui capture un maximum. On l'obtient en ajoutant un ? juste après le quantificateur habituel.
Exemple : que se passe-t-il si on teste le motif /<b>.*?<\/b>/ avec le texte Voici <b>un texte</b> en gras. ?
<?php
$regex = "/<b>.*?<\/b>/";
$texte = "Voici <b>un texte</b> en gras.";
echo preg_match($regex, $texte); // Affiche : 1
?>
Le motif .*? essaie de capturer le moins possible tout en respectant l'ensemble du motif <b>.*?</b>.
Voici comment le moteur va analyser le texte :
- <b> : Correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? : Correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? </b> : Correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? : Correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? </b> : Correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- ...
- Le moteur avance caractère par caractère, en étendant légèrement la portion capturée par .*?. À chaque étape, il vérifie si la suite du motif (</b>) correspond.
- ...
- <b> .*? : Correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? </b> : Correspondance non trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? : Correspondance trouvée -> "Voici <b> un texte</b> en gras."
- <b> .*? </b> : Correspondance trouvée -> "Voici <b> un texte </b> en gras."
Le quantificateur .*? s'est arrêté dès qu'il a rencontré la balise </b>. Il n'a pas continué jusqu'à la fin de la chaîne comme l'aurait fait un quantificateur gourmand.
Un quantificateur non-gourmand n'est pas forcément plus rapide. Il fait souvent plus d'essais, surtout s'il ne trouve pas rapidement ce qu'il attend.
Un quantificateur gourmand, au contraire, peut réussir en une seule tentative s'il avale tout et tombe directement sur la suite attendue. Mais s'il échoue, il devra revenir en arrière caractère par caractère, ce qui peut ralentir la recherche.
Le choix dépend du motif global et du type de texte traité. Les non-gourmands sont souvent préférés pour extraire plusieurs structures délimitées (comme des balises HTML) sans tout capturer d'un bloc.
Quantificateurs possessifs
Les quantificateurs possessifs sont une variante plus stricte des quantificateurs gourmands. Leur particularité est qu'ils n'autorisent aucun retour en arrière. Une fois qu'ils ont capturé des caractères, ces caractères sont considérés comme définitivement consommés. Le moteur ne les réexaminera pas, même si cela empêche la suite du motif de correspondre.
On les construit en ajoutant un + après un quantificateur classique :
- *+ : zéro ou plusieurs, sans backtracking
- ++ : une ou plusieurs, sans backtracking
- ?+ : zéro ou une, sans backtracking
- {n,m}+ : entre n et m occurrences, sans backtracking
Reprenons l'exemple utilisé précédemment avec les balises HTML. Que se passe-t-il si l'on utilise un quantificateur possessif au lieu d'un gourmand ou non-gourmand :
<?php
$texte = "Voici <b>un texte</b> en gras.";
$regex = "/<b>.*+<\/b>/"; // possessif
echo preg_match($regex, $texte); // Affiche : 0
?>
Étapes de l'analyse :
- <b> est bien trouvé en début de motif.
- .*+ capture tout jusqu'à la fin de la chaîne, y compris la balise </b>.
- Le moteur cherche ensuite une balise </b> après cette capture. Comme tout a déjà été consommé, il ne reste rien à analyser.
- Le moteur n'effectue aucun retour en arrière pour libérer une partie de la capture et retenter sa chance. Puisque tout a été consommé et qu'aucune correspondance n'est possible, l'analyse échoue immédiatement.
Ce comportement peut sembler rigide, mais il est très utile lorsque l'on sait qu'aucun retour en arrière n'est nécessaire. Dans ces cas, les quantificateurs possessifs permettent d'accélérer considérablement l'analyse, notamment sur de grandes chaînes de texte ou des motifs complexes.
Imaginons que l'on cherche une référence qui commence par au moins un chiffre et termine par la séquence REF :
<?php
$texte = "blablabla 123456REF bliblibli";
// Motif avec quantificateur gourmand
$regexGourmand = "/\d+REF/";
echo preg_match($regexGourmand, $texte); // Affiche : 1
// Motif avec quantificateur possessif
$regexPossessif = "/\d++REF/";
echo preg_match($regexPossessif, $texte); // Affiche : 1
?>
Dans ce cas, les deux regex fonctionnent aussi vite et renvoient une correspondance valide, il n'y a aucun backtracking, donc aucun avantage particulier à utiliser le quantificateur possessif.
Mais si la chaîne ne contient pas la référence attendue, le possessif permet d'éviter les tentatives inutiles :
<?php
$texte = "blablabla 123456PLOP bliblibli";
// Motif avec quantificateur gourmand
$regexGourmand = "/\d+REF/";
echo preg_match($regexGourmand, $texte); // Affiche : 0
// Motif avec quantificateur possessif
$regexPossessif = "/\d++REF/";
echo preg_match($regexPossessif, $texte); // Affiche : 0
?>
Dans ce scénario, le moteur utilisant \d+ va d'abord consommer tous les chiffres disponibles, puis revenir en arrière caractère par caractère si la suite du motif attendu (REF) ne correspond pas. En revanche, avec \d++, aucune tentative de retour n'est faite. Si la suite échoue, le moteur abandonne immédiatement, ce qui qui permet d'accélerer le traitement.
Conclusion
Les quantificateurs sont des outils puissants pour moduler la recherche, mais leur comportement influence directement les performances et les résultats obtenus. Voici quelques repères pour bien les choisir :
- Gourmands (par défaut) : Ils essaient de capturer le plus de caractères possible, puis reculent si nécessaire (backtracking). Pratiques dans de nombreux cas, mais peuvent poser problème s'ils capturent trop ou si la correspondance est difficile à atteindre.
- Non-gourmands (*?, +?, etc.) : À privilégier lorsque vous devez extraire plusieurs blocs délimités dans un même texte (balises HTML, sections, etc.). Ils s'arrêtent dès que possible, ce qui évite de tout capturer d'un coup. En revanche, ils peuvent être plus lents si la correspondance attendue est lointaine.
- Possessifs (*+, ++, etc.) : Ils sont utiles dans des contextes très contrôlés où l'on ne veut pas de retour en arrière. Cela permet d'éviter le backtracking et d'accélérer le traitement, surtout quand les motifs sont complexes ou que l'on traite de grandes chaînes, mais ils peuvent échouer là où un quantificateur gourmand réussirait.
Les Groupes
En expressions régulières, on parle de groupes lorsqu'on utilise des parenthèses pour regrouper certaines parties d'un motif. Les groupes permettent d'organiser l'expression, d'appliquer des quantificateurs à des ensembles de caractères ou d'extraire des sous-parties de texte.
Groupes capturants
Un groupe capturant (parenthèses simples (...)) sert à mémoriser la portion de texte qui correspond au motif placé entre les parenthèses. Chaque groupe capturé peut ensuite être retrouvé dans les résultats, ce qui permet de réutiliser ou d'analyser séparément chaque élément extrait.
Imaginons que l'on développe une application de gestion de commandes, et que chaque référence de commande suit le format CMD-2024-00123, où 2024 représente l'année et 00123 le numéro de la commande. Pour extraire l'année et le numéro de commande séparément, on peut utiliser des groupes de capture dans le motif /CMD-(\d{4})-(\d+)/ :
- CMD- : Motif littéral (interprété tel quel).
- ( : Ouverture du premier groupe de capture (année).
- \d{4} : Exactement 4 chiffres.
- ) : Fermeture du premier groupe de capture (année).
- - : Motif littéral (interprété tel quel).
- ( : Ouverture du second groupe de capture (numéro de commande).
- \d+ : Au moins un chiffre.
- ) : Fermeture du second groupe de capture (numéro de commande).
<?php
$regex = "/CMD-(\d{4})-(\d+)/";
$reference = "CMD-2024-00123";
if (preg_match($regex, $reference, $match))
{
// $match contient les correspondances trouvées.
print_r($match);
/*
Affiche :
Array
(
[0] => Array
(
[0] => 2024
[1] => 00123
)
)
*/
}
?>
Ici, chaque partie du motif entourée de parenthèses est capturée séparément, ce qui permet d'extraire facilement les informations utiles pour la suite du traitement.
Groupes capturants nommés
En plus des groupes capturants classiques, il est également possible d'utiliser des groupes nommés. Cette fonctionnalité permet d'attribuer un nom explicite à chaque groupe de capture, ce qui rend le code plus lisible et facilite l'accès aux informations extraites, en particulier lorsque l'expression régulière contient de nombreux groupes ou que leur ordre devient difficile à suivre.
Pour créer un groupe nommé en PHP, on utilise la syntaxe (?<nom>...) à la place des parenthèses simples. On peut alors accéder aux résultats à l'aide du nom choisi, via le tableau associatif retourné par preg_match ou preg_match_all.
<?php
$regex = "/CMD-(?<annee>\d{4})-(?<numero>\d+)/";
$reference = "CMD-2024-00123";
if (preg_match($regex, $reference, $match))
{
print_r($match);
/*
Affiche :
Array
(
[0] => CMD-2024-00123
[annee] => 2024
[numero] => 00123
[1] => 2024
[2] => 00123
)
*/
}
?>
Dans cet exemple, le motif utilise deux groupes nommés : annee et numero. On peut ainsi accéder directement à ces informations avec $match['annee'] et $match['numero'], ce qui simplifie l'exploitation du résultat.
Groupes capturés et texte de remplacement
Il est possible de réutiliser directement les groupes de capture dans les fonctions qui effectuent des remplacements, comme preg_replace. Dans ce contexte, on fait référence au premier groupe capturé avec $1, au second avec $2, et ainsi de suite. Cette fonctionnalité est particulièrement utile lorsqu'on souhaite, par exemple, inverser la position de plusieurs éléments dans une chaîne :
<?php
$regex = "/(\b\w+\b) (\b\w+\b) (.+)/";
$texte = "Claudy Focan aime la photographie!";
// Inverser l'ordre des deux premiers mots trouvés par notre expression régulière.
$resultat = preg_replace($regex, "$2 $1 $3", $texte);
echo $resultat; // Affiche : Focan Claudy aime la photographie!
?>
On pourrait être tenté de simplifier le motif en écrivant /(\b\w+\b ){2}(.+)/. Mais si on l'utilise avec preg_replace(), le résultat obtenu sera "aime la photographie! Focan" au lieu du résultat attendu. En effet, lorsqu'un quantificateur est appliqué à un groupe de capture, seule la dernière valeur capturée est retenue (les précédentes sont écrasées à chaque répétition). Dans cet exemple, le groupe capture d'abord Claudy, puis Focan (qui remplace la valeur précédente dans le même groupe). Le reste de la chaîne est ensuite capturé dans le second groupe. Au final, on se retrouve donc avec deux groupes : Focan et aime la photographie!, qui sont utilisés dans l'ordre spécifié par la chaîne de remplacement.
Groupes non-capturants
En plus des groupes de capture classiques, il existe aussi ce qu'on appelle des groupes non capturants, définis à l'aide de la syntaxe (?: ... ). Contrairement aux groupes de capture, ils permettent de regrouper des éléments dans l'expression régulière sans retenir leur contenu pour un usage ultérieur. Autrement dit, ce qui correspond à un groupe non capturant n'est pas mémorisé, on ne peut donc pas y accéder dans les résultats ou l'utiliser dans une opération de remplacement. Cette approche est utile lorsque l'on souhaite appliquer un quantificateur ou organiser la structure du motif sans alourdir la liste des groupes capturés, ce qui simplifie l'exploitation des résultats et évite de créer des captures inutiles.
Les Assertions Conditionnelles
Les assertions conditionnelles permettent d'imposer des contraintes particulières lors de la recherche de motifs dans une chaîne de caractères. Elles servent à affiner la recherche, en vérifiant par exemple si un motif donné est bien présent (ou absent) juste avant ou après une autre séquence. Comme les groupes non capturants, les assertions ne retiennent jamais de texte dans les résultats, elles servent uniquement à poser des conditions sur l'environnement du motif, sans extraire quoi que ce soit.
Il existe deux grandes familles d'assertions en expressions régulières, les assertions anticipées (lookahead), qui vérifient ce qui se trouve après une position, et les assertions retardées (lookbehind), qui vérifient ce qui se trouve avant. Chacune de ces familles peut être positive (présence d'un motif) ou négative (absence d'un motif).
Les assertions anticipées (lookahead)
Les assertions anticipées permettent de vérifier si un motif est suivi (positive lookahead) ou non (negative lookahead) d'un autre motif spécifique, sans inclure ce qui suit dans la sélection.
L'assertion anticipée positive (positive lookahead) X(?=Y) :
- X est le motif que nous recherchons.
- Y le motif qui doit nécessairement suivre le motif recherché.
Dans l'exemple suivant, nous désirons trouver les valeurs numériques qui sont précédées de px :
<?php
// Valeurs numériques (\d) d'au moins un caractère (+) qui sont précédées du mot "px" (?=px).
$regex = "/\d+(?=px)/";
$texte = "42 rectangle de 12px de longueur et 8px de largeur.";
// Lorsque l'on désire capturer plus d'une correspondance, on utilise la fonction "preg_match_all()" :
if (preg_match_all($regex, $texte, $matches))
{
// $matches contient les correspondances trouvées.
print_r($matches);
/*
Affiche :
Array
(
[0] => Array
(
[0] => 12
[1] => 8
)
)
*/
}
?>
L'assertion anticipée négative (negative lookahead) X(?!Y) :
- X est le motif que nous recherchons.
- Y le motif qui doit nécessairement ne pas suivre le motif recherché.
Les assertions retardées (lookbehind)
Les assertions retardées permettent de vérifier si un motif est précédé (positive lookbehind) ou non (negative lookbehind) d'un autre motif spécifique, sans inclure ce qui précède dans la sélection.
L'assertion retardée positive (positive lookbehind) (?<=Y)X :
- X est le motif que nous recherchons.
- Y le motif qui doit nécessairement précéder le motif recherché.
Dans l'exemple suivant, nous désirons trouver les valeurs numériques précédées de $ :
<?php
// Valeurs numériques (\d) d'au moins un caractère (+) qui sont précédées du signe dollar "$" (?<=$).
$regex = "/(?<=\$)\d+/";
$texte = 'La semaine, le prix est de $42 alors que le week-end, il est de $33. Un delta de 9 qui ne s\'explique pas !';
if (preg_match_all($regex, $texte, $matches))
{
// $matches contient les correspondances trouvées.
print_r($matches);
/*
Affiche :
Array
(
[0] => Array
(
[0] => 42
[1] => 33
)
)
*/
}
?>
L'assertion retardée négative (negative lookbehind) (?<!Y)X :
- X est le motif que nous recherchons.
- Y le motif qui doit nécessairement ne pas précéder le motif recherché.
Recherche non linéaire des motifs
Dans certains cas, on souhaite vérifier la présence de plusieurs séquences au sein d'une chaîne de caractères sans imposer un ordre spécifique entre elles.
Par exemple, cela s'applique aux mots de passe, où il est souvent nécessaire, pour des raisons de sécurité, d'inclure différents types de caractères tels que des lettres majuscules et minuscules, des chiffres et des caractères spéciaux, sans que leur ordre soit spécifié.
Les assertions anticipées peuvent être utilisées dans ce contexte car elles analysent la chaîne de caractères successivement, permettant ainsi de valider des motifs indépendamment de leur ordre d'apparition.
Il est à préciser que les assertions anticipées, en raison de leur nature non-capturante, ne capturent pas le texte correspondant au motif trouvé même en cas de réussite. Ainsi, si vous souhaitez obtenir le résultat de la recherche, vous devrez ajouter un motif de synthèse capturant immédiatement après. Ce motif capturant permettra alors de récupérer le texte associé à la réussite des assertions.
Voici un exemple illustrant la validation d'un mot de passe, s'assurant qu'il contient au moins 8 caractères et qu'il comprend au moins une lettre minuscule, une lettre majuscule, un chiffre et caractère spécial :
<?php
$regex = "/
^
(?=.*[a-z]+) # Assertion positive anticipée permettant de vérifier qu'au moins
# une lettre minuscule est présente peu importe où dans la chaîne.
(?=.*[A-Z]+) # Identique au motif précédent mais pour les lettres majuscules.
(?=.*\d+) # Identique au motif précédent mais pour les chiffres.
(?=.*[@$!%*?&]+) # Identique au motif précédent mais pour des caractères spéciaux.
[A-Za-z\d@$!%*?&]{8,} # Après la validation des assertions ci-dessus, ce motif vérifie que
# le mot de passe contient au moins 8 caractères ET que chacun d'eux
# appartient exclusivement à l'ensemble autorisé : lettres (a-z, A-Z),
# chiffres (0-9) et caractères spéciaux parmi @$!%*?&.
# Aucun autre caractère n'est accepté.
$
/x";
echo preg_match($regex, "8sR!") . PHP_EOL; // Affiche : 0
echo preg_match($regex, "12345678") . PHP_EOL; // Affiche : 0
echo preg_match($regex, "abcd5EFG") . PHP_EOL; // Affiche : 0
echo preg_match($regex, "!gRa7jJ@") . PHP_EOL; // Affiche : 1
?>
Les Modificateurs aussi appelés Drapeaux (flags)
Les modificateur sont utilisés avec les expressions régulières pour changer leur comportement lors de la recherche de correspondances dans une chaîne de caractères :
Le modificateur i (insensible à la casse)
Lorsque ce modificateur est utilisé, la recherche de correspondances est insensible à la casse, ce qui signifie qu'elle correspond à des lettres majuscules et minuscules.
- Sans le modificateur "i" :
<?php $regex = "/clette/"; $texte = "Quelle Clette ce pey !"; echo preg_match($regex, $texte); // Affiche : 0 ?> -
Avec le modificateur "i" :
<?php $regex = "/clette/i"; $texte = "Quelle Clette ce pey !"; echo preg_match($regex, $texte); // Affiche : 1 ?>
Le modificateur m (multi-lignes)
Ce modificateur est utile lorsque vous avez besoin d'utiliser les métacaractères de position ^ et $ ligne par ligne et non plus sur le début et la fin de la chaîne entière.
- Sans le modificateur "m" :
<?php // Le regex suivant correspond si la chaîne de caractères commence (^) par le mot "Ligne" suivi d'un espace et ensuite d'un chiffre (\d). $regex = "/^Ligne \d/"; $texte = "Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 2 Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 3 Lorem Ipsum !" . PHP_EOL; echo preg_match($regex, $texte); // Affiche : 0 ?> - Avec le modificateur "m" :
<?php // Le regex suivant correspond si une ligne commence (^) par le mot "Ligne" suivi d'un espace et ensuite d'un chiffre (\d). $regex = "/^Ligne \d/m"; $texte = "Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 2 Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 3 Lorem Ipsum !" . PHP_EOL; echo preg_match($regex, $texte, $match); // Affiche : 1 // Afficher la capture : print_r($match); /* Affiche : Array ( [0] => Ligne 2 ) */ // Pour capturer tous les éléments correspondant au motif du regex on utilise la fonction "preg_match_all()". echo preg_match_all($regex, $texte, $matches); // Affiche : 1 // Afficher les captures : print_r($matches); /* Affiche : Array ( [0] => Ligne 2 [0] => Ligne 3 ) */ ?>
Le modificateur s (dotall)
Ce modificateur permet au point . de correspondre à n'importe quel caractère, y compris les sauts de ligne.
- Sans le modificateur "s" :
<?php // Le regex suivant correspond à n'importe quel caractère (.) une ou plusieurs fois (+) jusqu'au premier saut de ligne. $regex = "/.+/"; $texte = "Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 2 Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 3 Lorem Ipsum !" . PHP_EOL; echo preg_match($regex, $texte, $match); // Affiche : 1 // Afficher la capture : print_r($match); /* Affiche : Array ( [0] => Lorem Ipsum ! ) */ ?> - Avec le modificateur "s" :
<?php // Le regex suivant correspond à n'importe quel caractère (.) une ou plusieurs fois (+) jusqu'à la fin de la chaîne. $regex = "/.+/s"; $texte = "Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 2 Lorem Ipsum !" . PHP_EOL; $texte .= "Ligne 3 Lorem Ipsum !" . PHP_EOL; echo preg_match($regex, $texte, $match); // Affiche : 1 // Afficher la capture : print_r($match); /* Affiche : Array ( [0] => Lorem Ipsum ! Ligne 2 Lorem Ipsum ! Ligne 3 Lorem Ipsum ! ) */ ?>
Le modificateur x (étendu)
Ce modificateur permet d'écrire les motifs complexe sur plusieurs ligne et d'y insérer des commentaires.
- Sans le modificateur "x" :
<?php // La chaîne de caractère peut débuter par 32 ou +32 ^(\+32|32)?, // l'indicatif peut être composé de 2 ou 3 chiffres et doit être situé entre parenthèses \(\d{2,3}\), // le corps du numéro de téléphone doit être composé de 6 chiffres. $regex = "/^(\+32|32)?\(\d{2,3}\)\d{6}$/x"; $texte = "32(010)654321"; echo preg_match($regex, $texte); // Affiche : 1 ?> - Avec le modificateur "x" :
<?php $regex = "/ ^ # Début de la chaîne (?:\+32|32)? # Le code du pays est facultatif (non capturant) \(\d{2,3}\) # Indicatif régional avec parenthèses \d{6} # Numéro de téléphone (6 chiffres) $ # Fin de la chaîne /x"; $texte = "32(010)654321"; echo preg_match($regex, $texte); // Affiche : 1 ?>
Notez qu'avec ce modificateur, les espaces, tabulations et retours à la ligne servent seulement à mettre la regex en forme. Ils sont donc ignorés par le moteur tant qu'ils ne font pas partie du motif. En revanche, si un espace est placé à l'intérieur d'un ensemble de caractères (ex.: [a b] signifie "a, b ou un espace"), il devient un caractère recherché. De même, un espace échappé (comme \ ) est traité comme un espace réel, ce qui permet de chercher explicitement un espace dans la chaîne.
Combiner plusieurs modificateurs
Notez qu'il est possible de combiner plusieurs modificateurs sur un même motif :
<?php
$regex = "/^erreur\s*:.*$/im";
$texte = "Info : démarrage du programme" . PHP_EOL;
$texte .= "Erreur : fichier introuvable" . PHP_EOL;
$texte .= "Avertissement : mémoire presque pleine" . PHP_EOL;
$texte .= "Erreur : accès refusé" . PHP_EOL;
preg_match_all($regex, $texte, $matches);
// Afficher la capture :
print_r($matches);
/*
Affiche :
Array
(
[0] => "Erreur : fichier introuvable",
[1] => "Erreur : accès refusé"
)
*/
?>
Les modificateurs que nous venons de voir sont les plus courament utilisés mais si vous désirez en savoir plus sur le sujet, rendez-vous sur la documentation de PHP.
Fonctions de Manipulation
il existe plusieurs fonctions pour manipuler des expressions régulières. Ces fonctions sont couramment utilisées pour effectuer des opérations de recherche, de remplacement et de correspondance dans des chaînes de caractères.
preg_match()
Utilisée pour vérifier si une expression régulière correspond à une partie d'une chaîne de caractères. Elle renvoie 1 si une correspondance est trouvée, 0 si aucune correspondance n'est trouvée, et false en cas d'erreur. La correspondance est stockée dans le tableau passé en référence en tant que 3ème argument. Ce tableau peut être composé de plusieurs éléments si des groupes de capture ont été utilisé. La fonction s'arrête dès qu'une correspondance est trouvée.
<?php
// Motif correspondant aux séquences de caractères commençant par la lettre "o" suivie de trois caractères alphanumériques consécutifs.
$regex = "/o\w{3}/";
$texte = "Claudy Focan aime la photographie.";
// Vérifier s'il existe une correspondance dans la chaîne de caractère "$texte" avec le motif "$regex".
echo preg_match($regex, $texte, $match); // Affiche : 1
// Afficher la correspondance.
print_r($match);
/*
Affiche :
Array
(
[0] => ocan
)
*/
?>
preg_match_all()
Cette fonction est utilisée pour rechercher toutes les occurrences d'une expression régulière dans une chaîne de caractères. Elle renvoie 1 si une correspondance est trouvée, 0 si aucune correspondance n'est trouvée, et false en cas d'erreur. Les correspondances seront stockées dans le tableau passé en référence en tant que 3ème argument. Contrairement à preg_match(), cette fonction ne s'arrête pas après avoir trouvé la première correspondance, elle continue à rechercher toutes les occurrences dans la chaîne.
<?php
// Motif correspondant aux séquences de caractères commençant par la lettre "o" suivie de trois caractères alphanumériques consécutifs.
$regex = "/o\w{3}/";
$texte = "Claudy Focan aime la photographie.";
// Vérifier s'il existe des correspondances dans la chaîne de caractère "$texte" avec le motif "$regex".
echo preg_match_all($regex, $texte, $matches); // Affiche : 1
// Afficher les correspondances.
print_r($matches);
/*
Affiche :
Array
(
[0] => ocan
[1] => otog
)
*/
?>
preg_replace()
Cette fonction est utilisée pour rechercher toutes les occurrences d'un motif dans une chaîne de caractères et les remplacer par une autre chaîne ou une valeur. Si des correspondances sont trouvées, la chaîne modifiée est renvoyée, sinon la chaîne d'origine est renvoyée sans modifications.
<?php
// Motif correspondant aux séquences de caractères commençant par la lettre "o" suivie de trois caractères alphanumériques consécutifs.
$regex = "/o\w{3}/";
$texte = "Claudy Focan aime la photographie.";
// Remplacer toutes les occurrences correspondantes dans la chaîne de caractères "$texte" par "énoménog".
$resultat = preg_replace($regex, "énoménog", $texte);
// Afficher la chaîne avec la séquence modifiée.
echo $resultat; // Affiche : Claudy Fénoménog aime la phénoménographie.
?>
Notez qu'il est possible de passer un tableau contenant plusieurs expressions régulières en premier argument, lesquelles seront exécutées successivement :
<?php
// Motif correspondant aux séquences de caractères commençant par la lettre "o" suivie de trois caractères alphanumériques consécutifs.
$regex1 = "/o\w{3}/";
// Motif littéral qui recherche un mot terminant par "laudy".
$regex2 = "/laudy\b/";
// Le tableau qui sera passé à la fonction "preg_replace()".
$regex = [$regex1, $regex2];
$texte = "Claudy Focan aime la photographie.";
// Remplacer toutes les occurrences correspondantes dans la chaîne de caractères "$texte" par "énoménog".
$resultat = preg_replace($regex, "énoménog", $texte);
// Afficher la chaîne avec la séquence modifiée.
echo $resultat; // Affiche : Cénoménog Fénoménog aime la phénoménographie.
?>
Lorsque le premier argument prend la forme d'un tableau, il est possible de fournir un second tableau en argument. Les indices de remplacement correspondront alors à ceux du tableau des expressions régulières.
<?php
// Motif correspondant aux séquences de caractères commençant par la lettre "o" suivie de trois caractères alphanumériques consécutifs.
$regex1 = "/o\w{3}/";
// Motif littéral qui recherche le mot "Claudy".
$regex2 = "/\bClaudy\b/";
// Le tableau qui sera passé à la fonction "preg_replace()".
$regex = [$regex1, $regex2];
$texte = "Claudy Focan aime la photographie.";
// Remplacer toutes les occurrences correspondantes dans la chaîne de caractères "$texte" par "énoménog".
$resultat = preg_replace($regex, ["énoménog", "Monsieur"], $texte);
// Afficher la chaîne avec la séquence modifiée.
echo $resultat; // Affiche : Monsieur Fénoménog aime la phénoménographie.
?>
preg_replace_callback()
Cette fonction est similaire à preg_replace() mais elle permet en plus de réaliser divers traitement sur les captures.
<?php
// Motif correspondant aux voyelles.
$regex = "/[aeiouy]/";
$texte = "Claudy Focan aime la photographie.";
// La fonction "pre_replace()" ne permet pas d'utiliser de fonction pour modifier la capture au sein de son deuxième argument.
// Pour pouvoir utiliser une fonction sur les captures, il faut utiliser la fonction "preg_replace_callback()" en lui passant
// une fonction de rappel en second argument. Ainsi, toutes les modifications souhaitées peuvent être réalisée sur les captures.
// Récuperer toutes les voyelles de la chaîne de caractère et les passer en majuscule :
$resultat = preg_replace_callback($regex, function($match){
return strtoupper($match[0]);
}, $texte);
// Afficher la chaîne avec la séquence modifiée.
echo $resultat; // Affiche : ClAUdY FOcAn AImE lA phOtOgrAphIE.
?>
Notez que les fonctions preg_match(), preg_match_all() et preg_replace_callback() permettent de passer des "flags" en argument pour modifier leur comportement de sortie. Par exemple, le "flag" PREG_OFFSET_CAPTURE permet de récupérer la position des éléments capturés dans la chaîne.
<?php
// Motif correspondant au nom de famille de Claudy.
$regex = "/\bFocan\b/";
$texte = "Claudy Focan aime la photographie.";
// PREG_OFFSET_CAPTURE ajoute dans $match l'indice où commence chaque élément capturé dans $texte.
echo preg_match($regex, $texte, $match, PREG_OFFSET_CAPTURE); // Affiche : 1
// Afficher la correspondance.
print_r($match);
/*
Affiche :
Array
(
[0] => Array
(
[0] => Focan
[1] => 7
)
)
*/
?>
Il existe de nombreuses autres fonctions pour manipuler les expressions régulières. Si vous désirez en savoir plus sur le sujet, rendez-vous sur la documentation de PHP.
Exercices
Exo-les-expressions-regulieres-01
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères contient une séquence littérale spécifique : "poudre de perlimpinpin".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "poudre de perlimpinpin : un remède prétendument miraculeux") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "remède prétendument miraculeux : poudre de perlimpinpin") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "poudre de merlimpinpin : un remède prétendument miraculeux") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "loutre de perlimpinpin : un animal féroce") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-02
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères commence par une séquence littérale spécifique : "poudre de perlimpinpin".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "poudre de perlimpinpin : un remède prétendument miraculeux") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "remède prétendument miraculeux : poudre de perlimpinpin") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "poudre de merlimpinpin : un remède prétendument miraculeux") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "loutre de perlimpinpin : un animal féroce") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-03
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères termine par une séquence littérale spécifique : "remède prétendument miraculeux".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "poudre de perlimpinpin : un remède prétendument miraculeux") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "remède prétendument miraculeux : poudre de perlimpinpin") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "poudre de merlimpinpin : un remède prétendument miraculeux") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "loutre de perlimpinpin : un animal féroce") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-04
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères est uniquement composée d'une séquence littérale spécifique : "loutre de perlimpinpin : un animal féroce".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "loutre de perlimpinpin : un animal féroce") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "La loutre de perlimpinpin : un animal féroce") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "loutre de perlimpinpin : un animal féroce porteur de chance") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-05
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères commence par la séquence suivante : Un mot débutant avec une seule lettre suivie de "oudre".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "Moudre le café") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Coudre du beurre") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Dissoudre Emilio") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "7oudre... vous vouliez dire Moudre?") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "Foudres de guerre") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-06
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères commence par la séquence suivante : Un mot débutant avec au moins une lettre suivie de "oudre".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "Moudre le café") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Coudre du beurre") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Dissoudre Emilio") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "7oudre... vous vouliez dire Moudre?") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "Foudres de guerre") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-07
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères commence par la séquence suivante : Soit par la lettre "F" soit par le chiffre "7".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "Dissoudre Emilio") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "Coudre du beurre") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "F et 7 ne sont pas des mots!") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "7oudre... vous vouliez dire Moudre?") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Foudres de guerre") . PHP_EOL; // Affiche : 1 ?>
Exo-les-expressions-regulieres-08
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères commence par la séquence suivante : Un mot débutant soit par la lettre "F" soit par le chiffre "7".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "Dissoudre Emilio") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "Coudre du beurre") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "F et 7 ne sont pas des mots!") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "7oudre... vous vouliez dire Moudre?") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Foudres de guerre") . PHP_EOL; // Affiche : 1 ?>
Exo-les-expressions-regulieres-09
- Objectif : Réalisez une expression régulière permettant de vérifier si une chaîne de caractères commence par la séquence suivante : Un mot ne débutant ni par la lettre "F" ni par le chiffre "7".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "Dissoudre Emilio") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "Coudre du beurre") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "F et 7 ne sont pas des mots!") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "7oudre... vous vouliez dire Moudre?") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "Foudres de guerre") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-10
- Objectif :
- À l'aide des groupes, réalisez une expression régulière permettant de capturer les chaînes de caractères suivantes :
- $1 : "le vent"
- $2 : " secoue "
- $3 : "les branches"
- $4 : " pendant que "
- $5 : "la maman"
- $6 : " change "
- $7 : "le bébé"
- Ensuite, à l'aide de la fonction preg_replace() inverser la position des groupe de capture 1 et 5 et faites de même avec les groupes de capture 3 et 7.
- À l'aide des groupes, réalisez une expression régulière permettant de capturer les chaînes de caractères suivantes :
- Code à compléter :
<?php // La chaîne à manipuler. $chaine = "le vent secoue les branches pendant que la maman change le bébé"; // Remplacez "null" par votre expression régulière. $regex = null; // Remplacez l'exemple de Claudy Focan par vos arguments. echo preg_replace("/(Claudy)\s(Focan)/", "$2, $1", "Claudy Focan"); // L'exemple affiche : "Focan, Claudy" ?>
Exo-les-expressions-regulieres-11
- Objectif : Réaliser une expression régulière permettant de vérifier si une chaîne de caractères est une couleur hexadécimal (sans alpha).
- Format d'une couleur hexadécimal (sans transparence) :
- Composée soit de 4 caractères pour le format court soit de 7 caractères pour le format standard.
- Commence toujours par un "#".
- Les caractères suivants peuvent être des chiffres ou des lettres (minuscules ou majuscules) allant de "a" à "f".
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "#123456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "#4242FF") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "#4242ff") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "#F90") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "#12345") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "#1234567") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "#4F4G42") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "#afé465") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "#g32") . PHP_EOL; // Affiche : 0 ?>
Exo-les-expressions-regulieres-12
- Objectif : Réalisez une expression régulière basée sur celle de l'exercice précédent afin de récupérer toutes les couleurs valides situées au début de chaque ligne.
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; $texte = "#123456" . PHP_EOL; $texte .= "#789 blabla #654" . PHP_EOL; $texte .= "#7F98 #654" . PHP_EOL; $texte .= "blibli #321123" . PHP_EOL; $texte .= "#987456 blublublu" . PHP_EOL; // Vérifier s'il existe des correspondances dans la chaîne de caractère "$texte" avec le motif "$regexExo2". preg_match_all($regex, $texte, $matches); // Afficher les correspondances. print_r($matches); /* Affiche : Array ( [0] => Array ( [0] => #123456 [1] => #789 [2] => #987456 ) ) */ ?>
Exo-les-expressions-regulieres-13
- Objectif : Réalisez une expression régulière permettant de convertir la première lettre du premier mot de chaque ligne, ainsi que la première lettre des mots "cigale" et "fourmi" en majuscule.
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; $texte = " la cigale ayant chanté tout l'été, se trouva fort dépourvue quand la bise fut venue. pas un seul petit morceau de mouche ou de vermisseau. elle alla crier famine chez la fourmi sa voisine, la priant de lui prêter quelque grain pour subsister jusqu'à la saison nouvelle. je vous paierai, lui dit-elle, avant l'août, foi d'animal, intérêt et principal. la fourmi n'est pas prêteuse, c'est là son moindre défaut. que faisiez-vous au temps chaud ? dit-elle à cette emprunteuse. nuit et jour à tout venant, je chantais, ne vous déplaise. vous chantiez ? j'en suis fort aise, eh bien! dansez maintenant. "; // Ajouter une majuscule aux captures. // C'est à dire à la première lettre de chaque mot situé en début de ligne. $resultat = preg_replace_callback($regex, function($match) { return strtoupper($match[0]); }, $texte); echo $resultat; /* Affiche : La Cigale ayant chanté Tout l'été, Se trouva fort dépourvue Quand la bise fut venue. Pas un seul petit morceau De mouche ou de vermisseau. Elle alla crier famine Chez la Fourmi sa voisine, La priant de lui prêter Quelque grain pour subsister Jusqu'à la saison nouvelle. Je vous paierai, lui dit-elle, Avant l'août, foi d'animal, Intérêt et principal. La Fourmi n'est pas prêteuse, C'est là son moindre défaut. Que faisiez-vous au temps chaud ? Dit-elle à cette emprunteuse. Nuit et jour à tout venant, Je chantais, ne vous déplaise. Vous chantiez ? j'en suis fort aise, Eh bien! dansez maintenant. */ ?>
Exo-les-expressions-regulieres-14
- Objectif : Réalisez une expression régulière permettant de convertir la première lettre du premier mot de chaque phrase en majuscule.
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; $texte = " je regarde les étoiles… elles sont si lointaines et leur lumière met si longtemps à nous atteindre. tout ce que nous voyons des étoiles sont de vieilles photos. on est en juillet 1959 et je suis amoureux. elle s'appelle Janey Slater, elle est physicienne comme moi. j'ai 30 ans! c'est un ami de la fac, Wally Weaver, qui nous a présentés. on est en 1981, Wally meurt d'un cancer dont on dit aujourd'hui que j'en suis la cause… "; $resultat = preg_replace_callback($regex, function($match) { return strtoupper($match[0]); }, $texte); echo $resultat; /* Affiche : Je regarde les étoiles… elles sont si lointaines et leur lumière met si longtemps à nous atteindre. Tout ce que nous voyons des étoiles sont de vieilles photos. On est en juillet 1959 et je suis amoureux. Elle s'appelle Janey Slater, elle est physicienne comme moi. J'ai 30 ans! C'est un ami de la fac, Wally Weaver, qui nous a présentés. On est en 1981, Wally meurt d'un cancer dont on dit aujourd'hui que j'en suis la cause… */ ?>
Exo-les-expressions-regulieres-15
- Objectif : Réalisez une expression régulière permettant de vérifier qu'un numéro de téléphone belge est valide tout en respectant divers formats.
- Formats : Le numéro est composé de trois parties pouvant être collées ou séparées par un espace :
- préfixe international (facultatif) :
- +32
- 0032
- 00 32
- (0032)
- (00 32)
- préfixe régional : Commence par 0 si l'indicatif international n'est pas précisé, sinon, le 0 est omis. Sans compter le 0, la séquence est composée de 3 chiffres.
- (0)10
- 010
- 10
- numéro local : Composé de 6 à 7 chiffres
- 123456
- 123 456
- 12 34 56
- 1234567
- 123 45 67
- préfixe international (facultatif) :
- Code à compléter :
<?php // Remplacez "null" par votre expression régulière. $regex = null; echo preg_match($regex, "+32123123456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "+32 123 123 456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "+32 12 123 44 56") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "(0032)123123456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "(00 32)123 123 456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "(00 32)123 123 45 67") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "(0)12123456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "012123456") . PHP_EOL; // Affiche : 1 echo preg_match($regex, "012 12 34 56") . PHP_EOL; // Affiche : 1 echo "--------------------------------------------" . PHP_EOL; echo preg_match($regex, "+320123123456") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "(0032)0123123456") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "(00 32)1234 12 34 56") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "(00 32)123 12345678") . PHP_EOL; // Affiche : 0 echo preg_match($regex, "12 12 34 56") . PHP_EOL; // Affiche : 0 ?>