Signature
strchr(string $haystack, string $needle, bool $before_needle = false): string|falsePurpose
strchr() searches a string for the first occurrence of another string and returns a slice of the original. By default the slice starts at the match and runs to the end of the haystack. Optionally it returns only the text before the match.
The PHP manual lists strchr() as an alias of strstr(). The two names are the same function, so they accept the same arguments and behave identically. The name comes from the C library, which is why you may see it in code written by developers with a C background. In new code, strstr() is the more common spelling.
Signature
The signature is the one documented for strstr(), because strchr() is an alias.
Parameters and defaults
$haystackis the string to search.$needleis the text to look for. It can be longer than one character, and the search is case-sensitive.$before_needledefaults tofalse. When it isfalse, the result begins at the match and includes the needle. When it istrue, the result is the part of the haystack before the first match, and the needle is not included.
Return value and failure
The function returns a string on success. If the needle is not found it returns false. It does not throw an exception for a missing needle, so you have to check the result yourself.
Because an empty string is also falsy in PHP, compare with === false rather than relying on a loose check. A successful call can legitimately return a string such as "0", which a loose boolean test would treat as a failure.
Example: splitting at a delimiter
A common use is pulling a domain or a username out of an email address. The default call keeps the delimiter, and $before_needle = true drops everything from the delimiter onward.
$email = 'ana@example.com';
echo strchr($email, '@'), "\n";
echo strchr($email, '@', true), "\n";@example.com ana
Note that the first result still contains the @. If you want only the domain, remove the first character from the result, or use a function designed for that purpose.
Example: first match and case sensitivity
Only the first match counts, so later occurrences of the needle stay inside the returned text. Matching is case-sensitive, so S and s are different needles.
$path = 'reports/2024/summary.final.txt';
$results = [
'first' => strchr($path, '.'),
'before' => strchr($path, '.', true),
'upper' => strchr($path, 'S'),
'lower' => strchr($path, 's'),
];
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);{"first":".final.txt","before":"reports/2024/summary","upper":false,"lower":"s/2024/summary.final.txt"}The upper entry is false because the path contains no uppercase S. The lower entry starts at the s that ends the word reports, not at the s in summary.
Example: handling false safely
Wrap the call when you need a string result every time. This sketch also shows a multi-character needle and an empty needle.
function tail(string $text, string $needle): string {
$result = strchr($text, $needle);
return $result === false ? '(not found)' : $result;
}
echo json_encode([
tail('key=value=extra', '='),
tail('key', '='),
tail('abc', 'bc'),
tail('abc', ''),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);["=value=extra","(not found)","bc","abc"]
The empty needle matches at the very start of the haystack, so the whole string comes back. This surprises people who expect an empty needle to fail.
Edge cases
- The needle is matched as bytes. It works on any string, but it is not aware of multibyte characters. A valid UTF-8 needle will still match whole characters in practice, because valid UTF-8 sequences do not appear inside other characters.
- When the match is at position zero, the default result is the entire haystack, and the
$before_needle = trueresult is an empty string. - If the haystack is empty and the needle is not, the result is
false. - Only the first occurrence is used. To work with the last one, a different function is needed.
Common mistakes
- Testing with
if (strchr(...)). An empty string or"0"is falsy even though the call succeeded. Compare againstfalsestrictly. - Forgetting the needle is included. The default result starts with the needle, so you may end up with a stray
@or.in your output. - Expecting case-insensitive matching. Use
stristr()for that. - Using it only to ask whether a substring exists.
str_contains()states that intent more clearly and returns a boolean. - Wanting an offset.
strchr()returns text, not a position. Usestrpos()when the index matters.
Related functions
strstr()is the function thatstrchr()aliases.stristr()is the case-insensitive version.strrchr()looks for the last occurrence of a character.strpos()returns the position of the first match.substr()extracts text by offset and length.str_contains(),str_starts_with()andstr_ends_with()return booleans for simple checks.
Recap
strchr() is another name for strstr(). It returns the haystack from the first match onward, or the text before the match when $before_needle is true. It returns false when nothing matches, so check with === false. The search is case-sensitive, and an empty needle returns the whole haystack.
Tested examples
$email = 'ana@example.com';
echo strchr($email, '@'), "\n";
echo strchr($email, '@', true), "\n";@example.com ana
$path = 'reports/2024/summary.final.txt';
$results = [
'first' => strchr($path, '.'),
'before' => strchr($path, '.', true),
'upper' => strchr($path, 'S'),
'lower' => strchr($path, 's'),
];
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);{"first":".final.txt","before":"reports/2024/summary","upper":false,"lower":"s/2024/summary.final.txt"}
function tail(string $text, string $needle): string {
$result = strchr($text, $needle);
return $result === false ? '(not found)' : $result;
}
echo json_encode([
tail('key=value=extra', '='),
tail('key', '='),
tail('abc', 'bc'),
tail('abc', ''),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);["=value=extra","(not found)","bc","abc"]