php – How to find a string based on a group of regular expressions

Question:

I'm trying to create a Template Engine in PHP for study purposes.

Suppose I have the following array :

$regexList = [
  'varPattern' => '/{{\s*\$(.*?)\s*}}/',
  'loopPattern' => '/@for\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}/',
  'statementPattern' => '/@if\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}/'
]

and the following functions :

getVar($nomeDaVariavel);
loop($nomeDoArray);
getStatementResult($expressãoBooleana);

and the following string :

$string = '

<span>{{ $nomeCompleto }}</span>

@for($nomes as $nome)
{{
  @if($nome == 'Eleandro)
  {{
    <p>{{ $nome }}</p>
  }}
}} ';

The idea is to read the string from top to bottom and based on the list of regular expressions find the result and deliver it to the correct function.

For example: The first thing to find has to be {{ $string }} , so we pass the variable name to the getVar($nomeDaVariávelEncontrada) .

Next is loop , so we call loop($comoNomeDoArrayEncontrado) ; and inside the loop the if will be found, so we take the contents of the if and we hand it the function getStatementResult($expressãoBooleanaEncontrada) with the found value.

How can I do this in the right order (top to bottom)?

Answer:

I'm using preg_replace_callback to replace the pattern results that are in $regexList .
[…] The problem is that this function takes the occurrence of the first pattern in the $regexList , instead of taking the first occurrence in the $string that has a pattern in the $regexList .

Use only one regex.

In that case, don't use an array. Instead:

$regexList = [
                 '/regex1/',
                 '/regex2/',
                 '/regex3/',
             ];

Use the same:

$regex = '/regex1|regex2|regex3/';

In function:

$resultado = preg_replace_callback(
                 $regex,
                 function ($matches) {
                     // ...
                     return 'substituído';
                 },
                 $texto
             );

But how do you know which regex was found?

You can use groups to identify them.

$regex = '/(regex1)|(regex2)|(regex3)/';

Then:

$resultado = preg_replace_callback(
                 $regex,
                 function ($matches) {
                     if ($matches[1]) {
                         return 'regex1 substituído';
                     } else if ($matches[2]) {
                         return 'regex2 substituído';
                     } else if ($matches[3]) {
                         return 'regex3 substituído';
                     }
                 },
                 $texto
             );

But you have other groups in the patterns, and counting the right group number can be tricky. We can use named groups to make things easier.

$regex = '/(?P<padrao1>regex1)|(?P<padrao2>regex2)|(?P<padrao3>regex3)/';


$resultado = preg_replace_callback(
                 $regex,
                 function ($matches) {
                     if ($matches['padrao1']) {
                         return 'regex1 substituído';

                     } else if ($matches['padrao2']) {
                         return 'regex2 substituído';

                     } else if ($matches['padrao3']) {
                         return 'regex3 substituído';
                     }
                 },
                 $texto
             );

Code

To answer your question, with a little recursion:

function analisar( $string ) {
    $regex = '/
                    (?P<var>       {{\s*\$(.*?)\s*}}                      )
                |
                    (?P<loop>      @for\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}  )
                |
                    (?P<statement> @if\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}   )
              /x';

    $resultado = preg_replace_callback(
                     $regex,
                     function ($matches) {
                         /*
                             //debug
                             echo "\n\nSubst: $matches[0]\n\$matches = ";
                             var_export($matches);
                         */
                         if ($matches['var']) {
                             return getVar($matches[2]);

                         } else if ($matches['loop']) {
                             return loop($matches[4], $matches[5]);

                         } else if ($matches['statement']) {
                             return getStatementResult($matches[7], $matches[8]);
                         }
                     },
                     $string
                 );

    return $resultado;
}

function getVar($nomeDaVariavel){
    return 'VAR('
        . analisar($nomeDaVariavel)
        . ')';
}
function loop($nomeDoArray, $codigo){
    return "LOOP\nLOOP-COND("
        . analisar($nomeDoArray)
        . ")\nLOOP-CODIGO("
        . analisar($codigo)
        . ')';
}
function getStatementResult($expressãoBooleana, $codigo){
    return "IF\nIF-COND("
        . analisar($expressãoBooleana)
        . ")\nIF-CODIGO("
        . analisar($codigo)
        . ')';
}

Test:

$string = '
<span>{{ $nomeCompleto }}</span>

@for($nomes as $nome)
{{
  @if($nome == \'Eleandro\')
  {{
    <p>{{ $nome }}</p>
  }}
}} 
';

echo analisar($string);

Result:

<span>VAR(nomeCompleto)</span>

LOOP
LOOP-COND($nomes as $nome)
LOOP-CODIGO(
  IF
IF-COND($nome == 'Eleandro')
IF-CODIGO(
    <p>VAR(nome)</p>
  )
) 

Demo: https://ideone.com/pBfQBP

Scroll to Top
AllEscort