mpociot/human-regex
Human Regex lets you build regular expressions in PHP with readable, fluent method calls instead of cryptic syntax. Chain start(), then(), capture(), and end() to compose patterns that are easier to write, review, and maintain.
Architecture fit. The package is a leaf utility, not a framework extension. There is no service provider, no config file, no facade, no bootstrapping, and no runtime lifecycle participation — it is a string builder that emits a plain PCRE pattern. That is the best possible shape for a third-party dependency in a Laravel codebase: it touches nothing until a call site invokes it, and it can be removed without touching the container, the cache, or the queue. The correct architectural placement is a single internal seam (App\Support\Pattern) that is the only import site; domain and application code should never use HumanRegex directly. With that boundary in place, the package is architecturally inert and its abandonment cost collapses to "edit one class."
Integration feasibility. High. One composer require, MIT license, output consumable by preg_match, preg_replace, and Laravel's regex: validation rule. The realistic integration effort is measured in hours for the pilot and one to two engineer-days for the wrapper plus contract tests. The competing option — writing an internal fluent regex wrapper — costs roughly the same one to two days, which is precisely why the buy-side argument is weak on cost and must be made on semantics instead: explicit quantifier binding, self-documenting vocabulary, and a lower review burden for regex-shy teams.
Technical risk. Concentrated in behavior, not in performance. Four risks dominate:
maybe(), oneOrMore() after a group), whether ->get() returns a delimited pattern or a bare body, and whether strings passed to then() are preg_quote-escaped are all unverified until you read the source. Any of these being wrong produces a pattern that compiles, matches something, and fails on an edge case in production.Key questions to resolve before commit.
->get() return — full delimited pattern, or an undelimited body?then($string) escape literals, or interpolate them raw (regex-injection exposure)?maybe() and oneOrMore() following a group?raw()/passthrough escape hatch, and how does it compose?u modifier and non-ASCII input handled — are letters()/digits() ASCII-only?Stack fit. Native PHP/Laravel with no bootstrapping surface. It drops into FormRequest validation rules, preg_match/preg_replace call sites in service classes, Str:: helpers, console commands and importers, and seeders/factories. It does not fit security-critical input sanitization, hot loops, or any pattern requiring lookbehind, backreferences, or Unicode property classes until coverage is confirmed. Those exclusions belong in a written product guardrail, not tribal knowledge.
Migration path. Opportunistic backfill only. Do not schedule a rewrite — that converts a two-week pilot into a project and burns the goodwill that made the pilot cheap to approve. The sequence that works:
composer.lock; pin the exact version.preg_match it against real input.raw() seam and name every pattern (Pattern::postalCode()), never anonymous inline chains.Compatibility. Three concrete checks. First, confirm the package's PHP constraint against the runtime, not the README. Second, decide delimiter and flag ownership once, in the wrapper, and add a test proving a pattern containing a slash compiles and matches — otherwise a URL-matching pattern blows up at runtime inside a conditional, which is the worst failure mode available. Third, confirm how output interacts with Laravel's regex: rule, which requires a fully delimited pattern.
Sequencing and scope. Pilot on a named set of five to ten painful patterns, chosen from what already exists in app/ and tests/ — one validation rule, one log-line parser, one extraction with a capture group. Roll out in value order: validation rules first (lowest blast radius, most readers), then preg_replace scrubbers, then console/import scripts (where the "only one person can read this" problem is worst), then seeders and factories as free wins. New code adopts first; existing files convert when someone is already touching them.
Maintenance. No vendor, no SLA, no issue-triage expectation, and a README that is a worked-examples format rather than a specification. The mitigation is ownership, not tooling: name a single owner for App\Support\Pattern, copy the handful of patterns you standardize on into onboarding docs, and add a line to the dependency risk register. Without that, the wrapper becomes an orphan, the next engineer distrusts the chain, and writes a fresh hand-rolled regex beside it — leaving you strictly worse off than before.
Support and ramp-up. Engineer ramp-up is roughly an hour for the vocabulary and fifteen minutes to read the source; the real cost sits in wrapper construction and fixture discipline, one to two engineer-days total. Support burden internalizes to the wrapper owner. On the benefit side, the reportable DX metrics are meaningful and easy to collect: reviewers required per PR touching patterns, time-to-review on validator changes, count of "regex specialist" approvals, and defect rate in converted files. Those are the numbers that justify or kill the wider rollout.
Scaling. Runtime cost is a non-issue — pattern construction is string concatenation and compiled patterns hit PCRE's own cache. The one rule: do not construct chains inside tight loops; hoist them into static methods or constants on the wrapper so the string is built once. Team scaling is the actual thesis — the package earns its place on semantics and reviewability, not on line count. If a converted pattern reads as "not much shorter than the regex," that is real signal that PCRE's x modifier plus named groups may be the better fit.
Failure modes to plan for.
/ — caught only by a slash fixture.then() interpolates unescaped user input — never interpolate variables into a chain; use raw() with preg_quote($input, '/').oneOrMore()->anything() matching the empty string — write fixtures before writing the chain.preg_last_error() checks as hand-written regex.How can I help you explore Laravel packages today?