Initiation aux expressions régulières en PHP

Image non disponible


précédentsommairesuivant

II. Syntaxe

Nous allons commencer par des regex très simples afin de nous familiariser avec la bestiole. Pour cela, nous allons écrire des expressions qui permettront de faire la même chose que certaines fonctions déjà mises à disposition par PHP. Bien entendu, ces fonctions sont à utiliser de préférence : nous ne les remplaçons ici qu'à des fins didactiques.

II-1. La base : trouver une sous chaîne dans une chaîne

En PHP, la fonction strpos() permet de savoir si une chaîne se trouve dans une autre chaîne :

Exemple d'utilisation de strpos()
Sélectionnez
<?php

$pattern  = "rossolini";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(strpos($subject, $pattern) !== FALSE)
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Tester ce script

N. B. : En cas de succès, la fonction strpos() retourne la position de la sous chaîne dans la chaîne mais cela ne nous est pas nécessaire : nous cherchons simplement à déterminer son existence.

Du moment que nous ne souhaitons pas récupérer cette valeur, nous pouvons simuler strpos() au moyen d'une regex. Pour cela, nous utiliserons la fonction preg_match() :

Exemple d'utilisation de preg_match()
Sélectionnez
<?php

$pattern  = "/rossolini/";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
/rossolini/ => La chaîne "rossolini" telle quelle.

Tester ce script

Évidemment, ce premier exemple n'est pas très intéressant. Il nous permet à peine d'introduire les délimiteurs.
Les délimiteurs sont, ici, les deux barres obliques encadrant la chaîne à chercher. En réalité, il est possible d'utiliser une grande variété de délimiteurs. J'aurais pu écrire #rossolini#, `rossolini`, [rossolini], <rossolini>, etc. Reportez-vous au manuel PHP pour en avoir la liste complète. Je ne recommande pas l'utilisation des parenthèses, crochets et accolades car cela peut compliquer les choses dans l'expression (cf. plus loin).
L'une des différences majeures entre les expressions POSIX et les PCRE est l'existence de ces délimiteurs ; ils permettent de spécifier des modificateurs à la suite de l'expression, par exemple :

Exemple de modificateur
Sélectionnez
<?php

$pattern  = "/ROSSOLINI/i";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
/ROSSOLINI/i => La chaîne "rossolini" quelle que soit la casse.

Tester ce script

Ici, j'ai ajouté le modificateur i permettant de rendre l'expression insensible à la casse, c'est-à-dire de chercher toutes les variantes majuscules/minuscules de "rossolini". Dans l'exemple ci-dessus, c'est équivalent à invoquer la fonction stripos().

II-2. Les alternatives (barre verticale |)

L'un des intérêts des regex est de permettre de donner des alternatives. Ainsi, nous pouvons savoir si une chaîne contient au moins l'une de plusieurs sous chaînes, cela sans à avoir à appeler plusieurs fois la fonction à la suite :

Exemple d'alternatives avec strpos()
Sélectionnez
<?php

$pattern_1 = "rossolini";
$pattern_2 = "www";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern_1</strong> ou <strong>$pattern_2</strong> correspond-il à :<ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(strpos($subject, $pattern_1) !== FALSE
        or strpos($subject, $pattern_2) !== FALSE)
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Tester ce script

Exemple d'alternatives avec une regex
Sélectionnez
<?php

$pattern  = "/rossolini|www/";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
/rossolini|www/ => Soit la chaîne "rossolini", soit "www".

Tester ce script

Cela dit, s'il s'agit de trouver deux sous chaînes dans une chaîne, alors il est préférable de lancer deux regex plutôt que d'essayer de le faire en une fois : cela pourrait s'avérer bien complexe, illisible et très long à exécuter.

II-3. L'ancrage (accent circonflexe ^ et signe dollar $)

Il est possible d'ancrer une regex, de manière à ne trouver de correspondances qu'au début de la chaîne, à la fin ou les deux (chaîne entière). Cela se fait au moyen des symboles ^ et $.

Exemples d'ancrages de regex
Sélectionnez
<?php

$patterns = array();
$patterns[0][0] = "#^http$#";
$patterns[0][1] = "#http$#";
$patterns[0][2] = "#^http#";
$patterns[1][0] = "#^.com/$#";
$patterns[1][1] = "#.com/$#";
$patterns[1][2] = "#^.com/#";
$patterns[2][0] = "#^Guillaume Rossolini$#";
$patterns[2][1] = "#Guillaume Rossolini$#";
$patterns[2][2] = "#^Guillaume Rossolini#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

foreach($patterns as $pattern)
{
    echo " se trouve <strong>".trim($pattern[0], '#^$')."</strong> ?<br /><ul>";
    foreach($subjects as $subject)
    {
        echo "<li>dans <strong>$subject</strong> ?<br />";
        if(preg_match($pattern[0], $subject))
        {
            echo "C'est la chaîne complète";
        }
        elseif(preg_match($pattern[1], $subject))
        {
            echo "À la fin";
        }
        elseif(preg_match($pattern[2], $subject))
        {
            echo "Au début";
        }
        else
        {
            echo "Nulle part";
        }
        echo "</li>";
    }
    echo "</ul>";
}

?>

Décomposition de la regex : [?]
#^http$# => La chaîne "http".
#http$# => La chaîne "http" en fin de chaîne.
#^http# => La chaîne "http" en début de chaîne.

Tester ce script

J'ai pris soin d'utiliser un délimiteur différent de la barre oblique, dans la mesure où elle se trouve dans le masque ($pattern). Sans cela, il aurait fallu échapper la barre oblique, or je préfère simplifier la lecture de la regex.

Il y a bien sûr des équivalences tant que nous cherchons une sous chaîne fixe :

Simulation d'ancrage de début ^
Sélectionnez
<?php

$pattern = "http";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> est-il au début :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li>dans <strong>$subject</strong> ?<br />";
    if(strpos($subject, $pattern) === 0)
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Tester ce script

Simulation d'ancrage de fin $
Sélectionnez
<?php

$pattern  = ".com/";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> est-il à la fin :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li>dans <strong>$subject</strong> ?<br />";
    if(strpos($subject, $pattern) === (strlen($subject) - strlen($pattern)))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Tester ce script

Simulation d'ancrage complet ^ et $
Sélectionnez
<?php

$pattern  = "Guillaume Rossolini";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> est-il équivalent à :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if($subject == $pattern)
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Tester ce script

Des recommandations de sécurité sont recensées en fin d'article lorsque l'ancrage de fin de chaîne est utilisé.

II-4. Les classes

Les regex permettent une chose intéressante : savoir si une chaîne contient une liste de caractères (plutôt qu'une sous chaîne précise).

Exemple de classe alphabétique
Sélectionnez
<?php

$pattern  = "/[a-z]/";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "<strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
/[a-z]/ => Il suffit d'une lettre minuscule dans la chaîne.

Tester ce script

Dans cet exemple, la regex regarde s'il y a au moins une lettre dans la chaîne. Le trait d'union permet d'indiquer une série : "[a-z]" comprend l'alphabet entier, "[0-9]" tous les chiffres, "[4-7]" uniquement les chiffres de 4 à 7 inclus. Nous pourrions indiquer "[4567]" à la place de "[4-7]" mais bon... Idem pour les lettres, il est possible d'écrire tout l'alphabet dans les crochets. Le trait d'union permet de simplifier l'expression.

Précisons que ce qui se trouve à l'intérieur d'une classe (à l'exception des délimiteurs, de la barre oblique arrière \ et du trait d'union -) n'a pas besoin d'être échappé. Ici, par exemple, le point est pris en tant que tel et non comme joker :

Exemple de classe mixte
Sélectionnez
<?php

$pattern  = "#^[a-z:/.-]+$#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#^[a-z:/.-]+$# => Un ou plusieurs caractères parmi les lettres de l'alphabet, les deux points, la barre oblique, le point et le trait d'union.

Tester ce script

Enfin, une note sur le .* : bien que pratique, il est souvent peu judicieux de l'utiliser. Il est généralement préférable d'utiliser une classe dite négative :

Exemple de classe négative
Sélectionnez
<?php

$pattern  = "#^[^ ]+$#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#^[^ ]+$# => N'importe quel caractère mais pas une seule espace dans la chaîne.

Tester ce script

Voici certaines des classes prédéfinies et leurs équivalents :
  • Caractère décimal
    [[:digit:]] ou [0-9] ou \d
    Équivalent PHP pour is_int($subject) : preg_match('/^\d+$/', $subject)
    Équivalent PHP approximatif pour is_numeric($subject) : preg_match('/^\d+(\.\d*)?$/', $subject)

  • Caractère alphabétique
    [[:alpha:]] ou [a-zA-Z]
    Équivalent PHP approximatif pour ctype_alpha($subject) : preg_match('/^[a-z]+$/i', $subject)

  • Caractère alphanumérique
    [[:alnum:]] ou [a-zA-Z0-9]

  • Caractère blanc (espace, tabulation, saut de ligne)
    [[:space:]] ou [\040\r\t] ou \s
    Équivalent C : isspace()

  • Limite de mot
    \b

Les classes du genre [[::]] sont héritées de la norme POSIX. Pour une liste plus complète, je vous laisse vous rendre vers l'article de mon collègue Hugo Étiévant.

II-5. Les quantifieurs

Il est possible de dire combien de fois le dernier caractère (ou la dernière classe) peut/doit être répété. C'est l'objectif des quantifieurs.

Liste des quantifieurs :
  • "Zéro ou une fois" : point d'interrogation ?
  • "Zéro, une ou plusieurs fois" : étoile *
  • "Au moins une fois" : signe plus +
  • "Deux fois" : {2}
  • "De deux à quatre fois" : {2,4}
  • "Au moins deux fois" : {2,}
À noter que :
  • ? est équivalent à {0,1}
  • * est équivalent à {0,}
  • + est équivalent à {1,}

Les regex pouvant être très rapidement compliquées, il est préférable de les simplifier tant que possible. Conseil d'ami.
Il est évidemment possible de mettre n'importe quel nombre dans les accolades (pourvu que le premier nombre soit inférieur au second).

N'oublions pas le fameux joker : le point. Il permet de remplacer n'importe quel caractère.

Récupérer les domaines et sous domaines
Sélectionnez
<?php

$pattern  = "#http://[a-z.-]+\.[a-z]{2,4}/#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo "Oui";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#http://[a-z.-]+\.[a-z]{2,4}/#

Détail :
  • http://
  • [a-z.-]+ : Au moins un caractère parmi les lettres de l'alphabet, le point et le trait d'union
  • \.[a-z]{2,4} : Un point suivi de deux à quatre lettres de l'alphabet

Tester ce script

II-6. Les parenthèses

Les regex permettent une autre chose intéressante, à savoir de capturer des portions de la chaîne parcourue. Cela se fait au moyen des parenthèses.
Pour récupérer une capture, il faut spécifier le paramètre optionnel de preg_match(). Cette variable contiendra un tableau des résultats : l'index zéro contiendra la chaîne complète correspondant au masque puis chaque index suivant sera rempli par une capture dans l'ordre où elles sont définies.

Les parenthèses capturantes

Exemple de capture simple
Sélectionnez
<?php

$pattern  = "#http://([a-z-]+)\.developpez.com/#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    $match = array();
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject, $match))
    {
        echo "Oui :<br /><pre>";
        print_r($match);
        echo "</pre>";
    }
    else
    {
        echo "Non<br /><br />";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#http://([a-z-]+)\.developpez.com/#

Détail :
  • http://
  • ([a-z-]+) : Un ou plusieurs caractères parmi les lettres et le trait d'union
  • \.developpez.com/

Tester ce script

Exemple de capture multiple
Sélectionnez
<?php

$pattern  = "#http://([a-z-]+)\.([a-z]+)\.([a-z]+)/#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    $match = array();
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject, $match))
    {
        echo "Oui :<br /><pre>";
        print_r($match);
        echo "</pre>";
    }
    else
    {
        echo "Non<br /><br />";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#http://([a-z-]+)\.([a-z]+)\.([a-z]+)/#

Détail :
  • http://
  • \.([a-z]+) : Un point suivi d'une ou plusieurs lettres.
  • /

Tester ce script

Une version plus intéressante (car moins redondante) serait : [?]
#http://([a-z-]+)(?:\.([a-z]+)){2}/#

Détail :
  • http://
  • (?:\.([a-z]+)){2} : Un point suivi d'un ou plusieurs caractères parmi les lettres et le trait d'union. Ce bloc est répété une fois.
  • /



Pour récupérer plusieurs fois un masque dans une même chaîne, nous pouvons utiliser la fonction preg_match_all() :

Exemple permettant de récupérer le sous domaine, le domaine et l'extension racine d'une l'URL
Sélectionnez
<?php

$pattern  = "#[./]([a-z-]+)#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    $matches = array();
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER))
    {
        echo "Oui :<br /><pre>";
        print_r($matches);
        echo "</pre>";
    }
    else
    {
        echo "Non<br /><br />";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#[./]([a-z-]+)# => Un point ou une barre oblique, puis au moins une lettre de l'alphabet.

Tester ce script

Une autre utilisation des captures est la référence arrière :

Exemple de remplacement
Sélectionnez
<?php

$pattern  = "#http://([a-z-]+)\.(developpez\.com)/#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";
$subjects[] = "Guillaume Rossolini";
$subjects[] = "0123456789";
$subjects[] = "01 23 45 67 89";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match($pattern, $subject))
    {
        echo preg_replace(
            $pattern,
            "Le sous domaine est '\1', le domaine est '\2'",
            $subject);
        echo "<br />";
        echo preg_replace(
            $pattern,
            "Le sous domaine est '$1', le domaine est '$2'",
            $subject);
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
#http://([a-z-]+)\.(developpez\.com)/#

Détail :
  • http://
  • ([a-z-]+)\. : Une ou plusieurs lettres de l'alphabet (ou traits d'union) suivies d'un point
  • (developpez\.com)/ : La chaîne "developpez.com"

Tester ce script

Ici, j'ai utilisé les deux manières d'appeler une référence arrière : au moyen de la barre oblique inversée \ ou au moyen du signe dollar $, suivies du numéro de la référence (en commençant à 1, non à 0). Les deux méthodes sont équivalentes. Faites attention si vous utilisez plus de neuf références car la convention de nommage peut poser problème. Reportez-vous à la documentation PHP pour toute information complémentaire.

Les parenthèses non capturantes

Il est possible d'utiliser des parenthèses sans pour autant définir une capture. Cela peut servir dans certaines situations, notamment lorsque nous voulons donner une alternative :

Parenthèses non capturantes
Sélectionnez
<?php

$pattern  = "/[a-z]+\s(?:[a-z]+['\s])?([a-z]+(?:es|ons|ez|ent|e))/i";

$subjects = array();
$subjects[] = "Je m'appelle, tu t'appelles, il s'appelle,"
    ." nous nous appelons, vous vous appelez, ils s'appellent";
$subjects[] = "Je programme, tu programmes, il programme,"
    ." nous programmons, vous programmez, ils programment";

echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
foreach($subjects as $subject)
{
    $matches = array();
    echo "<li><strong>$subject</strong> ?<br />";
    if(preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER))
    {
        echo "Oui : <br /><pre>";
        print_r($matches);
        echo "</pre>";
    }
    else
    {
        echo "Non";
    }
    echo "</li>";
}
echo "</ul>";

?>

Décomposition de la regex : [?]
/[a-z]+\s(?:[a-z]+['\s])?([a-z]+(?:es|ons|ez|ent|e))/i

Détail :
  • [a-z]+\s : Une ou plusieurs lettres de l'alphabet suivies d'une espace
  • (?:[a-z]+['\s])? : Une ou plusieurs lettres de l'alphabet suivies soit d'une espace soit d'une apostrophe (ce bloc est facultatif)
  • ([a-z]+(?:es|ons|ez|ent|e)) : Tout mot terminant par "e", "es", "ons", "ez" ou "ent" (on capture le mot entier)

Tester ce script

Dans cet exemple, ce qui nous intéresse n'est pas uniquement la terminaison du verbe mais le verbe entier. Il y a donc un couple de parenthèses capturantes autour du verbe et un couple de parenthèses non capturantes autour de la terminaison.

II-7. Les modificateurs

Les modificateurs sont un apport très consistant des PCRE depuis la norme POSIX. Ils permettent de modifier la manière dont le moteur de regex va parcourir la chaîne.

Voici mes trois modificateurs favoris :
  • U
    "Ungreedy", c'est-à-dire non gourmand. Cela signifie que l'expression trouvera des résultats aussi petits que possible.
    Essayez l'expression ci-dessous avec et sans le modificateur U. Dans certains cas, la regex pourrait même retourner depuis le premier <b> jusqu'au dernier </b> (cf. exemple ci-dessous) !

  • s
    Permet de demander au point de contenir également les sauts de ligne. Par défaut, ce n'est pas le cas.

  • i
    Permet de ne pas tenir compte de la casse. Ainsi, les masques [a-z], [A-Z] et toutes leurs variantes sont équivalents. Il est inutile de préciser [a-zA-Z], ce qui peut être pratique dans de nombreux cas.

Essayez ces exemples avec et sans leur modificateur :

Exemple de modificateur 'U' (expression non gourmande)
Sélectionnez
<?php

$patterns = array();
$patterns[] = "#<strong>(.*)</strong>#";
$patterns[] = "#<strong>(.*)</strong>#U";

$subjects = array();
$subjects[] = "Je m'appelle <strong>Guillaume</strong>,"
    ." mon site est <strong>http://g-rossolini.developpez.com/</strong>.";
$subjects[] = "Il s'appelle <strong>Pierre-Baptiste</strong>,"
    ." son site est <strong>http://pbnaigeon.developpez.com/</strong>.";

foreach($patterns as $pattern)
{
    echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
    foreach($subjects as $subject)
    {
        $matches = array();
        echo "<li><strong>$subject</strong> ?<br />";
        if(preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER))
        {
            echo "Oui : <br /><pre>";
            print_r($matches);
            echo "</pre>";
        }
        else
        {
            echo "Non";
        }
        echo "</li>";
    }
    echo "</ul>";
}

?>

Décomposition de la regex : [?]
#<b>(.*)</b>#U => N'importe quelle suite de caractères encadrée d'une balise de mise en gras.

Tester ce script

Exemple de modificateur 's'
Sélectionnez
<?php

$patterns = array();
$patterns[] = "#<strong>(.*)</strong>#";
$patterns[] = "#<strong>(.*)</strong>#s";

$subjects = array();
$subjects[] = "Je m'appelle <strong>Guillaume</strong>,\n"
    ." mon site est <strong>http://g-rossolini.developpez.com/</strong>. (avec saut de ligne)";
$subjects[] = "Il s'appelle <strong>Pierre-Baptiste</strong>,"
    ." son site est <strong>http://pbnaigeon.developpez.com/</strong>. (sans saut de ligne)";

foreach($patterns as $pattern)
{
    echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
    foreach($subjects as $subject)
    {
        $matches = array();
        echo "<li><strong>$subject</strong> ?<br />";
        if(preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER))
        {
            echo "Oui : <br /><pre>";
            print_r($matches);
            echo "</pre>";
        }
        else
        {
            echo "Non";
        }
        echo "</li>";
    }
    echo "</ul>";
}

?>

Décomposition de la regex : [?]
#<i>(.*)</i>#s => N'importe quelle suite de caractères encadrée d'une balise de mise en italique.

Tester ce script

II-8. Les assertions

Les assertions permettent de déterminer la présence ou l'absence de caractères avant ou après la position du curseur dans la chaîne. Pour plus de détails sur ce curseur, veuillez vous reporter à la documentation officielle.
Il existe des assertions simples telles que \b, décrit succinctement plus haut, ainsi que des assertions plus complexes. Nous allons nous attarder sur ces dernières.
Je ne traiterai pas des assertion positives car elles me semblent totalement inutiles : autant mettre la chaîne sans assertion, cela revient au même que faire une assertion positive. Pour les curieux, il s'agit de (?=) et (?<=). Je présenterai donc uniquement les assertions négatives : (?!) et (?<!).

N. B. : Les assertions ont besoin de parenthèses mais elles ne sont pas capturantes.

Les assertions négatives avant

Elles permettent de déterminer si une chaîne est suivie d'une autre chaîne (par son absence).

Le domaine ou sous domaine n'est pas...
Sélectionnez
<?php

$patterns = array();
$patterns[] = "#http://(?!www)#";
$patterns[] = "#http://(?!php)#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";

foreach($patterns as $pattern){
    echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
    foreach($subjects as $subject)
    {
        echo "<li><strong>$subject</strong> ?<br />";
        if(preg_match($pattern, $subject))
        {
            echo "Oui";
        }
        else
        {
            echo "Non";
        }
        echo "</li>";
    }
    echo "</ul>";
}

?>

Décomposition des regex : [?]
#http://(?!www)# => La chaîne "http://" non suivie de "www" #http://(?!php)# => La chaîne "http://" non suivie de "php"

Tester ce script

Les assertions négatives arrière

Elles permettent de déterminer si une chaîne est précédée d'une autre chaîne (par son absence).

Le sous domaine n'est pas...
Sélectionnez
<?php

$patterns = array();
$patterns[] = "#(?<!www)\.developpez#";
$patterns[] = "#(?<!php)\.developpez#";

$subjects = array();
$subjects[] = "ftp://ftp.developpez.com/g-rossolini/";
$subjects[] = "http://g-rossolini.developpez.com/";
$subjects[] = "http://www.developpez.com/";
$subjects[] = "http://php.developpez.com/";

foreach($patterns as $pattern)
{
    echo "Le masque <strong>$pattern</strong> correspond-il à :<br /><ul>";
    foreach($subjects as $subject)
    {
        echo "<li><strong>$subject</strong> ?<br />";
        if(preg_match($pattern, $subject))
        {
            echo "Oui";
        }
        else
        {
            echo "Non";
        }
        echo "</li>";
    }
    echo "</ul>";
}

?>

Décomposition des regex : [?]
#(?<!www)\.developpez# = La chaîne ".developpez" non précédée de "www" #(?<!php)\.developpez# = La chaîne ".developpez" non précédée de "php"

Tester ce script


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Guillaume Rossolini. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.