Learn how to use Value Objects in Laravel to eliminate primitive obsession, encapsulate attribute logic, and improve domain clarity in your models.
Previously in this course, we discussed Project Structure for Large Applications: Domain-Driven Laravel, emphasizing how to organize your code to support growth. In this lesson, we drill down into the domain layer by introducing Value Objects, a core concept in domain-driven design (DDD) that helps us stop treating our business data as mere arrays of strings and integers.
In a typical Laravel application, we often store business data using scalar types. For example, a Task might have a priority stored as an integer (e.g., 1, 2, 3) or a status stored as a string ('pending', 'active').
When we do this, the logic for handling these values—like checking if a priority is valid or formatting a status label—often leaks into our controllers or gets buried in bloated Eloquent models. This is "primitive obsession." It leads to code duplication and makes it impossible to enforce domain rules consistently.
A Value Object is a small, immutable object that represents a specific concept in your domain. It is defined by its attributes rather than an identity (like a database ID).
Let's improve our project board by introducing a Priority value object. Instead of checking if ($task->priority === 3) everywhere, we encapsulate that logic.
Create a new directory app/Domain/Tasks/ValueObjects and add the Priority class:
PHPnamespace App\Domain\Tasks\ValueObjects; use InvalidArgumentException; readonly class Priority { public const LOW = 1; public const MEDIUM = 2; public const HIGH = 3; public function __construct(public int $value) { if (!in_array($value, [self::LOW, self::MEDIUM, self::HIGH], true)) { throw new InvalidArgumentException("Invalid priority value: {$value}"); } } public function label(): string { return match($this->value) { self::LOW => 'Low Priority', self::MEDIUM => 'Medium Priority', self::HIGH => 'High Priority', }; } }
To use this in your Task model, you can use Eloquent's Casts. This keeps your database schema simple while your application code remains expressive.
Update your Task model to cast the priority column:
PHPnamespace App\Models; use App\Domain\Tasks\ValueObjects\Priority; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; class Task extends Model { protected function casts(): array { return [ 'priority' => 'integer', ]; } protected function priority(): Attribute { return Attribute::make( get: fn($value) => new Priority($value), set: fn(Priority $value) => $value->value, ); } }
Now, instead of passing integers around, you work with the object:
PHP#6A9955">// In a Controller or Service $task->priority = new Priority(Priority::HIGH); echo $task->priority->label(); #6A9955">// "High Priority"
By using Value Objects, you achieve three things:
Priority with a value of 99.Priority class.$task->priority->label() is self-documenting compared to a raw integer check.If you are interested in how similar patterns appear in other stacks, check out our Laravel Value Objects: A Beginner’s Guide to Encapsulating Domain Logic for a broader perspective on the pattern.
TaskStatus Value Object for your project board.Draft, InProgress, and Completed.isFinished() to the TaskStatus object that returns a boolean.Task model to cast the status column to this new object.$task->status->isFinished() instead of checking a string.Value Objects allow us to move logic out of our models and controllers into dedicated, type-safe classes. They effectively fight "primitive obsession" and make your domain code significantly easier to test and maintain. By using Eloquent's Attribute casting, we can integrate these objects seamlessly into our existing workflows.
Up next: We will apply the Strategy Pattern for Business Rules to handle complex, branching logic within our services.
Stop writing massive if-else chains for business logic. Learn how to implement the Strategy pattern in Laravel to keep your services clean and extensible.
Read moreStop drowning in a massive 'App' folder. Learn to use domain-driven architecture to organize your Laravel project for long-term scalability and sanity.
Using Value Objects