Signature
substr_count(string $haystack, string $needle, int $offset = 0, ?int $length = null): intPurpose
substr_count() answers one question: how many times does a given piece of text appear inside a larger string? It returns a plain integer, so it is a good fit for quick checks such as counting line breaks, tallying a keyword in a log, or verifying that a template contains the expected number of placeholders.
The search is case sensitive, and matches are non-overlapping. Once a match is found, the search continues after the end of that match.
Signature
Parameters and defaults
$haystackis the string to search in.$needleis the substring to count. It must not be empty.$offsetis where counting starts. The default is0, the start of the string. A negative offset counts from the end of the string.$lengthis the maximum number of characters, measured from the offset, in which to look. The default isnull, which means "search to the end". A negative length stops the search that many characters before the end of the haystack.
All positions are measured in bytes, not in characters of a multibyte encoding. For plain ASCII text the two are the same.
Return value and failure cases
The function returns an int, which is 0 when nothing matches. It does not return false for "not found".
Invalid arguments throw a ValueError:
- An empty
$needleis rejected. - An
$offsetthat lies outside the haystack is rejected. - A
$lengththat makes the search window extend past the haystack is rejected. The manual's own example uses an offset of 5 and a length of 10 on a 14-byte string.
Example 1: case and overlap
The first example shows the two rules that surprise people most. Uppercase IS does not match lowercase text, and aa occurs twice in aaaa rather than three times, because the matches may not overlap.
$text = 'This is a test';
echo substr_count($text, 'is'), PHP_EOL;
echo substr_count($text, 'IS'), PHP_EOL;
echo substr_count('aaaa', 'aa'), PHP_EOL;2 0 2
Example 2: offset and length
With an offset, only the remaining part of the string is searched. With a length, the search window is limited further. Negative values count from the end.
$text = 'This is a test';
echo substr_count($text, 'is', 3), PHP_EOL;
echo substr_count($text, 'is', 3, 3), PHP_EOL;
echo substr_count($text, 'is', -9), PHP_EOL;
echo substr_count($text, 'is', 0, -6), PHP_EOL;1 0 1 2
The offset of 3 leaves s is a test, which has one match. Adding a length of 3 shrinks the window to s i, which has none. The offset -9 begins at index 5, leaving is a test, with one match. The last call searches the first eight bytes, This is , which contain two matches.
Example 3: practical counting and errors
Counting newline characters tells you how many line breaks a block of text contains, and counting a keyword gives a quick tally. Invalid arguments can be caught as ValueError.
$log = "ok\nerror: disk\nok\nerror: cpu\n";
echo substr_count($log, "\n"), PHP_EOL;
echo substr_count($log, 'error'), PHP_EOL;
try {
echo substr_count($log, '');
} catch (ValueError $e) {
echo get_class($e), PHP_EOL;
}
try {
echo substr_count('abc', 'a', 10);
} catch (ValueError $e) {
echo get_class($e), PHP_EOL;
}4 2 ValueError ValueError
Edge cases
- Overlaps are skipped. Counting
gcdgcdingcdgcdgcdgives1, because the second possible match would reuse characters from the first. - An empty haystack is allowed with a non-empty needle and simply returns
0. - A needle longer than the haystack returns
0. - A length of
0is allowed according to the manual's changelog, and the search window is then empty. - Multibyte text is handled byte by byte. Counting a single-byte needle inside UTF-8 text is usually safe, but offsets and lengths may fall in the middle of a multibyte character. If you need offsets in characters, use a multibyte-aware approach instead.
Common mistakes
- Expecting case-insensitive matching. Normalise both strings first, for example with
strtolower()for ASCII text, if you want that behaviour. - Counting overlapping patterns.
substr_count('aaa', 'aa')is1, not2. A regular expression with a lookahead is the usual tool for overlapping counts. - Passing an empty needle, often from an unchecked variable. Validate the input before calling the function.
- Using it to count words. Searching for
thealso matches insideotherandbathe. Use a pattern that respects word boundaries when whole words matter. - Treating the result as a boolean flag and forgetting that
0is falsy. That is fine for an "is there any match" test, butstrpos()orstr_contains()states that intent more clearly. - Mixing up offset and length. Length is relative to the offset, not an absolute end position.
Related functions
strpos()finds the position of the first occurrence.substr()returns part of a string, which is useful for inspecting the window being searched.strstr()returns the string from the first occurrence onward.count_chars()reports character usage across a string.
Recap
substr_count() returns how many non-overlapping, case-sensitive occurrences of a non-empty needle appear in a haystack, optionally restricted by an offset and length. A missing needle gives 0, while an empty needle or an out-of-range window throws a ValueError.
Tested examples
$text = 'This is a test';
echo substr_count($text, 'is'), PHP_EOL;
echo substr_count($text, 'IS'), PHP_EOL;
echo substr_count('aaaa', 'aa'), PHP_EOL;
2 0 2
$text = 'This is a test';
echo substr_count($text, 'is', 3), PHP_EOL;
echo substr_count($text, 'is', 3, 3), PHP_EOL;
echo substr_count($text, 'is', -9), PHP_EOL;
echo substr_count($text, 'is', 0, -6), PHP_EOL;
1 0 1 2
$log = "ok\nerror: disk\nok\nerror: cpu\n";
echo substr_count($log, "\n"), PHP_EOL;
echo substr_count($log, 'error'), PHP_EOL;
try {
echo substr_count($log, '');
} catch (ValueError $e) {
echo get_class($e), PHP_EOL;
}
try {
echo substr_count('abc', 'a', 10);
} catch (ValueError $e) {
echo get_class($e), PHP_EOL;
}
4 2 ValueError ValueError