Working with Laravel is often a wonderful and refreshing experience. There’s so much thought put into developer happiness and it shows! That’s why it’s so popular.
That said, not every is the same and there can be little things that bother more OCD developer types like me. One of those is the inconsistency between the use of “snake_case” and “camelCase)” with model attributes. What do I mean by that?
Well, to begin, the convention in Laravel is for all database columns to be snake cased. So you access them by doing $model->column_name
. This is already a bit different from your standard PHP coding standard, but that’d be ok if it were consistent.
Things get messy once we add relationships. In Laravel, we define relationships between models using methods. These methods should use camel case to be consistent with PHP coding standards. That said, if you do that, then you need to use camel case to access the related model(s) like this $model->relationshipModel
.
Now, you could define the relationship methods using snake case. But again, this is inconsistent with the rest of PHP. Regardless of which you choose, you end up with something that doesn’t feel quite right.
This issue really bothered me when I started building Ymir. I stumbled on this Reddit thread about it. One answer suggested using constants for columns. I decided to try it out, and it’s solved that problem really well for me.
How does it work?
The idea is simple. Instead of referring to a model attribute or column directly, you use a constant. So let’s say that you have these two models:
namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * Get the blog posts written by this user. */ public function blogPosts() { return $this->hasMany(BlogPost::class); } } class BlogPost extends Model { /** * Get the author of the blog post. */ public function author() { return $this->hasOne(User::class); } }
If you wanted to get a user’s blog posts, you’d do $user->blogPosts
. And vice versa, for the BlogPost
class. If you wanted to get the author of a blog post, you’d do $blogPost->author
. But if your posts
table had a column called published_at
, you’d do $blogPost->published_at
.
namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * The blog posts written by this user. * * @var string */ public const ATTRIBUTE_BLOG_POSTS = 'blogPosts'; /** * Get the blog posts written by this user. */ public function blogPosts() { return $this->hasMany(BlogPost::class); } } class BlogPost extends Model { /** * The author of the blog post. * * @var string */ public const ATTRIBUTE_AUTHOR = 'author'; /** * The timestamp that the blog post was published at. * * @var string */ public const ATTRIBUTE_PUBLISHED_AT = 'published_at'; /** * Get the author of the blog post. */ public function author() { return $this->hasOne(User::class); } }
Here’s our updated models with the attribute constants prefixed with ATTRIBUTE_
. (We’ll discuss the prefix in a bit.) With these constants, you’d do $blogPost->{BlogPost::ATTRIBUTE_AUTHOR}
to get the blog post author. And $user->{User::ATTRIBUTE_BLOG_POSTS}
to get a user’s blog posts.
But you’d also do $blogPost->{BlogPost::ATTRIBUTE_PUBLISHED_AT}
to get the value of a database column. Both column and relationship values look the same. That’s the type of naming consistency that this approach brings to your attribute names.
The advantages and disadvantages
Now, the obvious disadvantage of this approach is that this makes your code a lot more verbose. There’s an enormous difference between writing $blogPost->{BlogPost::ATTRIBUTE_AUTHOR}
and just $blogPost->author
! So for that reason alone, I don’t think this approach will be for everyone. Although, you can make it shorter by using something like BlogPost::ATTR_AUTHOR
.
If the benefit of this approach was just the increased consistency, I think this would be a hard sell. That said, as I’ve used it, I’ve found other benefits to this approach beyond the one that made me try it in the first place. I’ve learned to appreciate these benefits almost as much as the increased consistency.
To begin, you only reference the database column or relationship once. There’s no need to perform a search and replace your entire project to rename all the references. All that you have to do is change the value of the constant and you’re done.
And speaking of search and replace, that’s often a risky thing to do. A lot of models have the same attribute or relationship names. So you can’t just blindly replace everything. You need to be careful, or you’ll introduce bugs. (I did that a few times before I used this method!)
But even if you’re not trying to search and replace an attribute, just searching for an attribute can be tedious. Something like $model->name
or some other common attribute name will yield tons of results to filter through. But with constants, you can just look for Model::ATTRIBUTE_NAME
with your IDE and you’ll find only relevant attribute uses.
Distinguishing between attribute and column
We also often need to reference the database column of attribute. For example, they’re needed for relationships. But you use them with factories too.
So because of that, I usually create another constant for database columns. But I only create those when I need them. And if both the attribute and the column share the same name, I just use the attribute constant.
So going back to BlogPost
model, I’d add this constant if I needed the reference the author
database column.
class BlogPost extends Model { // ... /** * The name of the author column. * * @var string */ public const COLUMN_AUTHOR = 'user_id'; // ... }
I use COLUMN_AUTHOR
to point to the user_id
database column. We can then use that constant with our relationships. Below, I updated the blogPosts
relationship method.
class User extends Model { // ... /** * Get the blog posts written by this user. */ public function blogPosts() { return $this->hasMany(BlogPost::class, BlogPost::COLUMN_AUTHOR); } }
I also like that I don’t have to mix domain naming with database schema things. So with my model, I can keep using author which is a domain specific term instead of user which is the table name. And, on the flip side, I don’t have to name my database column author_id
to keep it consistent with the domain term.
Increasing my developer happiness
So, as you can see, this method of dealing with Eloquent attributes isn’t hard to implement. It comes down more to whether or not you want to do it. You can’t really go at it half way or you’ll end up with an even larger mess.
For me, it’s been a real game changer. I don’t feel stuck between two naming standards anymore. And that’s made working with Eloquent models even more enjoyable.