zero-to-prod/data-model
DataModel hydrates PHP objects from external data with a single from($data) call using reflection and type hints. Validation and casting live in #[Describe] attributes (required, nullable, default, assign), replacing manual property wiring and scattered null checks.
#[Describe([...])]) instead of scattered constructor assignments, service-layer casting, and defensive isset() guards. That's a real architectural decision: DTO-centric mapping versus logic smeared across controllers, form requests, and Eloquent accessors.spatie/laravel-data) and against just using Eloquent casts plus validated arrays.required, nullable, default, cast, from, assign all live next to the property. That is a code-review and onboarding decision as much as a technical one, and it is unusually friendly to static analysis and to AI-assisted code generation (the README markets this explicitly as "low token cost").Consider it when:
$extra capture) — e.g., attaching UI labels or validation hints without subclassing.Look elsewhere when:
spatie/laravel-data. It has far broader adoption, first-party Laravel validation integration, resource/transformation support, and a much larger community to hire from. Switching costs likely exceed any ergonomic gain here.To executives (one paragraph): We have a recurring cost every time we integrate with an external system: hand-written mapping code that drifts, breaks silently, and is tedious to review. This is a small MIT-licensed PHP package that replaces that boilerplate with a declarative attribute on each property. It's low-cost to trial, requires no architectural change (it's a trait, adopted one class at a time), and its rules are visible to static analysis. The trade-off is maturity: it is a young, single-maintainer package with limited public adoption, so I recommend a scoped pilot on one non-critical integration rather than a standard-mandate, with the mapping isolated behind our own interface so we can swap it out at low cost.
To engineering (the substance):
use DataModel; on a class, call Class::from($data). Works with arrays, objects, or nothing.assign → default → cast → post → method-level cast → class-level cast → via → native assignment). That precedence table is the thing to review hardest — it is the package's real API.pre/post) let us centralize invariant checks next to the property rather than in a service; explicit exceptions (PropertyRequiredException, InvalidValue, DuplicateDescribeAttributeException) mean failures are typed and catchable rather than null propagating.from + cast + default) beat control-flow-heavy custom hydrators for reviewability.mapOf, factory, transformable), which covers the collection-transform cases we would otherwise hand-roll.The honest counter-pitch to put on the same slide:
spatie/laravel-data); we should benchmark our first two DTOs against it before committing either way.The ask: one sprint, two or three real DTOs on a non-critical integration, wrapped behind our own mapping interface. Success criteria: measurable reduction in mapping LOC and review time, no reflection-related latency regression, and a clear answer on whether to standardize, keep it local, or revert to spatie/laravel-data.
How can I help you explore Laravel packages today?