DEV Community

Wilbur Powery
Wilbur Powery

Posted on • Edited on

Easily use UUIDs in Laravel

First published on my website

What are UUIDs?

UUID stands for Universal Unique Identifier. It's a 128-bit number used to uniquely identify some object or in our case, a record in our database.

I won't go into depth about the pros or cons of UUIDs when compared to a regular auto incrementing ID, that's an entire blog post of its own. What I will do, is show you how easy it is to use UUIDs in your Laravel application if you wish too.

Prepare your migration

The first step of using UUIDs in your database is setting up your column to be of a specific type. By default, a Laravel migration includes a $table->primary('id'); column in each migration you create.

Using UUIDs is as simple as updating our migration to use the ->uuid() method that Laravel provides out of the box for us. For example, let's say we're creating a migration to describe the schema of a Post.

Schema::create('posts', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
});

The important line you should notice is this one: $table->uuid('id')->primary();. Notice we are using the uuid() method instead of the common increments() method. As you can see, the uuid() method specifies the column should be a UUID equivalent column and we also specify that it should be the primary key on the table.

Creating the UUID

If you run php artisan migrate, and try to create a new record using something like a factory, or manually creating it, you'll run into an error saying the id column cannot be null.

When using the primary() method on our migrations, Laravel will notice it's an auto-incrementing column and whip it up for us. But, since we switched over to using UUIDs, we'll need to create the ID ourselves.

Use Eloquent Model Events

Since you're using Laravel, I imagine you would also be using Eloquent to easily interact with your database.

Eloquent has what are known as Model Events. In total, it fires a total of 11 events in different scenarios. You have the following events:

  • retrieved
  • creating
  • created
  • updating
  • updated
  • saving
  • saved
  • deleting
  • deleted
  • restoring
  • restored.

If you want to learn more about Eloquent Events, you can find their documentation here.

Now, back to creating our UUIDs. Let's take a look at how your Post model could look like:

class Post extends Model
{
    protected $guarded = []; // YOLO

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

        static::creating(function ($post) {
            $post->{$post->getKeyName()} = (string) Str::uuid();
        });
    }

    public function getIncrementing()
    {
        return false;
    }

    public function getKeyType()
    {
        return 'string';
    }
}

Our Model has 3 methods in it. The boot method is where we can hook into our model and listen for any Eloquent events. The getIncrementing method is used by Eloquent to now if the IDs on the table are incrementing. Remember we are using UUIDs so we set auto incrementing to false.
Lastly, the getKeyType method just specifies that the IDs on the table should be stored as strings.

In our boot method, we are listening for the creating Eloquent event. This even is fired just before the record is actually stored in the database. We hook into this event, and use the uuid() method provided by the Str class in Laravel.

A while ago, people might have installed a package with Composer in order to generate a UUID but you can generate them easily using the uuid() method provided by the class.

Easy as that, you can use UUIDs in Laravel.

As I final note, I'll usually have a PHP trait called UsesUuid where I'll have the logic above. That way I wouldn't repeated the code on every model I wanted to use UUIDs.

This is what the UsesUuid trait would look like:

<?php

namespace App\Models\Concerns;

use Illuminate\Support\Str;

trait UsesUuid
{
    protected static function bootUsesUuid()
    {
        static::creating(function ($model) {
            if (! $model->getKey()) {
                $model->{$model->getKeyName()} = (string) Str::uuid();
            }
        });
    }

    public function getIncrementing()
    {
        return false;
    }

    public function getKeyType()
    {
        return 'string';
    }
}

Notice how everything is more generalized and not tied to a unique model.

Now, in any model that as the correct column in its migration you can simply use the UsesUuid trait like so:

class Post extends Model
{
  use App\Models\Concerns\UsesUuid;

  protected $guarded = []; // YOLO
}

That's it. In just a few simple steps you got UUIDs working in your Laravel app.

Top comments (22)

Collapse
 
risingad profile image
Rising • Edited

I am getting issue with sync() method.
I did somthing like:

$page->users()->sync([auth()->id() => ['status_id' => 1]]);
Enter fullscreen mode Exit fullscreen mode

then i got this error:

SQLSTATE[HY000]: General error: 1364 Field 'page_id' doesn't have a default value 
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bodrosh profile image
Bodrosh

I also encountered this problem, if you want to add a uuid to the table additionally when it already has a key, you can do this:

protected static function bootUsesUuid() {
static::creating(function ($model) {
if (! $model->uuid) {
$model->uuid = (string) Str::uuid();
}
});
}

Collapse
 
risingad profile image
Rising
wilburpowery image
I am using laravel version 5.7
Collapse
 
owenandrews profile image
Owen Andrews

Great post Wilbur, thank you. I think you can also just set public $incrementing = false;, which saves a few lines.

Collapse
 
wilburpowery profile image
Wilbur Powery

Thanks Owen. You're right, setting that to false is much easier, thanks for pointing it out.

Collapse
 
guernseygreen profile image
St Pierre d'Guernsey

Using public $incrementing = false; doesn't work as it gives you a dual declaration if you use the trait - stick to getIncrementing

Thread Thread
 
pstevek profile image
Steve Kamanke

That's only if you use do decide to use a Trait. If you're only using it on only one model then you don't need to create a Trait. public $incrementing = false; would work as intended.

Collapse
 
faridsa profile image
farid

Hi Wilbur, nice point. I'm using phpStan to check my code and the trait fires an error in following line:
$model->{$model->getKeyName()} = (string) Str::uuid();

Error:
Cannot cast Ramsey\Uuid\UuidInterface to string

Collapse
 
slipperydippery profile image
slipperydippery

Great post, I didn't know that it was so easy! I am running into a problem with my foreign keys:

SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `groups` add constraint `groups_user_id_foreign` foreign key (`user_id`) references `users` (`id`) on delete cascade)

Any idea on what I'm doing wrong?

Collapse
 
nwosucc profile image
Nwosu Cyprian

Check that both groups.user_id and users.id have same type definition e.g if users.id is UNSIGNED Integer, groups.user_id should be same

Collapse
 
keithmifsud profile image
Keith Mifsud

Great mini tutorial. Thank you for sharing :)

I would only make one point here regarding Dependency Inversion:

When possible try to generate your UUIDs before your domain/source code and not through the database model. Otherwise, your source is dependant on the database.

Collapse
 
yonasgg profile image
Yonas

It isn't working for me. it shows the following error, "Trait 'App\Concerns\UsesUuid' not found"

Collapse
 
applicationerror profile image
ApplicationError

Thank you! This is really great.

I am having issues with pivot tables though... I'm guessing the Trait will need to somehow ensure that pivot tables also get a uuid.

Collapse
 
taelkir profile image
Tom Stanley

Thanks for this, I've come back to this article two or three times in my career now. 😅

Collapse
 
blade93ny profile image
ZettaV

That Trait does NOT work in Laravel 8

Collapse
 
reakmsey profile image
say reakmsey

how to generate with laravel8

Some comments may only be visible to logged-in visitors. Sign in to view all comments.