PHP Conference Japan 2024

preg_replace_callback

(PHP 4 >= 4.0.5, PHP 5, PHP 7, PHP 8)

preg_replace_callback使用回调执行正则表达式搜索和替换

描述

preg_replace_callback(
    字符串|数组 $pattern,
    可调用 $callback,
    字符串|数组 $subject,
    整数 $limit = -1,
    整数 &$count = null,
    整数 $flags = 0
): 字符串|数组|null

此函数的行为几乎与 preg_replace() 相同,除了它使用 callback 参数代替 replacement 参数。

参数

pattern

要搜索的模式。它可以是字符串,也可以是包含字符串的数组。

callback

将被调用并传递 subject 字符串中匹配元素数组的回调函数。回调函数应返回替换字符串。这是回调函数签名

handler(数组 $matches): 字符串

您通常只需要在一个地方使用 preg_replace_callback()callback 函数。在这种情况下,您可以使用 匿名函数 在对 preg_replace_callback() 的调用中声明回调函数。通过这种方式,您可以将所有调用信息放在一个地方,并且不会因在其他地方未使用的回调函数名称而使函数命名空间混乱。

示例 #1 preg_replace_callback() 和匿名函数

<?php
/* 一个类 Unix 的命令行过滤器,用于将段落开头的
* 大写字母转换为小写 */
$fp = fopen("php://stdin", "r") or die("无法读取标准输入");
while (!
feof($fp)) {
$line = fgets($fp);
$line = preg_replace_callback(
'|<p>\s*\w|',
function (
$matches) {
return
strtolower($matches[0]);
},
$line
);
echo
$line;
}
fclose($fp);
?>

subject

要搜索和替换的字符串或包含字符串的数组。

limit

每个模式在每个 subject 字符串中进行的最大替换次数。默认为 -1(无限制)。

count

如果指定,此变量将填充已执行的替换次数。

flags

flags 可以是 PREG_OFFSET_CAPTUREPREG_UNMATCHED_AS_NULL 标志的组合,这些标志会影响匹配数组的格式。有关更多详细信息,请参见 preg_match() 中的说明。

返回值

preg_replace_callback() 如果 subject 参数是数组,则返回数组,否则返回字符串。如果发生错误,则返回值为 null

如果找到匹配项,则返回新的 subject,否则返回未更改的 subject

错误/异常

如果传递的正则表达式模式无法编译为有效的正则表达式,则会发出 E_WARNING

变更日志

版本 描述
7.4.0 添加了 flags 参数。

示例

示例 #2 preg_replace_callback() 示例

<?php
// 此文本于 2002 年使用
// 我们希望将其更新到 2003 年
$text = "愚人节是 04/01/2002\n";
$text.= "去年圣诞节是 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

示例 #3 使用递归结构处理封装的 BB 代码的 preg_replace_callback()

<?php
$input
= "plain [indent] deep [indent] deeper [/indent] deep [/indent] plain";

function
parseTagsRecursive($input)
{

$regex = '#\[indent]((?:[^[]|\[(?!/?indent])|(?R))+)\[/indent]#';

if (
is_array($input)) {
$input = '<div style="margin-left: 10px">'.$input[1].'</div>';
}

return
preg_replace_callback($regex, 'parseTagsRecursive', $input);
}

$output = parseTagsRecursive($input);

echo
$output;
?>

另请参阅

添加注释

用户贡献的注释 22 条注释

Richard
12 年前
将多个参数传递给回调函数的最简单方法是使用“use”关键字。

[这比使用全局变量更好,因为它即使在函数内部也能正常工作。]

在此示例中,回调函数是一个匿名函数,它接受一个参数 $match,由 preg_replace_callback() 提供。额外的
“use ($ten)” 将 $ten 变量引入函数的作用域。

<?php
$string
= "Some numbers: one: 1; two: 2; three: 3 end";
$ten = 10;
$newstring = preg_replace_callback(
'/(\\d+)/',
function(
$match) use ($ten) { return (($match[0] + $ten)); },
$string
);
echo
$newstring;
#输出 "Some numbers: one: 11; two: 12; three: 13 end";
?>
Sjon at hortensius dot net
17 年前
当达到 pcre.backtrack_limit 时,preg_replace_callback 返回 NULL;这有时比您预期的更快。也不会引发任何错误;因此不要忘记自己检查 NULL
Yuri
12 年前
如果要在类中调用非静态函数,可以执行以下操作。

对于 PHP 5.2,请将第二个参数用作 array($this, 'replace')
<?php
class test_preg_callback{

private function
process($text){
$reg = "/\{([0-9a-zA-Z\- ]+)\:([0-9a-zA-Z\- ]+):?\}/";
return
preg_replace_callback($reg, array($this, 'replace'), $text);
}

private function
replace($matches){
if (
method_exists($this, $matches[1])){
return @
$this->$matches[1]($matches[2]);
}
}
}
?>

对于 PHP 5.3,请将第二个参数用作 "self::replace"
<?php
class test_preg_callback{

private function
process($text){
$reg = "/\{([0-9a-zA-Z\- ]+)\:([0-9a-zA-Z\- ]+):?\}/";
return
preg_replace_callback($reg, "self::replace", $text);
}

private function
replace($matches){
if (
method_exists($this, $matches[1])){
return @
$this->$matches[1]($matches[2]);
}
}
}
?>
carlos dot ballesteros at softonic dot com
15 年前
一个简单的函数,用于替换字符串中完整单词或术语的列表(对于 PHP 5.3 或更高版本,因为使用了闭包)

<?php
function replace_words($list, $line, $callback) {
return
preg_replace_callback(
'/(^|[^\\w\\-])(' . implode('|', array_map('preg_quote', $list)) . ')($|[^\\w\\-])/mi',
function(
$v) use ($callback) { return $v[1] . $callback($v[2]) . $v[3]; },
$line
);
}
?>

用法示例
<?php
$list
= array('php', 'apache web server');
$str = "php and the apache web server work fine together. php-gtk, for example, won't match. apache web servers shouldn't too.";

echo
replace_words($list, $str, function($v) {
return
"<strong>{$v}</strong>";
});
?>
Drake
14 年前
PhpHex2Str 类的改进版本
<?php
PhpHex2Str
{
private
$strings;

private static function
x_hex2str($hex) {
$hex = substr($hex[0], 1);
$str = '';
for(
$i=0;$i < strlen($hex);$i+=2) {
$str.=chr(hexdec(substr($hex,$i,2)));
}
return
$str;
}

public function
decode($strings = null) {
$this->strings = (string) $strings;
return
preg_replace_callback('#\%[a-zA-Z0-9]{2}#', 'PhpHex2Str::x_hex2str', $this->strings);
}
}

// 示例
$obj = new PhpHex2Str;

$strings = $obj->decode($strings);
var_dump($strings);
?>
matt at mattsoft dot net
18 年前
使用 preg_replace_callback 函数而不是带 e 修饰符的 preg_replace 函数,在性能和最佳实践方面都更好。

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

// 运行 50000 次需要 2.76 秒
preg_replace("/\{(.*?)\}/e","a('\\1','\\2','\\3',\$b)",$a);

// 运行 50000 次需要 0.97 秒
preg_replace_callback("/\{(.*?)\}/s","a",$a);
Fredow
9 年前
<?php
// 一个将字符串转换为大写但保留 HTML 实体的小函数。
public static function strtoupper_entities($str) {

$patternMajEntities = '/(\&([A-Z])(ACUTE|CEDIL|CARON|CIRC|GRAVE|ORN|RING|SLASH|TH|TILDE|UML)\;)+/';
$str = preg_replace_callback ($patternMajEntities,
function (
$matches) {
return
"&" . $matches[2] . strtolower($matches[3]) . ";";
},
strtoupper($str));

return
$str;
}
T-Soloveychik at ya dot ru
11 年前
文本行编号
<?PHP
// 多行文本:
$Text = "
Some
Multieline
text
for
numeration"
;

// 用于计数:
$GLOBALS["LineNUMBER"] = 1;

// 将行首替换为编号:
PRINT preg_replace_callback("/^/m",function ()
{
return
$GLOBALS["LineNUMBER"]++." ";
},
$Text);

?>

1
2 Some
3 Multieline
4 text
5 for
6 numeration
development at HashNotAdam dot com
12 年前
从 PHP 5.3 开始,您可以使用匿名函数将局部变量传递到回调函数中。

<?php

public function replace_variables( $subject, $otherVars ) {
$linkPatterns = array(
'/(<a .*)href=(")([^"]*)"([^>]*)>/U',
"/(<a .*)href=(')([^']*)'([^>]*)>/U"
);

$callback = function( $matches ) use ( $otherVars ) {
$this->replace_callback($matches, $otherVars);
};

return
preg_replace_callback($this->patterns, $callback, $subject);
}

public function
replace_callback($matches, $otherVars) {
return
$matches[1] . $otherVars['myVar'];
}
?>
kkatpki
11 年前
请注意,从 PHP 5.3 开始,命名的子模式现在似乎也包含在匹配数组中,既有其命名键,也有其数字键。

为了扩展 Chris 之前的示例,从 PHP 5.3 开始,您*可以*执行以下操作

<?php

preg_replace_callback
('/(?<char>[a-z])/', 'callback', 'word');

function
callback($matches) {
var_dump($matches);
}

?>

并期望在您的函数中获得 $matches['char']。* 但仅限于 PHP 5.3*

如果您打算支持 PHP 5.2,请注意这一点。
Florian Arndt
12 年前
这个小程序允许 PHP 用户读取包含 include 语句的 JSON 文件。例如,include {{{ "relative/to/including.json" }}} 将被替换为位于 "relative/to/including.json" 的 JSON 文件的内容。

<?php
/**
* 处理包含 include 的 JSON 文件
* 目的:通过引入“include”来处理较大的 JSON 文件
*
* @author Florian Arndt
*/
JWI {
/**
* 解析 JSON 文件并返回其内容
* @param String $filename
*/
static function read($filename) {
if(!
file_exists($filename))
throw new
Exception('<b>JWI 错误:未找到 JSON 文件 <tt>'.$filename.'</tt>!</b>');
$content = join('', file($filename));
$dir = dirname($filename);
/**
* 替换
* include 语句
* 为
* 要包含的文件的内容
* 递归
*/
$content = preg_replace_callback(
'/{{{\s*"\s*(.+)\s*"\s*}}}/', // >include 文件< - 模式
create_function(
'$matches', // 回调参数
sprintf(
'$fn = "%s/".$matches[1];'.
'return JWI::read($fn);',
realpath(dirname($filename))
)
),
$content
);
return
$content;
}
}
chris at ocproducts dot com
14 年前
pcre.backtrack_limit 选项(在 PHP 5.2 中添加)可能会触发 NULL 返回,并且没有错误。默认的 pcre.backtrack_limit 值为 100000。如果您的匹配超过此限制的大约一半,则会触发 NULL 响应。
例如,我的限制为 100000,但 500500 触发了 NULL 响应。我没有运行 Unicode,但我*猜测* PCRE 以 utf-16 运行。
Anonymous
14 年前
创建此代码是为了获取锚标记的链接和名称。我在将 HTML 邮件清理为文本时使用它。不建议将正则表达式用于 HTML,但出于此目的,我认为没有问题。此代码并非设计用于处理嵌套的锚标记。

需要注意的一点
我主要关注的是有效的 HTML,因此如果属性不使用 ' 或 " 来包含值,则需要调整此代码。
如果您能编辑此代码使其工作得更好,请告诉我。


<?php
/**
* 将锚点标签替换为文本
* - 将搜索字符串并用文本替换所有锚点标签(不区分大小写)
*
* 工作原理:
* - 在字符串中搜索锚点标签,检查它是否符合条件
* 锚点搜索条件:
* - 1 - <a(必须具有锚点标签的开头)
* - 2 - 在 href 属性前后可以有任意数量的空格或其他属性
* - 3 - 必须关闭锚点标签
*
* - 检查通过后,它将用字符串替换替换锚点标签
* - 字符串替换可以自定义
*
* 已知问题:
* - 这不适用于不使用 ' 或 " 包含属性的锚点。
* (例如 - <a href=http: //php.net>PHP.net</a> 将不会被替换)
*/
function replaceAnchorsWithText($data) {
/**
* 必须修改 $regex 以便它可以发布到网站... 所以将其分成 6 部分。
*/
$regex = '/(<a\s*'; // 锚点标签的开头
$regex .= '(.*?)\s*'; // 可能存在或不存在的任何属性或空格
$regex .= 'href=[\'"]+?\s*(?P<link>\S+)\s*[\'"]+?'; // 获取链接
$regex .= '\s*(.*?)\s*>\s*'; // 闭合标签之前可能存在或不存在的任何属性或空格
$regex .= '(?P<name>\S+)'; // 获取名称
$regex .= '\s*<\/a>)/i'; // 闭合锚点标签之间的任意数量的空格(不区分大小写)

if (is_array($data)) {
// 这将替换链接(根据您的喜好修改)
$data = "{$data['name']}({$data['link']})";
}
return
preg_replace_callback($regex, 'replaceAnchorsWithText', $data);
}

$input = 'Test 1: <a href="http: //php.ac.cn1">PHP.NET1</a>.<br />';
$input .= 'Test 2: <A name="test" HREF=\'HTTP: //PHP.NET2\' target="_blank">PHP.NET2</A>.<BR />';
$input .= 'Test 3: <a hRef=http: //php.ac.cn3>php.net3</a><br />';
$input .= 'This last line had nothing to do with any of this';

echo
replaceAnchorsWithText($input).'<hr/>';
?>
将输出
Test 1: PHP.NET1(http: //php.ac.cn1).
Test 2: PHP.NET2(HTTP: //PHP.NET2).
Test 3: php.net3 (仍然是锚点)
This last line had nothing to do with any of this
Evgeny
1 year ago
请注意!如果您已定义命名空间,
则使用格式必须更改

echo preg_replace_callback(
"|(\d{2}/\d{2}/)(\d{4})|",
__NAMESPACE__ . '\\next_year',
$text);
steven at nevvix dot com
6 years ago
<?php
$format
= <<<SQL
CREATE DATABASE IF NOT EXISTS :database;
GRANT ALL PRIVILEGES ON :database_name.* TO ':user'@':host';
SET PASSWORD = PASSWORD(':pass');
SQL;
$args = ["database"=>"people", "user"=>"staff", "pass"=>"pass123", "host"=>"localhost"];

preg_replace_callback("/:(\w+)/", function ($matches) use ($args) {
return @
$args[$matches[1]] ?: $matches[0];
},
$format);

/*
结果:

CREATE DATABASE IF NOT EXISTS people;
GRANT ALL PRIVILEGES ON :database_name.* TO 'staff'@'localhost';
SET PASSWORD = PASSWORD('pass123');

`:database_name` 占位符在 `$args` 中不存在作为匹配键,因此按原样返回。
这样您就知道需要通过添加 "database_name" 项目来更正数组。
*/
2962051004 at qq dot com
6 years ago
<?php

/**
* 将中文转为Html实体
* Turning Chinese into Html entity
* 作者 QiangGe
* 邮箱 [email protected]
*
*/

$str = <<<EOT
你好 world
EOT;

function
ChineseToEntity($str) {
return
preg_replace_callback(
'/[\x{4e00}-\x{9fa5}]/u', // utf-8
// '/[\x7f-\xff]+/', // if gb2312
function ($matches) {
$json = json_encode(array($matches[0]));
preg_match('/\[\"(.*)\"\]/', $json, $arr);
/*
* 通过 json_encode 函数将中文转为 unicode
* 然后用正则取出 unicode
* Turn the Chinese into Unicode through the json_encode function, then extract Unicode from regular.
* 我觉得这个思路很巧妙。
*/
return '&#x'. str_replace('\\u', '', $arr[1]). ';';
},
$str
);
}

echo
ChineseToEntity($str);
// &#x4f60;&#x597d; world
Drake
14 年前
将十六进制解码为字符串=)
<?php
class PhpHex2Str
{
private
$strings;

private function
x_hex2str($hex) {
$hex = substr($hex[0], 1);
$str = '';
for(
$i=0;$i < strlen($hex);$i+=2) {
$str.=chr(hexdec(substr($hex,$i,2)));
}
return
$str;
}

public function
decode($strings = null) {
$this->strings = (string) $strings;
return
preg_replace_callback('#\%[a-zA-Z0-9]{2}#', 'x_hex2str', $this->strings);
}
}

// 示例
$strings = 'a %20 b%0A h %27 h %23';

$obj = new PhpHex2Str;
$strings = $obj->decode($strings);
var_dump($strings);
?>
Anteaus
9 年前
请注意,从 php5.4 开始,您**绝不能**通过引用传递变量,如 '[, int &$count ]' 中那样 - 如果您这样做会导致致命错误。
我认为作者试图说该函数通过引用接受参数,但这不是它的读取方式。- 手册需要更新/澄清?
alex dot cs00 at yahoo dot ca
14 年前
不要使用此函数来获取 BBCode,如所述。如果您有一些超过 5000 个字符(平均)的文本,它将超出其限制并使您下载 PHP 页面。

根据此建议,您应该使用更高级但更复杂的方案。您将需要一个名为“str_replace_once()”的函数(搜索一下),一个名为“countWord()”的函数,以及著名的“after()”、“before()”、“between()”函数。

str_replace_once 的功能与 str_replace 相同,但只替换第一个匹配项。至于 countWord,我想您知道如何计算某个单词出现的次数。至于 after、before 和 between,这些函数您可能可以在网站上的某个地方找到用户提供的实现。否则,您可以自己实现它们。

以下函数能够处理所有代码块,假设使用 [code] 和 [/code],您可能希望父标签之间的内容不被解析,包括 [code] 如果它位于另一个 [code] 内部。

<?php
function prepareCode($code, $op, $end)
{
$ix = 0;
$iy = 0;
$nbr_Op = countWord($op, $code);
while(
$ix < $nbr_Op)
{
if(
in_string($op, before($end, $code), false))
{
// 以下代码片段将默认的 [tag] 替换为 [tag:#]
$code = str_replace_once($op, substr($op, 0, -1).':'.$ix.']', $code);
$iy++;
}
elseif(
in_string($end, before($op, $code), false))
{
$iy = $iy-1;
$code = str_replace_once($end, substr($end, 0, -1).':'.($ix-1).']', $code);
$ix = $ix-2;
}
$ix++;
}
while(
in_string($end, $code))
{
$code = str_replace_once($end, substr($end, 0, -1).':'.($iy-1).']', $code);
$iy=$iy-1;
}

$code = preg_replace('#\\'.substr($end, 0, 1).':-[0-9]\]#i', '', $code);
if(
in_string(substr($op, 0, -1).':0]', $code) && !in_string(substr($end, 0, -1).':0]', $code))
{
$code .= substr($end, 0, -1).":0]";
}
return
$code;
}
?>

$code 返回整个半格式化的文本。您只需要像这样使用它
$code = prepareCode($code="您的文本", $op="[tag]" , $end="[/tag]");
然后只需替换父标签
str_replace("[tag:0]", "<tag>", $code);
str_replace("[/tag:0]", "</tag>", $code);
所以最终结果类似于
[
jobowo
2 年前
请注意,当使用 'Use ($variable)' 与 preg_replace_callback 时,如果您希望匿名函数修改该值,则必须通过引用传递该值。例如 preg_replace_callback($pattern, function($matches) use (&$alterThis) { $alterThis+=$something;},$string);
Underdog
10 年前
对于回调函数,我建议只使用永久函数或匿名函数。

根据使用情况,在使用 create_function 作为回调函数时,您可能会遇到内存问题,这可能是由于尝试与 PHP 5.2 或更早版本兼容导致的。某些服务器出于某种原因拒绝更新其 PHP 版本。

请仔细阅读 create_function 文档,以获取有关其内存用法的更多详细信息。

此致。
tgage at nobigfoot dot com
6 years ago
要使用匿名回调函数传递给 preg_replace_callback() 的父作用域中的变量,请使用 use() 参数。

$var1 = "one";
$var2 = "two";
$line = preg_replace_callback('/^.*$/',
function( $matches ) use ( $var1, $var2 ) {
return( $var1 . " " . $var2 );
}, $line);

将用父作用域中 $var1 和 $var2 的连接值(“one two”)替换整个字符串。
To Top