Product Decisions This Supports
- A build-vs-buy call on "regex maintainability," answered as a cheap pilot rather than a platform bet. If validation rules, import parsers, and log scrubbers have accumulated hand-rolled patterns that only one or two engineers will touch safely, this is a near-zero-cost experiment: MIT, one
composer require, and the output is a plain PCRE string you hand to preg_match or Laravel's regex rule. The realistic alternative — an internally maintained fluent wrapper — costs roughly 1–2 engineer-days. The asymmetry is the decision: fund a scoped pilot, keep the exit door open.
- Scoping a feature-level rollout instead of an architectural standard. Because the package compiles to a portable string, adoption is decided file-by-file: FormRequest validation rules,
preg_match/preg_replace calls in service classes, seeders and factories. That supports a concrete roadmap line — "convert the 5–10 ugliest patterns this quarter; leave everything else alone" — rather than a standard we'd have to unwind later.
- Framing a developer-experience initiative with reportable metrics. Regex literacy is an invisible recurring tax: slow review, a single-reviewer bottleneck, onboarding friction, and quiet fear of touching validators. Positioning this as a DX roadmap item gives numbers that fit on a slide — reviewers required per PR touching patterns, time-to-review on validator changes, count of "regex specialist" approvals, defect rate in converted files.
- Deciding explicitly where the DSL is and isn't allowed. Fits well: admin/back-office validation, CSV and log ingestion, fixture generation, developer tooling, teaching material. Write the guardrail down: not for security-critical input sanitization, not in hot loops, and not for anything requiring lookarounds, backreferences, or Unicode property classes until coverage is verified against the vendor source.
- Choosing the abstraction boundary up front — the decision that sets future cleanup cost. Hiding the package behind a thin internal wrapper (
App\Support\Pattern) converts an unmaintained third-party dependency into an internal seam. Removal then costs "swap a few methods in one class," not "migration project." That is a product call, not a code-style preference, and it should be a condition of approval for the pilot.
- Sequencing rollout without burning goodwill. Wrapper plus contract tests → new code only → opportunistic backfill when a file is touched for other reasons. Scheduling a rewrite converts a two-week pilot into a project. Approve the pilot, not the migration.
When to Consider This Package
Consider it when:
- Your team reads and re-reads regex far more than it writes latency-critical regex. This is readability sugar, not a performance tool — pattern building is string concatenation and compiled patterns hit PCRE's cache anyway.
- You want a low-commitment pilot: one dependency, portable string output, near-zero removal cost, no service provider, no config surface.
- The team is PHP/Laravel-fluent but regex-shy, and reviewers currently rubber-stamp patterns they cannot parse. That rubber-stamp is the actual symptom being treated.
- You already have 3–5 named, painful patterns to convert — one validation rule, one log-line parser, one extraction with a capture group. Evaluate against those, not against the README examples.
- You will commit to the two disciplines that make it safe: a single wrapper class as the only import site, and compiled
->get() output required in every PR that touches a pattern.
Look elsewhere — or stay hand-written — when:
- More than roughly a third of your sampled patterns don't fit the DSL. There is a hard ceiling: no lookarounds, backreferences, conditionals, named captures, or atomic groups. Two regex vocabularies with neither authoritative is worse than one ugly one.
- Patterns are security-critical, or user input reaches a compiled pattern in a request path without input-length guards. Fluent chains still backtrack catastrophically;
oneOrMore() over a permissive class is exactly as dangerous as hand-written regex.
- Fields accept names, addresses, or free text rather than machine-generated codes.
letters() and digits() are almost certainly ASCII-oriented — assume the DSL is wrong and use a raw passthrough with explicit Unicode property classes.
- You need variable interpolation inside a chain and cannot confirm
preg_quote-style escaping. Unescaped interpolation is a regex-injection bug behind an innocuous-looking call site.
- Nobody will own it. No vendor, no SLA, 113 stars, 0 dependents, a single maintainer, and a README written as worked examples rather than a specification. Without a named owner and internal docs, the wrapper becomes an orphan and the next engineer rewrites the regex alongside it.
- Free-spacing already solves it. PCRE's
x modifier plus named groups buys most of the readability with zero dependencies. If a converted pattern isn't meaningfully shorter than the original, that's real signal — the package earns its place on semantics (explicit quantifier binding, self-documenting vocabulary), not line count.
How to Pitch It (Stakeholders)
To executives — one paragraph: We have a small, persistent maintenance tax in our regex: a handful of patterns that only one or two people will safely change, which slows review and creates single-reviewer risk on validation and data-ingestion code. This package is a low-cost experiment to reduce that tax — MIT-licensed, one dependency, no infrastructure, and it outputs ordinary strings our code already accepts, so we can back it out by editing one internal class. I'm asking to fund a two-week pilot covering 5–10 existing patterns, with success measured in review time, number of required reviewers, and defect rate on converted files. No rewrite, no standard imposed, no lock-in.
To engineering — the shape of the ask:
- Pin the exact version and add it to the dependency risk register the same day it lands. Single maintainer, zero downstream dependents — nothing upstream will surface a breakage for us.
- Read the vendor source end-to-end (it's small) for three answers before writing anything real: the full method vocabulary, whether a raw passthrough exists, and whether
->get() returns a delimited pattern or a bare body. If it's a bare body, we own delimiter choice — decide that once, in the wrapper, with a test that a pattern containing a slash compiles and matches.
- One import site:
App\Support\Pattern, exposing named static methods plus our own raw() seam. Nobody outside that namespace uses the package directly.
- Two tests per pattern, always: one asserting the compiled string, one asserting positive/negative fixtures. The string test catches silent upgrades and quantifier-binding drift; the fixture test catches a chain that reads correctly and matches too broadly. Neither alone is sufficient.
- Review discipline: compiled output in the PR description, same ReDoS scrutiny and input-length guards as hand-written regex, and
preg_last_error() checking wherever user input reaches a pattern.
- Write the fixture set before the chain. Quantifier binding — does
->maybe() bind to the last token or the last group — is the subtle failure mode, and fixtures force the question.
What success looks like at the end of the pilot: five to ten patterns converted, each greppable through Pattern::, each with a locked compiled string and behavioural fixtures, review time on validator changes measurably down, and a clear list of the patterns that stayed hand-written and why. If more than a third of the sample didn't fit, we stop — that's the kill signal, and it's cheap to honour because the wrapper is the only thing to delete.