Word & Character Counter — PHP source
Live word, character, sentence, and paragraph counts plus reading-time estimate as you type.
This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/*
* word-counter — PHP
* ==================
* Polyglot showcase port of CosmoDev's "word-counter" tool, ported from the
* canonical TypeScript at src/lib/wordCount.ts (the live web library and
* unit-test surface).
*
* Display source — part of CosmoDev's polyglot tool pages
* (dev.cosmolabs.org), where each tool's pure logic is shown in six
* languages side by side. Pure string logic; no cryptographic surface.
*
* Public surface: count_words, count_chars, count_chars_no_spaces,
* count_sentences, count_paragraphs, reading_time_min.
*/
declare(strict_types=1);
/**
* Count whitespace-separated words.
*
* trim() drops leading/trailing whitespace; PREG_SPLIT_NO_EMPTY breaks on
* runs of whitespace (\s+) and discards empty segments, so empty or
* whitespace-only input yields 0.
*
* Note: PCRE's \s is ASCII-only ([ \t\r\n\v\f]); for typical prose this
* matches the canonical JavaScript \s exactly.
*/
function count_words(string $text): int
{
if (trim($text) === '') {
return 0;
}
$parts = preg_split('/\s+/', trim($text), -1, PREG_SPLIT_NO_EMPTY);
return count($parts ?: []);
}
/**
* Total character count, whitespace included.
*
* Counts Unicode code points via mb_strlen (UTF-8). The canonical TypeScript
* uses .length (UTF-16 code units); the two agree for BMP text and differ
* only for astral symbols (emoji, etc.), which JavaScript counts as 2.
*/
function count_chars(string $text): int
{
return mb_strlen($text, 'UTF-8');
}
/**
* Character count with all whitespace removed.
*/
function count_chars_no_spaces(string $text): int
{
// Remove ASCII whitespace, then count the remaining UTF-8 characters.
// preg_replace returns null on internal error; the `?? ''` fallback keeps
// the return type stable.
$stripped = preg_replace('/\s+/', '', $text) ?? '';
return mb_strlen($stripped, 'UTF-8');
}
/**
* Number of sentences: maximal runs of non-terminal characters followed by
* one or more sentence terminators (. ! ?). Text with no terminal
* punctuation therefore counts as 0, matching the canonical behaviour.
*/
function count_sentences(string $text): int
{
// preg_match_all returns the number of full pattern matches (int), or
// false on error; the (int) cast keeps strict_types satisfied either way.
return (int) preg_match_all('/[^.!?]+[.!?]+/', $text);
}
/**
* Number of paragraphs separated by one or more blank lines (2+ newlines).
* Segments that are blank after trimming do not count.
*/
function count_paragraphs(string $text): int
{
$segments = preg_split('/\n{2,}/', $text) ?: [];
$count = 0;
foreach ($segments as $segment) {
if (trim($segment) !== '') {
$count++;
}
}
return $count;
}
/**
* Estimated reading time in whole minutes at 200 words per minute.
*
* 0 for empty input, otherwise at least 1. Integer arithmetic —
* intdiv($words + 100, 200) — reproduces round(words / 200) exactly for
* non-negative word counts and avoids cross-language float differences.
*/
function reading_time_min(int $words): int
{
if ($words <= 0) {
return 0;
}
return max(1, intdiv($words + 100, 200));
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →