composer/pcre
Type-safe wrappers around PHP's preg_* functions that fail loudly instead of returning unexpected nulls. Enforces PREG_UNMATCHED_AS_NULL, simplifies return types for PHPStan and Psalm, and includes a PHPStan extension for better regex analysis.
Architecture fit
composer/pcre is a thin, stateless, static-method wrapper over PHP's preg_* functions (Composer\Pcre\Preg, Composer\Pcre\Regex) plus typed result objects (MatchResult, MatchAllResult, ReplaceResult, etc.). It has no service provider, no container bindings, no config, and no framework coupling — a clean fit for a Laravel codebase where regex work is currently scattered across validation rules, form requests, importers, log/route parsers, slug builders and Blade partials.preg_* fails silently (returns null/false), and PHPStan/Psalm see an unmanageable union of return types. This package collapses those unions to string/int/array/bool and throws PcreException on failure. For a TPM the relevant framing is defect class elimination — "unchecked preg_match returned false and the code string-cast it to ''" is a recurring production bug category.vendor/composer/pcre/extension.neon) is the highest-leverage part of the package: it validates literal patterns at analysis time and infers $matches array shapes. In a Laravel shop already running Larastan, this converts regex correctness from runtime to CI.rawr/t-regx is the stated alternative. Choosing composer/pcre is a safety/typing decision, not a capability decision.Integration feasibility
composer require composer/pcre — essentially free. It is already present transitively in most Laravel projects via composer/composer (which requires ^3.0), so the incremental dependency-graph cost is ~zero and there is no new transitive risk surface.^3.x. Laravel 8 on PHP 7.4 also works on 3.x. There is no realistic Laravel target that forces the older branch.preg_match(...) → Preg::isMatch(...) (bool), preg_match with offsets → Preg::matchWithOffsets, preg_replace → Preg::replace.$subject for replace methods, PREG_SET_ORDER rejected in matchAll, preg_filter unsupported, and PREG_OFFSET_CAPTURE/PREG_SPLIT_OFFSET_CAPTURE only available via dedicated methods. Any code relying on those shapes is not a drop-in.Technical risk
PREG_UNMATCHED_AS_NULL. Match arrays gain entries and previously-'' groups become null. Any downstream isset($m[4]) or $m[4] !== '' logic silently flips behaviour. This is the single largest migration hazard and it is invisible in a naive find-and-replace.null to '' now throws PcreException. Unguarded, that becomes a 500 where the old behaviour was a quietly wrong string. The risk is concentrated in request-handling and Blade paths.*StrictGroups variants throw when a subpattern is optional and unmatched — powerful, but easy to apply to a pattern that legitimately has optional branches, producing intermittent production exceptions driven by input data.pcre.backtrack_limit, UTF-8/invalid-subject handling, and JIT behaviour are all still PHP's. The package improves error handling, not pattern performance or safety.Str::match / Str::matchAll cover simple extraction cases. Without a written convention, the codebase drifts into three styles (preg_*, Str::match, Preg::match).Key questions
preg_* call sites sit in request/response hot paths where a new PcreException would surface as a 500 rather than a validation error?PREG_SET_ORDER, array subjects in replace, or preg_filter? Enumerate before committing.Str::match vs Preg::* vs Regex::*?PcreException → 422 / ValidationException) so failures are handled once, not per call site?Stack fit
config/app.php or a bootstrap/providers.php. It drops in at any layer: validation rules, jobs, console commands, importers, middleware.Str:: for display/formatting helpers, use Preg::* where correctness matters (multi-group extraction, replace with callbacks, grep/split on collections).extension.neon is where the package earns its keep; treat "PHPStan extension enabled" as a hard acceptance criterion, not a nice-to-have.Migration path
preg_match, preg_match_all, preg_replace, preg_replace_callback, preg_replace_callback_array, preg_grep, preg_split, preg_filter. Classify each as compatible / needs dedicated method / incompatible.PREG_SET_ORDER, array subjects, and preg_filter sites for manual rework or explicit exclusion rather than forcing them through.PREG_UNMATCHED_AS_NULL fallout and establish the exception-handling pattern.'' groups as a signal of intentional behaviour to preserve.preg_* usage in application code.Compatibility
^3.x in composer.json for consistency across first-party code so that transitive resolution and direct dependency never diverge.3.3.2, or upgrade PHPStan as part of the same workstream.composer/pcre narrowly — though since composer/composer already requires ^3.0, that pressure mostly resolves in favour of 3.x.Sequencing
*StrictGroups adoption only where patterns are provably non-optional.*StrictGroups; adopt it after conversion stabilizes, and only for patterns reviewed for optional branches.Maintenance
^3.0 constraint is a low-supervision hold.Support
PcreException) makes support tractable: one catch-and-map policy, one log channel assertion, one test helper.Preg vs Regex vs Str::match, and how to handle PcreException.preg_*: an exception with a pattern and a preg_last_error reason beats a silent null.Scaling
pcre.backtrack_limit / pcre.jit limits. If regex-heavy paths are a known latency source, pair this adoption with pattern-level profiling and input length caps.grep/split workloads retain their existing memory profile; nothing here changes allocation behaviour meaningfully.Failure modes
PcreException → 500. The headline risk. Previously silent null becomes an exception. Mitigation: map PcreException centrally (exception handler / middleware) to a 422 or a domain error, and never leave conversion call sites unguarded in request paths.PREG_UNMATCHED_AS_NULL changes array shape; downstream isset/!== '' checks silently invert. Highest-probability defect class in migration; caught only by tests that assert actual match arrays.*StrictGroups input-dependent exceptions. Throws only when a group doesn't participate, so it passes in CI and fails on the one customer record with an unusual payload. Treat as production risk, not a lint.PREG_SET_ORDER or PREG_OFFSET_CAPTURE to the wrong method throws — good, but produces a mid-migration failure rather than a compile-time one. The inventory phase front-loads these.Ramp-up
PREG_UNMATCHED_AS_NULL semantics table is the one thing everyone must actually read.How can I help you explore Laravel packages today?