As per Laravel documentation1, the
morphOne relationships allow us to define a default model if no related model is found. This pattern is known as
Null Object Pattern in software engineering world. This can be very useful from time to time and it is very easy to achieve, thanks to the eloquence of Laravel.
Consider the following polymorphic relationship between the
User and the
File model for a profile picture.
Aside from traditional
$this->morphOne(File::class, 'fileable'), we are returning an empty
File object using
withDefault() method if no corresponding profile picture is found for the user. Let’s take a look inside our
Here we have pretty standard stuff. Some attributes in the
$fillable property, the polymorphic relationship
fileable() and an accessor
url to sort out the actual URL from the path of the file.
Without doing any additional thing to the
withDefault() method in
User model relationship, we would get an empty File model. Sometimes, this is enough. But often we need more than that. For example, in this case, we would want to generate some sort of SVG for missing profile picture which is a standard practice. We can do so:
We can pass an array inside
withDefault() with the attributes we want to populate. For the
url attribute, we are using my SVG Avatar Generator library for Laravel to generate an SVG on the fly based on the string we are passing.
But “Houston, we have a problem”2. All of our users now have same profile picture cause we are passing same “Default User” string. That’s underwhelming. How can we make it more exciting?
Turns out, Laravel makes it very effortless (yeah, the level of attention to detail is 🤯). Instead of array, we can pass a closure to the
withDefault(). The closure receives two parameters; first, the related model; and second, the parent model:
Great! Now all users have customized SVGs based on their names. 💅
urlbut in the closure we set the value of
path. I don’t like to set an accessor value directly, I would rather generate the value based on the path. This is just a personal preference. If you do something like
$file->url = Svg::for($user->name)->toUrl()it would work as well.
Also, we have to modify the
url accessor on the
File model slightly. Since we are setting the URL directly in
path, we can no longer just call
Storage::url() over another URL. There should be a check if the
path is a file path or a URL. For this we can use Laravel’s
That’s all. Now if we set a URL from the parent relation, it will not be accidentally overwritten by the accessor. Note that, there is no additional trickery outside Laravel, it’s just simple things like this what makes the framework beautiful.