1

I need to run some code when one of my models is saved (created/updated) or deleted. What's the best way to do that?

There's three different ways that I'm aware of:

  1. Override the save and delete methods on the model
  2. Add creating/updating/deleting callbacks in the boot method
  3. Bind an observer in the boot method

I haven't seen these compared and contrasted, so I don't know what the differences are. I'm worried that the events won't fire under certain conditions.

For example, in Django, deletes only fire if you delete the models one-by-one, but not in a mass delete.


To be clear, I'm looking for answers that compare and contrast these (or other) methods -- not simply suggest even more ways of doing the same thing.

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Same as in Django with Eloquent: "When executing a mass delete statement via Eloquent, the deleting and deleted model events will not be fired for the deleted models. This is because the models are never actually retrieved when executing the delete statement." – jakub wrona Oct 16 '16 at 20:09
  • @jakubwrona So none of the 3 methods will support mass deletes? OK. Is there anything else I should be aware of? Are there differences in efficiency? Are any of them more robust/less likely to break than the others? Or is it just a matter of preference? Why so many ways to do the same thing? – mpen Oct 16 '16 at 20:13

5 Answers5

3

It's just my opinion for several methods you mention previously.

  1. Override the save and delete methods on the model ( If you override it then next update of Laravel change visibility of method your code does not work again. It would throw Exception or PHP error. You have to modify it to work again )
  2. Add creating/updating/deleting callbacks in the boot method ( exist in Laravel 4 you should check it again in Laravel 5 maybe different implementation using Event and Listener )
  3. Bind an observer in the boot method ( exist in Laravel 4 you should check it again in Laravel 5 maybe different implementation using Event and Listener )

I think you should using Event and Listener provided by Laravel. It maybe still work on next Laravel Update. I assume Event and Listener as minor change area in Laravel and changed maybe just different method implementation.

Laravel should have plan of development assign which part of Laravel will be developed as major change area ( big modification ) or minor change area ( little modification ). If you try to change or override major change area it would can't be used on next Laravel Update.

You can register Event and Listener for save and delete record. Laravel have fireModelEvent method on Model ( Illuminate\Database\Eloquent\Model ) which trigger specific Laravel Event. If you've registered Event, Dispatcher ( Illuminate\Events\Dispatcher ) will execute Listener of Event.

Documentation about Laravel Events:

https://laravel.com/docs/5.3/events

https://laravel.com/docs/5.2/events

I assume you have YourModel as Model then do the following action on the below.

  • Register Event and Listener. Open app\Providers\EventServiceProvider.php then Add Event and Listener to EventServiceProvider.listen properties for YourModel or follow Laravel Documentation to create event using other way.

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        ...
        'eloquent.saved: App\YourModel' => [
            'App\YourModel@eventSaved',
        ],
    ];
}

  • Adding eventSaved method on App\YourModel as Listener for Event so you can do specific action after save or delete.

class YourModel extends Model
{
    public function eventSaved(){
        // You can add your code to catch save here
    }
}

Joko Wandiro
  • 1,957
  • 1
  • 18
  • 28
1

You can create event handlers, for every create/update of model, for example to add to cache the model data which is just saved to database or going to save to database, easier to retrieve without select query call, while delete call, use forget for given key on cache handler event to delete cache as well as to delete from database too.

codebob
  • 81
  • 5
1

I'm partial to doing things manually when you need to know exactly how they're done. I recently used this Laravel Boilerplate to start a project and I like the way they manually fire events in the repository when a model is updated:

https://github.com/rappasoft/laravel-5-boilerplate/blob/master/app/Repositories/Backend/Access/User/EloquentUserRepository.php

Since models should always be updated through the repository, you always get to manually decide how events are handled. You could fire your own event when multiple models are deleted, and act accordingly. All of your options will work though, you just need to find the option that suits your needs best.

Jeff
  • 24,623
  • 4
  • 69
  • 78
1

You can create abstract Model class that extends Illuminate\Database\Eloquent\Model class and all your model will extend this class. With implementation like this you can have more control on the models. For example

<?php

namespace App\Base\Database;


use Illuminate\Database\Eloquent\Model as BaseModel;

abstract class Model extends BaseModel
{
    public function save(array $options = [])
    {
        //your code here
        return parent::save($options); 
    }
}

You can do this for all the methods of the Model class and also you can add additional methods that are relevant for all models in your application

Kliment
  • 2,250
  • 3
  • 18
  • 32
  • Right, but that would fire for all models -- I'm only interested in one particular model. – mpen Oct 18 '16 at 18:22
  • @mpen if you want this implementation on only one model you can override the save function just like in my example in your specific model. – Kliment Oct 18 '16 at 18:27
  • Indeed. That was option #1 in my question. I'm more interested to know if this has any advantages over the other 2 methods. – mpen Oct 18 '16 at 18:30
  • 1
    @mpen i use this option because my code is cleaner, every logic that is tied with the specific model is easy to find and i always have follow the same pattern so i don't have to remember why my model behaves like that because i know exactly where to find it. – Kliment Oct 18 '16 at 18:38
  • @mpen also i always use the implementation in my answer because it gives me flexibility for unpredictable behavior in my applications – Kliment Oct 18 '16 at 18:48
1

The three methods and 4th referred by @joko. There may be more as well but lets focus on the 4 methods.

Let me describe you them one by one:

1) Override the save and delete methods on the model

In this method you are using OOPD method overriding. You are overriding Laravel's interal save method and adding your additional code by defining your own save method on top of it. This should be avoided as Laravel keep evolving and it may happen that thing start to fail if major change is done like Suppose in future laravel replace save method with any other method to save the records. Then again you will have to create another method to override that new method. Also writing code here may grow your model class file. You model may keep handling things like he shouldn't handle(Example: Sending Email). This method should be avoided.

2) Add creating/updating/deleting callbacks in the boot method

Here you are defining code on the Boot method of the Model. This method should only be used if there is much little code/things that you need to handle on event. The drawback of this method is that it make code more complicated and cluttered as you may write all logic in one like like functional programming. Suppose if you have to do some stuff on before creating and after created. You boot method will grow.

3) Bind an observer in the boot method

This method is pretty good. You create one observer class which handles such stuff that what should happen on Laravel events. It makes code more cleaner and easy to maintain.

Example: Suppose you have to write code in creating, saving, saved, deleting in these methods. In this case, method 1) and method 2) won't be good practice because in

Method 1: We will have to create this 4 methods and override them as well and support them in future releases of Laravel. In this case, code in your Model will also grow because of overriding this methods

Method 2: In this case your boot method will grow as well so you Model file will become a junk of code.

In method 1 and 2 also remember that its not responsibility of your Model to do many of the stuff that you going to write. Like sending email when user is created. These codes you may end up writing in created method.

Suppose now you have scenario where you need to send email to user on created event as well as you need to make user's entry log user in customer CRM. then you will have to write code for both in same method. Probably, you may not following single responsibility principle there. What should we do in the case? See method 4.

4) Other method suggested by @joko

The scenario that i suggested in method 4's end. You may send email to user and log him in Customer CRM whenever it is created. Then your method will do 2 things(Sending email and logging in CRM). It may not following single responsibility principle. What if better we can decouple both of them. Then comes this method.

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'eloquent.saved: App\User' => 'App\Listeners\SendWelcomeEmailToUser'
        'eloquent.saved: App\User' => 'App\Listeners\LogUserInCRM'
    ];
}

Create two listener classes:

class SendWelcomeEmailToUser
{
    public function handle(User $user){
        // Write code to send email
    }
}

class LogUserInCRM
{
    public function handle(User $user){
        // Write code to log
    }
}

Through this you can separate out codes and make them more cleaner.

I generally prefer this method its mode clean. It also gives you much better idea that what actually happen when event happens. It becomes one single point for Event to Listener mapping.

Community
  • 1
  • 1
Bhaskar Dabhi
  • 841
  • 1
  • 11
  • 28