zero-to-prod/data-model
Reflection-based PHP data models that hydrate typed objects from arrays with a single from($data) call. Use #[Describe] attributes to define casting, validation, defaults, nullable/required rules, and assignments—keeping mapping logic predictable, readable, and verifiable.
#[Describe] attribute system mirrors Laravel’s validation and casting conventions (e.g., Castable, Validatable), reducing cognitive friction for teams already using Laravel’s ecosystem.toArray()/toJson() patterns, enabling seamless integration with API responses or database hydration.DataModel trait requires no base class or interface, making adoption straightforward for existing Laravel models or DTOs.DataModelHelper and Transformable packages suggest potential for custom Laravel integrations (e.g., DataModel-aware form requests, API resource transformations).from() calls can be wrapped in container bindings (e.g., App\Services\UserService::from()) for dependency injection.Model::query()->get()->map(fn ($m) => User::from($m->toArray()))), though ORM-specific optimizations (e.g., eager loading) may still be needed.array_map) is recommended.#[Describe] with complex hooks (e.g., nested pre/post logic) may reduce readability. Teams should enforce a "simple by default" convention.Validator; teams may need to duplicate rules or build bridges (e.g., via DataModelHelper).Model::remember()). Custom caching layers may be needed for nested objects.#[Describe] attributes with Laravel’s existing #[Cast]/#[Attribute] patterns?Validator remain the source of truth, or will DataModel attributes replace it? If hybrid, how will conflicts be resolved?pre/post) be tested? Mocking reflection attributes may require custom test utilities.DataModel attributes for better autocompletion?DataModel adoption? Start with DTOs/API responses or core domain models?json_decode() + array_map with DataModel::from($request->all()).FormRequest or Validator for hybrid validation (e.g., #[Describe(['required'])] + rules()).UserResource::from($user)). Avoid mixing with Eloquent’s mutators/accessors unless explicitly bridged.ProcessOrder::from($payload)).GitHubUser::from($client->fetch())).OrderCreatedEvent::from($data)).User::from(['name' => 'Test']) in PHPUnit).DataModel::from().// Before
$user = new User($request->input('name'), $request->input('age'));
// After
$user = User::from($request->all());
FormRequest/Validator rules. Replace redundant checks with #[Describe(['required', 'nullable'])].DataModelHelper to bridge gaps (e.g., attach Laravel validation errors to DataModel exceptions).DataModel-aware repository layer:
class UserRepository {
public function find(int $id): User {
return User::from((new UserModel)->find($id)->toArray());
}
}
DataModel with Eloquent’s magic methods (e.g., snake_case attributes).DataModel classes:
{
"scripts": {
"post-autoload-dump": "zero-to-prod-data-model ./docs/datamodels"
}
}
#[Describe(['cast' => 'trim'])] over manual trim() calls).| Component | Compatibility Notes |
|---|---|
| Laravel 10+ | Full support (PHP 8.1+). |
| Eloquent Models | Avoid mixing DataModel with Eloquent’s attributes, casts, or accessors. Use separate classes. |
| Laravel Validation | No native integration; use DataModelHelper or custom validation bridges. |
| API Resources | Replace JsonResource toArray() with DataModel::from($model)->toArray(). |
| Queues/Jobs | Hydrate job payloads via DataModel::from($payload). |
| Testing (PHPUnit) | Works natively; use DataModel for test data factories. |
| Caching | Recursive hydration bypasses Laravel’s query cache. Add custom caching for nested objects. |
DataModelHelper for array/collection transformations.FormRequest rules to #[Describe] where possible.DataModel errors to Laravel’s validation response format.DataModel-aware repositories for query results.DataModel instances.DataModel::from() calls.#[Describe] attributes centralize validation/casting logic, reducing maintenance in constructors/factories.#[Describe(['cast' => 'strtoupper'])] clarifies intent).Describe keys (e.g., pre/post hooks) may make classes harder to read.How can I help you explore Laravel packages today?