preg_replace_callback

(PHP 4 >= 4.0.5, PHP 5)

preg_replace_callback -- 用回调函数执行正则表达式的搜索和替换

说明

mixed preg_replace_callback ( mixed pattern, callback callback, mixed subject [, int limit] )

本函数的行为几乎和 preg_replace() 一样,除了不是提供一个 replacement 参数,而是指定一个 callback 函数。该函数将以目标字符串中的匹配数组作为输入参数,并返回用于替换的字符串。

例子 1. preg_replace_callback() 例子

<?php
  
// 此文本是用于 2002 年的,
  // 现在想使其能用于 2003 年
  
$text = "April fools day is 04/01/2002\n";
  
$text.= "Last christmas was 12/24/2001\n";

  
// 回调函数
  
function next_year($matches) {
    
// 通常:$matches[0] 是完整的匹配项
    // $matches[1] 是第一个括号中的子模式的匹配项
    // 以此类推
    
return $matches[1].($matches[2]+1);
  }

  echo
preg_replace_callback(
              
"|(\d{2}/\d{2}/)(\d{4})|",
              
"next_year",
              
$text);

  
// 结果为:
  // April fools day is 04/01/2003
  // Last christmas was 12/24/2002
?>

You'll often need the callback function for a preg_replace_callback() in just one place. In this case you can use create_function() to declare an anonymous function as callback within the call to preg_replace_callback(). By doing it this way you have all information for the call in one place and do not clutter the function namespace with a callback functions name not used anywhere else.

例子 2. preg_replace_callback()create_function()

<?php
  
/* 一个 UNIX 风格的命令行过滤器,将每个段落开头的
   * 大写字母转换成小写字母 */

  
$fp = fopen("php://stdin", "r") or die("can't read stdin");
  while (!
feof($fp)) {
      
$line = fgets($fp);
      
$line = preg_replace_callback(
          
'|<p>\s*\w|',
          
create_function(
              
// 这里使用单引号很关键,
              // 否则就把所有的 $ 换成 \$
              
'$matches',
              
'return strtolower($matches[0]);'
          
),
          
$line
      
);
      echo
$line;
  }
  
fclose($fp);
?>

参见 preg_replace()create_function()


add a note add a note User Contributed Notes
matt at mattsoft dot net
26-Apr-2006 05:16
it is much better on preformance and better practice to use the preg_replace_callback function instead of preg_replace with the e modifier.

function a($text){return($text);}

// 2.76 seconds to run 50000 times
preg_replace("/\{(.*?)\}/e","a('\\1','\\2','\\3',\$b)",$a);

// 0.97 seconds to run 50000 times
preg_replace_callback("/\{(.*?)\}/s","a",$a);
mplescano at gmail dot com
06-Mar-2006 02:12
class User {
   var $id;
   var $usuario;
   var $clave;

   function setId ($id) {
       $this->id = $id;
   }
  
   function getId () {
       return $this->id;
   }
  
   function setUsuario ($usuario) {
       $this->usuario = $usuario;
   }
  
   function getUsuario () {
       return $this->usuario;
   }
  
   function setClave ($clave) {
       $this->clave = $clave;
   }
  
   function getClave () {
       return $this->clave;
   }
  
   function User () {
      
   }
}

class PropertyUtils
{
...
   /**
     * Get the value of the specified property on the subject bean using
     * the standard notation for getter methods from the JavaBean spec.
     *
     * @return Object
     */
   function &getSimpleProperty(&$bean, $name)
   {
       if (is_null($bean)) {
           return null;
       }
       $method = 'get' . ucfirst($name);
       if (!method_exists($bean, $method)) {
           $method = 'is' . ucfirst($name);
       }
       if (!method_exists($bean, $method)) {
           $method = null;
       }
       if (is_null($method)) {
           return null;
       }
       $return =& $bean->$method();
       return $return;
   }
...
}
$statement['user.insert'][] = array('sentence'=>"INSERT INTO tb01 (id, usuario, clave) VALUES (#id#, #usuario#, #clave#)", 'parameter'=>'User');

class Mapper {
...   
   function insert ($clazzName, $newBean, $order = 0) {
       global $statement;
       $statementStr = $statement[$clazzName.'.'.'insert'][$order]['sentence'];
       if (get_class($newBean) !== false && $clazzName == get_class($newBean)) {
           $statementParsed = Mapper::parseStatementBeanParameter($newBean, $statementStr);
       }
       else if (is_array($newBean) && count($newBean) > 0) {
           $statementParsed = Mapper::parseStatementArrayParameter($newBean, $statementStr);
       }
       return $statementParsed;
   }
...
   function parseStatementBeanParameter (&$bean, $statement) {
      
       $parsedStatement = preg_replace("/#([^#\s]+)#/e","Mapper::searchParameterBean('\\1', \$bean)", $statement);
       return $parsedStatement;
   }
...
   function searchParameterBean ($index, $bean) {
       $clazzName = get_class($bean);
       $propertyValue = '';
       if (!($clazzName == '' || $clazzName === false)) {
           $propertyValue = PropertyUtils::getSimpleProperty($bean, $index);
       }
       return $propertyValue;
   }
}

$user =& new User();
$user->setId(1);
$user->setUsuario("antonio");
$user->setClave("aguiero");
echo Mapper::insert (get_class($user), $user);

Print:
INSERT INTO tb01 (id, usuario, clave) VALUES (1, antonio, aguiero)
07-Jan-2006 03:55
I've found php will crash when I store a reference to the $match parameter but there's an easy work around...

$mylist=array();

function my_callback($match) {
   global $mylist;
  
   // this crashes PHP...
   // $mylist[]=$match;
  
   // this copies the $match array so is safe
   $mylist[]=array_merge($match);
  
   return 1;
}

$subject="The rain in Spain falls mainly on the plane.";

echo "<p>".preg_replace_callback("/[aeiou]/i", "my_callback", $subject);
echo "<p>"; print_r($mylist);
user at lesplumesdusavoir dot net
31-Oct-2005 06:39
You can use this method instead of preg_replace_callback :

<?php
echo preg_replace("`(<\/?)(\w+)([^>]*>)`e", // Don't forget e
"html_body('\\1', '\\2', '\\3', \$extra_info)", $html_body); // To pass other params..., Don't '$extra_info' but \$extra_info.
?>

It works very well. Enjoy :)
admin at the-php-tree dot com
27-Jun-2005 07:15
Instead of using preg_replace_callback() you could use preg_replace() like below:

<?php

 
// ** Simple function: **
    
function my_function( $my_param_one, $my_param_two ) {
            
// ...
    
}
    
preg_replace( "! \{(.+)-(.+)\} !e", "my_function('\\1', '\\2')", $input_string );

 
// ** Class: **
  
class my_class {

           function
my_class() {
            
preg_replace( "! \{(.+)-(.+)\} !e", "\$this->my_function('\\1', '\\2')", $input_string );
           }

           function
my_function( $my_param_one, $my_param_two ) {
            
// ...
          
}
   }

?>
larry at fantasyclub dot ru
03-May-2005 12:08
A powerful php template parser may be written in one line of code :)
(Of course there are many ways of it, this is my own just developed)

print preg_replace_callback("/<\\?(.*?)\\?>/",
create_function('$m','extract($GLOBALS);
return(eval("return($m[1]);"));'),$s);

Template example:
<?$var["x1"]?><br>                         
<?$var["x2"]?><br>                         
<?$var["x1"]." php foreva ".$var["x2"]?><br>

So, as you can see, <? and ?> are template parser tags and it is even possible to make simple php expressions inside such tags. However not too complex to confuse designer :)
You may add \" around inner $m[1] at your taste and templates will be little different ;)
Another advantage of such parser that you do not need to worry about variables must be available in templates - all globals and global arrays will be :) Very handy in fact.

As I saw above, this can be optimized by making separate (not inline) callback function, and I will of courde do it in real program, but this is less gracefull than I wrote here :)
Jari Pennanen
11-Dec-2004 02:03
It is not typo, notice the "e" at the end of the pattern, it does the job.
 
But there is typo in another place, in class example there is one extra \\ before $this word.
24-Aug-2004 10:05
There is a small typo with the code (at least when I try to implement it) in the first comment. 

<?php
echo preg_replace("/(<\/?)(\w+)([^>]*>)/e", "html_body('\\1', '\\2', '\\3', '$extra_info')", $html_body);
?>

should really be

<?php
echo preg_replace("/(<\/?)(\w+)([^>]*>)/e", html_body('\\1', '\\2', '\\3', '$extra_info'), $html_body);
?>

That is, the second argument should not have quotes around it.  This should apply to all of the other examples in the comments as well.
18-Aug-2004 10:00
if you want to send some extra parameters to your call back function here's how to do it (using preg_replace instead!). i took the example from the manual, on how to convert html tags to upper case...

if you don't use a class:

<?php
$extra_info
= "some extra info";

echo
preg_replace("/(<\/?)(\w+)([^>]*>)/e", "html_body('\\1', '\\2', '\\3', '$extra_info')", $html_body);

function
html_body($open_tag, $the_tag, $close_tag, $extra_info) {

         return
$before . strtoupper($middle) . $after;
}
?>

class example:

<?php

class SomeClass {

     function
SomeClass() {
        
$extra_info = "some extra info";
         echo
preg_replace("/(<\/?)(\w+)([^>]*>)/e", "\\$this->html_body('\\1', '\\2', '\\3', '$extra_info')", $html_body);
     }

     function
html_body($open_tag, $the_tag, $close_tag, $extra_info) {

             return
$before . strtoupper($middle) . $after;
     }
}
?>

now, if you'd like to pass an array :)

<?php
$extra_info
= ("some extra info", "little more", "a bit more");

echo
preg_replace("/(<\/?)(\w+)([^>]*>)/e", "html_body('\\1', '\\2', '\\3', \\$extra_info)", $html_body);

function
html_body($open_tag, $the_tag, $close_tag, $extra_info) {

         return
$before . strtoupper($middle) . $after;
}
?>

you can also pass the array as a reference...cool!

hope this helps someone! ;)
ply at ukrpost dot net
16-Jun-2004 06:11
Example given for preg_replace_callback with create_function have one flow - it consume a lot of memory. Each call to create_function allocate some memory to create a new function, and to the end of the script a lot of memory could be consumed, if there are many lines recieved from stdin. Of course after ending all memory will be freed, but I have been hanged up my computer processing 20Mb file with similar code on Windows.

<?php
 $fp
= fopen("php://stdin", "r") or die("can't read stdin");
 while (!
feof($fp)) {
  
$line = fgets($fp);
  
$line = preg_replace_callback('|<p>\s*\w|',
// this will create as many new functions, as there are lines recieved
    
create_function(
      
'$matches',
      
'return strtolower($matches[0]);'
    
),
//
    
$line
  
);
   echo
$line;
 }
 
fclose($fp);
?>
ben at benchun dot net
19-Mar-2004 12:19
a repost (with correction) of a note originally attributed to matthew at de-construct dot com (26-Aug-2003 04:48) - a note i found in a compiled help file but which i do not see on this site.  he was missing a stripslashes call in his example, which i have fixed below:

<?
$outer_data
= array('foo'=>'abc','bar'=>'123');
$callback = create_function('$matches',
'
$inner_data = unserialize(stripslashes("'
. addslashes(serialize($outer_data)) .'"));
// do something here
return $inner_data[$matches[1]];
'
);
$template_data = preg_replace_callback("/\{[$](.*?)\}/", $callback, $template_data);
?>

i found this very useful for implementing a template engine!
spamhoneypot at acksys dot co dot uk
17-Dec-2003 10:08
I thought I'd post this as I'd been using the function preg_replace_callback with NP within functions, but as soon as I re-wrote my script as a class, it all fell apart.

When debugging I ended up writing test code and this is what follows. It works as I'd expect, YMMV of course.

<?php

// v v over simplified
class foo
{
  function
parse()
  {
  
$pattern = "/<a(.*?)href\s*=\s*['|\"|\s*](.*?)['|\"|>](.*?)>(.*?)<\/a>/i";
  
$string = "<a class='whatever' href='http://foo.com' target='_blank'>foo</a>";
   print
preg_replace_callback($pattern,array($this,'cb'),$string);
  }

  function
cb($matches)
  {
   return
"<a" . $matches[1] . "href='http://someothersite.com/foo.php?page=" . urlencode($matches[2]) . "'" . $matches[3] . ">" . $matches[4] . "</a>";
  }

}

$bar = new foo();
$bar->parse();

/**
output is
<a class='whatever' href='http://someothersite.com/foo.php?page=http%3A%2F%2Ffoo.com' target='_blank'>foo</a>
*/

?>
vlad4321 at fastmail dot fm
12-Nov-2003 07:47
You can use this function to display url address as a link or image based on suffix of the url.

Every string beginning with www or http(s):// will be displayed as an URL address.  But if the string ends with *.jpg or *.gif, it will be displayed as an image.

Please note: This function also verifies the size of the image. If is it more than 600x400, it changes it.
<?php

$pattern_html
= "/\b((http(s?):\/\/)|(www\.))([\w\.]+)";
$pattern_html .= "([\#\,\/\~\?\&\=\;\%\-\w+\.]+)\b/i";

$text = preg_replace_callback($pattern_html,'Check_if_Image',$text);
.
.
function
Check_if_Image($matches) {
$suffix = strtolower(substr($matches[0],-4));
if (
$suffix == '.jpg' or $suffix == '.gif') {
  
$dsn = 'http'.$matches[3].'://'.$matches[4].$matches[5];
   if (list(
$width, $height, $type, $attr) = getimagesize("$dsn")) {
   if ( (
$width > 600) ) {
      
$koef = $width / 600;
      
$width = 600;
      
$height /= $koef;
       }
   if ( (
$height > 400) ) {
      
$koef = $height / 400;
      
$height = 400;
      
$width /= $koef;
       }
   }
   else {
      
$height=400;
      
$width=600;
   }

  
$ret = "<img src=\"$dsn\"";
  
$ret .= "\" border=0 width=\"$width\" height=\"$height\">";
   }
else {
  
$ret = '<a href="http'.$matches[3].'://'.$matches[4].$matches[5];
  
$ret .='" target="_blank">'.$matches[0].'</a>';
   }
return (
"$ret");
}

?>
28-Oct-2003 03:05
Here's an example of how to evaluate PHP code inside a HTML block. In my example I have two timestamps which I insert at several places in a HTML-template. The formatting for the dates are put inside the template. When the insert is done the code is evaluated and the dates are formatted. All code inside "<php>" to "</php>"-tags will be evaluated.
-----------------------------------------------------------
function evalcode($matches)
{
eval("\\$matches[2] = $matches[2];");
return $matches[2];
}

$template = "<html><body>This really simple HTML-page that was created at <php>date('Y-m-d',{datecreated})</php> and modified at <php>date('H:i:s',{datemodified})</php> on the <php>date('dS',{datemodified})</php> of <php>date('F Y',{datemodified})</php>.</body></html>";

$datecreated = 1065162746;
$datemodified = time();
$output = $template;
$output = str_replace('{datecreated}', $datecreated, $output);
$output = str_replace('{datemodified}', $datemodified, $output);
$output = preg_replace_callback("/(<php>)(.*)(<\\/php>)/U", "evalcode", $output);
echo $output;
----------------------------------------------------------
The output will be something like "This really simple HTML-page that was created at 2003-10-03 and modified at 16:58:31 on the 27th of October 2003."
e-mail at dreamguard dot at
23-Oct-2003 05:02
If you want to do a date function in a template system you'll have to use the callback here.

Ex.:
function date_match ($matches)
{
   return date ($matches['2']);
}
$output = preg_replace_callback ('/({DATE=")(.{1,})("})/', 'date_match', $input);
hans at velum dot net
01-Aug-2003 10:31
Also, if you want to use a *static* class method for the callback function, you can refer to it like this:
   preg_replace_callback(pattern, array('ClassName', 'methodName'), subject)

In PHP5, from within the class:
   preg_replace_callback(pattern, array('self', 'methodName'), subject)
nospam at hirnbrand dot de
17-Oct-2002 02:58
if you want to pass the callback function more parameters than just match parts, use the e-modifier in preg_replace instead of preg_replace_callback
oyoryelNOSPAM at hotNOSPAMmail dot com
22-Apr-2002 12:23
If you want to be able to change variables of the class in the callback function, you have to use preg_replace_callback(pattern, array(&$this, 'method_name'), subject)
Probably very obvious, but it kept me busy for a while...