Strings7 min readExamples run on PHP 8.5.10

strchr()

Learn how strchr(), an alias of strstr(), returns the part of a string starting at the first match, with examples, edge cases and mistakes to avoid.

Signature

strchr(string $haystack, string $needle, bool $before_needle = false): string|false

Purpose

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

  • $haystack is the string to search.
  • $needle is the text to look for. It can be longer than one character, and the search is case-sensitive.
  • $before_needle defaults to false. When it is false, the result begins at the match and includes the needle. When it is true, 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.

Example
<?php
$email = 'ana@example.com';
echo strchr($email, '@'), "\n";
echo strchr($email, '@', true), "\n";
Output
@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.

Example
<?php
$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);
Output
{"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.

Example
<?php
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);
Output
["=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 = true result 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

  1. Testing with if (strchr(...)). An empty string or "0" is falsy even though the call succeeded. Compare against false strictly.
  2. Forgetting the needle is included. The default result starts with the needle, so you may end up with a stray @ or . in your output.
  3. Expecting case-insensitive matching. Use stristr() for that.
  4. Using it only to ask whether a substring exists. str_contains() states that intent more clearly and returns a boolean.
  5. Wanting an offset. strchr() returns text, not a position. Use strpos() when the index matters.
  • strstr() is the function that strchr() 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() and str_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

Splitting an email address at the @ sign
<?php
$email = 'ana@example.com';
echo strchr($email, '@'), "\n";
echo strchr($email, '@', true), "\n";
Output
@example.com
ana
First match and case sensitivity
<?php
$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);
Output
{"first":".final.txt","before":"reports/2024/summary","upper":false,"lower":"s/2024/summary.final.txt"}
Handling false safely
<?php
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);
Output
["=value=extra","(not found)","bc","abc"]