I want a version of str_replace()
that only replaces the first occurrence of $search
in the $subject
. Is there an easy solution to this, or do I need a hacky solution?

- 25,759
- 11
- 71
- 103

- 119,074
- 188
- 476
- 699
-
You might find [`s($subject)->replaceFirst($search)`](https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src/Str.php#L281) and [`s($subject)->replaceFirstIgnoreCase($search)`](https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src/Str.php#L294) helpful, as found in [this standalone library](https://github.com/delight-im/PHP-Str). – caw Jul 26 '16 at 23:49
-
You might wonder what is the `$count` param for, well it's for **OUTPUT** - how many replacements were **performed** - NOT a limit, so this is a completely valid question. This can be confusing with other languages, because e.g. Python's `str.replace` DOES use count param as a limit param. – jave.web Dec 04 '20 at 23:29
-
Great question, upvoted! Just a thought, maybe reconsider an accepted answer? Cheers! – HoldOffHunger Nov 07 '21 at 21:34
23 Answers
There's no version of it, but the solution isn't hacky at all.
$pos = strpos($haystack, $needle);
if ($pos !== false) {
$newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
}
Pretty easy, and saves the performance penalty of regular expressions.
Bonus: If you want to replace last occurrence, just use strrpos
in place of strpos
.

- 21,377
- 10
- 81
- 108

- 92,731
- 24
- 156
- 164
-
27Can be much faster and will use less memory than regular expressions. No idea why someone would vote that down... – Josh Davis Aug 10 '09 at 02:54
-
14I like this approach, but the code has an error, the last parameter of substr_replace call should be strlen($needle) instead of strlen($replace).. please beware about that!! – Nelson Sep 21 '10 at 11:47
-
2It is "hacky" in the sense that it takes considerably more time to figure out what's going on. Also if it was clear code, it wouldn't have been mentioned that the code has an error. If it's possible to make a mistake in such a small snippet, it's way too hacky already. – Camilo Martin Nov 12 '13 at 18:01
-
12I disagree with @CamiloMartin with regards to the number of lines vs. the possibility of mistakes. While `substr_replace` is a somewhat unwieldy function to use owing to all the parameters, the real issue is that doing string manipulation by numbers is just _tricky_ sometimes - you have to be careful to pass the right variable/offset to functions. I'd actually go so far as to say that the above code is the most straightforward, and to me, logical, approach. – Alex Apr 22 '14 at 15:21
-
1Brilliant approach. Works perfectly when replacing variable values that have reserved regex chars in them (so preg_replace is bear). This is straightforward and elegant. – Praesagus Nov 07 '14 at 02:55
-
Would be better with an example !! have no clue with that needle and haystack . – Scooter Daraf May 14 '16 at 17:59
-
Yes it could be faster. But I have doubts about memory use. If the haystack size is 1MB. How much consumed that solution? 2MB for the copy of the variable? It would need more memory than using regular expressions? The memory used on process, or caused by some cases of data manipulation can be an important factor to decide the solution. Not just for an ideal / generic scenario. – MikeBau Aug 30 '16 at 09:30
-
@martin - your edit was wrong and broke this answer. be careful and don't change code unless you test it first. rolled back – But those new buttons though.. Dec 04 '16 at 05:14
-
-
Brilliant. Excellent work, @zombat. Fantastic that your non-regex answer is still helping - over 12 years after you wrote it. – Rounin Nov 11 '21 at 22:33
-
Can be done with preg_replace:
function str_replace_first($search, $replace, $subject)
{
$search = '/'.preg_quote($search, '/').'/';
return preg_replace($search, $replace, $subject, 1);
}
echo str_replace_first('abc', '123', 'abcdef abcdef abcdef');
// outputs '123def abcdef abcdef'
The magic is in the optional fourth parameter [Limit]. From the documentation:
[Limit] - The maximum possible replacements for each pattern in each subject string. Defaults to -1 (no limit).
Though, see zombat's answer for a more efficient method (roughly, 3-4x faster).
-
44The downside to this method is the performance penalty of regular expressions. – zombat Aug 10 '09 at 02:42
-
29Another downside is you have to use preg_quote() on the "needle" and escape meta-characters $ and \ in the replacement. – Josh Davis Aug 10 '09 at 02:53
-
35This fails as a generic solution due to nasty escaping issues. – Jeremy Kauffman Jul 09 '11 at 00:39
-
@zombar I agree. If you don't need regex use str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] ) and make use of &$count – Chris Pillen Oct 22 '14 at 08:04
-
I don't think the performance or escaping issues are a barrier to this solution. It's the most elegant and easy to access for future developers that may inherit code. – AshBrad Nov 07 '14 at 01:14
-
@ChrisPillen How would you use $count to limit the replacement? AFAIK it would just allow you to see that it replaced too many after the replacement is done. – Byson Dec 29 '14 at 10:17
-
3Far too often regular expressions are dismissed due to 'performance', if performance were the primary concern, we would not be writing PHP! Something other than '/' could be used to wrap the pattern, perhaps '~', which would help avoid the escaping problem to some degree. It depends what the data is, and where it came from. – ThomasRedstone Dec 11 '15 at 23:26
-
The escaping of characters is the main issue here. Regular expressions are generally ignored or due to performance or the escaping problems around regular expressions. – Alex Jan 30 '16 at 10:19
-
This did it for me when other stackoverflow accepted answers did not. Functioning code. – Michael d Nov 30 '16 at 23:13
-
1Performance downsides aside - do those who complain about escaping issues have anything specific in mind, besides potential bugs in `preg_quote`? For example, @ThomasRedstone worries that the delimiter `/` could be dangerous if it appears in `$from`, but fortunately it isn't: it is properly escaped because of `preg_quote`'s second parameter (one can easily test that). I'd be interested to hear about specific issues (which would be serious PCRE security bugs in my book). – MvanGeest Jan 31 '17 at 02:30
-
@MvanGeest - while I can't be sure exactly what I was thinking over a year ago, using a different delimiter is more a matter of convenience than anything else, as I'm often working with URLs when I write regular expressions, I'm certainly not aware of any issues that can't be easily mitigated when using regular expressions. – ThomasRedstone Jan 31 '17 at 09:50
-
@zombat, have you actually profiled? According to my tests, regular expressions aren't always slower. – datasn.io Oct 22 '17 at 06:00
-
1The other downside is the potential error: "Compilation failed: regular expression is too large at offset 42822" – Nicolas BADIA Mar 20 '19 at 15:01
-
Please edit answer to fix escaping issues. I used this as a utility function in my program, and the RegEx ate up all my backslashes and dollar signs, breaking my code. – RedDragonWebDesign Dec 11 '21 at 06:08
Edit: both answers have been updated and are now correct. I'll leave the answer since the function timings are still useful.
The answers by 'zombat' and 'too much php' are unfortunately not correct. This is a revision to the answer zombat posted (as I don't have enough reputation to post a comment):
$pos = strpos($haystack,$needle);
if ($pos !== false) {
$newstring = substr_replace($haystack,$replace,$pos,strlen($needle));
}
Note the strlen($needle), instead of strlen($replace). Zombat's example will only work correctly if needle and replace are the same length.
Here's the same functionality in a function with the same signature as PHP's own str_replace:
function str_replace_first($search, $replace, $subject) {
$pos = strpos($subject, $search);
if ($pos !== false) {
return substr_replace($subject, $replace, $pos, strlen($search));
}
return $subject;
}
This is the revised answer of 'too much php':
implode($replace, explode($search, $subject, 2));
Note the 2 at the end instead of 1. Or in function format:
function str_replace_first($search, $replace, $subject) {
return implode($replace, explode($search, $subject, 2));
}
I timed the two functions and the first one is twice as fast when no match is found. They are the same speed when a match is found.

- 15,901
- 8
- 56
- 54

- 3,016
- 3
- 22
- 18
-
Why not genericize this like: str_replace_flexible(mixed $s, mixed $r, int $offset, int $limit) where the function replaces $limit occurrences starting at the $offset (nth) match. – Adam Friedman May 23 '14 at 19:09
-
-
4@Andrew [`stripos()`](http://php.net/manual/en/function.stripos.php) to the rescue :-) – Gras Double Feb 13 '16 at 19:23
I wondered which one was the fastest, so I tested them all.
Below you will find:
- A comprehensive list of all the functions that have been contributed onto this page
- Benchmark testing for each contrubution (average execution time over 10,000 runs)
- Links to each answer (for the full code)
All functions were tested with the same settings:
$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';
Functions that only replace the first occurrence of a string within a string:
substr_replace($string, $replace, 0, strlen($search));
[CONTRIBUTED BY] => zombat [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000062883 [SLOWER BY] => FASTEST
replace_first($search, $replace, $string);
[CONTRIBUTED BY] => too much php [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000073902 [SLOWER BY] => 17.52%
preg_replace($search, $replace, $string, 1);
[CONTRIBUTED BY] => karim79 [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000077519 [SLOWER BY] => 23.27%
str_replace_once($search, $replace, $string);
[CONTRIBUTED BY] => happyhardik [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000082286 [SLOWER BY] => 30.86%
str_replace_limit($search, $replace, $string, $count, 1);
[CONTRIBUTED BY] => bfrohs - expanded renocor [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000083342 [SLOWER BY] => 32.54%
str_replace_limit($search, $replace, $string, 1);
[CONTRIBUTED BY] => renocor [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000093116 [SLOWER BY] => 48.08%
str_replace_limit($string, $search, $replace, 1, 0);
[CONTRIBUTED BY] => jayoaK [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000093862 [SLOWER BY] => 49.26%
Functions that only replace the last occurrence of a string within a string:
substr_replace($string, $replace, strrpos($string, $search), strlen($search));
[CONTRIBUTED BY] => oLinkSoftware - modified zombat [OOO.OOO.OOO.S] => OOO.OOO.B.S [AVERAGE TIME] => 0.0000068083 [SLOWER BY] => FASTEST
strrev(implode(strrev($replace), explode(strrev($search), strrev($string), 2)));
[CONTRIBUTED BY] => oLinkSoftware [OOO.OOO.OOO.S] => OOO.OOO.B.S [AVERAGE TIME] => 0.0000084460 [SLOWER BY] => 24.05%

- 1
- 1

- 1,841
- 15
- 8
-
Thanks for this, I generally use preg_replace as it is the most flexible if future tweak are required in most cases 27% slower isn't going to be significant – zzapper Jan 28 '16 at 14:32
-
@oLinkWebDevelopment I'd be interested in seeing your benchmark script. I think it could prove to be useful. – Dave Morton Apr 07 '17 at 16:28
-
The reason why `substr_replace()` wins the result is simple; because it's an internal function. Two doing-the-same-thing internal and user-defined functions differ in performance, because the internal one runs in lower layers. So, why not `preg_match()`? Regular expressions are almost slower than every internal string manipulation function, because of their nation of searching in a string multiple times. – MAChitgarha Sep 21 '18 at 06:13
-
1I hope that the benchmark on your "winner" (`substr_replace($string, $replace, 0, strlen($search));`) did not merely write that static `0`. Part of the convolution of non-regex solutions is that they need to "find" the starting point before knowing where to replace. – mickmackusa Feb 20 '19 at 03:43
Unfortunately, I don't know of any PHP function which can do this.
You can roll your own fairly easily like this:
function replace_first($find, $replace, $subject) {
// stolen from the comments at PHP.net/str_replace
// Splits $subject into an array of 2 items by $find,
// and then joins the array with $replace
return implode($replace, explode($find, $subject, 2));
}

- 6,736
- 10
- 34
- 45

- 88,666
- 34
- 128
- 138
-
I think this is the [golfiest version](https://codegolf.stackexchange.com/a/148744/55735) of them all - using `join` instead of `implode`. – Titus Nov 21 '17 at 08:57
-
`return implode($replace, explode($find, $subject, $limit+1));` for custom replace numbers – beppe9000 Dec 07 '19 at 23:08
I created this little function that replaces string on string (case-sensitive) with limit, without the need of Regexp. It works fine.
function str_replace_limit($search, $replace, $string, $limit = 1) {
$pos = strpos($string, $search);
if ($pos === false) {
return $string;
}
$searchLen = strlen($search);
for ($i = 0; $i < $limit; $i++) {
$string = substr_replace($string, $replace, $pos, $searchLen);
$pos = strpos($string, $search);
if ($pos === false) {
break;
}
}
return $string;
}
Example usage:
$search = 'foo';
$replace = 'bar';
$string = 'foo wizard makes foo brew for evil foo and jack';
$limit = 2;
$replaced = str_replace_limit($search, $replace, $string, $limit);
echo $replaced;
// bar wizard makes bar brew for evil foo and jack

- 6,222
- 4
- 34
- 36
-
Though I would rather do `===false` instead of `is_bool(` to be more explicit - I'm giving this thumbs up **just because it has avoided the RegExp madness** ! ...and at the same time it is working and *clean* solution... – jave.web Mar 14 '16 at 15:03
-
Preferring an easily customizable `preg_` solution is not _madness_ but a personal preference. `return preg_replace('/'.preg_quote($search, '/').'/', $replace, $content, 1);` is pretty simple to read for people who do not fear regex. Need case-insensitive searching? Add `i` after the end pattern delimiter. Need unicode/multibyte support? Add `u` after the end pattern delimiter. Need word boundary support? Add `\b` on both sides of your search string. If you don't want regex, don't use regex. Horses for courses, but certainly not madness. – mickmackusa Feb 20 '19 at 03:52
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);
Using substr_replace we can replace the occurrence of first character only in string. as & is repeated multiple times but only at first position we have to replace & with ?

- 61
- 1
- 1
=> CODE WAS REVISED, so consider some comments too old
And thanks everyone on helping me to improve that
Any BUG, please communicate me; I'll fix that up right after
So, lets go for:
Replacing the first 'o' to 'ea' for example:
$s='I love you';
$s=str_replace_first('o','ea',$s);
echo $s;
//output: I leave you
The function:
function str_replace_first($this,$that,$s)
{
$w=strpos($s,$this);
if($w===false)return $s;
return substr($s,0,$w).$that.substr($s,$w+strlen($this));
}
-
-
I think that should be `substr($where,$b+strlen($this))`, not `substr($where,$b+1)`. And I guess that [`substr_replace`](https://stackoverflow.com/a/1252710/6442316) is faster. – Titus Nov 21 '17 at 09:00
-
-
This solution doesn't work as coded. Proof: https://3v4l.org/cMeZj And when you fix the variable naming issue, it doesn't work when the search value is not found -- it damages the input string. Proof: https://3v4l.org/XHtfc – mickmackusa Feb 20 '19 at 04:01
-
Is it fair for someone asks to FIX the code? @mickmackusa Can you check that again please? – PYK Feb 20 '19 at 15:04
-
`substr_count()` is not the most efficient way to check the existence of a substring. ...And calling `strpos($where,$this)` more than once is not best practice. – mickmackusa Feb 20 '19 at 21:16
-
@mickmackusa Can you check the code again and give us your feedback please? ;-) thanks bro – PYK Feb 24 '19 at 21:53
-
If you are seeking my review... 1. `substr()` is an inferior call because it must be called twice, versus `substr_replace()` only needs to be called once to perform the same action. 2. Your poorly named variables make your code less readable/intelligible. 3. You are not exercising proper spacing and tabbing in accordance with php coding standards. 4. I recommend always including curly braces with condition blocks to avoid misinterpretation by future readers. In the end, superior answers have already been provided, so this answer is of no real benefit to researchers. My review. – mickmackusa Feb 25 '19 at 03:09
-
That's OK ... I understand you. And thanks for your nice teaching and spending your time with me ;-) – PYK Feb 25 '19 at 12:12
-
Thanks for your help, but doesn't work for the first letter because `strpos` will return `0` which will be evaluated as `false` in the `if` statement. You should use [identical comparison](https://www.php.net/manual/en/language.operators.comparison.php) like `if ($w === false)`. – Wesley Gonçalves Apr 17 '19 at 17:54
-
-
You forgot to add another `=` symbol in the `if` statement so that the value type is also compared - [identical comparison](https://www.php.net/manual/en/language.operators.comparison.php) and your function will also work for the first letter. `if ($w === false)` – Wesley Gonçalves May 27 '19 at 15:21
-
The easiest way would be to use regular expression.
The other way is to find the position of the string with strpos() and then an substr_replace()
But i would really go for the RegExp.

- 29,200
- 6
- 68
- 84
-
1This "hint" is rather vague / low-value compared to other posts on this page. – mickmackusa Feb 20 '19 at 03:53
function str_replace_once($search, $replace, $subject) {
$pos = strpos($subject, $search);
if ($pos === false) {
return $subject;
}
return substr($subject, 0, $pos) . $replace . substr($subject, $pos + strlen($search));
}

- 6,792
- 6
- 60
- 75
-
Code-only answers are low-value on StackOverflow because they do a poor job of educating/empowering thousands of future researchers. – mickmackusa Feb 20 '19 at 03:54
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;

- 315
- 3
- 2
-
This is just the same as the first answer. Besides, you should do a `preg_quote` of `$find` before using it as an expression. – Emil Vikström Jun 21 '12 at 22:37
-
this is what I used, so I up-voted it. The first answer caused a conflict with Drupal, it must have overwritten a drupal helper function. So I just took the code that was inside of the function and used it in-line with the rest of the code... – Dan Mantyla Sep 19 '17 at 21:23
-
This code-only answer provides redundant advice on the page (not to mention it is lacking `preg_quote()`. This late duplicate answer can be safely purged from the page because its advice is provided by the earlier, and higher upvoted accepted answer. – mickmackusa Feb 20 '19 at 03:56
To expand on @renocor's answer, I've written a function that is 100% backward-compatible with str_replace()
. That is, you can replace all occurrences of str_replace()
with str_replace_limit()
without messing anything up, even those using arrays for the $search
, $replace
, and/or $subject
.
The function could be completely self-contained, if you wanted to replace the function call with ($string===strval(intval(strval($string))))
, but I'd recommend against it since valid_integer()
is a rather useful function when dealing with integers provided as strings.
Note: Whenever possible, str_replace_limit()
will use str_replace()
instead, so all calls to str_replace()
can be replaced with str_replace_limit()
without worrying about a hit to performance.
Usage
<?php
$search = 'a';
$replace = 'b';
$subject = 'abcabc';
$limit = -1; // No limit
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;
2 replacements -- bbcbbc
$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;
1 replacements -- bbcabc
$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;
2 replacements -- bbcbbc
Function
<?php
/**
* Checks if $string is a valid integer. Integers provided as strings (e.g. '2' vs 2)
* are also supported.
* @param mixed $string
* @return bool Returns boolean TRUE if string is a valid integer, or FALSE if it is not
*/
function valid_integer($string){
// 1. Cast as string (in case integer is provided)
// 1. Convert the string to an integer and back to a string
// 2. Check if identical (note: 'identical', NOT just 'equal')
// Note: TRUE, FALSE, and NULL $string values all return FALSE
$string = strval($string);
return ($string===strval(intval($string)));
}
/**
* Replace $limit occurences of the search string with the replacement string
* @param mixed $search The value being searched for, otherwise known as the needle. An
* array may be used to designate multiple needles.
* @param mixed $replace The replacement value that replaces found search values. An
* array may be used to designate multiple replacements.
* @param mixed $subject The string or array being searched and replaced on, otherwise
* known as the haystack. If subject is an array, then the search and replace is
* performed with every entry of subject, and the return value is an array as well.
* @param string $count If passed, this will be set to the number of replacements
* performed.
* @param int $limit The maximum possible replacements for each pattern in each subject
* string. Defaults to -1 (no limit).
* @return string This function returns a string with the replaced values.
*/
function str_replace_limit(
$search,
$replace,
$subject,
&$count,
$limit = -1
){
// Set some defaults
$count = 0;
// Invalid $limit provided. Throw a warning.
if(!valid_integer($limit)){
$backtrace = debug_backtrace();
trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
'`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting an '.
'integer', E_USER_WARNING);
return $subject;
}
// Invalid $limit provided. Throw a warning.
if($limit<-1){
$backtrace = debug_backtrace();
trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
'`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
'a positive integer', E_USER_WARNING);
return $subject;
}
// No replacements necessary. Throw a notice as this was most likely not the intended
// use. And, if it was (e.g. part of a loop, setting $limit dynamically), it can be
// worked around by simply checking to see if $limit===0, and if it does, skip the
// function call (and set $count to 0, if applicable).
if($limit===0){
$backtrace = debug_backtrace();
trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
'`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
'a positive integer', E_USER_NOTICE);
return $subject;
}
// Use str_replace() whenever possible (for performance reasons)
if($limit===-1){
return str_replace($search, $replace, $subject, $count);
}
if(is_array($subject)){
// Loop through $subject values and call this function for each one.
foreach($subject as $key => $this_subject){
// Skip values that are arrays (to match str_replace()).
if(!is_array($this_subject)){
// Call this function again for
$this_function = __FUNCTION__;
$subject[$key] = $this_function(
$search,
$replace,
$this_subject,
$this_count,
$limit
);
// Adjust $count
$count += $this_count;
// Adjust $limit, if not -1
if($limit!=-1){
$limit -= $this_count;
}
// Reached $limit, return $subject
if($limit===0){
return $subject;
}
}
}
return $subject;
} elseif(is_array($search)){
// Only treat $replace as an array if $search is also an array (to match str_replace())
// Clear keys of $search (to match str_replace()).
$search = array_values($search);
// Clear keys of $replace, if applicable (to match str_replace()).
if(is_array($replace)){
$replace = array_values($replace);
}
// Loop through $search array.
foreach($search as $key => $this_search){
// Don't support multi-dimensional arrays (to match str_replace()).
$this_search = strval($this_search);
// If $replace is an array, use the value of $replace[$key] as the replacement. If
// $replace[$key] doesn't exist, just an empty string (to match str_replace()).
if(is_array($replace)){
if(array_key_exists($key, $replace)){
$this_replace = strval($replace[$key]);
} else {
$this_replace = '';
}
} else {
$this_replace = strval($replace);
}
// Call this function again for
$this_function = __FUNCTION__;
$subject = $this_function(
$this_search,
$this_replace,
$subject,
$this_count,
$limit
);
// Adjust $count
$count += $this_count;
// Adjust $limit, if not -1
if($limit!=-1){
$limit -= $this_count;
}
// Reached $limit, return $subject
if($limit===0){
return $subject;
}
}
return $subject;
} else {
$search = strval($search);
$replace = strval($replace);
// Get position of first $search
$pos = strpos($subject, $search);
// Return $subject if $search cannot be found
if($pos===false){
return $subject;
}
// Get length of $search, to make proper replacement later on
$search_len = strlen($search);
// Loop until $search can no longer be found, or $limit is reached
for($i=0;(($i<$limit)||($limit===-1));$i++){
// Replace
$subject = substr_replace($subject, $replace, $pos, $search_len);
// Increase $count
$count++;
// Get location of next $search
$pos = strpos($subject, $search);
// Break out of loop if $needle
if($pos===false){
break;
}
}
// Return new $subject
return $subject;
}
}
-
6kinda bloated if you ask me. Also what I the most 'hate' at this solution is the error handling. It breaks the script if you pass incorrect values. You think it is looking professional but it is not, instead of an error produce a notice or warning instead. Better is to skip the bullshit, return false instead or null and never use a backtrace in function like this. The best solution is that the programmer can decide what to do when the output is wrong/ unexpected. – Codebeat Jul 16 '13 at 20:40
-
@Erwinus This uses `E_USER_WARNING` throughout, which is a **warning**, *not* an **error**. The backtrace is extremely useful to find out what code is passing the invalid data to the function in the first place (which is absolutely necessary to track down bugs in production). As for returning `$subject` instead of `false`/`null` or throwing an error, that was simply a personal choice for my use case. To match `str_replace()`'s functionality, using catchable fatal errors would be the best bet (as `str_replace()` does when providing a closure for the first two arguments). – 0b10011 Jul 17 '13 at 03:03
-
Ah, didn't notice about the E_USER_WARNING your are using, sorry for that. The problem with returning the subject is that you can never see there was something wrong, outside the function. That said, the function can be half the size if you do it smarter (it is possible). Second, comments are fine when it explains something complex but not very useful for simple things like increase a value. Overall I think it is unnecessary huge. Also, using warnings in a production environment can be security issue when you use this code on a server that does not suppress run-time messages by default (logs). – Codebeat Jul 17 '13 at 03:47
-
@Erwinus I was verbose when it came to the comments because some people don't understand the language as well as others, and comments can always be removed by those that do understand it. If you know of a better way of getting the same end result for all edge cases, by all means, edit the answer. And if your production environment doesn't suppress error messages, you've got a bigger issue than this function ;) – 0b10011 Jul 17 '13 at 03:58
-
TL;DR This snippet is so bloated that I can't imagine choosing it over a regex function (I hate scrolling). If you want to count the replacements made, there is a parameter for that in `preg_replace()`. Furthermore, `preg_replace()`/regex offers word boundary handling (if desirable) -- something that non-regex functions will not provide elegantly. – mickmackusa Feb 20 '19 at 04:17
-
@mickmackusa The point of this function wasn't to be the smallest possible, but rather the most correct. [`str_replace()`](https://secure.php.net/manual/en/function.str-replace.php) accepts arrays for `$search`, `$replace`, and `$subject`, so this aims to replicate that in order to be a drop-in replacement (it wouldn't break any existing functionality while adding new). Since `str_replace()` does quite a few different things itself, this function also has to. But I agree, if you're not looking to replicate `str_replace()`, there are better alternatives. – 0b10011 Feb 26 '19 at 15:29
-
According to my test result, I'd like to vote the regular_express one provided by karim79. (I don't have enough reputation to vote it now!)
The solution from zombat uses too many function calls, I even simplify the codes. I'm using PHP 5.4 to run both solutions for 100,000 times, and here's the result:
$str = 'Hello abc, have a nice day abc! abc!';
$pos = strpos($str, 'abc');
$str = substr_replace($str, '123', $pos, 3);
==> 1.85 sec
$str = 'Hello abc, have a nice day abc! abc!';
$str = preg_replace('/abc/', '123', $str, 1);
==> 1.35 sec
As you can see. The performance of preg_replace is not so bad as many people think. So I'd suggest the classy solution if your regular express is not complicated.

- 29
- 2
-
Your first snippet is an unfair comparison because it fails to use a correct implementation. You are not checking `$pos` for `false`, so when the needle does not exist in the haystack, it will damage the output. – mickmackusa Feb 20 '19 at 06:04
-
Thanks @mickmackusa, you're right. But that's not the point. I said this code is simplified just to compare the efficiency of implementations. – Hunter Wu Feb 23 '19 at 03:46
-
That is exactly my point. You must never make benchmark comparisons that do not perform the exact same process. Comparing apples to half-oranges is not useful. Fully implementing the complete non-regex approach will make the speed difference more profound. – mickmackusa Feb 23 '19 at 04:01
-
Well, thanks again. But what I want is to find the better implementation, not to make more difference profound. – Hunter Wu Feb 27 '19 at 03:13
To expand on zombat's answer (which I believe to be the best answer), I created a recursive version of his function that takes in a $limit
parameter to specify how many occurrences you want to replace.
function str_replace_limit($haystack, $needle, $replace, $limit, $start_pos = 0) {
if ($limit <= 0) {
return $haystack;
} else {
$pos = strpos($haystack,$needle,$start_pos);
if ($pos !== false) {
$newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
return str_replace_limit($newstring, $needle, $replace, $limit-1, $pos+strlen($replace));
} else {
return $haystack;
}
}
}

- 2,612
- 7
- 25
- 35
-
Note, there is no quality check on `$start_pos`, so if it is beyond the string length, this function will generate: `Warning: strpos(): Offset not contained in string...`. This function fails to make a replacement when `$start_pos` is beyond length. Proof of Failure: 3v4l.org/qGuVIR ... Your function can combine the `return $haystack` conditions and avoid declaring single-use variables like this: https://3v4l.org/Kdmqp However, as I've said in comments elsewhere on this page, I would rather use a very clean, direct, non-recursive `preg_replace()` call. – mickmackusa Feb 20 '19 at 04:33
-
yes so that you can add this line `else` statment `$start_pos > strlen($haystack) ? $start_pos = strlen($haystack) : '';` – ManojKiran A Apr 01 '19 at 06:54
For a string
$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';
//replace ONLY FIRST occurance of "OOO" with "B"
$string = substr_replace($string,$replace,0,strlen($search));
//$string => B.OOO.OOO.S
//replace ONLY LAST occurance of "OOOO" with "B"
$string = substr_replace($string,$replace,strrpos($string,$search),strlen($search))
//$string => OOO.OOO.B.S
//replace ONLY LAST occurance of "OOOO" with "B"
$string = strrev(implode(strrev($replace),explode(strrev($search),strrev($string),2)))
//$string => OOO.OOO.B.S
For a single character
$string[strpos($string,$search)] = $replace;
//EXAMPLE
$string = 'O.O.O.O.S';
$search = 'O';
$replace = 'B';
//replace ONLY FIRST occurance of "O" with "B"
$string[strpos($string,$search)] = $replace;
//$string => B.O.O.O.S
//replace ONLY LAST occurance of "O" with "B"
$string[strrpos($string,$search)] = $replace;
// $string => B.O.O.B.S

- 1,841
- 15
- 8
-
The first substr_replace() snippet fails when the search string is not at offset 0 of the input string. Proof of failure: https://3v4l.org/oIbRv And both of the `substr_replace()` techniques damage the input string when the search value is not present. Proof of failure: https://3v4l.org/HmEml (And that last technique with all of the `rev` calls is seriously convoluted / hard on the eyes.) – mickmackusa Feb 20 '19 at 04:08
Complementing what people said, remember that the entire string is an array:
$string = "Lorem ipsum lá lá lá";
$string[0] = "B";
echo $string;
"Borem ipsum lá lá lá"

- 518
- 8
- 14
-
3Unless it contains multibyte characters ...and then your technique fails. How unfortunate that you offered a sample input string containing `á`. [Demonstration of failure](https://3v4l.org/uu2kY) – mickmackusa Apr 21 '18 at 06:41
-
You can verify if your `string` is a multibyte string using `mb_strlen($subject) != strlen($subject)` – alexandre-rousseau May 30 '18 at 09:05
-
This function is heavily inspired by the answer by @renocor. It makes the function multi byte safe.
function str_replace_limit($search, $replace, $string, $limit)
{
$i = 0;
$searchLength = mb_strlen($search);
while(($pos = mb_strpos($string, $search)) !== false && $i < $limit)
{
$string = mb_substr_replace($string, $replace, $pos, $searchLength);
$i += 1;
}
return $string;
}
function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
{
$string = (array)$string;
$encoding = is_null($encoding) ? mb_internal_encoding() : $encoding;
$length = is_null($length) ? mb_strlen($string) - $start : $length;
$string = array_map(function($str) use ($replacement, $start, $length, $encoding){
$begin = mb_substr($str, 0, $start, $encoding);
$end = mb_substr($str, ($start + $length), mb_strlen($str), $encoding);
return $begin . $replacement . $end;
}, $string);
return ( count($string) === 1 ) ? $string[0] : $string;
}

- 1,468
- 15
- 17
You can use this:
function str_replace_once($str_pattern, $str_replacement, $string){
if (strpos($string, $str_pattern) !== false){
$occurrence = strpos($string, $str_pattern);
return substr_replace($string, $str_replacement, strpos($string, $str_pattern), strlen($str_pattern));
}
return $string;
}
Found this example from php.net
Usage:
$string = "Thiz iz an examplz";
var_dump(str_replace_once('z','Z', $string));
Output:
ThiZ iz an examplz
This may reduce the performance a little bit, but the easiest solution.

- 24,987
- 7
- 47
- 60
-
If that is the output than what is the point? Shouldn't it only replace the first lowercase "z" with an uppercase "Z"? Instead of replacing all of them? I thought that was what we were talking about here... – Swivel Mar 11 '14 at 06:08
-
-
This same advice was already offered by Bas nearly 3 years earlier (and without excessively calling `strpos()`). Downvoted because it doesn't add any new value to the page. – mickmackusa Feb 20 '19 at 08:59
For Loop Solution
<?php
echo replaceFirstMatchedChar("&", "?", "/property/details&id=202&test=123#tab-6");
function replaceFirstMatchedChar($searchChar, $replaceChar, $str)
{
for ($i = 0; $i < strlen($str); $i++) {
if ($str[$i] == $searchChar) {
$str[$i] = $replaceChar;
break;
}
}
return $str;
}

- 18,150
- 39
- 158
- 271
I would use preg instead. It has a LIMIT parameter you can set it to 1
preg_replace (regex, subst, string, limit) // default is -1

- 2,686
- 25
- 20
If you string does not contains any multibyte characters and if you want to replace only one char you can simply use strpos
Here a function who handle errors
/**
* Replace the first occurence of given string
*
* @param string $search a char to search in `$subject`
* @param string $replace a char to replace in `$subject`
* @param string $subject
* @return string
*
* @throws InvalidArgumentException if `$search` or `$replace` are invalid or if `$subject` is a multibytes string
*/
function str_replace_first(string $search , string $replace , string $subject) : string {
// check params
if(strlen($replace) != 1 || strlen($search) != 1) {
throw new InvalidArgumentException('$search & $replace must be char');
}elseif(mb_strlen($subject) != strlen($subject)){
throw new InvalidArgumentException('$subject is an multibytes string');
}
// search
$pos = strpos($subject, $search);
if($pos === false) {
// not found
return $subject;
}
// replace
$subject[$replace] = $subject;
return $subject;
}

- 2,321
- 26
- 33
-
wrong offset on line $subject[$replace] = $subject;, $replace is string in this function – L.C. Echo Chan Jun 08 '21 at 09:36
Here's a simple class I created to wrap our slightly modified str_replace() functions.
Our php::str_rreplace() function also allows you to carry out a reverse, limited str_replace() which can be very handy when trying to replace only the final X instance(s) of a string.
These examples both use preg_replace().
<?php
class php {
/**
* str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
*
* @param string $find
* @param string $replace
* @param string $subject
* @param int $replacement_limit | -1 to replace all references
*
* @return string
*/
public static function str_replace($find, $replace, $subject, $replacement_limit = -1) {
$find_pattern = str_replace('/', '\/', $find);
return preg_replace('/' . $find_pattern . '/', $replace, $subject, $replacement_limit);
}
/**
* str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
*
* @param string $find
* @param string $replace
* @param string $subject
* @param int $replacement_limit | -1 to replace all references
*
* @return string
*/
public static function str_rreplace($find, $replace, $subject, $replacement_limit = -1) {
return strrev( self::str_replace(strrev($find), strrev($replace), strrev($subject), $replacement_limit) );
}
}

- 2,429
- 1
- 19
- 19
-
Your post does not add value to this already over saturated page. Your regex solution fails on many fringe cases because you used the incorrect tool to escape characters in the needle string. Proof of failure: https://3v4l.org/dTdYK The heavily upvoted and accepted answer from **2009** already shows the proper execution of this technique. Your second method does not answer the question asked and was already provided by oLinkWebDevelopment. – mickmackusa Feb 21 '19 at 06:20
$str = "Hello there folks!"
$str_ex = explode("there, $str, 2); //explodes $string just twice
//outputs: array ("Hello ", " folks")
$str_final = implode("", $str_ex); // glues above array together
// outputs: str("Hello folks")
There is one more additional space but it didnt matter as it was for backgound script in my case.

- 91
- 1
- 1
- 9
-
This technique was provided by toomuchphp back in **2009**! I have downvoted because your post adds no new value to to researchers. Please ensure, before posting an answer, that your solution is unique to the page and adds value to the page. – mickmackusa Feb 21 '19 at 05:18