DEV Community

Cover image for Automatically Update `created_by` and `updated_by` in Laravel Using Bootable Traits
hasanmn
hasanmn

Posted on

Automatically Update `created_by` and `updated_by` in Laravel Using Bootable Traits

If you use timestamps feature in Laravel, then you got the free feature of auto-update on created_at and update_at columns when the model is created or updated. Now, what if you want the same thing for you own customs created_by and updated_by columns? You want them to be automatically updated with the logged in user's id. Well, you can use observer or hook into model's lifecycle in boot or booted methods of the model. Let's use example of Post model as follows:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{

    protected static function boot()
    {

        parent::boot();

        // updating created_by and updated_by when model is created
        static::creating(function ($model) {
            if (!$model->isDirty('created_by')) {
                $model->created_by = auth()->user()->id;
            }
            if (!$model->isDirty('updated_by')) {
                $model->updated_by = auth()->user()->id;
            }
        });

        // updating updated_by when model is updated
        static::updating(function ($model) {
            if (!$model->isDirty('updated_by')) {
                $model->updated_by = auth()->user()->id;
            }
        });
    }

}
Enter fullscreen mode Exit fullscreen mode

In the above code, we hook into creating and updating lifecycle of the Post model. This works perfectly and is certainly convenient if you have only few models to apply this feature to. But what if you have many models? You have to apply the above code to all the models. Yes, it will work, but you know, it is not DRY.

The bootable trait, which is also used by Laravel's SoftDeletes trait, can save you a ton in this situation. Laravel' Eloquent model will boot a trait's method with the name of pattern boot[TraitName]. If you take a look into Laravel's source code, you can find the snippet below.

    /**
     * Bootstrap the model and its traits.
     *
     * @return void
     */
    protected static function boot()
    {
        static::bootTraits();
    }

    /**
     * Boot all of the bootable traits on the model.
     *
     * @return void
     */
    protected static function bootTraits()
    {
        $class = static::class;

        $booted = [];

        foreach (class_uses_recursive($class) as $trait) {
            $method = 'boot'.class_basename($trait);

            if (method_exists($class, $method) && ! in_array($method, $booted)) {
                forward_static_call([$class, $method]);

                $booted[] = $method;
            }

        }
    }
Enter fullscreen mode Exit fullscreen mode

The above code tells us the trait with the name of boot[traitName] will also be invoked when the model is booted.

Knowing this, we now can create a new trait called CreatedUpdatedBy with a method of bootCreatedUpdatedBy as follows.

<?php

namespace App\Traits;

trait CreatedUpdatedBy
{
    public static function bootCreatedUpdatedBy()
    {
        // updating created_by and updated_by when model is created
        static::creating(function ($model) {
            if (!$model->isDirty('created_by')) {
                $model->created_by = auth()->user()->id;
            }
            if (!$model->isDirty('updated_by')) {
                $model->updated_by = auth()->user()->id;
            }
        });

        // updating updated_by when model is updated
        static::updating(function ($model) {
            if (!$model->isDirty('updated_by')) {
                $model->updated_by = auth()->user()->id;
            }
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, we can easily use it in any model that we want to apply it to just like when we use SoftDeletes trait. For example in our Post method, it should be looked like this.

<?php

namespace App\Models;

use App\Traits\CreatedUpdatedBy;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use CreatedUpdatedBy;

}
Enter fullscreen mode Exit fullscreen mode

Whenever the Post model is created or updated, the created_by and/or updated_by will be automatically updated. Cool! Isn't it? 😎

Note that the above is just one example of using bootable traits in the Eloquent model to automatically update created_by and updated_by. However, there are many applications of it. For example, you can use it to automatically delete related models, update slugs, and many others you can think of.

Top comments (9)

Collapse
 
arxeiss profile image
Pavel Kutáč • Edited

Thank you. This is really cool. However, I would like to point 2 things here, that might be useful for others and what stops me from using @lemyskaman Laravel-blame package.

  1. I have model, which might be inserted by user, but also by background job. So I would replace getting user ID by this auth()->user()?->id.
  2. It miss the deleted_by for soft delete tables. Below is the code which is required to support deleted_by too.
static::deleting(function ($model) {
    if (
        in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses_recursive(static::class), true)
        && !$model->isDirty('deleted_by')
    ) {
        $model->deleted_by = auth()->user()?->id;
    }
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lucasistak profile image
Lucas Istak • Edited

The "deleting" event does not automatically update the model before it is soft deleted.

I found a PR that was closed by Taylor Otwell, not merging the correction into master:
github.com/laravel/framework/pull/...

Did you guys manage to implement this suggestion from @arxeiss ?

EDIT: I'm using Laravel 11.x version and still doesn't work.

Collapse
 
polin_wei profile image
Polin Wei

You can use the code below

        // updating deleted_by when model is deleted while its used SoftDeletes
        static::deleting(function ($model) {
            if (
                in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses_recursive(static::class), true)
                && !$model->isDirty('deleted_by')
            ) {
                $model->forceFill([
                    'deleted_by' => auth()->user()->id,
                ])->save();
            }
        });
Enter fullscreen mode Exit fullscreen mode
Collapse
 
arxeiss profile image
Pavel Kutáč

I had some troubles too. So I stopped using that and hardcoded into model. Because I need that only at 1 model.

Collapse
 
lemyskaman profile image
lemys lopez

been a while since this, but this is absolutely a good idea, you can help me improve the laravel blame thing its open for every one

Collapse
 
__c103754b481da20e0cbd profile image
秉生 楊

I have a question
when i use
$orderItem->book()
->update([
'stock' => 1
]);

your idea can't automatically update updated_by
I know the reason, but any idea can make it work

Collapse
 
lemyskaman profile image
lemys lopez

You can also use an easy to use laravel package that implement all the techniques above in a redistributable way github.com/kamansoft/laravel-blame very usefull

Collapse
 
hasanmn profile image
hasanmn

thanks, will take a look!

Collapse
 
johninkesta profile image
John

Great stuff! Thanks!