PHPackages                             dseguy/regex-class - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. dseguy/regex-class

ActivePhp-ext

dseguy/regex-class
==================

Regexes as classes for PHP.

10C

Since Jul 23Pushed todayCompare

[ Source](https://github.com/dseguy/regexClass)[ Packagist](https://packagist.org/packages/dseguy/regex-class)[ RSS](/packages/dseguy-regex-class/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

regex — PHP OOP Regex Extension
===============================

[](#regex--php-oop-regex-extension)

[![License: PHP-3.01](https://camo.githubusercontent.com/2d0c6e79d68e3ecde571be7a99c822e88c8379ff247dc40dddea7cad6872b8fb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d5048502d2d332e30312d677265656e2e737667)](http://www.php.net/license/3_01.txt)[![PHP](https://camo.githubusercontent.com/966d7d5cbe6b0e61883bab846699a232b0005d5ee4aae3674fbb17ad36a1aeb2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e352532422d3838393242462e737667)](https://www.php.net/)[![PCRE2](https://camo.githubusercontent.com/c9b9d41e78cac70f7cd53d765111a70430c9f4eb3217f7453a99691767d462ef/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50435245322d25453225383925413525323031302e33302d626c75652e737667)](https://www.pcre.org/)

A PHP extension that exposes PCRE2 through an object-oriented API — no more `&$matches` reference parameters, `false`-on-failure return values, or global `preg_last_error()` state. Compile a pattern once into an immutable `Regex` object, then match, replace, split, and grep against it with typed, exception-driven results.

Targets PHP 8.5+ (PHP 8.6 feature target), built on PCRE2 ≥ 10.30. Originated as [a PHP RFC](rfc_oop_regex.md) proposing this API for `ext/pcre`; this repo is the reference implementation.

Why
---

[](#why)

The `preg_*` functions have been part of PHP since PHP 3 and show their age:

- Return types are a moving target: `int|false`, `string|string[]|null`, `false` — the exact shape depends on flags and outcome.
- Match data comes back through a `&$matches` out-parameter, not a return value.
- Errors surface as a `false`/`null` return plus a global `preg_last_error()` you have to remember to check.
- Checking whether a pattern even compiles means suppressing an `E_WARNING`.
- Interpolating a literal into a pattern requires a separate `preg_quote()` call.

This extension replaces that surface with typed objects (`RegexMatch`, `RegexMatchGroup`, `RegexSplitResult`) and exceptions (`InvalidRegexException`, `RegexExecutionException`) instead of sentinel return values and global state. See [Mapping from `preg_*`](#mapping-from-preg_) below for the direct translation.

Installing
----------

[](#installing)

This extension isn't packaged yet — build it from source (see [Building from source](#building-from-source) below). PIE/PECL packaging is tracked as future work; contributions welcome.

Classes
-------

[](#classes)

ClassRole`Regex`Immutable compiled pattern. Factory: `Regex::of($pattern)` or `Regex::compile($pattern)`.`RegexMatch`Single match result. Implements `ArrayAccess` (group access via `$m[0]`, `$m['name']`).`RegexMatchGroup`One capture group inside a `RegexMatch`.`RegexMatchIterator`Lazy iterator over all matches. Returned by `matchEach()`.`RegexSplitResult`Result of `split()`. Provides `parts()` and `separators()`.`RegexException`Base exception (extends `RuntimeException`).`InvalidRegexException`Thrown on compilation failure.`RegexExecutionException`Thrown on runtime PCRE2 error.Quick start
-----------

[](#quick-start)

```
// Compile once, use many times
$r = Regex::of('/^(?P\d{4})-(?P\d{2})-(?P\d{2})$/');

// Single match — returns RegexMatch or null
$m = $r->match('2026-06-01');
echo $m->value;                  // "2026-06-01"
echo $m->group('year')->value;   // "2006"
echo $m->group(1)->offset;       // byte offset of group 1

// All matches — returns RegexMatch[]
$matches = Regex::of('/\d+/')->matchAll('a1 b22 c333');
foreach ($matches as $m) {
    echo $m->value . ' at ' . $m->offset . "\n";
}

// Test (true/false)
var_dump(Regex::of('/^\d+$/')->test('42'));          // bool(true)
var_dump(Regex::of('/^\d+$/')->testStrict('42 '));   // bool(false)

// Replace
echo Regex::of('/\b\w/')->replace('hello world', strtoupper($m->value));
// Callback variant
echo Regex::of('/\b\w/')->replaceWith('hello world', fn($m) => strtoupper($m->value));

// Split
$res = Regex::of('/,\s*/')->split('a, b, c');
var_dump($res->parts());       // ["a", "b", "c"]
var_dump($res->separators());  // [RegexMatch(","), RegexMatch(", ")]

// Grep
$urls = ['http://a.com', 'ftp://b.org', 'https://c.net'];
var_dump(Regex::of('/^https?:/i')->grep($urls));         // http + https
var_dump(Regex::of('/^https?:/i')->grepInvert($urls));   // ftp only
```

Pattern format
--------------

[](#pattern-format)

Patterns use the same `/inner/flags` delimiter syntax as PHP's `preg_*` functions.

Bracket-style delimiters are supported: `{...}`, `[...]`, `(...)`, ``.

Any non-alphanumeric, non-backslash character may be used as a delimiter.

### Supported flags

[](#supported-flags)

FlagPCRE2 optionMeaning`i``PCRE2_CASELESS`Case-insensitive`m``PCRE2_MULTILINE``^`/`$` match per line`s``PCRE2_DOTALL``.` matches newline`x``PCRE2_EXTENDED`Ignore unescaped whitespace`u``PCRE2_UTF`Unicode (UTF-8) mode`n``PCRE2_NO_AUTO_CAPTURE`Suppress unnamed captures`U``PCRE2_UNGREEDY`Make quantifiers ungreedy`D``PCRE2_DOLLAR_ENDONLY``$` only at true end`A``PCRE2_ANCHORED`Anchor at start`J``PCRE2_DUPNAMES`Allow duplicate group namesAPI reference
-------------

[](#api-reference)

### `Regex`

[](#regex)

```
Regex::of(string $pattern): Regex          // Compile; throw InvalidRegexException on failure
Regex::compile(string $pattern): Regex     // Alias of of()
Regex::withLiteral(string $literal): Regex // Append literal (auto-escaped) to inner pattern
Regex::withPattern(string $fragment): Regex // Append raw regex fragment to inner pattern

match(string $subject, int $offset = 0): ?RegexMatch
matchAll(string $subject, int $offset = 0): RegexMatch[]
matchEach(string $subject): RegexMatchIterator
test(string $subject): bool                // true if any match exists
testStrict(string $subject): bool          // true only if the entire string matches

replace(string $subject, string $replacement): string       // Replace all; $1/${1} backrefs
replaceFirst(string $subject, string $replacement): string  // Replace first only
replaceWith(string $subject, callable $fn): string          // Callback receives RegexMatch
replaceFirstWith(string $subject, callable $fn): string

split(string $subject, int $limit = -1): RegexSplitResult

grep(array $subjects): array          // Preserves original keys
grepInvert(array $subjects): array

getPattern(): string                   // Full pattern string including delimiters+flags
getCaptureGroupCount(): int            // Count of all capture groups
getNamedGroups(): array                // Group names in order of appearance
__toString(): string                   // Same as getPattern()

```

### `RegexMatch`

[](#regexmatch)

```
readonly string $value       // Full matched text
readonly int    $offset      // Byte offset in subject
readonly int    $charOffset  // UTF-8 codepoint offset

group(int|string $index): ?RegexMatchGroup
namedGroups(): array   // name => value for all named groups

// ArrayAccess: $m[0] = $m->value, $m[1] = group(1)->value, $m['name'] = group('name')->value

```

### `RegexMatchGroup`

[](#regexmatchgroup)

```
readonly string $value
readonly int    $offset
readonly int    $charOffset
readonly string $name    // '' for unnamed groups
readonly int    $index   // 1-based capture group number

```

### `RegexSplitResult`

[](#regexsplitresult)

```
parts(): string[]                // Text between separators (count = separators + 1)
separators(): RegexMatch[]       // Matched separator objects (with offset info)

// Countable: count() = number of parts
// IteratorAggregate: foreach iterates parts

```

Building from source
--------------------

[](#building-from-source)

```
cd regex/
phpize
./configure --enable-regex
make -j$(nproc)
# Test
php run-tests.php -n -d extension=modules/regex.so -q tests/
php run-tests.php -n -d extension=modules/regex.so -q tests/pcre/
```

Requires PCRE2 development headers (`libpcre2-dev` / `pcre2-devel`). On macOS with Homebrew: `brew install pcre2`.

Tests
-----

[](#tests)

- `tests/` — 17 self-contained unit tests covering the full API surface
- `tests/pcre/` — 17 tests ported from PHP's `ext/pcre/tests/`, demonstrating API equivalence with `preg_*` functions

Run all 34 tests:

```
php run-tests.php -n -d extension=modules/regex.so -q tests/ tests/pcre/
```

Mapping from `preg_*`
---------------------

[](#mapping-from-preg_)

Old APINew API`preg_match($p, $s, $m)``$m = Regex::of($p)->match($s)``preg_match($p, $s, $m, PREG_OFFSET_CAPTURE)`Same; `$m->offset`, `$m->group(n)->offset` always available`preg_match($p, $s, $m, 0, $offset)``$m = Regex::of($p)->match($s, $offset)``preg_match_all($p, $s, $m)``$matches = Regex::of($p)->matchAll($s)``preg_replace($p, $r, $s)``Regex::of($p)->replace($s, $r)` (use `${n}` for backrefs)`preg_replace_callback($p, $fn, $s)``Regex::of($p)->replaceWith($s, $fn)` (callback gets `RegexMatch`, not array)`preg_split($p, $s)``Regex::of($p)->split($s)->parts()``preg_split($p, $s, -1, PREG_SPLIT_NO_EMPTY)``array_filter(Regex::of($p)->split($s)->parts(), fn($p) => $p !== '')``preg_split($p, $s, -1, PREG_SPLIT_DELIM_CAPTURE)`Interleave `->parts()` and `->separators()` manually`preg_grep($p, $arr)``Regex::of($p)->grep($arr)``preg_grep($p, $arr, PREG_GREP_INVERT)``Regex::of($p)->grepInvert($arr)``preg_quote($s, $delim)``Regex::withLiteral($s)` (auto-uses the pattern's delimiter)Contributing
------------

[](#contributing)

Bug reports and pull requests are welcome via the [issue tracker](https://github.com/dseguy/regex/issues).

- Add or update a `.phpt` test in `tests/` (or `tests/pcre/` for `preg_*` equivalence cases) for any behavior change.
- Run the full suite (`php run-tests.php -n -d extension=modules/regex.so -q tests/ tests/pcre/`) and confirm zero compiler warnings from `make` before opening a PR.
- Keep new public API consistent with the existing style: immutable value objects, typed properties, exceptions instead of sentinel returns.
- Discussion of the underlying language proposal belongs on the [PHP RFC](rfc_oop_regex.md) / , not this repo's tracker.

License
-------

[](#license)

PHP License 3.01, matching `ext/pcre` and the rest of PHP core — see [php.net/license/3\_01.txt](http://www.php.net/license/3_01.txt).

###  Health Score

21

—

LowBetter than 17% of packages

Maintenance65

Regular maintenance activity

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/15652256?v=4)[Exakat](/maintainers/exakat)[@exakat](https://github.com/exakat)

---

Top Contributors

[![christopheexakat](https://avatars.githubusercontent.com/u/33156062?v=4)](https://github.com/christopheexakat "christopheexakat (4 commits)")

### Embed Badge

![Health badge](/badges/dseguy-regex-class/health.svg)

```
[![Health](https://phpackages.com/badges/dseguy-regex-class/health.svg)](https://phpackages.com/packages/dseguy-regex-class)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
