With
preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE);
is it possible to search a string in reverse? ie. return the position of the last occurrence of the pattern in the subject similar to strripos
Or do i have to return the position of all matches with preg_match_all
and use the last element of $matches
?
Answer
PHP doesn’t have a regex method that search a string from right to left (like in .net). There are several possible recipes to solve that (this list isn’t exhaustive, but may provide ideas for your own workaround ):
- using
preg_match_all
withPREG_SET_ORDER
flag andend($matches)
will give you the last match set - reversing the string with
strrev
and building a “reversed” pattern to be used with preg_match - using
preg_match
and building a pattern anchored at the end of the string that ensures there is no more occurrences of the searched mask until the end of the string - using a greedy quantifier before the target and
K
to start the match result at the position you want. Once the end of the string is reached, the regex engine will backtrack until it finds a match.
Examples with the string $str = 'xxABC1xxxABC2xx'
for the pattern /x[A-Z]+d/
way 1: find all matches and displays the last.
if ( preg_match_all('/x[A-Z]+d/', $str, $matches, PREG_SET_ORDER) ) print_r(end($matches)[0]);
way 2: find the first match of the reversed string with a reversed pattern, and displays the reversed result.
if ( preg_match('/d[A-Z]+x/', strrev($str), $match) ) print_r(strrev($match[0]));
Note that it isn’t always so easy to reverse a pattern.
way 3: Jumps from x to x and checks with the negative lookahead if there’s no other x[A-Z]+d
matches from the end of the string.
if ( preg_match('/x[A-Z]+d(?!.*x[A-Z]+d)/', $str, $match) ) print_r($match[0]);
variants:
With a lazy quantifier
if ( preg_match('/x[A-Z]+d(?!.*?x[A-Z]+d)/', $str, $match) ) print_r($match[0]);
or with a “tempered quantifier”
if ( preg_match('/x[A-Z]+d(?=(?:(?!x[A-Z]+d).)*$)/', $str, $match) ) print_r($match[0]);
It can be interesting to choose between these variants when you know in advance where a match has the most probabilities to occur.
way 4: goes to the end of the string and backtracks until it finds a x[A-Z]+d
match. The K
removes the start of the string from the match result.
if ( preg_match('/^.*Kx[A-Z]+d/', $str, $match) ) print_r($match[0]);
way 4 (a more hand-driven variant): to limit backtracking steps, you can greedily advance from the start of the string, atomic group by atomic group, and backtrack in the same way by atomic groups, instead of by characters.
if ( preg_match('/^(?>[^x]*Kx)+[A-Z]+d/', $str, $match) ) print_r($match[0]);