php-standard-library/type
Runtime type validation for PHP built on the Parse, Don't Validate principle: coerce and assert unstructured input into well-typed data, with clear errors when values don't match.
Architecture fit. The package positions itself as a boundary layer, not a framework component. Its value proposition — parse unstructured input once at the edge, then pass guaranteed-typed values inward — maps cleanly onto the places a Laravel application is structurally weakest: form request payloads, job/queue serialization, webhook ingestion, third-party API responses, and console/ETL jobs where data has already been marshalled and type guarantees are gone. That is a genuine architectural gap: Laravel's Validator validates user intent against rules, it does not produce typed domain values, and PHP's type system stops at the framework boundary. This package is the narrow band between "the request was acceptable" and "this object is safe to use."
The important caveat is that it sits orthogonally to Laravel's validation, not inside it. There is no service provider, no facade, no Validator::extend() hook out of the box. Any Laravel-native ergonomics — a ParsedRequest base class, custom rules, middleware — are work we own. That is a modest amount of glue code, but it must be scoped, owned, and documented, or it becomes yet another ad-hoc pattern rather than the standardization it's meant to deliver.
Integration feasibility. Technically low-friction: MIT licensed, Composer-installable, no native extensions, pure PHP, and usable as a set of static parsers/assertions. Wrapping it in a small internal package (acme/type-kit) that exposes Laravel-shaped helpers is the obvious play and keeps a seam for replacement. The realistic constraints are version-related: verify the PHP floor against your fleet (older Laravel 8/9 applications may be excluded), confirm the split package's version line is kept in lockstep with the monorepo, and confirm that assertion failures surface as exceptions you can map to Laravel's ValidationException / HTTP 422 rather than raw 500s.
Technical risk. The headline risk is not code quality — the package is a split of a long-running, actively maintained standard-library project — it is supply-chain and provenance opacity:
"0", "false", or [] where a stricter contract was intended. The team needs a written convention: coercion only at the outermost boundary, strict assertion everywhere inside.Key questions.
webmozart/assert already present anywhere? If so, what is the migration argument, or does this become a second assertion library in the codebase?is_* checks and filter_var in under a sprint if the upstream project stalls?$casts, FormRequest, resource classes), and who arbitrates that?Stack fit. Strongest fit is a Laravel API/service layer where JSON crosses a trust boundary: FormRequest subclasses that parse into typed DTOs, queue job constructors that parse their own payloads, webhook controllers, and integration clients consuming third-party responses. Weaker fit is Eloquent hydration (casts already do this), Blade, and any code path where a static analyzer is already enforcing the contract. The honest framing for stakeholders: this is a boundary-hardening tool, not a codebase-wide typing initiative.
Migration path. Treat it as an additive, opt-in layer with a hard rule: no existing validation is removed until the replacement has a test proving equivalence.
acme/type-kit — a thin internal package that depends on php-standard-library/type, exposes Laravel-idiomatic helpers (a ParsesRequests trait, a ValidatedPayload rule, a ParseException → ValidationException mapper), and re-exports nothing else. This is the single most important step: it caps the blast radius of the dependency at one package, and it caps the exit cost at one rewrite.is_* lines.App\Data\..., every parser has a test for its failure message. This is the artifact that determines whether adoption feels like a standard or like a ninth opinion.acme/type-kit internals on native checks," which the seam makes cheap.Compatibility. Verify and pin the PHP constraint against your lowest-supported runtime; confirm behavior on the Laravel versions actually in production (middleware pipeline order matters for where parsing runs); confirm no conflict with existing validation middleware, TrimStrings/ConvertEmptyStringsToNull, or API resource serialization; and confirm the package does not interfere with PHPStan/Psalm baselines or larastan inference — if it does, that friction is a hidden adoption cost worth surfacing early.
Sequencing. Dependency vetting and a fork/exit assessment → internal wrapper package → single pilot boundary with pre-agreed metrics → convention document → pattern-by-pattern expansion. Roughly one sprint of engineering plus one sprint of observation before any go/no-go. Do not sequence this ahead of or against a planned validation-standardization effort; run it as the runtime complement to that work, not as a competing initiative.
Maintenance. Low code-churn, non-trivial attention-churn. Updates will be infrequent but will arrive bundled in monorepo releases that you must actively go read. Assign a named owner for: watching upstream releases, running the internal wrapper's test suite against new tags, and adjudicating the "should this be a parser or a FormRequest?" question. Budget it as a standing fraction of one engineer, not a project.
Support. Minimal vendor-style support; the practical support channel is the upstream issue tracker and your own documentation. The internal wrapper's README and the convention doc are the actual support artifacts — if they are thin, every new engineer will reinvent a parser and you will have defeated the purpose. Include a short "how to write a parser" guide with two or three real examples from the codebase.
Scaling. Runtime overhead per assertion is negligible; the scaling concern is aggregate volume in hot paths — per-item parsing inside large loops, or per-row parsing in ETL. Convention should be: parse the collection boundary once, not each element in a tight loop, and benchmark the first high-throughput job rather than assuming. Also watch memory on deeply nested structures and be deliberate about recursion limits for user-supplied arrays.
Failure modes. Enumerate them for on-call, because each produces a different incident signature:
null where "" was expected). Mitigation: parsers must be versioned alongside the contract; alert on parse-failure rate per integration, not just on 5xx.ParseException handler in the wrapper, covered by a test.Ramp-up. Low technical learning curve, moderate convention learning curve. Engineers will understand the API in an hour; the thing that takes longer is internalizing where parsing belongs versus where FormRequest ends. Plan for a short internal walkthrough (60–90 minutes), a convention doc in the repo, and one exemplar PR that reviewers can point at. Onboarding impact should be net positive — replacing scattered is_* chains with named parsers reduces the time a new engineer spends reverse-engineering implicit type assumptions — but only if the naming is disciplined and the failure messages are good. Make failure-message quality an explicit review criterion.
Bottom line for the decision record. Recommend a scoped pilot, not a fleet-wide mandate. The package solves a real architectural gap at system boundaries, the integration cost is genuinely low, and the internal wrapper makes the exit cost low too. The reasons to proceed cautiously are provenance opacity and single-maintainer sustainability, not code quality — and both are fully mitigated by the wrapper seam plus a 90-day review gate with pre-agreed success metrics.
How can I help you explore Laravel packages today?