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 →