php Laravel 模型事件 - 我对它们的去向有点困惑

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30332117/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 21:43:12  来源:igfitidea点击:

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

phplaravellaravel-5

提问by Hyman

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

所以我认为一个好的 Laravel 应用程序应该是非常模型驱动和事件驱动的。

I have a Model called Article. I wish to send email alerts when the following events happen:

我有一个名为Article. 我希望在发生以下事件时发送电子邮件警报:

  • When an Article is created
  • When an Article is updated
  • When an Article is deleted
  • 创建文章时
  • 文章更新时
  • 删除文章时

The docssay I can use Model Events and register them within the boot()function of App\Providers\EventServiceProvider.

该文档说我可以用模型事件和内注册它们boot()的功能App\Providers\EventServiceProvider

But this is confusing me because...

但这让我感到困惑,因为...

  • What happens when I add further models like Commentor Authorthat need full sets of all their own Model Events? Will the single boot()function of EventServiceProviderjust be absolutely huge?
  • What is the purpose of Laravel's 'other' Events? Why would I ever need to use them if realistically my events will only respond to Model CRUD actions?
  • 当我添加更多模型时会发生什么,比如CommentAuthor需要全套自己的模型事件?just的单一boot()功能EventServiceProvider会绝对庞大吗?
  • Laravel 的“其他”事件的目的是什么?如果实际上我的事件只会响应模型 CRUD 操作,为什么我还需要使用它们?

I am a beginner at Laravel, having come from CodeIgniter, so trying to wrap my head around the proper Laravel way of doing things. Thanks for your advice!

我是 Laravel 的初学者,来自 CodeIgniter,所以我试图围绕 Laravel 的正确做事方式进行思考。谢谢你的建议!

采纳答案by pinkal vansia

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 ModelEventLoggerTrait 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.

最近我在我的 Laravel 5 项目之一中遇到了同样的问题,我必须记录所有模型事件。我决定使用Traits. 我创建了ModelEventLoggerTrait 并简单地用于所有需要记录的 Model 类。我将根据您的需要更改它,如下所示。

<?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 ArticleModel.

现在,您可以在要为其抛出事件的任何模型中使用此特征。在您的Article模型中。

<?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.

现在在您app/Providers/EventServiceProvider.phpboot()方法中注册事件处理程序Article

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

Now create Class ArticleEventHandlerunder app/Handlers/Eventsdirectory as below,

现在ArticleEventHandlerapp/Handlers/Events目录下创建类,如下所示,

<?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.

正如您从不同的答案中看到的,来自不同的用户,处理模型事件的方法不止一种。还有自定义事件,可以在 Events 文件夹中创建,可以在 Handler 文件夹中处理,可以从不同的地方调度。我希望它有帮助。

回答by The Alpha

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:

此外,您需要在以下位置注册侦听器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\Eventsfolder/directory to handle that event. For example, article.createdhandler could be like this:

还要确保您已经在App\Handlers\Events文件夹/目录中创建了处理程序来处理该事件。例如,article.created处理程序可能是这样的:

<?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...
}

回答by Borjante

I found this the cleanest way to do what you want.

我发现这是做你想做的最干净的方式。

1.- Create an observer for the model (ArticleObserver)

1.- 为模型创建一个观察者(ArticleObserver)

use App\Article;

class ArticleObserver{

  public function __construct(Article $articles){
    $this->articles = $articles
  }

  public function created(Article $article){
    // Do anything you want to do, $article is the newly created article
  }

}

2.- Create a new ServiceProvider (ObserversServiceProvider), remember to add it to you config/app.php

2.- 创建一个新的ServiceProvider(ObserversServiceProvider),记得添加到你的config/app.php

use App\Observers\ArticleObserver;
use App\Article;
use Illuminate\Support\ServiceProvider;

class ObserversServiceProvider extends ServiceProvider
{

  public function boot()
  {
    Article::observe($this->app->make(ArticleObserver::class));
  }

  public function register()
  {
    $this->app->bindShared(ArticleObserver::class, function()
        {
            return new ArticleObserver(new Article());
        });
  }

}

回答by user2094178

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

您可以选择观察者方法来处理模型事件。例如,这是我的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.phpfile inside Observersdir and I include it from the AppServiceProvider. Here is a snippet from within the observers.phpfile:

关于侦听器,我observers.phpObserversdir 中创建了一个文件,并将它从AppServiceProvider. 这是observers.php文件中的一个片段:

<?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.

所有这些都与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.

如果您需要在创建记录后发送电子邮件,使用 Laravel 的“其他”事件会更干净,因为您将有一个专门的类来处理这个问题,并在您愿意时从控制器。

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.

随着您的应用程序变得更加自动化,“其他”事件将具有更多目的,想想您在某个时候需要的所有日常 cronjobs。除了“其他”事件之外,将没有更简洁的方法来处理它。

回答by edcs

You've tagged this question as Laravel 5, so I would suggest not using model events as you'll end up with lots of extra code in your models which may make things difficult to manage in future. Instead, my recommendation would be to make use of the command bus and events.

您已将此问题标记为 Laravel 5,因此我建议不要使用模型事件,因为您最终会在模型中产生大量额外代码,这可能会使将来难以管理。相反,我的建议是使用命令总线和事件。

Here's the docs for those features:

以下是这些功能的文档:

http://laravel.com/docs/5.0/bus

http://laravel.com/docs/5.0/bus

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

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

My recommendation would be to use the following pattern.

我的建议是使用以下模式。

  • You create a form which submits to your controller.
  • Your controller dispatches the data from the request generated to a command.
  • Your command does the heavy lifting - i.e. creates an entry in the database.
  • Your command then fires an event which canbe picked up by an event handler.
  • Your event handler does something like send an email or update something else.
  • 您创建一个提交给您的控制器的表单。
  • 您的控制器将生成的请求中的数据分派给命令。
  • 您的命令完成了繁重的工作 - 即在数据库中创建一个条目。
  • 你的命令,则触发一个事件可以通过事件处理程序被拾起。
  • 您的事件处理程序会执行一些操作,例如发送电子邮件或更新其他内容。

There are a few reasons why I like this pattern: Conceptually your commands handle things that are happening right now and events handle things that have just happened. Also, you can easily put command and event handlers onto a queue to be processed later on - this is great for sending emails as you tend not to want to do that in real time as they slow the HTTP request down a fair bit. You can also have multiple event handlers for a single event which is great for separating concerns.

我喜欢这种模式有几个原因:从概念上讲,您的命令处理正在发生的事情,而事件处理刚刚发生的事情。此外,您可以轻松地将命令和事件处理程序放入队列以供稍后处理 - 这对于发送电子邮件非常有用,因为您往往不想实时发送电子邮件,因为它们会降低 HTTP 请求的速度。您还可以为单个事件设置多个事件处理程序,这非常适合分离关注点。

It would be difficult to provide any actual code here as your question more about the concepts of Laravel, so I'd recommend viewing these videos so you get a good idea of how this pattern works:

很难在此处提供任何实际代码作为您关于 Laravel 概念的更多问题,因此我建议您观看这些视频,以便您对这种模式的工作原理有一个很好的了解:

This one describes the command bus:

这个描述了命令总线:

https://laracasts.com/lessons/laravel-5-events

https://laracasts.com/lessons/laravel-5-events

This one describes how events work:

这个描述了事件的工作原理:

https://laracasts.com/lessons/laravel-5-commands

https://laracasts.com/lessons/laravel-5-commands

回答by Martin Bean

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.

一个事件可以有多个侦听器。因此,您可能有一个侦听器在文章更新时发送电子邮件,但您可能有一个完全不同的侦听器来执行完全不同的操作——它们都将被执行。

回答by Ezequiel Moreno

1) You may create an event listener for each new Model (ArticleEventSubscriber,CommentEventSubscriber) at boot method:

1) 您可以在启动方法中为每个新模型 (ArticleEventSubscriber,CommentEventSubscriber) 创建一个事件侦听器:

EventServiceProvider.php

事件服务提供者.php

public function boot(DispatcherContract $events)
{
    parent::boot($events);

    $events->subscribe('App\Listeners\ArticleEventListener');
    $events->subscribe('App\Listeners\CommentEventListener');
}

or you may also use $subscribeproperty

或者你也可以使用$subscribe财产

protected $subscribe = [
        'App\Listeners\ArticleEventListener',
        'App\Listeners\CommentEventListener',
    ];

There are many ways to listen and handle events. Take a look to current master documentation for discovering more ways(like usings closures) to do so : Laravel Docs (master)and this other answer

有很多方法可以监听和处理事件。查看当前的主文档以发现更多方法(例如使用闭包):Laravel Docs(master)其他答案

2) Model events are just events provided by default by Eloquent.

2) 模型事件只是 Eloquent 默认提供的事件。

https://github.com/illuminate/database/blob/491d58b5cc4149fa73cf93d499efb292cd11c88d/Eloquent/Model.php#L1171

https://github.com/illuminate/database/blob/491d58b5cc4149fa73cf93d499efb292cd11c88d/Eloquent/Model.php#L1171

https://github.com/illuminate/database/blob/491d58b5cc4149fa73cf93d499efb292cd11c88d/Eloquent/Model.php#L1273

https://github.com/illuminate/database/blob/491d58b5cc4149fa73cf93d499efb292cd11c88d/Eloquent/Model.php#L1273

回答by Anwar

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

Laravel 5.X 解决方案

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

不要忘记添加 use File

app/Providers/AppServiceProvider.php

应用程序/提供商/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!

希望它可以帮助您编写尽可能少的代码!

回答by ymakux

Laravel 6, the shortest solution

Laravel 6,最短的解决方案

BaseSubscriberclass

基本订阅者

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");
            }
        }
    }

}

OrderEventSubscriberclass. Handlers for Ordermodel events

OrderEventSubscriber类。订单模型事件的处理程序

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
    }

}

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

模型事件特征。它适用于您的模型,在我的情况下 - 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

在服务提供者中注册订阅者,例如AppServiceProvider

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

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

如何将ModelEvents特征添加到您的模型中,调整您要注册的事件而不是默认事件:

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

Done!

完毕!