Php preg_replace назначает разные шаблоны замены для каждой группы захвата

Я пытаюсь выполнить полнотекстовый поиск mysql в логическом режиме, и мне нужно подготовить текст для поиска, прежде чем я создам запрос mysql.

Я решил, что для этого можно использовать PHP-функцию preg_replace и заменить каждую группу захвата одним конкретным шаблоном.

  1. Первый шаблон должен находить слова или предложения в кавычках ("hello world") и добавлять перед ним + (+"hello world").
  2. Второй шаблон должен найти остальные слова (без кавычек) и добавить + перед и * после (+how*).

шаблон регулярного выражения

["']+([^"']+)["']+|([^\s"']+)

образец замещения

+"\1" +\2*

ПРИМЕР

Для следующего ввода:

"hello world" how are you?

Он должен вернуть:

+"hello world" +how* +are* +you?*

Но вместо этого он возвращает Что-то не так':

+"hello world" +* +"" +how* +"" +are* +"" +you?*

Я знаю, что шаблон замены +"\1" +\2* никогда не будет работать, поскольку я нигде не говорю, что +"..." должен применяться только к первой группе захвата, а +...* - ко второй.

Проверить онлайн-регулярное выражение.

Код PHP

$query = preg_replace('~["\']+([^"\']+)["\']+|([^\s"\']+)~', '+"\1" +\2*', $query);

Есть ли способ добиться этого в PHP? Заранее спасибо.


РЕДАКТИРОВАТЬ / РЕШЕНИЕ

Благодаря предложению @revo использовать функцию PHP preg_replace_callback, мне удалось назначить шаблон замены для каждого шаблона поиска с помощью расширенной функции preg_replace_callback_array. Обратите внимание, что для этой функции требуется PHP> = 7.

Здесь я публикую окончательную версию функции, которую я использую для выполнения поиска по FULLTEXT через MATCH (...) AGAINST (...) IN BOOLEAN MODE. Функция объявлена ​​в class dbReader в подключаемом модуле Wordpress. Может может быть кому-то пригодится.

// Return maximum 100 ids of products matching $query in
// name or description searching for each word using MATCH AGAINST in BOOLEAN MODE
public function search_products($query) {

    function replace_callback($m, $f) {
        return sprintf($f, isset($m[1]) ? $m[1] : $m[0]);
    }

    // Replace simple quotes by double quotes in strings between quotes:
    // iPhone '8 GB' => iPhone "8 GB"
    // Apple's iPhone 8 '32 GB' => Apple's iPhone 8 "32 GB"
    // This is necessary later when the matches are devided in two groups:
    //      1. Strings not between double quotes
    //      2. Strings between double quotes
    $query = preg_replace("~(\s*)'+([^']+)'+(\s*)~", '$1"$2"$3', $query);

    // Do some magic to take numbers with their units as one word
    // iPhone 8 64 GB => iPhone 8 "64 GB"
    $pattern = array(
        '(\b[.,0-9]+)\s*(gb\b)',
        '(\b[.,0-9]+)\s*(mb\b)',
        '(\b[.,0-9]+)\s*(mm\b)',
        '(\b[.,0-9]+)\s*(mhz\b)',
        '(\b[.,0-9]+)\s*(ghz\b)'
    );
    array_walk($pattern, function(&$value) {
        // Surround with double quotes only if the user isn't doing manual grouping
        $value = '~'.$value.'(?=(?:[^"]*"[^"]*")*[^"]*\Z)~i';
    });
    $query = preg_replace($pattern, '"$1 $2"', $query);

    // Prepare query string for a "match against" in "boolean mode"
    $patterns = array(
        // 1. All strings not sorrounded by double quotes
        '~([^\s"]+)(?=(?:[^"]*"[^"]*")*[^"]*\Z)~'   => function($m){
            return replace_callback($m, '+%s*');
        },

        // 2. All strings between double quotes
        '~"+([^"]+)"+~'                             => function($m){
            return replace_callback($m, '+"%s"');
        }
    );

    // Replace every single word by a boolean expression: +some* +word*
    // Respect quoted strings: +"iPhone 8"
    // preg_replace_callback_array needs PHP Version >= 7
    $query = preg_replace_callback_array($patterns, $query);

    $fulltext_fields = array(
        'title'         => array(
            'importance'    => 1.5,
            'table'         => 'p',
            'fields'        => array(
                'field1',
                'field2',
                'field3',
                'field4'
            )
        ),
        'description'   => array(
            'importance'    => 1,
            'table'         => 'p',
            'fields'        => array(
                'field5',
                'field6',
                'field7',
                'field8'
            )
        )
    );
    $select_match = $match_full = $priority_order = "";

    $args = array();
    foreach ($fulltext_fields as $index => $obj) {
        $match          = $obj['table'].".".implode(", ".$obj['table'].".", $obj['fields']);
        $select_match  .= ", MATCH ($match) AGAINST (%s IN BOOLEAN MODE) AS {$index}_score";
        $match_full    .= ($match_full! = ""?", ":"").$match;
        $priority_order.= ($priority_order= = ""?"ORDER BY ":" + ")."({$index}_score * {$obj['importance']})";
        array_push($args, $query);
    }
    $priority_order .= $priority_order! = ""?" DESC":"";

    // User input $query is passed as %s parameter to db->prepare() in order to avoid SQL injection
    array_push($args, $query, $this->model_name, $this->view_name);

    return $this->db->get_col(
        $this->db->prepare(
            "SELECT p.__pk $select_match
            FROM ankauf_... AND
                    MATCH ($match_full) AGAINST (%s IN BOOLEAN MODE)
                INNER JOIN ...
            WHERE
                m.bezeichnung=%s AND
                a.bezeichnung=%s
                $priority_order
            LIMIT 100
            ;",
            $args
        )
    );
}

Вам нужен preg_replace_callback.

revo 17.07.2018 18:35

Теперь я хочу убедиться, что этот запрос не остается открытым для MySQL-инъекции. Как вы думаете, достаточно db->prepare("... AGAINST (%s IN BOOLEAN MODE) ..."), чтобы этого избежать? Заранее благодарю за совет.

Gerard Fígols 24.07.2018 11:42

Правильно используя подготовленные операторы, да, это было бы безопасно.

revo 24.07.2018 19:02

Спасибо. Я буду считать код завершенным.

Gerard Fígols 25.07.2018 10:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
2
4
802
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы должны использовать preg_replace_callback:

$str = '"hello world" how are you?';

echo preg_replace_callback('~("[^"]+")|\S+~', function($m) {
    return isset($m[1]) ? "+" . $m[1] : "+" . $m[0] . "*";
}, $str);

Выход:

+"hello world" +how* +are* +you?*

Живая демонстрация

Я принял ваш ответ, но не смог отметить его как «полезный» (голосование «за»), потому что мне нужно как минимум 15 очков репутации.

Gerard Fígols 24.07.2018 11:44

Другие вопросы по теме