将文本中的 @mentions 替换为可点击的文本,包括从数据库表中获取的相关 ID

问题描述

我希望能够点击每个 @.... 并转到他们的特定页面,以便我从数据库获取文本中每个匹配项的 ID,以便链接到匹配项的页面。我确实从 foreach 循环中获得了一组 id,但是当我在 foreach 循环中使用 preg_replace 时,我为多个值插入了相同的 id。我被卡住了,并尝试了许多不同的变化,但还没有运气。

$text = "I went to the dog park yesterday and saw @dog4 playing with @dog8 and @dog3 drinking water.";

public function getLinks($text) {

    preg_match_all("/@([\w]+)/",$text,$matches);

if ($matches) {
    $result = array_values($matches[1]);
}

$sql = "SELECT dogId FROM dogs WHERE dogName = :dogName";

foreach ($result as $dogName) {
    $stmt = $this->con->prepare($sql); 
    $stmt->execute(array(":dogName" => $dogName));
    $data = $stmt->fetch(PDO::FETCH_ASSOC);
    
    $i = 0;
    if (!empty($data)) {
        
        foreach ($data as $dogId) {
            $i++;
            // It's working here.  I'm getting an array of dogIds
            echo '<pre>'; print_r($data); echo '</pre>';
            Array
            (
                [dogId] => 4
            )
            Array
            (
                [dogId] => 8
            )
            Array
            (
                [dogId] => 3
            )
            if ($i == count($data)) {
                $pattern = "/@([\w]+)/";
                $dogPage = "<span onclick='openPage(\"dogs.PHP?id=$dogId\")' role='link' tabindex='0'>$0</span>";
                $dogLink = preg_replace($pattern,$dogPage,$text);

                // It's not working here.  I only get the last array value(3) inserted in $dogId for every match.
                echo '<pre>'; print_r($dogPage); echo '</pre>';
                "<span onclick='openPage(\"dogs.PHP?id=3\")' role='link' tabindex='0'>@dog4</span>"
                "<span onclick='openPage(\"dogs.PHP?id=3\")' role='link' tabindex='0'>@dog8</span>"
                "<span onclick='openPage(\"dogs.PHP?id=3\")' role='link' tabindex='0'>@dog3</span>"
            }           
        }                   
    } 
} return $dogLink
}

我得到的结果文本是

I went to the dog park yesterday and saw @dog4(id=3) playing with @dog8(id=3) and @dog3(id=3) drinking water.

但我希望实现的是

I went to the dog park yesterday and saw @dog4(id=4) playing with @dog8(id=8) and @dog3(id=3) drinking water.

先谢谢你!

解决方法

首先扫描所有@mentions 的输入文本,并构建一个没有前导“@”的值的平面数组(\K 表示忘记任何先前匹配的字符——在本例中为 @)。从 dogs 表中提取生成列表中的所有行,并将它们转换为以 dogName 作为键和 dogId 作为值的查找数组。

$count = preg_match_all('/@\K\w+/',$text,$mentions);
if ($count) {
    $in  = str_repeat('?,',$count - 1) . '?';
    $stmt = $db->prepare("SELECT dogName,dogId FROM dogs WHERE dogName IN ($in)");
    $stmt->execute($mentions[0]);
    $lookup = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
}

生成$mentions的演示:https://3v4l.org/O9v37
PDO 引用:https://stackoverflow.com/a/14767651/2943403 & https://phpdelusions.net/pdo/fetch_modes#FETCH_KEY_PAIR

然后您可以使用 /@(\w+)/ 作为 preg_replace_callback() 的第一个参数再次读取 @mentions 的输入文本。在回调的自定义函数范围内,使用 isset() 快速检查匹配的提及项是否作为查找中的键存在——如果是,则替换,如果不是,则不要更改文本。

$text = "I went to the dog park yesterday and saw @dog4 playing with @dog8 and @dog3 drinking water -- poor @dog33.";

$lookup = [
    'dog4' => 4,'dog8' => 8,'dog3' => 3,];

echo preg_replace_callback(
         '/@(\w+)/',function ($m) use($lookup) {
             return isset($lookup[$m[1]])
                        ? "<span onclick='openPage(\"dogs.php?id={$lookup[$m[1]]}\")' role='link' tabindex='0'>{$m[0]}</span>"
                        : $m[0];
         },$text
     );

演示:https://3v4l.org/7Z2Wp

输出:

I went to the dog park yesterday and saw <span onclick='openPage("dogs.php?id=4")' role='link' tabindex='0'>@dog4</span> playing with <span onclick='openPage("dogs.php?id=8")' role='link' tabindex='0'>@dog8</span> and <span onclick='openPage("dogs.php?id=3")' role='link' tabindex='0'>@dog3</span> drinking water -- poor @dog33.