在 php 和正则表达式中同时进行 Preg_replace 运算

Simultaneous Preg_replace operation in php and regex

我知道很多用户都问过这类问题,但我陷入了一个奇怪的境地。

我正在尝试一种逻辑,如果找到匹配项,具有唯一标识符的特定模式的多次出现将替换为某些条件数据库内容。

我的正则表达式模式是

'/{code#(\d+)}/'

其中 'd+' 将是我对上述模式的唯一标识符。

我的Php代码是:

<?php

 $text="The old version is {code#1}, The new version is {code#2}, The stable version is {code#3}"; 
 $newsld=preg_match_all('/{code#(\d+)}/',$text,$arr);
 $data = array("first Replace","Second Replace", "Third Replace");
 echo $data=str_replace($arr[0], $data, $text);

?>

这有效,但它根本不是动态的,模式中#tag 之后的数字是 id,即 1、2 和 3,它们各自的数据存储在数据库中。

我如何从模式中提到的相应 ID 的数据库中访问内容,并用相应的内容替换整个模式。

我真的没有办法。提前谢谢你

想想也不是那么难。我将使用 prepared statements 的 PDO。那么让我们设置一下:

$db = new PDO(                                      // New PDO object
    'mysql:host=localhost;dbname=projectn;charset=utf8', // Important: utf8 all the way through
    'username',
    'password',
    array(
        PDO::ATTR_EMULATE_PREPARES => false,        // Turn off prepare emulation
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    )
);

这是我们数据库的最基本设置。查看 this thread to learn more about emulated prepared statements and this external link 以开始使用 PDO。

我们从某个地方获得了输入,为了简单起见,我们将定义它:

$text = 'The old version is {code#1}, The new version is {code#2}, The stable version {code#3}';

现在有几种方法可以实现我们的目标。我给你看两个:

1.使用 preg_replace_callback():

$output = preg_replace_callback('/{code#(\d+)}/', function($m) use($db) {
    $stmt = $db->prepare('SELECT `content` FROM `footable` WHERE `id`=?');
    $stmt->execute(array($m[1]));
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if($row === false){
        return $m[0]; // Default value is the code we captured if there's no match in de DB
    }else{
        return $row['content'];
    }
}, $text);

echo $output;

注意我们如何使用 use() 来获得匿名函数范围内的 $dbglobal邪恶

现在的缺点是此代码将查询数据库以获取它遇到的每个代码以替换它。优点是在数据库中没有匹配项的情况下设置默认值。如果你没有那么多代码要替换,我会选择这个解决方案。

2。使用 preg_match_all():

if(preg_match_all('/{code#(\d+)}/', $text, $m)){
    $codes = $m[1];     // For sanity/tracking purposes
    $inQuery = implode(',', array_fill(0, count($codes), '?')); // Nice common trick: 
    $stmt = $db->prepare('SELECT `content` FROM `footable` WHERE `id` IN(' . $inQuery . ')');
    $stmt->execute($codes);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $contents = array_map(function($v){
        return $v['content'];
    }, $rows); // Get the content in a nice (numbered) array

    $patterns = array_fill(0, count($codes), '/{code#(\d+)}/'); // Create an array of the same pattern N times (N = the amount of codes we have)
    $text = preg_replace($patterns, $contents, $text, 1); // Do not forget to limit a replace to 1 (for each code)
    echo $text;
}else{
    echo 'no match';
}

上面代码的问题在于,如果数据库中没有匹配项,它会将代码替换为空值。这也可能会向上移动值,从而导致移动替换。示例(代码#2 在数据库中不存在):

Input: foo {code#1}, bar {code#2}, baz {code#3}
Output: foo AAA, bar CCC, baz
Expected output: foo AAA, bar , baz CCC

preg_replace_callback() 按预期工作。也许您可以想到一个混合解决方案。我会把它作为你的作业:)

这是解决问题的另一种变体:由于访问数据库的成本最高,我会选择一种设计,允许您为所有使用的代码查询一次数据库。

您得到的文本可以用不同的段表示,即 <TEXT><CODE> 标记的任意组合:

The old version is {code#1}, The new version is {code#2}, ...
<TEXT_____________><CODE__><TEXT_______________><CODE__><TEXT_ ...

将您的字符串缓冲区标记为这样的序列可以让您获得文档中使用的代码以及与代码相关的段的索引。

然后您可以获取每个代码的替换,然后用替换替换该代码的所有段。

让我们进行设置并定义输入文本、您的模式和标记类型:

$input = <<<BUFFER
The old version is {code#1}, The new version is {code#2}, The stable version is {code#3}
BUFFER;

$regex = '/{code#(\d+)}/';

const TOKEN_TEXT = 1;
const TOKEN_CODE = 2;

接下来是将输入分开放入标记中的部分,为此我使用了两个数组。一个是存储令牌的类型($tokens;文本或代码),另一个数组包含字符串数据($segments)。输入被复制到一个缓冲区中,缓冲区被消耗直到它为空:

$tokens   = [];
$segments = [];

$buffer = $input;

while (preg_match($regex, $buffer, $matches, PREG_OFFSET_CAPTURE, 0)) {
    if ($matches[0][1]) {
        $tokens[]   = TOKEN_TEXT;
        $segments[] = substr($buffer, 0, $matches[0][1]);
    }

    $tokens[]   = TOKEN_CODE;
    $segments[] = $matches[0][0];

    $buffer = substr($buffer, $matches[0][1] + strlen($matches[0][0]));
}

if (strlen($buffer)) {
    $tokens[]   = TOKEN_TEXT;
    $segments[] = $buffer;
    $buffer = "";
}

现在所有的输入都已经被处理并变成了标记和段。

现在这个"token-stream"可以用来获取所有使用的代码。此外,所有代码令牌都已编入索引,因此可以根据代码的编号确定哪些段需要替换。索引在 $patterns 数组中完成:

$patterns = [];

foreach ($tokens as $index => $token) {
    if ($token !== TOKEN_CODE) {
        continue;
    }
    preg_match($regex, $segments[$index], $matches);
    $code              = (int)$matches[1];
    $patterns[$code][] = $index;
}

现在,由于已从字符串中获取了所有代码,因此可以制定数据库查询来获取替换值。我通过创建行的结果数组来模拟该功能。这应该是为了这个例子。从技术上讲,您将触发一个允许一次获取所有结果的 SELECT ... FROM ... WHERE code IN (12, 44, ...) 查询。我通过计算结果来伪造这个:

$result = [];
foreach (array_keys($patterns) as $code) {
    $result[] = [
        'id'   => $code,
        'text' => sprintf('v%d.%d.%d%s', $code * 2 % 5 + $code % 2, 7 - 2 * $code % 5, 13 + $code, $code === 3 ? '' : '-beta'),
    ];
}

然后只剩下处理数据库结果并替换结果具有代码的那些段:

foreach ($result as $row) {
    foreach ($patterns[$row['id']] as $index) {
        $segments[$index] = $row['text'];
    }
}

然后做输出:

echo implode("", $segments);

然后就是这样。此示例的输出:

The old version is v3.5.14-beta, The new version is v4.3.15-beta, The stable version is v2.6.16

完整示例:

<?php
/**
 * Simultaneous Preg_replace operation in php and regex
 *
 * @link 
 */

$input = <<<BUFFER
The old version is {code#1}, The new version is {code#2}, The stable version is {code#3}
BUFFER;

$regex = '/{code#(\d+)}/';

const TOKEN_TEXT = 1;
const TOKEN_CODE = 2;

// convert the input into a stream of tokens - normal text or fields for replacement

$tokens   = [];
$segments = [];

$buffer = $input;

while (preg_match($regex, $buffer, $matches, PREG_OFFSET_CAPTURE, 0)) {
    if ($matches[0][1]) {
        $tokens[]   = TOKEN_TEXT;
        $segments[] = substr($buffer, 0, $matches[0][1]);
    }

    $tokens[]   = TOKEN_CODE;
    $segments[] = $matches[0][0];

    $buffer = substr($buffer, $matches[0][1] + strlen($matches[0][0]));
}

if (strlen($buffer)) {
    $tokens[]   = TOKEN_TEXT;
    $segments[] = $buffer;
    $buffer = "";
}

// index which tokens represent which codes

$patterns = [];

foreach ($tokens as $index => $token) {
    if ($token !== TOKEN_CODE) {
        continue;
    }
    preg_match($regex, $segments[$index], $matches);
    $code              = (int)$matches[1];
    $patterns[$code][] = $index;
}

// lookup all codes in a database at once (simulated)
// SELECT id, text FROM replacements_table WHERE id IN (array_keys($patterns))
$result = [];
foreach (array_keys($patterns) as $code) {
    $result[] = [
        'id'   => $code,
        'text' => sprintf('v%d.%d.%d%s', $code * 2 % 5 + $code % 2, 7 - 2 * $code % 5, 13 + $code, $code === 3 ? '' : '-beta'),
    ];
}

// process the database result

foreach ($result as $row) {
    foreach ($patterns[$row['id']] as $index) {
        $segments[$index] = $row['text'];
    }
}

// output the replacement result

echo implode("", $segments);