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() 完全相同,除了它並非使用 replacement 參數,而是指定一個 callback 參數。

參數

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("無法讀取 stdin");
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 字串中,每個模式的最大可能取代次數。預設為 -1(無限制)。

計數

如果指定,此變數將會填入已完成的取代次數。

旗標

flags 可以是 PREG_OFFSET_CAPTUREPREG_UNMATCHED_AS_NULL 旗標的組合,這些旗標會影響相符陣列的格式。有關更多詳細資訊,請參閱 preg_match() 中的說明。

回傳值

如果 subject 參數是陣列,preg_replace_callback() 會返回一個陣列;否則,會返回一個字串。發生錯誤時,回傳值為 null

如果找到相符項目,將會返回新的主旨;否則,將會返回未更改的 subject

錯誤/例外

如果傳遞的正規表示式模式無法編譯成有效的正規表示式,則會發出 E_WARNING

更新日誌

版本 說明
7.4.0 新增了 flags 參數。

範例

範例 #2 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

範例 #3 使用遞迴結構處理封裝 BBCode 的 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' 關鍵字。

[這比使用全域變數更好,因為即使我們已經在函式內部,它也能正常運作。]

在此範例中,回呼函式是一個匿名函式,它接受一個由 preg_replace_callback() 提供的參數 $match。「額外的
「use ($ten)」將 $ten 變數放入函式的作用域中。

<?php
$string
= "一些數字:一:1;二:2;三:3 結束";
$ten = 10;
$newstring = preg_replace_callback(
'/(\\d+)/',
function(
$match) use ($ten) { return (($match[0] + $ten)); },
$string
);
echo
$newstring;
#印出 "一些數字:一:11;二:12;三:13 結束";
?>
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
類別 test_preg_callback{

私有函式
process($text){
$reg = "/\{([0-9a-zA-Z\- ]+)\:([0-9a-zA-Z\- ]+):?\}/";
返回
preg_replace_callback($reg, "self::replace", $text);
}

私有函式
replace($matches){
如果 (
method_exists($this, $matches[1])){
返回 @
$this->$matches[1]($matches[2]);
}
}
}
?>
carlos dot ballesteros at softonic dot com
15 年前
用於替換字串中完整單字或詞彙列表的簡單函式(適用於 PHP 5.3 或更高版本,因為使用了閉包)

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

使用範例
<?php
$list
= 陣列('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.";

回應
replace_words($list, $str, function($v) {
返回
"<strong>{$v}</strong>";
});
?>
Drake
14 年前
PhpHex2Str 類別的正確版本
<?php
class 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
/**
* 處理包含其他檔案的 JSON 檔案
* 用途:透過「include」功能處理較大的 JSON 檔案
*
* @author Florian Arndt
*/
class 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 陳述式
* 替換為
* 要 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 執行。
匿名
14 年前
建立這個程式碼片段是為了取得錨點標籤的連結和名稱。我在將 HTML 電子郵件清理成純文字時使用它。雖然不建議對 HTML 使用正規表示式,但在這種情況下,我認為沒有問題。這並非設計用於處理巢狀錨點。

需要注意的一點
我主要關心的是有效的 HTML,所以如果屬性沒有使用 ' 或 " 來包含值,則需要調整這段程式碼。
如果您能編輯這段程式碼使其運作得更好,請告訴我。
<?php
/**
* 將錨點標籤替換為文字
* - 將搜尋字串並將所有錨點標籤替換為文字(不區分大小寫)
*
* 運作方式:
* - 在字串中搜尋錨點標籤,檢查是否符合條件
* 錨點搜尋條件:
* - 1 - <a(必須要有錨點標籤的開頭)
* - 2 - href 屬性前後可以有任意數量的空格或其他屬性
* - 3 - 必須關閉錨點標籤
*
* - 一旦檢查通過,它就會將錨點標籤替換為字串替代文字
* - 字串替代文字可以自訂
*
* 已知問題:
* - 這不適用於不使用 ' 或 " 來包含屬性的錨點。
* (例如:<a href=http: //php.dev.org.tw>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 = '測試 1: <a href="http: //php.dev.org.tw1">PHP.NET1</a>.<br />';
$input .= '測試 2: <A name="test" HREF=\'HTTP: //PHP.NET2\' target="_blank">PHP.NET2</A>.<BR />';
$input .= '測試 3: <a hRef=http: //php.dev.org.tw3>php.net3</a><br />';
$input .= '最後一行與此無關';

echo
replaceAnchorsWithText($input).'<hr/>';
?>
將輸出
測試 1: PHP.NET1(http: //php.dev.org.tw1)。
測試 2: PHP.NET2(HTTP: //PHP.NET2)。
測試 3: php.net3 (仍然是一個錨點)
最後這一行和以上內容無關。
Evgeny
1 年前
請注意!如果您定義了命名空間 (namespace),
則使用格式必須更改為

echo preg_replace_callback(
"|(\d{2}/\d{2}/)(\d{4})|",
__NAMESPACE__ . '\\next_year',
$text);
steven at nevvix dot com
6 年前
<?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');

由於 `$args` 中沒有符合 `:database_name` 的鍵值,所以它會被原樣返回。
這樣您就知道需要透過新增 "database_name" 項目來修正陣列。
*/
2962051004 at qq dot com
6 年前
<?php

/**
* 將中文轉換為 HTML 實體
* Turning Chinese into Html entity
* 作者:QiangGe
* 信箱:2962051004@qq.com
*
*/

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

function
ChineseToEntity($str) {
return
preg_replace_callback(
'/[\x{4e00}-\x{9fa5}]/u', // utf-8
// '/[\x7f-\xff]+/', // 如果是 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.
* I think this idea is seamless.
*/
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))
{
// The following piece of code replace the default [tag] by [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 年前
要使用傳遞給 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