10

The Problem

I would like to automatically add created_by and modified_by fields to every insert/update to a database table in Laravel 4, regardless of whether I am using Eloquent or Query Builder. However, not all my tables have these fields so any solution will have to check these columns exist before adding.

Attempted Solution

I have extended the Illuminate\Database\Eloquent\Model class and written an overwrite method save() in order to add some additional meta data fields for every record that is saved.

This is fine except that if I perform an insert using the Query Builder then this is bypassed. Looking at the Model class it appears that the database operations are actually done using the query builder.

I have had a look at the Illuminate\Database\Query\Builder model and it looks like I could probably write overwrite methods for insert() and update().

Is this a sensible way to go about performing some task for every insert/update or will I run into trouble later down the line?

Antonio Carlos Ribeiro
  • 86,191
  • 22
  • 213
  • 204
rgvcorley
  • 2,883
  • 4
  • 22
  • 41

5 Answers5

12

Adding to the above answers. You could do something like this.

Create a class in app/models called BaseModel.php extending \Eloquent

class BaseModel extends \Eloquent{

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

    static::creating(function($model)
    {
        //change to Auth::user() if you are using the default auth provider
        $user = Confide::user();
        $model->created_by = $user->id;
        $model->updated_by = $user->id;
    });

    static::updating(function($model)
    {
        //change to Auth::user() if you are using the default auth provider
        $user = Confide::user();
        $model->updated_by = $user->id;
    });
  }

}

Then in your individual model classes you need to extent the BaseModel instead of \Eloquent

class Product extends BaseModel {

    protected $table = 'product';

    //Booting the base model to add created_by and updated_by to all tables
    public static function boot()
    {
        parent::boot();
    }

}

Now any time you save or update a model, the created_by and updated_by fields would be updated automatically.

Note: This would only work when save or update is done through Eloquent. For query builder, you could have a common method to fetch and append the created_by and update_by column updates.

Mohamed Azher
  • 411
  • 5
  • 3
  • Perfect! This is exactly what I needed :) – Andrew Fox Aug 25 '16 at 04:39
  • How do I catch the update, create, delete event from queryBuilder? These methods you provided is only triggered when using Mode to update, create, delete. This will not work if Model delete something with relations. – Kreedz Zhen Dec 06 '19 at 03:47
7

You must never override the save method to override and add your functionnality.

You have to use the Model Events functionnality that eloquent provides to do that instead.

To put it simply, you have to define a saving event for you model to override/set/check data that the model is going to save.

A simple example to put in a User model class:

//Executed when loading model
public static function boot()
{
     parent::boot();

     User::creating(function($user){
         $user->value1 = $user->value2 +1;
     });
}

More information: http://four.laravel.com/docs/eloquent#model-events

Atrakeur
  • 4,126
  • 3
  • 17
  • 22
  • I think there is a confusion between models and Schema. A schema define the table at conception time. A model define the data that is inside this table at runtime. You can check if a model has a particular attribute, or create differents models for the differents tables to be on the safe side. – Atrakeur Aug 23 '13 at 09:29
  • 1
    This solution isn't suitable - I want this to work with Query Builder AND Eloquent, the events only work with models, i.e. Eloquent – rgvcorley Aug 23 '13 at 09:39
  • 1
    Also, if I call the parent `save()` method after adding my functionality, what is wrong with extending the core in this way? Is this not the whole point of using an OO framework? I moved from CI because I was sick of having to write hacks to extend the core rather than just doing a simple `extend`. The model events are just as bad as CI hooks in my eyes... – rgvcorley Aug 23 '13 at 09:46
  • I don't really get in which case you'll have to insert or update data directly with the query builder when something as powerfull as eloquent is available to do that for you. Also, if you extends the model, your modification isn't available in the query builder because you'll have to extends the query buildert too. I think you're trying to reinvent the wheel here. Can you clarify your use-case in your question so that we can find a way around? – Atrakeur Aug 23 '13 at 09:47
  • 1
    Updated the question. What if another programmer writes a quick and dirty addition to my application without knowing that I use model events to add these metadata fields and uses Query Builder to do the insert? – rgvcorley Aug 23 '13 at 09:55
  • If it's a good programmer, it'll correctly unit-test his solution and see that the fields arn't updated. If it's a bad programmer, then his solution will not work correctly and that's all. You can't prevent all the bad things other programmers (or yourself) can do. All you can do is unit-test your own code and well document this special feature. Also, eloquent provide created_at and updated_at fields in a build-in functionnality. Try to use that functionnality or at least check how it is implemented. – Atrakeur Aug 23 '13 at 10:03
  • Exactly what @Atrakeur said, you can't really determine which model is being updated from Query Builder anyway, so the solution would be rather hacky even if you did extend the Core. – Ben Aug 23 '13 at 10:04
  • You can't determine the model, but you can determine the table - `Illuminate\Database\Query\Builder->from` holds the target table name – rgvcorley Aug 23 '13 at 10:09
  • @Atrakeur but if I don't use events and extend the core so it works with Query Builder as well, then this problem will never arise! – rgvcorley Aug 23 '13 at 10:11
  • 3 steps of programming: Make it work, make it right, make it fast... You are at the first step, make it work... – Atrakeur Aug 23 '13 at 10:20
  • I'm actually in the second step - the update is working with the meta fields if I use eloquent to update - now I want to make it right by allowing either eloquent or query builder to be used... – rgvcorley Aug 25 '13 at 16:38
  • @Atrakeur In this case the Model will use the queryBuilder to update data. user->hasMany(sites), then $user->sites()->delete(), in this case Laravel will user queryBuilder to delete the record. delete * where something from sites table – Kreedz Zhen Dec 06 '19 at 03:51
5

In Laravel if you like to call a single method on each save/update from a single point without making any additional changes in each of the extended Models, you can have a custom listener for eloquent events. As documetation says it can be done only for per Model. But creating a custom listener allows to access any event in any Model.

Just add a listener to the boot() method in EventServiceProvider like below and modify accordingly.

Event::listen(['eloquent.saving: *', 'eloquent.creating: *'], function($event){
        //your method content
        //returning false will cancel saving the model
 });

Please note that wildcard used to match any model. See documentation for more on events.

ruwan800
  • 1,567
  • 17
  • 21
2

If you want to use Query Builder, and Eloquent the only way around this without extending the Core Components (which I don't deem necessary), you can just use the Event System.

Link: http://laravel.com/docs/events

So you'd use an event such as user.custom.save, then create a function for use with the query builder which at the end would trigger this event, same as with Eloquent.

Example:

class User extends Eloquent
{
    public function save()
    {
        Event::fire('user.custom.save', array($this));
        parent::save();
    }
}
Ben
  • 270
  • 1
  • 11
0

You can use venturecraft revisionable package because in a table from this package all info that you need is already stored, you just need this package to get them in an elegant way : https://github.com/fico7489/laravel-revisionable-upgrade

fico7489
  • 7,931
  • 7
  • 55
  • 89