Software Design Enthusiast And Engineer

Eloquent Models 🤝 PHP Constants

3 min read
Status: Completed Read year: 2024

For those out there that are working with codebases that have been around for some time, you've likely come across databases where the column names don't represent all the beautiful snake-cased columns you tend to see throughout the video tutorials online. This is often due to years of having developers come and go, or there's 1 or 2 developers and they could care less, etc. ​etc.

This idea actually originated prior to me having come up against these types of problems and, at the time, I was more interested in having a single source of truth for what was just a bunch of strings throughout the apps I was writing. And me being someone who relies heavily on TDD and refactoring, I've always given basic names to columns as a starting point. I would get a better sense of what the column names should be as time went on.

There's very little overhead when using this approach to constants. In PHPStorm, it's a simple SHFT+F6 and I'm renaming the constant throughout all files that are using it. In other frameworks or libraries, like Doctrine, this is more of a trivial thing, but in Laravel it's one of those paper cuts you never realized you had. You can not only rename the constant in one place and it updates throughout the codebase, if you want to change the actual name of the column, it's dead simple. You just need to change the value of the constant.
There's also built-in IDE completion that you now have. You can just start typing your model, e.g. \App\Models\User::, then cycle through all the columns that your IDE is now aware of. This makes them very quick and easy to ​work with. For example, if I know there's a​ User column that contains 'photo', I can just type: User::PH, then the IDE will do the rest, bringing that PROFILE_PHOTO_URL constant right to the top of the (auto-completion) list.

A few examples being:

in creating models

User::create([
    User::FIRST_NAME => 'john',
    User::LAST_NAME => 'doe', 
    User::EMAIL => 'johndoe@example.com', 
    // ... 
]);

in migrations

Schema::create(User::TABLE, function (Blueprint $table) {
    $table->ulid(User::ID)->primary();
    $table->string(User::FIRST_NAME);
    $table->string(User::LAST_NAME);
    
    //... etc.
});

in getters on the models themselves

class User extends Model 
{
    public const string EMAIL = 'loginid';
    
    public const string TABLE = 'web_users';
    
    protected $table = self::TABLE;
    
    
    public function email(): ?string
    {
        return $this->getAttribute(self::EMAIL);
    }
}

A couple things to note here:

  1. Notice how we're actually able to capture the intent of whatever the developer was thinking at the time when they decided to name the 'email' column 'loginid'.
  2. Using these getters in the models provides one more layer of flexibility.
  3. Having to new-up a model every time I've wanted to get the name of a table has always seemed weird to me. As a side note, never use ​the static ::make() method on models either. This instantiates them twice. Using constants, we've exposed the table name of the model to anywhere in the app. This is a nice little added benefit of relying on constants.

By now you may be wondering, doesn't this bloat up your models with all the other things that get thrown on the model??

Well, Eloquent models do come with a a lot of features out of the box and it's easy to get carried away with by throwing all kinds of logic on them. This topic will ultimately require a whole new series of blog posts, but ultimately, just be 'defensive' about what goes in your models. This often means you'll use 'pass-through' methods which defer logic to other classes within your project.