Как заменить только первое вхождение подстроки в PHP? (str_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;
}
Этот подход гарантирует корректную работу с любыми языками и символами.