Skip to content

Regex Tester — PHP source

Test regular expressions against any text with all flags (g/i/m/s/u/y). See every match, its index and capture groups, plus the constructed pattern. 100% in-browser.

This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * Regular expression tester: match a pattern against input text and report
 * each match with its capture groups and character index.
 *
 * Language: PHP
 * CosmoDev polyglot showcase port of the `regex-tester` tool.
 * Ported from src/tools/RegexTester.tsx — display source, part of CosmoDev's
 * polyglot tool pages.
 *
 * Compiles the pattern with PCRE modifiers and walks the matches, surfacing
 * the full match, its index, and the capture groups. JS flags map directly
 * onto PCRE: i→i, m→m, s→s, u→u (UTF-8). g selects preg_match_all vs
 * preg_match (first match). PCRE shares JS's backreference and lookahead
 * support, so pattern semantics match the island closely. Sticky y has no
 * PCRE modifier (the \G anchor approximates it); documented as unsupported.
 * PREG_UNMATCHED_AS_NULL makes optional groups read as null, mirroring the
 * JS source's `undefined` capture entries.
 */

declare(strict_types=1);

/**
 * Run $pattern (with $flags) against $testStr and collect every match.
 *
 * @param string $pattern Regular expression source (no delimiters).
 * @param string $flags   Flag letters drawn from g/i/m/s/u/y.
 * @param string $testStr The text to search.
 *
 * @return array{matches: list<array{match:string,index:int,groups:list<?string>}>, error: ?string}
 */
function run_regex(string $pattern, string $flags, string $testStr): array
{
    if ($pattern === '') {
        return ['matches' => [], 'error' => null];
    }

    // PCRE modifiers from the JS flag set. g is handled by the function choice
    // below, not as a modifier; y has no PCRE equivalent.
    $modifiers = '';
    foreach (['i', 'm', 's', 'u'] as $f) {
        if (str_contains($flags, $f)) {
            $modifiers .= $f;
        }
    }
    $global = str_contains($flags, 'g');

    // `~` delimiter, escaped inside the pattern so the user's source is
    // preserved verbatim; `~` is rare in regexes.
    $delim = '~';
    $expr = $delim . str_replace($delim, '\\' . $delim, $pattern) . $delim . $modifiers;

    // OFFSET_CAPTURE gives each entry a byte offset; UNMATCHED_AS_NULL keeps
    // optional groups present as [null, -1] instead of omitting them.
    $combine = PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL;
    if ($global) {
        $count = @preg_match_all($expr, $testStr, $sets, PREG_SET_ORDER | $combine);
    } else {
        $count = @preg_match($expr, $testStr, $one, $combine);
        $sets = $count ? [$one] : [];
    }

    if ($count === false) {
        return ['matches' => [], 'error' => preg_last_error_msg()];
    }

    $out = [];
    foreach ($sets as $set) {
        [$full, $index] = $set[0];
        $groups = [];
        for ($i = 1, $n = count($set); $i < $n; $i++) {
            $groups[] = $set[$i][0]; // null when the group did not participate
        }
        $out[] = ['match' => $full, 'index' => $index, 'groups' => $groups];
    }
    return ['matches' => $out, 'error' => null];
}

/**
 * Render a result as the tool's UI does:
 * `N. "match" @ index → [g1, g2]`, or `(none)` when empty.
 */
function format_matches(array $result): string
{
    if ($result['error'] !== null) {
        return $result['error'];
    }
    if (!$result['matches']) {
        return '(none)';
    }
    $lines = [];
    foreach ($result['matches'] as $i => $m) {
        $line = ($i + 1) . '. "' . $m['match'] . '" @ ' . $m['index'];
        if ($m['groups']) {
            $gs = array_map(fn ($g) => $g ?? '', $m['groups']);
            $line .= ' → [' . implode(', ', $gs) . ']';
        }
        $lines[] = $line;
    }
    return implode("\n", $lines);
}

// CLI demo: `php regex.php`
echo format_matches(run_regex(
    '\b(\w+)@(\w+)\.(\w+)\b',
    'gi',
    "mail@x.com nope bob@test.org"
)), PHP_EOL;

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 →