Question
How can I replace only the first occurrence of $search in $subject in PHP? I need an alternative to str_replace() that leaves all later matches unchanged. Is there a simple, reliable approach?
Short Answer
You will learn why str_replace() replaces every occurrence, how to limit a replacement to the first match, and when to choose a regular-expression or literal-string solution in PHP.
Concept
str_replace() is designed for global replacement: when it finds a matching string, it replaces all occurrences in the subject.
When you need to change only the first match, you need a way to:
- Find the position of the first occurrence.
- Replace text at that position only.
- Keep the rest of the string unchanged.
In PHP, two common approaches are:
preg_replace()with a replacement limit of1.strpos()plussubstr_replace()for a literal, non-regex search.
The literal approach is often easiest to reason about because the search text is treated exactly as text. preg_replace() is useful when you already need regular-expression matching.
Mental Model
Imagine a document with the word draft appearing three times.
str_replace()is like telling an editor: “Change everydraftin this document tofinal.”- A first-only replacement is like saying: “Find the first
draft, change it tofinal, then stop searching.”
strpos() finds the location of that first word, and substr_replace() performs the edit at that exact location.
Syntax and Examples
For a plain-text search, combine strpos() and substr_replace():
<?php
$subject = "red, blue, red";
$search = "red";
$replacement = "green";
$position = strpos($subject, $search);
if ($position !== false) {
$result = substr_replace(
$subject,
$replacement,
$position,
strlen($search)
);
} else {
$result = $subject;
}
echo $result; // green, blue, red
strpos() returns the zero-based position of the first match, or false when no match exists. substr_replace() replaces a section of a string:
substr_replace($subject, $replacement, $offset, $length);
$offsetis where replacement begins.$lengthis how many characters are removed.strlen($search)removes exactly the matched text.
You can also use preg_replace() with its fourth argument, $limit:
<?php
$subject = "red, blue, red";
$search = "red";
$replacement = "green";
$pattern = '/' . preg_quote($search, '/') . '/';
$result = preg_replace($pattern, $replacement, $subject, 1);
echo $result; // green, blue, red
preg_quote() is important here because it makes special regex characters in $search, such as ., +, and ?, behave as literal characters.
Step by Step Execution
Consider this literal replacement:
<?php
$subject = "cat and cat";
$search = "cat";
$replacement = "dog";
$position = strpos($subject, $search);
if ($position !== false) {
$result = substr_replace($subject, $replacement, $position, strlen($search));
}
Step by step:
$subjectcontains"cat and cat".strpos($subject, "cat")finds the firstcatat position0.strlen("cat")is3.substr_replace()starts at position0, removes3characters, and inserts"dog".- The remaining text,
" and cat", stays unchanged. $resultbecomes"dog and cat".
If the search text were not found, strpos() would return false, so the if block would not run.
Real World Use Cases
Replacing only the first match is useful when the first occurrence has special meaning:
- URL cleanup: replace only the first accidental double slash in a path after the protocol has been handled.
- Template processing: replace the first placeholder, such as
{{name}}, while processing values one at a time. - Log formatting: redact only the first occurrence of a token in a preview string.
- User interface text: change the first label or prefix while preserving later mentions.
- Data migration scripts: update the first matching marker in a legacy text field without changing historical content later in the field.
Real Codebase Usage
In real PHP projects, developers usually wrap this behavior in a small helper so its intent is clear and the not-found case is handled consistently:
<?php
function replaceFirst(string $subject, string $search, string $replacement): string
{
if ($search === '') {
return $subject;
}
$position = strpos($subject, $search);
if ($position === false) {
return $subject;
}
return substr_replace($subject, $replacement, $position, strlen($search));
}
This uses guard clauses:
- An empty search string returns immediately.
- A missing match returns the original string.
- The successful path stays short and readable.
Use preg_replace() instead when the thing you need to find is a pattern, not a fixed string. For example, replacing only the first sequence of digits:
$result = preg_replace('/\d+/', '[number]', $subject, 1);
For user-provided literal search text, do not place it directly into a regex pattern. Escape it with preg_quote().
Common Mistakes
Checking strpos() with if ($position)
This fails when the match starts at position 0, because 0 is treated as false in a boolean condition.
<?php
$position = strpos("cat and dog", "cat");
if ($position) { // Incorrect: $position is 0
echo "Found";
}
Always compare strictly with false:
if ($position !== false) {
echo "Found";
}
Using str_replace() and expecting one change
$result = str_replace("cat", "dog", "cat and cat");
// dog and dog
str_replace() has no argument for a replacement limit.
Using unescaped text in preg_replace()
<?php
$search = "file.txt";
$pattern = "/$search/"; // Incorrect for a literal search
In regex, . means “any character,” so this can match text you did not intend. Use:
$pattern = '/' . preg_quote($search, '/') . '/';
Forgetting the empty-search case
An empty search string is usually not meaningful for a replace-first helper. Handle it explicitly to avoid unclear behavior:
if ($search === '') {
return $subject;
}
Using byte-based functions for text that needs character positions
strpos() and strlen() work with bytes. For user-facing UTF-8 text where character positions matter, consider mb_strpos() and mb_strlen() if the mbstring extension is available.
Comparisons
| Approach | Best for | First match only? | Important detail |
|---|---|---|---|
str_replace() | Replacing fixed text everywhere | No | Replaces all matches. |
strpos() + substr_replace() | Replacing the first fixed, literal string | Yes | Check with !== false. |
preg_replace(..., 1) | Replacing the first regex pattern match | Yes | Escape literal input with preg_quote(). |
str_ireplace() | Case-insensitive replacement everywhere | No | Like str_replace(), it replaces all matches. |
Choose strpos() plus substr_replace() when searching for an exact piece of text. Choose preg_replace() when matching rules such as “the first number” or “the first run of whitespace.”
Cheat Sheet
// Replace the first literal occurrence
$position = strpos($subject, $search);
if ($search !== '' && $position !== false) {
$result = substr_replace($subject, $replacement, $position, strlen($search));
} else {
$result = $subject;
}
// Replace the first regex match
$result = preg_replace('/pattern/', $replacement, $subject, 1);
// Replace the first literal string using preg_replace
$pattern = '/' . preg_quote($search, '/') . '/';
$result = preg_replace($pattern, $replacement, $subject, 1);
Rules:
str_replace()always replaces every occurrence.strpos()returns0for a match at the start andfalsefor no match.- Use
!== false, never justif ($position). substr_replace()needs the matched length:strlen($search).- Escape literal search text with
preg_quote()before using it in a regex.
FAQ
Can str_replace() replace only the first occurrence in PHP?
No. str_replace() replaces every occurrence of the search string. Use strpos() with substr_replace(), or preg_replace() with a limit of 1.
What is the simplest way to replace the first literal match?
Use strpos() to find the first position, then use substr_replace() at that position.
Why must I use !== false with strpos()?
A valid match may occur at position 0. Since 0 is false-like in PHP, a loose boolean check incorrectly treats that match as missing.
How do I replace only the first regex match in PHP?
Pass 1 as the fourth argument to preg_replace():
preg_replace('/\d+/', 'X', $subject, 1);
Do I need preg_quote() with preg_replace()?
Yes, when your search value should be treated as literal text. It escapes regex metacharacters such as ., *, ?, and +.
What should happen when the search text does not exist?
Usually return the original subject unchanged. This makes the helper predictable and avoids errors.
Is the replacement case-sensitive?
strpos() and the shown preg_replace() examples are case-sensitive. For a case-insensitive literal search, use stripos() to find the position, while still using the correct matched length.
Mini Project
Description
Create a reusable PHP helper that replaces only the first literal occurrence of a search string. This is useful in text-processing code where a global replacement would change more content than intended.
Goal
Build and test a replaceFirst() function that safely changes one literal match and leaves the rest of the string untouched.
Requirements
- Create a function named
replaceFirstthat accepts a subject, search string, and replacement string. - Replace only the first occurrence of the search string.
- Return the original subject when no match exists.
- Return the original subject when the search string is empty.
- Demonstrate the function with a string containing at least two matches.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.