Strings7 min readExamples run on PHP 8.5.10

strstr()

Learn how PHP 8.4 strstr() returns the part of a string from the first match onward or before it, with examples, edge cases and pitfalls.

Signature

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

Purpose

strstr() searches a string for the first occurrence of another string and hands back a slice of the original. By default the slice starts at the match and runs to the end of the string, so the needle itself is included. With the optional third argument turned on, you get the opposite piece: everything before the match.

This makes it handy for splitting a value at a known delimiter, such as the @ in an email address, without computing offsets yourself. The search is case-sensitive.

Signature

Parameters and defaults

  • $haystack: the string to search in.
  • $needle: the string to look for. Since PHP 8.0 it may be an empty string, and an integer is no longer treated as a character code. If you have a number, convert it deliberately with a string cast, or use chr() when you really mean a byte value.
  • $before_needle: defaults to false. When true, the function returns the part of the haystack before the first match, and the needle is not included.

Return value and failure cases

The function returns a string when the needle is found and false when it is not. It does not throw for a missing needle. Because a successful result can be an empty string (for example with $before_needle set to true and a match at position 0), always compare against false with === rather than testing truthiness.

If you only need to know whether the needle exists, str_contains() is simpler and cheaper. If you need the numeric position, use strpos().

Example 1: splitting an email address

The same call, with and without the third argument, gives both halves around the first @.

Example
<?php
$email = 'name@example.com';
echo json_encode([
    'domain' => strstr($email, '@'),
    'user' => strstr($email, '@', true),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"domain":"@example.com","user":"name"}

Notice that the domain part keeps the @, because the match is included by default.

Example 2: case sensitivity and a missing needle

A lowercase needle does not match uppercase text, so the first call reports failure. Use stristr() when case should be ignored.

Example
<?php
$text = 'Report.PDF';
var_dump(strstr($text, 'pdf'));
var_dump(strstr($text, 'PDF'));
Output
bool(false)
string(3) "PDF"

Example 3: first match only and the empty needle

When the needle occurs several times, only the first occurrence counts. An empty needle is accepted in PHP 8 and matches at the very start, so the whole haystack comes back, or an empty string when $before_needle is true.

Example
<?php
$path = 'a/b/c';
echo json_encode([
    strstr($path, '/'),
    strstr($path, '/', true),
    strstr($path, ''),
    strstr($path, '', true),
    strstr('', ''),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["/b/c","a","a/b/c","",""]

Edge cases

  • Match at the start: with $before_needle set to true the result is "", which is a valid string and not a failure.
  • Match at the end: by default you receive just the needle, since nothing follows it.
  • Repeated needles: only the first one is used. To work from the last occurrence of a single character, look at strrchr().
  • Bytes, not characters: the function works on bytes. For ASCII delimiters this is safe even inside UTF-8 text, but it does not understand multibyte case rules or grapheme boundaries.
  • Needle types: passing an integer as a character code stopped being supported in PHP 8.0. Pass strings.

Common mistakes

  1. Testing the result with if (strstr(...)). An empty string is falsy, and so is the string "0". Use !== false to detect a match.
  2. Expecting the needle to be removed. By default it is included in the result. Use the third argument for the leading part, or trim the delimiter yourself for the trailing part.
  3. Forgetting case sensitivity. strstr('Report.PDF', 'pdf') fails. Normalize the case first or use stristr().
  4. Using it only for a yes/no check. str_contains() states that intent more clearly and avoids creating a new string.
  5. Assuming the result is always a string. Code that passes the result straight to another string function without checking can end up handling false.
  • stristr(): the case-insensitive version.
  • strrchr(): finds the last occurrence of a character.
  • strpos(): returns the position of the first match instead of a substring.
  • strpbrk(): searches for any one of a set of characters.
  • str_contains(): a boolean check for presence.
  • preg_match(): for patterns that a fixed string cannot describe.

Recap

strstr() returns the tail of a string starting at the first case-sensitive match, or the head before it when $before_needle is true. It returns false when nothing matches, so compare strictly. Empty needles are allowed in PHP 8, and for simple presence checks str_contains() is the better tool.

Tested examples

Splitting an email address at the first @
<?php
$email = 'name@example.com';
echo json_encode([
    'domain' => strstr($email, '@'),
    'user' => strstr($email, '@', true),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"domain":"@example.com","user":"name"}
Case sensitivity and a missing needle
<?php
$text = 'Report.PDF';
var_dump(strstr($text, 'pdf'));
var_dump(strstr($text, 'PDF'));
Output
bool(false)
string(3) "PDF"
First match only and the empty needle
<?php
$path = 'a/b/c';
echo json_encode([
    strstr($path, '/'),
    strstr($path, '/', true),
    strstr($path, ''),
    strstr($path, '', true),
    strstr('', ''),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["/b/c","a","a/b/c","",""]