Product Decisions This Supports
- Build vs. buy for the DTO layer. This is a ~single-responsibility package (MIT, thin surface, one base class + casts/attributes). Choosing to buy replaces hundreds of lines of hand-rolled "validate the request, then map array keys into a typed object" glue that tends to be re-implemented inconsistently per team. Buying is the default; building only makes sense if we already have a homegrown DTO convention we can't migrate.
- Architecture decision: FormRequest-only vs. a real DTO boundary. Today most Laravel product teams validate in FormRequests and pass arrays into services. This package makes the alternative cheap: one class holds
rules(), defaults(), casts(), and the typed properties, so the contract between HTTP/queues/CLI and the domain is an object rather than an associative array. That's a decision point for any roadmap item involving service-layer extraction, modularization, or an internal API.
- Multi-channel input standardization. Because the same DTO can be built
fromArray, from a request, from a JSON string, from an Eloquent model, or from an artisan command, it becomes the single validation contract for API endpoints, webhook ingestion, queued jobs, CLI commands, and admin imports. This is the feature most likely to unlock a roadmap item — e.g. "add a public API" or "add CSV/CLI data ingestion" — without duplicating validation rules per entry point.
- API surface and versioning. Adding a field means touching one class (property + rule + cast + default). That shortens the change cost of API evolution and makes v1→v2 payload divergence an explicit, reviewable code change rather than scattered
$request->validated('foo') lookups.
- Controller ergonomics / delivery velocity. Container-resolved DTO injection in controller actions (
__invoke(UserDTO $dto)) removes per-action validation boilerplate. This is a legible, cheap velocity win to put in a quarterly plan.
- Consistency via codegen. Customizable stubs mean
artisan make-style scaffolding can encode our house conventions (naming, base class, docblocks, directory layout). This supports onboarding and reduces review noise — a standardization initiative rather than a feature.
- Testability and data hygiene. Typed properties plus casts (
BooleanCast, StringCast, Carbon with timezone offset preservation, enums, nested DTOs, collections, models) mean service tests can construct DTOs directly without HTTP fakes. Also supports transformation/export paths (toArray, Arrayable/JsonSerializable, DataTransformers, SkipOnTransform) for serialization into responses, events, or logs.
- Lazy validation (v3.10) matters for product decisions where a DTO is used in a partially-hydrated or read-only path — e.g. previews, partial updates, or internal jobs that should defer rule evaluation.
- Use cases to route here, not elsewhere: form/API submissions, webhook and third-party payload normalization, queue payloads, artisan importers, and any place a service signature currently takes
array $data.
When to Consider This Package
Adopt when most of these hold:
- The stack is Laravel (10/11/12/13 supported per recent releases) on a modern PHP version, and we want a dependency with essentially no transitive footprint.
- We have — or are about to have — more than one input channel for the same data (HTTP + queue + CLI + webhook), which is exactly where DTO reuse pays for itself.
- The team is small-to-mid and already comfortable with Laravel validation syntax;
rules() returning standard Laravel rules means near-zero learning curve.
- We want validation and typing co-located, but do not need a full data-mapping framework (automatic type→rule inference, validation groups, OpenAPI/schema generation, advanced property mapping).
- We are willing to accept a single-maintainer dependency for a non-critical, easily-replaceable seam — mitigated by wrapping it in our own base DTO class so a swap is a one-file change.
- Consistency matters more than completeness: custom stubs + one base class give us a house style quickly.
Look elsewhere when any of these hold:
- Ecosystem/vendor risk is a gate. Observed signals are modest: ~774 stars and 0 Packagist dependents, so there's little third-party validation or community backpressure. In regulated or supply-chain-reviewed environments, a single-maintainer package with no dependents will fail vendor diligence; prefer
spatie/laravel-data (large ecosystem, richer feature set) or a small in-house base class.
- We need features beyond the core. If the roadmap requires OpenAPI generation, rule inference from property types, named validation groups, partial/DTO-driven updates, or deep nested mapping with strict typed collections, this package will run out of road; adopt a fuller data-object library up front rather than migrating later.
- Non-Laravel services — the container resolution and validation are Laravel-bound.
- We already have a working FormRequest convention and no concrete pain. Adopting a DTO layer is a refactor with migration cost; without a driver (multi-channel input, service extraction, API growth) the ROI is unclear.
- Performance-critical ingestion where per-request object hydration and validation are already a measured bottleneck (unlikely, but worth a spike before committing).
- Long deprecation runway needed. If we can't pin the major version and budget for the swap, don't adopt.
Adoption shape to decide: pin the major version, introduce exactly one internal BaseDTO extends ValidatedDTO to bound lock-in, and pilot on a single new endpoint (ideally one with both an HTTP and a queued path) before broad rollout.
How to Pitch It (Stakeholders)
For executives (one paragraph): We're standardizing how validated data enters our application. Today each endpoint re-validates and re-maps the same fields by hand, so every new API field or new input channel (webhook, CLI import, queue job) is duplicated work and a source of inconsistency. This is a small, MIT-licensed, zero-cost Laravel package that lets one class define the data shape, its validation rules, and its type conversions — and reuse it across HTTP, queues, and commands. It's cheap to adopt, contained to one layer, and reversible: we wrap it in our own base class so we can swap the implementation without touching product code. Recommendation: pilot on one new endpoint with multiple input paths, then standardize via scaffolding if it lands.
For engineering (the technical case):
- One class = contract + validation + casting. Declare typed properties,
rules(), defaults(), casts(); construct from array, request, JSON, Eloquent model, or artisan command.
- Container injection. Type-hint the DTO in a controller action and Laravel resolves and validates it from the current request — boilerplate validation disappears from controllers.
- Real-world casting coverage already handled: booleans/strings/numbers, enums, Carbon (with timezone-offset preservation), nested DTOs, Collections, Eloquent models — and correct
toArray/JSON serialization via Arrayable/JsonSerializable.
- Escape hatches: custom stubs for house conventions,
DataTransformers for export shape, SkipOnTransform to exclude fields, after-validation hooks, lazy validation.
- Maturity signals: v4.x line, active in 2026, docs site (VitePress), CI tests, published changelog and security policy — good hygiene for its size.
- Risk controls we should adopt: pin the major version, single internal base class to isolate the dependency, no reliance on it for authorization or business-critical invariants (validation ≠ domain rules), and a documented fallback (in-house base DTO or
spatie/laravel-data) if maintenance stalls. Note: 0 Packagist dependents means we'd be an early adopter — acceptable for a seam this small, not acceptable to build a platform on.