yoeunes / regex-parser
Static analysis for PHP regular expressions: parse, validate, explain, prove ReDoS safety, lint, optimize and compare patterns, with PHPStan, Psalm, Rector, Symfony and Laravel integrations.
Fund package maintenance!
Requires
- php: >=8.2
Requires (Dev)
None
Suggests
- phpstan/extension-installer: To automatically enable the PHPStan rule for regex validation.
- phpstan/phpstan: To run static analysis and detect invalid regex patterns.
- psr/cache: To share AST cache via PSR-6 pools.
- psr/simple-cache: To share AST cache via PSR-16 caches.
Provides
None
Conflicts
None
Replaces
None
- 2.x-dev
- 1.x-dev
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.10
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- v1.0.0-RC1
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.0
- v0.26.0
- v0.25.2
- v0.25.1
- v0.25.0
- v0.24.0
- v0.23.0
- v0.22.0
- v0.21.0
- v0.20.0
- v0.19.0
- v0.18.0
- v0.17.4
- v0.17.3
- v0.17.2
- v0.17.1
- v0.17.0
- v0.16.0
- v0.15.1
- v0.15.0
- v0.14.13
- v0.14.12
- v0.14.11
- v0.14.10
- v0.14.9
- v0.14.8
- v0.14.7
- v0.14.6
- v0.14.5
- v0.14.4
- v0.14.3
- v0.14.2
- v0.14.1
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.0
- v0.9.0
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.1
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.10
- v0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
This package is auto-updated.
Last update: 2026-10-13 12:43:23 UTC
README
PHPRegex: Static Analysis, Linter & Logic Solver
PHPRegex is a PHP 8.2+ library that treats regular expressions as code.
Unlike simple wrappers around preg_match, PHPRegex implements a complete compiler pipeline (Lexer → Parser → AST) and an Automata-based Logic Solver (normalized form → NFA → DFA).
This architecture allows for advanced static analysis:
- Linting: Detect redundancy, useless flags, and common mistakes.
- Safety: Prove a pattern safe from catastrophic backtracking (ReDoS), or hand you the input that triggers it.
- Logic: Compare patterns via NFA/DFA (Intersection, Equivalence, Subset) for the regular subset it supports.
Built for robust regex tooling in PHP projects.
The documentation lives at php-regex.com: a regex tutorial, a guide for every integration, and the reference pages behind each verdict the library prints.
⚠️ How to read the verdicts. ReDoS analysis proves safety for the subset of PCRE it models, one match attempt at a time; outside that subset, structural heuristics decide — and the verdict says so. The parser follows PCRE closely (see the conformance page) without covering every edge of the engine. Treat findings as leads to investigate, not as a guarantee.
2.0 is coming
The focused php-regex/* packages are published as 2.x-dev, and the stable
2.0 tag is close. Install the package for the surface you need and shape the
release with us:
composer config minimum-stability dev
composer config prefer-stable true
composer require --dev php-regex/regex-cli:2.x-dev
Try it, break it, and tell us what your project needs — feedback and wishes go to the discussions.
If you are new to regex, start with the Regex Tutorial. If you want a short overview, see the Quick Start Guide.
Getting started
# Install the CLI (the API toolkit comes with it) composer config minimum-stability dev composer config prefer-stable true composer require --dev php-regex/regex-cli:2.x-dev # Try the command-line tool vendor/bin/regex explain '/\d{4}-\d{2}-\d{2}/'
The CLI requires the mbstring and tokenizer extensions; the standalone
parser package needs only mbstring.
PHPRegex is a family of packages, developed together in php-regex/php-regex and released with one version number. Install the one you need; the package guide helps you choose:
| package | what it holds |
|---|---|
php-regex/regex-toolkit |
the Regex facade: every library below in one call |
php-regex/regex-parser |
lexer, parser, immutable AST, validator, PCRE2 release targeting, AST cache |
php-regex/regex-explain |
explanations, highlighting, ASCII tree, Mermaid and railroad diagrams |
php-regex/regex-optimizer |
shorter equivalent patterns, modernized syntax |
php-regex/regex-generator |
sample strings and test cases |
php-regex/regex-automata |
language equivalence, intersection and subset |
php-regex/regex-redos |
catastrophic backtracking (ReDoS) analysis |
php-regex/regex-transpiler |
JavaScript, HTML pattern and Python targets |
php-regex/regex-linter |
lint rules and pattern extraction from PHP sources |
php-regex/regex-cli |
the regex command |
php-regex/regex-language-server |
diagnostics in any LSP editor |
php-regex/regex-phpstan |
the PHPStan extension |
php-regex/regex-psalm |
the Psalm plugin: $matches typed from the pattern, invalid patterns reported |
php-regex/regex-rector |
Rector rules: preg_* calls to the string functions they prove equal to |
php-regex/regex-symfony |
the Symfony bundle |
php-regex/regex-laravel |
the Laravel integration |
Coming from 1.x (yoeunes/regex-parser)? See UPGRADE-2.0.md.
What PHPRegex provides
- 🏗️ Deep Parsing: Parse
/pattern/flagsinto a structured, typed AST. - 🧠 Logic Solver: Compare two regexes using NFA/DFA transformation (intersection, equivalence, subset). Works for patterns in the regular subset it supports — every character set asked from the running PCRE2 — and refuses the rest with the reason named.
- 🛡️ ReDoS Analysis: Prove the backtracking cost of a pattern — linear, polynomial or exponential — with the attack input when it is vulnerable, and replay that attack on the running PCRE. Outside the modelled subset, structural heuristics decide and say so.
- 🧹 Linter: Detect useless flags, redundant groups, and common mistakes via the CLI.
- 📖 Explanation: Explain patterns in plain English.
- 🔧 Visitor API: A flexible API for building custom regex tooling.
Philosophy & Accuracy
PHPRegex separates what it can guarantee from what is heuristic:
- Guaranteed: parsing and AST structure for the targeted PHP/PCRE version.
- Measured: syntax validation and error offsets follow PHP's engine; the PCRE2 conformance page publishes how closely, case by case.
- Proven: a ReDoS verdict marked
(proven)holds for one match attempt on a model of PCRE's backtracking;safe (proven)means no input makes that attempt backtrack beyond a linear number of steps. The ReDoS guide lists its limits. - Heuristic: patterns outside that model (backreferences, conditionals, recursion, …) are judged by structural rules, marked
(heuristic); treat those as potential risk unless confirmed. - Context matters: PCRE version, JIT, and backtrack/recursion limits change practical impact.
Tested against the real engine
Round-trip correctness (compile(parse(x)) behaves like the original) is
verified in CI by differential tests that compare PHPRegex's output against
PHP's native preg_match():
- A fixture of 212 PCRE patterns is checked for validity, match result,
and captured groups (
OfficialPcreComplianceTest). - Additional behavioral tests cover named groups, lookarounds, conditionals,
atomic groups, and other features (
BehavioralComplianceTest,AdvancedFeaturesComplianceTest). - PCRE2's own official test suite (10.48, pinned) is replayed at compile level under PHP's compile options: the compile verdict agrees on more than 4,100 of the roughly 4,400 extractable cases, and fewer than 100 patterns that PHP refuses to compile are accepted. The exact counts, the skipped cases and the fix plan are on the PCRE2 conformance page.
These tests compare against a limited set of subjects, so they catch clear regressions but are not a formal proof of full PCRE equivalence. There may be edge cases that the test suite does not yet cover.
Separately, the linter and ReDoS analyzer have been run over a corpus of
over 1,700 unique patterns collected from around 200 real-world PHP
projects (Symfony,
Laravel, Composer, PHPUnit, …). The lint results are in var/log/corpus.log.
This corpus run is not part of the automated CI differential test — it is
a snapshot used to validate that the lint and ReDoS rules produce sensible
output on real code.
The corpus checkouts themselves are not committed. corpus.json, at the root
next to composer.json, lists every repository with its URL, branch and the
commit it was pinned to, much like a lock file. bin/corpus manages the
checkouts, composer-style:
php bin/corpus install # rebuild corpus/ exactly at the pinned commits php bin/corpus update # prune, clone what is missing, then pull everything, # and write the new commits back to corpus.json php bin/corpus update --no-prune # keep checkouts that are no longer listed php bin/corpus update --add https://github.com/vendor/repo.git [--as path] [--branch main] php bin/corpus update --write-manifest # rewrite corpus.json from what is on disk
Every corpus.json key is a vendor/name path — a repository added without
--as is checked out under the vendor/name derived from its URL, and a URL
that offers no vendor segment is refused rather than guessed at.
install never writes to corpus.json: delete corpus/ at any time and one
command rebuilds it, repository by repository, at the exact commits the
manifest pins. update is the only command that moves the pins. A checkout
with local changes is never reset or removed unless --force is given, and a
repository cloned by hand is removed on the next run unless it is added with
--add or listed with --write-manifest first.
Regenerate var/log/corpus.log after updating the corpus, from a terminal:
php bin/regex lint corpus/ --php-version=runtime --output=var/log/corpus.log
--php-version=runtime judges the corpus for the PHP running the command and
the PCRE2 it links; without it, the command would judge for the lowest PHP this
repository's composer.json allows.
Piping the command instead of running it in a terminal renders the severity badges without their padding, which reformats every severity line of the tracked file.
How to report a vulnerability responsibly
If you believe a pattern is exploitable:
- Run confirmed mode: it replays the attack on your PCRE and prints the input length that makes
preg_match()fail. - Include the pattern, input lengths, timings, JIT setting, and PCRE limits.
- Verify impact in the real code path before filing a security issue.
See SECURITY.md for reporting channels.
Safer rewrites (verify behavior)
These techniques reduce backtracking but can change matching behavior. Always validate with tests.
/(a+)+$/ -> /a+$/ (semantics often preserved, but verify captures)
/(a+)+$/ -> /a++$/ (possessive, no backtracking)
/(a|aa)+/ -> /a+/ (only if alternation is redundant)
/(a|aa)+/ -> /(?>a|aa)+/ (atomic, avoids backtracking)
How it works
Regex::parse()splits the literal into pattern and flags.- The lexer produces a token stream.
- The parser builds an AST (
RegexNode). - Visitors walk the AST to validate, explain, analyze, or transform.
For the full architecture, see the architecture overview.
CLI quick tour
# Parse and validate a pattern vendor/bin/regex parse '/^hello world$/' # Get plain English explanation vendor/bin/regex explain '/\d{4}-\d{2}-\d{2}/' # Check for ReDoS: a proven verdict, and the attack when vulnerable vendor/bin/regex analyze '/(a+)+$/' # Colorize pattern for better readability vendor/bin/regex highlight '/\d+/' # Lint your entire codebase vendor/bin/regex lint src/
PHP API at a glance
use PHPRegex\Toolkit\Regex; use PHPRegex\Redos\RedosMode; $regex = Regex::create([ 'runtime_pcre_validation' => true, ]); // Parse a pattern into AST $ast = $regex->parse('/^hello world$/i'); // Validate pattern syntax and semantics $result = $regex->validate('/(?<=test)foo/'); if (!$result->isValid) { echo $result->error; } // Check for ReDoS (theoretical by default) $analysis = $regex->redos('/(a+)+$/'); echo $analysis->severity->value; // 'critical' echo $analysis->headline(); // 'Exponential backtracking (proven)' echo $analysis->witness->render(); // '"a" x n . "!"': the input that triggers it // Optional: replay the attack on the running PCRE $confirmed = $regex->redos('/(a+)+$/', mode: RedosMode::Confirmed); echo $confirmed->isConfirmed() ? 'confirmed' : 'theoretical'; // 'confirmed' // Get human-readable explanation echo $regex->explain('/\d{4}-\d{2}-\d{2}/');
Integrations
PHPRegex integrates with common PHP tooling:
- Symfony bundle: the Symfony guide
- Laravel: the Laravel guide
- Language server: the language server guide
- PHPStan: enabled by extension-installer, or through
vendor/php-regex/regex-phpstan/extension.neon. It reports a pattern your target PHP refuses while the PHP running PHPStan compiles it; lint rules and ReDoS analysis come withrules.neon. See the PHPStan guide - Psalm:
vendor/bin/psalm-plugin enable php-regex/regex-psalmtypes$matchesfrom the pattern wherepreg_match()returned 1 and afterpreg_match_all(), and reports the patterns your target PHP refuses asInvalidRegexPattern. See the Psalm guide - Rector:
RegexSetList::STRING_FUNCTIONSrewritespreg_match('/^https:/', $url)into\str_starts_with($url, 'https:'),preg_replace()intostr_replace()andpreg_split()intoexplode(), only where the automata prove them equal. See the Rector guide - GitHub Actions:
vendor/bin/regex lintin your CI pipeline - Demo applications: complete Symfony and Laravel apps under
demo/, wired to this repository through a Composer path repository and run in CI; standalone scripts cover every library surface in the same folder
Performance
PHPRegex measures its own time and memory with PHPBench, one group per subsystem (lexer, parser, validate, lint, redos, redos-corpus, automata, optimizer, capture-shape, formatter).
- Run one group:
composer bench -- --group=automata - Run the whole suite:
composer bench(long;redos-corpus, one variant per corpus pattern, is the longest group)
See the maintainers guide for the inputs and how to add a case.
Documentation
Read the docs online at https://php-regex.com.
Start here:
Guides:
Key references:
- Architecture
- Concepts: AST, PCRE versions, ReDoS, visitors
- API Reference
- Diagnostics
- Feature Support Matrix
- PCRE2 Conformance
- FAQ & Glossary
- Backward Compatibility Promise
Contributing
Contributions are welcome! See CONTRIBUTING.md to get started.
# Set up development environment composer install # Run tests composer phpunit # Check code style composer phpcs # Run static analysis composer phpstan
Sponsors
If PHPRegex saves you time, consider sponsoring its maintenance.
Support
If you run into issues or have questions, please open an issue on GitHub: https://github.com/php-regex/php-regex/issues.
License
Released under the MIT License.
