Laravel Model Events - I'm a bit confused about where they're meant to go

后端 未结 9 791
长发绾君心
长发绾君心 2020-12-07 13:15

So the way I see it is that a good Laravel application should be very model- and event-driven.

I have a Model called Article. I wish to send email alert

相关标签:
9条回答
  • 2020-12-07 13:43

    You can opt for the Observer approach to deal with Model Events. For example, here is my BaseObserver:

    <?php 
    namespace App\Observers;
    
    use Illuminate\Database\Eloquent\Model as Eloquent;
    
    class BaseObserver {
    
        public function saving(Eloquent $model) {}
    
        public function saved(Eloquent $model) {}
    
        public function updating(Eloquent $model) {}
    
        public function updated(Eloquent $model) {}
    
        public function creating(Eloquent $model) {}
    
        public function created(Eloquent $model) {}
    
        public function deleting(Eloquent $model) {}
    
        public function deleted(Eloquent $model) {}
    
        public function restoring(Eloquent $model) {}
    
        public function restored(Eloquent $model) {}
    }
    

    Now if I am to create a Product Model, its Observer would look like this:

    <?php
    namespace App\Observers;
    
    use App\Observers\BaseObserver;
    
    class ProductObserver extends BaseObserver {
    
        public function creating(Eloquent $model)
        {
            $model->author_id = Sentry::getUser()->id;
        }
    
        public function created(Eloquent $model)
        {
            if(Input::hasFile('logo')) Image::make(Input::file('logo')->getRealPath())->save(public_path() ."/gfx/product/logo_{$model->id}.png");
        }
    
        public function updating(Eloquent $model)
        {
            $model->author_id = Sentry::getUser()->id;
        }
    
        public function updated(Eloquent $model)
        {
            if(Input::has('payment_types')) $model->paymentTypes()->attach(Input::get('payment_types'));
    
            //Upload logo
            $this->created($model);
        }
    }
    

    Regarding listeners, I create an observers.php file inside Observers dir and I include it from the AppServiceProvider. Here is a snippet from within the observers.php file:

    <?php
    
    \App\Models\Support\Ticket::observe(new \App\Observers\Support\TicketObserver);
    \App\Models\Support\TicketReply::observe(new \App\Observers\Support\TicketReplyObserver);
    

    All of this is regarding Model Events.

    If you need to send an e-mail after a record is created, it would be cleaner to use the Laravel 'other' Events, as you will have a dedicated class to deal with just that, and fire it, when you wish, from the Controller.

    The 'other' Events will have much more purpose as the more automated your app becomes, think of all the daily cronjobs you will need at some point. There will be no more cleaner way to deal with that other than 'other' Events.

    0 讨论(0)
  • 2020-12-07 13:44

    You can have multiple listeners on an event. So you may have a listener that sends an email when an article is updated, but you could have a totally different listener that does something totally different—they’ll both be executed.

    0 讨论(0)
  • 2020-12-07 13:44

    I might come after the battle, but If you do not want all the fuss of extending classes or creating traits, you might want to give a try to this file exploration solution.

    Laravel 5.X solution

    Beware the folder you choose to fetch the models should only contain models to make this solution to work

    Do not forget to add the use File

    app/Providers/AppServiceProvider.php

    <?php
    
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use File;
    
    class AppServiceProvider extends ServiceProvider
    {
        /**
         * Bootstrap any application services.
         *
         * @return void
         */
        public function boot()
        {
            $model_location = base_path() . '/app'; // Change to wherever your models are located at
            $files = File::files( $model_location );
    
            foreach( $files as $data ) {
                $model_name = "App\\" . pathinfo($data)['filename'];
    
                $model_name::creating(function($model) {
                    // ...  
                });
    
                $model_name::created(function($model) {
                    // ...  
                });
    
                $model_name::updating(function($model) {
                    // ...  
                });
    
                $model_name::updated(function($model) {
                    // ...  
                });
    
                $model_name::deleting(function($model) {
                    // ...  
                });
    
                $model_name::deleted(function($model) {
                    // ...  
                });
    
                $model_name::saving(function($model) {
                    // ...  
                });
    
                $model_name::saved(function($model) {
                    // ...  
                });
            }
        }
    
        /**
         * Register any application services.
         *
         * @return void
         */
        public function register()
        {
            //
        }
    }
    

    Hope it helps you write the less code possible!

    0 讨论(0)
  • 2020-12-07 13:46

    Recently I came to same problem in one of my Laravel 5 project, where I had to log all Model Events. I decided to use Traits. I created ModelEventLogger Trait and simply used in all Model class which needed to be logged. I am going to change it as per your need Which is given below.

    <?php
    
    namespace App\Traits;
    
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Support\Facades\Event;
    
    /**
     * Class ModelEventThrower 
     * @package App\Traits
     *
     *  Automatically throw Add, Update, Delete events of Model.
     */
    trait ModelEventThrower {
    
        /**
         * Automatically boot with Model, and register Events handler.
         */
        protected static function bootModelEventThrower()
        {
            foreach (static::getModelEvents() as $eventName) {
                static::$eventName(function (Model $model) use ($eventName) {
                    try {
                        $reflect = new \ReflectionClass($model);
                        Event::fire(strtolower($reflect->getShortName()).'.'.$eventName, $model);
                    } catch (\Exception $e) {
                        return true;
                    }
                });
            }
        }
    
        /**
         * Set the default events to be recorded if the $recordEvents
         * property does not exist on the model.
         *
         * @return array
         */
        protected static function getModelEvents()
        {
            if (isset(static::$recordEvents)) {
                return static::$recordEvents;
            }
    
            return [
                'created',
                'updated',
                'deleted',
            ];
        }
    } 
    

    Now you can use this trait in any Model you want to throw events for. In your case in Article Model.

    <?php namespace App;
    
    use App\Traits\ModelEventThrower;
    use Illuminate\Database\Eloquent\Model;
    
    class Article extends Model {
    
        use ModelEventThrower;
    
        //Just in case you want specific events to be fired for Article model
        //uncomment following line of code
    
       // protected static $recordEvents = ['created'];
    
    }
    

    Now in your app/Providers/EventServiceProvider.php, in boot() method register Event Handler for Article.

     public function boot(DispatcherContract $events)
     {
         parent::boot($events);
         $events->subscribe('App\Handlers\Events\ArticleEventHandler');
     }
    

    Now create Class ArticleEventHandler under app/Handlers/Events directory as below,

    <?php namespace App\Handlers\Events;
    
    use App\Article;
    
    class ArticleEventHandler{
    
        /**
         * Create the event handler.
         *
         * @return \App\Handlers\Events\ArticleEventHandler
         */
        public function __construct()
        {
            //
        }
    
        /**
        * Handle article.created event
        */
    
       public function created(Article $article)
       {
          //Implement logic
       }
    
       /**
       * Handle article.updated event
       */
    
       public function updated(Article $article)
       {
          //Implement logic
       }
    
      /**
      * Handle article.deleted event
      */
    
      public function deleted(Article $article)
      {
         //Implement logic
      }
    
     /**
     * @param $events
     */
     public function subscribe($events)
     {
         $events->listen('article.created',
                'App\Handlers\Events\ArticleEventHandler@created');
         $events->listen('article.updated',
                'App\Handlers\Events\ArticleEventHandler@updated');
         $events->listen('article.deleted',
                'App\Handlers\Events\ArticleEventHandler@deleted');
     }
    
    }
    

    As you can see from different answers, from different Users, there are more than 1 way of handling Model Events. There are also Custom events That can be created in Events folder and can be handled in Handler folder and can be dispatched from different places. I hope it helps.

    0 讨论(0)
  • 2020-12-07 13:47

    Laravel 6, the shortest solution

    BaseSubscriber class

    namespace App\Listeners;
    
    use Illuminate\Events\Dispatcher;
    use Illuminate\Support\Str;
    
    /**
     * Class BaseSubscriber
     * @package App\Listeners
     */
    abstract class BaseSubscriber
    {
    
        /**
         * Returns the first part of an event name (before the first dot)
         * Can be a class namespace
         * @return string
         */
        protected abstract function getEventSubject(): string;
    
        /**
         * Register the listeners for the subscriber.
         * @param Dispatcher $events
         */
        public function subscribe($events)
        {
            $currentNamespace = get_class($this);
            $eventSubject = strtolower(class_basename($this->getEventSubject()));
    
            foreach (get_class_methods($this) as $method) {
                if (Str::startsWith($method, 'handle')) {
                    $suffix = strtolower(Str::after($method, 'handle'));
                    $events->listen("$eventSubject.$suffix", "$currentNamespace@$method");
                }
            }
        }
    
    }
    

    OrderEventSubscriber class. Handlers for Order model events

    use App\Models\Order;
    
    /**
     * Class OrderEventSubscriber
     * @package App\Listeners
     */
    class OrderEventSubscriber extends BaseSubscriber
    {
    
        /**
         * @return string
         */
        protected function getEventSubject(): string
        {
            return Order::class; // Or just 'order'
        }
    
        /**
         * @param Order $order
         */
        public function handleSaved(Order $order)
        {
          // Handle 'saved' event
        }
    
        /**
         * @param Order $order
         */
        public function handleCreating(Order $order)
        {
           // Handle 'creating' event
        }
    
    }
    

    ModelEvents trait. It goes to your models, in my case - App\Model\Order

    namespace App\Traits;
    
    use Illuminate\Database\Eloquent\Model;
    
    /**
     * Trait ModelEvents
     * @package App\Traits
     */
    trait ModelEvents
    {
    
        /**
         * Register model events
         */
        protected static function bootModelEvents()
        {
            foreach (static::registerModelEvents() as $eventName) {
                static::$eventName(function (Model $model) use ($eventName) {
                    event(strtolower(class_basename(static::class)) . ".$eventName", $model);
                });
            }
        }
    
        /**
         * Returns an array of default registered model events
         * @return array
         */
        protected static function registerModelEvents(): array
        {
            return [
                'created',
                'updated',
                'deleted',
            ];
        }
    }
    

    Register the subscriber in a service provider, e.g AppServiceProvider

    /**
     * @param Dispatcher $events
     */
    public function boot(Dispatcher $events)
    {
        $events->subscribe(OrderEventSubscriber::class);
    }
    

    How just add the ModelEvents trait into your model, adjust the events you want to register instead of default ones:

    protected static function registerModelEvents(): array
        {
            return [
                'creating',
                'saved',
            ];
        }
    

    Done!

    0 讨论(0)
  • 2020-12-07 13:50

    In your case, you may also use following approach:

    // Put this code in your Article Model
    
    public static function boot() {
    
        parent::boot();
    
        static::created(function($article) {
            Event::fire('article.created', $article);
        });
    
        static::updated(function($article) {
            Event::fire('article.updated', $article);
        });
    
        static::deleted(function($article) {
            Event::fire('article.deleted', $article);
        });
    }
    

    Also, you need to register listeners in App\Providers\EventServiceProvider:

    protected $listen = [
        'article.created' => [
            'App\Handlers\Events\ArticleEvents@articleCreated',
        ],
        'article.updated' => [
            'App\Handlers\Events\ArticleEvents@articleUpdated',
        ],
        'article.deleted' => [
            'App\Handlers\Events\ArticleEvents@articleDeleted',
        ],
    ];
    

    Also make sure you have created the handlers in App\Handlers\Events folder/directory to handle that event. For example, article.created handler could be like this:

    <?php namespace App\Handlers\Events;
    
    use App\Article;
    use App\Services\Email\Mailer; // This one I use to email as a service class
    
    class ArticleEvents {
    
        protected $mailer = null;
    
        public function __construct(Mailer $mailer)
        {
            $this->mailer = $mailer;
        }
    
        public function articleCreated(Article $article)
        {
            // Implement mailer or use laravel mailer directly
            $this->mailer->notifyArticleCreated($article);
        }
    
        // Other Handlers/Methods...
    }
    
    0 讨论(0)
提交回复
热议问题