Value objects are small objects, like money, strings, dates or date ranges. Their equality is based on state, not on identity; two value objects are equal when they have the same value.
Here is an example of a value object:
The value object encapsulates behavior and communicates intent.
Value objects should also be immutable. If you want a different value, create a new object. You can thus share value objects without worrying about one part of the application affecting the other. If you add one
Price to another, you get a completely new object.
Using value objects together with Eloquent can be a bit tricky. There are several implementations that result in unwanted behavior: value objects getting wrapped in other value objects, rules not being enforced, and limitations being set on the use of value objects that consist of more than one field.
To work around this we must first understand how Eloquent behaves. You should be familiar with Eloquent’s accessors and mutators and be aware of the following:
In order for a model’s attribute to be saved to the database it must be stored in the Eloquent model’s
$attributes array. The
setAttribute() method is responsible for saving attributes to this array.
There are many ways to create a new Eloquent model instance, but
setAttribute() is eventually called in almost every case: The constructor,
firstOrCreate() — all of these methods ultimately call
setAttribute() on each of your attributes.
setAttribute() is also “magically” called when you directly set the attribute on the model’s instance, e.g.
$product->price = 1000, if the property hasn’t been explicitly defined on the model, or isn’t visible in the current scope.
setPriceAttribute() is called when attempting to set the value of the
price attribute on the model: e.g.
$model->price = 1000;.
getPriceAttribute() is called when attempting to retrieve the value of
However, the mutator/accessor will not be called if the property is explicitly defined on the model and is visible in the current scope. For example, if the property is defined as
public, or if it’s
private and referenced within the class itself, the accessor/mutator will not be invoked.
With this in mind let’s create an example Eloquent model.
We will still access the attributes directly, like $model->price, instead of using setters and getters. We will also add a named constructor (
addProduct()) just for fun.
We could also store the
$price value object directly on our model.
If you make it
public, any user of your class will be able to bypass the logic that maps the price fields to the database (only
price_amount in this case). Doing
$product->price = 'blah'; will not trigger the mutator.
However, making it
$price invisible from the outside and forces Eloquent to call the mutator instead. But the mutator will not be called if you set the price within the class itself. In this case you will need to manually call the mutator
setPriceAttribute() or create a setter method.
You can create additional enforcements if you’d like. Just remember that there are still Eloquent methods that let you bypass the value object (e.g
It’s not a pretty solution. When using Eloquent, your “model” is tied to the persistence layer and there’s no way around it. This is how the Active Record Pattern works. If you decide to use it you should either embrace it or consider something else.