substr_count
Counts how many non-overlapping times a substring occurs in a string.
substr_count(string $haystack, string $needle, int $offset = 0, ?int $length = null): int- Added in PHP 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$haystack | string | The string to search in. |
$needle | string | The substring to count. May not be an empty string. |
$offsetoptional | int= 0 | The position at which counting starts. Negative values count back from the end of the string. |
$lengthoptional | ?int= null | The maximum number of bytes to search after offset. A negative value stops that many bytes before the end of haystack. null searches to the end. |
Return value
intThe number of times needle occurs in the searched portion of haystack.
Errors
Throws a ValueError if needle is an empty string, or if offset or length falls outside the haystack.
Changelog
- PHP 8.0.0length is nullable now.
- PHP 7.1.0Negative offset and length values are now supported, and length may be 0.
substr_count counts occurrences of needle in haystack, moving past each match before looking for the next. That means matches never overlap: substr_count('aaa', 'aa') is 1, not 2.
When to reach for it
It is the fastest way to answer "how many times" for a literal substring — counting newlines to get a line number, counting a delimiter to validate a CSV row, or checking that a template has exactly one placeholder. Compared with preg_match_all it does no pattern compilation, and compared with count(explode(...)) it builds no array at all.
What trips people up
Overlapping matches are the number one surprise; if you need them, use preg_match_all with a lookahead. An empty needle throws a ValueError rather than returning zero, so validate any needle that comes from user input.
The offset and length arguments are measured in bytes and are validated strictly: an offset past the end of the string throws instead of returning 0. And, like the rest of the str* family, this counts byte sequences — counting a multibyte character works because you are matching whole sequences, but a length limit can cut one in half.
Example
When to use it
- Count newlines in a buffer to report the line number of a parse error.
- Verify that every row of a CSV chunk has the expected number of separators before splitting it.
- Check that a message template contains exactly one occurrence of a required placeholder.
- Measure how often a search term appears in a document for a simple relevance score.
Frequently asked questions
Does substr_count count overlapping occurrences?
No. It resumes searching after the end of each match, so substr_count('aaaa', 'aa') is 2. Use preg_match_all with a lookahead such as /(?=aa)/ to count overlaps.
Is substr_count case-sensitive?
Yes. Lowercase both strings first, or use preg_match_all with the i modifier, if case should be ignored.
What happens if the needle is an empty string?
It throws a ValueError. Check that the needle is non-empty before calling substr_count with a value that came from input.
More examples
Reporting the line a match is on
Prints 'found on line 3' and 'total lines: 4'. Counting newlines only up to the match position — using the length argument — is the cheapest way to turn a byte offset into a line number.
<?php
$doc = "alpha\nbeta\ngamma\ndelta\n";
$pos = strpos($doc, 'gamma');
$line = substr_count($doc, "\n", 0, $pos) + 1;
echo "found on line $line\n";
echo 'total lines: ', substr_count($doc, "\n"), "\n";Counting overlapping matches instead
substr_count reports 2 because it resumes after each match, while the zero-width lookahead pattern reports 3, catching the overlap at offset 2. Use the regex form whenever overlaps count.
<?php
$s = 'abababa';
echo substr_count($s, 'aba'), "\n"; // 2
preg_match_all('/(?=aba)/', $s, $m);
echo count($m[0]), "\n"; // 3
Discussion