php-regex / regex-optimizer
Rewrites regex patterns into shorter equivalents and modernizes old syntax; equivalence can be proven, opt-in, by regex-automata.
Fund package maintenance!
Requires
- php: >=8.2
- php-regex/regex-automata: ^2.0
- php-regex/regex-parser: ^2.0
- php-regex/regex-redos: ^2.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-05 22:21:46 UTC
README
PHPRegex Optimizer
Rewrites regex patterns into shorter equivalents and modernizes old syntax; equivalence can be proven, opt-in, by regex-automata.
Features
[0-9]becomes\d,[a-zA-Z0-9_]becomes\w; character class members are sorted, merged and deduplicated- Repeated items collapse into a quantifier,
/aaaa/becoming/a{4}/, frommin_quantifier_countrepetitions on - Alternatives sharing a prefix share it,
/abc|abd/becoming/ab(?:c|d)/— opt-in - A quantifier becomes possessive where nothing after it can take back what it matched,
/[0-9]+;/becoming/\d++;/— opt-in - Each rewrite can be proven by the automata engine to write the same
$matches, or at least to match the same strings, before it is offered — opt-in RedosRepairerrepairs a pattern open to catastrophic backtracking,/href="([^"]+)*"/becoming/href="([^"]*)"/, each repair proven linear and, when the automata can tell, proven to match the same subjects- A
Modernizervisitor cleans legacy syntax: unneeded escapes dropped, redundant non-capturing groups unwrapped - A pattern that cannot be improved comes back unchanged, with an empty change log
Installation
composer require php-regex/regex-optimizer
Requires PHP 8.2+. MIT licensed. php-regex/regex-parser, and the php-regex/regex-automata
engine the equivalence proof runs on, are installed with it. This package is part of
PHPRegex, released with its siblings under one version number.
Configuration
optimize() takes an OptimizerOptions value, or an array it reads. Array keys
are the snake_case names below; regex.json and the PHPStan parameters spell
the same options in camelCase (OptimizerOptions::fromCamelCaseArray()).
| Option | Type | Default | What it does |
|---|---|---|---|
digits |
bool | true |
[0-9] becomes \d |
word |
bool | true |
[a-zA-Z0-9_] becomes \w |
ranges |
bool | true |
runs of consecutive characters become ranges |
canonicalize_char_classes |
bool | true |
members of a class are sorted and merged |
possessive |
bool | false |
a quantifier becomes possessive where nothing after it can take the match back |
factorize |
bool | false |
alternatives sharing a prefix share it |
min_quantifier_count |
int | 4 |
how many repetitions of an item become a quantifier, at least 2 |
verify_with_automata |
bool | false |
a rewrite is only offered when the automata prove it matches the same strings |
possessive and factorize are off by default: a backreference can make a
possessive quantifier change what the pattern matches, and factorization makes
an /x pattern harder to read. verify_with_automata is off by default too:
the proof builds automata for both patterns, which costs time on large ones,
and a rewrite it disproves is withdrawn, the original pattern returned.
An unknown key or a value of the wrong type throws InvalidRegexOptionException. The 1.x names
autoPossessify and allowAlternationFactorization are possessive and factorize since 2.0.
Usage
use PHPRegex\Optimizer\Optimizer; use PHPRegex\Parser\RegexParser; $optimizer = new Optimizer(RegexParser::create()); $result = $optimizer->optimize('/[0-9][0-9][0-9][0-9]/'); echo $result->optimized; // '/\d{4}/' echo $result->changes[0]; // 'Optimized pattern.'
Each rewrite is opt-out: turn off the ones you do not want, keep the rest.
$result = $optimizer->optimize('/[abc][0-9]/', ['digits' => false]); echo $result->optimized; // '/[a-c][0-9]/'
The same options as a value object, turning on the two off-by-default rewrites:
use PHPRegex\Optimizer\OptimizerOptions; $options = new OptimizerOptions(possessive: true, factorize: true); echo $optimizer->optimize('/[0-9]+;/', $options)->optimized; // '/\d++;/' echo $optimizer->optimize('/abc|abd/', $options)->optimized; // '/ab(?:c|d)/'
The instance keeps the solver its automata checks ask, with a DFA cache: a
later optimize() on the same instance reuses the automata an earlier one
built. Keep one instance for a batch of patterns rather than building one per
call; the cache it stores them in is the constructor's optional second
argument, any DfaCacheInterface.
To have every rewrite proven before it is offered:
$result = $optimizer->optimize('/[0-9][0-9][0-9][0-9]/', ['verify_with_automata' => true]); echo $result->optimized; // '/\d{4}/'
The Modernizer visitor rewrites old syntax on the AST; render its result with
the parser's printer:
use PHPRegex\Optimizer\Modernizer; use PHPRegex\Parser\Printer\PatternPrinter; use PHPRegex\Parser\RegexParser; $parser = RegexParser::create(); $modernized = $parser->parse('/a\-(?:[0-9])/')->accept(new Modernizer()); echo $modernized->accept(new PatternPrinter()); // '/a-\d/'
Documentation
- API reference —
optimize()in full: both option spellings andOptimizationResult - Correctness contracts — what the optimizer guarantees, and when it declines a rewrite
- Backward compatibility promise — the public surface of this package and what stays stable within 2.x
- Feature support matrix — which constructs each analyzer, the optimizer included, handles
Resources
- The batteries-included facade: regex-toolkit
- The equivalence engine: regex-automata
- Documentation
- Changelog
- Report issues and send pull requests in the main PHPRegex repository
Sponsors
If PHPRegex saves you time, consider sponsoring its maintenance.
License
MIT. See LICENSE.