Skip to content

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 →