Laravel模型事件 - 我对它们的目的地有点困惑

时间:2015-05-19 17:19:06

标签: php laravel laravel-5

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

我有一个名为Article的模型。我希望在发生以下事件时发送电子邮件提醒:

  • 创建文章时
  • 文章更新时
  • 删除文章时

The docs说我可以使用模型事件并在boot()的{​​{1}}函数中注册它们。

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

  • 当我添加需要完整设置所有模型事件的App\Providers\EventServiceProviderComment等其他模型时会发生什么? Author的单boot()函数是否绝对是巨大的?
  • Laravel's 'other' Events的目的是什么?为什么我真的需要使用它们,如果我的事件只会响应模型CRUD操作?

我是Laravel的初学者,来自CodeIgniter,因此试图围绕正确的Laravel做事方式。谢谢你的建议!

9 个答案:

答案 0 :(得分:35)

在您的情况下,您还可以使用以下方法:

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

此外,您需要在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',
    ],
];

还要确保在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...
}

答案 1 :(得分:25)

最近我在我的一个Laravel 5项目中出现了同样的问题,我必须记录所有模型事件。我决定使用 self.viewControllers = [ ViewControllerA(), ViewControllerB(), ViewControllerC()] for viewController in viewControllers { if let vc = viewController as? UICollectionViewController, collectionView = vc.collectionView { collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomContentInset(), right: 0) } } 。我创建了Traits Trait,只是在需要记录的所有Model类中使用。我将根据您的需要对其进行更改,具体如下。

ModelEventLogger

现在,您可以在要为其投放事件的任何模型中使用此特征。在你的情况下<?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', ]; } } 模型。

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']; } app/Providers/EventServiceProvider.php方法中注册boot()的事件处理程序。

Article

现在在 public function boot(DispatcherContract $events) { parent::boot($events); $events->subscribe('App\Handlers\Events\ArticleEventHandler'); } 目录下创建类ArticleEventHandler,如下所示,

app/Handlers/Events

从不同的用户可以看出,不同的用户可以通过多种方式处理模型事件。还有自定义事件可以在事件文件夹中创建,可以在Handler文件夹中处理,可以从不同的地方分派。我希望它有所帮助。

答案 2 :(得分:9)

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

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.-创建一个新的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());
        });
  }

}

答案 3 :(得分:3)

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

以下是这些功能的文档:

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

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

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

  • 您创建一个提交给您的控制器的表单。
  • 您的控制器将生成的请求中的数据分派给命令。
  • 您的命令执行繁重 - 即在数据库中创建一个条目。
  • 然后,您的命令会触发事件处理程序可以选择的事件。
  • 您的事件处理程序会执行诸如发送电子邮件或更新其他内容之类的内容。

我喜欢这种模式有几个原因:从概念上讲,你的命令处理正在发生的事情,事件处理刚刚发生的事情。此外,您可以轻松地将命令和事件处理程序放入队列中以便稍后处理 - 这非常适合发送电子邮件,因为您不希望实时执行此操作,因为它们会缓慢降低HTTP请求。您还可以为单个事件提供多个事件处理程序,这对于分离问题非常有用。

这里很难提供任何实际代码作为您的问题更多关于Laravel的概念,所以我建议您查看这些视频,以便您了解这种模式的工作原理:

这个描述了命令总线:

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

这个描述了事件的运作方式:

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

答案 4 :(得分:2)

您可以选择Observer方法来处理模型事件。例如,这是我的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) {}
}

现在,如果我要创建一个产品模型,它的Observer将如下所示:

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

关于听众,我在observers.php目录中创建了一个Observers文件,并将其包含在AppServiceProvider中。以下是observers.php文件中的代码段:

<?php

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

所有这些都与Model Events有关。

如果您需要在创建记录后发送电子邮件,那么使用Laravel“其他”活动会更加清晰,因为您将有一个专门的课程来处理这个问题,并在您开始时解雇它希望来自财务主任。

“其他”活动将有更多的目的,因为您的应用程序变得越自动化,想想您在某些时候需要的所有日常cronjobs。除了“其他”活动之外,没有更清洁的方式来处理其他事件了。

答案 5 :(得分:1)

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

EventServiceProvider.php

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

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

或者您也可以使用$subscribe属性

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

有很多方法可以监听和处理事件。查看当前的主文档,了解更多方法(如使用闭包):Laravel Docs (master)this other answer

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

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

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

答案 6 :(得分:0)

您可以在一个事件中拥有多个侦听器。所以你可能有一个监听器在文章更新时发送电子邮件,但是你可以有一个完全不同的监听器来做一些完全不同的事情 - 它们都会被执行。

答案 7 :(得分:0)

我可能会在战斗结束后来到,但如果你不想要扩展类或创建特征的大惊小怪,你可能想试试这个文件探索解决方案。

Laravel 5.X解决方案

请注意,您选择提取模型的文件夹应仅包含使此解决方案有效的模型

不要忘记添加use File

应用程序/提供者/ 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()
    {
        //
    }
}

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

答案 8 :(得分:0)

Laravel 6,最短的解决方案

BaseSubscriber

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 类。 订购模型事件的处理程序

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 特征。对于您的模型,就我而言-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',
        ];
    }
}

在服务提供商(例如 AppServiceProvider

)中注册订阅者
/**
 * @param Dispatcher $events
 */
public function boot(Dispatcher $events)
{
    $events->subscribe(OrderEventSubscriber::class);
}

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

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

完成!