Avatar ·

Как заменить только первое вхождение подстроки в PHP? (str_replace для первого совпадения)

Всем привет! Я пишу на PHP и столкнулся с неожиданной проблемой. У меня есть строка, в которой нужно заменить только первое вхождение определённой подстроки, а все остальные оставить нетронутыми. Вот пример. Допустим, у меня есть HTML-код или просто текст: ```php $text = "Яблоко, банан, яблоко, апельсин, яблоко"; $search = "яблоко"; $replace = "груша"; ``` Я хочу, чтобы результат был: "Яблоко, банан, груша, апельсин, яблоко" — то есть заменено только второе вхождение (первое с учётом регистра, но это не важно). Или, если я хочу заменить именно первое вхождение, чтобы получилось "груша, банан, яблоко, апельсин, яблоко". Проблема в том, что стандартная функция `str_replace()` заменяет ВСЕ вхождения. Я пробовал `str_replace($search, $replace, $text)`, но она заменяет каждое "яблоко" на "груша", а мне нужно только одно. Я знаю, что есть `preg_replace()`, но может быть есть более простое решение без регулярных выражений? Или как правильно использовать `preg_replace()` с ограничением? Подскажите, как это сделать правильно и просто. Заранее спасибо! <<>>

Решение: функция str_replace_once()

Действительно, стандартная str_replace() в PHP заменяет все вхождения подстроки. Для замены только первого совпадения можно написать простую пользовательскую функцию. Вот самый популярный и эффективный способ:

function str_replace_once($search, $replace, $subject) {
    $pos = strpos($subject, $search);
    if ($pos !== false) {
        return substr_replace($subject, $replace, $pos, strlen($search));
    }
    return $subject;
}

// Пример использования:
$text = "Яблоко, банан, яблоко, апельсин, яблоко";
$result = str_replace_once("яблоко", "груша", $text);
echo $result; // Выведет: "груша, банан, яблоко, апельсин, яблоко"

Как это работает

  • strpos() — находит позицию первого вхождения искомой строки. Если строка не найдена, возвращает false.
  • substr_replace() — заменяет часть строки, начиная с найденной позиции, на новую строку. Длина заменяемого фрагмента равна длине искомой строки (strlen($search)).
  • Если вхождение не найдено, возвращается исходная строка без изменений.

Вариант с учётом регистра (регистронезависимый)

Если нужно игнорировать регистр (например, заменить "Яблоко" или "яблоко"), используйте stripos() вместо strpos():

function str_ireplace_once($search, $replace, $subject) {
    $pos = stripos($subject, $search);
    if ($pos !== false) {
        return substr_replace($subject, $replace, $pos, strlen($search));
    }
    return $subject;
}

$text = "Яблоко, банан, яблоко";
echo str_ireplace_once("яблоко", "груша", $text); // "груша, банан, яблоко"

Альтернатива с explode/implode

Можно также использовать разбиение строки на массив:

function str_replace_once_explode($search, $replace, $subject) {
    $parts = explode($search, $subject, 2); // Ограничиваем до 2 частей
    if (count($parts) == 2) {
        return $parts[0] . $replace . $parts[1];
    }
    return $subject;
}

Этот метод менее производительный для больших строк, но тоже рабочий.

Решение через preg_replace (с ограничением)

Если вы всё же хотите использовать регулярные выражения, то четвёртый параметр preg_replace() задаёт лимит замен:

$text = "Яблоко, банан, яблоко";
$result = preg_replace('/яблоко/', 'груша', $text, 1); // Лимит = 1
echo $result; // "груша, банан, яблоко"

Этот способ короче, но требует экранирования спецсимволов в шаблоне, если строка поиска содержит метасимволы регулярных выражений (например, точки, скобки).

Вывод

Рекомендую использовать первый вариант с strpos() и substr_replace() — он самый быстрый и безопасный. Для большинства задач этого достаточно. Если нужна регистронезависимость — замените strpos на stripos.

<<>>

Дополнение: замена N-го вхождения

Если нужно заменить не первое, а, например, второе или третье вхождение, можно модифицировать функцию. Вот пример для замены по порядковому номеру:

function str_replace_nth($search, $replace, $subject, $n = 1) {
    $pos = 0;
    for ($i = 1; $i <= $n; $i++) {
        $pos = strpos($subject, $search, $pos);
        if ($pos === false) {
            return $subject; // Меньше вхождений, чем нужно
        }
        if ($i == $n) {
            return substr_replace($subject, $replace, $pos, strlen($search));
        }
        $pos += strlen($search);
    }
    return $subject;
}

// Пример: заменить второе вхождение
$text = "раз, два, раз, три, раз";
echo str_replace_nth("раз", "один", $text, 2); 
// Выведет: "раз, два, один, три, раз"

Важное замечание

При работе с многобайтовыми кодировками (например, UTF-8) используйте mb_strpos() и mb_substr() вместо strpos() и substr_replace(), чтобы избежать проблем с кириллицей и спецсимволами. Пример для UTF-8:

function mb_str_replace_once($search, $replace, $subject) {
    $pos = mb_strpos($subject, $search);
    if ($pos !== false) {
        return mb_substr($subject, 0, $pos) . $replace . mb_substr($subject, $pos + mb_strlen($search));
    }
    return $subject;
}

Этот подход гарантирует корректную работу с любыми языками и символами.