lionix/castable-request
Laravel package for casting incoming request data into typed objects and structured values, helping keep controllers clean and reducing manual array handling when working with validated input.
FormRequest. This package replaces that with a declarative casts() map. Because 2.0.0 depends only on illuminate/database, illuminate/http, and illuminate/support rather than the full laravel/framework, the audit, licensing, and dependency surface is smaller than the in-house trait it replaces — an unusually clean buy case for a small package.OrderStatus and Carbon instances rather than strings. That pays out in fewer defensive null/format checks downstream, better static analysis (Larastan), and tests that assert domain objects instead of array shapes — all measurable in defect rates on input-handling paths.^2.0 specifically.$request->input()/$request->all() after validation (cast values land in the request, and Symfony's InputBag rejects non-stringable objects — read from validated()/safe() instead).Adopt when all of the following hold:
composer why-not lionix/castable-request ^2.0 before committing — the split illuminate/* dependencies surface version conflicts here rather than as a runtime surprise.FormRequest trait that has quietly become load-bearing framework-internals code. Below that threshold, the package adds a concept without removing work.ImMemoryCastsRegistry → InMemoryCastsRegistry, removed Handlers\RequestResolvingHandler, added native types to all contracts, and reordered validation vs. casting. A pre-2.0.0 custom caster without return types fatals at resolution. That is fine for a team with CI and a clean upgrade path; it is a hazard for a codebase full of undocumented customisations.rules() that only passed because the value had already been cast (use date_format:Y-m-d rather than date when the cast is what normalises), deleting now-dead try/catch blocks that mapped InvalidArgumentException to 422, and grepping for strict comparisons against validated() payloads ('pending' === $data['status'] silently changes truthiness once that value is an enum).^2.0, mirror the package's own CI matrix (HTTP tests, Pint, Larastan across supported PHP/Laravel pairs), and keep the integration behind your own thin abstraction so a future swap is a day's work rather than a quarter's.Look elsewhere — or build in-house — when:
Enum rule plus a small factory method covers it with zero dependency.RequestResolvingHandler — that handler is gone, and the two-phase resolve/after-resolve split no longer exists. Migration cost may exceed the benefit.To executives (one paragraph): This is a small, MIT-licensed Laravel library that removes hand-written boilerplate for turning incoming API request data into typed objects. The business case is not the library — it is the two things adoption forces us to do properly: upgrade off an end-of-life Laravel 11 that carries open security advisories, and standardise the error contract on our public API so that malformed input returns a clean 422 field-level error instead of a 500. Both are risk reductions with a scheduled cost, not speculative engineering. The dependency itself is low-risk to try and cheap to remove: it pulls in three Laravel sub-packages rather than the whole framework, and it can be isolated behind a thin internal abstraction. The counterweight is that it is a small, low-adoption project (23 stars, no dependents), so we pin the version, run its test and static-analysis matrix in our own pipeline, and treat it as infrastructure to be monitored rather than assumed.
To engineering (what we are signing up for):
rules() method in the diff needs reading. Anything relying on $request->input() already being typed — custom rules, prepareForValidation(), middleware — needs re-checking. Read from validated()/safe(), which now return cast values; do not write cast values back via $request->merge().try/catch blocks and handler mappings that converted caster exceptions into 422s; delete index-keyed workarounds for the old wildcard bug; delete manual forget() calls that papered over cross-request cast leakage. Use CastsRegistryInterface::forget() in tearDown() for test isolation, and never call it at runtime — it mutates a shared singleton visible to every Octane worker and queued job.castAttributes() for batch casting outside the form-request lifecycle. Input is replaced once per request rather than once per attribute, which is faster and avoids half-cast intermediate states.^2.0, mirror the package's CI matrix (HTTP-level tests, Pint, Larastan across every supported PHP/Laravel combination), and keep the integration behind our own interface so the exit cost stays low. Check UPGRADE.md on every minor bump — the project has demonstrated it will ship breaking changes in a major.How can I help you explore Laravel packages today?