Signature
str_word_count(string $string, int $format = 0, ?string $characters = null): array|intPurpose
str_word_count() inspects a string and reports on the words in it. Depending on the format argument it returns how many words it found, a plain list of the words, or a list keyed by the byte position where each word starts. It is a quick tool for rough statistics such as a reading-time estimate, a length hint or a short preview. It is not a full text-analysis tool, because its idea of a "word" is deliberately simple.
The manual defines a word as a locale-dependent run of alphabetic characters that may also contain, but not start with, an apostrophe (') or a hyphen (-). Multibyte locales are not supported.
Signature
Parameters and defaults
$string: the text to examine.$format: selects the result shape. The default is0.0returns the number of words as an integer.1returns a list array of the words in order.2returns an associative array. Each key is the byte offset where the word begins and each value is the word.
$characters: an optional string of extra characters that should be treated as part of words. The default isnull, meaning no extras. Since PHP 8.0 this parameter is explicitly nullable.
Return value and failure cases
The return type is array|int. With format 0 you get an int, otherwise an array. A string with no words gives 0 or an empty array. The function does not return false for ordinary input. In PHP 8, passing a $format other than 0, 1 or 2 throws a ValueError, so do not feed it unchecked user values.
Example: counting and listing
The simplest use is to count words, or to ask for the words themselves with format 1.
$text = "The quick brown fox";
echo str_word_count($text), "\n";
echo json_encode(str_word_count($text, 1), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";4 ["The","quick","brown","fox"]
The count is 4, and the list contains the same four words in their original order.
Example: positions with format 2
Format 2 keeps track of where each word starts. Offsets are measured in bytes from the beginning of the string, starting at zero. Apostrophes and hyphens inside a word stay part of it.
$text = "Don't stop: well-known rules!";
$words = str_word_count($text, 2);
echo json_encode($words, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"0":"Don't","6":"stop","12":"well-known","23":"rules"}
Because the keys are not consecutive, json_encode() writes this array as an object. Punctuation such as the colon and the exclamation mark is never part of a word here.
Example: extra characters
By default, digits are not letters, so they split words. The third argument lets you declare extra characters that belong inside words.
$text = "Order A1 and B22 shipped";
$plain = str_word_count($text, 1);
$withDigits = str_word_count($text, 1, "0123456789");
echo json_encode($plain, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode($withDigits, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo count($plain), " ", count($withDigits), "\n";["Order","A","and","B","shipped"] ["Order","A1","and","B22","shipped"] 5 5
Notice that both versions find five words, yet the contents differ. Without the extra characters, A1 becomes A, and B22 becomes B, so the number alone would not reveal the difference.
Edge cases
- Digits split words. The manual's own example shows
fri3ndbecomingfriandndunless3is passed in$characters. - Non-ASCII text. The function works on bytes and depends on the locale, and multibyte locales are not supported. Accented letters or non-Latin scripts may be treated as separators and split words apart. For such text consider
preg_split()with theumodifier, or themb_*functions. - Apostrophes and hyphens. The documented rule says words may contain but not start with them. The implementation is simple, so test your own punctuation-heavy input rather than assuming the rule is applied perfectly.
- Empty or symbol-only strings. These produce
0or an empty array, never an error. - Offsets are bytes. The keys from format
2are byte positions, not character positions.
Common mistakes
- Treating the result as a universal word count. Numbers, emoji, URLs and email addresses are not counted the way a person would count them.
- Forgetting that the return type changes with
$format. Callingcount()on the default integer result is wrong, whilecount()on formats 1 or 2 is fine. - Passing a variable
$formatwithout validating it, which can throw aValueError. - Using it on UTF-8 text in languages with accents and expecting correct splitting.
- Assuming format
2keys are sequential indexes. They are offsets, so looping withfor ($i = 0; ...)will miss entries.
Related functions
explode()splits on a fixed separator and keeps punctuation attached.preg_split()splits on a regular expression, giving full control, including Unicode.count_chars()reports on the characters in a string rather than the words.substr_count()counts occurrences of one specific substring.
Recap
str_word_count() returns a count, a list or an offset-keyed array depending on $format. Words are runs of letters, with apostrophes and hyphens allowed inside, and $characters lets you add more word characters such as digits. It is byte-oriented and locale-dependent, so use it for simple English-style text and choose a regex or multibyte approach for anything more demanding.
Tested examples
$text = "The quick brown fox";
echo str_word_count($text), "\n";
echo json_encode(str_word_count($text, 1), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";4 ["The","quick","brown","fox"]
$text = "Don't stop: well-known rules!";
$words = str_word_count($text, 2);
echo json_encode($words, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"0":"Don't","6":"stop","12":"well-known","23":"rules"}
$text = "Order A1 and B22 shipped";
$plain = str_word_count($text, 1);
$withDigits = str_word_count($text, 1, "0123456789");
echo json_encode($plain, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode($withDigits, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo count($plain), " ", count($withDigits), "\n";["Order","A","and","B","shipped"] ["Order","A1","and","B22","shipped"] 5 5