Storing arbitrary attributes on Laravel Eloquent Models

Storing arbitrary attributes on Laravel Eloquent Models
Photo by C M / Unsplash

More often than not you'll need to store some data on an Eloquent model that isn't possible to be normalised to a format that fits into the database or you already know that the data will be removed again in the near future. Whatever the reasons are, there are packages like spatie/laravel-schemaless-attributes which allow you to store that data in a database columns which stores all data as JSON and then loads it into memory and allows for some nice DX for manipulations but it comes at the cost of an extra database column on every single table where you want to store those schemaless attributes.

This approach didn't work for me on a recent project because there were two dozen models which had these schemaless attributes and I didn't want to add a new column to dozens of tables. That's where faustbrian/laravel-arbitrary-attributes comes in. It allows you to store key-value pairs for any value without having to do more than adding a trait to your models.

<?php

use Faust\ArbitraryAttributes\HasArbitraryAttributes;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasArbitraryAttributes;
}

Once this trait has been added to your model you'll gain access to a few methods to make your life easier when working with these arbitrary attributes.

// Get the value of "key"
$user->getArbitraryAttributes()->get('key');

// Set the "key" value to "value"
$user->getArbitraryAttributes()->set('key', 'value');

// Forget the value of "key"
$user->getArbitraryAttributes()->forget('key');

// Count how many keys there are
$user->getArbitraryAttributes()->count();

// Commit any changes to the database.
$user->getArbitraryAttributes()->commit();

All of the above actions happen in-memory and are not persisted to the database until you call the commit method. This method will execute all commits from the set and forget methods in synchronous order. Depending on the type of commit it'll either execute a delete query or perform an upsert for all attributes that were set.

This approach is a lot more flexible than storing the schemaless attributes in a database column because it enables us to store attributes for a new model type without having to alter the database over and over again. It also allows for proper filtering, sorting and aggregating of those arbitrary attributes because they are just another Eloquent model with all of its power.