Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Data Model Laravel Package

zero-to-prod/data-model

Type-safe PHP data models that hydrate from arrays/JSON via a single from($data) call. Uses reflection, type hints, and #[Describe] attributes for defaults, required/nullable rules, casting, and assignment—ideal for APIs, DB rows, and user input.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require zero-to-prod/data-model
    
  2. Basic Usage:

    use Zerotoprod\DataModel\DataModel;
    
    class User {
        use DataModel;
    
        public string $name;
        public int $age;
    }
    
    $user = User::from(['name' => 'Alice', 'age' => 30]);
    
  3. First Use Case: Replace manual array-to-object mapping in API responses or form submissions:

    $apiResponse = json_decode($request->getContent(), true);
    $user = User::from($apiResponse['user']);
    

Where to Look First

  • DataModel trait: Core functionality for type-safe hydration.
  • #[Describe] attribute: Centralized property configuration (validation, casting, defaults).
  • from() method: Static entry point for hydration.

Implementation Patterns

Core Workflows

  1. API Response Handling:

    $dto = Order::from($request->json()->all());
    return new JsonResponse($dto);
    
  2. Form Request Validation:

    $data = $request->validate([
        'name' => 'required|string',
        'age' => 'required|integer'
    ]);
    $user = User::from($data);
    
  3. Nested Object Hydration:

    class Order {
        use DataModel;
    
        public string $id;
        public User $customer;
    }
    

Integration Tips

  • Laravel Eloquent:

    $model = User::from($this->validated());
    $model->save();
    
  • Collection Mapping:

    $users = collect($apiResponse['users'])
        ->map(fn($user) => User::from($user))
        ->values();
    
  • Custom Casting:

    #[Describe(['cast' => [User::class, 'formatDate']])]
    public string $created_at;
    
    public static function formatDate(string $date): string {
        return (new DateTime($date))->format('Y-m-d');
    }
    

Type-Safe Patterns

  • Union Types:

    #[Describe(['cast' => fn($v) => is_string($v) ? $v : (int)$v])]
    public string|int $variant;
    
  • Recursive Arrays:

    class Product {
        use DataModel;
    
        #[Describe(['via' => [Product::class, 'fromArray']])]
        public array $items;
    }
    

Gotchas and Tips

Common Pitfalls

  1. Missing required Properties:

    // Throws PropertyRequiredException
    User::from(['name' => 'Alice']); // Missing 'age'
    

    Fix: Add #[Describe(['required' => false])] or provide defaults.

  2. Circular References:

    class A { use DataModel; public B $b; }
    class B { use DataModel; public A $a; }
    

    Fix: Use #[Describe(['via' => fn($data) => new B()])] with manual assignment.

  3. Attribute Overrides:

    // Only the last attribute applies
    #[Describe(['cast' => 'strtoupper'])]
    #[Describe(['cast' => 'strtolower'])]
    public string $name;
    

Debugging Tips

  • Inspect Resolution Order:

    dd(DataModel::resolveProperty($user, 'name', ['name' => 'Alice']));
    
  • Custom Exceptions:

    try {
        $user = User::from([]);
    } catch (PropertyRequiredException $e) {
        report($e);
    }
    

Configuration Quirks

  1. Callable Signatures:

    • pre/post hooks receive $value even if null (check for null explicitly).
    • cast must return a value matching the property type.
  2. Default Values:

    // Overrides missing keys
    #[Describe(['default' => fn() => now()->format('Y-m-d')])]
    public string $date;
    
  3. Performance:

    • Avoid complex cast logic in tight loops (cache results if needed).
    • Use #[Describe(['ignore' => true])] for properties you won’t hydrate.

Extension Points

  1. Custom Attributes:

    #[Describe(['my_key' => 'value'])]
    public string $name;
    

    Access via $attribute->getArguments()['my_key'].

  2. Global Defaults:

    class BaseModel {
        use DataModel;
    
        #[Describe(['default' => null])]
        public ?string $optional;
    }
    
  3. Laravel Integration:

    // Override from() for model binding
    public static function from(array $data): static {
        return parent::from($data)->setAttribute('guard', 'api');
    }
    

Laravel-Specific Tips

  • Form Requests:

    public function rules() {
        return [
            'name' => 'required|string',
            'age' => 'required|integer|min:18'
        ];
    }
    
    public function prepareForValidation() {
        $this->merge([
            'age' => $this->age ?? 0
        ]);
    }
    
    public function validated() {
        return User::from(parent::validated());
    }
    
  • API Resources:

    public function toArray($request) {
        return User::from($this->resource)->toArray();
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport