Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
LaravelPHPJune 22, 20264 min read

Laravel Value Objects: A Beginner’s Guide to Encapsulating Domain Logic

Laravel Value Objects help you eliminate primitive obsession and clean up your domain logic. Learn how to implement them to write safer, more readable code.

LaravelPHPValue ObjectsDomain Driven DesignClean CodeRefactoringTutorial

I remember staring at a User model two years ago, trying to figure out why I had five different methods checking if an email address was valid or formatted correctly. The logic was scattered across controllers, jobs, and even a few random service classes. I was suffering from "primitive obsession," treating an email address like a simple string when it was actually a core part of my domain.

That’s when I started using Value Objects. If you’re tired of passing around raw strings or integers and wondering if they contain valid data, this guide is for you.

What is a Value Object?

At its core, a Value Object is a small, immutable object that represents a simple entity whose equality is based on its value, not its identity. Think of a Money object or an EmailAddress. If you have two EmailAddress objects with the same string value, they are effectively the same thing.

In the context of Domain Driven Design, Value Objects allow you to group data and behavior together. Instead of asking a controller to validate a string, you ask the Value Object to instantiate itself. If the data is invalid, the object simply refuses to exist.

Implementing Laravel Value Objects

Let’s look at a concrete example. We'll build an EmailAddress Value Object.

PHP
namespace App\ValueObjects;

use InvalidArgumentException;

readonly class EmailAddress
{
    public function __construct(public string $value)
    {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email format: {$value}");
        }
    }

    public function domain(): string
    {
        return substr(strrchr($this->value, "@"), 1);
    }
}

By using the readonly keyword available since PHP 8.2, we ensure the object is immutable. Once it’s created, the email cannot change. If you need to "change" it, you create a new instance.

Why not just use a string?

When you pass a raw string into a function, you have no guarantee what's inside that string. When you type-hint EmailAddress, you gain a contract. You know that if the code reaches your method, the email is already validated. You don't have to write the same regex or filter_var checks over and over again.

This approach pairs perfectly with Mastering Laravel DTOs: Type-Safe Data Handling for Clean Code, as both patterns aim to move logic out of your controllers and into structured objects.

Integrating with Eloquent

A common question I get from junior engineers is: "How do I store this in the database?"

You have two main paths:

  1. Cast the attribute: Use a custom Eloquent cast. This is the cleanest way to bridge the gap between your domain and the database.
  2. Getter/Setter methods: Manually convert the object to a string before saving and back to an object when retrieving.

Here’s a quick look at how to implement a custom cast in Laravel 10+:

PHP
namespace App\Casts;

use App\ValueObjects\EmailAddress;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class EmailAddressCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        return new EmailAddress($value);
    }

    public function set($model, $key, $value, $attributes)
    {
        return $value instanceof EmailAddress ? $value->value : $value;
    }
}

Now, in your User model, you simply add:

PHP
protected $casts = [
    'email' => EmailAddressCast::class,
];

Suddenly, $user->email isn't just a string anymore. It’s an instance of EmailAddress, and you can call $user->email->domain() anywhere in your application.

The Trade-offs

I won't lie—there’s a bit of overhead. You’ll have more files to manage. For a simple CRUD app, creating a separate class for a PhoneNumber or ZipCode might feel like overkill. We once tried to wrap every single primitive in a large legacy project, and it slowed down our development speed significantly for about two weeks while we adjusted.

Start small. Use Value Objects for data that carries specific behavior, like currency, coordinates, or status codes. If it’s just a label, a string is probably fine.

If you find yourself needing to configure these objects cleanly, you might want to look into Mastering Laravel Tap Helper for Cleaner Object Configuration to keep your instantiation logic readable.

Frequently Asked Questions

Are Value Objects the same as DTOs?

Not exactly. A Data Transfer Object (DTO) is usually a structure used to move data between layers. A Value Object contains domain logic—it knows how to validate itself and perform operations based on its value.

Should I use Value Objects for every database field?

No. That leads to "over-engineering." Reserve them for values that have constraints or behavior (e.g., EmailAddress, Money, Duration).

Does this hurt performance?

In PHP, the performance impact of creating these small objects is negligible for 99% of web applications. The gain in maintainability far outweighs the cost of instantiating a few extra objects per request.

Final Thoughts

The goal of writing Clean Code isn't to create the most abstract system possible. It's to make your code speak the language of your domain. When you see EmailAddress in a method signature, you know exactly what to expect.

I’m still refining how I handle complex Value Object collections—sometimes it gets tricky when you need to perform calculations on a list of objects—but I haven't looked back since moving away from primitive obsession. Give it a try in your next feature; your future self will thank you when you’re debugging that edge case two months from now.

Back to Blog

Similar Posts

LaravelPHPJune 22, 20264 min read

Mastering Laravel Tap Helper for Cleaner Object Configuration

Mastering the Laravel tap helper allows you to simplify object configuration and improve PHP method chaining. Learn to write cleaner, more readable code today.

Read more
LaravelPHP
June 21, 2026
5 min read

Mastering Laravel DTOs: Type-Safe Data Handling for Clean Code

Master Laravel DTOs to replace messy associative arrays with type-safe objects. Learn how to write cleaner, more maintainable code in your PHP applications.

Read more
LaravelPHPJune 21, 20264 min read

Laravel helpers: How to build and use custom global functions

Laravel helpers and PHP global functions can drastically simplify your code. Learn how to implement custom helper functions following Laravel best practices.

Read more