Using UUID and ULID in Laravel Eloquent

Updated: January 16, 2024 By: Guest Contributor Post a comment

Introduction

When developing web applications, using unique identifiers for database records is crucial. Traditionally, databases use auto-increment integers for primary keys. However, as applications scale and distributed systems become more common, the need for more robust solutions arises. Enter UUID (Universally Unique Identifier) and ULID (Universally Unique Lexicographically Sortable Identifier). This tutorial will guide you on how to use UUIDs and ULIDs in Laravel’s Eloquent.

What are UUIDs?

UUID is a 128-bit number used to uniquely identify information in computer systems. The chance of a collision (two generated UUIDs being the same) is incredibly low. UUID comes in different versions; for the purpose of this tutorial, we’ll be focusing on version 4 (random).

What are ULIDs?

ULID is a newer identifier, similar to UUID, but has benefits such as lexicographic sorting, which means they can be sorted alphabetically while preserving the order in which they were created. ULIDs are encoded in 26 characters, compared to UUID’s 36 characters, making them more compact for storage.

Setting Up Laravel & Eloquent Models

Before we dive into implementing UUIDs and ULIDs, ensure you have a Laravel project set up and running correctly. Create a new Eloquent model that we’ll use for demonstration.

php artisan make:model Product

This creates a new model and, if required, a migration file.

Using UUIDs in Laravel Eloquent

Let’s start by modifying the migration file for our new Product model to use UUIDs.

<?php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

class CreateProductsTable extends Migration
{
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('products');
    }
}

Next, modify the Eloquent model to include a method for setting the UUID on creation:

<?php

namespace App;

use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Support\\Str;

class Product extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';

    protected static function boot()
    {
        parent::boot();
        static::creating(function ($model) {
            if (empty($model->{{id}})) {
                $model->{{id}} = Str::uuid();
            }
        });
    }
}

Create a new product in the controller, and it will be assigned a UUID automatically:

$product = Product::create(['name' => 'Example Product']);
// $product->id would be a UUID like e.g., '123e4567-e89b-12d3-a456-426614174000'

Using ULIDs in Laravel Eloquent

For ULIDs, the process is very similar. Modify the migration file to expect a string as the primary key:

...
$table->string('id', 26)->primary();
...

In our Product model, we will auto-generate the ULID:

use Ulid\\Ulid;

protected static function boot()
{
    parent::boot();
    static::creating(function ($model) {
        if (empty($model->{{id}})) {
            $model->{{id}} = (string) Ulid::generate();
        }
    });
}

Create a new product in the controller and observe the ULID:

$product = Product::create(['name' => 'New Product']);
// $product->id would be a ULID like e.g., '01F8MMBCVGACEVB4BC23T4HE6W'

Querying by UUIDs and ULIDs

Finding records by using UUIDs or ULIDs is as simple as querying by any other field:

$product = Product::find('01F8MMBCVGACEVB4BC23T4HE6W');
// If exists, $product will contain the product with that ULID

Additional Concerns

When using UUIDs and ULIDs, especially indexing and performance on large datasets can be a concern. To alleviate these, consider:

  • Using UUID version 1 if ordering based on time of creation is needed.
  • Index optimization by using appropriate database types or binary storage for UUIDs.

Advanced Example

Creating a Laravel migration that auto-generates ULIDs or UUIDs as primary keys directly through database engine functions requires a bit of customization. I’ll provide you with an example for both scenarios. Keep in mind that database-level UUID or ULID generation can depend on the specific database you are using (MySQL, PostgreSQL, etc.), and ensuring cross-database compatibility might require conditional logic based on the database type.

Example for UUIDs

Here’s how you might create a migration for a table with a UUID primary key in Laravel. This example assumes your database engine has native support for UUIDs.

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateYourTableNameTable extends Migration
{
    public function up()
    {
        Schema::create('your_table_name', function (Blueprint $table) {
            $table->uuid('id')->primary(); // Using UUID as primary key
            $table->string('name');
            // other fields...
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('your_table_name');
    }
}

Example for ULIDs

For ULIDs, it’s slightly more complex as most databases do not have native ULID functions. You might need to handle the generation in your application code (e.g., in your model’s creating event). However, here’s a basic migration setup for a ULID as a primary key:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateYourTableNameTable extends Migration
{
    public function up()
    {
        Schema::create('your_table_name', function (Blueprint $table) {
            $table->char('id', 26)->primary(); // ULID is 26 characters long
            $table->string('name');
            // other fields...
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('your_table_name');
    }
}

In your Eloquent Model, you would then handle the generation of the ULID:

use Illuminate\Database\Eloquent\Model;

class YourModel extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            if (empty($model->{$model->getKeyName()})) {
                $model->{$model->getKeyName()} = (string) \Ulid\Ulid::generate(); // Assuming you are using a ULID package
            }
        });
    }
}

Notes

  1. Cross-Database Compatibility: If you need to ensure cross-database compatibility, consider generating UUIDs or ULIDs in the application layer rather than at the database level.
  2. Database-Specific Features: Some databases like PostgreSQL have built-in support for UUIDs and might have specific functions you can use. For others like MySQL, you’ll typically handle UUIDs as strings.
  3. Performance Considerations: Using UUIDs or ULIDs as primary keys can have performance implications, particularly on large datasets. It’s important to consider indexing and the storage size of these identifiers.

Conclusion

In conclusion, integrating UUID and ULID into Laravel Eloquent elevates the capability to handle unique identifiers in distributed systems, aids scaling, and ensures that conflicts are virtually non-existent.