mb_substr_count
Counts how many non-overlapping times a substring occurs in a string.
mb_substr_count(string $haystack, string $needle, ?string $encoding = null): int- Added in PHP 4.3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$haystack | string | The string being searched. |
$needle | string | The substring to count. Must not be empty. |
$encodingoptional | ?string= null | Character encoding of both strings. Falls back to the internal encoding when null. |
Return value
intThe number of times the needle substring occurs in the haystack string.
Errors
Throws a ValueError if needle is an empty string, or if encoding names an unknown character encoding.
Changelog
- PHP 8.0.0encoding is nullable now.
mb_substr_count() returns how many times needle appears in haystack, counting non-overlapping matches: after a match it resumes searching from the end of that match, so 'ααα' contains 'αα' exactly once, not twice.
When to reach for it
Use it for quick tallies where a regular expression would be overkill — counting delimiters to guess a CSV column count, counting newlines to get a line count, checking how often a term appears in a body of text, or spotting a placeholder that appears more than once in a template.
Unlike the byte-based substr_count(), it cannot report a phantom match caused by a byte sequence that happens to straddle two characters. In UTF-8 that is not actually possible, so for UTF-8 both functions agree; the multibyte version matters in stateful and fixed-width encodings such as ISO-2022-JP and UTF-16.
What trips people up
There is no word-boundary logic. Counting 'το' in Greek text also matches the tail of 'τρίτο', and counting 'is' in English matches inside 'this'. Matching is also case-sensitive — lowercase both sides first if you want a case-insensitive tally. An empty needle throws a ValueError; it does not return 0.
Example
When to use it
- Count the delimiters in a line to validate a CSV row's column count.
- Check that a template placeholder appears exactly once before substituting it.
- Tally how often a search term appears in an article for a relevance score.
- Count newline characters to get the number of lines in a block of text.
Frequently asked questions
Does mb_substr_count count overlapping matches?
No. Searching resumes after the end of each match, so counting 'aa' in 'aaaa' returns 2, not 3. Use preg_match_all with a lookahead such as /(?=aa)/ if you need overlapping counts.
Is mb_substr_count case-insensitive?
No, it is case-sensitive. Apply mb_strtolower() to both the haystack and the needle first, or use preg_match_all with the i and u modifiers.
What happens if the needle is an empty string?
It throws a ValueError: 'mb_substr_count(): Argument #2 ($needle) must not be empty'. Guard against empty needles rather than relying on a zero return.
More examples
Case sensitivity and word boundaries
The first count is 3: two standalone "το" plus the ending of "τρίτο", while the capitalised "Το" is missed. Lowercasing first raises it to 4. Only preg_match_all with word boundaries gives the 3 real words, which is the number most people actually wanted.
<?php
$text = "Το ένα και το άλλο και το τρίτο";
echo mb_substr_count($text, "το"), "\n";
echo mb_substr_count(mb_strtolower($text), "το"), "\n";
echo preg_match_all("/\\bτο\\b/u", mb_strtolower($text)), "\n";Validating a template's placeholders
{nom} is reported twice and {date} zero times, which is enough to reject the template before rendering. The last line counts the accented é twice, showing that the needle can itself be a multibyte character.
<?php
$template = "Bonjour {nom}, votre café {nom} est prêt.";
foreach (["{nom}", "{date}"] as $key) {
$n = mb_substr_count($template, $key);
echo $key, " appears ", $n, " time(s)\n";
}
echo mb_substr_count($template, "é"), "\n";
Discussion