Backward Compatibility Promise
PHPRegex follows semantic versioning. Every package of
the family (php-regex/regex-*) is released together, with one version number,
and each requires its siblings at exactly its own version (Composer’s
self.version): you never compute a compatibility matrix between them. Install
and update them together, with composer update 'php-regex/*'; Composer refuses
a partial update.
This page says what a minor release (2.1, 2.2, …) and a patch release (2.0.1, …) may change, and what only the next major release (3.0) may change.
What the promise covers
Every class, interface, trait and enum not marked @internal, with its
public methods, public properties and public constants. A class marked
@internal is not part of the API: it may change in any release.
Some @internal classes and constructors are shared between packages. They
may change in any release, since siblings always run at the same version. Code
outside this project must not use them.
The PHPRegex\Parser\Hir classes of regex-parser are @internal like the
rest: regex-automata and regex-redos, which build on them, move with them.
Result objects are built by the library. Their class, methods and properties
are public, but their constructors are @internal: read them, never build
them. A minor release may add a constructor parameter, required or not. If a
test of your own code needs one, get it from the producer on a known pattern,
for example LanguageSolver::equivalent() for an EquivalenceResult or
RedosAnalyzer::analyze() for a RedosAnalysis. The result objects are:
regex-parser:TolerantParseResult,Validation\ValidationResult,Analysis\GroupNumbering,Analysis\LiteralExtractionResult,Analysis\CaptureShape,Analysis\CaptureGroupShape,Engine\PcreMatch,Engine\PcreError,Analysis\PatternInfo,Validation\PatternCompatibility,Validation\TargetVerdict;regex-optimizer:OptimizationResult,RedosRepair;regex-automata:Solver\EquivalenceResult,Solver\IntersectionResult,Solver\SubsetResult,Solver\MatchEquivalenceResult,Language,TrivialMatch;regex-redos:RedosAnalysis,Finding,Hotspot,RedosWitness,RedosSearchCost,Confirmation,ConfirmationSample;regex-transpiler:TranspileResult;regex-linter:Rule\RuleViolation;regex-toolkit:AnalysisReport.
@internal still means removable. The 2.0.0 release removed Automata\Alphabet\CharSet
(use Parser\Hir\CharSet) and the AST-walking transformer behind the solver
(now Transform\HirToNfaTransformer); UPGRADE-2.0.md
maps every removed name.
In short, the public surface is:
| package | public |
|---|---|
regex-parser |
RegexParser, Attribute\RegexPattern, ParserOptions, PcreTarget, PcreFeature, ErrorCode, DelimitedPattern, TolerantParseResult, NodeVisitorInterface, AbstractNodeVisitor, AbstractTraversingVisitor, NodeWalker, NodeFinder, TraversalAction, Token\Token, Token\TokenStream, Token\TokenType, Validation\ValidationResult, Validation\ValidationErrorCategory, Printer\PatternPrinter, Printer\NodeDumper, Analysis\ComplexityScorer, Analysis\GroupNumbering, Analysis\GroupNumberingCollector, Analysis\LengthRangeCalculator, Analysis\LiteralExtractor, Analysis\LiteralExtractionResult, Analysis\LiteralSet, Analysis\MetricsCollector, Analysis\CaptureShapeAnalyzer, Analysis\CaptureShape, Analysis\CaptureGroupShape, Analysis\Participation, Analysis\RequiredLiteralAnalyzer, Analysis\PatternInfoAnalyzer, Analysis\PatternInfo, NewlineConvention, BsrConvention, Validation\CompatibilityChecker, Validation\PatternCompatibility, Validation\TargetVerdict, Cache\CacheInterface, Cache\RemovableCacheInterface, Cache\ArrayCache, Cache\NullCache, Cache\FilesystemCache, Cache\PsrCacheAdapter, Cache\PsrSimpleCacheAdapter, Engine\PcreEngine, Engine\PcreError, Engine\PcreLimits, Engine\PcreMatch, Exception\ExceptionInterface, Exception\RegexException, Exception\LexerException, Exception\ParserException, Exception\SyntaxErrorException, Exception\SemanticErrorException, Exception\RecursionLimitException, Exception\ResourceLimitException, Exception\InvalidRegexOptionException, Exception\CacheException, Node\* |
regex-explain |
TextExplainer, HtmlExplainer, AsciiTreeRenderer, MermaidRenderer, RailroadSvgRenderer, Highlighter\ConsoleHighlighter, Highlighter\HtmlHighlighter |
regex-optimizer |
Optimizer, OptimizerOptions, OptimizationResult, Modernizer, RedosRepairer, RedosRepair |
regex-generator |
SampleGenerator, TestCaseGenerator, SampleGenerationException |
regex-automata |
LanguageSolver, Options\SolverOptions, Options\MatchMode, Determinization\DeterminizationAlgorithm, Minimization\MinimizationAlgorithm, Solver\EquivalenceResult, Solver\IntersectionResult, Solver\SubsetResult, Solver\MatchEquivalenceResult, Model\Dfa, Model\DfaState, Solver\DfaCacheInterface, Solver\InMemoryDfaCache, Exception\ComplexityException, TrivialMatchClassifier, TrivialMatch, TrivialMatchKind, Language |
regex-redos |
RedosAnalyzer, RedosAnalysis, RedosOptions, RedosSeverity, RedosComplexity, RedosProof, RedosWitness, RedosSearchCost, RedosMode, RedosConfidence, Finding, Hotspot, Heatmap, Confirmation, ConfirmationSample, ConfirmationOptions, ConfirmationRunner, ConfirmationRunnerInterface |
regex-transpiler |
Transpiler, TranspileOptions, TranspileResult, TranspileException |
regex-linter |
PatternLinter, LintSeverity, LintException, Rule\RuleViolation |
regex-toolkit |
Regex, AnalysisReport, OutputFormat |
regex-phpstan |
RegexPatternRule, RegexPatternArgumentRule |
regex-psalm |
Plugin |
regex-rector |
PregMatchToStringComparisonRector, PregReplaceToStrReplaceRector, PregSplitToExplodeRector, EscapeLiteralBraceRector, Set\RegexSetList |
regex-symfony |
PHPRegexBundle |
regex-laravel |
PHPRegexServiceProvider, Facades\Regex |
regex-cli |
no PHP class is public |
regex-language-server |
no PHP class is public |
Each path is relative to the package namespace: Analysis\CaptureShape in
regex-parser is PHPRegex\Parser\Analysis\CaptureShape. Node\* is every
class of Node\.
In regex-parser, NodeVisitorInterface, AbstractNodeVisitor and
AbstractTraversingVisitor are the visitor base classes. NewlineConvention
and BsrConvention are the enums of Analysis\PatternInfo. Analysis\ByteCharSet,
Analysis\CharSetAnalyzer and Cache\AstSerializer are not public, nor is any
other class of those namespaces left out of the row.
In regex-automata, Determinization\DeterminizationAlgorithm and
Minimization\MinimizationAlgorithm are the two algorithm enums. The promise
covers TrivialMatchClassifier::matchedLiteral() with classify(): a minor
release may prove more patterns than the one before, and its CHANGELOG says so.
In regex-redos, the promise covers RedosAnalyzer::ANALYSIS_VERSION,
RedosAnalysis::isProvenSafe() and headline(), RedosSeverity::rank(), and
Confirmation::wasSkipped() and Confirmation::LIMITS_UNAVAILABLE.
isProvenSafe() and headline() speak of one match attempt: a pattern whose
unanchored search is quadratic stays safe (proven), and carries that cost in
RedosAnalysis::$searchCost, a RedosSearchCost (null when no witness was
found, never a proof of a linear search).
The bridges and tools carry more than their classes:
regex-phpstan: thephpRegexparameters ofextension.neon.regex-psalm: theInvalidRegexPatternissue name and the plugin’s<phpVersion>and<pcreVersion>options stay for all of 2.x. The$matchestypes follow the capture shape: a minor release may type a pattern more narrowly than the one before, which may require regenerating a Psalm baseline; its CHANGELOG says so.regex-rector: the four rules keep their names for all of 2.x and take no configuration, andRegexSetList::STRING_FUNCTIONSandRegexSetList::PCRE_UPGRADEname the sets that register them. Each rewrites only what the automata prove; a minor release may prove more calls than the one before, and its CHANGELOG says so.regex-symfony: thephp_regexconfiguration, and the commands’ names and options.regex-laravel: theRegexfacade (Facades\Regex), thephp-regexconfiguration, and the commands’ names and options.regex-cli: theregexcommand, with its commands, options, exit codes and JSON output (every key).regex-language-server: the protocol it speaks.
Using the API: call, extend, implement
Calling a public method, reading a public property, catching a public exception and type-hinting against a public class or interface are covered.
Extending and implementing are covered only where the API is meant for it:
- Extend
AbstractNodeVisitororAbstractTraversingVisitorto write a visitor. ImplementingNodeVisitorInterfacedirectly is not covered: a minor release may add a method to it for a new node, and the abstract classes provide that method for you. - Implement
CacheInterfaceorRemovableCacheInterfaceto bring your own AST cache, andDfaCacheInterfacefor your own DFA cache.
Every other public interface (NodeInterface, ConfirmationRunnerInterface, …)
is there to type against, not to implement. Lint rules, transpiler targets and
pattern sources are not extension points in 2.0: there is no public registry to
add one to yet.
What a minor release may change
- New node types, with the matching
visitX()method onNodeVisitorInterfaceand its default in the abstract visitors. - New enum cases, in
ErrorCode,TokenType,PcreFeature,GroupTypeand the others. Amatchover one of these enums needs adefaultarm. A newAnalysis\Participationcase only refinesMayBeUnset, so adefaultarm that answersMayBeUnsetstays sound. - New optional parameters: a node constructor or a method may gain a
trailing parameter with a default value. Pass arguments by position for the
ones you set, or by name. 2.0.0 adds one:
Optimizer::__construct()takes an optional trailing?DfaCacheInterface $dfaCache = null— with it, one instance reuses the DFAs its equivalence checks compiled, and without it a fresh in-memory cache is used, as before. - New classes, methods and options, and new keys in a configuration file.
- New keys and values in the JSON output: a key in any JSON document the
regexcommand prints, an optional key in its error envelope, and a value of an open enum field, such asstageorseverity. The JSON output reference lists what may change and which fields are open. - Message texts: an error or lint message may be reworded, in the console
and in JSON alike; the CHANGELOG says so. Match on the error code, the
issue_idor thestage, not on the message. - Support for a new PCRE2 release, as
PcreFeaturecases and the targets that use them. - A wider ReDoS model: a construct the structural heuristics judge today
(a backreference, a conditional, a non-atomic lookaround, …) may become
proven, and an ambiguity listed as without witness may get one, with
RedosAnalyzer::ANALYSIS_VERSIONraised. Their patterns move fromproof: heuristictoproof: proven, and their severity may move with them. The heuristic lint issues (nested quantifiers, dot-star in a quantifier, overlapping character sets) are dropped for a pattern the analysis proves linear, so they may disappear for a pattern the wider model now proves. - A narrower capture shape: the facts of
CaptureShapeAnalyzerand the stringsCaptureShape::matchShape()andCaptureShape::matchAllShape()write may become more precise, still holding every$matchesPHP writes, and either string may be written differently for the same type.CaptureShapeAnalyzer::ANALYSIS_VERSIONrises with any such change, and a patch may widen an answer to make it sound again. A PHPStan baseline that prints the type may need regenerating; the CHANGELOG says when. See Capture Shapes. - Narrower pattern info: the sound bounds of
Analysis\PatternInfo,minMatchLength,maxMatchLength,maxLookbehind,anchoredStartandanchoredEnd, may become more precise, still holding for every match PCRE2 makes: a length range may tighten and an anchor may become proven.PatternInfoAnalyzer::ANALYSIS_VERSIONrises with any such change, and a patch may widen an answer to make it sound again. The exact facts (captureCount,names,maxBackreference,usesBackslashC, the limits,newlineandbsr) equal PCRE2’s: a difference is a bug, fixed in a patch. See Pattern Info. - A wider compatibility matrix:
Validation\CompatibilityCheckerjudges every PHP version a rule of the library changes at, with every PCRE2 release it knows. Each PHP or PCRE2 release the library learns adds points, so the number of verdictsPatternCompatibility::verdicts()lists may grow, andisValidEverywhere()may turn false for a pattern a new point refuses.regex lintvalidates over the same points when it reads the PHP range ofcomposer.json, so a new point may fail a project that passed. See Pattern Info. - More PHP version boundaries:
PcreTarget::phpVersionBoundaries()lists the PHP versions at which a verdict of the library may change. It grows in a minor release, when the library learns a PHP release or a rule that changes at a PHP version, and the compatibility matrix and the lint range grow with it. - New ReDoS options: a configuration key for the analysis budget may be added; none is removed.
- What a lint rule reports: a rule may report more or fewer patterns when its analysis reads a case it missed, and the CHANGELOG names the rule. A patch only removes a false positive.
- Deprecations: anything removed in 3.0 is deprecated in a 2.x minor first, with the replacement named.
What stays for all of 2.x
- The values of
ErrorCode(regex.group.unclosed, …) and the identifiers the PHPStan extension reports (regex.invalidForTarget,regex.redos,regex.redos.search, …), with the lint issue ids (regex.lint.redos,regex.lint.redos.search, …): baselines and ignore lists keep working. - The text of the PHPStan ReDoS messages:
Exponential backtracking (ReDoS): %s,Polynomial backtracking (ReDoS): %sandPotential backtracking (ReDoS): %s, and the one of a quadratic unanchored search,Quadratic search (ReDoS): %sunderregex.redos.search, followed by the pattern. The severity, the proof and the attack are in the tip, which may change. Which patterns get an error is not frozen: when the analysis improves, an error may appear, disappear or move to another class (Exponential, Polynomial, Potential). Regenerate the baseline after an upgrade that changes the analysis; the CHANGELOG says when one does. - The configuration keys of
regex.json, of the Symfony bundle, of the Laravel config file and of the PHPStan extension, and the exit codes of every command (0 done, 1 a pattern or file problem, 2 a usage or configuration error). - The JSON output of the
regexcommand: a key keeps its name, its type and its meaning, and a success document never gains a top-levelerror(see JSON output). The ReDoS analysis’search_cost,nullor{degree, witness: {prefix, run, breaker}, replayed}, is one of them. - The severity of the lint rules: a minor never raises an existing rule to
error severity, the one that fails
regex lint, and a new rule lands at warning severity or lower. - The pattern in the machine reports: the JSON, Checkstyle and JUnit
reports of
regex lintcarry each pattern as it is written, with only what their format cannot hold spelled as an escape, so a tool can match a result to its source. The console and GitHub reports show a display form that reads back as the same pattern; that console form may be refined. The PHPStan messages show it cut after 50 characters, and that rendering stays: a baseline written for 2.0 keeps matching. - The
regex lintbaseline file: a baseline generated by 2.0 is read by every 2.x, and an issue it records stays matched when its line or its message changes. - The PHP floor, PHP 8.2: no 2.x release raises it.
What a patch release may change
A patch release fixes bugs. A pattern the library judged differently from PHP’s PCRE2 for the same release is a bug: its fix ships in a patch even though it changes a verdict, an error code or an offset, and the CHANGELOG lists it.
The same holds for the ReDoS model. safe (proven) promises that the model
holds no ambiguity; a pattern given that verdict on which the running engine
exhausts its backtrack limit in one match attempt is a soundness bug. Its fix
ships in a patch, with RedosAnalyzer::ANALYSIS_VERSION raised, even though
it changes a severity, and may move the pattern to proof: heuristic.
What only the next major release may change
3.0 may change anything this page does not promise:
- code marked
@internal, which may already change in any release (see What the promise covers); - the constructors of the result objects,
@internaltoday — read them, never build them; - any name the tables above leave out. 2.0 itself removed names when the
@internalsurface changed; UPGRADE-2.0.md maps every one of them, and 3.0 ships the same kind of map.
Nothing promised disappears silently: anything removed from the promised surface in 3.0 is deprecated in a 2.x minor first, with the replacement named (see Deprecations).
Caches
RegexParser::CACHE_VERSION changes whenever the code that builds a tree
changes, in any release, and every cached tree is rebuilt then. It covers the
code that turns a tree into automata too: a DfaCacheInterface you keep across
releases is keyed on it, so its DFAs are rebuilt after such an upgrade. A cache
is never a format to depend on.